VulpesOS/gaia/apps/system/js/ftu_ping.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

445 lines
14 KiB
JavaScript
Executable file

/* -*- 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));