Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
217 lines
7.1 KiB
JavaScript
217 lines
7.1 KiB
JavaScript
'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));
|