gecko-dev/toolkit/modules/Region.jsm

151 lines
3.9 KiB
JavaScript

/* 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/. */
"use strict";
const EXPORTED_SYMBOLS = ["Region"];
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
XPCOMUtils.defineLazyModuleGetters(this, {
LocationHelper: "resource://gre/modules/LocationHelper.jsm",
Services: "resource://gre/modules/Services.jsm",
setTimeout: "resource://gre/modules/Timer.jsm",
});
XPCOMUtils.defineLazyGlobalGetters(this, ["fetch"]);
XPCOMUtils.defineLazyPreferenceGetter(
this,
"wifiScanningEnabled",
"browser.region.network.scan",
true
);
XPCOMUtils.defineLazyPreferenceGetter(
this,
"regionFetchTimeout",
"browser.region.timeout",
5000
);
XPCOMUtils.defineLazyPreferenceGetter(
this,
"loggingEnabled",
"browser.region.log",
false
);
const log = console.createInstance({
prefix: "Region.jsm",
maxLogLevel: loggingEnabled ? "All" : "Warn",
});
function timeout(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* This module keeps track of the users current region (country).
* so the SearchService and other consumers can apply region
* specific customisations.
*/
class RegionDetector {
// Keep track of the wifi data across listener events.
wifiDataPromise = null;
// Store the AbortController for the geolocation requests so we
// can abort the request on timeout.
fetchController = null;
/**
* Fetch the region that we consider to be the users home region.
*
* @returns {object}
* JSON with country_code field defining users current region.
*/
async getHomeRegion() {
return Promise.race([this._getRegion(), this._timeout()]);
}
/**
* Implementation of the above region fetching.
*/
async _getRegion() {
log.info("getRegion called");
this.fetchController = new AbortController();
let fetchOpts = {
headers: { "Content-Type": "application/json" },
credentials: "omit",
signal: this.fetchController.signal,
};
if (wifiScanningEnabled) {
let wifiData = await this._fetchWifiData();
if (wifiData) {
let postData = JSON.stringify({ wifiAccessPoints: wifiData });
log.info("Sending wifi details: ", wifiData);
fetchOpts.method = "POST";
fetchOpts.body = postData;
}
}
let url = Services.urlFormatter.formatURLPref("browser.region.network.url");
let req = await fetch(url, fetchOpts);
try {
let res = await req.json();
this.fetchController = null;
log.info("getRegion returning " + res.country_code);
return { country_code: res.country_code };
} catch (err) {
log.error("Error fetching region", err);
throw new Error("region-fetch-no-result");
}
}
/**
* Implement the timeout for region requests. This will be run for
* all region requests, but the error will only be returned if it
* completes first.
*/
async _timeout() {
await timeout(regionFetchTimeout);
if (this.fetchController) {
this.fetchController.abort();
}
throw new Error("region-fetch-timeout");
}
async _fetchWifiData() {
log.info("fetchWifiData called");
this.wifiService = Cc["@mozilla.org/wifi/monitor;1"].getService(
Ci.nsIWifiMonitor
);
this.wifiService.startWatching(this);
return new Promise(resolve => {
this.wifiDataPromise = resolve;
});
}
onChange(accessPoints) {
log.info("onChange called");
if (!accessPoints || !this.wifiDataPromise) {
return;
}
if (this.wifiService) {
this.wifiService.stopWatching(this);
this.wifiService = null;
}
if (this.wifiDataPromise) {
let data = LocationHelper.formatWifiAccessPoints(accessPoints);
this.wifiDataPromise(data);
this.wifiDataPromise = null;
}
}
}
let Region = new RegionDetector();