Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
445 lines
14 KiB
JavaScript
445 lines
14 KiB
JavaScript
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
|
|
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
|
|
|
|
'use strict';
|
|
|
|
/* global MobileOperator, SIMSlotManager, dump, TelemetryRequest,
|
|
LazyLoader */
|
|
|
|
/**
|
|
* A simple ping that is kicked off on first time use
|
|
*/
|
|
(function(exports) {
|
|
var DEBUG = false;
|
|
|
|
const FTU_PING_ACTIVATION = 'ftu.pingActivation';
|
|
const FTU_PING_ENABLED = 'ftu.pingEnabled';
|
|
const FTU_PING_MAX_NETWORK_FAILS = 'ftu.pingMaxNetworkFails';
|
|
const FTU_PING_NETWORK_FAIL_COUNT = 'ftu.pingNetworkFailCount';
|
|
const FTU_PING_URL = 'ftu.pingURL';
|
|
const FTU_PING_TIMEOUT = 'ftu.pingTimeout';
|
|
const FTU_PING_TRY_INTERVAL = 'ftu.pingTryInterval';
|
|
|
|
const DEFAULT_TRY_INTERVAL = 60 * 60 * 1000;
|
|
const DEFAULT_MAX_NETWORK_FAILS = 24;
|
|
const DEFAULT_PING_TIMEOUT = 60 * 1000;
|
|
|
|
// Used by the telemetry server to help identify the payload format
|
|
const TELEMETRY_VERSION = 1;
|
|
const TELEMETRY_REASON = 'ftu';
|
|
|
|
// Settings to observe value changes for while the ping has not been sent
|
|
const OBSERVE_SETTINGS = ['deviceinfo.os',
|
|
'deviceinfo.software',
|
|
'deviceinfo.product_model',
|
|
'deviceinfo.firmware_revision',
|
|
'deviceinfo.hardware'];
|
|
|
|
const URL_SETTINGS = ['deviceinfo.platform_build_id',
|
|
'deviceinfo.platform_version',
|
|
'app.update.channel'];
|
|
|
|
function FtuPing() {
|
|
}
|
|
|
|
FtuPing.prototype = {
|
|
// whether or not required properties have been loaded
|
|
_pingReady: false,
|
|
_pingReadyCallback: null,
|
|
|
|
// interval timer for first time ping
|
|
_pingTimer: null,
|
|
|
|
// time between ping tries
|
|
_tryInterval: DEFAULT_TRY_INTERVAL,
|
|
|
|
// URL for first time ping
|
|
_pingURL: null,
|
|
|
|
// Whether or not FTU ping is enabled
|
|
_pingEnabled: true,
|
|
|
|
// The number of times to wait for SIM / voice network data before sending
|
|
// the ping anyway
|
|
_maxNetworkFails: DEFAULT_MAX_NETWORK_FAILS,
|
|
|
|
// Number of network failures accrued
|
|
_networkFailCount: 0,
|
|
|
|
// Data used in ping
|
|
_pingData: {},
|
|
|
|
// Data used in info
|
|
_infoData: {},
|
|
|
|
// Timeout for ping requests
|
|
_pingTimeout: DEFAULT_PING_TIMEOUT,
|
|
_settingObserver: null,
|
|
|
|
debug: function fp_debug(msg) {
|
|
if (DEBUG) {
|
|
dump('[FtuPing] ' + msg + '\n');
|
|
}
|
|
},
|
|
|
|
reset: function fp_reset() {
|
|
this._pingReady = false;
|
|
this._pingReadyCallback = null;
|
|
this._pingTimer = null;
|
|
this._tryInterval = DEFAULT_TRY_INTERVAL;
|
|
this._pingTimeout = DEFAULT_PING_TIMEOUT;
|
|
this._settingObserver = null;
|
|
|
|
this._pingURL = null;
|
|
this._pingEnabled = true;
|
|
|
|
this._maxNetworkFails = DEFAULT_MAX_NETWORK_FAILS;
|
|
this._networkFailCount = 0;
|
|
|
|
this._pingData = {};
|
|
},
|
|
|
|
initSettings: function fp_initSettings() {
|
|
var self = this;
|
|
function getAsyncItems() {
|
|
return new Promise(function(resolve, reject) {
|
|
self.getAsyncStorageItems([FTU_PING_ACTIVATION,
|
|
FTU_PING_ENABLED,
|
|
FTU_PING_NETWORK_FAIL_COUNT],
|
|
function(items) {
|
|
|
|
self._pingData.activationTime = items[FTU_PING_ACTIVATION];
|
|
self._pingEnabled = items[FTU_PING_ENABLED];
|
|
|
|
if (!self._pingData.activationTime) {
|
|
self._pingData.activationTime = Date.now();
|
|
window.asyncStorage.setItem(FTU_PING_ACTIVATION,
|
|
self._pingData.activationTime);
|
|
}
|
|
|
|
if (self._pingEnabled === null) {
|
|
self._pingEnabled = true;
|
|
}
|
|
|
|
if (typeof(items[FTU_PING_NETWORK_FAIL_COUNT]) === 'number') {
|
|
self._networkFailCount = items[FTU_PING_NETWORK_FAIL_COUNT];
|
|
}
|
|
resolve();
|
|
});
|
|
});
|
|
}
|
|
|
|
function getSettings() {
|
|
return new Promise(function(resolve, reject) {
|
|
var allSettings = [FTU_PING_URL, FTU_PING_TRY_INTERVAL,
|
|
FTU_PING_TIMEOUT, FTU_PING_MAX_NETWORK_FAILS].
|
|
concat(OBSERVE_SETTINGS);
|
|
allSettings = allSettings.concat(URL_SETTINGS);
|
|
|
|
self.getSettings(allSettings, function(settings) {
|
|
self._pingURL = settings[FTU_PING_URL];
|
|
self._tryInterval = settings[FTU_PING_TRY_INTERVAL] ||
|
|
self._tryInterval;
|
|
self._pingTimeout = settings[FTU_PING_TIMEOUT] || self._pingTimeout;
|
|
self._maxNetworkFails = settings[FTU_PING_MAX_NETWORK_FAILS] ||
|
|
self._maxNetworkFails;
|
|
|
|
var mozSettings = window.navigator.mozSettings;
|
|
if (!self._settingObserver) {
|
|
self._settingObserver = self.onSettingChanged.bind(self);
|
|
}
|
|
|
|
OBSERVE_SETTINGS.forEach(function(observeSetting) {
|
|
self._pingData[observeSetting] = settings[observeSetting];
|
|
mozSettings.addObserver(observeSetting, self._settingObserver);
|
|
});
|
|
|
|
URL_SETTINGS.forEach(function(urlSetting) {
|
|
self._infoData[urlSetting] = settings[urlSetting];
|
|
mozSettings.addObserver(urlSetting, self._settingObserver);
|
|
});
|
|
resolve();
|
|
});
|
|
});
|
|
}
|
|
|
|
self.reset();
|
|
|
|
self._pingData.screenHeight = window.screen.height;
|
|
self._pingData.screenWidth = window.screen.width;
|
|
self._pingData.devicePixelRatio = window.devicePixelRatio;
|
|
|
|
return Promise.all([getAsyncItems(), getSettings()]);
|
|
},
|
|
|
|
initPreinstalledApps: function fp_initPreinstalledApps(callback) {
|
|
var preinstalled = this._pingData.preinstalled = {};
|
|
var self = this;
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
window.navigator.mozApps.mgmt.getAll().onsuccess = function(evt) {
|
|
var apps = evt.target.result;
|
|
|
|
apps.forEach(function(app) {
|
|
var url;
|
|
try {
|
|
url = new URL(app.manifestURL);
|
|
} catch (e) {
|
|
// Don't die if somehow the manifestURL is invalid
|
|
}
|
|
|
|
// Only report non-gaia apps
|
|
if (!url || url.hostname.indexOf('gaiamobile.org') === -1) {
|
|
preinstalled[app.manifestURL] = app.manifest.name;
|
|
}
|
|
});
|
|
|
|
self.debug(self._pingData.preinstalled);
|
|
resolve();
|
|
};
|
|
});
|
|
},
|
|
|
|
getAsyncStorageItems: function fp_getAsyncStorageItems(itemKeys, callback) {
|
|
var itemsLeft = itemKeys.length;
|
|
var items = {};
|
|
itemKeys.forEach(function(key) {
|
|
window.asyncStorage.getItem(key, function(value) {
|
|
itemsLeft--;
|
|
items[key] = value;
|
|
if (itemsLeft === 0 && callback) {
|
|
callback(items);
|
|
}
|
|
});
|
|
});
|
|
},
|
|
|
|
getSettings: function fp_getSettings(settingKeys, callback) {
|
|
var settingsLeft = settingKeys.length;
|
|
var settings = {};
|
|
var lock = window.navigator.mozSettings.createLock();
|
|
settingKeys.forEach(function(key) {
|
|
var request = lock.get(key);
|
|
request.onsuccess = function(evt) {
|
|
var value = request.result[key];
|
|
settingsLeft--;
|
|
settings[key] = value;
|
|
if (settingsLeft === 0 && callback) {
|
|
callback(settings);
|
|
}
|
|
};
|
|
});
|
|
},
|
|
|
|
ensurePing: function fp_ensurePing() {
|
|
var self = this;
|
|
LazyLoader.load('shared/js/telemetry.js').then(function() {
|
|
return Promise.all([self.initSettings(), self.initPreinstalledApps()])
|
|
.then(self.startPing.bind(self));
|
|
});
|
|
},
|
|
|
|
onSettingChanged: function fp_onSettingChanged(evt) {
|
|
this._pingData[evt.settingName] = evt.settingValue;
|
|
},
|
|
|
|
startPing: function fp_startPing() {
|
|
if (this._pingEnabled === false) {
|
|
this.debug('FTU ping disabled');
|
|
return;
|
|
}
|
|
|
|
if (!this._pingURL) {
|
|
this.debug('No FTU ping URL');
|
|
return;
|
|
}
|
|
if (this._pingTimer !== null) {
|
|
return;
|
|
}
|
|
|
|
this.debug('Starting FTU ping');
|
|
this._pingTimer = setInterval(this.tryPing.bind(this), this._tryInterval);
|
|
},
|
|
|
|
maybeThrowNetworkFailure: function fp_maybeThrowNetworkFailure(message) {
|
|
this._networkFailCount++;
|
|
window.asyncStorage.setItem(FTU_PING_NETWORK_FAIL_COUNT,
|
|
this._networkFailCount);
|
|
|
|
if (this._networkFailCount < this._maxNetworkFails) {
|
|
throw message + ' (' + this._networkFailCount + ' of ' +
|
|
this._maxNetworkFails + ' failures)';
|
|
} else {
|
|
this.debug('Warning: ' + message);
|
|
}
|
|
},
|
|
|
|
checkMobileNetwork: function fp_checkMobileNetwork() {
|
|
// Wait until we have a valid network connection
|
|
if (SIMSlotManager.noSIMCardConnectedToNetwork()) {
|
|
this.maybeThrowNetworkFailure('No SIM cards connected to a network');
|
|
}
|
|
|
|
var conns = window.navigator.mozMobileConnections;
|
|
if (!conns || conns.length === 0) {
|
|
this.maybeThrowNetworkFailure('No mobile connections');
|
|
return;
|
|
}
|
|
|
|
var slots = SIMSlotManager.getSlots().filter(function(slot) {
|
|
return !slot.isAbsent() && !slot.isLocked();
|
|
});
|
|
|
|
if (slots.length === 0) {
|
|
this.maybeThrowNetworkFailure('No unlocked or active SIM cards found');
|
|
return;
|
|
}
|
|
|
|
var conn = slots[0].conn;
|
|
var iccObj = navigator.mozIccManager.getIccById(conn.iccId);
|
|
var iccInfo = iccObj ? iccObj.iccInfo : null;
|
|
var voiceNetwork = conn.voice ? conn.voice.network : null;
|
|
|
|
if (!iccInfo && !voiceNetwork) {
|
|
this.maybeThrowNetworkFailure('No voice network or ICC info');
|
|
return;
|
|
}
|
|
|
|
this._pingData.network = MobileOperator.userFacingInfo(conn);
|
|
if (voiceNetwork) {
|
|
this._pingData.network.mnc = voiceNetwork.mnc;
|
|
this._pingData.network.mcc = voiceNetwork.mcc;
|
|
}
|
|
|
|
if (iccInfo) {
|
|
this._pingData.icc = {
|
|
mnc: iccInfo.mnc,
|
|
mcc: iccInfo.mcc,
|
|
spn: iccInfo.spn
|
|
};
|
|
}
|
|
},
|
|
|
|
tryPing: function fp_tryPing() {
|
|
var self = this;
|
|
try {
|
|
this.checkMobileNetwork();
|
|
if (this._networkFailCount >= this._maxNetworkFails) {
|
|
this.debug('Max voice network failures reached, pinging anyway!');
|
|
if (this._pingData.network === undefined) {
|
|
this._pingData.network = null;
|
|
}
|
|
|
|
if (this._pingData.icc === undefined) {
|
|
this._pingData.icc = null;
|
|
}
|
|
}
|
|
|
|
if (!this._pingData['deviceinfo.os']) {
|
|
this.debug('No OS information, holding off');
|
|
return false;
|
|
}
|
|
var promise = TelemetryRequest.getDeviceID();
|
|
promise.then(function(deviceID) {
|
|
self._pingData.pingID = deviceID;
|
|
self.ping();
|
|
}).catch(function(error) {
|
|
self.debug('Generating deviceID: ' + error);
|
|
});
|
|
} catch (e) {
|
|
this.debug('Error while trying FTU ping: ' + e);
|
|
return false;
|
|
}
|
|
},
|
|
|
|
ping: function fp_ping() {
|
|
var pingData = this.assemblePingData();
|
|
this._pingData.pingTime = Date.now();
|
|
|
|
var request = new TelemetryRequest({
|
|
reason: TELEMETRY_REASON,
|
|
deviceID: pingData.pingID,
|
|
ver: TELEMETRY_VERSION,
|
|
url: this._pingURL,
|
|
appUpdateChannel: this._infoData['app.update.channel'],
|
|
appVersion: this._infoData['deviceinfo.platform_version'],
|
|
appBuildID: this._infoData['deviceinfo.platform_build_id']
|
|
}, pingData);
|
|
|
|
var self = this;
|
|
request.send({
|
|
timeout: this._pingTimeout,
|
|
onload: function() {
|
|
self.pingSuccess(this.responseText);
|
|
},
|
|
ontimeout: function() {
|
|
self.pingError('Timed out');
|
|
},
|
|
onerror: function() {
|
|
self.pingError(this.statusText);
|
|
}
|
|
});
|
|
},
|
|
|
|
pingSuccess: function fp_pingSuccess(result) {
|
|
if (result !== 'OK') {
|
|
this.debug('Ping response unexpected: ' + result);
|
|
return;
|
|
}
|
|
|
|
this._pingEnabled = false;
|
|
window.asyncStorage.setItem(FTU_PING_ENABLED, false);
|
|
|
|
var settings = window.navigator.mozSettings;
|
|
OBSERVE_SETTINGS.forEach(function(setting) {
|
|
settings.removeObserver(setting, this._settingObserver);
|
|
}, this);
|
|
|
|
URL_SETTINGS.forEach(function(setting) {
|
|
settings.removeObserver(setting, this._settingObserver);
|
|
}, this);
|
|
|
|
clearInterval(this._pingTimer);
|
|
},
|
|
|
|
pingError: function fp_pingError(message) {
|
|
this.debug('Ping error: ' + message);
|
|
},
|
|
|
|
isEnabled: function fp_isEnabled() {
|
|
return this._pingEnabled;
|
|
},
|
|
|
|
getPingURL: function fp_getPingURL() {
|
|
return this._pingURL;
|
|
},
|
|
|
|
assemblePingData: function fp_assemblePingData() {
|
|
this._pingData.pingTime = Date.now();
|
|
this._pingData.locale = window.navigator.language;
|
|
return this._pingData;
|
|
},
|
|
|
|
getInfoData: function fp_getInfoData() {
|
|
return this._infoData;
|
|
},
|
|
|
|
getNetworkFailCount: function fp_getNetworkFailCount() {
|
|
return this._networkFailCount;
|
|
},
|
|
|
|
getTryInterval: function fp_getTryInterval() {
|
|
return this._tryInterval;
|
|
},
|
|
|
|
getPingTimeout: function fp_getPingTimeout() {
|
|
return this._pingTimeout;
|
|
},
|
|
|
|
getMaxNetworkFails: function fp_getMaxNetworkFails() {
|
|
return this._maxNetworkFails;
|
|
}
|
|
};
|
|
|
|
exports.FtuPing = FtuPing;
|
|
}(window));
|