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(
10 "resource://gre/modules/AppConstants.jsm"
12 ChromeUtils.defineModuleGetter(
15 "resource://gre/modules/UpdateUtils.jsm"
17 ChromeUtils.defineModuleGetter(
19 "TelemetryEnvironment",
20 "resource://gre/modules/TelemetryEnvironment.jsm"
22 ChromeUtils.defineModuleGetter(
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";
37 * Observe various notifications and send them to a telemetry endpoint.
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.
44 constructor(options) {
46 throw new Error("Must specify topic.");
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);
66 return this._enabled && this._fhrEnabled;
69 _onLoggingPrefChange(aSubject, aTopic, prefKey) {
70 this.logging = this._prefs.getBoolPref(prefKey);
73 _onTelemetryPrefChange(aSubject, aTopic, prefKey) {
74 this._enabled = this._prefs.getBoolPref(prefKey);
77 _onFhrPrefChange(aSubject, aTopic, prefKey) {
78 this._fhrEnabled = this._prefs.getBoolPref(prefKey);
81 _createExperimentsPayload() {
82 let activeExperiments = TelemetryEnvironment.getActiveExperiments();
84 for (let experimentID in activeExperiments) {
86 activeExperiments[experimentID] &&
87 activeExperiments[experimentID].branch
89 experiments[experimentID] = {
90 branch: activeExperiments[experimentID].branch,
97 _createStructuredIngestionPing(data) {
98 let experiments = this._createExperimentsPayload();
99 let locale = data.locale || Services.locale.appLocaleAsBCP47;
103 version: AppConstants.MOZ_APP_VERSION,
104 release_channel: UpdateUtils.getUpdateChannel(false),
111 static _gzipCompressString(string) {
114 onStreamComplete(loader, context, status, length, result) {
115 this.buffer = String.fromCharCode(...result);
119 let scs = Cc["@mozilla.org/streamConverters;1"].getService(
120 Ci.nsIStreamConverterService
122 let listener = Cc["@mozilla.org/network/stream-loader;1"].createInstance(
125 listener.init(observer);
126 let converter = scs.asyncConvertData(
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;
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(
152 "application/json; charset=UTF-8"
154 request.setRequestHeader("Content-Encoding", "gzip");
155 request.setRequestHeader("Date", new Date().toUTCString());
157 request.onload = event => {
158 if (request.status !== 200) {
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);
177 * Sends a ping to the Structured Ingestion telemetry pipeline.
179 * The payload would be compressed using gzip.
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
187 sendStructuredIngestionPing(data, endpoint) {
189 return Promise.resolve();
192 const ping = this._createStructuredIngestionPing(data);
193 const payload = JSON.stringify(ping);
196 Services.console.logStringMessage(
197 `TELEMETRY PING (${this._topic}): ${payload}\n`
201 return PingCentre._sendInGzip(endpoint, payload).catch(event => {
203 `Structured Ingestion ping failure with error: ${event.type}`
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
222 this.PingCentre = PingCentre;
223 this.PingCentreConstants = {
224 FHR_UPLOAD_ENABLED_PREF,
228 const EXPORTED_SYMBOLS = ["PingCentre", "PingCentreConstants"];