Bug 1700051: part 45) Remove outdated part of comment in <mozInlineSpellChecker.cpp...
[gecko.git] / browser / modules / PingCentre.jsm
blob3f81c07c171410162022099ad8b4070130ef9700
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
7 ChromeUtils.defineModuleGetter(
8   this,
9   "AppConstants",
10   "resource://gre/modules/AppConstants.jsm"
12 ChromeUtils.defineModuleGetter(
13   this,
14   "UpdateUtils",
15   "resource://gre/modules/UpdateUtils.jsm"
17 ChromeUtils.defineModuleGetter(
18   this,
19   "TelemetryEnvironment",
20   "resource://gre/modules/TelemetryEnvironment.jsm"
22 ChromeUtils.defineModuleGetter(
23   this,
24   "ServiceRequest",
25   "resource://gre/modules/ServiceRequest.jsm"
28 const PREF_BRANCH = "browser.ping-centre.";
30 const TELEMETRY_PREF = `${PREF_BRANCH}telemetry`;
31 const LOGGING_PREF = `${PREF_BRANCH}log`;
32 const STRUCTURED_INGESTION_SEND_TIMEOUT = 30 * 1000; // 30 seconds
34 const FHR_UPLOAD_ENABLED_PREF = "datareporting.healthreport.uploadEnabled";
36 /**
37  * Observe various notifications and send them to a telemetry endpoint.
38  *
39  * @param {Object} options
40  * @param {string} options.topic - a unique ID for users of PingCentre to distinguish
41  *                  their data on the server side.
42  */
43 class PingCentre {
44   constructor(options) {
45     if (!options.topic) {
46       throw new Error("Must specify topic.");
47     }
49     this._topic = options.topic;
50     this._prefs = Services.prefs.getBranch("");
52     this._enabled = this._prefs.getBoolPref(TELEMETRY_PREF);
53     this._onTelemetryPrefChange = this._onTelemetryPrefChange.bind(this);
54     this._prefs.addObserver(TELEMETRY_PREF, this._onTelemetryPrefChange);
56     this._fhrEnabled = this._prefs.getBoolPref(FHR_UPLOAD_ENABLED_PREF);
57     this._onFhrPrefChange = this._onFhrPrefChange.bind(this);
58     this._prefs.addObserver(FHR_UPLOAD_ENABLED_PREF, this._onFhrPrefChange);
60     this.logging = this._prefs.getBoolPref(LOGGING_PREF);
61     this._onLoggingPrefChange = this._onLoggingPrefChange.bind(this);
62     this._prefs.addObserver(LOGGING_PREF, this._onLoggingPrefChange);
63   }
65   get enabled() {
66     return this._enabled && this._fhrEnabled;
67   }
69   _onLoggingPrefChange(aSubject, aTopic, prefKey) {
70     this.logging = this._prefs.getBoolPref(prefKey);
71   }
73   _onTelemetryPrefChange(aSubject, aTopic, prefKey) {
74     this._enabled = this._prefs.getBoolPref(prefKey);
75   }
77   _onFhrPrefChange(aSubject, aTopic, prefKey) {
78     this._fhrEnabled = this._prefs.getBoolPref(prefKey);
79   }
81   _createExperimentsPayload() {
82     let activeExperiments = TelemetryEnvironment.getActiveExperiments();
83     let experiments = {};
84     for (let experimentID in activeExperiments) {
85       if (
86         activeExperiments[experimentID] &&
87         activeExperiments[experimentID].branch
88       ) {
89         experiments[experimentID] = {
90           branch: activeExperiments[experimentID].branch,
91         };
92       }
93     }
94     return experiments;
95   }
97   _createStructuredIngestionPing(data) {
98     let experiments = this._createExperimentsPayload();
99     let locale = data.locale || Services.locale.appLocaleAsBCP47;
100     const payload = {
101       experiments,
102       locale,
103       version: AppConstants.MOZ_APP_VERSION,
104       release_channel: UpdateUtils.getUpdateChannel(false),
105       ...data,
106     };
108     return payload;
109   }
111   static _gzipCompressString(string) {
112     let observer = {
113       buffer: "",
114       onStreamComplete(loader, context, status, length, result) {
115         this.buffer = String.fromCharCode(...result);
116       },
117     };
119     let scs = Cc["@mozilla.org/streamConverters;1"].getService(
120       Ci.nsIStreamConverterService
121     );
122     let listener = Cc["@mozilla.org/network/stream-loader;1"].createInstance(
123       Ci.nsIStreamLoader
124     );
125     listener.init(observer);
126     let converter = scs.asyncConvertData(
127       "uncompressed",
128       "gzip",
129       listener,
130       null
131     );
132     let stringStream = Cc[
133       "@mozilla.org/io/string-input-stream;1"
134     ].createInstance(Ci.nsIStringInputStream);
135     stringStream.data = string;
136     converter.onStartRequest(null, null);
137     converter.onDataAvailable(null, stringStream, 0, string.length);
138     converter.onStopRequest(null, null, null);
139     return observer.buffer;
140   }
142   static _sendInGzip(endpoint, payload) {
143     return new Promise((resolve, reject) => {
144       let request = new ServiceRequest({ mozAnon: true });
145       request.mozBackgroundRequest = true;
146       request.timeout = STRUCTURED_INGESTION_SEND_TIMEOUT;
148       request.open("POST", endpoint, true);
149       request.overrideMimeType("text/plain");
150       request.setRequestHeader(
151         "Content-Type",
152         "application/json; charset=UTF-8"
153       );
154       request.setRequestHeader("Content-Encoding", "gzip");
155       request.setRequestHeader("Date", new Date().toUTCString());
157       request.onload = event => {
158         if (request.status !== 200) {
159           reject(event);
160         } else {
161           resolve(event);
162         }
163       };
164       request.onerror = reject;
165       request.onabort = reject;
166       request.ontimeout = reject;
168       let payloadStream = Cc[
169         "@mozilla.org/io/string-input-stream;1"
170       ].createInstance(Ci.nsIStringInputStream);
171       payloadStream.data = PingCentre._gzipCompressString(payload);
172       request.sendInputStream(payloadStream);
173     });
174   }
176   /**
177    * Sends a ping to the Structured Ingestion telemetry pipeline.
178    *
179    * The payload would be compressed using gzip.
180    *
181    * @param {Object} data     The payload to be sent.
182    * @param {String} endpoint The destination endpoint. Note that Structured Ingestion
183    *                          requires a different endpoint for each ping. It's up to the
184    *                          caller to provide that. See more details at
185    *                          https://github.com/mozilla/gcp-ingestion/blob/master/docs/edge.md#postput-request
186    */
187   sendStructuredIngestionPing(data, endpoint) {
188     if (!this.enabled) {
189       return Promise.resolve();
190     }
192     const ping = this._createStructuredIngestionPing(data);
193     const payload = JSON.stringify(ping);
195     if (this.logging) {
196       Services.console.logStringMessage(
197         `TELEMETRY PING (${this._topic}): ${payload}\n`
198       );
199     }
201     return PingCentre._sendInGzip(endpoint, payload).catch(event => {
202       Cu.reportError(
203         `Structured Ingestion ping failure with error: ${event.type}`
204       );
205     });
206   }
208   uninit() {
209     try {
210       this._prefs.removeObserver(TELEMETRY_PREF, this._onTelemetryPrefChange);
211       this._prefs.removeObserver(LOGGING_PREF, this._onLoggingPrefChange);
212       this._prefs.removeObserver(
213         FHR_UPLOAD_ENABLED_PREF,
214         this._onFhrPrefChange
215       );
216     } catch (e) {
217       Cu.reportError(e);
218     }
219   }
222 this.PingCentre = PingCentre;
223 this.PingCentreConstants = {
224   FHR_UPLOAD_ENABLED_PREF,
225   TELEMETRY_PREF,
226   LOGGING_PREF,
228 const EXPORTED_SYMBOLS = ["PingCentre", "PingCentreConstants"];