diff options
Diffstat (limited to 'mobile/android/components/FxAccountsPush.js')
-rw-r--r-- | mobile/android/components/FxAccountsPush.js | 164 |
1 files changed, 164 insertions, 0 deletions
diff --git a/mobile/android/components/FxAccountsPush.js b/mobile/android/components/FxAccountsPush.js new file mode 100644 index 0000000000..e6054a2de6 --- /dev/null +++ b/mobile/android/components/FxAccountsPush.js @@ -0,0 +1,164 @@ +/* jshint moz: true, esnext: true */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cu = Components.utils; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/Messaging.jsm"); +const { + PushCrypto, + getCryptoParams, +} = Cu.import("resource://gre/modules/PushCrypto.jsm"); + +XPCOMUtils.defineLazyServiceGetter(this, "PushService", + "@mozilla.org/push/Service;1", "nsIPushService"); +XPCOMUtils.defineLazyGetter(this, "_decoder", () => new TextDecoder()); + +const FXA_PUSH_SCOPE = "chrome://fxa-push"; +const Log = Cu.import("resource://gre/modules/AndroidLog.jsm", {}).AndroidLog.bind("FxAccountsPush"); + +function FxAccountsPush() { + Services.obs.addObserver(this, "FxAccountsPush:ReceivedPushMessageToDecode", false); + + Messaging.sendRequestForResult({ + type: "FxAccountsPush:Initialized" + }); +} + +FxAccountsPush.prototype = { + observe: function (subject, topic, data) { + switch (topic) { + case "android-push-service": + if (data === "android-fxa-subscribe") { + this._subscribe(); + } else if (data === "android-fxa-unsubscribe") { + this._unsubscribe(); + } + break; + case "FxAccountsPush:ReceivedPushMessageToDecode": + this._decodePushMessage(data); + break; + } + }, + + _subscribe() { + Log.i("FxAccountsPush _subscribe"); + return new Promise((resolve, reject) => { + PushService.subscribe(FXA_PUSH_SCOPE, + Services.scriptSecurityManager.getSystemPrincipal(), + (result, subscription) => { + if (Components.isSuccessCode(result)) { + Log.d("FxAccountsPush got subscription"); + resolve(subscription); + } else { + Log.w("FxAccountsPush failed to subscribe", result); + reject(new Error("FxAccountsPush failed to subscribe")); + } + }); + }) + .then(subscription => { + Messaging.sendRequest({ + type: "FxAccountsPush:Subscribe:Response", + subscription: { + pushCallback: subscription.endpoint, + pushPublicKey: urlsafeBase64Encode(subscription.getKey('p256dh')), + pushAuthKey: urlsafeBase64Encode(subscription.getKey('auth')) + } + }); + }) + .catch(err => { + Log.i("Error when registering FxA push endpoint " + err); + }); + }, + + _unsubscribe() { + Log.i("FxAccountsPush _unsubscribe"); + return new Promise((resolve) => { + PushService.unsubscribe(FXA_PUSH_SCOPE, + Services.scriptSecurityManager.getSystemPrincipal(), + (result, ok) => { + if (Components.isSuccessCode(result)) { + if (ok === true) { + Log.d("FxAccountsPush unsubscribed"); + } else { + Log.d("FxAccountsPush had no subscription to unsubscribe"); + } + } else { + Log.w("FxAccountsPush failed to unsubscribe", result); + } + return resolve(ok); + }); + }).catch(err => { + Log.e("Error during unsubscribe", err); + }); + }, + + _decodePushMessage(data) { + Log.i("FxAccountsPush _decodePushMessage"); + data = JSON.parse(data); + let { headers, message } = this._messageAndHeaders(data); + return new Promise((resolve, reject) => { + PushService.getSubscription(FXA_PUSH_SCOPE, + Services.scriptSecurityManager.getSystemPrincipal(), + (result, subscription) => { + if (!subscription) { + return reject(new Error("No subscription found")); + } + return resolve(subscription); + }); + }).then(subscription => { + return PushCrypto.decrypt(subscription.p256dhPrivateKey, + new Uint8Array(subscription.getKey("p256dh")), + new Uint8Array(subscription.getKey("auth")), + headers, message); + }) + .then(plaintext => { + let decryptedMessage = plaintext ? _decoder.decode(plaintext) : ""; + Messaging.sendRequestForResult({ + type: "FxAccountsPush:ReceivedPushMessageToDecode:Response", + message: decryptedMessage + }); + }) + .catch(err => { + Log.d("Error while decoding incoming message : " + err); + }); + }, + + // Copied from PushServiceAndroidGCM + _messageAndHeaders(data) { + // Default is no data (and no encryption). + let message = null; + let headers = null; + + if (data.message && data.enc && (data.enckey || data.cryptokey)) { + headers = { + encryption_key: data.enckey, + crypto_key: data.cryptokey, + encryption: data.enc, + encoding: data.con, + }; + // Ciphertext is (urlsafe) Base 64 encoded. + message = ChromeUtils.base64URLDecode(data.message, { + // The Push server may append padding. + padding: "ignore", + }); + } + return { headers, message }; + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]), + + classID: Components.ID("{d1bbb0fd-1d47-4134-9c12-d7b1be20b721}") +}; + +function urlsafeBase64Encode(key) { + return ChromeUtils.base64URLEncode(new Uint8Array(key), { pad: false }); +} + +var components = [ FxAccountsPush ]; +this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components); |