mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-04-02 20:42:49 +00:00
Bug 1442133 - FxA messages client implementation. r=markh,tcsc
MozReview-Commit-ID: EWYlZLdyUA0 --HG-- extra : rebase_source : ac540a5d1c26067c95314d07a32db8994f3dcee6
This commit is contained in:
parent
be53524747
commit
783ac499bc
@ -1432,6 +1432,9 @@ pref("identity.fxaccounts.migrateToDevEdition", true);
|
||||
pref("identity.fxaccounts.migrateToDevEdition", false);
|
||||
#endif
|
||||
|
||||
// If activated, send tab will use the new FxA messages backend.
|
||||
pref("identity.fxaccounts.messages.enabled", false);
|
||||
|
||||
// On GTK, we now default to showing the menubar only when alt is pressed:
|
||||
#ifdef MOZ_WIDGET_GTK
|
||||
pref("ui.key.menuAccessKeyFocuses", true);
|
||||
|
@ -319,10 +319,34 @@ var gSync = {
|
||||
switchToTabHavingURI(url, true, { replaceQueryString: true });
|
||||
},
|
||||
|
||||
sendTabToDevice(url, clientId, title) {
|
||||
Weave.Service.clientsEngine.sendURIToClientForDisplay(url, clientId, title).catch(e => {
|
||||
console.error("Could not send tab to device", e);
|
||||
});
|
||||
async sendTabToDevice(url, clients, title) {
|
||||
let devices;
|
||||
try {
|
||||
devices = await fxAccounts.getDeviceList();
|
||||
} catch (e) {
|
||||
console.error("Could not get the FxA device list", e);
|
||||
devices = []; // We can still run in degraded mode.
|
||||
}
|
||||
const toSendMessages = [];
|
||||
for (const client of clients) {
|
||||
const device = devices.find(d => d.id == client.fxaDeviceId);
|
||||
if (device && fxAccounts.messages.canReceiveSendTabMessages(device)) {
|
||||
toSendMessages.push(device);
|
||||
} else {
|
||||
try {
|
||||
await Weave.Service.clientsEngine.sendURIToClientForDisplay(url, client.id, title);
|
||||
} catch (e) {
|
||||
console.error("Could not send tab to device", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (toSendMessages.length) {
|
||||
try {
|
||||
await fxAccounts.messages.sendTab(toSendMessages, {url, title});
|
||||
} catch (e) {
|
||||
console.error("Could not send tab to device", e);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
populateSendTabToDevicesMenu(devicesPopup, url, title, createDeviceNodeFn) {
|
||||
@ -363,18 +387,23 @@ var gSync = {
|
||||
devicesPopup.appendChild(fragment);
|
||||
},
|
||||
|
||||
// TODO: once our transition from the old-send tab world is complete,
|
||||
// this list should be built using the FxA device list instead of the client
|
||||
// collection.
|
||||
_appendSendTabDeviceList(fragment, createDeviceNodeFn, url, title) {
|
||||
const onSendAllCommand = (event) => {
|
||||
this.sendTabToDevice(url, this.remoteClients, title);
|
||||
};
|
||||
const onTargetDeviceCommand = (event) => {
|
||||
let clients = event.target.getAttribute("clientId") ?
|
||||
[event.target.getAttribute("clientId")] :
|
||||
this.remoteClients.map(client => client.id);
|
||||
|
||||
clients.forEach(clientId => this.sendTabToDevice(url, clientId, title));
|
||||
const clientId = event.target.getAttribute("clientId");
|
||||
const client = this.remoteClients.find(c => c.id == clientId);
|
||||
this.sendTabToDevice(url, [client], title);
|
||||
};
|
||||
|
||||
function addTargetDevice(clientId, name, clientType, lastModified) {
|
||||
const targetDevice = createDeviceNodeFn(clientId, name, clientType, lastModified);
|
||||
targetDevice.addEventListener("command", onTargetDeviceCommand, true);
|
||||
targetDevice.addEventListener("command", clientId ? onTargetDeviceCommand :
|
||||
onSendAllCommand, true);
|
||||
targetDevice.classList.add("sync-menuitem", "sendtab-target");
|
||||
targetDevice.setAttribute("clientId", clientId);
|
||||
targetDevice.setAttribute("clientType", clientType);
|
||||
|
@ -444,6 +444,7 @@ BrowserGlue.prototype = {
|
||||
this._onDeviceDisconnected();
|
||||
}
|
||||
break;
|
||||
case "fxaccounts:messages:display-tabs":
|
||||
case "weave:engine:clients:display-uris":
|
||||
this._onDisplaySyncURIs(subject);
|
||||
break;
|
||||
@ -598,6 +599,7 @@ BrowserGlue.prototype = {
|
||||
os.addObserver(this, "fxaccounts:device_connected");
|
||||
os.addObserver(this, "fxaccounts:verify_login");
|
||||
os.addObserver(this, "fxaccounts:device_disconnected");
|
||||
os.addObserver(this, "fxaccounts:messages:display-tabs");
|
||||
os.addObserver(this, "weave:engine:clients:display-uris");
|
||||
os.addObserver(this, "session-save");
|
||||
os.addObserver(this, "places-init-complete");
|
||||
@ -640,6 +642,7 @@ BrowserGlue.prototype = {
|
||||
os.removeObserver(this, "fxaccounts:device_connected");
|
||||
os.removeObserver(this, "fxaccounts:verify_login");
|
||||
os.removeObserver(this, "fxaccounts:device_disconnected");
|
||||
os.removeObserver(this, "fxaccounts:messages:display-tabs");
|
||||
os.removeObserver(this, "weave:engine:clients:display-uris");
|
||||
os.removeObserver(this, "session-save");
|
||||
if (this._bookmarksBackupIdleTime) {
|
||||
@ -2543,7 +2546,7 @@ BrowserGlue.prototype = {
|
||||
await Promise.all(URIs.slice(1).map(URI => openTab(URI)));
|
||||
|
||||
let title, body;
|
||||
const deviceName = Weave.Service.clientsEngine.getClientName(URIs[0].clientId);
|
||||
const deviceName = URIs[0].sender.name;
|
||||
const bundle = Services.strings.createBundle("chrome://browser/locale/accounts.properties");
|
||||
if (URIs.length == 1) {
|
||||
// Due to bug 1305895, tabs from iOS may not have device information, so
|
||||
@ -2567,7 +2570,7 @@ BrowserGlue.prototype = {
|
||||
}
|
||||
} else {
|
||||
title = bundle.GetStringFromName("multipleTabsArrivingNotification.title");
|
||||
const allSameDevice = URIs.every(URI => URI.clientId == URIs[0].clientId);
|
||||
const allSameDevice = URIs.every(URI => URI.sender.id == URIs[0].sender.id);
|
||||
const unknownDevice = allSameDevice && !deviceName;
|
||||
let tabArrivingBody;
|
||||
if (unknownDevice) {
|
||||
|
@ -30,6 +30,9 @@ ChromeUtils.defineModuleGetter(this, "jwcrypto",
|
||||
ChromeUtils.defineModuleGetter(this, "FxAccountsOAuthGrantClient",
|
||||
"resource://gre/modules/FxAccountsOAuthGrantClient.jsm");
|
||||
|
||||
ChromeUtils.defineModuleGetter(this, "FxAccountsMessages",
|
||||
"resource://gre/modules/FxAccountsMessages.js");
|
||||
|
||||
ChromeUtils.defineModuleGetter(this, "FxAccountsProfile",
|
||||
"resource://gre/modules/FxAccountsProfile.jsm");
|
||||
|
||||
@ -41,6 +44,7 @@ XPCOMUtils.defineLazyPreferenceGetter(this, "FXA_ENABLED",
|
||||
|
||||
// All properties exposed by the public FxAccounts API.
|
||||
var publicProperties = [
|
||||
"_withCurrentAccountState", // fxaccounts package only!
|
||||
"accountStatus",
|
||||
"canGetKeys",
|
||||
"checkVerificationStatus",
|
||||
@ -51,6 +55,7 @@ var publicProperties = [
|
||||
"getKeys",
|
||||
"getOAuthToken",
|
||||
"getProfileCache",
|
||||
"getPushSubscription",
|
||||
"getSignedInUser",
|
||||
"getSignedInUserProfile",
|
||||
"handleAccountDestroyed",
|
||||
@ -60,6 +65,7 @@ var publicProperties = [
|
||||
"invalidateCertificate",
|
||||
"loadAndPoll",
|
||||
"localtimeOffsetMsec",
|
||||
"messages",
|
||||
"notifyDevices",
|
||||
"now",
|
||||
"removeCachedOAuthToken",
|
||||
@ -410,6 +416,14 @@ FxAccountsInternal.prototype = {
|
||||
return this._profile;
|
||||
},
|
||||
|
||||
_messages: null,
|
||||
get messages() {
|
||||
if (!this._messages) {
|
||||
this._messages = new FxAccountsMessages(this);
|
||||
}
|
||||
return this._messages;
|
||||
},
|
||||
|
||||
// A hook-point for tests who may want a mocked AccountState or mocked storage.
|
||||
newAccountState(credentials) {
|
||||
let storage = new FxAccountsStorageManager();
|
||||
@ -417,6 +431,22 @@ FxAccountsInternal.prototype = {
|
||||
return new AccountState(storage);
|
||||
},
|
||||
|
||||
// "Friend" classes of FxAccounts (e.g. FxAccountsMessages) know about the
|
||||
// "current account state" system. This method allows them to read and write
|
||||
// safely in it.
|
||||
// Example of usage:
|
||||
// fxAccounts._withCurrentAccountState(async (getUserData, updateUserData) => {
|
||||
// const userData = await getUserData(['device']);
|
||||
// ...
|
||||
// await updateUserData({device: null});
|
||||
// });
|
||||
_withCurrentAccountState(func) {
|
||||
const state = this.currentAccountState;
|
||||
const getUserData = (fields) => state.getUserAccountData(fields);
|
||||
const updateUserData = (data) => state.updateUserAccountData(data);
|
||||
return func(getUserData, updateUserData);
|
||||
},
|
||||
|
||||
/**
|
||||
* Send a message to a set of devices in the same account
|
||||
*
|
||||
@ -741,6 +771,9 @@ FxAccountsInternal.prototype = {
|
||||
this._profile.tearDown();
|
||||
this._profile = null;
|
||||
}
|
||||
if (this._messages) {
|
||||
this._messages = null;
|
||||
}
|
||||
// We "abort" the accountState and assume our caller is about to throw it
|
||||
// away and replace it with a new one.
|
||||
return this.currentAccountState.abort();
|
||||
@ -994,7 +1027,7 @@ FxAccountsInternal.prototype = {
|
||||
throw new Error("Signed in user changed while fetching keys!");
|
||||
}
|
||||
|
||||
// Next statements must be synchronous until we setUserAccountData
|
||||
// Next statements must be synchronous until we updateUserAccountData
|
||||
// so that we don't risk getting into a weird state.
|
||||
let kBbytes = CryptoUtils.xor(CommonUtils.hexToBytes(data.unwrapBKey),
|
||||
wrapKB);
|
||||
@ -1649,6 +1682,20 @@ FxAccountsInternal.prototype = {
|
||||
});
|
||||
},
|
||||
|
||||
// @returns Promise<Subscription>.
|
||||
getPushSubscription() {
|
||||
return this.fxaPushService.getSubscription();
|
||||
},
|
||||
|
||||
// Once FxA messages is stable, remove this, hardcode the capabilities,
|
||||
// and reset the device registration version.
|
||||
get deviceCapabilities() {
|
||||
if (Services.prefs.getBoolPref("identity.fxaccounts.messages.enabled", true)) {
|
||||
return [CAPABILITY_MESSAGES, CAPABILITY_MESSAGES_SENDTAB];
|
||||
}
|
||||
return [];
|
||||
},
|
||||
|
||||
// If you change what we send to the FxA servers during device registration,
|
||||
// you'll have to bump the DEVICE_REGISTRATION_VERSION number to force older
|
||||
// devices to re-register when Firefox updates
|
||||
@ -1673,6 +1720,7 @@ FxAccountsInternal.prototype = {
|
||||
deviceOptions.pushAuthKey = urlsafeBase64Encode(authKey);
|
||||
}
|
||||
}
|
||||
deviceOptions.capabilities = this.deviceCapabilities;
|
||||
|
||||
let device;
|
||||
if (currentDevice && currentDevice.id) {
|
||||
@ -1687,6 +1735,7 @@ FxAccountsInternal.prototype = {
|
||||
|
||||
await this.currentAccountState.updateUserAccountData({
|
||||
device: {
|
||||
...currentDevice, // Copy the other properties (e.g. messagesIndex).
|
||||
id: device.id,
|
||||
registrationVersion: this.DEVICE_REGISTRATION_VERSION
|
||||
}
|
||||
|
@ -409,6 +409,7 @@ this.FxAccountsClient.prototype = {
|
||||
body.pushPublicKey = options.pushPublicKey;
|
||||
body.pushAuthKey = options.pushAuthKey;
|
||||
}
|
||||
body.capabilities = options.capabilities;
|
||||
|
||||
return this._request(path, "POST", creds, body);
|
||||
},
|
||||
@ -446,6 +447,49 @@ this.FxAccountsClient.prototype = {
|
||||
deriveHawkCredentials(sessionTokenHex, "sessionToken"), body);
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieves messages from our device's message box.
|
||||
*
|
||||
* @method getMessages
|
||||
* @param sessionTokenHex - Session token obtained from signIn
|
||||
* @param [index] - If specified, only messages received after the one who
|
||||
* had that index will be retrieved.
|
||||
* @param [limit] - Maximum number of messages to retrieve.
|
||||
*/
|
||||
getMessages(sessionTokenHex, {index, limit}) {
|
||||
const params = new URLSearchParams();
|
||||
if (index != undefined) {
|
||||
params.set("index", index);
|
||||
}
|
||||
if (limit != undefined) {
|
||||
params.set("limit", limit);
|
||||
}
|
||||
const path = `/account/device/messages?${params.toString()}`;
|
||||
return this._request(path, "GET",
|
||||
deriveHawkCredentials(sessionTokenHex, "sessionToken"));
|
||||
},
|
||||
|
||||
/**
|
||||
* Stores a message in the recipient's message box.
|
||||
*
|
||||
* @method sendMessage
|
||||
* @param sessionTokenHex - Session token obtained from signIn
|
||||
* @param topic
|
||||
* @param to - Recipient device ID.
|
||||
* @param data
|
||||
* @return Promise
|
||||
* Resolves to the request's response, (which should be an empty object)
|
||||
*/
|
||||
sendMessage(sessionTokenHex, topic, to, data) {
|
||||
const body = {
|
||||
topic,
|
||||
to,
|
||||
data
|
||||
};
|
||||
return this._request("/account/devices/messages", "POST",
|
||||
deriveHawkCredentials(sessionTokenHex, "sessionToken"), body);
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the session or name for an existing device
|
||||
*
|
||||
@ -458,6 +502,8 @@ this.FxAccountsClient.prototype = {
|
||||
* Device name
|
||||
* @param [options]
|
||||
* Extra device options
|
||||
* @param options.capabilities
|
||||
* Device capabilities
|
||||
* @param [options.pushCallback]
|
||||
* `pushCallback` push endpoint callback
|
||||
* @param [options.pushPublicKey]
|
||||
@ -483,6 +529,7 @@ this.FxAccountsClient.prototype = {
|
||||
body.pushPublicKey = options.pushPublicKey;
|
||||
body.pushAuthKey = options.pushAuthKey;
|
||||
}
|
||||
body.capabilities = options.capabilities;
|
||||
|
||||
return this._request(path, "POST", creds, body);
|
||||
},
|
||||
|
@ -74,6 +74,9 @@ exports.FXA_PUSH_SCOPE_ACCOUNT_UPDATE = "chrome://fxa-device-update";
|
||||
exports.ON_PROFILE_CHANGE_NOTIFICATION = "fxaccounts:profilechange"; // WebChannel
|
||||
exports.ON_ACCOUNT_STATE_CHANGE_NOTIFICATION = "fxaccounts:statechange";
|
||||
|
||||
exports.CAPABILITY_MESSAGES = "messages";
|
||||
exports.CAPABILITY_MESSAGES_SENDTAB = "messages.sendtab";
|
||||
|
||||
// UI Requests.
|
||||
exports.UI_REQUEST_SIGN_IN_FLOW = "signInFlow";
|
||||
exports.UI_REQUEST_REFRESH_AUTH = "refreshAuthentication";
|
||||
|
223
services/fxaccounts/FxAccountsMessages.js
Normal file
223
services/fxaccounts/FxAccountsMessages.js
Normal file
@ -0,0 +1,223 @@
|
||||
/* 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 EXPORTED_SYMBOLS = ["FxAccountsMessages", /* the rest are for testing only */
|
||||
"FxAccountsMessagesSender", "FxAccountsMessagesReceiver",
|
||||
"FxAccountsMessagesHandler"];
|
||||
|
||||
ChromeUtils.import("resource://gre/modules/FxAccountsCommon.js");
|
||||
ChromeUtils.import("resource://gre/modules/Preferences.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "PushCrypto",
|
||||
"resource://gre/modules/PushCrypto.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
ChromeUtils.import("resource://services-common/observers.js");
|
||||
|
||||
const AES128GCM_ENCODING = "aes128gcm"; // The only Push encoding we support.
|
||||
const TOPICS = {
|
||||
SEND_TAB: "sendtab"
|
||||
};
|
||||
|
||||
class FxAccountsMessages {
|
||||
constructor(fxAccounts, options = {}) {
|
||||
this.fxAccounts = fxAccounts;
|
||||
this.sender = options.sender || new FxAccountsMessagesSender(fxAccounts);
|
||||
this.receiver = options.receiver || new FxAccountsMessagesReceiver(fxAccounts);
|
||||
}
|
||||
|
||||
_isDeviceMessagesAware(device) {
|
||||
return device.capabilities && device.capabilities.includes(CAPABILITY_MESSAGES);
|
||||
}
|
||||
|
||||
canReceiveSendTabMessages(device) {
|
||||
return this._isDeviceMessagesAware(device) &&
|
||||
device.capabilities.includes(CAPABILITY_MESSAGES_SENDTAB);
|
||||
}
|
||||
|
||||
consumeRemoteMessages() {
|
||||
if (!Services.prefs.getBoolPref("identity.fxaccounts.messages.enabled", true)) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return this.receiver.consumeRemoteMessages();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Device[]} to - Device objects (typically returned by fxAccounts.getDevicesList()).
|
||||
* @param {Object} tab
|
||||
* @param {string} tab.url
|
||||
* @param {string} tab.title
|
||||
*/
|
||||
async sendTab(to, tab) {
|
||||
log.info(`Sending a tab to ${to.length} devices.`);
|
||||
const ourDeviceId = await this.fxAccounts.getDeviceId();
|
||||
const payload = {
|
||||
topic: TOPICS.SEND_TAB,
|
||||
data: {
|
||||
from: ourDeviceId,
|
||||
entries: [{title: tab.title, url: tab.url}]
|
||||
}
|
||||
};
|
||||
return this.sender.send(TOPICS.SEND_TAB, to, payload);
|
||||
}
|
||||
}
|
||||
|
||||
class FxAccountsMessagesSender {
|
||||
constructor(fxAccounts) {
|
||||
this.fxAccounts = fxAccounts;
|
||||
}
|
||||
|
||||
async send(topic, to, data) {
|
||||
const userData = await this.fxAccounts.getSignedInUser();
|
||||
if (!userData) {
|
||||
throw new Error("No user.");
|
||||
}
|
||||
const {sessionToken} = userData;
|
||||
if (!sessionToken) {
|
||||
throw new Error("_send called without a session token.");
|
||||
}
|
||||
const encoder = new TextEncoder("utf8");
|
||||
const client = this.fxAccounts.getAccountsClient();
|
||||
for (const device of to) {
|
||||
try {
|
||||
const bytes = encoder.encode(JSON.stringify(data));
|
||||
const payload = await this._encrypt(bytes, device, encoder);
|
||||
await client.sendMessage(sessionToken, topic, device.id, payload);
|
||||
log.info(`Payload sent to device ${device.id}.`);
|
||||
} catch (e) {
|
||||
log.error(`Could not send data to device ${device.id}.`, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async _encrypt(bytes, device) {
|
||||
let {pushPublicKey, pushAuthKey} = device;
|
||||
if (!pushPublicKey || !pushAuthKey) {
|
||||
throw new Error(`Device ${device.id} does not have push keys.`);
|
||||
}
|
||||
pushPublicKey = ChromeUtils.base64URLDecode(pushPublicKey, {padding: "ignore"});
|
||||
pushAuthKey = ChromeUtils.base64URLDecode(pushAuthKey, {padding: "ignore"});
|
||||
const {ciphertext} = await PushCrypto.encrypt(bytes, pushPublicKey, pushAuthKey);
|
||||
return ChromeUtils.base64URLEncode(ciphertext, {pad: false});
|
||||
}
|
||||
}
|
||||
|
||||
class FxAccountsMessagesReceiver {
|
||||
constructor(fxAccounts, options = {}) {
|
||||
this.fxAccounts = fxAccounts;
|
||||
this.handler = options.handler || new FxAccountsMessagesHandler(this.fxAccounts);
|
||||
}
|
||||
|
||||
async consumeRemoteMessages() {
|
||||
log.info(`Consuming unread messages.`);
|
||||
const messages = await this._fetchMessages();
|
||||
if (!messages || !messages.length) {
|
||||
log.info(`No new messages.`);
|
||||
return;
|
||||
}
|
||||
const decoder = new TextDecoder("utf8");
|
||||
const keys = await this._getOwnKeys();
|
||||
const payloads = [];
|
||||
for (const {index, data} of messages) {
|
||||
try {
|
||||
const bytes = await this._decrypt(data, keys);
|
||||
const payload = JSON.parse(decoder.decode(bytes));
|
||||
payloads.push(payload);
|
||||
} catch (e) {
|
||||
log.error(`Could not unwrap message ${index}`, e);
|
||||
}
|
||||
}
|
||||
if (payloads.length) {
|
||||
await this.handler.handle(payloads);
|
||||
}
|
||||
}
|
||||
|
||||
async _fetchMessages() {
|
||||
return this.fxAccounts._withCurrentAccountState(async (getUserData, updateUserData) => {
|
||||
const userData = await getUserData(["sessionToken", "device"]);
|
||||
if (!userData) {
|
||||
throw new Error("No user.");
|
||||
}
|
||||
const {sessionToken, device} = userData;
|
||||
if (!sessionToken) {
|
||||
throw new Error("No session token.");
|
||||
}
|
||||
if (!device) {
|
||||
throw new Error("No device registration.");
|
||||
}
|
||||
const opts = {};
|
||||
if (device.messagesIndex) {
|
||||
opts.index = device.messagesIndex;
|
||||
}
|
||||
const client = this.fxAccounts.getAccountsClient();
|
||||
log.info(`Fetching unread messages with ${JSON.stringify(opts)}.`);
|
||||
const {index: newIndex, messages} = await client.getMessages(sessionToken, opts);
|
||||
await updateUserData({
|
||||
device: {...device, messagesIndex: newIndex}
|
||||
});
|
||||
return messages;
|
||||
});
|
||||
}
|
||||
|
||||
async _getOwnKeys() {
|
||||
const subscription = await this.fxAccounts.getPushSubscription();
|
||||
return {
|
||||
pushPrivateKey: subscription.p256dhPrivateKey,
|
||||
pushPublicKey: new Uint8Array(subscription.getKey("p256dh")),
|
||||
pushAuthKey: new Uint8Array(subscription.getKey("auth"))
|
||||
};
|
||||
}
|
||||
|
||||
async _decrypt(ciphertext, {pushPrivateKey, pushPublicKey, pushAuthKey}) {
|
||||
ciphertext = ChromeUtils.base64URLDecode(ciphertext, {padding: "reject"});
|
||||
return PushCrypto.decrypt(pushPrivateKey, pushPublicKey,
|
||||
pushAuthKey,
|
||||
{encoding: AES128GCM_ENCODING},
|
||||
ciphertext);
|
||||
}
|
||||
}
|
||||
|
||||
class FxAccountsMessagesHandler {
|
||||
constructor(fxAccounts) {
|
||||
this.fxAccounts = fxAccounts;
|
||||
}
|
||||
|
||||
async handle(payloads) {
|
||||
const sendTabPayloads = [];
|
||||
for (const payload of payloads) {
|
||||
switch (payload.topic) {
|
||||
case TOPICS.SEND_TAB:
|
||||
sendTabPayloads.push(payload.data);
|
||||
default:
|
||||
log.info(`Unknown messages topic: ${payload.topic}.`);
|
||||
}
|
||||
}
|
||||
|
||||
// Only one type of payload so far!
|
||||
if (sendTabPayloads.length) {
|
||||
await this._handleSendTabPayloads(sendTabPayloads);
|
||||
}
|
||||
}
|
||||
|
||||
async _handleSendTabPayloads(payloads) {
|
||||
const toDisplay = [];
|
||||
const fxaDevices = await this.fxAccounts.getDeviceList();
|
||||
for (const payload of payloads) {
|
||||
const current = payload.hasOwnProperty("current") ? payload.current :
|
||||
payload.entries.length - 1;
|
||||
const device = fxaDevices.find(d => d.id == payload.from);
|
||||
if (!device) {
|
||||
log.warn("Incoming tab is from an unknown device (maybe disconnected?)");
|
||||
}
|
||||
const sender = {
|
||||
id: device ? device.id : "",
|
||||
name: device ? device.name : ""
|
||||
};
|
||||
const {title, url: uri} = payload.entries[current];
|
||||
toDisplay.push({uri, title, sender});
|
||||
}
|
||||
|
||||
Observers.notify("fxaccounts:messages:display-tabs", toDisplay);
|
||||
}
|
||||
}
|
||||
|
@ -158,6 +158,11 @@ FxAccountsPushService.prototype = {
|
||||
return;
|
||||
}
|
||||
let payload = message.data.json();
|
||||
if (payload.topic) {
|
||||
this.log.debug(`received messages tickle with topic ${payload.topic}`);
|
||||
this.fxAccounts.messages.consumeRemoteMessages();
|
||||
return;
|
||||
}
|
||||
this.log.debug(`push command: ${payload.command}`);
|
||||
switch (payload.command) {
|
||||
case ON_DEVICE_CONNECTED_NOTIFICATION:
|
||||
@ -241,6 +246,27 @@ FxAccountsPushService.prototype = {
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Get our Push server subscription.
|
||||
*
|
||||
* Ref: https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIPushService#getSubscription()
|
||||
*
|
||||
* @returns {Promise}
|
||||
*/
|
||||
getSubscription() {
|
||||
return new Promise((resolve) => {
|
||||
this.pushService.getSubscription(FXA_PUSH_SCOPE_ACCOUNT_UPDATE,
|
||||
Services.scriptSecurityManager.getSystemPrincipal(),
|
||||
(result, subscription) => {
|
||||
if (!subscription) {
|
||||
this.log.info("FxAccountsPushService no subscription found");
|
||||
return resolve(null);
|
||||
}
|
||||
return resolve(subscription);
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
// Service registration below registers with FxAccountsComponents.manifest
|
||||
|
@ -26,6 +26,7 @@ EXTRA_JS_MODULES += [
|
||||
'FxAccountsClient.jsm',
|
||||
'FxAccountsCommon.js',
|
||||
'FxAccountsConfig.jsm',
|
||||
'FxAccountsMessages.js',
|
||||
'FxAccountsOAuthGrantClient.jsm',
|
||||
'FxAccountsProfile.jsm',
|
||||
'FxAccountsProfileClient.jsm',
|
||||
|
210
services/fxaccounts/tests/xpcshell/test_messages.js
Normal file
210
services/fxaccounts/tests/xpcshell/test_messages.js
Normal file
@ -0,0 +1,210 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
ChromeUtils.import("resource://testing-common/Assert.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/FxAccountsMessages.js");
|
||||
|
||||
add_task(async function test_sendTab() {
|
||||
const fxAccounts = {
|
||||
async getDeviceId() {
|
||||
return "my-device-id";
|
||||
}
|
||||
};
|
||||
const sender = {
|
||||
send: sinon.spy()
|
||||
};
|
||||
const fxAccountsMessages = new FxAccountsMessages(fxAccounts, {sender});
|
||||
|
||||
const to = [{
|
||||
id: "deviceid-1",
|
||||
pushPublicKey: "pubkey-1",
|
||||
pushAuthKey: "authkey-1"
|
||||
}];
|
||||
const tab = {url: "https://foo.com", title: "Foo"};
|
||||
await fxAccountsMessages.sendTab(to, tab);
|
||||
Assert.ok(sender.send.calledOnce);
|
||||
Assert.equal(sender.send.args[0][0], "sendtab");
|
||||
Assert.deepEqual(sender.send.args[0][1], to);
|
||||
Assert.deepEqual(sender.send.args[0][2], {
|
||||
topic: "sendtab",
|
||||
data: {
|
||||
from: "my-device-id",
|
||||
entries: [{title: "Foo", url: "https://foo.com"}]
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_consumeRemoteMessages() {
|
||||
const fxAccounts = {};
|
||||
const receiver = {
|
||||
consumeRemoteMessages: sinon.spy()
|
||||
};
|
||||
const fxAccountsMessages = new FxAccountsMessages(fxAccounts, {receiver});
|
||||
fxAccountsMessages.consumeRemoteMessages();
|
||||
Assert.ok(receiver.consumeRemoteMessages.calledOnce);
|
||||
});
|
||||
|
||||
add_task(async function test_canReceiveSendTabMessages() {
|
||||
const fxAccounts = {};
|
||||
const messages = new FxAccountsMessages(fxAccounts);
|
||||
Assert.ok(!messages.canReceiveSendTabMessages({id: "device-id-1"}));
|
||||
Assert.ok(!messages.canReceiveSendTabMessages({id: "device-id-1", capabilities: []}));
|
||||
Assert.ok(!messages.canReceiveSendTabMessages({id: "device-id-1", capabilities: ["messages"]}));
|
||||
Assert.ok(messages.canReceiveSendTabMessages({id: "device-id-1", capabilities: ["messages", "messages.sendtab"]}));
|
||||
});
|
||||
|
||||
add_task(async function test_sender_send() {
|
||||
const sandbox = sinon.sandbox.create();
|
||||
const fxaClient = {
|
||||
sendMessage: sinon.spy()
|
||||
};
|
||||
const sessionToken = "toktok";
|
||||
const fxAccounts = {
|
||||
async getSignedInUser() {
|
||||
return {sessionToken};
|
||||
},
|
||||
getAccountsClient() {
|
||||
return fxaClient;
|
||||
}
|
||||
};
|
||||
const sender = new FxAccountsMessagesSender(fxAccounts);
|
||||
sandbox.stub(sender, "_encrypt").callsFake((_, device) => {
|
||||
if (device.pushPublicKey == "pubkey-1") {
|
||||
return "encrypted-text-1";
|
||||
}
|
||||
return "encrypted-text-2";
|
||||
});
|
||||
|
||||
const topic = "mytopic";
|
||||
const to = [{
|
||||
id: "deviceid-1",
|
||||
pushPublicKey: "pubkey-1",
|
||||
pushAuthKey: "authkey-1"
|
||||
}, {
|
||||
id: "deviceid-2",
|
||||
pushPublicKey: "pubkey-2",
|
||||
pushAuthKey: "authkey-2"
|
||||
}];
|
||||
const payload = {foo: "bar"};
|
||||
|
||||
await sender.send(topic, to, payload);
|
||||
|
||||
Assert.ok(fxaClient.sendMessage.calledTwice);
|
||||
const checkCallArgs = (callNum, deviceId, encrypted) => {
|
||||
Assert.equal(fxaClient.sendMessage.args[callNum][0], sessionToken);
|
||||
Assert.equal(fxaClient.sendMessage.args[callNum][1], topic);
|
||||
Assert.equal(fxaClient.sendMessage.args[callNum][2], deviceId);
|
||||
Assert.equal(fxaClient.sendMessage.args[callNum][3], encrypted);
|
||||
};
|
||||
checkCallArgs(0, "deviceid-1", "encrypted-text-1");
|
||||
checkCallArgs(1, "deviceid-2", "encrypted-text-2");
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
add_task(async function test_receiver_consumeRemoteMessages() {
|
||||
const fxaClient = {
|
||||
getMessages: sinon.spy(async () => {
|
||||
return {
|
||||
index: "idx-2",
|
||||
messages: [{
|
||||
index: "idx-1",
|
||||
data: "#giberish#"
|
||||
}, {
|
||||
index: "idx-2",
|
||||
data: "#encrypted#"
|
||||
}]
|
||||
};
|
||||
})
|
||||
};
|
||||
const fxAccounts = {
|
||||
accountState: {sessionToken: "toktok", device: {}},
|
||||
_withCurrentAccountState(fun) {
|
||||
const get = () => this.accountState;
|
||||
const update = (obj) => { this.accountState = {...this.accountState, ...obj}; };
|
||||
return fun(get, update);
|
||||
},
|
||||
getAccountsClient() {
|
||||
return fxaClient;
|
||||
}
|
||||
};
|
||||
const sandbox = sinon.sandbox.create();
|
||||
const messagesHandler = {
|
||||
handle: sinon.spy()
|
||||
};
|
||||
const receiver = new FxAccountsMessagesReceiver(fxAccounts, {
|
||||
handler: messagesHandler
|
||||
});
|
||||
sandbox.stub(receiver, "_getOwnKeys").callsFake(async () => {});
|
||||
sandbox.stub(receiver, "_decrypt").callsFake((ciphertext) => {
|
||||
if (ciphertext == "#encrypted#") {
|
||||
return new TextEncoder("utf-8").encode(JSON.stringify({"foo": "bar"}));
|
||||
}
|
||||
throw new Error("Boom!");
|
||||
});
|
||||
|
||||
await receiver.consumeRemoteMessages();
|
||||
|
||||
Assert.ok(fxaClient.getMessages.calledOnce);
|
||||
Assert.equal(fxaClient.getMessages.args[0][0], "toktok");
|
||||
Assert.deepEqual(fxaClient.getMessages.args[0][1], {});
|
||||
Assert.ok(messagesHandler.handle.calledOnce);
|
||||
Assert.deepEqual(messagesHandler.handle.args[0][0], [{"foo": "bar"}]);
|
||||
fxaClient.getMessages.reset();
|
||||
|
||||
await receiver.consumeRemoteMessages();
|
||||
|
||||
Assert.ok(fxaClient.getMessages.calledOnce);
|
||||
Assert.equal(fxaClient.getMessages.args[0][0], "toktok");
|
||||
Assert.deepEqual(fxaClient.getMessages.args[0][1], {"index": "idx-2"});
|
||||
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
add_task(async function test_handler_handle_sendtab() {
|
||||
const fxAccounts = {
|
||||
async getDeviceList() {
|
||||
return [{id: "1234a", name: "My Computer"}];
|
||||
}
|
||||
};
|
||||
const handler = new FxAccountsMessagesHandler(fxAccounts);
|
||||
const payloads = [{
|
||||
topic: "sendtab",
|
||||
data: {
|
||||
from: "1234a",
|
||||
current: 0,
|
||||
entries: [{title: "Foo", url: "https://foo.com"},
|
||||
{title: "Bar", url: "https://bar.com"}]
|
||||
}
|
||||
}, {
|
||||
topic: "sendtab",
|
||||
data: {
|
||||
from: "unknown_device",
|
||||
entries: [{title: "Foo2", url: "https://foo2.com"},
|
||||
{title: "Bar2", url: "https://bar2.com"}]
|
||||
}
|
||||
}, {
|
||||
topic: "unknowntopic",
|
||||
data: {foo: "bar"}
|
||||
}];
|
||||
const notificationPromise = promiseObserver("fxaccounts:messages:display-tabs");
|
||||
await handler.handle(payloads);
|
||||
const {subject} = await notificationPromise;
|
||||
const toDisplay = subject.wrappedJSObject.object;
|
||||
const expected = [
|
||||
{uri: "https://foo.com", title: "Foo", sender: {id: "1234a", name: "My Computer"}},
|
||||
{uri: "https://bar2.com", title: "Bar2", sender: {id: "", name: ""}}
|
||||
];
|
||||
Assert.deepEqual(toDisplay, expected);
|
||||
});
|
||||
|
||||
function promiseObserver(aTopic) {
|
||||
return new Promise(resolve => {
|
||||
Services.obs.addObserver(function onNotification(subject, topic, data) {
|
||||
Services.obs.removeObserver(onNotification, topic);
|
||||
resolve({subject, data});
|
||||
}, aTopic);
|
||||
});
|
||||
}
|
||||
|
@ -406,6 +406,36 @@ add_test(function observePushTopicPasswordReset() {
|
||||
pushService.observe(msg, mockPushService.pushTopic, FXA_PUSH_SCOPE_ACCOUNT_UPDATE);
|
||||
});
|
||||
|
||||
add_task(async function messagesTickle() {
|
||||
let msg = {
|
||||
data: {
|
||||
json: () => ({
|
||||
topic: "sendtab"
|
||||
})
|
||||
},
|
||||
QueryInterface() {
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
let fxAccountsMock = {};
|
||||
const promiseConsumeRemoteMessagesCalled = new Promise(res => {
|
||||
fxAccountsMock.messages = {
|
||||
consumeRemoteMessages() {
|
||||
res();
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
let pushService = new FxAccountsPushService({
|
||||
pushService: mockPushService,
|
||||
fxAccounts: fxAccountsMock,
|
||||
});
|
||||
|
||||
pushService.observe(msg, mockPushService.pushTopic, FXA_PUSH_SCOPE_ACCOUNT_UPDATE);
|
||||
await promiseConsumeRemoteMessagesCalled;
|
||||
});
|
||||
|
||||
add_test(function observeSubscriptionChangeTopic() {
|
||||
let customAccounts = Object.assign(mockFxAccounts, {
|
||||
updateDeviceRegistration() {
|
||||
|
@ -11,6 +11,7 @@ support-files =
|
||||
[test_client.js]
|
||||
[test_credentials.js]
|
||||
[test_loginmgr_storage.js]
|
||||
[test_messages.js]
|
||||
[test_oauth_grant_client.js]
|
||||
[test_oauth_grant_client_server.js]
|
||||
[test_oauth_tokens.js]
|
||||
|
@ -903,9 +903,10 @@ ClientEngine.prototype = {
|
||||
* topic. The callback will receive an array as the subject parameter
|
||||
* containing objects with the following keys:
|
||||
*
|
||||
* uri URI (string) that is requested for display.
|
||||
* clientId ID of client that sent the command.
|
||||
* title Title of page that loaded URI (likely) corresponds to.
|
||||
* uri URI (string) that is requested for display.
|
||||
* sender.id ID of client that sent the command.
|
||||
* sender.name Name of client that sent the command.
|
||||
* title Title of page that loaded URI (likely) corresponds to.
|
||||
*
|
||||
* The 'data' parameter to the callback will not be defined.
|
||||
*
|
||||
@ -919,7 +920,13 @@ ClientEngine.prototype = {
|
||||
* String title of page that URI corresponds to. Older clients may not
|
||||
* send this.
|
||||
*/
|
||||
_handleDisplayURIs: function _handleDisplayURIs(uris) {
|
||||
_handleDisplayURIs(uris) {
|
||||
uris.forEach(uri => {
|
||||
uri.sender = {
|
||||
id: uri.clientId,
|
||||
name: this.getClientName(uri.clientId)
|
||||
};
|
||||
});
|
||||
Svc.Obs.notify("weave:engine:clients:display-uris", uris);
|
||||
},
|
||||
|
||||
|
@ -20,6 +20,8 @@ ChromeUtils.defineModuleGetter(this, "Status",
|
||||
"resource://services-sync/status.js");
|
||||
ChromeUtils.defineModuleGetter(this, "AddonManager",
|
||||
"resource://gre/modules/AddonManager.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "fxAccounts",
|
||||
"resource://gre/modules/FxAccounts.jsm");
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "IdleService",
|
||||
"@mozilla.org/widget/idleservice;1",
|
||||
"nsIIdleService");
|
||||
@ -531,6 +533,11 @@ SyncScheduler.prototype = {
|
||||
return;
|
||||
}
|
||||
Services.tm.dispatchToMainThread(() => {
|
||||
// Terrible hack below: we do the fxa messages polling in the sync
|
||||
// scheduler to get free post-wake/link-state etc detection.
|
||||
fxAccounts.messages.consumeRemoteMessages().catch(e => {
|
||||
this._log.error("Error while polling for FxA messages.", e);
|
||||
});
|
||||
this.service.sync({engines, why});
|
||||
});
|
||||
},
|
||||
|
@ -76,7 +76,8 @@
|
||||
"fxa_utils.js": ["initializeIdentityWithTokenServerResponse"],
|
||||
"fxaccounts.jsm": ["Authentication"],
|
||||
"FxAccounts.jsm": ["fxAccounts", "FxAccounts"],
|
||||
"FxAccountsCommon.js": ["log", "logPII", "FXACCOUNTS_PERMISSION", "DATA_FORMAT_VERSION", "DEFAULT_STORAGE_FILENAME", "ASSERTION_LIFETIME", "ASSERTION_USE_PERIOD", "CERT_LIFETIME", "KEY_LIFETIME", "POLL_SESSION", "ONLOGIN_NOTIFICATION", "ONVERIFIED_NOTIFICATION", "ONLOGOUT_NOTIFICATION", "ON_DEVICE_CONNECTED_NOTIFICATION", "ON_DEVICE_DISCONNECTED_NOTIFICATION", "ON_PROFILE_UPDATED_NOTIFICATION", "ON_PASSWORD_CHANGED_NOTIFICATION", "ON_PASSWORD_RESET_NOTIFICATION", "ON_VERIFY_LOGIN_NOTIFICATION", "ON_ACCOUNT_DESTROYED_NOTIFICATION", "ON_COLLECTION_CHANGED_NOTIFICATION", "FXA_PUSH_SCOPE_ACCOUNT_UPDATE", "ON_PROFILE_CHANGE_NOTIFICATION", "ON_ACCOUNT_STATE_CHANGE_NOTIFICATION", "UI_REQUEST_SIGN_IN_FLOW", "UI_REQUEST_REFRESH_AUTH", "FX_OAUTH_CLIENT_ID", "WEBCHANNEL_ID", "PREF_LAST_FXA_USER", "ERRNO_ACCOUNT_ALREADY_EXISTS", "ERRNO_ACCOUNT_DOES_NOT_EXIST", "ERRNO_INCORRECT_PASSWORD", "ERRNO_UNVERIFIED_ACCOUNT", "ERRNO_INVALID_VERIFICATION_CODE", "ERRNO_NOT_VALID_JSON_BODY", "ERRNO_INVALID_BODY_PARAMETERS", "ERRNO_MISSING_BODY_PARAMETERS", "ERRNO_INVALID_REQUEST_SIGNATURE", "ERRNO_INVALID_AUTH_TOKEN", "ERRNO_INVALID_AUTH_TIMESTAMP", "ERRNO_MISSING_CONTENT_LENGTH", "ERRNO_REQUEST_BODY_TOO_LARGE", "ERRNO_TOO_MANY_CLIENT_REQUESTS", "ERRNO_INVALID_AUTH_NONCE", "ERRNO_ENDPOINT_NO_LONGER_SUPPORTED", "ERRNO_INCORRECT_LOGIN_METHOD", "ERRNO_INCORRECT_KEY_RETRIEVAL_METHOD", "ERRNO_INCORRECT_API_VERSION", "ERRNO_INCORRECT_EMAIL_CASE", "ERRNO_ACCOUNT_LOCKED", "ERRNO_ACCOUNT_UNLOCKED", "ERRNO_UNKNOWN_DEVICE", "ERRNO_DEVICE_SESSION_CONFLICT", "ERRNO_SERVICE_TEMP_UNAVAILABLE", "ERRNO_PARSE", "ERRNO_NETWORK", "ERRNO_UNKNOWN_ERROR", "OAUTH_SERVER_ERRNO_OFFSET", "ERRNO_UNKNOWN_CLIENT_ID", "ERRNO_INCORRECT_CLIENT_SECRET", "ERRNO_INCORRECT_REDIRECT_URI", "ERRNO_INVALID_FXA_ASSERTION", "ERRNO_UNKNOWN_CODE", "ERRNO_INCORRECT_CODE", "ERRNO_EXPIRED_CODE", "ERRNO_OAUTH_INVALID_TOKEN", "ERRNO_INVALID_REQUEST_PARAM", "ERRNO_INVALID_RESPONSE_TYPE", "ERRNO_UNAUTHORIZED", "ERRNO_FORBIDDEN", "ERRNO_INVALID_CONTENT_TYPE", "ERROR_ACCOUNT_ALREADY_EXISTS", "ERROR_ACCOUNT_DOES_NOT_EXIST", "ERROR_ACCOUNT_LOCKED", "ERROR_ACCOUNT_UNLOCKED", "ERROR_ALREADY_SIGNED_IN_USER", "ERROR_DEVICE_SESSION_CONFLICT", "ERROR_ENDPOINT_NO_LONGER_SUPPORTED", "ERROR_INCORRECT_API_VERSION", "ERROR_INCORRECT_EMAIL_CASE", "ERROR_INCORRECT_KEY_RETRIEVAL_METHOD", "ERROR_INCORRECT_LOGIN_METHOD", "ERROR_INVALID_EMAIL", "ERROR_INVALID_AUDIENCE", "ERROR_INVALID_AUTH_TOKEN", "ERROR_INVALID_AUTH_TIMESTAMP", "ERROR_INVALID_AUTH_NONCE", "ERROR_INVALID_BODY_PARAMETERS", "ERROR_INVALID_PASSWORD", "ERROR_INVALID_VERIFICATION_CODE", "ERROR_INVALID_REFRESH_AUTH_VALUE", "ERROR_INVALID_REQUEST_SIGNATURE", "ERROR_INTERNAL_INVALID_USER", "ERROR_MISSING_BODY_PARAMETERS", "ERROR_MISSING_CONTENT_LENGTH", "ERROR_NO_TOKEN_SESSION", "ERROR_NO_SILENT_REFRESH_AUTH", "ERROR_NOT_VALID_JSON_BODY", "ERROR_OFFLINE", "ERROR_PERMISSION_DENIED", "ERROR_REQUEST_BODY_TOO_LARGE", "ERROR_SERVER_ERROR", "ERROR_SYNC_DISABLED", "ERROR_TOO_MANY_CLIENT_REQUESTS", "ERROR_SERVICE_TEMP_UNAVAILABLE", "ERROR_UI_ERROR", "ERROR_UI_REQUEST", "ERROR_PARSE", "ERROR_NETWORK", "ERROR_UNKNOWN", "ERROR_UNKNOWN_DEVICE", "ERROR_UNVERIFIED_ACCOUNT", "ERROR_UNKNOWN_CLIENT_ID", "ERROR_INCORRECT_CLIENT_SECRET", "ERROR_INCORRECT_REDIRECT_URI", "ERROR_INVALID_FXA_ASSERTION", "ERROR_UNKNOWN_CODE", "ERROR_INCORRECT_CODE", "ERROR_EXPIRED_CODE", "ERROR_OAUTH_INVALID_TOKEN", "ERROR_INVALID_REQUEST_PARAM", "ERROR_INVALID_RESPONSE_TYPE", "ERROR_UNAUTHORIZED", "ERROR_FORBIDDEN", "ERROR_INVALID_CONTENT_TYPE", "ERROR_NO_ACCOUNT", "ERROR_AUTH_ERROR", "ERROR_INVALID_PARAMETER", "ERROR_CODE_METHOD_NOT_ALLOWED", "ERROR_MSG_METHOD_NOT_ALLOWED", "DERIVED_KEYS_NAMES", "FXA_PWDMGR_PLAINTEXT_FIELDS", "FXA_PWDMGR_SECURE_FIELDS", "FXA_PWDMGR_MEMORY_FIELDS", "FXA_PWDMGR_REAUTH_WHITELIST", "FXA_PWDMGR_HOST", "FXA_PWDMGR_REALM", "SERVER_ERRNO_TO_ERROR", "ERROR_TO_GENERAL_ERROR_CLASS"],
|
||||
"FxAccountsCommon.js": ["log", "logPII", "FXACCOUNTS_PERMISSION", "DATA_FORMAT_VERSION", "DEFAULT_STORAGE_FILENAME", "ASSERTION_LIFETIME", "ASSERTION_USE_PERIOD", "CERT_LIFETIME", "KEY_LIFETIME", "POLL_SESSION", "ONLOGIN_NOTIFICATION", "ONVERIFIED_NOTIFICATION", "ONLOGOUT_NOTIFICATION", "ON_DEVICE_CONNECTED_NOTIFICATION", "ON_DEVICE_DISCONNECTED_NOTIFICATION", "ON_PROFILE_UPDATED_NOTIFICATION", "ON_PASSWORD_CHANGED_NOTIFICATION", "ON_PASSWORD_RESET_NOTIFICATION", "ON_VERIFY_LOGIN_NOTIFICATION", "ON_ACCOUNT_DESTROYED_NOTIFICATION", "ON_COLLECTION_CHANGED_NOTIFICATION", "FXA_PUSH_SCOPE_ACCOUNT_UPDATE", "ON_PROFILE_CHANGE_NOTIFICATION", "ON_ACCOUNT_STATE_CHANGE_NOTIFICATION", "CAPABILITY_MESSAGES", "CAPABILITY_MESSAGES_SENDTAB", "UI_REQUEST_SIGN_IN_FLOW", "UI_REQUEST_REFRESH_AUTH", "FX_OAUTH_CLIENT_ID", "WEBCHANNEL_ID", "PREF_LAST_FXA_USER", "ERRNO_ACCOUNT_ALREADY_EXISTS", "ERRNO_ACCOUNT_DOES_NOT_EXIST", "ERRNO_INCORRECT_PASSWORD", "ERRNO_UNVERIFIED_ACCOUNT", "ERRNO_INVALID_VERIFICATION_CODE", "ERRNO_NOT_VALID_JSON_BODY", "ERRNO_INVALID_BODY_PARAMETERS", "ERRNO_MISSING_BODY_PARAMETERS", "ERRNO_INVALID_REQUEST_SIGNATURE", "ERRNO_INVALID_AUTH_TOKEN", "ERRNO_INVALID_AUTH_TIMESTAMP", "ERRNO_MISSING_CONTENT_LENGTH", "ERRNO_REQUEST_BODY_TOO_LARGE", "ERRNO_TOO_MANY_CLIENT_REQUESTS", "ERRNO_INVALID_AUTH_NONCE", "ERRNO_ENDPOINT_NO_LONGER_SUPPORTED", "ERRNO_INCORRECT_LOGIN_METHOD", "ERRNO_INCORRECT_KEY_RETRIEVAL_METHOD", "ERRNO_INCORRECT_API_VERSION", "ERRNO_INCORRECT_EMAIL_CASE", "ERRNO_ACCOUNT_LOCKED", "ERRNO_ACCOUNT_UNLOCKED", "ERRNO_UNKNOWN_DEVICE", "ERRNO_DEVICE_SESSION_CONFLICT", "ERRNO_SERVICE_TEMP_UNAVAILABLE", "ERRNO_PARSE", "ERRNO_NETWORK", "ERRNO_UNKNOWN_ERROR", "OAUTH_SERVER_ERRNO_OFFSET", "ERRNO_UNKNOWN_CLIENT_ID", "ERRNO_INCORRECT_CLIENT_SECRET", "ERRNO_INCORRECT_REDIRECT_URI", "ERRNO_INVALID_FXA_ASSERTION", "ERRNO_UNKNOWN_CODE", "ERRNO_INCORRECT_CODE", "ERRNO_EXPIRED_CODE", "ERRNO_OAUTH_INVALID_TOKEN", "ERRNO_INVALID_REQUEST_PARAM", "ERRNO_INVALID_RESPONSE_TYPE", "ERRNO_UNAUTHORIZED", "ERRNO_FORBIDDEN", "ERRNO_INVALID_CONTENT_TYPE", "ERROR_ACCOUNT_ALREADY_EXISTS", "ERROR_ACCOUNT_DOES_NOT_EXIST", "ERROR_ACCOUNT_LOCKED", "ERROR_ACCOUNT_UNLOCKED", "ERROR_ALREADY_SIGNED_IN_USER", "ERROR_DEVICE_SESSION_CONFLICT", "ERROR_ENDPOINT_NO_LONGER_SUPPORTED", "ERROR_INCORRECT_API_VERSION", "ERROR_INCORRECT_EMAIL_CASE", "ERROR_INCORRECT_KEY_RETRIEVAL_METHOD", "ERROR_INCORRECT_LOGIN_METHOD", "ERROR_INVALID_EMAIL", "ERROR_INVALID_AUDIENCE", "ERROR_INVALID_AUTH_TOKEN", "ERROR_INVALID_AUTH_TIMESTAMP", "ERROR_INVALID_AUTH_NONCE", "ERROR_INVALID_BODY_PARAMETERS", "ERROR_INVALID_PASSWORD", "ERROR_INVALID_VERIFICATION_CODE", "ERROR_INVALID_REFRESH_AUTH_VALUE", "ERROR_INVALID_REQUEST_SIGNATURE", "ERROR_INTERNAL_INVALID_USER", "ERROR_MISSING_BODY_PARAMETERS", "ERROR_MISSING_CONTENT_LENGTH", "ERROR_NO_TOKEN_SESSION", "ERROR_NO_SILENT_REFRESH_AUTH", "ERROR_NOT_VALID_JSON_BODY", "ERROR_OFFLINE", "ERROR_PERMISSION_DENIED", "ERROR_REQUEST_BODY_TOO_LARGE", "ERROR_SERVER_ERROR", "ERROR_SYNC_DISABLED", "ERROR_TOO_MANY_CLIENT_REQUESTS", "ERROR_SERVICE_TEMP_UNAVAILABLE", "ERROR_UI_ERROR", "ERROR_UI_REQUEST", "ERROR_PARSE", "ERROR_NETWORK", "ERROR_UNKNOWN", "ERROR_UNKNOWN_DEVICE", "ERROR_UNVERIFIED_ACCOUNT", "ERROR_UNKNOWN_CLIENT_ID", "ERROR_INCORRECT_CLIENT_SECRET", "ERROR_INCORRECT_REDIRECT_URI", "ERROR_INVALID_FXA_ASSERTION", "ERROR_UNKNOWN_CODE", "ERROR_INCORRECT_CODE", "ERROR_EXPIRED_CODE", "ERROR_OAUTH_INVALID_TOKEN", "ERROR_INVALID_REQUEST_PARAM", "ERROR_INVALID_RESPONSE_TYPE", "ERROR_UNAUTHORIZED", "ERROR_FORBIDDEN", "ERROR_INVALID_CONTENT_TYPE", "ERROR_NO_ACCOUNT", "ERROR_AUTH_ERROR", "ERROR_INVALID_PARAMETER", "ERROR_CODE_METHOD_NOT_ALLOWED", "ERROR_MSG_METHOD_NOT_ALLOWED", "DERIVED_KEYS_NAMES", "FXA_PWDMGR_PLAINTEXT_FIELDS", "FXA_PWDMGR_SECURE_FIELDS", "FXA_PWDMGR_MEMORY_FIELDS", "FXA_PWDMGR_REAUTH_WHITELIST", "FXA_PWDMGR_HOST", "FXA_PWDMGR_REALM", "SERVER_ERRNO_TO_ERROR", "ERROR_TO_GENERAL_ERROR_CLASS"],
|
||||
"FxAccountsMessages.js": ["FxAccountsMessages", "FxAccountsMessagesSender", "FxAccountsMessagesReceiver", "FxAccountsMessagesHandler"],
|
||||
"FxAccountsOAuthGrantClient.jsm": ["FxAccountsOAuthGrantClient", "FxAccountsOAuthGrantClientError"],
|
||||
"FxAccountsProfileClient.jsm": ["FxAccountsProfileClient", "FxAccountsProfileClientError"],
|
||||
"FxAccountsPush.js": ["FxAccountsPushService"],
|
||||
|
Loading…
x
Reference in New Issue
Block a user