"use strict";
const EXPORTED_SYMBOLS = ["core"];
const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
const core = {
api: null,
init: function Geolocation_init(api) {
this.api = api;
YaGeolocation.startup();
}
,
finalize: function Geolocation_finalize() {
YaGeolocation.shutdown();
delete this.api;
}
};
const YaGeolocation = {
GEO_API_VERSION: "1.2",
GEO_URL: "http://wi2geo.mobile.yandex.net/getlocation",
GEO_KEY: "AQIAAHBWEoNf/VB4O81cL2HOFJvtdNtP",
TRUSTED_DOMAINS: [".ya.ru", ".yandex.ru", ".yandex.kz", ".yandex.ua", ".yandex.by", ".yandex.com", ".yandex.com.tr", ".moikrug.ru"],
UPDATE_INTERVAL: 10 * 60 * 1000,
_updated: false,
wifi: "",
lastWifi: "",
lastIP: "",
position: null,
_watchingTimer: null,
get canRun() {
delete this.canRun;
return this.canRun = Ci.nsIWifiMonitor && (core.api.Environment.os.name === "windows" || ! core.api.Environment.browser.version.isLessThan("25.a1"));
}
,
get WifiMonitorService() {
delete this.WifiMonitorService;
return this.WifiMonitorService = Cc["@mozilla.org/wifi/monitor;1"].getService(Ci.nsIWifiMonitor);
}
,
get DNSService() {
delete this.DNSService;
return this.DNSService = Cc["@mozilla.org/network/dns-service;1"].getService(Ci.nsIDNSService);
}
,
get PrefsModule() {
return core.api.Settings.PrefsModule;
}
,
get prefBranchPath() {
delete this.prefBranchPath;
return this.prefBranchPath = core.api.Settings.getComponentBranchPath();
}
,
getPref: function YaGeolocation_getPref(aName, aDefaultValue) {
var defValue = arguments.length == 1 ? "" : aDefaultValue;
return this.PrefsModule.get(this.prefBranchPath + aName,defValue);
}
,
setPref: function YaGeolocation_setPref(aName, aValue) {
return this.PrefsModule.set(this.prefBranchPath + aName,aValue);
}
,
startup: function YaGeolocation_startup() {
if (! this.canRun)
return;
var dataStr = this.getPref("position");
var savedPosition = null;
try {
savedPosition = JSON.parse(dataStr);
}
catch (e) {

}

if (savedPosition)
{
this.position = savedPosition;
this.wifi = this.lastWifi = this.getPref("wifi.data");
}

this.lastIP = this.getPref("ip");
this.startWatching();
this.log("Startup\nSaved position: " + dataStr + "\nWiFi: " + this.wifi + "\nLast IP: " + this.lastIP);
}
,
shutdown: function YaGeolocation_shutdown() {
if (! this.canRun)
return;
this.stopWatching();
this.log("Shutdown");
}
,
getLocalIP: function YaGeolocation_getLocalIP() {
try {
let DNSRecord = this.DNSService.resolve(this.DNSService.myHostName,this.DNSService.RESOLVE_BYPASS_CACHE);
while (DNSRecord.hasMore()) {
let ipAddress = DNSRecord.getNextAddrAsString();
if (ipAddress.indexOf(":") == - 1)
return ipAddress;
}

}
catch (e) {

}

return "";
}
,
startWatching: function YaGeolocation_startWatching() {
if (this._watchingTimer)
return;
this._watchingTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
this._watchingTimer.initWithCallback(this.update.bind(this),5 * 1000,this._watchingTimer.TYPE_REPEATING_SLACK);
this.WifiMonitorService.startWatching(this);
this.log("Start watching.");
}
,
stopWatching: function YaGeolocation_stopWatching() {
if (! this._watchingTimer)
return;
this._watchingTimer.cancel();
this._watchingTimer = null;
this.WifiMonitorService.stopWatching(this);
this.removeCookies();
}
,
updateWiFi: function YaGeolocation_updateWiFi(aWifiString) {
this.log("Update WiFi: " + aWifiString);
this.wifi = aWifiString;
if (! this._updated)
this.update();
}
,
update: function YaGeolocation_update() {
if (this._watchingTimer)
this._watchingTimer.delay = this.UPDATE_INTERVAL;
var ip = this.getLocalIP();
var ipChanged = ip != this.lastIP;
this.log("Update on timer. Current IP = '" + ip + "', prev IP = '" + this.lastIP + "'.");
if (ipChanged)
{
this.lastIP = ip;
this.setPref("ip",ip);
}

var lastPositionTimestamp = this.position && this.position.timestamp || 0;
var isOldPosition = Math.abs(lastPositionTimestamp - Date.now()) > 60 * 30 * 1000;
if (isOldPosition || ! this.wifi || ! this.equalWiFi(this.wifi,this.lastWifi))
{
this.lastWifi = this.wifi;
if (! this.wifi && ! ipChanged && ! isOldPosition)
{
this._updated = false;
this.setCookies();
return;
}

this._updated = true;
this.getPositionByWiFi();
}
 else
{
this._updated = false;
this.setCookies();
}

}
,
equalWiFi: function YaGeolocation_equalWiFi(wifi1, wifi2) {
var arr1 = wifi1.split(",");
var arr2 = wifi2.split(",");
if (arr1.length != arr2.length)
return false;
for (let i = 1, len = arr1.length;i < len;i += 3) {
if (arr1[i] != arr2[i])
return false;
}

return true;
}
,
getPositionByWiFi: function YaGeolocation_getPositionByWiFi() {
var params = ["version=" + this.GEO_API_VERSION, "apikey=" + encodeURIComponent(this.GEO_KEY)];
if (this.wifi)
params.push("wifinetworks=" + encodeURIComponent(this.wifi));
params.push("nogeocode");
var request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
request.open("POST",this.GEO_URL,true);
request.setRequestHeader("Content-Type","application/x-www-form-urlencoded; charset=UTF-8");
request.setRequestHeader("Connection","close");
var requestCallback = this.getPositionByWiFiCallback.bind(this);
var target = request.QueryInterface(Ci.nsIDOMEventTarget);
target.addEventListener("load",requestCallback,false);
target.addEventListener("error",requestCallback,false);
target.addEventListener("abort",requestCallback,false);
request.send(params.join("&"));
this.log("Get position by WiFi.\nPost data:\n" + params.join("\n"));
}
,
getPositionByWiFiCallback: function YaGeolocation_getPositionByWiFiCallback(aRequest) {
try {
this.log("wi2geo response: " + aRequest.target.responseText);
}
catch (e) {
this.log("wi2geo response: [error]");
}

var dataElement;
try {
dataElement = aRequest.target.responseXML.querySelector("iamhere");
}
catch (e) {

}

if (! dataElement)
return;
var position = Object.create(null);
["latitude", "longitude", "precision"].forEach(function (positionProp) {
var element = dataElement.querySelector(positionProp);
var propValue = element && element.textContent;
if (! propValue)
return;
position[positionProp] = propValue;
}
,this);
position.timestamp = Date.now();
this.position = position;
this.setPref("position",JSON.stringify(position));
this.log("Get position by WiFi callback\nPosition: " + JSON.stringify(position));
this.setCookies();
}
,
setCookies: function YaGeolocation_setCookies(aSubCookieValue) {
var debugInfo = [];
const cookieSvc = Cc["@mozilla.org/cookieService;1"].getService(Ci.nsICookieService);
this.TRUSTED_DOMAINS.forEach(function (aDomain) {
var cookieURI = Cc["@mozilla.org/network/standard-url;1"].createInstance(Ci.nsIURI);
cookieURI.spec = "http://" + aDomain.replace(/^\./,"");
var allCookiesStr = cookieSvc.getCookieString(cookieURI,null) || "";
var cookieMatch = allCookiesStr.match("(?:^|;)\\s*ys=([^;]*)");
var newCookieValue = this.parseCookie(cookieMatch ? decodeURIComponent(cookieMatch[1]) : "",aSubCookieValue);
var cookieStr = "ys=" + encodeURIComponent(newCookieValue);
if (! newCookieValue.length)
cookieStr += ";expires=" + new Date(0);
if (aDomain.charAt(0) == ".")
cookieStr += ";domain=" + aDomain;
cookieSvc.setCookieString(cookieURI,null,cookieStr,null);
debugInfo.push(cookieURI.spec + ": " + cookieStr);
}
,this);
this.log("Set cookies:\n" + debugInfo.join("\n"));
}
,
removeCookies: function YaGeolocation_removeCookies() {
this.log("Remove cookies");
this.setCookies("");
}
,
parseCookie: function YaGeolocation_parseCookie(aCookieContainerStr, aSubCookieValue) {
var gpCookieText = typeof aSubCookieValue == "undefined" ? this.getCookieText() : aSubCookieValue;
var ySubCookies = aCookieContainerStr.split("#");
var found = false;
if (gpCookieText !== null)
{
for (let i = ySubCookies.length;i--;) {
if (ySubCookies[i].indexOf("gpauto.") == 0)
{
found = true;
ySubCookies[i] = gpCookieText;
break;
}

}

}

if (! found && gpCookieText)
ySubCookies.push(gpCookieText);
return ySubCookies.filter(function (v) ! ! v).join("#");
}
,
getCookieText: function YaGeolocation_getCookieText() {
var position = this.position;
if (position)
{
try {
return "gpauto." + [position.latitude, position.longitude, position.precision, "1", parseInt(Date.now() / 1000,10)].join(":").replace(/\./g,"_");
}
catch (e) {

}

}

return null;
}
,
onChange: function YaGeolocation_onChange(aAccessPoints) {
var wifiData = aAccessPoints.map(function (aPoint, aIndex) {
return [aIndex, aPoint.mac.replace(/\-/g,":"), aPoint.signal].join(",");
}
).join(",");
var lastWifiData = this.getPref("wifi.data");
if (wifiData == lastWifiData)
return;
this.setPref("wifi.data",wifiData);
this.updateWiFi(wifiData);
}
,
onError: function YaGeolocation_onError(aValue) {
this.log("Wifi scan error: " + aValue);
}
,
QueryInterface: function YaGeolocation_QueryInterface(iid) {
if (iid.equals(Ci.nsISupports) || iid.equals(Ci.nsIWifiListener))
return this;
throw Cr.NS_ERROR_NO_INTERFACE;
}
,
log: function YaGeolocation_log(aMessage) {
core.api && core.api.logger.trace(aMessage);
}
};
