var UR = UR || {}; UR.client = { init : function (onLoaded, onLostConnection, onAuthenticationNeeded, onUnauthorized) { var isConnecting = true, connectionId, clientNonce = UR.util.newGUID(), serverNonce, credentials, source = "web-" + UR.util.myGUID(); var request, connect, reconnect, startHandshake, doHandshaking, resumeHandshake, endHandshake, startRemoteSync, onRemoteSyncHash, onRemoteSyncUpdates; request = function(data, success, error) { data.Source = source; $.ajax({ type: 'POST', url: '/client/request', data: JSON.stringify(data), headers : { 'UR-Connection-ID' : connectionId } }) //Default implementation .done(success || function (data) { console.debug("Request success, request: \n" + JSON.stringify(data)); }) //Default implementation .fail(error || function(xhr, textStatus, errorThrown) { var msg = { status: textStatus, headers: this.headers, error: errorThrown.message, data: data }; console.debug(msg); }); }; var fails = 0; longPollEvents = function(success, error) { if (isConnecting) { // Await connection setTimeout(function() { longPollEvents(success, error); }, 300); return; } $.ajax({ type: 'GET', url: '/client/event', headers : { 'UR-Connection-ID' : connectionId } }) //Default implementation .done(success || function (data) { console.debug("Request success, data: \n" + JSON.stringify(data)); }) //Default implementation .fail(error || function(xhr, textStatus, errorThrown) { fails++; if (fails > 5) { fails = 0; reconnect(); } var msg = { name: "Request failed", status: textStatus, headers: this.headers, error: errorThrown.message }; console.debug(msg); }) .always(function () { longPollEvents(success, error); }); }; onRemoteSyncUpdates = function (data) { if (data.Remotes) { UR.store.saveRemotes(data.Remotes); model.remotes = UR.store.getRemoteList(); model.active_remotes = _.where(model.remotes, {Hidden: false}).length; } if (data.Layouts) { UR.store.updateLayouts(data.Layouts, function (layoutID, callback) { fetchRemoteLayout(layoutID, callback); }); } isConnecting = false; onLoaded(); }; onRemoteSyncHash = function (data) { var rhash = UR.store.getRemoteHash(); var lHash = UR.store.getLayoutsHash(); var req = { Action: UR.enums.DataPacketAction.Sync, Request: UR.enums.DataPacketAction.Sync, Remotes: rhash, Layouts: lHash }; request(req, onRemoteSyncUpdates); }; startRemoteSync = function (data) { tip("Synching remotes"); var req = { Action: UR.enums.DataPacketAction.Hash, Request: UR.enums.DataPacketAction.Hash }; request(req, onRemoteSyncHash); }; endHandshake = function (data) { if (data.Security === UR.enums.DataSecurity.Invalid) { UR.store.resetCredentials(); disconnect(); onUnauthorized(); } if (data.Security === UR.enums.DataSecurity.Disabled) { UR.store.saveCredentials(credentials); //Success startRemoteSync(); } if (data.Security === UR.enums.DataSecurity.Enable) { alert("Sorry, encryption is not supported in this client"); } }; doHandshaking = function(challenge) { serverNonce = (challenge || {}).Password var req = { Capabilities: { Actions: true, Sync: true, Grid: true, Fast: false, //to begin with, sends test later Loading: true, Encryption2: true // supports v2 encryption }, Action: UR.enums.DataPacketAction.Authenticate, Request: UR.enums.DataPacketAction.Authenticate }; credentials = UR.store.getSavedCredentials(); if (challenge.Security === UR.enums.DataSecurity.Users) { // Function to resume handshake resumeHandshake = function() { req["Password"] = UR.util.sha256(serverNonce + credentials.username + ":" + credentials.password + clientNonce); request(req, endHandshake); }; // false is nothing saved if (credentials === false) { onAuthenticationNeeded(true); // Allow user interface to prompt for user/pass } else { resumeHandshake(); // Continue with saved credentials } } if (challenge.Security === UR.enums.DataSecurity.Anonymous) { // Function to resume handshake resumeHandshake = function() { req["Password"] = UR.util.sha256(serverNonce + credentials.password + clientNonce); request(req, endHandshake); }; // false is nothing saved if (credentials === false) { onAuthenticationNeeded(false); // Allow user interface to prompt for pass } else { resumeHandshake(); // Continue with saved credentials } } if (challenge.Security == UR.enums.DataSecurity.Disabled) { request(req,endHandshake); // Proceed as usual } }; startHandshake = function() { var req = { Action : UR.enums.DataPacketAction.Handshake, Request: UR.enums.DataPacketAction.Handshake, Version: UR.enums.HandshakeVersionCode, Password: clientNonce, Platform: "web" }; request(req, doHandshaking); }; connect = function() { $.get("/client/connect") .done(function(data) { id = (data.id || "").trim(); connectionId = id; console.debug("Client connected (" + id + ")"); startHandshake(); }) .fail(function(xhr, textStatus, errorThrown) { var msg = { name: "Connect failed", status: textStatus, headers: this.headers, error: errorThrown }; console.debug(msg); setTimeout(function() { reconnect(); }, 3000); }); }; disconnect = function(remote) { /* Stop server connection */ $.ajax({ type: 'GET', url: '/client/disconnect', headers : { 'UR-Connection-ID' : connectionId } }) .done(function(data) { console.debug("Client stopped"); }) .fail(function(xhr, textStatus, errorThrown) { console.log("Failed to stop connection"); }); }; reconnect = function () { onLostConnection(); isConnecting = true; connect(); }; fetchRemoteLayout = function (id, onComplete) { var req = { Action : UR.enums.DataPacketAction.Layout, Request: UR.enums.DataPacketAction.Layout, ID: id, Layout: { Hash: 0 } }; request(req, function (data) { onComplete(data.Layout); }); }; getRemoteLayout = function (id, onComplete) { var l = UR.store.getLayout(id); if (l) { // Is in cache onComplete(l); } else { fetchRemoteLayout(id, onComplete); } }; loadRemote = function (remote, layout, onComplete) { var req = { ID: remote.ID, Action: UR.enums.DataPacketAction.Load, Request: UR.enums.DataPacketAction.Load, //Destination: remote.Source, Layout: _.pick(layout, "Hash") }; request(req, function (data) { onComplete(data); }); }; unloadRemote = function (remote, onComplete) { var req = { ID: remote.ID, Action: UR.enums.DataPacketAction.Unload, Request: UR.enums.DataPacketAction.Unload, //Destination: remote.Source, }; request(req, function (data) { onComplete(data); }); }; fetchState = function (remote, onComplete) { var req = { ID: remote.ID, Action: UR.enums.DataPacketAction.State, Request: UR.enums.DataPacketAction.State }; request(req, function (data) { onComplete(data); }); }; sendRemoteAction = function (remote, action, onComplete) { var req = { ID: remote.ID, Action: UR.enums.DataPacketAction.Action, Request: UR.enums.DataPacketAction.Action, Run: action }; request(req, function (data) { onComplete(data); }); }; sendKeepAlive = function () { if (isConnecting) return; var req = { KeepAlive: true }; request(req, function (data) { console.debug("KeepAlive sent!"); }); }; var keepAliveTimer; startListen = function (f) { longPollEvents(f); keepAliveTimer = setInterval(function() { sendKeepAlive(); }, 60 * 1000); }; connect(); return { startRemote : function (remote, onRemoteLayout, onRemoteLoad) { getRemoteLayout(remote.ID, function (layout) { onRemoteLayout(layout); console.debug("Remote " + remote.ID + " loading"); loadRemote(remote, layout, function (data) { console.debug("Remote " + remote.ID + " loaded"); onRemoteLoad(data); fetchState(remote, function (data) { console.debug("Remote " + remote.ID + " focused"); }); }); }); }, stopRemote : function (remote) { console.debug("Remote " + remote.ID + " unloading remote"); unloadRemote(remote, function(data) { console.debug("Remote " + remote.ID + " unloaded"); }); }, sendAction : function (remote, action) { if (action === undefined) { return; // Ignore } sendRemoteAction(remote, action, function (data) { console.debug("Action " + JSON.stringify(action) + " sent!"); }); }, listenToEvents : function (f) { startListen(f); }, onAuthentication: function(u, p) { credentials = {username: u, password: p}; resumeHandshake(); }, stop : function (remote) { isConnecting = true; clearInterval(keepAliveTimer); disconnect(); }, restart: function () { isConnecting = true; connect(); } }; } }