409 lines
15 KiB
TypeScript
Raw Normal View History

// (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 { Platform } from 'ionic-angular';
import { Badge } from '@ionic-native/badge';
import { Push, PushObject, PushOptions } from '@ionic-native/push';
import { Device } from '@ionic-native/device';
import { CoreAppProvider } from '@providers/app';
import { CoreLoggerProvider } from '@providers/logger';
import { CoreSitesProvider } from '@providers/sites';
import { AddonPushNotificationsDelegate } from './delegate';
import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreTextUtilsProvider } from '@providers/utils/text';
import { CoreConfigProvider } from '@providers/config';
import { CoreConfigConstants } from '.././../../configconstants';
/**
* Service to handle push notifications.
*/
@Injectable()
export class AddonPushNotificationsProvider {
protected logger;
protected pushID: string;
protected appDB: any;
static COMPONENT = 'mmaPushNotifications';
// Variables for database.
protected BADGE_TABLE = 'mma_pushnotifications_badge';
protected tablesSchema = [
{
name: this.BADGE_TABLE,
columns: [
{
name: 'siteid',
type: 'INTEGER'
},
{
name: 'addon',
type: 'TEXT'
},
{
name: 'number',
type: 'INTEGER'
}
],
primaryKeys: ['siteid', 'addon']
}
];
constructor(logger: CoreLoggerProvider, protected appProvider: CoreAppProvider, private platform: Platform,
protected pushNotificationsDelegate: AddonPushNotificationsDelegate, protected sitesProvider: CoreSitesProvider,
private badge: Badge, private localNotificationsProvider: CoreLocalNotificationsProvider,
private utils: CoreUtilsProvider, private textUtils: CoreTextUtilsProvider, private push: Push,
private configProvider: CoreConfigProvider, private device: Device) {
this.logger = logger.getInstance('AddonPushNotificationsProvider');
this.appDB = appProvider.getDB();
this.appDB.createTablesFromSchema(this.tablesSchema);
}
/**
* Delete all badge records for a given site.
*
* @param {string} siteId Site ID.
* @return {Promise<any>} Resolved when done.
*/
cleanSiteCounters(siteId: string): Promise<any> {
return this.appDB.deleteRecords(this.BADGE_TABLE, {siteid: siteId} ).finally(() => {
this.updateAppCounter();
});
}
/**
* Returns options for push notifications based on
* @return {Promise<PushOptions>} [description]
*/
protected getOptions(): Promise<PushOptions> {
// @todo: CoreSettingsProvider.NOTIFICATION_SOUND
return this.configProvider.get('CoreSettingsProvider.NOTIFICATION_SOUND', true).then((soundEnabled) => {
return {
android: {
senderID: CoreConfigConstants.gcmpn,
sound: !!soundEnabled
},
ios: {
alert: 'true',
badge: true,
sound: !!soundEnabled
},
windows: {
sound: !!soundEnabled
}
};
});
}
/**
* Get the pushID for this device.
*
* @return {string} Push ID.
*/
getPushId(): string {
return this.pushID;
}
/**
* 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);
}
/**
* Function called when a push notification is clicked. Redirect the user to the right state.
*
* @param {any} notification Notification.
*/
notificationClicked(notification: any): void {
this.platform.ready().then(() => {
this.pushNotificationsDelegate.clicked(notification);
});
}
/**
* 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(),
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.
this.platform.ready().then(() => {
data.title = notification.title;
data.message = notification.message;
this.pushNotificationsDelegate.received(data);
});
} else {
// The notification was clicked.
// For compatibility with old push plugin implementation we'll merge all the notification data in a single object.
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) => {
// The app badge counter does not support strings, so parse to int before.
return previous + parseInt(counter, 10);
}, 0);
// 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 = [];
for (const x in addons) {
promises.push(this.getAddonBadge(siteId, addons[x]));
}
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) => {
this.logger.log('Received a notification', notification);
this.onMessageReceived(notification);
});
pushObject.on('registration').subscribe((registrationId: any) => {
this.pushID = registrationId;
this.registerDeviceOnMoodle().catch((error) => {
this.logger.warn('Can\'t register device', error);
});
});
pushObject.on('error').subscribe((error: any) => {
this.logger.warn('Error with Push plugin', error);
});
});
} 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> {
return this.appDB.getRecord(this.BADGE_TABLE, {siteid: siteId, addon: addon}).then((entry) => {
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
};
return this.appDB.insertOrUpdateRecord(this.BADGE_TABLE, entry, {siteid: siteId, addon: addon}).then(() => {
return value;
});
}
}