diff --git a/src/addon/pushnotifications/providers/pushnotifications.ts b/src/addon/pushnotifications/providers/pushnotifications.ts index 46be2c89e..adb48e419 100644 --- a/src/addon/pushnotifications/providers/pushnotifications.ts +++ b/src/addon/pushnotifications/providers/pushnotifications.ts @@ -21,7 +21,7 @@ import { TranslateService } from '@ngx-translate/core'; import { CoreAppProvider } from '@providers/app'; import { CoreInitDelegate } from '@providers/init'; import { CoreLoggerProvider } from '@providers/logger'; -import { CoreSitesProvider } from '@providers/sites'; +import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites'; import { AddonPushNotificationsDelegate } from './delegate'; import { CoreLocalNotificationsProvider } from '@providers/local-notifications'; import { CoreUtilsProvider } from '@providers/utils/utils'; @@ -31,6 +31,54 @@ import { CoreConstants } from '@core/constants'; import { CoreConfigConstants } from '../../../configconstants'; import { ILocalNotification } from '@ionic-native/local-notifications'; import { SQLiteDBTableSchema } from '@classes/sqlitedb'; +import { CoreSite } from '@classes/site'; + +/** + * Data needed to register a device in a Moodle site. + */ +export interface AddonPushNotificationsRegisterData { + /** + * App ID. + * @type {string} + */ + appid: string; + + /** + * Device UUID. + * @type {string} + */ + uuid: string; + + /** + * Device name. + * @type {string} + */ + name: string; + + /** + * Device model. + * @type {string} + */ + model: string; + + /** + * Device platform. + * @type {string} + */ + platform: string; + + /** + * Device version. + * @type {string} + */ + version: string; + + /** + * Push ID. + * @type {string} + */ + pushid: string; +} /** * Service to handle push notifications. @@ -44,7 +92,8 @@ export class AddonPushNotificationsProvider { // Variables for database. static BADGE_TABLE = 'addon_pushnotifications_badge'; - protected tablesSchema: SQLiteDBTableSchema[] = [ + static REGISTERED_DEVICES_TABLE = 'addon_pushnotifications_registered_devices'; + protected appTablesSchema: SQLiteDBTableSchema[] = [ { name: AddonPushNotificationsProvider.BADGE_TABLE, columns: [ @@ -64,6 +113,46 @@ export class AddonPushNotificationsProvider { primaryKeys: ['siteid', 'addon'] } ]; + protected siteSchema: CoreSiteSchema = { + name: 'AddonPushNotificationsProvider', + version: 1, + tables: [ + { + name: AddonPushNotificationsProvider.REGISTERED_DEVICES_TABLE, + columns: [ + { + name: 'appid', + type: 'TEXT', + }, + { + name: 'uuid', + type: 'TEXT' + }, + { + name: 'name', + type: 'TEXT' + }, + { + name: 'model', + type: 'TEXT' + }, + { + name: 'platform', + type: 'TEXT' + }, + { + name: 'version', + type: 'TEXT' + }, + { + name: 'pushid', + type: 'TEXT' + }, + ], + primaryKeys: ['appid', 'uuid'] + } + ], + }; constructor(logger: CoreLoggerProvider, protected appProvider: CoreAppProvider, private initDelegate: CoreInitDelegate, protected pushNotificationsDelegate: AddonPushNotificationsDelegate, protected sitesProvider: CoreSitesProvider, @@ -73,7 +162,8 @@ export class AddonPushNotificationsProvider { private translate: TranslateService, private platform: Platform) { this.logger = logger.getInstance('AddonPushNotificationsProvider'); this.appDB = appProvider.getDB(); - this.appDB.createTablesFromSchema(this.tablesSchema); + this.appDB.createTablesFromSchema(this.appTablesSchema); + this.sitesProvider.registerSiteSchema(this.siteSchema); platform.ready().then(() => { // Create the default channel. @@ -150,6 +240,23 @@ export class AddonPushNotificationsProvider { return this.pushID; } + /** + * Get data to register the device in Moodle. + * + * @return {AddonPushNotificationsRegisterData} Data. + */ + protected getRegisterData(): AddonPushNotificationsRegisterData { + return { + appid: CoreConfigConstants.app_id, + name: this.device.manufacturer || '', + model: this.device.model, + platform: this.device.platform + '-fcm', + version: this.device.version, + pushid: this.pushID, + uuid: this.device.uuid + }; + } + /** * Get Sitebadge counter from the database. * @@ -227,10 +334,10 @@ export class AddonPushNotificationsProvider { /** * Unregisters a device from a certain Moodle site. * - * @param {any} site Site to unregister from. - * @return {Promise} Promise resolved when device is unregistered. + * @param {CoreSite} site Site to unregister from. + * @return {Promise} Promise resolved when device is unregistered. */ - unregisterDeviceOnMoodle(site: any): Promise { + unregisterDeviceOnMoodle(site: CoreSite): Promise { if (!site || !this.appProvider.isMobile()) { return Promise.reject(null); } @@ -246,6 +353,12 @@ export class AddonPushNotificationsProvider { if (!response || !response.removed) { return Promise.reject(null); } + + // Remove the device from the local DB. + return site.getDb().deleteRecords(AddonPushNotificationsProvider.REGISTERED_DEVICES_TABLE, this.getRegisterData()) + .catch(() => { + // Ignore errors. + }); }); } @@ -388,28 +501,54 @@ export class AddonPushNotificationsProvider { } /** - * Registers a device on current Moodle site. + * Registers a device on a Moodle site if needed. * - * @return {Promise} Promise resolved when device is registered. + * @param {string} [siteId] Site ID. If not defined, current site. + * @param {boolean} [forceUnregister] Whether to force unregister and register. + * @return {Promise} Promise resolved when device is registered. */ - registerDeviceOnMoodle(): Promise { + registerDeviceOnMoodle(siteId?: string, forceUnregister?: boolean): Promise { this.logger.debug('Register device on Moodle.'); - if (!this.sitesProvider.isLoggedIn() || !this.pushID || !this.appProvider.isMobile()) { + if (!this.pushID || !this.appProvider.isMobile()) { return Promise.reject(null); } - const data = { - appid: CoreConfigConstants.app_id, - name: this.device.manufacturer || '', - model: this.device.model, - platform: this.device.platform + '-fcm', - version: this.device.version, - pushid: this.pushID, - uuid: this.device.uuid - }; + const data = this.getRegisterData(); + let result, + site: CoreSite; + + return this.sitesProvider.getSite(siteId).then((s) => { + site = s; + + if (forceUnregister) { + return {unregister: true, register: true}; + } else { + // Check if the device is already registered. + return this.shouldRegister(data, site); + } + }).then((res) => { + result = res; + + if (result.unregister) { + // Unregister the device first. + return this.unregisterDeviceOnMoodle(site).catch(() => { + // Ignore errors. + }); + } + }).then(() => { + if (result.register) { + // Now register the device. + return site.write('core_user_add_user_device', this.utils.clone(data)).then((response) => { + // Insert the device in the local DB. + return site.getDb().insertRecord(AddonPushNotificationsProvider.REGISTERED_DEVICES_TABLE, data) + .catch((error) => { + // Ignore errors. + }); + }); + } + }); - return this.sitesProvider.getCurrentSite().write('core_user_add_user_device', data); } /** @@ -448,4 +587,58 @@ export class AddonPushNotificationsProvider { return value; }); } + + /** + * Check if device should be registered (and unregistered first). + * + * @param {AddonPushNotificationsRegisterData} data Data of the device. + * @param {CoreSite} site Site to use. + * @return {Promise<{register: boolean, unregister: boolean}>} Promise resolved with booleans: whether to register/unregister. + */ + protected shouldRegister(data: AddonPushNotificationsRegisterData, site: CoreSite) + : Promise<{register: boolean, unregister: boolean}> { + + // Check if the device is already registered. + return site.getDb().getRecords(AddonPushNotificationsProvider.REGISTERED_DEVICES_TABLE, { + appid: data.appid, + uuid: data.uuid + }).catch(() => { + // Ignore errors. + return []; + }).then((records: AddonPushNotificationsRegisterData[]) => { + let isStored = false, + versionOrPushChanged = false; + + records.forEach((record) => { + if (record.name == data.name && record.model == data.model && record.platform == data.platform) { + if (record.version == data.version && record.pushid == data.pushid) { + // The device is already stored. + isStored = true; + } else { + // The version or pushid has changed. + versionOrPushChanged = true; + } + } + }); + + if (isStored) { + // The device has already been registered, no need to register it again. + return { + register: false, + unregister: false + }; + } else if (versionOrPushChanged) { + // This data can be updated by calling register WS, no need to call unregister. + return { + register: true, + unregister: false + }; + } else { + return { + register: true, + unregister: true + }; + } + }); + } } diff --git a/src/addon/pushnotifications/providers/register-cron-handler.ts b/src/addon/pushnotifications/providers/register-cron-handler.ts new file mode 100644 index 000000000..fd3ea87ff --- /dev/null +++ b/src/addon/pushnotifications/providers/register-cron-handler.ts @@ -0,0 +1,71 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { CoreCronHandler } from '@providers/cron'; +import { AddonPushNotificationsProvider } from './pushnotifications'; + +/** + * Cron handler to force a register on a Moodle site when a site is manually synchronized. + */ +@Injectable() +export class AddonPushNotificationsRegisterCronHandler implements CoreCronHandler { + name = 'AddonPushNotificationsRegisterCronHandler'; + + constructor(private pushNotificationsProvider: AddonPushNotificationsProvider) {} + + /** + * Check whether the sync can be executed manually. Call isSync if not defined. + * + * @return {boolean} Whether the sync can be executed manually. + */ + canManualSync(): boolean { + return true; // Execute the handler when the site is manually synchronized. + } + + /** + * Execute the process. + * Receives the ID of the site affected, undefined for all sites. + * + * @param {string} [siteId] ID of the site affected, undefined for all sites. + * @return {Promise} Promise resolved when done, rejected if failure. + */ + execute(siteId?: string): Promise { + if (!siteId) { + // It's not a specific site, don't do anything. + return Promise.resolve(); + } + + // Register the device again. + return this.pushNotificationsProvider.registerDeviceOnMoodle(siteId, true); + } + + /** + * Get the time between consecutive executions. + * + * @return {number} Time between consecutive executions (in ms). + */ + getInterval(): number { + return 86400000; // 1 day. We won't do anything with automatic execution, so use a big number. + } + + /** + * Check whether it's a synchronization process or not. True if not defined. + * + * @return {boolean} Whether it's a synchronization process or not. + */ + isSync(): boolean { + return false; + } +} diff --git a/src/addon/pushnotifications/pushnotifications.module.ts b/src/addon/pushnotifications/pushnotifications.module.ts index 6de574ecc..9ae4bde6a 100644 --- a/src/addon/pushnotifications/pushnotifications.module.ts +++ b/src/addon/pushnotifications/pushnotifications.module.ts @@ -16,6 +16,8 @@ import { NgModule } from '@angular/core'; import { Platform } from 'ionic-angular'; import { AddonPushNotificationsProvider } from './providers/pushnotifications'; import { AddonPushNotificationsDelegate } from './providers/delegate'; +import { AddonPushNotificationsRegisterCronHandler } from './providers/register-cron-handler'; +import { CoreCronDelegate } from '@providers/cron'; import { CoreEventsProvider } from '@providers/events'; import { CoreLoggerProvider } from '@providers/logger'; import { CoreLocalNotificationsProvider } from '@providers/local-notifications'; @@ -34,16 +36,21 @@ export const ADDON_PUSHNOTIFICATIONS_PROVIDERS: any[] = [ ], providers: [ AddonPushNotificationsProvider, - AddonPushNotificationsDelegate + AddonPushNotificationsDelegate, + AddonPushNotificationsRegisterCronHandler ] }) export class AddonPushNotificationsModule { constructor(platform: Platform, pushNotificationsProvider: AddonPushNotificationsProvider, eventsProvider: CoreEventsProvider, localNotificationsProvider: CoreLocalNotificationsProvider, loggerProvider: CoreLoggerProvider, - updateManager: CoreUpdateManagerProvider) { + updateManager: CoreUpdateManagerProvider, cronDelegate: CoreCronDelegate, + registerCronHandler: AddonPushNotificationsRegisterCronHandler) { const logger = loggerProvider.getInstance('AddonPushNotificationsModule'); + // Register the handlers. + cronDelegate.register(registerCronHandler); + // Register device on GCM or APNS server. platform.ready().then(() => { pushNotificationsProvider.registerDevice();