From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- dom/push/PushServiceAndroidGCM.jsm | 275 +++++++++++++++++++++++++++++++++++++ 1 file changed, 275 insertions(+) create mode 100644 dom/push/PushServiceAndroidGCM.jsm (limited to 'dom/push/PushServiceAndroidGCM.jsm') diff --git a/dom/push/PushServiceAndroidGCM.jsm b/dom/push/PushServiceAndroidGCM.jsm new file mode 100644 index 000000000..ed07be339 --- /dev/null +++ b/dom/push/PushServiceAndroidGCM.jsm @@ -0,0 +1,275 @@ +/* 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/. */ + +"use strict"; + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cu = Components.utils; +const Cr = Components.results; + +const {PushDB} = Cu.import("resource://gre/modules/PushDB.jsm"); +const {PushRecord} = Cu.import("resource://gre/modules/PushRecord.jsm"); +const {PushCrypto} = Cu.import("resource://gre/modules/PushCrypto.jsm"); +Cu.import("resource://gre/modules/Messaging.jsm"); /*global: Messaging */ +Cu.import("resource://gre/modules/Services.jsm"); /*global: Services */ +Cu.import("resource://gre/modules/Preferences.jsm"); /*global: Preferences */ +Cu.import("resource://gre/modules/Promise.jsm"); /*global: Promise */ +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); /*global: XPCOMUtils */ + +const Log = Cu.import("resource://gre/modules/AndroidLog.jsm", {}).AndroidLog.bind("Push"); + +this.EXPORTED_SYMBOLS = ["PushServiceAndroidGCM"]; + +XPCOMUtils.defineLazyGetter(this, "console", () => { + let {ConsoleAPI} = Cu.import("resource://gre/modules/Console.jsm", {}); + return new ConsoleAPI({ + dump: Log.i, + maxLogLevelPref: "dom.push.loglevel", + prefix: "PushServiceAndroidGCM", + }); +}); + +const kPUSHANDROIDGCMDB_DB_NAME = "pushAndroidGCM"; +const kPUSHANDROIDGCMDB_DB_VERSION = 5; // Change this if the IndexedDB format changes +const kPUSHANDROIDGCMDB_STORE_NAME = "pushAndroidGCM"; + +const FXA_PUSH_SCOPE = "chrome://fxa-push"; + +const prefs = new Preferences("dom.push."); + +/** + * The implementation of WebPush push backed by Android's GCM + * delivery. + */ +this.PushServiceAndroidGCM = { + _mainPushService: null, + _serverURI: null, + + newPushDB: function() { + return new PushDB(kPUSHANDROIDGCMDB_DB_NAME, + kPUSHANDROIDGCMDB_DB_VERSION, + kPUSHANDROIDGCMDB_STORE_NAME, + "channelID", + PushRecordAndroidGCM); + }, + + validServerURI: function(serverURI) { + if (!serverURI) { + return false; + } + + if (serverURI.scheme == "https") { + return true; + } + if (serverURI.scheme == "http") { + // Allow insecure server URLs for development and testing. + return !!prefs.get("testing.allowInsecureServerURL"); + } + console.info("Unsupported Android GCM dom.push.serverURL scheme", serverURI.scheme); + return false; + }, + + observe: function(subject, topic, data) { + switch (topic) { + case "nsPref:changed": + if (data == "dom.push.debug") { + // Reconfigure. + let debug = !!prefs.get("debug"); + console.info("Debug parameter changed; updating configuration with new debug", debug); + this._configure(this._serverURI, debug); + } + break; + case "PushServiceAndroidGCM:ReceivedPushMessage": + this._onPushMessageReceived(data); + break; + default: + break; + } + }, + + _onPushMessageReceived(data) { + // TODO: Use Messaging.jsm for this. + if (this._mainPushService == null) { + // Shouldn't ever happen, but let's be careful. + console.error("No main PushService! Dropping message."); + return; + } + if (!data) { + console.error("No data from Java! Dropping message."); + return; + } + data = JSON.parse(data); + console.debug("ReceivedPushMessage with data", data); + + let { headers, message } = this._messageAndHeaders(data); + + console.debug("Delivering message to main PushService:", message, headers); + this._mainPushService.receivedPushMessage( + data.channelID, "", headers, message, (record) => { + // Always update the stored record. + return record; + }); + }, + + _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 }; + }, + + _configure: function(serverURL, debug) { + return Messaging.sendRequestForResult({ + type: "PushServiceAndroidGCM:Configure", + endpoint: serverURL.spec, + debug: debug, + }); + }, + + init: function(options, mainPushService, serverURL) { + console.debug("init()"); + this._mainPushService = mainPushService; + this._serverURI = serverURL; + + prefs.observe("debug", this); + Services.obs.addObserver(this, "PushServiceAndroidGCM:ReceivedPushMessage", false); + + return this._configure(serverURL, !!prefs.get("debug")).then(() => { + Messaging.sendRequestForResult({ + type: "PushServiceAndroidGCM:Initialized" + }); + }); + }, + + uninit: function() { + console.debug("uninit()"); + Messaging.sendRequestForResult({ + type: "PushServiceAndroidGCM:Uninitialized" + }); + + this._mainPushService = null; + Services.obs.removeObserver(this, "PushServiceAndroidGCM:ReceivedPushMessage"); + prefs.ignore("debug", this); + }, + + onAlarmFired: function() { + // No action required. + }, + + connect: function(records) { + console.debug("connect:", records); + // It's possible for the registration or subscriptions backing the + // PushService to not be registered with the underlying AndroidPushService. + // Expire those that are unrecognized. + return Messaging.sendRequestForResult({ + type: "PushServiceAndroidGCM:DumpSubscriptions", + }) + .then(subscriptions => { + console.debug("connect:", subscriptions); + // subscriptions maps chid => subscription data. + return Promise.all(records.map(record => { + if (subscriptions.hasOwnProperty(record.keyID)) { + console.debug("connect:", "hasOwnProperty", record.keyID); + return Promise.resolve(); + } + console.debug("connect:", "!hasOwnProperty", record.keyID); + // Subscription is known to PushService.jsm but not to AndroidPushService. Drop it. + return this._mainPushService.dropRegistrationAndNotifyApp(record.keyID) + .catch(error => { + console.error("connect: Error dropping registration", record.keyID, error); + }); + })); + }); + }, + + isConnected: function() { + return this._mainPushService != null; + }, + + disconnect: function() { + console.debug("disconnect"); + }, + + register: function(record) { + console.debug("register:", record); + let ctime = Date.now(); + let appServerKey = record.appServerKey ? + ChromeUtils.base64URLEncode(record.appServerKey, { + // The Push server requires padding. + pad: true, + }) : null; + let message = { + type: "PushServiceAndroidGCM:SubscribeChannel", + appServerKey: appServerKey, + } + if (record.scope == FXA_PUSH_SCOPE) { + message.service = "fxa"; + } + // Caller handles errors. + return Messaging.sendRequestForResult(message) + .then(data => { + console.debug("Got data:", data); + return PushCrypto.generateKeys() + .then(exportedKeys => + new PushRecordAndroidGCM({ + // Straight from autopush. + channelID: data.channelID, + pushEndpoint: data.endpoint, + // Common to all PushRecord implementations. + scope: record.scope, + originAttributes: record.originAttributes, + ctime: ctime, + systemRecord: record.systemRecord, + // Cryptography! + p256dhPublicKey: exportedKeys[0], + p256dhPrivateKey: exportedKeys[1], + authenticationSecret: PushCrypto.generateAuthenticationSecret(), + appServerKey: record.appServerKey, + }) + ); + }); + }, + + unregister: function(record) { + console.debug("unregister: ", record); + return Messaging.sendRequestForResult({ + type: "PushServiceAndroidGCM:UnsubscribeChannel", + channelID: record.keyID, + }); + }, + + reportDeliveryError: function(messageID, reason) { + console.warn("reportDeliveryError: Ignoring message delivery error", + messageID, reason); + }, +}; + +function PushRecordAndroidGCM(record) { + PushRecord.call(this, record); + this.channelID = record.channelID; +} + +PushRecordAndroidGCM.prototype = Object.create(PushRecord.prototype, { + keyID: { + get() { + return this.channelID; + }, + }, +}); -- cgit v1.2.3