2018-02-23 14:44:17 +01:00
|
|
|
// (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.
|
|
|
|
|
2018-06-21 15:59:06 +02:00
|
|
|
import { Injectable, NgZone } from '@angular/core';
|
2018-09-07 10:20:40 +02:00
|
|
|
import { TranslateService } from '@ngx-translate/core';
|
2018-02-27 08:39:45 +01:00
|
|
|
import { Badge } from '@ionic-native/badge';
|
|
|
|
import { Push, PushObject, PushOptions } from '@ionic-native/push';
|
|
|
|
import { Device } from '@ionic-native/device';
|
2018-02-23 14:44:17 +01:00
|
|
|
import { CoreAppProvider } from '@providers/app';
|
2018-09-19 10:54:41 +02:00
|
|
|
import { CoreInitDelegate } from '@providers/init';
|
2018-02-23 14:44:17 +01:00
|
|
|
import { CoreLoggerProvider } from '@providers/logger';
|
|
|
|
import { CoreSitesProvider } from '@providers/sites';
|
|
|
|
import { AddonPushNotificationsDelegate } from './delegate';
|
2018-02-27 08:39:45 +01:00
|
|
|
import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
|
|
|
|
import { CoreUtilsProvider } from '@providers/utils/utils';
|
|
|
|
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
|
|
|
import { CoreConfigProvider } from '@providers/config';
|
2018-04-09 11:44:12 +02:00
|
|
|
import { CoreConstants } from '@core/constants';
|
2018-03-12 15:44:41 +01:00
|
|
|
import { CoreConfigConstants } from '../../../configconstants';
|
2018-02-23 14:44:17 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Service to handle push notifications.
|
|
|
|
*/
|
|
|
|
@Injectable()
|
|
|
|
export class AddonPushNotificationsProvider {
|
|
|
|
protected logger;
|
|
|
|
protected pushID: string;
|
|
|
|
protected appDB: any;
|
2018-03-08 13:25:20 +01:00
|
|
|
static COMPONENT = 'AddonPushNotificationsProvider';
|
2018-02-23 14:44:17 +01:00
|
|
|
|
|
|
|
// Variables for database.
|
2018-05-30 12:13:39 +02:00
|
|
|
static BADGE_TABLE = 'addon_pushnotifications_badge';
|
2018-02-23 14:44:17 +01:00
|
|
|
protected tablesSchema = [
|
|
|
|
{
|
2018-05-30 12:13:39 +02:00
|
|
|
name: AddonPushNotificationsProvider.BADGE_TABLE,
|
2018-02-23 14:44:17 +01:00
|
|
|
columns: [
|
|
|
|
{
|
|
|
|
name: 'siteid',
|
2018-03-08 13:25:20 +01:00
|
|
|
type: 'TEXT'
|
2018-02-23 14:44:17 +01:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: 'addon',
|
|
|
|
type: 'TEXT'
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: 'number',
|
|
|
|
type: 'INTEGER'
|
|
|
|
}
|
2018-02-27 08:39:45 +01:00
|
|
|
],
|
|
|
|
primaryKeys: ['siteid', 'addon']
|
2018-02-23 14:44:17 +01:00
|
|
|
}
|
|
|
|
];
|
|
|
|
|
2018-09-19 10:54:41 +02:00
|
|
|
constructor(logger: CoreLoggerProvider, protected appProvider: CoreAppProvider, private initDelegate: CoreInitDelegate,
|
2018-02-27 08:39:45 +01:00
|
|
|
protected pushNotificationsDelegate: AddonPushNotificationsDelegate, protected sitesProvider: CoreSitesProvider,
|
|
|
|
private badge: Badge, private localNotificationsProvider: CoreLocalNotificationsProvider,
|
|
|
|
private utils: CoreUtilsProvider, private textUtils: CoreTextUtilsProvider, private push: Push,
|
2018-09-07 10:20:40 +02:00
|
|
|
private configProvider: CoreConfigProvider, private device: Device, private zone: NgZone,
|
|
|
|
private translate: TranslateService) {
|
2018-02-23 14:44:17 +01:00
|
|
|
this.logger = logger.getInstance('AddonPushNotificationsProvider');
|
|
|
|
this.appDB = appProvider.getDB();
|
|
|
|
this.appDB.createTablesFromSchema(this.tablesSchema);
|
|
|
|
}
|
|
|
|
|
2018-02-27 08:39:45 +01:00
|
|
|
/**
|
|
|
|
* Delete all badge records for a given site.
|
|
|
|
*
|
|
|
|
* @param {string} siteId Site ID.
|
|
|
|
* @return {Promise<any>} Resolved when done.
|
|
|
|
*/
|
|
|
|
cleanSiteCounters(siteId: string): Promise<any> {
|
2018-05-30 12:13:39 +02:00
|
|
|
return this.appDB.deleteRecords(AddonPushNotificationsProvider.BADGE_TABLE, {siteid: siteId} ).finally(() => {
|
2018-02-27 08:39:45 +01:00
|
|
|
this.updateAppCounter();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-03-08 13:25:20 +01:00
|
|
|
* Returns options for push notifications based on device.
|
|
|
|
*
|
|
|
|
* @return {Promise<PushOptions>} Promise with the push options resolved when done.
|
2018-02-27 08:39:45 +01:00
|
|
|
*/
|
|
|
|
protected getOptions(): Promise<PushOptions> {
|
2018-04-09 11:44:12 +02:00
|
|
|
return this.configProvider.get(CoreConstants.SETTINGS_NOTIFICATION_SOUND, true).then((soundEnabled) => {
|
2018-02-27 08:39:45 +01:00
|
|
|
return {
|
2018-03-08 13:25:20 +01:00
|
|
|
android: {
|
|
|
|
senderID: CoreConfigConstants.gcmpn,
|
|
|
|
sound: !!soundEnabled
|
|
|
|
},
|
|
|
|
ios: {
|
|
|
|
alert: 'true',
|
|
|
|
badge: true,
|
|
|
|
sound: !!soundEnabled
|
|
|
|
},
|
|
|
|
windows: {
|
|
|
|
sound: !!soundEnabled
|
|
|
|
}
|
2018-02-27 08:39:45 +01:00
|
|
|
};
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-02-23 14:44:17 +01:00
|
|
|
/**
|
|
|
|
* Get the pushID for this device.
|
|
|
|
*
|
|
|
|
* @return {string} Push ID.
|
|
|
|
*/
|
|
|
|
getPushId(): string {
|
|
|
|
return this.pushID;
|
|
|
|
}
|
|
|
|
|
2018-02-27 08:39:45 +01:00
|
|
|
/**
|
|
|
|
* Get Sitebadge counter from the database.
|
|
|
|
*
|
|
|
|
* @param {string} siteId Site ID.
|
|
|
|
* @return {Promise<any>} Promise resolved with the stored badge counter for the site.
|
|
|
|
*/
|
|
|
|
getSiteCounter(siteId: string): Promise<any> {
|
|
|
|
return this.getAddonBadge(siteId);
|
|
|
|
}
|
|
|
|
|
2018-02-23 14:44:17 +01:00
|
|
|
/**
|
|
|
|
* Function called when a push notification is clicked. Redirect the user to the right state.
|
|
|
|
*
|
|
|
|
* @param {any} notification Notification.
|
|
|
|
*/
|
|
|
|
notificationClicked(notification: any): void {
|
2018-09-19 10:54:41 +02:00
|
|
|
this.initDelegate.ready().then(() => {
|
2018-02-23 14:44:17 +01:00
|
|
|
this.pushNotificationsDelegate.clicked(notification);
|
|
|
|
});
|
|
|
|
}
|
2018-02-27 08:39:45 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* This function is called when we receive a Notification from APNS or a message notification from GCM.
|
|
|
|
* The app can be in foreground or background,
|
|
|
|
* if we are in background this code is executed when we open the app clicking in the notification bar.
|
|
|
|
*
|
|
|
|
* @param {any} notification Notification received.
|
|
|
|
*/
|
|
|
|
onMessageReceived(notification: any): void {
|
|
|
|
const data = notification ? notification.additionalData : {};
|
|
|
|
|
|
|
|
this.sitesProvider.getSite(data.site).then(() => {
|
|
|
|
if (this.utils.isTrueOrOne(data.foreground)) {
|
|
|
|
// If the app is in foreground when the notification is received, it's not shown. Let's show it ourselves.
|
|
|
|
if (this.localNotificationsProvider.isAvailable()) {
|
|
|
|
const localNotif = {
|
|
|
|
id: 1,
|
|
|
|
at: new Date(),
|
2018-09-07 10:20:40 +02:00
|
|
|
channelParams: {
|
|
|
|
channelID: 'notifications',
|
|
|
|
channelName: this.translate.instant('addon.notifications.notifications'),
|
|
|
|
importance: 4 // IMPORTANCE_HIGH
|
|
|
|
},
|
2018-02-27 08:39:45 +01:00
|
|
|
data: {
|
|
|
|
notif: data.notif,
|
|
|
|
site: data.site
|
|
|
|
},
|
|
|
|
title: '',
|
|
|
|
text: ''
|
|
|
|
},
|
|
|
|
promises = [];
|
|
|
|
|
|
|
|
// Apply formatText to title and message.
|
|
|
|
promises.push(this.textUtils.formatText(notification.title, true, true).then((formattedTitle) => {
|
|
|
|
localNotif.title = formattedTitle;
|
|
|
|
}).catch(() => {
|
|
|
|
localNotif.title = notification.title;
|
|
|
|
}));
|
|
|
|
|
|
|
|
promises.push(this.textUtils.formatText(notification.message, true, true).then((formattedMessage) => {
|
|
|
|
localNotif.text = formattedMessage;
|
|
|
|
}).catch(() => {
|
|
|
|
localNotif.text = notification.message;
|
|
|
|
}));
|
|
|
|
|
|
|
|
Promise.all(promises).then(() => {
|
|
|
|
this.localNotificationsProvider.schedule(localNotif, AddonPushNotificationsProvider.COMPONENT, data.site);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Trigger a notification received event.
|
2018-09-19 10:54:41 +02:00
|
|
|
this.initDelegate.ready().then(() => {
|
2018-02-27 08:39:45 +01:00
|
|
|
data.title = notification.title;
|
|
|
|
data.message = notification.message;
|
|
|
|
this.pushNotificationsDelegate.received(data);
|
|
|
|
});
|
|
|
|
} else {
|
2018-03-01 16:55:49 +01:00
|
|
|
// The notification was clicked.
|
|
|
|
// For compatibility with old push plugin implementation we'll merge all the notification data in a single object.
|
2018-02-27 08:39:45 +01:00
|
|
|
data.title = notification.title;
|
|
|
|
data.message = notification.message;
|
|
|
|
this.notificationClicked(data);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Unregisters a device from a certain Moodle site.
|
|
|
|
*
|
|
|
|
* @param {any} site Site to unregister from.
|
|
|
|
* @return {Promise<any>} Promise resolved when device is unregistered.
|
|
|
|
*/
|
|
|
|
unregisterDeviceOnMoodle(site: any): Promise<any> {
|
|
|
|
if (!site || !this.appProvider.isMobile()) {
|
|
|
|
return Promise.reject(null);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.logger.debug(`Unregister device on Moodle: '${site.id}'`);
|
|
|
|
|
|
|
|
const data = {
|
|
|
|
appid: CoreConfigConstants.app_id,
|
|
|
|
uuid: this.device.uuid
|
|
|
|
};
|
|
|
|
|
|
|
|
return site.write('core_user_remove_user_device', data).then((response) => {
|
|
|
|
if (!response || !response.removed) {
|
|
|
|
return Promise.reject(null);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Update Counter for an addon. It will update the refered siteId counter and the total badge.
|
|
|
|
* It will return the updated addon counter.
|
|
|
|
*
|
|
|
|
* @param {string} addon Registered addon name to set the badge number.
|
|
|
|
* @param {number} value The number to be stored.
|
|
|
|
* @param {string} [siteId] Site ID. If not defined, use current site.
|
|
|
|
* @return {Promise<any>} Promise resolved with the stored badge counter for the addon on the site.
|
|
|
|
*/
|
|
|
|
updateAddonCounter(addon: string, value: number, siteId?: string): Promise<any> {
|
|
|
|
if (this.pushNotificationsDelegate.isCounterHandlerRegistered(addon)) {
|
|
|
|
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
|
|
|
|
|
|
|
return this.saveAddonBadge(value, siteId, addon).then(() => {
|
|
|
|
return this.updateSiteCounter(siteId).then(() => {
|
|
|
|
return value;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return Promise.resolve(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Update total badge counter of the app.
|
|
|
|
*
|
|
|
|
* @return {Promise<any>} Promise resolved with the stored badge counter for the site.
|
|
|
|
*/
|
|
|
|
updateAppCounter(): Promise<any> {
|
|
|
|
return this.sitesProvider.getSitesIds().then((sites) => {
|
|
|
|
const promises = [];
|
|
|
|
sites.forEach((siteId) => {
|
|
|
|
promises.push(this.getAddonBadge(siteId));
|
|
|
|
});
|
|
|
|
|
|
|
|
return Promise.all(promises).then((counters) => {
|
|
|
|
const total = counters.reduce((previous, counter) => {
|
2018-03-08 13:25:20 +01:00
|
|
|
// The app badge counter does not support strings, so parse to int before.
|
|
|
|
return previous + parseInt(counter, 10);
|
|
|
|
}, 0);
|
2018-02-27 08:39:45 +01:00
|
|
|
|
2018-05-07 10:08:24 +02:00
|
|
|
if (!this.appProvider.isDesktop() && !this.appProvider.isMobile()) {
|
|
|
|
// Browser doesn't have an app badge, stop.
|
|
|
|
return total;
|
|
|
|
}
|
|
|
|
|
2018-02-27 08:39:45 +01:00
|
|
|
// Set the app badge.
|
|
|
|
return this.badge.set(total).then(() => {
|
|
|
|
return total;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Update counter for a site using the stored addon data. It will update the total badge application number.
|
|
|
|
* It will return the updated site counter.
|
|
|
|
*
|
|
|
|
* @param {string} siteId Site ID.
|
|
|
|
* @return {Promise<any>} Promise resolved with the stored badge counter for the site.
|
|
|
|
*/
|
|
|
|
updateSiteCounter(siteId: string): Promise<any> {
|
|
|
|
const addons = this.pushNotificationsDelegate.getCounterHandlers(),
|
|
|
|
promises = [];
|
|
|
|
|
2018-03-01 16:55:49 +01:00
|
|
|
for (const x in addons) {
|
|
|
|
promises.push(this.getAddonBadge(siteId, addons[x]));
|
|
|
|
}
|
2018-02-27 08:39:45 +01:00
|
|
|
|
|
|
|
return Promise.all(promises).then((counters) => {
|
|
|
|
let plus = false,
|
|
|
|
total = counters.reduce((previous, counter) => {
|
|
|
|
// Check if there is a plus sign at the end of the counter.
|
|
|
|
if (counter != parseInt(counter, 10)) {
|
|
|
|
plus = true;
|
|
|
|
counter = parseInt(counter, 10);
|
|
|
|
}
|
|
|
|
|
|
|
|
return previous + counter;
|
|
|
|
}, 0);
|
|
|
|
|
|
|
|
total = plus && total > 0 ? total + '+' : total;
|
|
|
|
|
|
|
|
// Save the counter on site.
|
|
|
|
return this.saveAddonBadge(total, siteId);
|
|
|
|
}).then((siteTotal) => {
|
|
|
|
return this.updateAppCounter().then(() => {
|
|
|
|
return siteTotal;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Register a device in Apple APNS or Google GCM.
|
|
|
|
*
|
|
|
|
* @return {Promise<any>} Promise resolved when the device is registered.
|
|
|
|
*/
|
|
|
|
registerDevice(): Promise<any> {
|
|
|
|
try {
|
|
|
|
// Check if sound is enabled for notifications.
|
|
|
|
return this.getOptions().then((options) => {
|
|
|
|
const pushObject: PushObject = this.push.init(options);
|
|
|
|
|
|
|
|
pushObject.on('notification').subscribe((notification: any) => {
|
2018-06-21 15:59:06 +02:00
|
|
|
// Execute the callback in the Angular zone, so change detection doesn't stop working.
|
|
|
|
this.zone.run(() => {
|
|
|
|
this.logger.log('Received a notification', notification);
|
|
|
|
this.onMessageReceived(notification);
|
|
|
|
});
|
2018-02-27 08:39:45 +01:00
|
|
|
});
|
|
|
|
|
2018-03-20 14:53:25 +01:00
|
|
|
pushObject.on('registration').subscribe((data: any) => {
|
2018-06-21 15:59:06 +02:00
|
|
|
// Execute the callback in the Angular zone, so change detection doesn't stop working.
|
|
|
|
this.zone.run(() => {
|
|
|
|
this.pushID = data.registrationId;
|
2018-09-10 11:37:39 +02:00
|
|
|
if (this.sitesProvider.isLoggedIn()) {
|
|
|
|
this.registerDeviceOnMoodle().catch((error) => {
|
|
|
|
this.logger.warn('Can\'t register device', error);
|
|
|
|
});
|
|
|
|
}
|
2018-03-01 16:55:49 +01:00
|
|
|
});
|
2018-02-27 08:39:45 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
pushObject.on('error').subscribe((error: any) => {
|
2018-06-21 15:59:06 +02:00
|
|
|
// Execute the callback in the Angular zone, so change detection doesn't stop working.
|
|
|
|
this.zone.run(() => {
|
|
|
|
this.logger.warn('Error with Push plugin', error);
|
|
|
|
});
|
2018-02-27 08:39:45 +01:00
|
|
|
});
|
|
|
|
});
|
|
|
|
} catch (ex) {
|
|
|
|
// Ignore errors.
|
|
|
|
this.logger.warn(ex);
|
|
|
|
}
|
|
|
|
|
|
|
|
return Promise.reject(null);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Registers a device on current Moodle site.
|
|
|
|
*
|
|
|
|
* @return {Promise<any>} Promise resolved when device is registered.
|
|
|
|
*/
|
|
|
|
registerDeviceOnMoodle(): Promise<any> {
|
|
|
|
this.logger.debug('Register device on Moodle.');
|
|
|
|
|
|
|
|
if (!this.sitesProvider.isLoggedIn() || !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,
|
|
|
|
version: this.device.version,
|
|
|
|
pushid: this.pushID,
|
|
|
|
uuid: this.device.uuid
|
|
|
|
};
|
|
|
|
|
|
|
|
return this.sitesProvider.getCurrentSite().write('core_user_add_user_device', data);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the addon/site badge counter from the database.
|
|
|
|
*
|
|
|
|
* @param {string} siteId Site ID.
|
|
|
|
* @param {string} [addon='site'] Registered addon name. If not defined it will store the site total.
|
|
|
|
* @return {Promise<any>} Promise resolved with the stored badge counter for the addon or site or 0 if none.
|
|
|
|
*/
|
|
|
|
protected getAddonBadge(siteId?: string, addon: string = 'site'): Promise<any> {
|
2018-05-30 12:13:39 +02:00
|
|
|
return this.appDB.getRecord(AddonPushNotificationsProvider.BADGE_TABLE, {siteid: siteId, addon: addon}).then((entry) => {
|
2018-02-27 08:39:45 +01:00
|
|
|
return (entry && entry.number) || 0;
|
|
|
|
}).catch(() => {
|
|
|
|
return 0;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Save the addon/site badgecounter on the database.
|
|
|
|
*
|
|
|
|
* @param {number} value The number to be stored.
|
|
|
|
* @param {string} [siteId] Site ID. If not defined, use current site.
|
|
|
|
* @param {string} [addon='site'] Registered addon name. If not defined it will store the site total.
|
|
|
|
* @return {Promise<any>} Promise resolved with the stored badge counter for the addon or site.
|
|
|
|
*/
|
|
|
|
protected saveAddonBadge(value: number, siteId?: string, addon: string = 'site'): Promise<any> {
|
|
|
|
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
|
|
|
|
|
|
|
const entry = {
|
|
|
|
siteid: siteId,
|
|
|
|
addon: addon,
|
|
|
|
number: value
|
|
|
|
};
|
|
|
|
|
2018-05-30 12:13:39 +02:00
|
|
|
return this.appDB.insertRecord(AddonPushNotificationsProvider.BADGE_TABLE, entry).then(() => {
|
2018-02-27 08:39:45 +01:00
|
|
|
return value;
|
|
|
|
});
|
|
|
|
}
|
2018-02-23 14:44:17 +01:00
|
|
|
}
|