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.
This commit is contained in:
parent
93e3a48004
commit
b38dea0597
25901 changed files with 9982925 additions and 369 deletions
40
gaia/apps/wappush/js/activity_picker.js
Executable file
40
gaia/apps/wappush/js/activity_picker.js
Executable file
|
|
@ -0,0 +1,40 @@
|
|||
/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
|
||||
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
|
||||
|
||||
/* global MozActivity */
|
||||
|
||||
(function(exports) {
|
||||
'use strict';
|
||||
|
||||
function activityDefaultError(event) {
|
||||
console.warn('Unhandled error spawning activity: ' +
|
||||
event.target.error.message + '\n');
|
||||
}
|
||||
|
||||
var ActivityPicker = {
|
||||
url: function ap_browse(url, onsuccess, onerror) {
|
||||
if (typeof onerror !== 'function') {
|
||||
onerror = activityDefaultError;
|
||||
}
|
||||
|
||||
if (window.MozActivity) {
|
||||
var activity = new MozActivity({
|
||||
name: 'view',
|
||||
data: {
|
||||
type: 'url',
|
||||
url: url
|
||||
}
|
||||
});
|
||||
|
||||
if (typeof onsuccess === 'function') {
|
||||
activity.onsuccess = onsuccess;
|
||||
}
|
||||
|
||||
activity.onerror = onerror;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.ActivityPicker = ActivityPicker;
|
||||
|
||||
}(this));
|
||||
133
gaia/apps/wappush/js/auth.js
Executable file
133
gaia/apps/wappush/js/auth.js
Executable file
|
|
@ -0,0 +1,133 @@
|
|||
/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
|
||||
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
|
||||
|
||||
/* global CryptoJS */
|
||||
|
||||
/* exported ProvisioningAuthentication */
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Singleton object that authenticates the sender of the client provisioning
|
||||
* message.
|
||||
*/
|
||||
var ProvisioningAuthentication = (function() {
|
||||
/** The PIN introduced by the user */
|
||||
var pin = null;
|
||||
|
||||
/** The object holding auth info */
|
||||
var auth = null;
|
||||
|
||||
/**
|
||||
* Determines whether the sender (tipically the Client Provisioning server)
|
||||
* could be authenticate as the sender of the message.
|
||||
*
|
||||
* @param {String} pin The PIN introduced by the user.
|
||||
* @param {Object} auth An object containing some info such as the
|
||||
* security mechanism used, the MAC, the raw message
|
||||
* data in binary format (Uint8Array object).
|
||||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
function pa_isDocumentValid(userPin, authInfo) {
|
||||
pin = userPin;
|
||||
auth = authInfo;
|
||||
|
||||
var sec = auth.sec;
|
||||
switch (sec) {
|
||||
case 'NETWPIN':
|
||||
// This function won't be called since gecko will be responsible for
|
||||
// authenticate the sender of client provisioning messages with this
|
||||
// authentication mechanism. Let's throw an error.
|
||||
throw new Error('cp-finish-confirm-dialog-message-auth-error');
|
||||
case 'USERPIN':
|
||||
return pa_userPinAuthentication();
|
||||
case 'USERNETWPIN':
|
||||
return pa_userNetwPinAuthentication();
|
||||
case 'USERPINMAC':
|
||||
return pa_userPinMacAuthentication();
|
||||
default:
|
||||
throw new Error(
|
||||
'cp-finish-confirm-dialog-message-auth-unsupported-mechanism'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper than handles USERPIN authentication mechanism.
|
||||
*
|
||||
* @return {Boolean} A flag that indicates the sender of the message could be
|
||||
* authenticated as valid.
|
||||
*/
|
||||
function pa_userPinAuthentication() {
|
||||
var mac = auth.mac;
|
||||
|
||||
return mac === pa_sign(pin, auth.data, auth.data.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper than handles USERNETWPIN authentication mechanism.
|
||||
*
|
||||
* @return {Boolean} A flag that indicates the sender of the message could be
|
||||
* authenticated as valid.
|
||||
*/
|
||||
function pa_userNetwPinAuthentication() {
|
||||
// TODO: This authentication mechanism must be handle somehow by gecko
|
||||
// as well. This is not supported yet. Let's throw an error then.
|
||||
throw new Error(
|
||||
'cp-finish-confirm-dialog-message-auth-unsupported-mechanism'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper than handles USERPINMAC authentication mechanism.
|
||||
*
|
||||
* @return {Boolean} A flag that indicates the sender of the message could be
|
||||
* authenticated as valid.
|
||||
*/
|
||||
function pa_userPinMacAuthentication() {
|
||||
// TODO: This authentication mechanism has not been tested against a real
|
||||
// server so we have decided to leave it out from the implementation until
|
||||
// we are able to tested properly. Let's throw an error then.
|
||||
throw new Error(
|
||||
'cp-finish-confirm-dialog-message-auth-unsupported-mechanism'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Signs the message data. It performs a HMAC SHA1 signing method.
|
||||
*
|
||||
* @param {String} key The key string.
|
||||
* @param {Uint8Array} data The message data.
|
||||
* @param {Integer} dataLength The length of the message data.
|
||||
*
|
||||
* @return {String} The MAC we have generated.
|
||||
*/
|
||||
function pa_sign(key, data, dataLength) {
|
||||
function bin2hex(buffer) {
|
||||
var i, f = 0, a = [], data = new Uint8Array(buffer);
|
||||
f = data.length;
|
||||
for (i = 0; i < f; i++) {
|
||||
a[i] = data[i].toString(16).replace(/^([\da-f])$/, '0$1');
|
||||
}
|
||||
return a.join('');
|
||||
}
|
||||
|
||||
var _data = new Uint8Array(dataLength);
|
||||
for (var i = 0; i < dataLength; i++) {
|
||||
_data[i] = data[i];
|
||||
}
|
||||
|
||||
var hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA1, key);
|
||||
var words = CryptoJS.enc.Hex.parse(bin2hex(_data));
|
||||
|
||||
hmac.update(words);
|
||||
var hash = hmac.finalize();
|
||||
|
||||
return hash.toString(CryptoJS.enc.Hex).toUpperCase();
|
||||
}
|
||||
|
||||
return {
|
||||
isDocumentValid: pa_isDocumentValid
|
||||
};
|
||||
})();
|
||||
529
gaia/apps/wappush/js/cp_screen_helper.js
Executable file
529
gaia/apps/wappush/js/cp_screen_helper.js
Executable file
|
|
@ -0,0 +1,529 @@
|
|||
/* global MessageDB, ParsedProvisioningDoc, ProvisioningAuthentication,
|
||||
StoreProvisioning, WapPushManager, Utils */
|
||||
|
||||
/* exported CpScreenHelper */
|
||||
|
||||
'use strict';
|
||||
|
||||
var CpScreenHelper = (function() {
|
||||
/** Content provisioning screen node */
|
||||
var cpScreen = null;
|
||||
|
||||
/** Pin screen node */
|
||||
var pinScreen = null;
|
||||
|
||||
/** APN screen node */
|
||||
var apnScreen = null;
|
||||
|
||||
/** Details screen node */
|
||||
var detailsScreen = null;
|
||||
|
||||
/** Quit app button node */
|
||||
var quitButton = null;
|
||||
|
||||
/** Cancel quit app button node */
|
||||
var cancelQuitButton = null;
|
||||
|
||||
/** Accept install configuration button node */
|
||||
var acceptInstallCfgButton = null;
|
||||
|
||||
/** Cancel install configuration button node */
|
||||
var cancelInstallCfgButton = null;
|
||||
|
||||
/** Finish button node */
|
||||
var finishButton = null;
|
||||
|
||||
/** PIN input node */
|
||||
var pin = null;
|
||||
|
||||
/** Quit app confirm dialog node */
|
||||
var quitAppConfirmDialog = null;
|
||||
|
||||
/** Install configuration confirm dialog node */
|
||||
var installCfgConfirmDialog = null;
|
||||
|
||||
/** Auth failure message */
|
||||
var authFailureMessage = null;
|
||||
|
||||
/** Help message */
|
||||
var pinHelp = null;
|
||||
|
||||
/** Finish confirm dialog node */
|
||||
var finishConfirmDialog = null;
|
||||
|
||||
/** User PIN is needed, show input node */
|
||||
var showPINInput = false;
|
||||
|
||||
/** The message has not been authenticated yet */
|
||||
var authenticated = false;
|
||||
|
||||
/** Flag that holds the result of the authentication process */
|
||||
var isDocumentValid = false;
|
||||
|
||||
/** The message has not been processed yet */
|
||||
var processed = false;
|
||||
|
||||
/** Message id for notification management */
|
||||
var messageTag = null;
|
||||
|
||||
/** Authentication parameter for the WAP PUSH message. */
|
||||
var authInfo = null;
|
||||
|
||||
/** WAP provisioning document */
|
||||
var provisioningDoc = null;
|
||||
|
||||
/** All APNs list */
|
||||
var apns = null;
|
||||
|
||||
/** Index of the card on which the message was received. */
|
||||
var iccCardIndex = 0;
|
||||
|
||||
/** Accept button node for each screen */
|
||||
var acceptApnButton = null;
|
||||
var acceptPinButton = null;
|
||||
var acceptDetailsButton = null;
|
||||
|
||||
/** Title of the message, usually holds the sender's number */
|
||||
var titleApn = null;
|
||||
var titleDetails = null;
|
||||
var titlePin = null;
|
||||
|
||||
/** Flag to define the flow of details screen */
|
||||
var multipleApns = false;
|
||||
|
||||
/** Header of details screen */
|
||||
var headerDetails = null;
|
||||
|
||||
function cpsh_init() {
|
||||
// Let's consider the message is completely processed once the settings are
|
||||
// stored into the database.
|
||||
processed = false;
|
||||
|
||||
// Retrieve the various page elements
|
||||
titleApn = document.getElementById('title-apn');
|
||||
titleDetails = document.getElementById('title-details');
|
||||
titlePin = document.getElementById('title-pin');
|
||||
|
||||
quitAppConfirmDialog = document.getElementById('cp-quit-app-confirm');
|
||||
quitButton = quitAppConfirmDialog.querySelector('.quit');
|
||||
cancelQuitButton = quitAppConfirmDialog.querySelector('.cancel');
|
||||
|
||||
installCfgConfirmDialog =
|
||||
document.getElementById('cp-install-configuration-confirm');
|
||||
acceptInstallCfgButton = installCfgConfirmDialog.querySelector('.accept');
|
||||
cancelInstallCfgButton = installCfgConfirmDialog.querySelector('.cancel');
|
||||
|
||||
authFailureMessage = document.getElementById('cp-auth-failure');
|
||||
pinHelp = document.getElementById('cp-done-help');
|
||||
|
||||
finishConfirmDialog = document.getElementById('cp-finish-confirm');
|
||||
finishButton = finishConfirmDialog.querySelector('button');
|
||||
|
||||
cpScreen = document.getElementById('cp-screen');
|
||||
|
||||
pinScreen = document.getElementById('cp-pin-screen');
|
||||
pin = pinScreen.querySelector('input');
|
||||
|
||||
apnScreen = document.getElementById('cp-apn-screen');
|
||||
detailsScreen = document.getElementById('cp-details-screen');
|
||||
|
||||
acceptApnButton = document.getElementById('apn-accept');
|
||||
acceptPinButton = document.getElementById('pin-accept');
|
||||
acceptDetailsButton = document.getElementById('details-accept');
|
||||
|
||||
var headerApnList = document.getElementById('header-apn-list');
|
||||
headerDetails = document.getElementById('header-details');
|
||||
var headerPin = document.getElementById('header-pin');
|
||||
|
||||
// Event handlers
|
||||
quitButton.addEventListener('click', cpsh_onQuit);
|
||||
cancelQuitButton.addEventListener('click', cpsh_onCancelQuit);
|
||||
finishButton.addEventListener('click', cpsh_onFinish);
|
||||
pin.addEventListener('keyup', cpsh_onPinInput);
|
||||
acceptApnButton.addEventListener('click', cpsh_onApnAccept);
|
||||
acceptPinButton.addEventListener('click', cpsh_onPinAccept);
|
||||
acceptDetailsButton.addEventListener('click', cpsh_onDetailsAccept);
|
||||
headerApnList.addEventListener('action', cpsh_onClose);
|
||||
headerDetails.addEventListener('action', cpsh_closeDetails);
|
||||
headerPin.addEventListener('action', cpsh_closePin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides all the panels.
|
||||
*/
|
||||
function cpsh_hide() {
|
||||
cpScreen.hidden = true;
|
||||
finishConfirmDialog.hidden = true;
|
||||
installCfgConfirmDialog.hidden = true;
|
||||
quitAppConfirmDialog.hidden = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the application flow showing a warning prompt that asks to the user if
|
||||
* wants to install the configuration information.
|
||||
*/
|
||||
function cpsh_showConfirmInstallationDialog(message) {
|
||||
installCfgConfirmDialog.hidden = false;
|
||||
acceptInstallCfgButton
|
||||
.addEventListener('click',cpsh_onAcceptInstallCfg.bind(null, message));
|
||||
cancelInstallCfgButton.addEventListener('click', cpsh_onCancelInstallCfg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the client provisioning screen visible and populate it.
|
||||
*/
|
||||
function cpsh_populateScreen(message) {
|
||||
cpScreen.hidden = false;
|
||||
|
||||
var title = Utils.prepareMessageTitle(message);
|
||||
navigator.mozL10n.setAttributes(titleApn, title.id, title.args);
|
||||
navigator.mozL10n.setAttributes(titleDetails, title.id, title.args);
|
||||
navigator.mozL10n.setAttributes(titlePin, title.id, title.args);
|
||||
|
||||
iccCardIndex = message.serviceId;
|
||||
messageTag = message.timestamp;
|
||||
|
||||
authInfo = message.provisioning.authInfo;
|
||||
provisioningDoc = message.provisioning.provisioningDoc;
|
||||
|
||||
// Get the number of APNs to show details or the list of them.
|
||||
var parsedProvisioningDoc = ParsedProvisioningDoc.from(provisioningDoc);
|
||||
apns = parsedProvisioningDoc.getApns();
|
||||
|
||||
// The provisioning document might not have valid APNs (other ones that
|
||||
// those ones in APPLICATION nodes for the Browsing Enabler and AC for the
|
||||
// Multimedia Messaging System Enabler). In this case we must not continue.
|
||||
if (apns.length === 0) {
|
||||
message = finishConfirmDialog.querySelector('strong');
|
||||
navigator.mozL10n.setAttributes(message,
|
||||
'cp-finish-confirm-dialog-message-no-apns');
|
||||
finishConfirmDialog.hidden = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Make the PIN input visible when the authentication mechanism needs a PIN
|
||||
// 0 NETWPIN, there is no need for an user PIN
|
||||
// 1 USERPIN, the authentication mechanism needs a PIN from the user
|
||||
// 1 USERNETWPIN, the authentication mechanism needs a PIN from the user
|
||||
// 1 USERPINMAC, the authentication mechanism needs a PIN from the user
|
||||
showPINInput = (authInfo.sec !== 'NETWPIN');
|
||||
// 'message.authInfo.checked' property tell us the authentication process
|
||||
// was already performed by gecko
|
||||
authenticated = authInfo.checked;
|
||||
// 'message.authInfo.pass' property tell us the result of the authentication
|
||||
// process when this process was performed by gecko
|
||||
isDocumentValid = authInfo.pass;
|
||||
|
||||
if (showPINInput) {
|
||||
// If the document has not been authenticated yet and the PIN code is
|
||||
// needed, show some info and the PIN input element to the user.
|
||||
navigator.mozL10n.setAttributes(pinHelp, 'cp-accept-help-pin');
|
||||
pin.type = 'number';
|
||||
acceptPinButton.disabled = true;
|
||||
} else {
|
||||
navigator.mozL10n.setAttributes(pinHelp, 'cp-done-help');
|
||||
pin.type = 'hidden';
|
||||
pin.blur();
|
||||
acceptPinButton.disabled = false;
|
||||
}
|
||||
|
||||
if (apns.length > 1) {
|
||||
multipleApns = true;
|
||||
cpsh_showApnList();
|
||||
return;
|
||||
}
|
||||
|
||||
multipleApns = false;
|
||||
cpsh_showDetails(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the apn list screen when there are multiple apns in a configuration
|
||||
* message populating a list with each apn name.
|
||||
*/
|
||||
function cpsh_showApnList() {
|
||||
var fragment = document.createDocumentFragment();
|
||||
|
||||
for (var i = 0; i < apns.length; i++) {
|
||||
var list = document.createElement('li');
|
||||
|
||||
var a = document.createElement('a');
|
||||
a.addEventListener('click', cpsh_showDetails.bind(null, i));
|
||||
|
||||
var aside = document.createElement('aside');
|
||||
aside.className = 'pack-end';
|
||||
aside.setAttribute('data-icon', 'forward');
|
||||
// Icon is for presentation only.
|
||||
aside.setAttribute('aria-hidden', true);
|
||||
|
||||
var p = document.createElement('p');
|
||||
p.textContent = apns[i].carrier;
|
||||
|
||||
a.appendChild(aside);
|
||||
a.appendChild(p);
|
||||
list.appendChild(a);
|
||||
fragment.appendChild(list);
|
||||
}
|
||||
|
||||
var content = apnScreen.querySelector('.message');
|
||||
content.innerHTML = '';
|
||||
|
||||
content.appendChild(fragment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the application flow when the user clicks on the 'next' button
|
||||
* from the apn list screen.
|
||||
*/
|
||||
function cpsh_onApnAccept() {
|
||||
if (showPINInput) {
|
||||
pinScreen.addEventListener('transitionend', cpsh_setPinFocus);
|
||||
}
|
||||
|
||||
apnScreen.classList.add('left');
|
||||
pinScreen.classList.remove('right');
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the apn details screen.
|
||||
*/
|
||||
function cpsh_showDetails(index) {
|
||||
if (multipleApns) {
|
||||
acceptDetailsButton.style.visibility = 'hidden';
|
||||
headerDetails.setAttribute('action', 'back');
|
||||
}
|
||||
|
||||
var details = detailsScreen.querySelector('.message');
|
||||
details.innerHTML = '';
|
||||
|
||||
cpsh_createNode(details, 'Name', apns[index].carrier);
|
||||
cpsh_createNode(details, 'APN', apns[index].apn);
|
||||
cpsh_createNode(details, 'Proxy', apns[index].proxy);
|
||||
cpsh_createNode(details, 'Port', apns[index].port);
|
||||
cpsh_createNode(details, 'Username', apns[index].user);
|
||||
cpsh_createNode(details, 'Password', apns[index].password);
|
||||
cpsh_createNode(details, 'MMSC', apns[index].mmsc);
|
||||
cpsh_createNode(details, 'MMS Proxy', apns[index].mmsproxy);
|
||||
cpsh_createNode(details, 'MMS Port', apns[index].mmsport);
|
||||
cpsh_createNode(details, 'Authentication Type', apns[index].authType);
|
||||
cpsh_createNode(details, 'APN Type', apns[index].types);
|
||||
|
||||
detailsScreen.classList.remove('right');
|
||||
apnScreen.classList.add('left');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the application flow when the user clicks on the 'back'/ 'close'
|
||||
* button from the details screen.
|
||||
* If there are multiple APNs then back button to go back to apn list screen.
|
||||
* If only one APN then shows quit confirm.
|
||||
*/
|
||||
function cpsh_closeDetails(evt) {
|
||||
if (!multipleApns) {
|
||||
cpsh_onClose(evt);
|
||||
return;
|
||||
}
|
||||
|
||||
detailsScreen.classList.add('right');
|
||||
apnScreen.classList.remove('left');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the application flow when the user clicks on the 'next' button
|
||||
* from the details screen.
|
||||
*/
|
||||
function cpsh_onDetailsAccept() {
|
||||
if (showPINInput) {
|
||||
pinScreen.addEventListener('transitionend', cpsh_setPinFocus);
|
||||
}
|
||||
|
||||
detailsScreen.classList.add('left');
|
||||
pinScreen.classList.remove('right');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function used to populate the list of the APN details.
|
||||
*/
|
||||
function cpsh_createNode(rootNode, key, value) {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
var element = document.createElement('li');
|
||||
var nodeKey = document.createElement('p');
|
||||
var nodeValue = document.createElement('p');
|
||||
nodeKey.textContent = key;
|
||||
nodeValue.textContent = value;
|
||||
element.appendChild(nodeKey);
|
||||
element.appendChild(nodeValue);
|
||||
rootNode.appendChild(element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function used to set focus to pin input when transition to pin
|
||||
* screen has finished.
|
||||
*/
|
||||
function cpsh_setPinFocus() {
|
||||
pin.focus();
|
||||
pinScreen.removeEventListener('transitionend', cpsh_setPinFocus);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the application flow when the user clicks on the 'back' button
|
||||
* from the pin screen.
|
||||
*/
|
||||
function cpsh_closePin() {
|
||||
pin.blur();
|
||||
|
||||
if (!multipleApns) {
|
||||
pinScreen.classList.add('right');
|
||||
detailsScreen.classList.remove('left');
|
||||
return;
|
||||
}
|
||||
|
||||
pinScreen.classList.add('right');
|
||||
apnScreen.classList.remove('left');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the application flow when the user clicks on the 'close' button
|
||||
* from the client provisioning screen.
|
||||
*/
|
||||
function cpsh_onClose(evt) {
|
||||
if (processed) {
|
||||
WapPushManager.close();
|
||||
return;
|
||||
}
|
||||
quitAppConfirmDialog.hidden = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the application flow when the user clicks on the 'Quit' button
|
||||
* from the client provisioning quit app confirm dialog.
|
||||
*/
|
||||
function cpsh_onQuit(evt) {
|
||||
evt.preventDefault();
|
||||
|
||||
MessageDB.deleteByTimestamp(messageTag).then(function() {
|
||||
WapPushManager.close();
|
||||
}, function() {
|
||||
console.error('Could not delete message from the database');
|
||||
WapPushManager.close();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the application flow when the user clicks on the 'Cancel' button
|
||||
* from the client provisioning quit app confirm dialog.
|
||||
*/
|
||||
function cpsh_onCancelQuit(evt) {
|
||||
evt.preventDefault();
|
||||
quitAppConfirmDialog.hidden = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the application flow when the user clicks on the 'Cancel' button
|
||||
* from the client install configuration screen.
|
||||
*/
|
||||
function cpsh_onCancelInstallCfg(evt) {
|
||||
evt.preventDefault();
|
||||
WapPushManager.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the application flow when the user clicks on the 'Cancel' button
|
||||
* from the client install configuration screen.
|
||||
*/
|
||||
function cpsh_onAcceptInstallCfg(message, evt) {
|
||||
evt.preventDefault();
|
||||
cpsh_populateScreen(message);
|
||||
installCfgConfirmDialog.hidden = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepts the message, authenticates the sender and presents the settings to
|
||||
* be stored to the user
|
||||
*/
|
||||
function cpsh_onPinAccept() {
|
||||
var message = null;
|
||||
|
||||
if (!authenticated) {
|
||||
if (!pin.value) {
|
||||
// Need a valid PIN code, show error.
|
||||
cpsh_pinError();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Need to perform the authentication process.
|
||||
isDocumentValid =
|
||||
ProvisioningAuthentication.isDocumentValid(pin.value,
|
||||
authInfo);
|
||||
} catch (e) {
|
||||
message = finishConfirmDialog.querySelector('strong');
|
||||
navigator.mozL10n.setAttributes(message, e.message);
|
||||
finishConfirmDialog.hidden = false;
|
||||
return;
|
||||
}
|
||||
|
||||
authenticated = true;
|
||||
if (!isDocumentValid) {
|
||||
// The document couldn't be authenticated, show error.
|
||||
authenticated = false;
|
||||
cpsh_pinError();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
processed = true;
|
||||
// Store APNs into the database.
|
||||
StoreProvisioning.provision(apns, iccCardIndex);
|
||||
|
||||
/* Show finish confirm dialog after having deleted the message, this is
|
||||
* done even if the deletion fails for some reason. */
|
||||
MessageDB.deleteByTimestamp(messageTag).then(function() {
|
||||
finishConfirmDialog.hidden = false;
|
||||
}, function(e) {
|
||||
console.error('Could not delete message from the database: ', e);
|
||||
finishConfirmDialog.hidden = false;
|
||||
});
|
||||
}
|
||||
|
||||
function cpsh_pinError() {
|
||||
authFailureMessage.hidden = false;
|
||||
pinHelp.hidden = true;
|
||||
pin.focus();
|
||||
// Set max to -1 in order to show invalid input.
|
||||
pin.setAttribute('max', -1);
|
||||
pin.addEventListener('keyup', cpsh_onPinErrorRestore);
|
||||
}
|
||||
|
||||
function cpsh_onPinErrorRestore() {
|
||||
authFailureMessage.hidden = true;
|
||||
pinHelp.hidden = false;
|
||||
pin.removeAttribute('max');
|
||||
pin.addEventListener('keyup', cpsh_onPinInput);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the application flow when the user clicks on the 'Finish' button
|
||||
* from the client provisioning finish confirm dialog.
|
||||
*/
|
||||
function cpsh_onFinish(evt) {
|
||||
evt.preventDefault();
|
||||
WapPushManager.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable / disable accept button if pin field is empty.
|
||||
*/
|
||||
function cpsh_onPinInput(evt) {
|
||||
acceptPinButton.disabled = (pin.value.length === 0);
|
||||
}
|
||||
|
||||
return {
|
||||
init: cpsh_init,
|
||||
hide: cpsh_hide,
|
||||
showConfirmInstallationDialog: cpsh_showConfirmInstallationDialog
|
||||
};
|
||||
})();
|
||||
17
gaia/apps/wappush/js/ext/CryptoJSv3.1.2/rollups/hmac-sha1.js
Executable file
17
gaia/apps/wappush/js/ext/CryptoJSv3.1.2/rollups/hmac-sha1.js
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
CryptoJS v3.1.2
|
||||
code.google.com/p/crypto-js
|
||||
(c) 2009-2013 by Jeff Mott. All rights reserved.
|
||||
code.google.com/p/crypto-js/wiki/License
|
||||
*/
|
||||
var CryptoJS=CryptoJS||function(g,l){var e={},d=e.lib={},m=function(){},k=d.Base={extend:function(a){m.prototype=this;var c=new m;a&&c.mixIn(a);c.hasOwnProperty("init")||(c.init=function(){c.$super.init.apply(this,arguments)});c.init.prototype=c;c.$super=this;return c},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var c in a)a.hasOwnProperty(c)&&(this[c]=a[c]);a.hasOwnProperty("toString")&&(this.toString=a.toString)},clone:function(){return this.init.prototype.extend(this)}},
|
||||
p=d.WordArray=k.extend({init:function(a,c){a=this.words=a||[];this.sigBytes=c!=l?c:4*a.length},toString:function(a){return(a||n).stringify(this)},concat:function(a){var c=this.words,q=a.words,f=this.sigBytes;a=a.sigBytes;this.clamp();if(f%4)for(var b=0;b<a;b++)c[f+b>>>2]|=(q[b>>>2]>>>24-8*(b%4)&255)<<24-8*((f+b)%4);else if(65535<q.length)for(b=0;b<a;b+=4)c[f+b>>>2]=q[b>>>2];else c.push.apply(c,q);this.sigBytes+=a;return this},clamp:function(){var a=this.words,c=this.sigBytes;a[c>>>2]&=4294967295<<
|
||||
32-8*(c%4);a.length=g.ceil(c/4)},clone:function(){var a=k.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var c=[],b=0;b<a;b+=4)c.push(4294967296*g.random()|0);return new p.init(c,a)}}),b=e.enc={},n=b.Hex={stringify:function(a){var c=a.words;a=a.sigBytes;for(var b=[],f=0;f<a;f++){var d=c[f>>>2]>>>24-8*(f%4)&255;b.push((d>>>4).toString(16));b.push((d&15).toString(16))}return b.join("")},parse:function(a){for(var c=a.length,b=[],f=0;f<c;f+=2)b[f>>>3]|=parseInt(a.substr(f,
|
||||
2),16)<<24-4*(f%8);return new p.init(b,c/2)}},j=b.Latin1={stringify:function(a){var c=a.words;a=a.sigBytes;for(var b=[],f=0;f<a;f++)b.push(String.fromCharCode(c[f>>>2]>>>24-8*(f%4)&255));return b.join("")},parse:function(a){for(var c=a.length,b=[],f=0;f<c;f++)b[f>>>2]|=(a.charCodeAt(f)&255)<<24-8*(f%4);return new p.init(b,c)}},h=b.Utf8={stringify:function(a){try{return decodeURIComponent(escape(j.stringify(a)))}catch(c){throw Error("Malformed UTF-8 data");}},parse:function(a){return j.parse(unescape(encodeURIComponent(a)))}},
|
||||
r=d.BufferedBlockAlgorithm=k.extend({reset:function(){this._data=new p.init;this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=h.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var c=this._data,b=c.words,f=c.sigBytes,d=this.blockSize,e=f/(4*d),e=a?g.ceil(e):g.max((e|0)-this._minBufferSize,0);a=e*d;f=g.min(4*a,f);if(a){for(var k=0;k<a;k+=d)this._doProcessBlock(b,k);k=b.splice(0,a);c.sigBytes-=f}return new p.init(k,f)},clone:function(){var a=k.clone.call(this);
|
||||
a._data=this._data.clone();return a},_minBufferSize:0});d.Hasher=r.extend({cfg:k.extend(),init:function(a){this.cfg=this.cfg.extend(a);this.reset()},reset:function(){r.reset.call(this);this._doReset()},update:function(a){this._append(a);this._process();return this},finalize:function(a){a&&this._append(a);return this._doFinalize()},blockSize:16,_createHelper:function(a){return function(b,d){return(new a.init(d)).finalize(b)}},_createHmacHelper:function(a){return function(b,d){return(new s.HMAC.init(a,
|
||||
d)).finalize(b)}}});var s=e.algo={};return e}(Math);
|
||||
(function(){var g=CryptoJS,l=g.lib,e=l.WordArray,d=l.Hasher,m=[],l=g.algo.SHA1=d.extend({_doReset:function(){this._hash=new e.init([1732584193,4023233417,2562383102,271733878,3285377520])},_doProcessBlock:function(d,e){for(var b=this._hash.words,n=b[0],j=b[1],h=b[2],g=b[3],l=b[4],a=0;80>a;a++){if(16>a)m[a]=d[e+a]|0;else{var c=m[a-3]^m[a-8]^m[a-14]^m[a-16];m[a]=c<<1|c>>>31}c=(n<<5|n>>>27)+l+m[a];c=20>a?c+((j&h|~j&g)+1518500249):40>a?c+((j^h^g)+1859775393):60>a?c+((j&h|j&g|h&g)-1894007588):c+((j^h^
|
||||
g)-899497514);l=g;g=h;h=j<<30|j>>>2;j=n;n=c}b[0]=b[0]+n|0;b[1]=b[1]+j|0;b[2]=b[2]+h|0;b[3]=b[3]+g|0;b[4]=b[4]+l|0},_doFinalize:function(){var d=this._data,e=d.words,b=8*this._nDataBytes,g=8*d.sigBytes;e[g>>>5]|=128<<24-g%32;e[(g+64>>>9<<4)+14]=Math.floor(b/4294967296);e[(g+64>>>9<<4)+15]=b;d.sigBytes=4*e.length;this._process();return this._hash},clone:function(){var e=d.clone.call(this);e._hash=this._hash.clone();return e}});g.SHA1=d._createHelper(l);g.HmacSHA1=d._createHmacHelper(l)})();
|
||||
(function(){var g=CryptoJS,l=g.enc.Utf8;g.algo.HMAC=g.lib.Base.extend({init:function(e,d){e=this._hasher=new e.init;"string"==typeof d&&(d=l.parse(d));var g=e.blockSize,k=4*g;d.sigBytes>k&&(d=e.finalize(d));d.clamp();for(var p=this._oKey=d.clone(),b=this._iKey=d.clone(),n=p.words,j=b.words,h=0;h<g;h++)n[h]^=1549556828,j[h]^=909522486;p.sigBytes=b.sigBytes=k;this.reset()},reset:function(){var e=this._hasher;e.reset();e.update(this._iKey)},update:function(e){this._hasher.update(e);return this},finalize:function(e){var d=
|
||||
this._hasher;e=d.finalize(e);d.reset();return d.finalize(this._oKey.clone().concat(e))}})})();
|
||||
48
gaia/apps/wappush/js/link_action_handler.js
Executable file
48
gaia/apps/wappush/js/link_action_handler.js
Executable file
|
|
@ -0,0 +1,48 @@
|
|||
/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
|
||||
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
|
||||
|
||||
/* global ActivityPicker */
|
||||
|
||||
(function(exports) {
|
||||
'use strict';
|
||||
|
||||
var inProgress = false;
|
||||
|
||||
var LinkActionHandler = {
|
||||
onClick: function lah_onClick(event) {
|
||||
var dataset = event.target.dataset;
|
||||
var action = dataset.action;
|
||||
var type;
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* To avoid activity pile up, return immediately if the last activity is
|
||||
* still in progress. */
|
||||
if (inProgress) {
|
||||
return;
|
||||
}
|
||||
|
||||
inProgress = true;
|
||||
|
||||
type = action.replace('-link', '');
|
||||
|
||||
/* Use `LinkActionHandler.reset` (this.reset) as BOTH the success and
|
||||
* error callback. This ensure that any activities will be freed
|
||||
* regardless of their resulting state. */
|
||||
|
||||
ActivityPicker[type](
|
||||
dataset[type], this.reset, this.reset
|
||||
);
|
||||
},
|
||||
|
||||
reset: function lah_reset() {
|
||||
inProgress = false;
|
||||
}
|
||||
};
|
||||
|
||||
exports.LinkActionHandler = LinkActionHandler;
|
||||
}(this));
|
||||
412
gaia/apps/wappush/js/messagedb.js
Executable file
412
gaia/apps/wappush/js/messagedb.js
Executable file
|
|
@ -0,0 +1,412 @@
|
|||
/* global EventDispatcher, IDBKeyRange, Promise */
|
||||
|
||||
/* exported MessageDB */
|
||||
|
||||
'use strict';
|
||||
|
||||
var MessageDB = (function() {
|
||||
var DB_NAME = 'wappush';
|
||||
var DB_VERSION = 2;
|
||||
var MESSAGE_STORE_NAME = 'message';
|
||||
var VIEWED_STORE_NAME = 'viewed';
|
||||
var KEY_PATH = 'timestamp';
|
||||
var ID_INDEX = 'id';
|
||||
var ID_INDEX_KEYPATH = 'id';
|
||||
|
||||
var db = null;
|
||||
|
||||
/**
|
||||
* Turns an IndexedDB transaction in a promise that is resolved when the
|
||||
* transaction completes or is rejected if it fails.
|
||||
*
|
||||
* @param {Object} transaction An IDBTransaction object.
|
||||
* @return {Promise} A promise that is resolved when the transaction
|
||||
* completes or is rejected if an error occurs.
|
||||
*/
|
||||
function mdb_transactionPromise(transaction) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
transaction.oncomplete = function mdb_onTransactionComplete(event) {
|
||||
resolve();
|
||||
};
|
||||
transaction.onerror = function mdb_onTransactionError(event) {
|
||||
reject(event.target.error);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all the messages that have been marked as viewed, this is done at
|
||||
* startup to get rid of all the messages that have already been shown to the
|
||||
* user.
|
||||
*
|
||||
* @return {Object} A promise for this operation
|
||||
*/
|
||||
function mdb_deleteViewedMessages() {
|
||||
var transaction =
|
||||
db.transaction([ MESSAGE_STORE_NAME, VIEWED_STORE_NAME ], 'readwrite');
|
||||
var promise = mdb_transactionPromise(transaction);
|
||||
var store = transaction.objectStore(MESSAGE_STORE_NAME);
|
||||
var viewedStore = transaction.objectStore(VIEWED_STORE_NAME);
|
||||
var req = viewedStore.openCursor();
|
||||
|
||||
req.onsuccess = event => {
|
||||
var cursor = event.target.result;
|
||||
|
||||
if (cursor) {
|
||||
store.delete(cursor.value.timestamp);
|
||||
cursor.delete();
|
||||
cursor.continue();
|
||||
}
|
||||
};
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the database, creates it if it has not been created already. Returns
|
||||
* a promise that is resolved with the value of the database object or
|
||||
* rejected with an error object.
|
||||
*
|
||||
* @return {Object} A promise for this operation.
|
||||
*/
|
||||
function mdb_open() {
|
||||
if (db) {
|
||||
return Promise.resolve(db);
|
||||
}
|
||||
|
||||
var indexedDB;
|
||||
|
||||
try {
|
||||
indexedDB = window.indexedDB || window.webkitIndexedDB ||
|
||||
window.mozIndexedDB || window.msIndexedDB;
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
|
||||
if (!indexedDB) {
|
||||
return Promise.reject(new Error('IndexedDB is not available'));
|
||||
}
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
var req;
|
||||
|
||||
try {
|
||||
req = indexedDB.open(DB_NAME, DB_VERSION);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
return;
|
||||
}
|
||||
|
||||
req.onsuccess = function mdb_opened(event) {
|
||||
db = event.target.result;
|
||||
mdb_deleteViewedMessages().then(() => resolve(db));
|
||||
};
|
||||
req.onerror = function mdb_openError(event) {
|
||||
reject(event.target.error);
|
||||
};
|
||||
req.onupgradeneeded = function mdb_upgradeNeeded(event) {
|
||||
var db = event.target.result;
|
||||
var store = null;
|
||||
|
||||
switch (event.oldVersion) {
|
||||
case 0:
|
||||
store = db.createObjectStore(
|
||||
MESSAGE_STORE_NAME, { keyPath: KEY_PATH }
|
||||
);
|
||||
store.createIndex(ID_INDEX, ID_INDEX_KEYPATH, { unique: false });
|
||||
/* falls through */
|
||||
case 1:
|
||||
store = db.createObjectStore(
|
||||
VIEWED_STORE_NAME, { keyPath: KEY_PATH }
|
||||
);
|
||||
break; // We're done with the upgrade
|
||||
default:
|
||||
event.target.transaction.abort();
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a message executing the associated action. This deals with
|
||||
* messages with a 'delete' action by deleting all coresponding message and
|
||||
* with new messages by storing them.
|
||||
*
|
||||
* @param {Object} store The messages' object store.
|
||||
* @param {Object} message The message to be processed.
|
||||
* @returns {Boolean} Returns true if the message has been stored, false
|
||||
* if it's been deleted.
|
||||
*/
|
||||
function mdb_processMessage(store, message) {
|
||||
/* If the message has a 'delete' action delete all messages with the
|
||||
* corresponding 'si-id' field and drop the current message. All other
|
||||
* messages should be stored. */
|
||||
if (message.action === 'delete') {
|
||||
mdb_deleteById(store, message.id);
|
||||
return false;
|
||||
} else {
|
||||
store.put(message);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a message to the message database according to the out-of-order
|
||||
* delivery rules specified in WAP-167 6.2. Returns a promise that is
|
||||
* resolved when the message has been stored. This will dispatch all the
|
||||
* appropriate events.
|
||||
*
|
||||
* @fires new
|
||||
* @fires update
|
||||
* @fires discard
|
||||
* @fires delete
|
||||
*
|
||||
* @param {Object} message A parsed WAP Push message to be processed.
|
||||
*
|
||||
* @return {Object} A promise for this operation.
|
||||
*/
|
||||
function mdb_put(message) {
|
||||
return mdb_open().then(function mdb_openResolved(db) {
|
||||
var transaction = db.transaction(MESSAGE_STORE_NAME, 'readwrite');
|
||||
var promise = mdb_transactionPromise(transaction);
|
||||
var store = transaction.objectStore(MESSAGE_STORE_NAME);
|
||||
var stored;
|
||||
|
||||
if (!message.id) {
|
||||
// The message has no 'si-id' field, store it
|
||||
store.put(message);
|
||||
mdb_dispatchEvent('new', message);
|
||||
return promise;
|
||||
}
|
||||
|
||||
if (!message.created) {
|
||||
/* The message has a 'si-id' field but no creation time, process it and
|
||||
* store it if needed */
|
||||
stored = mdb_processMessage(store, message);
|
||||
mdb_dispatchEvent(stored ? 'new' : 'discard', message);
|
||||
return promise;
|
||||
}
|
||||
|
||||
/* The message has both a 'si-id' and a 'created' field, make it go
|
||||
* through the out-of-order delivery logic. This will check if the
|
||||
* message is new, updates an existing one or is just outdated and
|
||||
* needs to be discarded. */
|
||||
var index = store.index(ID_INDEX);
|
||||
var req = index.openCursor(IDBKeyRange.only(message.id));
|
||||
|
||||
req.onsuccess = function mdb_cursorSuccess(event) {
|
||||
var cursor = event.target.result;
|
||||
|
||||
if (cursor) {
|
||||
var storedMessage = cursor.value;
|
||||
|
||||
/* If this is a new version of an existing message, expire the
|
||||
* previous message and flag this message as updated. Older versions
|
||||
* are discarded right away with no further action required. */
|
||||
if (storedMessage.created) {
|
||||
if (storedMessage.created < message.created) {
|
||||
cursor.delete(storedMessage);
|
||||
message.timestamp = storedMessage.timestamp;
|
||||
stored = mdb_processMessage(store, message);
|
||||
mdb_dispatchEvent(stored ? 'update' : 'discard', message);
|
||||
} else {
|
||||
mdb_dispatchEvent('discard', message);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
cursor.continue();
|
||||
} else {
|
||||
/* We found no matching message with a 'created' property set,
|
||||
* process the message and store it if needed */
|
||||
stored = mdb_processMessage(store, message);
|
||||
mdb_dispatchEvent(stored ? 'new' : 'discard', message);
|
||||
}
|
||||
};
|
||||
|
||||
return promise;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all messages with the specified si-id. Generates a 'delete'
|
||||
* event for every message that was deleted.
|
||||
*
|
||||
* @param {Object} store The messages' object store.
|
||||
* @param {String} id The si-id of the messages to be deleted.
|
||||
*/
|
||||
function mdb_deleteById(store, id) {
|
||||
var index = store.index(ID_INDEX);
|
||||
var req = index.openCursor(id);
|
||||
|
||||
req.onsuccess = function mdb_openCursorSuccess(event) {
|
||||
var cursor = event.target.result;
|
||||
|
||||
if (cursor) {
|
||||
var message = cursor.value;
|
||||
|
||||
cursor.delete().onsuccess = function mdb_onDeleted() {
|
||||
mdb_dispatchEvent('delete', message);
|
||||
};
|
||||
cursor.continue();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a message and passes it to the success callback as its sole
|
||||
* parameter. Once retrieved the message is marked for removal and deleted
|
||||
* the next time the application is started except if it's a CP message in
|
||||
* which case it can only be deleted explicitly. A promise is returned that
|
||||
* will resolve to the message. If the message is not found then the promise
|
||||
* will resolve to the null value.
|
||||
*
|
||||
* @param {Number} timestamp The timestamp identifiying the message to be
|
||||
* retrieved.
|
||||
*
|
||||
* @return {Object} A promise for this operation.
|
||||
*/
|
||||
function mdb_retrieve(timestamp) {
|
||||
return mdb_open().then(function mdb_openResolved(db) {
|
||||
var transaction =
|
||||
db.transaction([ MESSAGE_STORE_NAME, VIEWED_STORE_NAME ], 'readwrite');
|
||||
var promise = mdb_transactionPromise(transaction);
|
||||
|
||||
var store = transaction.objectStore(MESSAGE_STORE_NAME);
|
||||
var req = store.get(timestamp.toString());
|
||||
var message = null;
|
||||
|
||||
req.onsuccess = function mdb_gotMessage(event) {
|
||||
if (event.target.result) {
|
||||
message = event.target.result;
|
||||
|
||||
if (message.type != 'text/vnd.wap.connectivity-xml') {
|
||||
var viewedStore = transaction.objectStore(VIEWED_STORE_NAME);
|
||||
|
||||
/* Mark message as viewed, we'll delete it the next time we
|
||||
* open the application. */
|
||||
viewedStore.put(message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return promise.then(() => {
|
||||
if (message) {
|
||||
return message;
|
||||
} else {
|
||||
return Promise.reject(
|
||||
new Error('Message with id ' + timestamp + ' not found')
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all the messages with the specified timestamp.
|
||||
*
|
||||
* @fires delete
|
||||
*
|
||||
* @param {String} timestamp The timestamp used to identify the messages to
|
||||
* be deleted.
|
||||
*
|
||||
* @return {Promise} A promise for this operation.
|
||||
*/
|
||||
function mdb_deleteByTimestamp(timestamp) {
|
||||
return mdb_open().then(function mdb_deleteCallback(db) {
|
||||
var transaction = db.transaction(MESSAGE_STORE_NAME, 'readwrite');
|
||||
var promise = mdb_transactionPromise(transaction);
|
||||
var store = transaction.objectStore(MESSAGE_STORE_NAME);
|
||||
|
||||
timestamp = timestamp.toString();
|
||||
store.get(timestamp).onsuccess = function(event) {
|
||||
var message = event.target.result;
|
||||
|
||||
store.delete(timestamp).onsuccess = function mdb_onDeleted() {
|
||||
mdb_dispatchEvent('delete', message);
|
||||
};
|
||||
};
|
||||
|
||||
return promise;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all messages from the database. Returns a promise that is
|
||||
* resolved when the operation has completed. This does not fire events.
|
||||
*
|
||||
* @return {Object} A promise for this operation.
|
||||
*/
|
||||
function mdb_clear() {
|
||||
return mdb_open().then(function mdb_openResolved(db) {
|
||||
var transaction =
|
||||
db.transaction([ MESSAGE_STORE_NAME, VIEWED_STORE_NAME ], 'readwrite');
|
||||
var promise = mdb_transactionPromise(transaction);
|
||||
var store = transaction.objectStore(MESSAGE_STORE_NAME);
|
||||
var viewedStore = transaction.objectStore(VIEWED_STORE_NAME);
|
||||
|
||||
store.clear();
|
||||
viewedStore.clear();
|
||||
|
||||
return promise;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches a new event to the registered event handlers.
|
||||
*
|
||||
* @param {String} type The event type.
|
||||
* @param {Object} target The event target, usually a message.
|
||||
*/
|
||||
function mdb_dispatchEvent(type, target) {
|
||||
/* We explicitly use the MessageDB singleton as the EventDispatcher code
|
||||
* requires the `this' reference to operate. */
|
||||
MessageDB.emit(type, target);
|
||||
}
|
||||
|
||||
/**
|
||||
* New event, emitted when a new message has been stored in the database
|
||||
*
|
||||
* @event MessageDB#new
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Update event, emitted when an existing message was updated.
|
||||
*
|
||||
* @event MessageDB#update
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Discard event, emitted when a message was discarded instea of being
|
||||
* stored in the database.
|
||||
*
|
||||
* @event MessageDB#discard
|
||||
*/
|
||||
|
||||
/**
|
||||
* Delete event : emitted when a message has been deleted
|
||||
*
|
||||
* @event MessageDB@delete
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
/**
|
||||
* List of events which can be listened to on this object.
|
||||
*/
|
||||
var mdb_allowedEvents = [
|
||||
'new',
|
||||
'update',
|
||||
'discard',
|
||||
'delete'
|
||||
];
|
||||
|
||||
return EventDispatcher.mixin({
|
||||
put: mdb_put,
|
||||
retrieve: mdb_retrieve,
|
||||
deleteByTimestamp: mdb_deleteByTimestamp,
|
||||
clear: mdb_clear
|
||||
}, mdb_allowedEvents);
|
||||
})();
|
||||
361
gaia/apps/wappush/js/parsed_doc.js
Executable file
361
gaia/apps/wappush/js/parsed_doc.js
Executable file
|
|
@ -0,0 +1,361 @@
|
|||
/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
|
||||
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
|
||||
|
||||
(function(exports) {
|
||||
'use strict';
|
||||
|
||||
var CONTENT_TYPE = 'application/xml';
|
||||
var AUTHTYPE_MAPPING = {
|
||||
'PAP': '1',
|
||||
'CHAP': '2'
|
||||
};
|
||||
var TYPE_MAPPING = {
|
||||
'w2': 'default',
|
||||
'w4': 'mms'
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a parsed WAP provisioning document. The most important elements
|
||||
* in this document are the APN objects the device needs to set up data calls.
|
||||
*
|
||||
* @param {String} provisioningDoc The WAP provisioning document (XML string).
|
||||
*/
|
||||
function ParsedProvisioningDoc(provisioningDoc) {
|
||||
/** WAP provisioning document */
|
||||
this._provisioningDoc = provisioningDoc;
|
||||
|
||||
/** Holds the list of APPLICATION (APPLICATION characteristic is used to
|
||||
define application protocol parameters and to describe the attributes
|
||||
of an application service access point available using the protocol)
|
||||
nodes in the document. */
|
||||
this._applicationNodes = null;
|
||||
|
||||
/** Holds the list of NAPDEF (NAPDEF characteristics define network access
|
||||
points) nodes in the document */
|
||||
this._napDefNodes = null;
|
||||
|
||||
/** Holds the list of PXLOGICAL (PXLOGICAL characteristics define logical
|
||||
proxies) nodes in the document. */
|
||||
this._pxLogicalNodes = null;
|
||||
|
||||
/** Array containing APN objects */
|
||||
this._apns = [],
|
||||
|
||||
/** Array containing APN proxy objects */
|
||||
this._proxies = [];
|
||||
|
||||
/** APNs are ready to use (flag) */
|
||||
this._apnsReady = false;
|
||||
}
|
||||
|
||||
ParsedProvisioningDoc.prototype = {
|
||||
/**
|
||||
* Given a PXLOGICAL node it returns the list of PXPHYSICAL (PXPHYSICAL
|
||||
* characteristics convey information on physical instances of a logical
|
||||
* proxy) nodes inside.
|
||||
*
|
||||
* @param {element} toProxyNode A PXLOGICAL node.
|
||||
*
|
||||
* @return {NodeList} List of PXPHYSICAL nodes.
|
||||
*/
|
||||
getPxPhysicalNodes: function pd_getPxPhysicalNodes(toProxyNode) {
|
||||
var toProxy = toProxyNode.getAttribute('value');
|
||||
var pxLogicalNode = null;
|
||||
for (var i = 0; i < this._pxLogicalNodes.length; i++) {
|
||||
var selector = 'parm[name="PROXY-ID"][value="' + toProxy + '"]';
|
||||
pxLogicalNode = this._pxLogicalNodes[i].querySelector(selector);
|
||||
if (pxLogicalNode) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!pxLogicalNode) {
|
||||
return null;
|
||||
}
|
||||
return this._pxLogicalNodes[i].querySelectorAll(
|
||||
'characteristic[type="PXPHYSICAL"]'
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Given a network access point id it returns the NAPDEF node.
|
||||
*
|
||||
* @param {String} toNapId Network access point id.
|
||||
*
|
||||
* @return {element} A NAPDEF node.
|
||||
*/
|
||||
getNapDefNode: function pd_getNapDefNode(toNapId) {
|
||||
for (var i = 0; i < this._napDefNodes.length; i++) {
|
||||
var selector = 'parm[name="NAPID"][value="' + toNapId + '"]';
|
||||
var napDefNode = this._napDefNodes[i].querySelector(selector);
|
||||
if (napDefNode) {
|
||||
return this._napDefNodes[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Parses the WAP provisioning document. It mainly parses the document and
|
||||
* saves the APN objects.
|
||||
*/
|
||||
parse: function pd_parse() {
|
||||
if (!this._provisioningDoc) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function.
|
||||
* Creates an untyped object from a PXPHYSICAL node.
|
||||
*
|
||||
* @param {element} pxPhysicalNode An PXPHYSICAL node.
|
||||
* @param {boolean} isMmsProxy Determines whether PXPHYSICAL node is a
|
||||
* proxy for MMS.
|
||||
*
|
||||
* @return {Object} An untyped object holding the properties of an
|
||||
* PXPHYSICAL node.
|
||||
*/
|
||||
function parsePxPhysicalNode(pxPhysicalNode, isMmsProxy) {
|
||||
var obj = {};
|
||||
var proxyKey = null, portKey = null;
|
||||
|
||||
proxyKey = isMmsProxy ? 'mmsproxy' : 'proxy';
|
||||
portKey = isMmsProxy ? 'mmsport' : 'port';
|
||||
|
||||
var pxAddrNode = pxPhysicalNode.querySelector('parm[name="PXADDR"]');
|
||||
if (pxAddrNode) {
|
||||
obj[proxyKey] = pxAddrNode.getAttribute('value');
|
||||
}
|
||||
|
||||
obj['TO-NAPID'] = [];
|
||||
var toNapIdNode = pxPhysicalNode.querySelector('parm[name="TO-NAPID"]');
|
||||
if (toNapIdNode) {
|
||||
obj['TO-NAPID'].push(toNapIdNode.getAttribute('value'));
|
||||
}
|
||||
|
||||
var portNodes =
|
||||
pxPhysicalNode.querySelectorAll('characteristic[type="PORT"]');
|
||||
for (var j = 0; j < portNodes.length; j++) {
|
||||
var portNode = portNodes[j].querySelector('parm[name="PORTNBR"]');
|
||||
obj[portKey] = portNode.getAttribute('value');
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function.
|
||||
* Creates an untyped object from a NAPDEF node.
|
||||
*
|
||||
* @param {element} napDefNode An NAPDEF node.
|
||||
*
|
||||
* @return {Object} An untyped object holding the properties of an
|
||||
* PXPHYSICAL node.
|
||||
*/
|
||||
function parseNapDefNode(napDefNode) {
|
||||
var obj = {};
|
||||
|
||||
var napIdNode = napDefNode.querySelector('parm[name="NAPID"]');
|
||||
if (napIdNode) {
|
||||
obj.NAPID = napIdNode.getAttribute('value');
|
||||
}
|
||||
|
||||
var nameNode = napDefNode.querySelector('parm[name="NAME"]');
|
||||
if (nameNode) {
|
||||
obj.carrier = nameNode.getAttribute('value');
|
||||
}
|
||||
|
||||
var apnNode = napDefNode.querySelector('parm[name="NAP-ADDRESS"]');
|
||||
if (apnNode) {
|
||||
obj.apn = apnNode.getAttribute('value');
|
||||
}
|
||||
|
||||
var napAuthInfoNode =
|
||||
napDefNode.querySelector('characteristic[type="NAPAUTHINFO"]');
|
||||
if (napAuthInfoNode) {
|
||||
var authTypeNode =
|
||||
napAuthInfoNode.querySelector('parm[name="AUTHTYPE"]');
|
||||
if (authTypeNode) {
|
||||
var authType = AUTHTYPE_MAPPING[
|
||||
authTypeNode.getAttribute('value')
|
||||
];
|
||||
if (authType) {
|
||||
obj.authtype = authType;
|
||||
}
|
||||
}
|
||||
|
||||
var authNameNode =
|
||||
napAuthInfoNode.querySelector('parm[name="AUTHNAME"]');
|
||||
if (authNameNode) {
|
||||
obj.user = authNameNode.getAttribute('value');
|
||||
}
|
||||
|
||||
var authSecretNode =
|
||||
napAuthInfoNode.querySelector('parm[name="AUTHSECRET"]');
|
||||
if (authSecretNode) {
|
||||
obj.password = authSecretNode.getAttribute('value');
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
var parser = new DOMParser();
|
||||
var domDocument = parser.parseFromString(this._provisioningDoc,
|
||||
CONTENT_TYPE);
|
||||
this._applicationNodes =
|
||||
domDocument.querySelectorAll('characteristic[type="APPLICATION"]');
|
||||
this._napDefNodes =
|
||||
domDocument.querySelectorAll('characteristic[type="NAPDEF"]');
|
||||
this._pxLogicalNodes =
|
||||
domDocument.querySelectorAll('characteristic[type="PXLOGICAL"]');
|
||||
|
||||
// The APPLICATION nodes can occur 0 or more times but it makes no sense
|
||||
// to continue when the current document doesn't have at least one one of
|
||||
// them.
|
||||
if (!this._applicationNodes) {
|
||||
return;
|
||||
}
|
||||
|
||||
var napDefNode = null;
|
||||
var apn = null;
|
||||
var type = [];
|
||||
var proxy = null;
|
||||
|
||||
var applicationNode = null;
|
||||
for (var j = 0; j < this._applicationNodes.length; j++) {
|
||||
applicationNode = this._applicationNodes[j];
|
||||
|
||||
var appIdNode = null, appId = null;
|
||||
appIdNode = applicationNode.querySelector('parm[name="APPID"]');
|
||||
appId = appIdNode.getAttribute('value');
|
||||
|
||||
var toProxyNodes = null;
|
||||
toProxyNodes =
|
||||
applicationNode.querySelectorAll('parm[name="TO-PROXY"]');
|
||||
|
||||
var toNapIdNodes = null;
|
||||
toNapIdNodes =
|
||||
applicationNode.querySelectorAll('parm[name="TO-NAPID"]');
|
||||
|
||||
var addrNode = null, addr = null;
|
||||
addrNode = applicationNode.querySelector('parm[name="ADDR"]');
|
||||
if (addrNode) {
|
||||
addr = addrNode.getAttribute('value');
|
||||
}
|
||||
|
||||
// Only look for APPLICATION nodes for the Browsing Enabler and AC for
|
||||
// the Multimedia Messaging System Enabler
|
||||
if (appId && (appId !== 'w2') && (appId !== 'w4')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!toNapIdNodes && !toProxyNodes) {
|
||||
// The TO-NAPID parameter refers to a network access point with a
|
||||
// matching NAPID parameter. At least we need one of them since it
|
||||
// links to the NAPDEF characteristic node containig the APN.
|
||||
|
||||
// The TO-PROXY parameter refers to a logical proxy with a matching
|
||||
// PROXY-ID. The logical proxy might contain a phisical proxy which
|
||||
// might point to an APN.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add the type for the APN relaying on the APPID param value. The type
|
||||
// will be added as an additional proprerty to the other APN properties.
|
||||
type = [];
|
||||
type.push(TYPE_MAPPING[appId]);
|
||||
|
||||
if (toProxyNodes) {
|
||||
// If the application characteristic node points to a logical proxy
|
||||
// characteristic node let's find the APN through the physical
|
||||
// proxy characteristic nodes that the logical proxy must contain.
|
||||
for (var l = 0; l < toProxyNodes.length; l++) {
|
||||
var pxPhysicalNodes = this.getPxPhysicalNodes(toProxyNodes[l]);
|
||||
|
||||
if (!pxPhysicalNodes) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (var m = 0; m < pxPhysicalNodes.length; m++) {
|
||||
proxy = parsePxPhysicalNode(pxPhysicalNodes[m],
|
||||
(TYPE_MAPPING[appId] === 'mms'));
|
||||
this._proxies.push(proxy);
|
||||
for (var n = 0; n < proxy['TO-NAPID'].length; n++) {
|
||||
napDefNode = this.getNapDefNode(proxy['TO-NAPID'][n]);
|
||||
apn = parseNapDefNode(napDefNode);
|
||||
// Add type property.
|
||||
apn.types = type;
|
||||
// Add mmsc property when MMS APN
|
||||
if ((TYPE_MAPPING[appId] === 'mms') && addr) {
|
||||
apn.mmsc = addr;
|
||||
}
|
||||
this._apns.push(apn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (toNapIdNodes) {
|
||||
for (var o = 0; o < toNapIdNodes.length; o++) {
|
||||
var toNapId = toNapIdNodes[o].getAttribute('value');
|
||||
if (!toNapId) {
|
||||
continue;
|
||||
}
|
||||
napDefNode = this.getNapDefNode(toNapId);
|
||||
apn = parseNapDefNode(napDefNode);
|
||||
// Add type property.
|
||||
apn.types = type;
|
||||
this._apns.push(apn);
|
||||
}
|
||||
}
|
||||
} // for this._applicationNodes
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the list of APN objects in the WAP provisioning document.
|
||||
*
|
||||
* @return {Array} The list of APN objects.
|
||||
*/
|
||||
getApns: function ppd_getApns() {
|
||||
function addProperties(src, dst) {
|
||||
for (var key in src) {
|
||||
dst[key] = src[key];
|
||||
}
|
||||
}
|
||||
|
||||
if (this._apnsReady) {
|
||||
return this._apns;
|
||||
}
|
||||
for (var i = 0; i < this._proxies.length; i++) {
|
||||
var proxy = this._proxies[i];
|
||||
for (var j = 0; j < proxy['TO-NAPID'].length; j++) {
|
||||
var TO_NAPID = proxy['TO-NAPID'][j];
|
||||
for (var k = 0; k < this._apns.length; k++) {
|
||||
var apn = this._apns[k];
|
||||
if (TO_NAPID === apn.NAPID) {
|
||||
addProperties(proxy, apn);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._apnsReady = true;
|
||||
return this._apns;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses a WAP provisioning document and returns the object holding the list
|
||||
* of APN objects in the document.
|
||||
*
|
||||
* @param {String} provisioningDoc The WAP provisioning document (XML string).
|
||||
*
|
||||
* @return {Object} A ParsedProvisioningDoc object.
|
||||
*/
|
||||
ParsedProvisioningDoc.from = function ppd_from(provisioningDoc) {
|
||||
var obj = new ParsedProvisioningDoc(provisioningDoc);
|
||||
obj.parse();
|
||||
|
||||
return obj;
|
||||
};
|
||||
|
||||
exports.ParsedProvisioningDoc = ParsedProvisioningDoc;
|
||||
})(window);
|
||||
244
gaia/apps/wappush/js/parsed_message.js
Executable file
244
gaia/apps/wappush/js/parsed_message.js
Executable file
|
|
@ -0,0 +1,244 @@
|
|||
/* global Promise, Provisioning, MessageDB */
|
||||
|
||||
(function(exports) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Default constructor, use it to copy objects. To parse objects from XML use
|
||||
* the factory function from()
|
||||
*
|
||||
* @param {Object} [obj] An object from which to copy the fields.
|
||||
*/
|
||||
function ParsedMessage(obj) {
|
||||
if (obj) {
|
||||
for (var key in obj) {
|
||||
this[key] = obj[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ParsedMessage.prototype = {
|
||||
/**
|
||||
* Constructor, this is for internal use only
|
||||
*/
|
||||
constructor: ParsedMessage,
|
||||
|
||||
/**
|
||||
* Creates an untyped object from a typed one
|
||||
*
|
||||
* @return {Object} An untyped representation of this object.
|
||||
*/
|
||||
toJSON: function pm_toJSON() {
|
||||
var obj = {
|
||||
type: this.type,
|
||||
sender: this.sender,
|
||||
serviceId: this.serviceId,
|
||||
timestamp: this.timestamp
|
||||
};
|
||||
|
||||
if (this.href) {
|
||||
obj.href = this.href;
|
||||
}
|
||||
|
||||
if (this.id) {
|
||||
obj.id = this.id;
|
||||
}
|
||||
|
||||
if (this.created) {
|
||||
obj.created = this.created;
|
||||
}
|
||||
|
||||
if (this.expires) {
|
||||
obj.expires = this.expires;
|
||||
}
|
||||
|
||||
if (this.text) {
|
||||
obj.text = this.text;
|
||||
}
|
||||
|
||||
if (this.action) {
|
||||
obj.action = this.action;
|
||||
}
|
||||
|
||||
if (this.provisioning) {
|
||||
obj.provisioning = this.provisioning;
|
||||
}
|
||||
|
||||
return obj;
|
||||
},
|
||||
|
||||
/**
|
||||
* Saves the message in the database. Returns a promise that resolves
|
||||
* to a string describing the status of the message: 'new' if the message
|
||||
* was new, 'updated' if the message updated an existing message or
|
||||
* 'discarded' if the message was discarded.
|
||||
*
|
||||
* @return {Object} A promise for this operation.
|
||||
*/
|
||||
save: function pm_save() {
|
||||
var self = this;
|
||||
var json_message = this.toJSON();
|
||||
|
||||
return MessageDB.put(json_message).then(function(status) {
|
||||
if (status === 'updated') {
|
||||
// In case the message was updated we must update the original too.
|
||||
self.timestamp = json_message.timestamp;
|
||||
}
|
||||
|
||||
return Promise.resolve(status);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if the message has already expired
|
||||
*/
|
||||
isExpired: function pm_isExpired() {
|
||||
return (this.expires && (this.expires < Date.now())) || false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the message unique identifier
|
||||
*
|
||||
* @returns {String} An identifier for this message
|
||||
*/
|
||||
getUniqueId: function pm_getUniqueId() {
|
||||
return this.timestamp;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse a message and returns an object holding its fields. The returned
|
||||
* object will contain the following fields:
|
||||
* - type: the content type of the message, either 'text/vnd.wap.si' or
|
||||
* 'text/vnd.wap.sl'
|
||||
* - sender: the sender of this message, a MSISDN
|
||||
* - timestamp: a timestamp taken when parsing the object
|
||||
* - href: optional for SI messages, required for SL, a URL to be displayed
|
||||
* - id: optional for SI messages, a pseudo-unique ID for the message
|
||||
* - created: optional for SI messages, creation time of this message
|
||||
* - expires: optional for SI messages, expiration time of this message
|
||||
* - action: optional for SI/SL message, action to be executed
|
||||
* - provisioning: only for CP messages, CP related object
|
||||
* - text: optional, text to be displayed
|
||||
*
|
||||
* @param {Object} message A WAP Push message as delivered by the system.
|
||||
* @param {Object} timestamp A timestamp for the message reception time.
|
||||
*
|
||||
* @return {Object} An object holding the contents of the WAP message as
|
||||
* separate fields.
|
||||
*/
|
||||
ParsedMessage.from = function pm_from(message, timestamp) {
|
||||
var parser = new DOMParser();
|
||||
var doc = parser.parseFromString(message.content, 'text/xml');
|
||||
var obj = new ParsedMessage();
|
||||
|
||||
// We don't check for errors as the message has already been validated
|
||||
|
||||
obj.type = message.contentType;
|
||||
obj.sender = message.sender;
|
||||
obj.serviceId = message.serviceId;
|
||||
obj.timestamp = timestamp.toString();
|
||||
|
||||
if (message.contentType === 'text/vnd.wap.si') {
|
||||
// SI message
|
||||
var indicationNode = doc.querySelector('indication');
|
||||
|
||||
// 'href' attribute, optional, string
|
||||
if (indicationNode.hasAttribute('href')) {
|
||||
obj.href = indicationNode.getAttribute('href');
|
||||
}
|
||||
|
||||
obj.text = indicationNode.textContent;
|
||||
|
||||
// 'created' attribute, optional, date in ISO 8601 format
|
||||
if (indicationNode.hasAttribute('created')) {
|
||||
var date = new Date(indicationNode.getAttribute('created'));
|
||||
|
||||
obj.created = date.getTime();
|
||||
}
|
||||
|
||||
// 'si-expires' attribute, optional, date in ISO 8601 format
|
||||
if (indicationNode.hasAttribute('si-expires')) {
|
||||
var expiresDate = new Date(indicationNode.getAttribute('si-expires'));
|
||||
|
||||
obj.expires = expiresDate.getTime();
|
||||
}
|
||||
|
||||
/* 'action' attribute, optional, string, defaults to 'signal-medium' when
|
||||
* not present in the incoming message, see WAP-167 7.2 */
|
||||
if (indicationNode.hasAttribute('action')) {
|
||||
obj.action = indicationNode.getAttribute('action');
|
||||
} else {
|
||||
obj.action = 'signal-medium';
|
||||
}
|
||||
|
||||
// 'si-id' attribute, optional, string
|
||||
if (indicationNode.hasAttribute('si-id')) {
|
||||
obj.id = indicationNode.getAttribute('si-id');
|
||||
} else if (obj.action === 'delete') {
|
||||
/* If the message has a 'delete' action but no 'si-id' field than it's
|
||||
* malformed and should be immediately discarded, see WAP-167 6.2 */
|
||||
return null;
|
||||
} else if (obj.href) {
|
||||
/* If the 'si-id' attribute is not specified then its value is
|
||||
* considered to be the same as the value of the 'href' attribute,
|
||||
* see WAP-167 5.2.1 */
|
||||
obj.id = obj.href;
|
||||
}
|
||||
} else if (message.contentType === 'text/vnd.wap.sl') {
|
||||
// SL message
|
||||
var slNode = doc.querySelector('sl');
|
||||
|
||||
// 'href' attribute, always present
|
||||
obj.href = slNode.getAttribute('href');
|
||||
|
||||
/* 'action' attribute, optional, string, defaults to 'execute-low' when
|
||||
* not present in the incoming message, see WAP-168 5.2 */
|
||||
if (slNode.hasAttribute('action')) {
|
||||
obj.action = slNode.getAttribute('action');
|
||||
} else {
|
||||
obj.action = 'execute-low';
|
||||
}
|
||||
} else if (message.contentType === 'text/vnd.wap.connectivity-xml') {
|
||||
// Client provisioning (CP) message
|
||||
obj.provisioning = Provisioning.fromMessage(message);
|
||||
|
||||
// Security information is mandatory for the application. The application
|
||||
// must discard any message with no security information.
|
||||
if (!obj.provisioning.authInfo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var authInfo = obj.provisioning.authInfo;
|
||||
// Let's discard any message that was not successfully authenticated by
|
||||
// gecko.
|
||||
if (authInfo.checked && !authInfo.pass) {
|
||||
return null;
|
||||
}
|
||||
|
||||
obj.text = 'cp-message-received';
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
return obj;
|
||||
};
|
||||
|
||||
/**
|
||||
* Loads the message corresponding to the specified timestamp from the
|
||||
* database. Returns a promise that resolves to the message. If the message
|
||||
* is not present the promise will be rejected.
|
||||
*
|
||||
* @param {Number} timestamp The timestamp of the message we want to
|
||||
* retrieve.
|
||||
*
|
||||
* @returns {Object} A promise for this operation.
|
||||
*/
|
||||
ParsedMessage.load = function pm_load(timestamp) {
|
||||
return MessageDB.retrieve(timestamp).then(function(message) {
|
||||
return new ParsedMessage(message);
|
||||
});
|
||||
};
|
||||
|
||||
exports.ParsedMessage = ParsedMessage;
|
||||
})(window);
|
||||
24
gaia/apps/wappush/js/provisioning.js
Executable file
24
gaia/apps/wappush/js/provisioning.js
Executable file
|
|
@ -0,0 +1,24 @@
|
|||
/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
|
||||
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
|
||||
|
||||
(function(exports) {
|
||||
'use strict';
|
||||
|
||||
function Provisioning(obj) {
|
||||
if (obj) {
|
||||
for (var key in obj) {
|
||||
this[key] = obj[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Provisioning.fromMessage = function p_fromMessage(message) {
|
||||
var obj = new Provisioning();
|
||||
|
||||
obj.provisioningDoc = message.content;
|
||||
obj.authInfo = message.authInfo;
|
||||
return obj;
|
||||
};
|
||||
|
||||
exports.Provisioning = Provisioning;
|
||||
})(window);
|
||||
88
gaia/apps/wappush/js/si_sl_screen_helper.js
Executable file
88
gaia/apps/wappush/js/si_sl_screen_helper.js
Executable file
|
|
@ -0,0 +1,88 @@
|
|||
/* global LinkActionHandler, Utils, WapPushManager */
|
||||
|
||||
/* exported SiSlScreenHelper */
|
||||
|
||||
'use strict';
|
||||
|
||||
var SiSlScreenHelper = (function() {
|
||||
/** Screen node */
|
||||
var screen = null;
|
||||
|
||||
/** Header node */
|
||||
var header = null;
|
||||
|
||||
/** Title of the message, usually holds the sender's number */
|
||||
var title = null;
|
||||
|
||||
/** Message container */
|
||||
var container = null;
|
||||
|
||||
/** Message text */
|
||||
var text = null;
|
||||
|
||||
/** Message link */
|
||||
var link = null;
|
||||
|
||||
function sssh_init() {
|
||||
// Retrieve the various page elements
|
||||
title = document.getElementById('title-si-sl');
|
||||
screen = document.getElementById('si-sl-screen');
|
||||
header = document.getElementById('header-si-sl');
|
||||
|
||||
container = screen.querySelector('.container');
|
||||
|
||||
text = container.querySelector('p');
|
||||
link = container.querySelector('a');
|
||||
|
||||
// Event handlers
|
||||
header.addEventListener('action', sssh_onClose);
|
||||
link.addEventListener(
|
||||
'click',
|
||||
LinkActionHandler.onClick.bind(LinkActionHandler)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
function sssh_populateScreen(message) {
|
||||
screen.hidden = false;
|
||||
|
||||
// Populate the message
|
||||
if (message && !message.isExpired()) {
|
||||
var l10nTitle = Utils.prepareMessageTitle(message);
|
||||
|
||||
navigator.mozL10n.setAttributes(title, l10nTitle.id, l10nTitle.args);
|
||||
text.textContent = message.text;
|
||||
link.textContent = message.href;
|
||||
link.href = message.href;
|
||||
link.dataset.url = message.href;
|
||||
} else {
|
||||
/* If we couldn't retrieve the message then it means that the
|
||||
* message has been expired before it was displayed. */
|
||||
navigator.mozL10n.setAttributes(title, 'wap-push-message');
|
||||
navigator.mozL10n.setAttributes(text, 'this-message-has-expired');
|
||||
link.textContent = '';
|
||||
link.href = '';
|
||||
link.dataset.url = '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the application
|
||||
*/
|
||||
function sssh_onClose() {
|
||||
WapPushManager.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the SI/SL screen helper
|
||||
*/
|
||||
function sssh_hide() {
|
||||
screen.hidden = true;
|
||||
}
|
||||
|
||||
return {
|
||||
init: sssh_init,
|
||||
hide: sssh_hide,
|
||||
populateScreen: sssh_populateScreen
|
||||
};
|
||||
})();
|
||||
8
gaia/apps/wappush/js/startup.js
Executable file
8
gaia/apps/wappush/js/startup.js
Executable file
|
|
@ -0,0 +1,8 @@
|
|||
/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
|
||||
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
|
||||
|
||||
/* global WapPushManager */
|
||||
|
||||
'use strict';
|
||||
|
||||
WapPushManager.init();
|
||||
171
gaia/apps/wappush/js/store.js
Executable file
171
gaia/apps/wappush/js/store.js
Executable file
|
|
@ -0,0 +1,171 @@
|
|||
/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
|
||||
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
|
||||
|
||||
/* exported StoreProvisioning */
|
||||
|
||||
/* global uuid */
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* The StoreProvisioning singleton is a database helper in charge of storing the
|
||||
* APNs into the settings database.
|
||||
*
|
||||
*/
|
||||
var StoreProvisioning = (function() {
|
||||
var CP_APN_KEY = 'ril.data.cp.apns';
|
||||
|
||||
var MCC_KEY = 'operatorvariant.mcc';
|
||||
|
||||
var MNC_KEY = 'operatorvariant.mnc';
|
||||
|
||||
var mccMncCodes = { mcc: '000', mnc: '00' };
|
||||
|
||||
/**
|
||||
* Loads the operator numeric value (MCC and MNC codes) stored into the
|
||||
* settings database. The APNs are stored relaying on these codes.
|
||||
*
|
||||
* @param {Number} iccCardIndex Index of the card on which the message was
|
||||
* received. We are going to add the apns to
|
||||
* the right card.
|
||||
* @param {function} callback It will be called once the work is done (only
|
||||
* useful for unit testing). This function doesn't
|
||||
* any parameter.
|
||||
*/
|
||||
function sp_getMccMncCodes(iccCardIndex, callback) {
|
||||
var settings = navigator.mozSettings;
|
||||
if (!settings) {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
iccCardIndex = iccCardIndex || 0;
|
||||
|
||||
var transaction = navigator.mozSettings.createLock();
|
||||
var mccRequest = transaction.get(MCC_KEY);
|
||||
mccRequest.onsuccess = function() {
|
||||
var mccs = mccRequest.result[MCC_KEY];
|
||||
if (!mccs || !Array.isArray(mccs) || !mccs[iccCardIndex]) {
|
||||
mccMncCodes.mcc = '000';
|
||||
} else {
|
||||
mccMncCodes.mcc = mccs[iccCardIndex];
|
||||
}
|
||||
var mncRequest = transaction.get(MNC_KEY);
|
||||
mncRequest.onsuccess = function() {
|
||||
var mncs = mncRequest.result[MNC_KEY];
|
||||
if (!mncs || !Array.isArray(mncs) || !mncs[iccCardIndex]) {
|
||||
mccMncCodes.mnc = '00';
|
||||
} else {
|
||||
mccMncCodes.mnc = mncs[iccCardIndex];
|
||||
}
|
||||
if (callback) {
|
||||
callback(mccMncCodes.mcc, mccMncCodes.mnc);
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the APNs into the settings database.
|
||||
*
|
||||
* @param {Array} parameters The array containing the APNs.
|
||||
* @param {Number} iccCardIndex Index of the card on which the message was
|
||||
* received.
|
||||
* @param {function} callback It will be called once the work is done (only
|
||||
* useful for unit testing). This function doesn't
|
||||
* accetp any parameter.
|
||||
*/
|
||||
function sp_provision(parameters, iccCardIndex, callback) {
|
||||
var existingApns = null;
|
||||
var newApns = {};
|
||||
|
||||
// Add an unique id to identify APNs with the same carrier name
|
||||
for (var i = 0; i < parameters.length; i++) {
|
||||
parameters[i]._id = uuid();
|
||||
}
|
||||
|
||||
var settings = navigator.mozSettings;
|
||||
if (!settings) {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
return;
|
||||
}
|
||||
sp_getMccMncCodes(iccCardIndex, function sp_getMccMncCodesCb() {
|
||||
var transaction = navigator.mozSettings.createLock();
|
||||
var load = transaction.get(CP_APN_KEY);
|
||||
load.onsuccess = function onsuccessCb() {
|
||||
existingApns = load.result[CP_APN_KEY];
|
||||
var data = {}, mcc = mccMncCodes.mcc, mnc = mccMncCodes.mnc;
|
||||
|
||||
if (!existingApns) {
|
||||
newApns[mcc] = {};
|
||||
newApns[mcc][mnc] = parameters;
|
||||
data[CP_APN_KEY] = newApns;
|
||||
} else {
|
||||
if (!existingApns[mcc]) {
|
||||
existingApns[mcc] = {};
|
||||
}
|
||||
if (!existingApns[mcc][mnc]) {
|
||||
existingApns[mcc][mnc] = [];
|
||||
}
|
||||
// TODO: Should we handle possible duplicated APNs?
|
||||
existingApns[mcc][mnc] = existingApns[mcc][mnc].concat(parameters);
|
||||
data[CP_APN_KEY] = existingApns;
|
||||
}
|
||||
transaction.set(data);
|
||||
|
||||
iccCardIndex = iccCardIndex || 0;
|
||||
var request = transaction.get('ril.data.apnSettings');
|
||||
request.onsuccess = function() {
|
||||
var apnSettings = request.result['ril.data.apnSettings'];
|
||||
if (!apnSettings || !Array.isArray(apnSettings)) {
|
||||
apnSettings = [[], []];
|
||||
}
|
||||
|
||||
var apnList = apnSettings[iccCardIndex];
|
||||
parameters.forEach(function(apn) {
|
||||
var apnEnabled = false;
|
||||
for (var i = 0; i < apnList.length; i++) {
|
||||
if (apnList[i].types[0] === apn.types[0]) {
|
||||
apnList[i] = apn;
|
||||
apnEnabled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!apnEnabled) {
|
||||
apnList.push(apn);
|
||||
}
|
||||
});
|
||||
|
||||
transaction.set({
|
||||
'ril.data.apnSettings': apnSettings,
|
||||
'apn.selections': null
|
||||
});
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
request.onerror = function onError() {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
};
|
||||
load.onerror = function onerrorCb() {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
getMccMncCodes: sp_getMccMncCodes,
|
||||
provision: sp_provision
|
||||
};
|
||||
})();
|
||||
56
gaia/apps/wappush/js/utils.js
Executable file
56
gaia/apps/wappush/js/utils.js
Executable file
|
|
@ -0,0 +1,56 @@
|
|||
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
|
||||
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
|
||||
|
||||
(function(exports) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Various utility functions mainly used to process strings and URLs
|
||||
*/
|
||||
var Utils = {
|
||||
/**
|
||||
* Retrieves the parameters from an URL and forms an object with them
|
||||
*
|
||||
* @param {String} input A string holding the parameters attached to an URL.
|
||||
*
|
||||
* @return {Object} An object built using the parameters.
|
||||
*/
|
||||
deserializeParameters: function ut_deserializeParameters(input) {
|
||||
var rparams = /([^?=&]+)(?:=([^&]*))?/g;
|
||||
var parsed = {};
|
||||
|
||||
input.replace(rparams, function($0, $1, $2) {
|
||||
parsed[$1] = decodeURIComponent($2);
|
||||
});
|
||||
|
||||
return parsed;
|
||||
},
|
||||
|
||||
/**
|
||||
* Generate message title according from message sender and the
|
||||
* serviceId if there are multiple SIMs.
|
||||
*
|
||||
* @param {Object} The message from which the title will be generated.
|
||||
*/
|
||||
prepareMessageTitle: function ut_prepareMessageTitle(message) {
|
||||
if (navigator.mozIccManager &&
|
||||
navigator.mozIccManager.iccIds.length > 1) {
|
||||
return {
|
||||
id: 'message-title-with-sim',
|
||||
args: {
|
||||
id: +message.serviceId + 1,
|
||||
title: message.sender
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
id: 'message-title',
|
||||
args: { title: message.sender }
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.Utils = Utils;
|
||||
|
||||
}(this));
|
||||
461
gaia/apps/wappush/js/wappush.js
Executable file
461
gaia/apps/wappush/js/wappush.js
Executable file
|
|
@ -0,0 +1,461 @@
|
|||
/* 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);
|
||||
51
gaia/apps/wappush/js/whitelist.js
Executable file
51
gaia/apps/wappush/js/whitelist.js
Executable file
|
|
@ -0,0 +1,51 @@
|
|||
/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
|
||||
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
|
||||
|
||||
/* global LazyLoader */
|
||||
/* exported WhiteList */
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* MSISDN whitelist, if not empty only the messages sent from the MSISDNs
|
||||
* present in the list will be shown to the user, others will be discarded.
|
||||
*/
|
||||
var WhiteList = {
|
||||
/** An array holding the list of MSISDNs */
|
||||
_whiteList: [],
|
||||
|
||||
/**
|
||||
* Read the whitelist.json file from disk and initialize the whitelist with
|
||||
* it. If the file is not present or empty the whitelist will be empty.
|
||||
*/
|
||||
init: function wl_init() {
|
||||
return LazyLoader.getJSON('js/whitelist.json')
|
||||
.then(
|
||||
(function wl_load(list) {
|
||||
this._whiteList = list;
|
||||
}).bind(this),
|
||||
function () {
|
||||
/**
|
||||
* This reject handler is important here because in case of
|
||||
* whitelist json file missing or empty, getJSON rejects the promise.
|
||||
* If this handler is not present here, the returned promise from
|
||||
* this function will fail for the outside code
|
||||
*/
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if the element is in the whitelist.
|
||||
*
|
||||
* @return {Boolean} true if the element is in the whitelist, always return
|
||||
* true if the whitelist is empty.
|
||||
*/
|
||||
has: function wl_has(value) {
|
||||
if (this._whiteList.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (this._whiteList.indexOf(value) !== -1);
|
||||
}
|
||||
};
|
||||
Loading…
Reference in a new issue