b2g/chrome/content/payment.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
     2 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
     3 /* This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     5  * You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 // This JS shim contains the callbacks to fire DOMRequest events for
     8 // navigator.pay API within the payment processor's scope.
    10 "use strict";
    12 let { classes: Cc, interfaces: Ci, utils: Cu }  = Components;
    13 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    14 Cu.import("resource://gre/modules/Services.jsm");
    16 const PREF_DEBUG = "dom.payment.debug";
    18 let _debug;
    19 try {
    20   _debug = Services.prefs.getPrefType(PREF_DEBUG) == Ci.nsIPrefBranch.PREF_BOOL
    21            && Services.prefs.getBoolPref(PREF_DEBUG);
    22 } catch(e){
    23   _debug = false;
    24 }
    26 function LOG(s) {
    27   if (!_debug) {
    28     return;
    29   }
    30   dump("== Payment flow == " + s + "\n");
    31 }
    33 function LOGE(s) {
    34   dump("== Payment flow ERROR == " + s + "\n");
    35 }
    37 if (_debug) {
    38   LOG("Frame script injected");
    39 }
    41 XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
    42                                    "@mozilla.org/childprocessmessagemanager;1",
    43                                    "nsIMessageSender");
    45 XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
    46                                    "@mozilla.org/uuid-generator;1",
    47                                    "nsIUUIDGenerator");
    49 XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
    50                                   "resource://gre/modules/SystemAppProxy.jsm");
    52 #ifdef MOZ_B2G_RIL
    53 XPCOMUtils.defineLazyServiceGetter(this, "gRil",
    54                                    "@mozilla.org/ril;1",
    55                                    "nsIRadioInterfaceLayer");
    57 XPCOMUtils.defineLazyServiceGetter(this, "iccProvider",
    58                                    "@mozilla.org/ril/content-helper;1",
    59                                    "nsIIccProvider");
    61 XPCOMUtils.defineLazyServiceGetter(this, "smsService",
    62                                    "@mozilla.org/sms/smsservice;1",
    63                                    "nsISmsService");
    65 XPCOMUtils.defineLazyServiceGetter(this, "gSettingsService",
    66                                    "@mozilla.org/settingsService;1",
    67                                    "nsISettingsService");
    69 const kSilentSmsReceivedTopic = "silent-sms-received";
    70 const kMozSettingsChangedObserverTopic = "mozsettings-changed";
    72 const kRilDefaultDataServiceId = "ril.data.defaultServiceId";
    73 const kRilDefaultPaymentServiceId = "ril.payment.defaultServiceId";
    75 const MOBILEMESSAGECALLBACK_CID =
    76   Components.ID("{b484d8c9-6be4-4f94-ab60-c9c7ebcc853d}");
    78 // In order to send messages through nsISmsService, we need to implement
    79 // nsIMobileMessageCallback, as the WebSMS API implementation is not usable
    80 // from JS.
    81 function SilentSmsRequest() {
    82 }
    84 SilentSmsRequest.prototype = {
    85   __exposedProps__: {
    86     onsuccess: "rw",
    87     onerror: "rw"
    88   },
    90   QueryInterface: XPCOMUtils.generateQI([Ci.nsIMobileMessageCallback]),
    92   classID: MOBILEMESSAGECALLBACK_CID,
    94   set onsuccess(aSuccessCallback) {
    95     this._onsuccess = aSuccessCallback;
    96   },
    98   set onerror(aErrorCallback) {
    99     this._onerror = aErrorCallback;
   100   },
   102   notifyMessageSent: function notifyMessageSent(aMessage) {
   103     if (_debug) {
   104       LOG("Silent message successfully sent");
   105     }
   106     this._onsuccess(aMessage);
   107   },
   109   notifySendMessageFailed: function notifySendMessageFailed(aError) {
   110     LOGE("Error sending silent message " + aError);
   111     this._onerror(aError);
   112   }
   113 };
   115 function PaymentSettings() {
   116   Services.obs.addObserver(this, kMozSettingsChangedObserverTopic, false);
   118   [kRilDefaultDataServiceId, kRilDefaultPaymentServiceId].forEach(setting => {
   119     gSettingsService.createLock().get(setting, this);
   120   });
   121 }
   123 PaymentSettings.prototype = {
   124   QueryInterface: XPCOMUtils.generateQI([Ci.nsISettingsServiceCallback,
   125                                          Ci.nsIObserver]),
   127   dataServiceId: 0,
   128   _paymentServiceId: 0,
   130   get paymentServiceId() {
   131     return this._paymentServiceId;
   132   },
   134   set paymentServiceId(serviceId) {
   135     // We allow the payment provider to set the service ID that will be used
   136     // for the payment process.
   137     // This service ID will be the one used by the silent SMS flow.
   138     // If the payment is done with an external SIM, the service ID must be set
   139     // to null.
   140     if (serviceId != null && serviceId >= gRil.numRadioInterfaces) {
   141       LOGE("Invalid service ID " + serviceId);
   142       return;
   143     }
   145     gSettingsService.createLock().set(kRilDefaultPaymentServiceId,
   146                                       serviceId, null);
   147     this._paymentServiceId = serviceId;
   148   },
   150   setServiceId: function(aName, aValue) {
   151     switch (aName) {
   152       case kRilDefaultDataServiceId:
   153         this.dataServiceId = aValue;
   154         if (_debug) {
   155           LOG("dataServiceId " + this.dataServiceId);
   156         }
   157         break;
   158       case kRilDefaultPaymentServiceId:
   159         this._paymentServiceId = aValue;
   160         if (_debug) {
   161           LOG("paymentServiceId " + this._paymentServiceId);
   162         }
   163         break;
   164     }
   165   },
   167   handle: function(aName, aValue) {
   168     if (aName != kRilDefaultDataServiceId) {
   169       return;
   170     }
   172     this.setServiceId(aName, aValue);
   173   },
   175   observe: function(aSubject, aTopic, aData) {
   176     if (aTopic != kMozSettingsChangedObserverTopic) {
   177       return;
   178     }
   180     try {
   181       let setting = JSON.parse(aData);
   182       if (!setting.key ||
   183           (setting.key !== kRilDefaultDataServiceId &&
   184            setting.key !== kRilDefaultPaymentServiceId)) {
   185         return;
   186       }
   187       this.setServiceId(setting.key, setting.value);
   188     } catch (e) {
   189       LOGE(e);
   190     }
   191   },
   193   cleanup: function() {
   194     Services.obs.removeObserver(this, kMozSettingsChangedObserverTopic);
   195   }
   196 };
   197 #endif
   199 const kClosePaymentFlowEvent = "close-payment-flow-dialog";
   201 let gRequestId;
   203 let PaymentProvider = {
   204 #ifdef MOZ_B2G_RIL
   205   __exposedProps__: {
   206     paymentSuccess: "r",
   207     paymentFailed: "r",
   208     paymentServiceId: "rw",
   209     iccInfo: "r",
   210     sendSilentSms: "r",
   211     observeSilentSms: "r",
   212     removeSilentSmsObserver: "r"
   213   },
   214 #else
   215   __exposedProps__: {
   216     paymentSuccess: "r",
   217     paymentFailed: "r"
   218   },
   219 #endif
   221   _init: function _init() {
   222 #ifdef MOZ_B2G_RIL
   223     this._settings = new PaymentSettings();
   224 #endif
   225   },
   227   _closePaymentFlowDialog: function _closePaymentFlowDialog(aCallback) {
   228     // After receiving the payment provider confirmation about the
   229     // successful or failed payment flow, we notify the UI to close the
   230     // payment flow dialog and return to the caller application.
   231     let id = kClosePaymentFlowEvent + "-" + uuidgen.generateUUID().toString();
   233     let detail = {
   234       type: kClosePaymentFlowEvent,
   235       id: id,
   236       requestId: gRequestId
   237     };
   239     // In order to avoid race conditions, we wait for the UI to notify that
   240     // it has successfully closed the payment flow and has recovered the
   241     // caller app, before notifying the parent process to fire the success
   242     // or error event over the DOMRequest.
   243     SystemAppProxy.addEventListener("mozContentEvent",
   244                                function closePaymentFlowReturn(evt) {
   245       if (evt.detail.id == id && aCallback) {
   246         aCallback();
   247       }
   249       SystemAppProxy.removeEventListener("mozContentEvent",
   250                                   closePaymentFlowReturn);
   252       let glue = Cc["@mozilla.org/payment/ui-glue;1"]
   253                    .createInstance(Ci.nsIPaymentUIGlue);
   254       glue.cleanup();
   255     });
   257     SystemAppProxy.dispatchEvent(detail);
   259 #ifdef MOZ_B2G_RIL
   260     this._cleanUp();
   261 #endif
   262   },
   264   paymentSuccess: function paymentSuccess(aResult) {
   265     if (_debug) {
   266       LOG("paymentSuccess " + aResult);
   267     }
   269     PaymentProvider._closePaymentFlowDialog(function notifySuccess() {
   270       if (!gRequestId) {
   271         return;
   272       }
   273       cpmm.sendAsyncMessage("Payment:Success", { result: aResult,
   274                                                  requestId: gRequestId });
   275     });
   276   },
   278   paymentFailed: function paymentFailed(aErrorMsg) {
   279     LOGE("paymentFailed " + aErrorMsg);
   281     PaymentProvider._closePaymentFlowDialog(function notifyError() {
   282       if (!gRequestId) {
   283         return;
   284       }
   285       cpmm.sendAsyncMessage("Payment:Failed", { errorMsg: aErrorMsg,
   286                                                 requestId: gRequestId });
   287     });
   288   },
   290 #ifdef MOZ_B2G_RIL
   291   get paymentServiceId() {
   292     return this._settings.paymentServiceId;
   293   },
   295   set paymentServiceId(serviceId) {
   296     this._settings.paymentServiceId = serviceId;
   297   },
   299   // We expose to the payment provider the information of all the SIMs
   300   // available in the device. iccInfo is an object of this form:
   301   //  {
   302   //    "serviceId1": {
   303   //       mcc: <string>,
   304   //       mnc: <string>,
   305   //       iccId: <string>,
   306   //       dataPrimary: <boolean>
   307   //     },
   308   //    "serviceIdN": {...}
   309   //  }
   310   get iccInfo() {
   311     if (!this._iccInfo) {
   312       this._iccInfo = {};
   313       for (let i = 0; i < gRil.numRadioInterfaces; i++) {
   314         let info = iccProvider.getIccInfo(i);
   315         if (!info) {
   316           LOGE("Tried to get the ICC info for an invalid service ID " + i);
   317           continue;
   318         }
   320         this._iccInfo[i] = {
   321           iccId: info.iccid,
   322           mcc: info.mcc,
   323           mnc: info.mnc,
   324           dataPrimary: i == this._settings.dataServiceId
   325         };
   326       }
   327     }
   329     return Cu.cloneInto(this._iccInfo, content);
   330   },
   332   _silentNumbers: null,
   333   _silentSmsObservers: null,
   335   sendSilentSms: function sendSilentSms(aNumber, aMessage) {
   336     if (_debug) {
   337       LOG("Sending silent message " + aNumber + " - " + aMessage);
   338     }
   340     let request = new SilentSmsRequest();
   342     if (this._settings.paymentServiceId === null) {
   343       LOGE("No payment service ID set. Cannot send silent SMS");
   344       let runnable = {
   345         run: function run() {
   346           request.notifySendMessageFailed("NO_PAYMENT_SERVICE_ID");
   347         }
   348       };
   349       Services.tm.currentThread.dispatch(runnable,
   350                                          Ci.nsIThread.DISPATCH_NORMAL);
   351       return request;
   352     }
   354     smsService.send(this._settings.paymentServiceId, aNumber, aMessage, true,
   355                     request);
   356     return request;
   357   },
   359   observeSilentSms: function observeSilentSms(aNumber, aCallback) {
   360     if (_debug) {
   361       LOG("observeSilentSms " + aNumber);
   362     }
   364     if (!this._silentSmsObservers) {
   365       this._silentSmsObservers = {};
   366       this._silentNumbers = [];
   367       Services.obs.addObserver(this._onSilentSms.bind(this),
   368                                kSilentSmsReceivedTopic,
   369                                false);
   370     }
   372     if (!this._silentSmsObservers[aNumber]) {
   373       this._silentSmsObservers[aNumber] = [];
   374       this._silentNumbers.push(aNumber);
   375       smsService.addSilentNumber(aNumber);
   376     }
   378     if (this._silentSmsObservers[aNumber].indexOf(aCallback) == -1) {
   379       this._silentSmsObservers[aNumber].push(aCallback);
   380     }
   381   },
   383   removeSilentSmsObserver: function removeSilentSmsObserver(aNumber, aCallback) {
   384     if (_debug) {
   385       LOG("removeSilentSmsObserver " + aNumber);
   386     }
   388     if (!this._silentSmsObservers || !this._silentSmsObservers[aNumber]) {
   389       if (_debug) {
   390         LOG("No observers for " + aNumber);
   391       }
   392       return;
   393     }
   395     let index = this._silentSmsObservers[aNumber].indexOf(aCallback);
   396     if (index != -1) {
   397       this._silentSmsObservers[aNumber].splice(index, 1);
   398       if (this._silentSmsObservers[aNumber].length == 0) {
   399         this._silentSmsObservers[aNumber] = null;
   400         this._silentNumbers.splice(this._silentNumbers.indexOf(aNumber), 1);
   401         smsService.removeSilentNumber(aNumber);
   402       }
   403     } else if (_debug) {
   404       LOG("No callback found for " + aNumber);
   405     }
   406   },
   408   _onSilentSms: function _onSilentSms(aSubject, aTopic, aData) {
   409     if (_debug) {
   410       LOG("Got silent message! " + aSubject.sender + " - " + aSubject.body);
   411     }
   413     let number = aSubject.sender;
   414     if (!number || this._silentNumbers.indexOf(number) == -1) {
   415       if (_debug) {
   416         LOG("No observers for " + number);
   417       }
   418       return;
   419     }
   421     // If the service ID is null it means that the payment provider asked the
   422     // user for her MSISDN, so we are in a MT only SMS auth flow. In this case
   423     // we manually set the service ID to the one corresponding with the SIM
   424     // that received the SMS.
   425     if (this._settings.paymentServiceId === null) {
   426       let i = 0;
   427       while(i < gRil.numRadioInterfaces) {
   428         if (this.iccInfo[i].iccId === aSubject.iccId) {
   429           this._settings.paymentServiceId = i;
   430           break;
   431         }
   432         i++;
   433       }
   434     }
   436     this._silentSmsObservers[number].forEach(function(callback) {
   437       callback(aSubject);
   438     });
   439   },
   441   _cleanUp: function _cleanUp() {
   442     if (_debug) {
   443       LOG("Cleaning up!");
   444     }
   446     if (!this._silentNumbers) {
   447       return;
   448     }
   450     while (this._silentNumbers.length) {
   451       let number = this._silentNumbers.pop();
   452       smsService.removeSilentNumber(number);
   453     }
   454     this._silentNumbers = null;
   455     this._silentSmsObservers = null;
   456     this._settings.cleanup();
   457     Services.obs.removeObserver(this._onSilentSms, kSilentSmsReceivedTopic);
   458   }
   459 #endif
   460 };
   462 // We save the identifier of the DOM request, so we can dispatch the results
   463 // of the payment flow to the appropriate content process.
   464 addMessageListener("Payment:LoadShim", function receiveMessage(aMessage) {
   465   gRequestId = aMessage.json.requestId;
   466   PaymentProvider._init();
   467 });
   469 addEventListener("DOMWindowCreated", function(e) {
   470   content.wrappedJSObject.mozPaymentProvider = PaymentProvider;
   471 });
   473 #ifdef MOZ_B2G_RIL
   474 // If the trusted dialog is not closed via paymentSuccess or paymentFailed
   475 // a mozContentEvent with type 'cancel' is sent from the UI. We need to listen
   476 // for this event to clean up the silent sms observers if any exists.
   477 SystemAppProxy.addEventListener("mozContentEvent", function(e) {
   478   if (e.detail.type === "cancel") {
   479     PaymentProvider._cleanUp();
   480   }
   481 });
   482 #endif

mercurial