summaryrefslogtreecommitdiffstats
path: root/toolkit/components/utils/ClientEnvironment.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/utils/ClientEnvironment.sys.mjs')
-rw-r--r--toolkit/components/utils/ClientEnvironment.sys.mjs264
1 files changed, 264 insertions, 0 deletions
diff --git a/toolkit/components/utils/ClientEnvironment.sys.mjs b/toolkit/components/utils/ClientEnvironment.sys.mjs
new file mode 100644
index 0000000000..7f1b4cb970
--- /dev/null
+++ b/toolkit/components/utils/ClientEnvironment.sys.mjs
@@ -0,0 +1,264 @@
+/* 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/. */
+
+import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
+ AttributionCode: "resource:///modules/AttributionCode.sys.mjs",
+ NormandyUtils: "resource://normandy/lib/NormandyUtils.sys.mjs",
+ ShellService: "resource:///modules/ShellService.sys.mjs",
+ TelemetryArchive: "resource://gre/modules/TelemetryArchive.sys.mjs",
+ TelemetryController: "resource://gre/modules/TelemetryController.sys.mjs",
+ UpdateUtils: "resource://gre/modules/UpdateUtils.sys.mjs",
+ WindowsVersionInfo:
+ "resource://gre/modules/components-utils/WindowsVersionInfo.sys.mjs",
+});
+
+/**
+ * Create an object that provides general information about the client application.
+ *
+ * Components like Normandy RecipeRunner use this as part of the context for filter expressions,
+ * so avoid adding non-getter functions as attributes, as filter expressions
+ * cannot execute functions.
+ *
+ * Also note that, because filter expressions implicitly resolve promises, you
+ * can add getter functions that return promises for async data.
+ */
+export class ClientEnvironmentBase {
+ static get distribution() {
+ return Services.prefs
+ .getDefaultBranch(null)
+ .getCharPref("distribution.id", "default");
+ }
+
+ static get telemetry() {
+ return (async () => {
+ const pings = await lazy.TelemetryArchive.promiseArchivedPingList();
+
+ // get most recent ping per type
+ const mostRecentPings = {};
+ for (const ping of pings) {
+ if (ping.type in mostRecentPings) {
+ if (
+ mostRecentPings[ping.type].timestampCreated < ping.timestampCreated
+ ) {
+ mostRecentPings[ping.type] = ping;
+ }
+ } else {
+ mostRecentPings[ping.type] = ping;
+ }
+ }
+
+ const telemetry = {};
+ for (const key in mostRecentPings) {
+ const ping = mostRecentPings[key];
+ telemetry[ping.type] =
+ await lazy.TelemetryArchive.promiseArchivedPingById(ping.id);
+ }
+ return telemetry;
+ })();
+ }
+
+ static get liveTelemetry() {
+ // Construct a proxy object that forwards access to the main ping, and
+ // throws errors for other ping types. The intent is to allow using
+ // `telemetry` and `liveTelemetry` in similar ways, but to fail fast if
+ // the wrong telemetry types are accessed.
+ let target = {};
+ try {
+ target.main = lazy.TelemetryController.getCurrentPingData();
+ } catch (err) {
+ console.error(err);
+ }
+
+ return new Proxy(target, {
+ get(target, prop, receiver) {
+ if (prop == "main") {
+ return target.main;
+ }
+ if (prop == "then") {
+ // this isn't a Promise, but it's not a problem to check
+ return undefined;
+ }
+ throw new Error(
+ `Live telemetry only includes the main ping, not the ${prop} ping`
+ );
+ },
+ has(target, prop) {
+ return prop == "main";
+ },
+ });
+ }
+
+ // Note that we intend to replace usages of this with client_id in https://bugzilla.mozilla.org/show_bug.cgi?id=1542955
+ static get randomizationId() {
+ let id = Services.prefs.getCharPref("app.normandy.user_id", "");
+ if (!id) {
+ id = lazy.NormandyUtils.generateUuid();
+ Services.prefs.setCharPref("app.normandy.user_id", id);
+ }
+ return id;
+ }
+
+ static get version() {
+ return AppConstants.MOZ_APP_VERSION_DISPLAY;
+ }
+
+ static get channel() {
+ return lazy.UpdateUtils.getUpdateChannel(false);
+ }
+
+ static get isDefaultBrowser() {
+ return lazy.ShellService.isDefaultBrowser();
+ }
+
+ static get searchEngine() {
+ return (async () => {
+ const defaultEngineInfo = await Services.search.getDefault();
+ return defaultEngineInfo.telemetryId;
+ })();
+ }
+
+ static get syncSetup() {
+ return Services.prefs.prefHasUserValue("services.sync.username");
+ }
+
+ static get syncDesktopDevices() {
+ return Services.prefs.getIntPref(
+ "services.sync.clients.devices.desktop",
+ 0
+ );
+ }
+
+ static get syncMobileDevices() {
+ return Services.prefs.getIntPref("services.sync.clients.devices.mobile", 0);
+ }
+
+ static get syncTotalDevices() {
+ return this.syncDesktopDevices + this.syncMobileDevices;
+ }
+
+ static get addons() {
+ return (async () => {
+ const addons = await lazy.AddonManager.getAllAddons();
+ return addons.reduce((acc, addon) => {
+ const {
+ id,
+ isActive,
+ name,
+ type,
+ version,
+ installDate: installDateN,
+ } = addon;
+ const installDate = new Date(installDateN);
+ acc[id] = { id, isActive, name, type, version, installDate };
+ return acc;
+ }, {});
+ })();
+ }
+
+ static get plugins() {
+ return (async () => {
+ const plugins = await lazy.AddonManager.getAddonsByTypes(["plugin"]);
+ return plugins.reduce((acc, plugin) => {
+ const { name, description, version } = plugin;
+ acc[name] = { name, description, version };
+ return acc;
+ }, {});
+ })();
+ }
+
+ static get locale() {
+ return Services.locale.appLocaleAsBCP47;
+ }
+
+ static get doNotTrack() {
+ return Services.prefs.getBoolPref(
+ "privacy.donottrackheader.enabled",
+ false
+ );
+ }
+
+ static get os() {
+ function coerceToNumber(version) {
+ const parts = version.split(".");
+ return parseFloat(parts.slice(0, 2).join("."));
+ }
+
+ function getOsVersion() {
+ let version = null;
+ try {
+ version = Services.sysinfo.getProperty("version", null);
+ } catch (_e) {
+ // getProperty can throw if the version does not exist
+ }
+ if (version) {
+ version = coerceToNumber(version);
+ }
+ return version;
+ }
+
+ let osInfo = {
+ isWindows: AppConstants.platform == "win",
+ isMac: AppConstants.platform === "macosx",
+ isLinux: AppConstants.platform === "linux",
+
+ get windowsVersion() {
+ if (!osInfo.isWindows) {
+ return null;
+ }
+ return getOsVersion();
+ },
+
+ /**
+ * Gets the windows build number by querying the OS directly. The initial
+ * version was copied from toolkit/components/telemetry/app/TelemetryEnvironment.jsm
+ * @returns {number | null} The build number, or null on non-Windows platform or if there is an error.
+ */
+ get windowsBuildNumber() {
+ if (!osInfo.isWindows) {
+ return null;
+ }
+
+ return lazy.WindowsVersionInfo.get({ throwOnError: false }).buildNumber;
+ },
+
+ get macVersion() {
+ const darwinVersion = osInfo.darwinVersion;
+ // Versions of OSX with Darwin < 5 don't follow this pattern
+ if (darwinVersion >= 5) {
+ // OSX 10.1 used Darwin 5, OSX 10.2 used Darwin 6, and so on.
+ const intPart = Math.floor(darwinVersion);
+ return 10 + 0.1 * (intPart - 4);
+ }
+ return null;
+ },
+
+ get darwinVersion() {
+ if (!osInfo.isMac) {
+ return null;
+ }
+ return getOsVersion();
+ },
+
+ // Version information on linux is a lot harder and a lot less useful, so
+ // don't do anything about it here.
+ };
+
+ return osInfo;
+ }
+
+ static get attribution() {
+ return lazy.AttributionCode.getAttrDataAsync();
+ }
+
+ static get appinfo() {
+ Services.appinfo.QueryInterface(Ci.nsIXULAppInfo);
+ Services.appinfo.QueryInterface(Ci.nsIPlatformInfo);
+ return Services.appinfo;
+ }
+}