From f934acce6c238eff06a0e6efe9109b3925289cda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Jim=C3=A9nez?= Date: Tue, 30 Jul 2013 00:50:22 +0200 Subject: [PATCH] Bug 816564 - Silent SMS to Authenticate Device for Mobile Billing. Part 2: mozPaymentProvider. r=vicamo, fabrice --- b2g/chrome/content/payment.js | 213 +++++++++++++++++++++++++++++++--- 1 file changed, 195 insertions(+), 18 deletions(-) diff --git a/b2g/chrome/content/payment.js b/b2g/chrome/content/payment.js index f7759b7c615c..f88d88f104c0 100644 --- a/b2g/chrome/content/payment.js +++ b/b2g/chrome/content/payment.js @@ -9,10 +9,13 @@ "use strict"; -dump("======================= payment.js ======================= \n"); +let _DEBUG = false; +function _debug(s) { dump("== Payment flow == " + s + "\n"); } +_debug("Frame script injected"); let { classes: Cc, interfaces: Ci, utils: Cu } = Components; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); XPCOMUtils.defineLazyServiceGetter(this, "cpmm", "@mozilla.org/childprocessmessagemanager;1", @@ -26,20 +29,77 @@ XPCOMUtils.defineLazyServiceGetter(this, "uuidgen", XPCOMUtils.defineLazyServiceGetter(this, "mobileConnection", "@mozilla.org/ril/content-helper;1", "nsIMobileConnectionProvider"); -#endif +XPCOMUtils.defineLazyServiceGetter(this, "smsService", + "@mozilla.org/sms/smsservice;1", + "nsISmsService"); + +const kSilentSmsReceivedTopic = "silent-sms-received"; + +const MOBILEMESSAGECALLBACK_CID = + Components.ID("{b484d8c9-6be4-4f94-ab60-c9c7ebcc853d}"); + +// In order to send messages through nsISmsService, we need to implement +// nsIMobileMessageCallback, as the WebSMS API implementation is not usable +// from JS. +function SilentSmsRequest() { +} +SilentSmsRequest.prototype = { + __exposedProps__: { + onsuccess: 'rw', + onerror: 'rw' + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIMobileMessageCallback]), + + classID: MOBILEMESSAGECALLBACK_CID, + + set onsuccess(aSuccessCallback) { + this._onsuccess = aSuccessCallback; + }, + + set onerror(aErrorCallback) { + this._onerror = aErrorCallback; + }, + + notifyMessageSent: function notifyMessageSent(aMessage) { + if (_DEBUG) { + _debug("Silent message successfully sent"); + } + this._onsuccess(aMessage); + }, + + notifySendMessageFailed: function notifySendMessageFailed(aError) { + if (_DEBUG) { + _debug("Error sending silent message " + aError); + } + this._onerror(aError); + } +}; +#endif const kClosePaymentFlowEvent = "close-payment-flow-dialog"; -let _requestId; +let gRequestId; + +let gBrowser = Services.wm.getMostRecentWindow("navigator:browser"); let PaymentProvider = { - +#ifdef MOZ_B2G_RIL __exposedProps__: { paymentSuccess: 'r', paymentFailed: 'r', - iccIds: 'r' + iccIds: 'r', + sendSilentSms: 'r', + observeSilentSms: 'r', + removeSilentSmsObserver: 'r' }, +#else + __exposedProps__: { + paymentSuccess: 'r', + paymentFailed: 'r' + }, +#endif _closePaymentFlowDialog: function _closePaymentFlowDialog(aCallback) { // After receiving the payment provider confirmation about the @@ -47,8 +107,7 @@ let PaymentProvider = { // payment flow dialog and return to the caller application. let id = kClosePaymentFlowEvent + "-" + uuidgen.generateUUID().toString(); - let browser = Services.wm.getMostRecentWindow("navigator:browser"); - let content = browser.getContentWindow(); + let content = gBrowser.getContentWindow(); if (!content) { return; } @@ -56,7 +115,7 @@ let PaymentProvider = { let detail = { type: kClosePaymentFlowEvent, id: id, - requestId: _requestId + requestId: gRequestId }; // In order to avoid race conditions, we wait for the UI to notify that @@ -77,49 +136,167 @@ let PaymentProvider = { glue.cleanup(); }); - browser.shell.sendChromeEvent(detail); + gBrowser.shell.sendChromeEvent(detail); + +#ifdef MOZ_B2G_RIL + this._cleanUp(); +#endif }, paymentSuccess: function paymentSuccess(aResult) { + if (_DEBUG) { + _debug("paymentSuccess " + aResult); + } + PaymentProvider._closePaymentFlowDialog(function notifySuccess() { - if (!_requestId) { + if (!gRequestId) { return; } cpmm.sendAsyncMessage("Payment:Success", { result: aResult, - requestId: _requestId }); + requestId: gRequestId }); }); }, paymentFailed: function paymentFailed(aErrorMsg) { + if (_DEBUG) { + _debug("paymentFailed " + aErrorMsg); + } + PaymentProvider._closePaymentFlowDialog(function notifyError() { - if (!_requestId) { + if (!gRequestId) { return; } cpmm.sendAsyncMessage("Payment:Failed", { errorMsg: aErrorMsg, - requestId: _requestId }); + requestId: gRequestId }); }); }, - get iccIds() { #ifdef MOZ_B2G_RIL + get iccIds() { // Until bug 814629 is done, we only have support for a single SIM, so we // can only provide a single ICC ID. However, we return an array so the // payment provider facing API won't need to change once we support // multiple SIMs. return [mobileConnection.iccInfo.iccid]; -#else - return null; -#endif }, + _silentNumbers: null, + _silentSmsObservers: null, + + sendSilentSms: function sendSilentSms(aNumber, aMessage) { + if (_DEBUG) { + _debug("Sending silent message " + aNumber + " - " + aMessage); + } + + let request = new SilentSmsRequest(); + smsService.send(aNumber, aMessage, true, request); + return request; + }, + + observeSilentSms: function observeSilentSms(aNumber, aCallback) { + if (_DEBUG) { + _debug("observeSilentSms " + aNumber); + } + + if (!this._silentSmsObservers) { + this._silentSmsObservers = {}; + this._silentNumbers = []; + Services.obs.addObserver(this._onSilentSms.bind(this), + kSilentSmsReceivedTopic, + false); + } + + if (!this._silentSmsObservers[aNumber]) { + this._silentSmsObservers[aNumber] = []; + this._silentNumbers.push(aNumber); + smsService.addSilentNumber(aNumber); + } + + if (this._silentSmsObservers[aNumber].indexOf(aCallback) == -1) { + this._silentSmsObservers[aNumber].push(aCallback); + } + }, + + removeSilentSmsObserver: function removeSilentSmsObserver(aNumber, aCallback) { + if (_DEBUG) { + _debug("removeSilentSmsObserver " + aNumber); + } + + if (!this._silentSmsObservers || !this._silentSmsObservers[aNumber]) { + if (_DEBUG) { + _debug("No observers for " + aNumber); + } + return; + } + + let index = this._silentSmsObservers[aNumber].indexOf(aCallback); + if (index != -1) { + this._silentSmsObservers[aNumber].splice(index, 1); + if (this._silentSmsObservers[aNumber].length == 0) { + this._silentSmsObservers[aNumber] = null; + this._silentNumbers.splice(this._silentNumbers.indexOf(aNumber), 1); + smsService.removeSilentNumber(aNumber); + } + } else if (_DEBUG) { + _debug("No callback found for " + aNumber); + } + }, + + _onSilentSms: function _onSilentSms(aSubject, aTopic, aData) { + if (_DEBUG) { + _debug("Got silent message! " + aSubject.sender + " - " + aSubject.body); + } + + let number = aSubject.sender; + if (!number || this._silentNumbers.indexOf(number) == -1) { + if (_DEBUG) { + _debug("No observers for " + number); + } + return; + } + + this._silentSmsObservers[number].forEach(function(callback) { + callback(aSubject); + }); + }, + + _cleanUp: function _cleanUp() { + if (_DEBUG) { + _debug("Cleaning up!"); + } + + if (!this._silentNumbers) { + return; + } + + while (this._silentNumbers.length) { + let number = this._silentNumbers.pop(); + smsService.removeSilentNumber(number); + } + this._silentNumbers = null; + this._silentSmsObservers = null; + Services.obs.removeObserver(this._onSilentSms, kSilentSmsReceivedTopic); + } +#endif }; // We save the identifier of the DOM request, so we can dispatch the results // of the payment flow to the appropriate content process. addMessageListener("Payment:LoadShim", function receiveMessage(aMessage) { - _requestId = aMessage.json.requestId; + gRequestId = aMessage.json.requestId; }); addEventListener("DOMWindowCreated", function(e) { content.wrappedJSObject.mozPaymentProvider = PaymentProvider; }); + +#ifdef MOZ_B2G_RIL +// If the trusted dialog is not closed via paymentSuccess or paymentFailed +// a mozContentEvent with type 'cancel' is sent from the UI. We need to listen +// for this event to clean up the silent sms observers if any exists. +gBrowser.getContentWindow().addEventListener("mozContentEvent", function(e) { + if (e.detail.type === "cancel") { + PaymentProvider._cleanUp(); + } +}); +#endif