mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-28 11:28:38 +00:00
7d9c066f35
MozReview-Commit-ID: 8vAuTImx7IH --HG-- extra : rebase_source : 15995e30bd1fdb697eb2374a0c28c68e0828e1e6
181 lines
7.2 KiB
JavaScript
181 lines
7.2 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/. */
|
|
|
|
this.EXPORTED_SYMBOLS = ["checkVersions", "addTestBlocklistClient"];
|
|
|
|
const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu } = Components;
|
|
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
Cu.import("resource://gre/modules/Services.jsm");
|
|
Cu.importGlobalProperties(["fetch"]);
|
|
XPCOMUtils.defineLazyModuleGetter(this, "UptakeTelemetry",
|
|
"resource://services-common/uptake-telemetry.js");
|
|
|
|
const PREF_SETTINGS_SERVER = "services.settings.server";
|
|
const PREF_SETTINGS_SERVER_BACKOFF = "services.settings.server.backoff";
|
|
const PREF_BLOCKLIST_CHANGES_PATH = "services.blocklist.changes.path";
|
|
const PREF_BLOCKLIST_LAST_UPDATE = "services.blocklist.last_update_seconds";
|
|
const PREF_BLOCKLIST_LAST_ETAG = "services.blocklist.last_etag";
|
|
const PREF_BLOCKLIST_CLOCK_SKEW_SECONDS = "services.blocklist.clock_skew_seconds";
|
|
|
|
// Telemetry update source identifier.
|
|
const TELEMETRY_HISTOGRAM_KEY = "settings-changes-monitoring";
|
|
|
|
|
|
XPCOMUtils.defineLazyGetter(this, "gBlocklistClients", function() {
|
|
const BlocklistClients = Cu.import("resource://services-common/blocklist-clients.js", {});
|
|
return {
|
|
[BlocklistClients.OneCRLBlocklistClient.collectionName]: BlocklistClients.OneCRLBlocklistClient,
|
|
[BlocklistClients.AddonBlocklistClient.collectionName]: BlocklistClients.AddonBlocklistClient,
|
|
[BlocklistClients.GfxBlocklistClient.collectionName]: BlocklistClients.GfxBlocklistClient,
|
|
[BlocklistClients.PluginBlocklistClient.collectionName]: BlocklistClients.PluginBlocklistClient,
|
|
[BlocklistClients.PinningPreloadClient.collectionName]: BlocklistClients.PinningPreloadClient,
|
|
};
|
|
});
|
|
|
|
// Add a blocklist client for testing purposes. Do not use for any other purpose
|
|
this.addTestBlocklistClient = (name, client) => { gBlocklistClients[name] = client; }
|
|
|
|
|
|
async function pollChanges(url, lastEtag) {
|
|
//
|
|
// Fetch a versionInfo object from the server that looks like:
|
|
// {"data":[
|
|
// {
|
|
// "host":"kinto-ota.dev.mozaws.net",
|
|
// "last_modified":1450717104423,
|
|
// "bucket":"blocklists",
|
|
// "collection":"certificates"
|
|
// }]}
|
|
|
|
// Use ETag to obtain a `304 Not modified` when no change occurred.
|
|
const headers = {};
|
|
if (lastEtag) {
|
|
headers["If-None-Match"] = lastEtag;
|
|
}
|
|
const response = await fetch(url, {headers});
|
|
|
|
let versionInfo = [];
|
|
// If no changes since last time, go on with empty list of changes.
|
|
if (response.status != 304) {
|
|
let payload;
|
|
try {
|
|
payload = await response.json();
|
|
} catch (e) {}
|
|
if (!payload.hasOwnProperty("data")) {
|
|
// If the server is failing, the JSON response might not contain the
|
|
// expected data (e.g. error response - Bug 1259145)
|
|
throw new Error(`Server error response ${JSON.stringify(payload)}`);
|
|
}
|
|
versionInfo = payload.data;
|
|
}
|
|
// The server should always return ETag. But we've had situations where the CDN
|
|
// was interfering.
|
|
const currentEtag = response.headers.has("ETag") ? response.headers.get("ETag") : undefined;
|
|
const serverTimeMillis = Date.parse(response.headers.get("Date"));
|
|
|
|
// Check if the server asked the clients to back off.
|
|
let backoffSeconds;
|
|
if (response.headers.has("Backoff")) {
|
|
const value = parseInt(response.headers.get("Backoff"), 10);
|
|
if (!isNaN(value)) {
|
|
backoffSeconds = value;
|
|
}
|
|
}
|
|
|
|
return {versionInfo, currentEtag, serverTimeMillis, backoffSeconds};
|
|
}
|
|
|
|
|
|
// This is called by the ping mechanism.
|
|
// returns a promise that rejects if something goes wrong
|
|
this.checkVersions = async function() {
|
|
// Check if the server backoff time is elapsed.
|
|
if (Services.prefs.prefHasUserValue(PREF_SETTINGS_SERVER_BACKOFF)) {
|
|
const backoffReleaseTime = Services.prefs.getCharPref(PREF_SETTINGS_SERVER_BACKOFF);
|
|
const remainingMilliseconds = parseInt(backoffReleaseTime, 10) - Date.now();
|
|
if (remainingMilliseconds > 0) {
|
|
// Backoff time has not elapsed yet.
|
|
UptakeTelemetry.report(TELEMETRY_HISTOGRAM_KEY,
|
|
UptakeTelemetry.STATUS.BACKOFF);
|
|
throw new Error(`Server is asking clients to back off; retry in ${Math.ceil(remainingMilliseconds / 1000)}s.`);
|
|
} else {
|
|
Services.prefs.clearUserPref(PREF_SETTINGS_SERVER_BACKOFF);
|
|
}
|
|
}
|
|
|
|
// Right now, we only use the collection name and the last modified info
|
|
const kintoBase = Services.prefs.getCharPref(PREF_SETTINGS_SERVER);
|
|
const changesEndpoint = kintoBase + Services.prefs.getCharPref(PREF_BLOCKLIST_CHANGES_PATH);
|
|
|
|
let lastEtag;
|
|
if (Services.prefs.prefHasUserValue(PREF_BLOCKLIST_LAST_ETAG)) {
|
|
lastEtag = Services.prefs.getCharPref(PREF_BLOCKLIST_LAST_ETAG);
|
|
}
|
|
|
|
let pollResult;
|
|
try {
|
|
pollResult = await pollChanges(changesEndpoint, lastEtag);
|
|
} catch (e) {
|
|
// Report polling error to Uptake Telemetry.
|
|
let report;
|
|
if (/Server/.test(e.message)) {
|
|
report = UptakeTelemetry.STATUS.SERVER_ERROR;
|
|
} else if (/NetworkError/.test(e.message)) {
|
|
report = UptakeTelemetry.STATUS.NETWORK_ERROR;
|
|
} else {
|
|
report = UptakeTelemetry.STATUS.UNKNOWN_ERROR;
|
|
}
|
|
UptakeTelemetry.report(TELEMETRY_HISTOGRAM_KEY, report);
|
|
// No need to go further.
|
|
throw new Error(`Polling for changes failed: ${e.message}.`);
|
|
}
|
|
|
|
const {serverTimeMillis, versionInfo, currentEtag, backoffSeconds} = pollResult;
|
|
|
|
// Report polling success to Uptake Telemetry.
|
|
const report = versionInfo.length == 0 ? UptakeTelemetry.STATUS.UP_TO_DATE
|
|
: UptakeTelemetry.STATUS.SUCCESS;
|
|
UptakeTelemetry.report(TELEMETRY_HISTOGRAM_KEY, report);
|
|
|
|
// Check if the server asked the clients to back off (for next poll).
|
|
if (backoffSeconds) {
|
|
const backoffReleaseTime = Date.now() + backoffSeconds * 1000;
|
|
Services.prefs.setCharPref(PREF_SETTINGS_SERVER_BACKOFF, backoffReleaseTime);
|
|
}
|
|
|
|
// Record new update time and the difference between local and server time.
|
|
// Negative clockDifference means local time is behind server time
|
|
// by the absolute of that value in seconds (positive means it's ahead)
|
|
const clockDifference = Math.floor((Date.now() - serverTimeMillis) / 1000);
|
|
Services.prefs.setIntPref(PREF_BLOCKLIST_CLOCK_SKEW_SECONDS, clockDifference);
|
|
Services.prefs.setIntPref(PREF_BLOCKLIST_LAST_UPDATE, serverTimeMillis / 1000);
|
|
|
|
// Iterate through the collections version info and initiate a synchronization
|
|
// on the related blocklist client.
|
|
let firstError;
|
|
for (const collectionInfo of versionInfo) {
|
|
const {bucket, collection, last_modified: lastModified} = collectionInfo;
|
|
const client = gBlocklistClients[collection];
|
|
if (client && client.bucketName == bucket) {
|
|
try {
|
|
await client.maybeSync(lastModified, serverTimeMillis);
|
|
} catch (e) {
|
|
if (!firstError) {
|
|
firstError = e;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (firstError) {
|
|
// cause the promise to reject by throwing the first observed error
|
|
throw firstError;
|
|
}
|
|
|
|
// Save current Etag for next poll.
|
|
if (currentEtag) {
|
|
Services.prefs.setCharPref(PREF_BLOCKLIST_LAST_ETAG, currentEtag);
|
|
}
|
|
};
|