"use strict";
var EXPORTED_SYMBOLS = ["XMPP"];
var {API: API} = require("api");
var CONFIG = {
HOST: NativeAPI.Settings.getValue("debugServer") ? "xmpp-tst.disk.yandex.net" : "xmpp.disk.yandex.net",
PORT: 5222,
DOMAIN: "yandex.ru"};
var XMPP = {
init: function XMPP_init() {
this._logger = NativeAPI.logger.getLogger("XMPP");
this._logger.debug("init");
this._token = require("auth").Auth.token;
if (! this._token)
throw new Error("Can not get auth token");
var TCPSocket = require("libs/TCPSocket").TCPSocket;
this._TCPSocket = new TCPSocket();
this._Stanza = require("libs/xmpp-xml").Stanza;
this._XMPPParser = require("libs/xmpp-xml").XMPPParser;
this._createSocket();
}
,
finalize: function XMPP_finalize() {
this._logger.debug("finalize");
if (! this._token)
return;
this._token = null;
if (this._socket)
{
this._socket.close();
this._socket = null;
}

if (this._TCPSocket)
{
this._TCPSocket.close();
this._TCPSocket = null;
}

if (this._parser)
{
this._parser.destroy();
this._parser = null;
}

this._XMPPParser = null;
this._Stanza = null;
}
,
getXmppEngineName: function XMPP_getXmppEngineName(engineName) {
if (! engineName)
return null;
var map = {
Autofill: "AUTOFILL",
Bookmarks: "BOOKMARK",
Nigori: "NIGORI",
Passwords: "PASSWORD",
Pinned: "YANDEX_GLOBAL_SETTING",
Tophistory: "YANDEX_ELEMENTS",
Typedurls: "TYPED_URL"};
return map[engineName] || null;
}
,
getEngineByXmppName: function XMPP_getEngineByXmppName(name) {
if (! name)
return null;
var map = {
AUTOFILL: "Autofill",
BOOKMARK: "Bookmarks",
NIGORI: "Nigori",
PASSWORD: "Passwords",
YANDEX_GLOBAL_SETTING: "Pinned",
YANDEX_ELEMENTS: "Tophistory",
TYPED_URL: "Typedurls"};
var engineName = map[name];
if (! engineName)
return null;
var {Service: Service} = require("service");
return Service.engineManager.get(engineName);
}
,
_startStream: function XMPP__startStream() {
if (this._parser)
this._parser.destroy();
this._parser = new this._XMPPParser(this);
this._socket.send("<?xml version='1.0'?><stream:stream to='" + CONFIG.DOMAIN + "' xml:lang='en' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:client'>");
}
,
_createSocket: function XMPP__createSocket() {
this._socket = this._TCPSocket.open(CONFIG.HOST,CONFIG.PORT,{
useSSL: true});
this._socket.onopen = (function (e) {
this._logger.debug("[SOCKET] open");
this.onXmppStanza = this._XMPPListener.onStream;
this._startStream();
}
).bind(this);
this._socket.ondata = (function (e) {
this._logger.trace("[SOCKET]\nINPUT:\n" + e.data);
var istream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
var length = e.data.length;
istream.setData(e.data,length);
this._parser.onDataAvailable(istream,0,length);
}
).bind(this);
this._socket.onerror = (function (e) {
this._logger.debug("[SOCKET] error",e);
}
).bind(this);
this._socket.onclose = (function (e, x) {
this._logger.debug("[SOCKET] close " + x);
if (this._socket && this._socket.reconnect)
{
this._createSocket();
return;
}

}
).bind(this);
return this._socket;
}
,
_handshake: function XMPP__handshake() {
this._socket._transport.securityInfo.QueryInterface(Ci.nsISSLSocketControl).StartTLS();
}
,
_subscribe: function XMPP__subscribe() {
var data = require("protobuf").Protobuf.getGatewayMessage(this.token,true);
this.onXmppStanza = this._XMPPListener.onSubscribe;
this._socket.send(this._message(data));
}
,
get token() {
var tokenObj = JSON.parse(NativeAPI.Settings.getValue("xmpp.token") || "{}");
var username = require("auth").Auth.token.username;
if (! username)
{
this._logger.debug("Trying to get xmpp token without username");
return null;
}

var token = tokenObj[username] || null;
return token;
}
,
set token(val) {
var tokenObj = JSON.parse(NativeAPI.Settings.getValue("xmpp.token") || "{}");
var username = require("auth").Auth.token.username;
if (! username)
{
this._logger.debug("Trying to set xmpp token without username");
return;
}

tokenObj[username] = val;
NativeAPI.Settings.setValue("xmpp.token",JSON.stringify(tokenObj));
}
,
onXmppStanza: function XMPP_onXmppStanza() {
throw new Error("Should be overriden");
}
,
onXMLError: function XMPP_onXMLError(err) {
this._logger.debug("[SOCKET] onXMLError " + err);
}
,
_message: function XMPP__message(data) {
var login = this._token.username + "@" + CONFIG.DOMAIN;
var recipientNode = this._Stanza.node("recipient",this._Stanza.yasync,{
to: "sync@bot.ya.ru"});
var dataNode = this._Stanza.node("data",this._Stanza.yasync,null,data);
var messageBody = this._Stanza.node("push",this._Stanza.yasync,null,[recipientNode, dataNode]);
var message = this._Stanza.message(login,null,null,{
type: "headline"},messageBody).getXML();
return message;
}
,
_XMPPListener: {
_logger: NativeAPI.logger.getLogger("XMPPListener"),
onXmppStanza: function XMPPListener_onXmppStanza(node) {
var data = node.getElement(["push:push", "push:data"]);
if (! data)
{
this._logger.error("Wrong node on xmpp stanza " + node.getXML());
return;
}

var protobuf = require("protobuf").Protobuf;
var message = protobuf.parseClientGatewayMessage(data.innerText);
var networkMessage = protobuf.parseServerToClientMessage(message.network_message);
var inv = networkMessage.invalidation_message && networkMessage.invalidation_message.invalidation;
this._logger.debug("inv",inv);
this._logger.debug(networkMessage);
if (! inv)
return;
Array.forEach(inv,function (invP) {
var name = invP.object_id.name;
name = [String.fromCharCode(name[k]) for (k in name)].join("");
this._logger.debug("name",name);
var engine = this.getEngineByXmppName(name);
this._logger.debug("engine",engine);
if (engine)
engine.update();
}
,this);
}
,
onStream: function XMPPListener_onStream(node) {
if (node.localName !== "features")
{
this._logger.error("Wrong node " + node.getXML());
this._socket.close();
return;
}

if (node.getElement(["starttls"]))
{
this.onXmppStanza = this._XMPPListener.onTLS;
let stanza = this._Stanza.node("starttls",this._Stanza.NS.tls).getXML();
this._socket.send(stanza);
return;
}

var mech = node.getElement(["mechanisms"]);
if (mech)
{
mech = mech.getChildren("mechanism")[0].innerText;
if (mech !== "X-YANDEX-OAUTH")
{
this.onXmppStanza = this._XMPPListener.onAuth;
this.onXmppStanza(node);
return;
}

}

if (! node.getElement(["bind"]))
{
this._logger.error("Wrong node on binding " + node.getXML());
this._socket.close();
return;
}

this.onXmppStanza = this._XMPPListener.onBind;
var bindNode = this._Stanza.node("bind",this._Stanza.NS.bind);
var stanza = this._Stanza.iq("set",null,null,bindNode).getXML();
this._socket.send(stanza);
}
,
onBind: function XMPPListener_onBind(node) {
var bindNode = node.getElement(["bind"]);
if (! bindNode)
{
this._logger.error("Wrong node on binding " + node.getXML());
this._socket.close();
return;
}

this.onXmppStanza = this._XMPPListener.onSession;
var sessionNode = this._Stanza.node("session",this._Stanza.NS.session);
var stanza = this._Stanza.iq("set",null,null,sessionNode).getXML();
this._socket.send(stanza);
}
,
onSession: function XMPPListener_onSession(node) {
if (! node.getElement(["session"]))
{
this._logger.error("Wrong session " + node.getXML());
this._socket.close();
return;
}

this.onXmppStanza = function (node) {
if (node.attributes.type !== "result")
return;
var protoData = require("protobuf").Protobuf.getGatewayMessage();
var message = this._message(protoData);
this.onXmppStanza = this._XMPPListener.onGetToken;
this._socket.send(message);
}
;
var login = this._token.username + "@" + CONFIG.DOMAIN;
this._socket.send("<iq type='set' to='" + login + "'><subscribe xmlns='ya:push:sync'><item channel='ya_sync' from=''/></subscribe></iq>");
}
,
onGetToken: function XMPPListener_onGetToken(node) {
var data = node.getElement(["push:push", "push:data"]);
if (! data)
{
this._logger.error("Wrong node on get token " + node.getXML());
this._socket.close();
return;
}

var protobuf = require("protobuf").Protobuf;
var message = protobuf.parseClientGatewayMessage(data.innerText);
var networkMessage = protobuf.parseServerToClientMessage(message.network_message);
this.token = networkMessage.token_control_message.new_token;
this._subscribe();
}
,
onSubscribe: function XMPPListener_onSubscribe(node) {
var data = node.getElement(["push:push", "push:data"]);
if (! data)
{
this._logger.error("Wrong node on get token " + node.getXML());
this._socket.close();
return;
}

var protobuf = require("protobuf").Protobuf;
var message = protobuf.parseClientGatewayMessage(data.innerText);
var networkMessage = protobuf.parseServerToClientMessage(message.network_message);
this.onXmppStanza = this._XMPPListener.onXmppStanza;
}
,
onAuth: function XMPPListener_onAuth(node) {
if (node.localName !== "features")
{
this._logger.error("Wrong node " + node.getXML());
this._socket.close();
}

var mech = node.getElement(["mechanisms"]);
mech = mech.getChildren("mechanism")[0].innerText;
if (mech !== "X-YANDEX-OAUTH")
{
this._logger.error("Mechanism should be X-YANDEX-OAUTH, but it is " + mech);
this._socket.close();
}

var {username: username, value: tokenValue} = this._token;
var stanza = this._Stanza.node("auth",this._Stanza.NS.sasl,{
mechanism: mech},btoa(username + "\x00" + tokenValue)).getXML();
this.onXmppStanza = (function (node) {
this.onXmppStanza = this._XMPPListener.onStream;
this._startStream();
}
).bind(this);
this._socket.send(stanza);
}
,
onTLS: function XMPPListener_onTLS(node) {
if (node.localName != "proceed")
{
this._logger.error("Wrong node " + JSON.stringify(node));
this._socket.close();
}

this.onXmppStanza = this._XMPPListener.onAuth;
this._handshake();
this._startStream();
}
},
_TCPSocket: null,
_Stanza: null,
_XMPPParser: null,
_token: null,
_socket: null,
_parser: null};
