b2g/chrome/content/payment.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

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

mercurial