VulpesOS/gaia/shared/js/telemetry.js
OverSu b38dea0597 Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
2026-09-30 23:13:03 +02:00

217 lines
7.1 KiB
JavaScript
Executable file

'use strict';
// Utilities for sending data to the FxOS Telemetry Server
/* global asyncStorage, crypto, TextEncoder */
(function(exports) {
const DEFAULT_URL = 'https://fxos.telemetry.mozilla.org/submit/telemetry';
const DEFAULT_APPNAME = 'FirefoxOS';
const UNKNOWN = 'unknown';
const DOGFOOD_KEY = 'debug.performance_data.dogfooding';
const TR = TelemetryRequest;
// Set to true to enable debugging
TR.DEBUG = false;
// Create an XHR for the FxOS Telemetry Server.
// Arguments:
// args: Telemetry arguments
// - reason: 'reason' URL field (i.e 'ftu' or 'appusage'. required)
// - deviceID: unique ID tied to the device or request (required)
// - url: base URL for the telemetry server
// (default: https://fxos.telemetry.mozilla.org/submit/telemetry)
// - ver: Payload version (default: 1)
// - appName: appName URL field (default: FirefoxOS)
// - appUpdateChannel: update channel URL field (default: 'unknown')
// - appBuildID: buildID URL field (default 'unknown')
// - appVersion: app version URL field (default 'unknown')
// info: Arbitrary JSON serializable data that will be recorded by Telemetry
function TelemetryRequest(args, info) {
if (!args) {
throw new Error('No arguments');
}
if (!args.reason) {
throw new Error('No reason given');
}
if (!args.deviceID) {
throw new Error('No deviceID given');
}
// clone so we don't put data into the object that was given to us
this.info = info ? JSON.parse(JSON.stringify(info)) : {};
this.packet = {
ver: args.ver || 1,
info: this.info
};
this.info.reason = args.reason;
this.info.appName = args.appName || DEFAULT_APPNAME;
this.info.appUpdateChannel = args.appUpdateChannel || UNKNOWN;
this.info.appBuildID = args.appBuildID || UNKNOWN;
this.info.appVersion = args.appVersion || UNKNOWN;
var uriParts = [
args.url || DEFAULT_URL,
encodeURIComponent(args.deviceID),
encodeURIComponent(this.info.reason),
encodeURIComponent(this.info.appName),
encodeURIComponent(this.info.appVersion),
encodeURIComponent(this.info.appUpdateChannel),
encodeURIComponent(this.info.appBuildID)
];
this.url = uriParts.join('/');
}
function debug(...args) {
if (TR.DEBUG) {
args.unshift('[Telemetry]');
console.log.apply(console, args);
}
}
// Open and send the underlying XMLHttpRequest
// xhrAttrs: attributes to set on the XHR
// - timeout: The timeout for the request
// - onload / onerror / onabort / ontimeout: callbacks
TelemetryRequest.prototype.send = function(xhrAttrs) {
var xhr = new XMLHttpRequest({ mozSystem: true, mozAnon: true });
xhr.open('POST', this.url);
debug(this.url);
if (xhrAttrs && xhrAttrs.timeout) {
xhr.timeout = xhrAttrs.timeout;
}
xhr.setRequestHeader('Content-type', 'application/json');
xhr.responseType = 'text';
var data = JSON.stringify(this.packet);
xhr.send(data);
debug(data);
if (xhrAttrs) {
xhr.onload = xhrAttrs.onload;
xhr.onerror = xhrAttrs.onerror;
xhr.onabort = xhrAttrs.onabort;
xhr.ontimeout = xhrAttrs.ontimeout;
}
return xhr;
};
TelemetryRequest.getDeviceID = function(key) {
return new Promise(function(resolve, reject) {
if (!navigator.mozSettings) {
reject('Invalid mozSettings');
return;
}
var dogFood = navigator.mozSettings.createLock().get(DOGFOOD_KEY);
if (!dogFood) {
reject('Couldn\'t query ' + DOGFOOD_KEY);
return;
}
dogFood.onsuccess = function onSuccess() {
var isDogfooder = dogFood.result[DOGFOOD_KEY];
if (!isDogfooder && typeof key !== 'undefined') {
//return the stored uuid from the passed in key here
asyncStorage.getItem(key, function(value) {
if (value) {
resolve(value);
} else {
reject('No deviceID found');
}
});
} else {
var dialPromise = navigator.mozTelephony.dial('*#06#', 0);
dialPromise.then(function(call) {
return call.result;
}).then(function(result) {
// Check to make sure our telephony stack is initialized and
// has returned an imei.
if (result && result.success && (result.serviceCode === 'scImei')) {
return result.statusMessage;
} else {
reject('Unable to get IMEI');
}
}).then(function (imei) {
if (isDogfooder) {
resolve(imei);
} else {
return TelemetryRequest.createHash(imei);
}
}).then(function (hashed) {
resolve(hashed);
}).catch(reject);
}
};
});
};
TelemetryRequest.createHash = function createHash(id) {
return new Promise(function (resolve, reject) {
var salt = new Uint8Array([44, 27, 51, 17, 55, 64, 183, 55, 37, 31, 246,
196, 130, 73, 14, 95, 122, 241, 143, 18, 40, 109,
238, 59, 168, 82, 28, 199, 177, 37, 135, 98]);
var hexDigits = '0123456789ABCDEF';
var ftuID = '';
var macString = navigator.mozWifiManager.macAddress.replace(/:/g, '');
var bytes = new TextEncoder('utf-8').encode(id + macString);
crypto.subtle.importKey('raw', bytes, {name:'PBKDF2'},
false, ['deriveBits']).then(function (key) {
return crypto.subtle.deriveBits({
name: 'PBKDF2',
iterations: 1000,
salt: salt,
hash: 'SHA-1'
}, key, 160);
}).then(function (digest) {
var digestArray = new Uint8Array(digest).slice(0, 4);
digestArray.forEach(function(value) {
ftuID += (hexDigits[value >> 4] + hexDigits[value & 15]);
});
resolve(ftuID);
});
});
};
/*
* A utility function shared between telemetry modules that is used to get
* values for all of the specified settings. settingKeysAndDefaults is an
* object that maps settings keys to default values. We query the value of
* each of those settings and then create an object that maps keys to values
* (or to the default values) and pass that object to the callback function.
*/
TelemetryRequest.getSettings = function getSettings(settingKeysAndDefaults,
callback) {
var pendingQueries = 0;
var results = {};
var lock = window.navigator.mozSettings.createLock();
for (var key in settingKeysAndDefaults) {
var defaultValue = settingKeysAndDefaults[key];
query(key, defaultValue);
pendingQueries++;
}
function query(key, defaultValue) {
var request = lock.get(key);
request.onsuccess = function() {
var value = request.result[key];
if (value === undefined || value === null) {
value = defaultValue;
}
results[key] = value;
pendingQueries--;
if (pendingQueries === 0) {
callback(results);
}
};
}
};
exports.TelemetryRequest = TelemetryRequest;
}(window));