gecko-dev/mobile/android/components/FxAccountsPush.js

177 строки
6.1 KiB
JavaScript

/* 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 {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
const {EventDispatcher} = ChromeUtils.import("resource://gre/modules/Messaging.jsm");
const {
PushCrypto,
getCryptoParams,
} = ChromeUtils.import("resource://gre/modules/PushCrypto.jsm", null);
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 = ChromeUtils.import("resource://gre/modules/AndroidLog.jsm", {}).AndroidLog.bind("FxAccountsPush");
function FxAccountsPush() {
Services.obs.addObserver(this, "FxAccountsPush:ReceivedPushMessageToDecode");
EventDispatcher.instance.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();
} else if (data === "android-fxa-resubscribe") {
// If unsubscription fails, we still want to try to subscribe.
this._unsubscribe().then(this._subscribe, this._subscribe);
}
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);
const err = new Error("FxAccountsPush failed to subscribe");
err.result = result;
reject(err);
}
});
})
.then(subscription => {
EventDispatcher.instance.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);
EventDispatcher.instance.sendRequest({
type: "FxAccountsPush:Subscribe:Response",
error: err.result.toString(), // Convert to string because the GeckoBundle can't getLong();
});
});
},
_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 (!Components.isSuccessCode(result)) {
return reject(new Error(`Error getting subscription (${result})`));
}
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) : "";
EventDispatcher.instance.sendRequestForResult({
type: "FxAccountsPush:ReceivedPushMessageToDecode:Response",
message: decryptedMessage,
});
})
.catch(err => {
Log.d("Error while decoding incoming message : " + err);
EventDispatcher.instance.sendRequestForResult({
type: "FxAccountsPush:ReceivedPushMessageToDecode:Response",
error: err.message || "",
});
});
},
// 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: ChromeUtils.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);