PingCentre.jsm 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  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/. */
  4. const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
  5. const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
  6. XPCOMUtils.defineLazyGlobalGetters(this, ["fetch"]);
  7. ChromeUtils.defineModuleGetter(this, "AppConstants",
  8. "resource://gre/modules/AppConstants.jsm");
  9. ChromeUtils.defineModuleGetter(this, "UpdateUtils",
  10. "resource://gre/modules/UpdateUtils.jsm");
  11. ChromeUtils.defineModuleGetter(this, "ClientID",
  12. "resource://gre/modules/ClientID.jsm");
  13. ChromeUtils.defineModuleGetter(this, "TelemetryEnvironment",
  14. "resource://gre/modules/TelemetryEnvironment.jsm");
  15. const PREF_BRANCH = "browser.ping-centre.";
  16. const TELEMETRY_PREF = `${PREF_BRANCH}telemetry`;
  17. const LOGGING_PREF = `${PREF_BRANCH}log`;
  18. const PRODUCTION_ENDPOINT_PREF = `${PREF_BRANCH}production.endpoint`;
  19. const FHR_UPLOAD_ENABLED_PREF = "datareporting.healthreport.uploadEnabled";
  20. const BROWSER_SEARCH_REGION_PREF = "browser.search.region";
  21. // Only report region for following regions, to ensure that users in countries
  22. // with small user population (less than 10000) cannot be uniquely identified.
  23. // See bug 1421422 for more details.
  24. const REGION_WHITELIST = new Set([
  25. "AE", "AF", "AL", "AM", "AR", "AT", "AU", "AZ", "BA", "BD", "BE", "BF",
  26. "BG", "BJ", "BO", "BR", "BY", "CA", "CH", "CI", "CL", "CM", "CN", "CO",
  27. "CR", "CU", "CY", "CZ", "DE", "DK", "DO", "DZ", "EC", "EE", "EG", "ES",
  28. "ET", "FI", "FR", "GB", "GE", "GH", "GP", "GR", "GT", "HK", "HN", "HR",
  29. "HU", "ID", "IE", "IL", "IN", "IQ", "IR", "IS", "IT", "JM", "JO", "JP",
  30. "KE", "KH", "KR", "KW", "KZ", "LB", "LK", "LT", "LU", "LV", "LY", "MA",
  31. "MD", "ME", "MG", "MK", "ML", "MM", "MN", "MQ", "MT", "MU", "MX", "MY",
  32. "MZ", "NC", "NG", "NI", "NL", "NO", "NP", "NZ", "OM", "PA", "PE", "PH",
  33. "PK", "PL", "PR", "PS", "PT", "PY", "QA", "RE", "RO", "RS", "RU", "RW",
  34. "SA", "SD", "SE", "SG", "SI", "SK", "SN", "SV", "SY", "TG", "TH", "TN",
  35. "TR", "TT", "TW", "TZ", "UA", "UG", "US", "UY", "UZ", "VE", "VN", "ZA",
  36. "ZM", "ZW",
  37. ]);
  38. /**
  39. * Observe various notifications and send them to a telemetry endpoint.
  40. *
  41. * @param {Object} options
  42. * @param {string} options.topic - a unique ID for users of PingCentre to distinguish
  43. * their data on the server side.
  44. * @param {string} options.overrideEndpointPref - optional pref for URL where the POST is sent.
  45. */
  46. class PingCentre {
  47. constructor(options) {
  48. if (!options.topic) {
  49. throw new Error("Must specify topic.");
  50. }
  51. this._topic = options.topic;
  52. this._prefs = Services.prefs.getBranch("");
  53. this._setPingEndpoint(options.topic, options.overrideEndpointPref);
  54. this._enabled = this._prefs.getBoolPref(TELEMETRY_PREF);
  55. this._onTelemetryPrefChange = this._onTelemetryPrefChange.bind(this);
  56. this._prefs.addObserver(TELEMETRY_PREF, this._onTelemetryPrefChange);
  57. this._fhrEnabled = this._prefs.getBoolPref(FHR_UPLOAD_ENABLED_PREF);
  58. this._onFhrPrefChange = this._onFhrPrefChange.bind(this);
  59. 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. }
  64. /**
  65. * Lazily get the Telemetry id promise
  66. */
  67. get telemetryClientId() {
  68. Object.defineProperty(this, "telemetryClientId", {value: ClientID.getClientID()});
  69. return this.telemetryClientId;
  70. }
  71. get enabled() {
  72. return this._enabled && this._fhrEnabled;
  73. }
  74. _setPingEndpoint(topic, overrideEndpointPref) {
  75. const overrideValue = overrideEndpointPref &&
  76. this._prefs.getStringPref(overrideEndpointPref);
  77. if (overrideValue) {
  78. this._pingEndpoint = overrideValue;
  79. } else {
  80. this._pingEndpoint = this._prefs.getStringPref(PRODUCTION_ENDPOINT_PREF);
  81. }
  82. }
  83. _onLoggingPrefChange(aSubject, aTopic, prefKey) {
  84. this.logging = this._prefs.getBoolPref(prefKey);
  85. }
  86. _onTelemetryPrefChange(aSubject, aTopic, prefKey) {
  87. this._enabled = this._prefs.getBoolPref(prefKey);
  88. }
  89. _onFhrPrefChange(aSubject, aTopic, prefKey) {
  90. this._fhrEnabled = this._prefs.getBoolPref(prefKey);
  91. }
  92. _createExperimentsString(activeExperiments, filter) {
  93. let experimentsString = "";
  94. for (let experimentID in activeExperiments) {
  95. if (!activeExperiments[experimentID] ||
  96. !activeExperiments[experimentID].branch ||
  97. (filter && !experimentID.includes(filter))) {
  98. continue;
  99. }
  100. let expString = `${experimentID}:${activeExperiments[experimentID].branch}`;
  101. experimentsString = experimentsString.concat(`${expString};`);
  102. }
  103. return experimentsString;
  104. }
  105. _getRegion() {
  106. let region = "UNSET";
  107. if (Services.prefs.prefHasUserValue(BROWSER_SEARCH_REGION_PREF)) {
  108. region = Services.prefs.getStringPref(BROWSER_SEARCH_REGION_PREF);
  109. if (region === "") {
  110. region = "EMPTY";
  111. } else if (!REGION_WHITELIST.has(region)) {
  112. region = "OTHER";
  113. }
  114. }
  115. return region;
  116. }
  117. async _createPing(data, options) {
  118. let filter = options && options.filter;
  119. let experiments = TelemetryEnvironment.getActiveExperiments();
  120. let experimentsString = this._createExperimentsString(experiments, filter);
  121. let clientID = data.client_id || await this.telemetryClientId;
  122. let locale = data.locale || Services.locale.appLocaleAsLangTag;
  123. let profileCreationDate = TelemetryEnvironment.currentEnvironment.profile.resetDate ||
  124. TelemetryEnvironment.currentEnvironment.profile.creationDate;
  125. const payload = Object.assign({
  126. locale,
  127. topic: this._topic,
  128. client_id: clientID,
  129. version: AppConstants.MOZ_APP_VERSION,
  130. release_channel: UpdateUtils.getUpdateChannel(false),
  131. }, data);
  132. if (experimentsString) {
  133. payload.shield_id = experimentsString;
  134. }
  135. if (profileCreationDate) {
  136. payload.profile_creation_date = profileCreationDate;
  137. }
  138. payload.region = this._getRegion();
  139. return payload;
  140. }
  141. async _createStructuredIngestionPing(data, options) {
  142. let filter = options && options.filter;
  143. let experiments = TelemetryEnvironment.getActiveExperiments();
  144. let experimentsString = this._createExperimentsString(experiments, filter);
  145. let clientID = data.client_id || await this.telemetryClientId;
  146. let locale = data.locale || Services.locale.appLocaleAsLangTag;
  147. const payload = Object.assign({
  148. locale,
  149. client_id: clientID,
  150. version: AppConstants.MOZ_APP_VERSION,
  151. release_channel: UpdateUtils.getUpdateChannel(false),
  152. }, data);
  153. if (experimentsString) {
  154. payload.shield_id = experimentsString;
  155. }
  156. return payload;
  157. }
  158. async sendPing(data, options) {
  159. if (!this.enabled) {
  160. return Promise.resolve();
  161. }
  162. const payload = await this._createPing(data, options);
  163. if (this.logging) {
  164. // performance related pings cause a lot of logging, so we mute them
  165. if (data.action !== "activity_stream_performance") {
  166. Services.console.logStringMessage(`TELEMETRY PING: ${JSON.stringify(payload)}\n`);
  167. }
  168. }
  169. return fetch(this._pingEndpoint, {
  170. method: "POST",
  171. body: JSON.stringify(payload),
  172. credentials: "omit",
  173. }).then(response => {
  174. if (!response.ok) {
  175. Cu.reportError(`Ping failure with HTTP response code: ${response.status}`);
  176. }
  177. }).catch(e => {
  178. Cu.reportError(`Ping failure with error: ${e}`);
  179. });
  180. }
  181. /**
  182. * Sends a ping to the Structured Ingestion telemetry pipeline.
  183. *
  184. * @param {Object} data The payload to be sent.
  185. * @param {String} endpoint The destination endpoint. Note that Structured Ingestion
  186. * requires a different endpoint for each ping. It's up to the
  187. * caller to provide that. See more details at
  188. * https://github.com/mozilla/gcp-ingestion/blob/master/docs/edge.md#postput-request
  189. * @param {Object} options Other options for this ping.
  190. */
  191. async sendStructuredIngestionPing(data, endpoint, options) {
  192. if (!this.enabled) {
  193. return Promise.resolve();
  194. }
  195. const payload = await this._createStructuredIngestionPing(data, options);
  196. if (this.logging) {
  197. Services.console.logStringMessage(`TELEMETRY PING (STRUCTURED INGESTION): ${JSON.stringify(payload)}\n`);
  198. }
  199. return fetch(endpoint, {
  200. method: "POST",
  201. body: JSON.stringify(payload),
  202. credentials: "omit",
  203. }).then(response => {
  204. if (!response.ok) {
  205. Cu.reportError(`Structured Ingestion ping failure with HTTP response code: ${response.status}`);
  206. }
  207. }).catch(e => {
  208. Cu.reportError(`Structured Ingestion ping failure with error: ${e}`);
  209. });
  210. }
  211. uninit() {
  212. try {
  213. this._prefs.removeObserver(TELEMETRY_PREF, this._onTelemetryPrefChange);
  214. this._prefs.removeObserver(LOGGING_PREF, this._onLoggingPrefChange);
  215. this._prefs.removeObserver(FHR_UPLOAD_ENABLED_PREF, this._onFhrPrefChange);
  216. } catch (e) {
  217. Cu.reportError(e);
  218. }
  219. }
  220. }
  221. this.PingCentre = PingCentre;
  222. this.PingCentreConstants = {
  223. PRODUCTION_ENDPOINT_PREF,
  224. FHR_UPLOAD_ENABLED_PREF,
  225. TELEMETRY_PREF,
  226. LOGGING_PREF,
  227. };
  228. const EXPORTED_SYMBOLS = ["PingCentre", "PingCentreConstants"];