VulpesOS/gaia/apps/wappush/js/wappush.js
OverSu b38dea0597 Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
2026-09-30 23:13:03 +02:00

461 lines
14 KiB
JavaScript
Executable file

/* global CpScreenHelper, DUMP, MessageDB, Notification, NotificationHelper,
ParsedMessage, Promise, SiSlScreenHelper, WhiteList, Utils */
/* exported WapPushManager */
(function(exports) {
'use strict';
/**
* Handles incoming WAP Push messages and contains the functionality required
* to post notifications and display their contents
*/
var WapPushManager = {
init: wpm_init,
close: wpm_close,
displayWapPushMessage: wpm_displayWapPushMessage,
onVisibilityChange: wpm_onVisibilityChange,
onWapPushReceived: wpm_onWapPushReceived
};
/** Settings key for enabling/disabling WAP Push messages */
var wapPushEnableKey = 'wap.push.enabled';
/** Enable/disable WAP Push notifications */
var wapPushEnabled;
/** A reference to the app's object */
var app;
/** Timer used to schedule a close operation */
var closeTimeout;
/**
* Number of messages that have been received but haven't been fully
* processed yet
*/
var pendingMessages;
/** Currently displayed SI message (null for SL and CP messages) */
var displayedSiMessage;
/**
* Returns a promise used to retrieve the app's own object.
*
* @return {Object} A promise for the app object.
*/
function wpm_getApp() {
return new Promise(function(resolve, reject) {
var req = navigator.mozApps.getSelf();
req.onsuccess = function wpm_gotApp() {
resolve(this.result);
};
req.onerror = function wpm_getAppError() {
reject(this.error);
};
});
}
/**
* Returns a promise used to retrieve the configuration of the app.
*
* @return {Object} A promise for the app configuration.
*/
function wpm_getConfig() {
return new Promise(function(resolve, reject) {
var req = navigator.mozSettings.createLock().get(wapPushEnableKey);
req.onsuccess = function wpm_settingsLockSuccess() {
resolve(this.result[wapPushEnableKey]);
};
req.onerror = function wpm_settingsLockError() {
reject(this.error);
};
});
}
/**
* Initialize the WAP Push manager, this only subscribes to the
* wappush-received message handler at the moment.
*
* @return {Object} A promise that will be fullfilled once the component has
* been fully initialized.
*/
function wpm_init() {
// Reset the internal state to default values.
wapPushEnabled = true;
app = null;
closeTimeout = null;
pendingMessages = 0;
displayedSiMessage = null;
// Listen to settings changes right away
navigator.mozSettings.addObserver(wapPushEnableKey, wpm_onSettingsChange);
// Get the app object and configuration
var promise = Promise.all([
wpm_getApp(), wpm_getConfig(), WhiteList.init()
]);
promise = promise.then(function(values) {
app = values[0];
wapPushEnabled = values[1];
// Init screen helpers
SiSlScreenHelper.init();
CpScreenHelper.init();
// Register event and message handlers only after initialization is done
MessageDB.on('new', wpm_onNew);
MessageDB.on('update', wpm_onUpdate);
MessageDB.on('discard', wpm_onDiscard);
MessageDB.on('delete', wpm_onDelete);
document.addEventListener('visibilitychange', wpm_onVisibilityChange);
window.navigator.mozSetMessageHandler('notification', wpm_onNotification);
window.navigator.mozSetMessageHandler('wappush-received',
wpm_onWapPushReceived);
}).catch(function(error) {
// If we encountered an error don't process messages
wapPushEnabled = false;
var message = error.message || 'Unknown error';
console.error('Could not initialize the WAP push manager: ', message);
throw error;
});
return promise;
}
/**
* Handler invoked when the 'wap.push.enabled' option changes.
*
* @param {Object} A settings object holding the new value.
*/
function wpm_onSettingsChange(v) {
wapPushEnabled = v.settingValue;
}
/**
* Closes the application whenever it is hidden
*/
function wpm_onVisibilityChange() {
if (document.hidden) {
wpm_close();
} else {
window.clearTimeout(closeTimeout);
closeTimeout = null;
}
}
/**
* Establish if we must show this message or not; the message is shown only
* if WAP Push functionality is enabled, the sender's MSISDN is whitelisted
* or whitelisting is disabled
*
* @param {Object} message A parsed WAP Push message.
*
* @return {Boolean} true if the message should be displayed, false otherwise.
*/
function wpm_shouldDisplayMessage(message) {
if (!wapPushEnabled || (message === null)) {
/* WAP push functionality is either completely disabled, ignore it. */
return false;
}
if ((message.type !== 'text/vnd.wap.connectivity-xml') &&
(!WhiteList.has(message.sender) || message.isExpired())) {
/* The message isn't a provisioning message and comes from a non
* white-listed MSISDN or it has already been expired, ignore it. */
return false;
}
return true;
}
/**
* Send a notification for the provided message
*
* @param {Object} message The message the user needs to be notified of
*
* @return {Object} A promise that is resolved once the notification has been
* sent and its events hooked up properly.
*/
function wpm_sendNotification(message) {
var iconURL = NotificationHelper.getIconURI(app);
/* Build the notification's text, for text/vnd.wap.connectivity-xml
* messages this needs to be localized. */
var body;
if (message.type === 'text/vnd.wap.connectivity-xml') {
body = message.text; // The text will be localized
} else {
body = {
id: (message.type === 'text/vnd.wap.si') ? 'si-message-body'
: 'sl-message-body',
args: {
text: message.text || '', // The text won't be localized
url: message.href
}
};
}
var title = Utils.prepareMessageTitle(message);
var options = {
icon: iconURL,
bodyL10n: body,
tag: message.getUniqueId()
};
return NotificationHelper.send(title, options)
.then(function(notification) {
notification.addEventListener('click',
function wpm_onNotificationClick(event) {
wpm_displayWapPushMessage(event.target.tag);
}
);
});
}
/**
* Handler for the wappush-received system messages, stores the message into
* the internal database and posts a notification which can be used to
* display the message. This method returns a promise which is currently used
* only when testing. It is also exposed for testing reasons but shouldn't be
* used outside of this file.
*
* @param {Object} wapMessage The WAP Push message as provided by the system.
*
* @return {Object} A promise that is resolved when the method executed
* successfully or rejected with the error code that caused the
* method to fail.
*/
function wpm_onWapPushReceived(wapMessage) {
DUMP('Received a message: ', wapMessage);
pendingMessages++;
var message = ParsedMessage.from(wapMessage, Date.now());
if (!wpm_shouldDisplayMessage(message)) {
DUMP('The message will not be displayed');
wpm_finish();
return Promise.resolve();
}
return message.save().catch(error => {
var message = error.message || 'Unknown error';
console.log('Could not add a message to the database: ' + message + '\n');
wpm_finish();
throw error;
});
}
/**
* Displays the contents of a WAP Push message
*
* @param {Object} message The notification event.
*/
function wpm_onNotification(message) {
if (!message.clicked) {
return;
}
wpm_displayWapPushMessage(message.tag);
}
/**
* Reacts to a new message being added to the database by showing a
* notification or directly showing the message if required by the action
* field.
*
* @param {Object} obj The new message in untyped form (i.e. JSON)
*/
function wpm_onNew(obj) {
DUMP('A new message added to the DB');
var message = new ParsedMessage(obj);
if (message.action === 'signal-high' ||
message.action === 'execute-high')
{
/* We just decrease the number of pending messages instead of invoking
* wpm_finish() since we don't want to start the close procedure. */
pendingMessages--;
wpm_displayWapPushMessage(message.getUniqueId());
} else {
wpm_sendNotification(message).then(() => wpm_finish());
}
}
/**
* Reacts to a message being updated in the database by updating the
* notification and possibly the message being shown to the user.
*
* @param {Object} obj The new message in untyped form (i.e. JSON)
*/
function wpm_onUpdate(obj) {
DUMP('A message was updated in the DB');
var message = new ParsedMessage(obj);
// Update the message if we're currently displaying it
if (displayedSiMessage &&
displayedSiMessage.getUniqueId() === message.getUniqueId()) {
displayedSiMessage = message;
SiSlScreenHelper.populateScreen(message);
}
// This updates the existing notification
wpm_sendNotification(message).then(() => wpm_finish());
}
/**
* Removes the notifications for messages that have been deleted and closes
* the app if the message being viewed has been deleted.
*
* @param {Object} obj The deleted message in untyped form (i.e. JSON)
*/
function wpm_onDelete(obj) {
DUMP('A message was deleted');
var message = new ParsedMessage(obj);
// Close the application if we're displaying the deleted message
if (displayedSiMessage &&
displayedSiMessage.getUniqueId() === message.getUniqueId()) {
wpm_close();
}
// Remove the notification associated to the deleted message
wpm_clearNotifications(+message.getUniqueId());
}
/**
* Prints a debug statement when a message has been discarded, no further
* action required.
*/
function wpm_onDiscard() {
DUMP('The message was discarded');
wpm_finish();
}
/**
* Retrieves a WAP Push message from the database and displays it
*
* @param {String} id The message id as a string.
* @return {Promise} A promise that resolves once the message has been
* displayed and the associated notification cleared.
*/
function wpm_displayWapPushMessage(id) {
DUMP('Displaying message ' + id);
// Clear the close timer as the application will soon become visible
app.launch();
window.clearTimeout(closeTimeout);
closeTimeout = null;
return ParsedMessage.load(id).then(
function wpm_loadResolved(message) {
switch (message.type) {
case 'text/vnd.wap.si':
displayedSiMessage = message;
CpScreenHelper.hide();
if (message.isExpired()) {
// Notify the user that the message has expired
SiSlScreenHelper.populateScreen();
} else {
SiSlScreenHelper.populateScreen(message);
}
return wpm_clearNotifications(id);
case 'text/vnd.wap.sl':
displayedSiMessage = null;
CpScreenHelper.hide();
SiSlScreenHelper.populateScreen(message);
return wpm_clearNotifications(id);
case 'text/vnd.wap.connectivity-xml':
displayedSiMessage = null;
SiSlScreenHelper.hide();
CpScreenHelper.showConfirmInstallationDialog(message);
return Promise.resolve();
}
}
).catch(function(error) {
var message = error.message || 'Unknown error';
console.error('Could not retrieve the message: ', message);
throw error;
});
}
/**
* Remove notifications for a given tag.
*
* @return {Promise} A promise that gets resolved once the notifications have
* been cleared and rejects in case of error.
*/
function wpm_clearNotifications(tag) {
return Notification.get({tag: tag}).then(
function onSuccess(notifications) {
for (var i = 0; i < notifications.length; i++) {
notifications[i].close();
}
},
function onError(error) {
var message = error.message || 'Unknown error';
console.error('Could not get notification: ', message);
throw error;
}
);
}
/**
* Marks a message as processed and close the application if no more messages
* are present and the application is not visible.
*/
function wpm_finish() {
pendingMessages--;
if (document.hidden) {
wpm_close();
}
}
/**
* Closes the application. Whenever this function is called it
* starts a timer that will eventually close the application when no more
* messages are being processed. Calling close multiple times is safe as only
* one timer can be active at a time so subsequent calls will effectively be
* no-ops.
*/
function wpm_close() {
if (closeTimeout !== null) {
// We're already trying to close the app
return;
}
/* We do not close the app immediately, instead we spin the event loop once
* in the hope of catching pending messages that have been sent to the app
* and have not yet been processed. If some messages are pending we'll
* reschedule the close until they've all been processed. */
closeTimeout = window.setTimeout(function wpm_delayedClose() {
if (pendingMessages > 0) {
// Pending messages were received since we set this timer
closeTimeout = window.setTimeout(wpm_delayedClose, 100);
return;
}
/* There's no more pending messages and the application is in the
* background, close for real */
DUMP('Automatically closing the application');
closeTimeout = null;
window.close();
}, 100);
}
exports.WapPushManager = WapPushManager;
})(this);