forked from mirrors/gecko-dev
		
	 6a0111db7b
			
		
	
	
		6a0111db7b
		
	
	
	
	
		
			
			Differential Revision: https://phabricator.services.mozilla.com/D45079 --HG-- extra : moz-landing-system : lando
		
			
				
	
	
		
			317 lines
		
	
	
	
		
			8.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			317 lines
		
	
	
	
		
			8.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* 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 { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 | |
| const { XPCOMUtils } = ChromeUtils.import(
 | |
|   "resource://gre/modules/XPCOMUtils.jsm"
 | |
| );
 | |
| 
 | |
| ChromeUtils.defineModuleGetter(
 | |
|   this,
 | |
|   "PushDB",
 | |
|   "resource://gre/modules/PushDB.jsm"
 | |
| );
 | |
| ChromeUtils.defineModuleGetter(
 | |
|   this,
 | |
|   "PushRecord",
 | |
|   "resource://gre/modules/PushRecord.jsm"
 | |
| );
 | |
| ChromeUtils.defineModuleGetter(
 | |
|   this,
 | |
|   "PushCrypto",
 | |
|   "resource://gre/modules/PushCrypto.jsm"
 | |
| );
 | |
| ChromeUtils.defineModuleGetter(
 | |
|   this,
 | |
|   "EventDispatcher",
 | |
|   "resource://gre/modules/Messaging.jsm"
 | |
| );
 | |
| ChromeUtils.defineModuleGetter(
 | |
|   this,
 | |
|   "Preferences",
 | |
|   "resource://gre/modules/Preferences.jsm"
 | |
| );
 | |
| 
 | |
| XPCOMUtils.defineLazyGetter(this, "Log", () => {
 | |
|   return ChromeUtils.import(
 | |
|     "resource://gre/modules/AndroidLog.jsm",
 | |
|     {}
 | |
|   ).AndroidLog.bind("Push");
 | |
| });
 | |
| 
 | |
| const EXPORTED_SYMBOLS = ["PushServiceAndroidGCM"];
 | |
| 
 | |
| XPCOMUtils.defineLazyGetter(this, "console", () => {
 | |
|   let { ConsoleAPI } = ChromeUtils.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.
 | |
|  */
 | |
| var PushServiceAndroidGCM = {
 | |
|   _mainPushService: null,
 | |
|   _serverURI: null,
 | |
| 
 | |
|   newPushDB() {
 | |
|     return new PushDB(
 | |
|       kPUSHANDROIDGCMDB_DB_NAME,
 | |
|       kPUSHANDROIDGCMDB_DB_VERSION,
 | |
|       kPUSHANDROIDGCMDB_STORE_NAME,
 | |
|       "channelID",
 | |
|       PushRecordAndroidGCM
 | |
|     );
 | |
|   },
 | |
| 
 | |
|   observe(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) {
 | |
|       if (data.enc && (data.enckey || data.cryptokey)) {
 | |
|         headers = {
 | |
|           encryption_key: data.enckey,
 | |
|           crypto_key: data.cryptokey,
 | |
|           encryption: data.enc,
 | |
|           encoding: data.con,
 | |
|         };
 | |
|       } else if (data.con == "aes128gcm") {
 | |
|         headers = {
 | |
|           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(serverURL, debug) {
 | |
|     return EventDispatcher.instance.sendRequestForResult({
 | |
|       type: "PushServiceAndroidGCM:Configure",
 | |
|       endpoint: serverURL.spec,
 | |
|       debug,
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   init(options, mainPushService, serverURL) {
 | |
|     console.debug("init()");
 | |
|     this._mainPushService = mainPushService;
 | |
|     this._serverURI = serverURL;
 | |
| 
 | |
|     prefs.observe("debug", this);
 | |
|     Services.obs.addObserver(this, "PushServiceAndroidGCM:ReceivedPushMessage");
 | |
| 
 | |
|     return this._configure(serverURL, !!prefs.get("debug")).then(() => {
 | |
|       EventDispatcher.instance.sendRequestForResult({
 | |
|         type: "PushServiceAndroidGCM:Initialized",
 | |
|       });
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   uninit() {
 | |
|     console.debug("uninit()");
 | |
|     EventDispatcher.instance.sendRequestForResult({
 | |
|       type: "PushServiceAndroidGCM:Uninitialized",
 | |
|     });
 | |
| 
 | |
|     this._mainPushService = null;
 | |
|     Services.obs.removeObserver(
 | |
|       this,
 | |
|       "PushServiceAndroidGCM:ReceivedPushMessage"
 | |
|     );
 | |
|     prefs.ignore("debug", this);
 | |
|   },
 | |
| 
 | |
|   onAlarmFired() {
 | |
|     // No action required.
 | |
|   },
 | |
| 
 | |
|   connect(records, broadcastListeners) {
 | |
|     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 EventDispatcher.instance
 | |
|       .sendRequestForResult({
 | |
|         type: "PushServiceAndroidGCM:DumpSubscriptions",
 | |
|       })
 | |
|       .then(subscriptions => {
 | |
|         subscriptions = JSON.parse(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
 | |
|                 );
 | |
|               });
 | |
|           })
 | |
|         );
 | |
|       });
 | |
|   },
 | |
| 
 | |
|   async sendSubscribeBroadcast(serviceId, version) {
 | |
|     // Not implemented yet
 | |
|   },
 | |
| 
 | |
|   isConnected() {
 | |
|     return this._mainPushService != null;
 | |
|   },
 | |
| 
 | |
|   disconnect() {
 | |
|     console.debug("disconnect");
 | |
|   },
 | |
| 
 | |
|   register(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,
 | |
|     };
 | |
|     if (record.scope == FXA_PUSH_SCOPE) {
 | |
|       message.service = "fxa";
 | |
|     }
 | |
|     // Caller handles errors.
 | |
|     return EventDispatcher.instance.sendRequestForResult(message).then(data => {
 | |
|       data = JSON.parse(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,
 | |
|             systemRecord: record.systemRecord,
 | |
|             // Cryptography!
 | |
|             p256dhPublicKey: exportedKeys[0],
 | |
|             p256dhPrivateKey: exportedKeys[1],
 | |
|             authenticationSecret: PushCrypto.generateAuthenticationSecret(),
 | |
|             appServerKey: record.appServerKey,
 | |
|           })
 | |
|       );
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   unregister(record) {
 | |
|     console.debug("unregister: ", record);
 | |
|     return EventDispatcher.instance.sendRequestForResult({
 | |
|       type: "PushServiceAndroidGCM:UnsubscribeChannel",
 | |
|       channelID: record.keyID,
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   reportDeliveryError(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;
 | |
|     },
 | |
|   },
 | |
| });
 |