diff options
Diffstat (limited to 'toolkit/components/utils/ClientEnvironment.sys.mjs')
-rw-r--r-- | toolkit/components/utils/ClientEnvironment.sys.mjs | 264 |
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; + } +} |