123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266 |
- /* 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 {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
- const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
- XPCOMUtils.defineLazyGlobalGetters(this, ["fetch"]);
- ChromeUtils.defineModuleGetter(this, "AppConstants",
- "resource://gre/modules/AppConstants.jsm");
- ChromeUtils.defineModuleGetter(this, "UpdateUtils",
- "resource://gre/modules/UpdateUtils.jsm");
- ChromeUtils.defineModuleGetter(this, "ClientID",
- "resource://gre/modules/ClientID.jsm");
- ChromeUtils.defineModuleGetter(this, "TelemetryEnvironment",
- "resource://gre/modules/TelemetryEnvironment.jsm");
- const PREF_BRANCH = "browser.ping-centre.";
- const TELEMETRY_PREF = `${PREF_BRANCH}telemetry`;
- const LOGGING_PREF = `${PREF_BRANCH}log`;
- const PRODUCTION_ENDPOINT_PREF = `${PREF_BRANCH}production.endpoint`;
- const FHR_UPLOAD_ENABLED_PREF = "datareporting.healthreport.uploadEnabled";
- const BROWSER_SEARCH_REGION_PREF = "browser.search.region";
- // Only report region for following regions, to ensure that users in countries
- // with small user population (less than 10000) cannot be uniquely identified.
- // See bug 1421422 for more details.
- const REGION_WHITELIST = new Set([
- "AE", "AF", "AL", "AM", "AR", "AT", "AU", "AZ", "BA", "BD", "BE", "BF",
- "BG", "BJ", "BO", "BR", "BY", "CA", "CH", "CI", "CL", "CM", "CN", "CO",
- "CR", "CU", "CY", "CZ", "DE", "DK", "DO", "DZ", "EC", "EE", "EG", "ES",
- "ET", "FI", "FR", "GB", "GE", "GH", "GP", "GR", "GT", "HK", "HN", "HR",
- "HU", "ID", "IE", "IL", "IN", "IQ", "IR", "IS", "IT", "JM", "JO", "JP",
- "KE", "KH", "KR", "KW", "KZ", "LB", "LK", "LT", "LU", "LV", "LY", "MA",
- "MD", "ME", "MG", "MK", "ML", "MM", "MN", "MQ", "MT", "MU", "MX", "MY",
- "MZ", "NC", "NG", "NI", "NL", "NO", "NP", "NZ", "OM", "PA", "PE", "PH",
- "PK", "PL", "PR", "PS", "PT", "PY", "QA", "RE", "RO", "RS", "RU", "RW",
- "SA", "SD", "SE", "SG", "SI", "SK", "SN", "SV", "SY", "TG", "TH", "TN",
- "TR", "TT", "TW", "TZ", "UA", "UG", "US", "UY", "UZ", "VE", "VN", "ZA",
- "ZM", "ZW",
- ]);
- /**
- * Observe various notifications and send them to a telemetry endpoint.
- *
- * @param {Object} options
- * @param {string} options.topic - a unique ID for users of PingCentre to distinguish
- * their data on the server side.
- * @param {string} options.overrideEndpointPref - optional pref for URL where the POST is sent.
- */
- class PingCentre {
- constructor(options) {
- if (!options.topic) {
- throw new Error("Must specify topic.");
- }
- this._topic = options.topic;
- this._prefs = Services.prefs.getBranch("");
- this._setPingEndpoint(options.topic, options.overrideEndpointPref);
- this._enabled = this._prefs.getBoolPref(TELEMETRY_PREF);
- this._onTelemetryPrefChange = this._onTelemetryPrefChange.bind(this);
- this._prefs.addObserver(TELEMETRY_PREF, this._onTelemetryPrefChange);
- this._fhrEnabled = this._prefs.getBoolPref(FHR_UPLOAD_ENABLED_PREF);
- this._onFhrPrefChange = this._onFhrPrefChange.bind(this);
- this._prefs.addObserver(FHR_UPLOAD_ENABLED_PREF, this._onFhrPrefChange);
- this.logging = this._prefs.getBoolPref(LOGGING_PREF);
- this._onLoggingPrefChange = this._onLoggingPrefChange.bind(this);
- this._prefs.addObserver(LOGGING_PREF, this._onLoggingPrefChange);
- }
- /**
- * Lazily get the Telemetry id promise
- */
- get telemetryClientId() {
- Object.defineProperty(this, "telemetryClientId", {value: ClientID.getClientID()});
- return this.telemetryClientId;
- }
- get enabled() {
- return this._enabled && this._fhrEnabled;
- }
- _setPingEndpoint(topic, overrideEndpointPref) {
- const overrideValue = overrideEndpointPref &&
- this._prefs.getStringPref(overrideEndpointPref);
- if (overrideValue) {
- this._pingEndpoint = overrideValue;
- } else {
- this._pingEndpoint = this._prefs.getStringPref(PRODUCTION_ENDPOINT_PREF);
- }
- }
- _onLoggingPrefChange(aSubject, aTopic, prefKey) {
- this.logging = this._prefs.getBoolPref(prefKey);
- }
- _onTelemetryPrefChange(aSubject, aTopic, prefKey) {
- this._enabled = this._prefs.getBoolPref(prefKey);
- }
- _onFhrPrefChange(aSubject, aTopic, prefKey) {
- this._fhrEnabled = this._prefs.getBoolPref(prefKey);
- }
- _createExperimentsString(activeExperiments, filter) {
- let experimentsString = "";
- for (let experimentID in activeExperiments) {
- if (!activeExperiments[experimentID] ||
- !activeExperiments[experimentID].branch ||
- (filter && !experimentID.includes(filter))) {
- continue;
- }
- let expString = `${experimentID}:${activeExperiments[experimentID].branch}`;
- experimentsString = experimentsString.concat(`${expString};`);
- }
- return experimentsString;
- }
- _getRegion() {
- let region = "UNSET";
- if (Services.prefs.prefHasUserValue(BROWSER_SEARCH_REGION_PREF)) {
- region = Services.prefs.getStringPref(BROWSER_SEARCH_REGION_PREF);
- if (region === "") {
- region = "EMPTY";
- } else if (!REGION_WHITELIST.has(region)) {
- region = "OTHER";
- }
- }
- return region;
- }
- async _createPing(data, options) {
- let filter = options && options.filter;
- let experiments = TelemetryEnvironment.getActiveExperiments();
- let experimentsString = this._createExperimentsString(experiments, filter);
- let clientID = data.client_id || await this.telemetryClientId;
- let locale = data.locale || Services.locale.appLocaleAsLangTag;
- let profileCreationDate = TelemetryEnvironment.currentEnvironment.profile.resetDate ||
- TelemetryEnvironment.currentEnvironment.profile.creationDate;
- const payload = Object.assign({
- locale,
- topic: this._topic,
- client_id: clientID,
- version: AppConstants.MOZ_APP_VERSION,
- release_channel: UpdateUtils.getUpdateChannel(false),
- }, data);
- if (experimentsString) {
- payload.shield_id = experimentsString;
- }
- if (profileCreationDate) {
- payload.profile_creation_date = profileCreationDate;
- }
- payload.region = this._getRegion();
- return payload;
- }
- async _createStructuredIngestionPing(data, options) {
- let filter = options && options.filter;
- let experiments = TelemetryEnvironment.getActiveExperiments();
- let experimentsString = this._createExperimentsString(experiments, filter);
- let clientID = data.client_id || await this.telemetryClientId;
- let locale = data.locale || Services.locale.appLocaleAsLangTag;
- const payload = Object.assign({
- locale,
- client_id: clientID,
- version: AppConstants.MOZ_APP_VERSION,
- release_channel: UpdateUtils.getUpdateChannel(false),
- }, data);
- if (experimentsString) {
- payload.shield_id = experimentsString;
- }
- return payload;
- }
- async sendPing(data, options) {
- if (!this.enabled) {
- return Promise.resolve();
- }
- const payload = await this._createPing(data, options);
- if (this.logging) {
- // performance related pings cause a lot of logging, so we mute them
- if (data.action !== "activity_stream_performance") {
- Services.console.logStringMessage(`TELEMETRY PING: ${JSON.stringify(payload)}\n`);
- }
- }
- return fetch(this._pingEndpoint, {
- method: "POST",
- body: JSON.stringify(payload),
- credentials: "omit",
- }).then(response => {
- if (!response.ok) {
- Cu.reportError(`Ping failure with HTTP response code: ${response.status}`);
- }
- }).catch(e => {
- Cu.reportError(`Ping failure with error: ${e}`);
- });
- }
- /**
- * Sends a ping to the Structured Ingestion telemetry pipeline.
- *
- * @param {Object} data The payload to be sent.
- * @param {String} endpoint The destination endpoint. Note that Structured Ingestion
- * requires a different endpoint for each ping. It's up to the
- * caller to provide that. See more details at
- * https://github.com/mozilla/gcp-ingestion/blob/master/docs/edge.md#postput-request
- * @param {Object} options Other options for this ping.
- */
- async sendStructuredIngestionPing(data, endpoint, options) {
- if (!this.enabled) {
- return Promise.resolve();
- }
- const payload = await this._createStructuredIngestionPing(data, options);
- if (this.logging) {
- Services.console.logStringMessage(`TELEMETRY PING (STRUCTURED INGESTION): ${JSON.stringify(payload)}\n`);
- }
- return fetch(endpoint, {
- method: "POST",
- body: JSON.stringify(payload),
- credentials: "omit",
- }).then(response => {
- if (!response.ok) {
- Cu.reportError(`Structured Ingestion ping failure with HTTP response code: ${response.status}`);
- }
- }).catch(e => {
- Cu.reportError(`Structured Ingestion ping failure with error: ${e}`);
- });
- }
- uninit() {
- try {
- this._prefs.removeObserver(TELEMETRY_PREF, this._onTelemetryPrefChange);
- this._prefs.removeObserver(LOGGING_PREF, this._onLoggingPrefChange);
- this._prefs.removeObserver(FHR_UPLOAD_ENABLED_PREF, this._onFhrPrefChange);
- } catch (e) {
- Cu.reportError(e);
- }
- }
- }
- this.PingCentre = PingCentre;
- this.PingCentreConstants = {
- PRODUCTION_ENDPOINT_PREF,
- FHR_UPLOAD_ENABLED_PREF,
- TELEMETRY_PREF,
- LOGGING_PREF,
- };
- const EXPORTED_SYMBOLS = ["PingCentre", "PingCentreConstants"];
|