diff --git a/background.html b/background.html
index d4a7773a9..e2e89d9ba 100644
--- a/background.html
+++ b/background.html
@@ -2,7 +2,6 @@
-
@@ -17,7 +16,6 @@
-
diff --git a/js-deps/jquery.atmosphere.js b/js-deps/jquery.atmosphere.js
deleted file mode 100644
index 7f0c41dcb..000000000
--- a/js-deps/jquery.atmosphere.js
+++ /dev/null
@@ -1,2949 +0,0 @@
-/**
- * Copyright 2013 Jeanfrancois Arcand
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-/*
- * IE streaming/XDR supports is copied/highly inspired by http://code.google.com/p/jquery-stream/
- *
- * Copyright 2011, Donghwan Kim
- * Licensed under the Apache License, Version 2.0
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * LocalStorage supports is copied/highly inspired by https://github.com/flowersinthesand/jquery-socket
- * Copyright 2011, Donghwan Kim
- * Licensed under the Apache License, Version 2.0
- * http://www.apache.org/licenses/LICENSE-2.0
- * */
-/**
- * Official documentation of this library: https://github.com/Atmosphere/atmosphere/wiki/jQuery.atmosphere.js-API
- */
-jQuery.atmosphere = function () {
-
- jQuery(window).bind("unload.atmosphere", function () {
- jQuery.atmosphere.unsubscribe();
- });
-
- jQuery(window).bind("offline", function () {
- jQuery.atmosphere.unsubscribe();
- });
-
- // Prevent ESC to kill the connection from Firefox.
- jQuery(window).keypress(function (e) {
- if (e.keyCode === 27) {
- e.preventDefault();
- }
- });
-
- var parseHeaders = function (headerString) {
- var match, rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, headers = {};
- while (match = rheaders.exec(headerString)) {
- headers[match[1]] = match[2];
- }
- return headers;
- };
-
- return {
- version: "2.0.5-jquery",
- uuid : 0,
- requests: [],
- callbacks: [],
-
- onError: function (response) {
- },
- onClose: function (response) {
- },
- onOpen: function (response) {
- },
- onMessage: function (response) {
- },
- onReconnect: function (request, response) {
- },
- onMessagePublished: function (response) {
- },
- onTransportFailure: function (errorMessage, _request) {
- },
- onLocalMessage: function (response) {
- },
- onClientTimeout: function(request){
- },
- onFailureToReconnect: function (request, response) {
- },
-
- AtmosphereRequest: function (options) {
-
- /**
- * {Object} Request parameters.
- *
- * @private
- */
- var _request = {
- timeout: 300000,
- method: 'GET',
- headers: {},
- contentType: '',
- callback: null,
- url: '',
- data: '',
- suspend: true,
- maxRequest: -1,
- reconnect: true,
- maxStreamingLength: 10000000,
- lastIndex: 0,
- logLevel: 'info',
- requestCount: 0,
- fallbackMethod: 'GET',
- fallbackTransport: 'streaming',
- transport: 'long-polling',
- webSocketImpl: null,
- webSocketBinaryType: null,
- dispatchUrl: null,
- webSocketPathDelimiter: "@@",
- enableXDR: false,
- rewriteURL: false,
- attachHeadersAsQueryString: true,
- executeCallbackBeforeReconnect: false,
- readyState: 0,
- lastTimestamp: 0,
- withCredentials: false,
- trackMessageLength: false,
- messageDelimiter: '|',
- connectTimeout: -1,
- reconnectInterval: 0,
- dropAtmosphereHeaders: true,
- uuid: 0,
- shared: false,
- readResponsesHeaders: false,
- maxReconnectOnClose: 5,
- enableProtocol: true,
- onError: function (response) {
- },
- onClose: function (response) {
- },
- onOpen: function (response) {
- },
- onMessage: function (response) {
- },
- onReopen: function (request, response) {
- },
- onReconnect: function (request, response) {
- },
- onMessagePublished: function (response) {
- },
- onTransportFailure: function (reason, request) {
- },
- onLocalMessage: function (request) {
- },
- onFailureToReconnect: function (request, response) {
- },
- onClientTimeout: function(request){
- }
- };
-
- /**
- * {Object} Request's last response.
- *
- * @private
- */
- var _response = {
- status: 200,
- reasonPhrase: "OK",
- responseBody: '',
- messages: [],
- headers: [],
- state: "messageReceived",
- transport: "polling",
- error: null,
- request: null,
- partialMessage: "",
- errorHandled: false,
- closedByClientTimeout: false
- };
-
- /**
- * {websocket} Opened web socket.
- *
- * @private
- */
- var _websocket = null;
-
- /**
- * {SSE} Opened SSE.
- *
- * @private
- */
- var _sse = null;
-
- /**
- * {XMLHttpRequest, ActiveXObject} Opened ajax request (in case of http-streaming or long-polling)
- *
- * @private
- */
- var _activeRequest = null;
-
- /**
- * {Object} Object use for streaming with IE.
- *
- * @private
- */
- var _ieStream = null;
-
- /**
- * {Object} Object use for jsonp transport.
- *
- * @private
- */
- var _jqxhr = null;
-
- /**
- * {boolean} If request has been subscribed or not.
- *
- * @private
- */
- var _subscribed = true;
-
- /**
- * {number} Number of test reconnection.
- *
- * @private
- */
- var _requestCount = 0;
-
- /**
- * {boolean} If request is currently aborded.
- *
- * @private
- */
- var _abordingConnection = false;
-
- /**
- * A local "channel' of communication.
- *
- * @private
- */
- var _localSocketF = null;
-
- /**
- * The storage used.
- *
- * @private
- */
- var _storageService;
-
- /**
- * Local communication
- *
- * @private
- */
- var _localStorageService = null;
-
- /**
- * A Unique ID
- *
- * @private
- */
- var guid = jQuery.now();
-
- /** Trace time */
- var _traceTimer;
-
- /** Key for connection sharing */
- var _sharingKey;
-
- // Automatic call to subscribe
- _subscribe(options);
-
- /**
- * Initialize atmosphere request object.
- *
- * @private
- */
- function _init() {
- _subscribed = true;
- _abordingConnection = false;
- _requestCount = 0;
-
- _websocket = null;
- _sse = null;
- _activeRequest = null;
- _ieStream = null;
- }
-
- /**
- * Re-initialize atmosphere object.
- *
- * @private
- */
- function _reinit() {
- _clearState();
- _init();
- }
-
- /**
- * Subscribe request using request transport.
- * If request is currently opened, this one will be closed.
- *
- * @param {Object} Request parameters.
- * @private
- */
- function _subscribe(options) {
- _reinit();
-
- _request = jQuery.extend(_request, options);
- // Allow at least 1 request
- _request.mrequest = _request.reconnect;
- if (!_request.reconnect) {
- _request.reconnect = true;
- }
- }
-
- /**
- * Check if web socket is supported (check for custom implementation provided by request object or browser implementation).
- *
- * @returns {boolean} True if web socket is supported, false otherwise.
- * @private
- */
- function _supportWebsocket() {
- return _request.webSocketImpl != null || window.WebSocket || window.MozWebSocket;
- }
-
- /**
- * Check if server side events (SSE) is supported (check for custom implementation provided by request object or browser implementation).
- *
- * @returns {boolean} True if web socket is supported, false otherwise.
- * @private
- */
- function _supportSSE() {
- return window.EventSource;
- }
-
- /**
- * Open request using request transport.
- * If request transport is 'websocket' but websocket can't be opened, request will automatically reconnect using fallback transport.
- *
- * @private
- */
- function _execute() {
- // Shared across multiple tabs/windows.
- if (_request.shared) {
- _localStorageService = _local(_request);
- if (_localStorageService != null) {
- if (_request.logLevel === 'debug') {
- jQuery.atmosphere.debug("Storage service available. All communication will be local");
- }
-
- if (_localStorageService.open(_request)) {
- // Local connection.
- return;
- }
- }
-
- if (_request.logLevel === 'debug') {
- jQuery.atmosphere.debug("No Storage service available.");
- }
- // Wasn't local or an error occurred
- _localStorageService = null;
- }
-
- // Protocol
- _request.firstMessage = jQuery.atmosphere.uuid == 0 ? true : false;
- _request.isOpen = false;
- _request.ctime = jQuery.now();
- _request.uuid = jQuery.atmosphere.uuid;
- _request.closedByClientTimeout = false;
-
- if (_request.transport !== 'websocket' && _request.transport !== 'sse') {
- _executeRequest(_request);
-
- } else if (_request.transport === 'websocket') {
- if (!_supportWebsocket()) {
- _reconnectWithFallbackTransport("Websocket is not supported, using request.fallbackTransport (" + _request.fallbackTransport
- + ")");
- } else {
- _executeWebSocket(false);
- }
- } else if (_request.transport === 'sse') {
- if (!_supportSSE()) {
- _reconnectWithFallbackTransport("Server Side Events(SSE) is not supported, using request.fallbackTransport ("
- + _request.fallbackTransport + ")");
- } else {
- _executeSSE(false);
- }
- }
- }
-
- function _local(request) {
- var trace, connector, orphan, name = "atmosphere-" + request.url, connectors = {
- storage: function () {
- if (!jQuery.atmosphere.supportStorage()) {
- return;
- }
-
- var storage = window.localStorage, get = function (key) {
- return jQuery.parseJSON(storage.getItem(name + "-" + key));
- }, set = function (key, value) {
- storage.setItem(name + "-" + key, jQuery.stringifyJSON(value));
- };
-
- return {
- init: function () {
- set("children", get("children").concat([guid]));
- jQuery(window).on("storage.socket", function (event) {
- event = event.originalEvent;
- if (event.key === name && event.newValue) {
- listener(event.newValue);
- }
- });
- return get("opened");
- },
- signal: function (type, data) {
- storage.setItem(name, jQuery.stringifyJSON({
- target: "p",
- type: type,
- data: data
- }));
- },
- close: function () {
- var index, children = get("children");
-
- jQuery(window).off("storage.socket");
- if (children) {
- index = jQuery.inArray(request.id, children);
- if (index > -1) {
- children.splice(index, 1);
- set("children", children);
- }
- }
- }
- };
- },
- windowref: function () {
- var win = window.open("", name.replace(/\W/g, ""));
-
- if (!win || win.closed || !win.callbacks) {
- return;
- }
-
- return {
- init: function () {
- win.callbacks.push(listener);
- win.children.push(guid);
- return win.opened;
- },
- signal: function (type, data) {
- if (!win.closed && win.fire) {
- win.fire(jQuery.stringifyJSON({
- target: "p",
- type: type,
- data: data
- }));
- }
- },
- close: function () {
- function remove(array, e) {
- var index = jQuery.inArray(e, array);
- if (index > -1) {
- array.splice(index, 1);
- }
- }
-
- // Removes traces only if the parent is alive
- if (!orphan) {
- remove(win.callbacks, listener);
- remove(win.children, guid);
- }
- }
-
- };
- }
- };
-
- // Receives open, close and message command from the parent
- function listener(string) {
- var command = jQuery.parseJSON(string), data = command.data;
-
- if (command.target === "c") {
- switch (command.type) {
- case "open":
- _open("opening", 'local', _request);
- break;
- case "close":
- if (!orphan) {
- orphan = true;
- if (data.reason === "aborted") {
- _close();
- } else {
- // Gives the heir some time to reconnect
- if (data.heir === guid) {
- _execute();
- } else {
- setTimeout(function () {
- _execute();
- }, 100);
- }
- }
- }
- break;
- case "message":
- _prepareCallback(data, "messageReceived", 200, request.transport);
- break;
- case "localMessage":
- _localMessage(data);
- break;
- }
- }
- }
-
- function findTrace() {
- var matcher = new RegExp("(?:^|; )(" + encodeURIComponent(name) + ")=([^;]*)").exec(document.cookie);
- if (matcher) {
- return jQuery.parseJSON(decodeURIComponent(matcher[2]));
- }
- }
-
- // Finds and validates the parent socket's trace from the cookie
- trace = findTrace();
- if (!trace || jQuery.now() - trace.ts > 1000) {
- return;
- }
-
- // Chooses a connector
- connector = connectors.storage() || connectors.windowref();
- if (!connector) {
- return;
- }
-
- return {
- open: function () {
- var parentOpened;
-
- // Checks the shared one is alive
- _traceTimer = setInterval(function () {
- var oldTrace = trace;
- trace = findTrace();
- if (!trace || oldTrace.ts === trace.ts) {
- // Simulates a close signal
- listener(jQuery.stringifyJSON({
- target: "c",
- type: "close",
- data: {
- reason: "error",
- heir: oldTrace.heir
- }
- }));
- }
- }, 1000);
-
- parentOpened = connector.init();
- if (parentOpened) {
- // Firing the open event without delay robs the user of the opportunity to bind connecting event handlers
- setTimeout(function () {
- _open("opening", 'local', request);
- }, 50);
- }
- return parentOpened;
- },
- send: function (event) {
- connector.signal("send", event);
- },
- localSend: function (event) {
- connector.signal("localSend", jQuery.stringifyJSON({
- id: guid,
- event: event
- }));
- },
- close: function () {
- // Do not signal the parent if this method is executed by the unload event handler
- if (!_abordingConnection) {
- clearInterval(_traceTimer);
- connector.signal("close");
- connector.close();
- }
- }
- };
- }
-
- function share() {
- var storageService, name = "atmosphere-" + _request.url, servers = {
- // Powered by the storage event and the localStorage
- // http://www.w3.org/TR/webstorage/#event-storage
- storage: function () {
- if (!jQuery.atmosphere.supportStorage()) {
- return;
- }
-
- var storage = window.localStorage;
-
- return {
- init: function () {
- // Handles the storage event
- jQuery(window).on("storage.socket", function (event) {
- event = event.originalEvent;
- // When a deletion, newValue initialized to null
- if (event.key === name && event.newValue) {
- listener(event.newValue);
- }
- });
- },
- signal: function (type, data) {
- storage.setItem(name, jQuery.stringifyJSON({
- target: "c",
- type: type,
- data: data
- }));
- },
- get: function (key) {
- return jQuery.parseJSON(storage.getItem(name + "-" + key));
- },
- set: function (key, value) {
- storage.setItem(name + "-" + key, jQuery.stringifyJSON(value));
- },
- close: function () {
- jQuery(window).off("storage.socket");
- storage.removeItem(name);
- storage.removeItem(name + "-opened");
- storage.removeItem(name + "-children");
- }
-
- };
- },
- // Powered by the window.open method
- // https://developer.mozilla.org/en/DOM/window.open
- windowref: function () {
- // Internet Explorer raises an invalid argument error
- // when calling the window.open method with the name containing non-word characters
- var neim = name.replace(/\W/g, ""), win = (jQuery('iframe[name="' + neim + '"]')[0] || jQuery(
- '').hide().appendTo("body")[0]).contentWindow;
-
- return {
- init: function () {
- // Callbacks from different windows
- win.callbacks = [listener];
- // In IE 8 and less, only string argument can be safely passed to the function in other window
- win.fire = function (string) {
- var i;
-
- for (i = 0; i < win.callbacks.length; i++) {
- win.callbacks[i](string);
- }
- };
- },
- signal: function (type, data) {
- if (!win.closed && win.fire) {
- win.fire(jQuery.stringifyJSON({
- target: "c",
- type: type,
- data: data
- }));
- }
- },
- get: function (key) {
- return !win.closed ? win[key] : null;
- },
- set: function (key, value) {
- if (!win.closed) {
- win[key] = value;
- }
- },
- close: function () {
- }
- };
- }
- };
-
- // Receives send and close command from the children
- function listener(string) {
- var command = jQuery.parseJSON(string), data = command.data;
-
- if (command.target === "p") {
- switch (command.type) {
- case "send":
- _push(data);
- break;
- case "localSend":
- _localMessage(data);
- break;
- case "close":
- _close();
- break;
- }
- }
- }
-
- _localSocketF = function propagateMessageEvent(context) {
- storageService.signal("message", context);
- };
-
- function leaveTrace() {
- document.cookie = _sharingKey + "=" +
- // Opera's JSON implementation ignores a number whose a last digit of 0 strangely
- // but has no problem with a number whose a last digit of 9 + 1
- encodeURIComponent(jQuery.stringifyJSON({
- ts: jQuery.now() + 1,
- heir: (storageService.get("children") || [])[0]
- })) + "; path=/";
- }
-
- // Chooses a storageService
- storageService = servers.storage() || servers.windowref();
- storageService.init();
-
- if (_request.logLevel === 'debug') {
- jQuery.atmosphere.debug("Installed StorageService " + storageService);
- }
-
- // List of children sockets
- storageService.set("children", []);
-
- if (storageService.get("opened") != null && !storageService.get("opened")) {
- // Flag indicating the parent socket is opened
- storageService.set("opened", false);
- }
- // Leaves traces
- _sharingKey = encodeURIComponent(name);
- leaveTrace();
- _traceTimer = setInterval(leaveTrace, 1000);
-
- _storageService = storageService;
- }
-
- /**
- * @private
- */
- function _open(state, transport, request) {
- if (_request.shared && transport !== 'local') {
- share();
- }
-
- if (_storageService != null) {
- _storageService.set("opened", true);
- }
-
- request.close = function () {
- _close();
- };
-
- if (_requestCount > 0 && state === 're-connecting') {
- request.isReopen = true;
- _tryingToReconnect(_response);
- } else if (_response.error == null) {
- _response.request = request;
- var prevState = _response.state;
- _response.state = state;
- var prevTransport = _response.transport;
- _response.transport = transport;
-
- var _body = _response.responseBody;
- _invokeCallback();
- _response.responseBody = _body;
-
- _response.state = prevState;
- _response.transport = prevTransport;
- }
- }
-
- /**
- * Execute request using jsonp transport.
- *
- * @param request {Object} request Request parameters, if undefined _request object will be used.
- * @private
- */
- function _jsonp(request) {
- // When CORS is enabled, make sure we force the proper transport.
- request.transport = "jsonp";
-
- var rq = _request;
- if ((request != null) && (typeof (request) !== 'undefined')) {
- rq = request;
- }
-
- var url = rq.url;
- if (rq.dispatchUrl != null) {
- url += rq.dispatchUrl;
- }
-
- var data = rq.data;
- if (rq.attachHeadersAsQueryString) {
- url = _attachHeaders(rq);
- if (data !== '') {
- url += "&X-Atmosphere-Post-Body=" + encodeURIComponent(data);
- }
- data = '';
- }
-
- _jqxhr = jQuery.ajax({
- url: url,
- type: rq.method,
- dataType: "jsonp",
- error: function (jqXHR, textStatus, errorThrown) {
- _response.error = true;
- if (jqXHR.status < 300) {
- _reconnect(_jqxhr, rq, 0);
- } else {
- _onError(jqXHR.status, errorThrown);
- }
- },
- jsonp: "jsonpTransport",
- success: function (json) {
- if (rq.reconnect) {
- if (rq.maxRequest === -1 || rq.requestCount++ < rq.maxRequest) {
- _readHeaders(_jqxhr, rq);
-
- if (!rq.executeCallbackBeforeReconnect) {
- _reconnect(_jqxhr, rq, 0);
- }
-
- var msg = json.message;
- if (msg != null && typeof msg !== 'string') {
- try {
- msg = jQuery.stringifyJSON(msg);
- } catch (err) {
- // The message was partial
- }
- }
-
- var skipCallbackInvocation = _trackMessageSize(msg, rq, _response);
- if (!skipCallbackInvocation) {
- _prepareCallback(_response.responseBody, "messageReceived", 200, rq.transport);
- }
-
- if (rq.executeCallbackBeforeReconnect) {
- _reconnect(_jqxhr, rq, 0);
- }
- } else {
- jQuery.atmosphere.log(_request.logLevel, ["JSONP reconnect maximum try reached " + _request.requestCount]);
- _onError(0, "maxRequest reached");
- }
- }
- },
- data: rq.data,
- beforeSend: function (jqXHR) {
- _doRequest(jqXHR, rq, false);
- }
- });
- }
-
- /**
- * Execute request using ajax transport.
- *
- * @param request {Object} request Request parameters, if undefined _request object will be used.
- * @private
- */
- function _ajax(request) {
- var rq = _request;
- if ((request != null) && (typeof (request) !== 'undefined')) {
- rq = request;
- }
-
- var url = rq.url;
- if (rq.dispatchUrl != null) {
- url += rq.dispatchUrl;
- }
-
- var data = rq.data;
- if (rq.attachHeadersAsQueryString) {
- url = _attachHeaders(rq);
- if (data !== '') {
- url += "&X-Atmosphere-Post-Body=" + encodeURIComponent(data);
- }
- data = '';
- }
-
- var async = typeof (rq.async) !== 'undefined' ? rq.async : true;
- _jqxhr = jQuery.ajax({
- url: url,
- type: rq.method,
- error: function (jqXHR, textStatus, errorThrown) {
- _response.error = true;
- if (jqXHR.status < 300) {
- _reconnect(_jqxhr, rq);
- } else {
- _onError(jqXHR.status, errorThrown);
- }
- },
- success: function (data, textStatus, jqXHR) {
-
- if (rq.reconnect) {
- if (rq.maxRequest === -1 || rq.requestCount++ < rq.maxRequest) {
- if (!rq.executeCallbackBeforeReconnect) {
- _reconnect(_jqxhr, rq, 0);
- }
- var skipCallbackInvocation = _trackMessageSize(data, rq, _response);
- if (!skipCallbackInvocation) {
- _prepareCallback(_response.responseBody, "messageReceived", 200, rq.transport);
- }
-
- if (rq.executeCallbackBeforeReconnect) {
- _reconnect(_jqxhr, rq, 0);
- }
- } else {
- jQuery.atmosphere.log(_request.logLevel, ["AJAX reconnect maximum try reached " + _request.requestCount]);
- _onError(0, "maxRequest reached");
- }
- }
- },
- beforeSend: function (jqXHR) {
- _doRequest(jqXHR, rq, false);
- },
- crossDomain: rq.enableXDR,
- async: async
- });
- }
-
- /**
- * Build websocket object.
- *
- * @param location {string} Web socket url.
- * @returns {websocket} Web socket object.
- * @private
- */
- function _getWebSocket(location) {
- if (_request.webSocketImpl != null) {
- return _request.webSocketImpl;
- } else {
- if (window.WebSocket) {
- return new WebSocket(location);
- } else {
- return new MozWebSocket(location);
- }
- }
- }
-
- /**
- * Build web socket url from request url.
- *
- * @return {string} Web socket url (start with "ws" or "wss" for secure web socket).
- * @private
- */
- function _buildWebSocketUrl() {
- var url = _attachHeaders(_request);
-
- return decodeURI(jQuery('')[0].href.replace(/^http/, "ws"));
- }
-
- /**
- * Build SSE url from request url.
- *
- * @return a url with Atmosphere's headers
- * @private
- */
- function _buildSSEUrl() {
- var url = _attachHeaders(_request);
- return url;
- }
-
- /**
- * Open SSE.
- * Automatically use fallback transport if SSE can't be opened.
- *
- * @private
- */
- function _executeSSE(sseOpened) {
-
- _response.transport = "sse";
-
- var location = _buildSSEUrl(_request.url);
-
- if (_request.logLevel === 'debug') {
- jQuery.atmosphere.debug("Invoking executeSSE");
- jQuery.atmosphere.debug("Using URL: " + location);
- }
-
- if (_request.enableProtocol && sseOpened) {
- var time = jQuery.now() - _request.ctime;
- _request.lastTimestamp = Number(_request.stime) + Number(time);
- }
-
- if (sseOpened && !_request.reconnect) {
- if (_sse != null) {
- _clearState();
- }
- return;
- }
-
- try {
- _sse = new EventSource(location, {
- withCredentials: _request.withCredentials
- });
- } catch (e) {
- _onError(0, e);
- _reconnectWithFallbackTransport("SSE failed. Downgrading to fallback transport and resending");
- return;
- }
-
- if (_request.connectTimeout > 0) {
- _request.id = setTimeout(function () {
- if (!sseOpened) {
- _clearState();
- }
- }, _request.connectTimeout);
- }
-
- _sse.onopen = function (event) {
- _timeout(_request);
- if (_request.logLevel === 'debug') {
- jQuery.atmosphere.debug("SSE successfully opened");
- }
-
- if (!_request.enableProtocol) {
- if (!sseOpened) {
- _open('opening', "sse", _request);
- } else {
- _open('re-opening', "sse", _request);
- }
- }
- sseOpened = true;
-
- if (_request.method === 'POST') {
- _response.state = "messageReceived";
- _sse.send(_request.data);
- }
- };
-
- _sse.onmessage = function (message) {
- _timeout(_request);
- if (!_request.enableXDR && message.origin !== window.location.protocol + "//" + window.location.host) {
- jQuery.atmosphere.log(_request.logLevel, ["Origin was not " + window.location.protocol + "//" + window.location.host]);
- return;
- }
-
- _response.state = 'messageReceived';
- _response.status = 200;
-
- message = message.data;
- var skipCallbackInvocation = _trackMessageSize(message, _request, _response);
- if (!skipCallbackInvocation) {
- _invokeCallback();
- _response.responseBody = '';
- _response.messages = [];
- }
- };
-
- _sse.onerror = function (message) {
- clearTimeout(_request.id);
-
- if (_response.closedByClientTimeout) return;
-
- _invokeClose(sseOpened);
- _clearState();
-
- if (_abordingConnection) {
- jQuery.atmosphere.log(_request.logLevel, ["SSE closed normally"]);
- } else if (!sseOpened) {
- _reconnectWithFallbackTransport("SSE failed. Downgrading to fallback transport and resending");
- } else if (_request.reconnect && (_response.transport === 'sse')) {
- if (_requestCount++ < _request.maxReconnectOnClose) {
- _open('re-connecting', _request.transport, _request);
- if (_request.reconnectInterval > 0) {
- _request.id = setTimeout(function () {
- _executeSSE(true);
- }, _request.reconnectInterval);
- } else {
- _executeSSE(true);
- }
- _response.responseBody = "";
- _response.messages = [];
- } else {
- jQuery.atmosphere.log(_request.logLevel, ["SSE reconnect maximum try reached " + _requestCount]);
- _onError(0, "maxReconnectOnClose reached");
- }
- }
- };
- }
-
- /**
- * Open web socket.
- * Automatically use fallback transport if web socket can't be opened.
- *
- * @private
- */
- function _executeWebSocket(webSocketOpened) {
-
- _response.transport = "websocket";
-
- if (_request.enableProtocol && webSocketOpened) {
- var time = jQuery.now() - _request.ctime;
- _request.lastTimestamp = Number(_request.stime) + Number(time);
- }
-
- var location = _buildWebSocketUrl(_request.url);
- if (_request.logLevel === 'debug') {
- jQuery.atmosphere.debug("Invoking executeWebSocket");
- jQuery.atmosphere.debug("Using URL: " + location);
- }
-
- if (webSocketOpened && !_request.reconnect) {
- if (_websocket != null) {
- _clearState();
- }
- return;
- }
-
- _websocket = _getWebSocket(location);
- if (_request.webSocketBinaryType != null) {
- _websocket.binaryType = _request.webSocketBinaryType;
- }
-
- if (_request.connectTimeout > 0) {
- _request.id = setTimeout(function () {
- if (!webSocketOpened) {
- var _message = {
- code: 1002,
- reason: "",
- wasClean: false
- };
- _websocket.onclose(_message);
- // Close it anyway
- try {
- _clearState();
- } catch (e) {
- }
- return;
- }
-
- }, _request.connectTimeout);
- }
-
- _websocket.onopen = function (message) {
- _timeout(_request);
- if (_request.logLevel === 'debug') {
- jQuery.atmosphere.debug("Websocket successfully opened");
- }
-
- if (!_request.enableProtocol) {
- if (!webSocketOpened) {
- _open('opening', "websocket", _request);
- } else {
- _open('re-opening', "websocket", _request);
- }
- }
-
- webSocketOpened = true;
- if (_websocket != null) {
- _websocket.webSocketOpened = webSocketOpened;
-
- if (_request.method === 'POST') {
- _response.state = "messageReceived";
- _websocket.send(_request.data);
- }
- }
- };
-
- _websocket.onmessage = function (message) {
- _timeout(_request);
-
- _response.state = 'messageReceived';
- _response.status = 200;
-
- message = message.data;
- var isString = typeof (message) === 'string';
- if (isString) {
- var skipCallbackInvocation = _trackMessageSize(message, _request, _response);
- if (!skipCallbackInvocation) {
- _invokeCallback();
- _response.responseBody = '';
- _response.messages = [];
- }
- } else {
- if (!_handleProtocol(_request, message))
- return;
-
- _response.responseBody = message;
- _invokeCallback();
- _response.responseBody = null;
- }
- };
-
- _websocket.onerror = function (message) {
- clearTimeout(_request.id);
- };
-
- _websocket.onclose = function (message) {
- if (_response.state === 'closed')
- return;
- clearTimeout(_request.id);
-
- var reason = message.reason;
- if (reason === "") {
- switch (message.code) {
- case 1000:
- reason = "Normal closure; the connection successfully completed whatever purpose for which " + "it was created.";
- break;
- case 1001:
- reason = "The endpoint is going away, either because of a server failure or because the "
- + "browser is navigating away from the page that opened the connection.";
- break;
- case 1002:
- reason = "The endpoint is terminating the connection due to a protocol error.";
- break;
- case 1003:
- reason = "The connection is being terminated because the endpoint received data of a type it "
- + "cannot accept (for example, a text-only endpoint received binary data).";
- break;
- case 1004:
- reason = "The endpoint is terminating the connection because a data frame was received that " + "is too large.";
- break;
- case 1005:
- reason = "Unknown: no status code was provided even though one was expected.";
- break;
- case 1006:
- reason = "Connection was closed abnormally (that is, with no close frame being sent).";
- break;
- }
- }
-
- if (_request.logLevel === 'warn') {
- jQuery.atmosphere.warn("Websocket closed, reason: " + reason);
- jQuery.atmosphere.warn("Websocket closed, wasClean: " + message.wasClean);
- }
-
- if (_response.closedByClientTimeout) {
- return;
- }
-
- _invokeClose(webSocketOpened);
-
- _response.state = 'closed';
-
- if (_abordingConnection) {
- jQuery.atmosphere.log(_request.logLevel, ["Websocket closed normally"]);
- } else if (!webSocketOpened) {
- _reconnectWithFallbackTransport("Websocket failed. Downgrading to Comet and resending");
-
- } else if (_request.reconnect && _response.transport === 'websocket') {
- _clearState();
- if (_requestCount++ < _request.maxReconnectOnClose) {
- _open('re-connecting', _request.transport, _request);
- if (_request.reconnectInterval > 0) {
- _request.id = setTimeout(function () {
- _response.responseBody = "";
- _response.messages = [];
- _executeWebSocket(true);
- }, _request.reconnectInterval);
- } else {
- _response.responseBody = "";
- _response.messages = [];
- _executeWebSocket(true);
- }
- } else {
- jQuery.atmosphere.log(_request.logLevel, ["Websocket reconnect maximum try reached " + _request.requestCount]);
- if (_request.logLevel === 'warn') {
- jQuery.atmosphere.warn("Websocket error, reason: " + message.reason);
- }
- _onError(0, "maxReconnectOnClose reached");
- }
- }
- };
-
- if (_websocket.url === undefined) {
- // Android 4.1 does not really support websockets and fails silently
- _websocket.onclose({
- reason: "Android 4.1 does not support websockets.",
- wasClean: false
- });
- }
- }
-
- function _handleProtocol(request, message) {
- // The first messages is always the uuid.
- var b = true;
- if (jQuery.trim(message).length !== 0 && request.enableProtocol && request.firstMessage) {
- request.firstMessage = false;
- var messages = message.split(request.messageDelimiter);
- var pos = messages.length === 2 ? 0 : 1;
- request.uuid = jQuery.trim(messages[pos]);
- request.stime = jQuery.trim(messages[pos + 1]);
- b = false;
- if (request.transport !== 'long-polling') {
- _triggerOpen(request);
- }
-
- jQuery.atmosphere.uuid = request.uuid;
- } else if (request.enableProtocol && request.firstMessage) {
- // In case we are getting some junk from IE
- b = false;
- } else {
- _triggerOpen(request);
- }
- return b;
- }
-
- function _timeout(_request) {
- clearTimeout(_request.id);
- if (_request.timeout > 0 && _request.transport !== 'polling') {
- _request.id = setTimeout(function () {
- _onClientTimeout(_request);
- _disconnect();
- _clearState();
- }, _request.timeout);
- }
- }
-
- function _onClientTimeout(_request) {
- _response.closedByClientTimeout = true;
- _response.state = 'closedByClient';
- _response.responseBody = "";
- _response.status = 408;
- _response.messages = [];
- _invokeCallback();
- }
-
- function _onError(code, reason) {
- _clearState();
- clearTimeout(_request.id);
- _response.state = 'error';
- _response.reasonPhrase = reason;
- _response.responseBody = "";
- _response.status = code;
- _response.messages = [];
- _invokeCallback();
- }
-
- /**
- * Track received message and make sure callbacks/functions are only invoked when the complete message has been received.
- *
- * @param message
- * @param request
- * @param response
- */
- function _trackMessageSize(message, request, response) {
- if (!_handleProtocol(_request, message))
- return true;
- if (message.length === 0)
- return true;
-
- if (request.trackMessageLength) {
- // prepend partialMessage if any
- message = response.partialMessage + message;
-
- var messages = [];
- var messageStart = message.indexOf(request.messageDelimiter);
- while (messageStart !== -1) {
- var str = jQuery.trim(message.substring(0, messageStart));
- var messageLength = parseInt(str, 10);
- if (isNaN(messageLength))
- throw 'message length "' + str + '" is not a number';
- messageStart += request.messageDelimiter.length;
- if (messageStart + messageLength > message.length) {
- // message not complete, so there is no trailing messageDelimiter
- messageStart = -1;
- } else {
- // message complete, so add it
- messages.push(message.substring(messageStart, messageStart + messageLength));
- // remove consumed characters
- message = message.substring(messageStart + messageLength, message.length);
- messageStart = message.indexOf(request.messageDelimiter);
- }
- }
-
- /* keep any remaining data */
- response.partialMessage = message;
-
- if (messages.length !== 0) {
- response.responseBody = messages.join(request.messageDelimiter);
- response.messages = messages;
- return false;
- } else {
- response.responseBody = "";
- response.messages = [];
- return true;
- }
- } else {
- response.responseBody = message;
- }
- return false;
- }
-
- /**
- * Reconnect request with fallback transport.
- * Used in case websocket can't be opened.
- *
- * @private
- */
- function _reconnectWithFallbackTransport(errorMessage) {
- jQuery.atmosphere.log(_request.logLevel, [errorMessage]);
-
- if (typeof (_request.onTransportFailure) !== 'undefined') {
- _request.onTransportFailure(errorMessage, _request);
- } else if (typeof (jQuery.atmosphere.onTransportFailure) !== 'undefined') {
- jQuery.atmosphere.onTransportFailure(errorMessage, _request);
- }
-
- _request.transport = _request.fallbackTransport;
- var reconnectInterval = _request.connectTimeout === -1 ? 0 : _request.connectTimeout;
- if (_request.reconnect && _request.transport !== 'none' || _request.transport == null) {
- _request.method = _request.fallbackMethod;
- _response.transport = _request.fallbackTransport;
- _request.fallbackTransport = 'none';
- if (reconnectInterval > 0) {
- _request.id = setTimeout(function () {
- _execute();
- }, reconnectInterval);
- } else {
- _execute();
- }
- } else {
- _onError(500, "Unable to reconnect with fallback transport");
- }
- }
-
- /**
- * Get url from request and attach headers to it.
- *
- * @param request {Object} request Request parameters, if undefined _request object will be used.
- *
- * @returns {Object} Request object, if undefined, _request object will be used.
- * @private
- */
- function _attachHeaders(request, url) {
- var rq = _request;
- if ((request != null) && (typeof (request) !== 'undefined')) {
- rq = request;
- }
-
- if (url == null) {
- url = rq.url;
- }
-
- // If not enabled
- if (!rq.attachHeadersAsQueryString)
- return url;
-
- // If already added
- if (url.indexOf("X-Atmosphere-Framework") !== -1) {
- return url;
- }
-
- url += (url.indexOf('?') !== -1) ? '&' : '?';
- url += "X-Atmosphere-tracking-id=" + rq.uuid;
- url += "&X-Atmosphere-Framework=" + jQuery.atmosphere.version;
- url += "&X-Atmosphere-Transport=" + rq.transport;
-
- if (rq.trackMessageLength) {
- url += "&X-Atmosphere-TrackMessageSize=" + "true";
- }
-
- if (rq.lastTimestamp != null) {
- url += "&X-Cache-Date=" + rq.lastTimestamp;
- } else {
- url += "&X-Cache-Date=" + 0;
- }
-
- if (rq.contentType !== '') {
- url += "&Content-Type=" + rq.contentType;
- }
-
- if (rq.enableProtocol) {
- url += "&X-atmo-protocol=true";
- }
-
- jQuery.each(rq.headers, function (name, value) {
- var h = jQuery.isFunction(value) ? value.call(this, rq, request, _response) : value;
- if (h != null) {
- url += "&" + encodeURIComponent(name) + "=" + encodeURIComponent(h);
- }
- });
-
- return url;
- }
-
- function _triggerOpen(rq) {
- if (!rq.isOpen) {
- rq.isOpen = true;
- _open('opening', rq.transport, rq);
- } else if (rq.isReopen) {
- rq.isReopen = false;
- _open('re-opening', rq.transport, rq);
- }
- }
-
- /**
- * Execute ajax request.
- *
- * @param request {Object} request Request parameters, if undefined _request object will be used.
- * @private
- */
- function _executeRequest(request) {
- var rq = _request;
- if ((request != null) || (typeof (request) !== 'undefined')) {
- rq = request;
- }
-
- rq.lastIndex = 0;
- rq.readyState = 0;
-
- // CORS fake using JSONP
- if ((rq.transport === 'jsonp') || ((rq.enableXDR) && (jQuery.atmosphere.checkCORSSupport()))) {
- _jsonp(rq);
- return;
- }
-
- if (rq.transport === 'ajax') {
- _ajax(request);
- return;
- }
-
- if (jQuery.browser.msie && jQuery.browser.version < 10) {
- if ((rq.transport === 'streaming')) {
- if (rq.enableXDR && window.XDomainRequest) {
- _ieXDR(rq);
- } else {
- _ieStreaming(rq);
- }
- return;
- }
-
- if ((rq.enableXDR) && (window.XDomainRequest)) {
- _ieXDR(rq);
- return;
- }
- }
-
- var reconnectF = function () {
- rq.lastIndex = 0;
- if (rq.reconnect && _requestCount++ < rq.maxReconnectOnClose) {
- _open('re-connecting', request.transport, request);
- _reconnect(ajaxRequest, rq, request.reconnectInterval);
- } else {
- _onError(0, "maxReconnectOnClose reached");
- }
- };
-
- if (rq.reconnect && (rq.maxRequest === -1 || rq.requestCount++ < rq.maxRequest)) {
- var ajaxRequest = jQuery.ajaxSettings.xhr();
- ajaxRequest.hasData = false;
-
- _doRequest(ajaxRequest, rq, true);
-
- if (rq.suspend) {
- _activeRequest = ajaxRequest;
- }
-
- if (rq.transport !== 'polling') {
- _response.transport = rq.transport;
-
- ajaxRequest.onabort = function () {
- _invokeClose(true);
- };
-
- ajaxRequest.onerror = function () {
- _response.error = true;
- try {
- _response.status = XMLHttpRequest.status;
- } catch (e) {
- _response.status = 500;
- }
-
- if (!_response.status) {
- _response.status = 500;
- }
- _clearState();
- if (!_response.errorHandled) {
- reconnectF();
- }
- };
- }
-
- ajaxRequest.onreadystatechange = function () {
- if (_abordingConnection) {
- return;
- }
-
- _response.error = null;
- var skipCallbackInvocation = false;
- var update = false;
-
- if (rq.transport === 'streaming' && rq.readyState > 2 && ajaxRequest.readyState === 4) {
- _clearState();
- reconnectF();
- return;
- }
-
- rq.readyState = ajaxRequest.readyState;
-
- if (rq.transport === 'streaming' && ajaxRequest.readyState >= 3) {
- update = true;
- } else if (rq.transport === 'long-polling' && ajaxRequest.readyState === 4) {
- update = true;
- }
- _timeout(_request);
-
- if (rq.transport !== 'polling') {
- if ((!rq.enableProtocol || !request.firstMessage) && ajaxRequest.readyState === 2) {
- _triggerOpen(rq);
- }
- // MSIE 9 and lower status can be higher than 1000, Chrome can be 0
- var status = 200;
- if (ajaxRequest.readyState > 1) {
- status = ajaxRequest.status > 1000 ? 0 : ajaxRequest.status;
- }
-
- if (status >= 300 || status === 0) {
- // Prevent onerror callback to be called
- _response.errorHandled = true;
- _clearState();
- reconnectF();
- return;
- }
- } else if (ajaxRequest.readyState === 4) {
- update = true;
- }
-
- if (update) {
- var responseText = ajaxRequest.responseText;
-
- if (jQuery.trim(responseText.length).length === 0 && rq.transport === 'long-polling') {
- // For browser that aren't support onabort
- if (!ajaxRequest.hasData) {
- reconnectF();
- } else {
- ajaxRequest.hasData = false;
- }
- return;
- }
- ajaxRequest.hasData = true;
-
- _readHeaders(ajaxRequest, _request);
-
- if (rq.transport === 'streaming') {
- if (!jQuery.browser.opera) {
- var message = responseText.substring(rq.lastIndex, responseText.length);
- skipCallbackInvocation = _trackMessageSize(message, rq, _response);
-
- rq.lastIndex = responseText.length;
- if (skipCallbackInvocation) {
- return;
- }
- } else {
- jQuery.atmosphere.iterate(function () {
- if (_response.status !== 500 && ajaxRequest.responseText.length > rq.lastIndex) {
- try {
- _response.status = ajaxRequest.status;
- _response.headers = parseHeaders(ajaxRequest.getAllResponseHeaders());
-
- _readHeaders(ajaxRequest, _request);
-
- } catch (e) {
- _response.status = 404;
- }
- _timeout(_request);
-
- _response.state = "messageReceived";
- var message = ajaxRequest.responseText.substring(rq.lastIndex);
- rq.lastIndex = ajaxRequest.responseText.length;
-
- skipCallbackInvocation = _trackMessageSize(message, rq, _response);
- if (!skipCallbackInvocation) {
- _invokeCallback();
- }
-
- _verifyStreamingLength(ajaxRequest, rq);
- } else if (_response.status > 400) {
- // Prevent replaying the last message.
- rq.lastIndex = ajaxRequest.responseText.length;
- return false;
- }
- }, 0);
- }
- } else {
- skipCallbackInvocation = _trackMessageSize(responseText, rq, _response);
- }
-
- try {
- _response.status = ajaxRequest.status;
- _response.headers = parseHeaders(ajaxRequest.getAllResponseHeaders());
-
- _readHeaders(ajaxRequest, rq);
- } catch (e) {
- _response.status = 404;
- }
-
- if (rq.suspend) {
- _response.state = _response.status === 0 ? "closed" : "messageReceived";
- } else {
- _response.state = "messagePublished";
- }
-
- var isAllowedToReconnect = request.transport !== 'streaming' && request.transport !== 'polling';;
- if (isAllowedToReconnect && !rq.executeCallbackBeforeReconnect) {
- _reconnect(ajaxRequest, rq, 0);
- }
-
- if (_response.responseBody.length !== 0 && !skipCallbackInvocation)
- _invokeCallback();
-
- if (isAllowedToReconnect && rq.executeCallbackBeforeReconnect) {
- _reconnect(ajaxRequest, rq, 0);
- }
-
- _verifyStreamingLength(ajaxRequest, rq);
- }
- };
-
- ajaxRequest.send(rq.data);
- _subscribed = true;
- } else {
- if (rq.logLevel === 'debug') {
- jQuery.atmosphere.log(rq.logLevel, ["Max re-connection reached."]);
- }
- _onError(0, "maxRequest reached");
- }
- }
-
- /**
- * Do ajax request.
- *
- * @param ajaxRequest Ajax request.
- * @param request Request parameters.
- * @param create If ajax request has to be open.
- */
- function _doRequest(ajaxRequest, request, create) {
- // Prevent Android to cache request
- var url = request.url;
- if (request.dispatchUrl != null && request.method === 'POST') {
- url += request.dispatchUrl;
- }
- url = _attachHeaders(request, url);
- url = jQuery.atmosphere.prepareURL(url);
-
- if (create) {
- ajaxRequest.open(request.method, url, true);
- if (request.connectTimeout > 0) {
- request.id = setTimeout(function () {
- if (request.requestCount === 0) {
- _clearState();
- _prepareCallback("Connect timeout", "closed", 200, request.transport);
- }
- }, request.connectTimeout);
- }
- }
-
- if (_request.withCredentials) {
- if ("withCredentials" in ajaxRequest) {
- ajaxRequest.withCredentials = true;
- }
- }
-
- if (!_request.dropAtmosphereHeaders) {
- ajaxRequest.setRequestHeader("X-Atmosphere-Framework", jQuery.atmosphere.version);
- ajaxRequest.setRequestHeader("X-Atmosphere-Transport", request.transport);
- if (request.lastTimestamp != null) {
- ajaxRequest.setRequestHeader("X-Cache-Date", request.lastTimestamp);
- } else {
- ajaxRequest.setRequestHeader("X-Cache-Date", 0);
- }
-
- if (request.trackMessageLength) {
- ajaxRequest.setRequestHeader("X-Atmosphere-TrackMessageSize", "true");
- }
- ajaxRequest.setRequestHeader("X-Atmosphere-tracking-id", request.uuid);
- }
-
- if (request.contentType !== '') {
- ajaxRequest.setRequestHeader("Content-Type", request.contentType);
- }
-
- jQuery.each(request.headers, function (name, value) {
- var h = jQuery.isFunction(value) ? value.call(this, ajaxRequest, request, create, _response) : value;
- if (h != null) {
- ajaxRequest.setRequestHeader(name, h);
- }
- });
- }
-
- function _reconnect(ajaxRequest, request, reconnectInterval) {
- if (request.reconnect || (request.suspend && _subscribed)) {
- var status = 0;
- if (ajaxRequest.readyState !== 0) {
- status = ajaxRequest.status > 1000 ? 0 : ajaxRequest.status;
- }
- _response.status = status === 0 ? 204 : status;
- _response.reason = status === 0 ? "Server resumed the connection or down." : "OK";
-
- // Reconnect immedialtely
- clearTimeout(request.id);
- if (reconnectInterval > 0) {
- request.id = setTimeout(function () {
- _executeRequest(request);
- }, reconnectInterval);
- } else {
- _executeRequest(request);
- }
- }
- }
-
- function _tryingToReconnect(response) {
- response.state = 're-connecting';
- _invokeFunction(response);
- }
-
- // From jquery-stream, which is APL2 licensed as well.
- function _ieXDR(request) {
- if (request.transport !== "polling") {
- _ieStream = _configureXDR(request);
- _ieStream.open();
- } else {
- _configureXDR(request).open();
- }
- }
-
- function _configureXDR(request) {
- var rq = _request;
- if ((request != null) && (typeof (request) !== 'undefined')) {
- rq = request;
- }
-
- var transport = rq.transport;
- var lastIndex = 0;
- var xdr = new window.XDomainRequest();
-
- var reconnect = function () {
- if (rq.transport === "long-polling" && (rq.reconnect && (rq.maxRequest === -1 || rq.requestCount++ < rq.maxRequest))) {
- xdr.status = 200;
- _ieXDR(rq);
- }
- };
-
- var rewriteURL = rq.rewriteURL || function (url) {
- // Maintaining session by rewriting URL
- // http://stackoverflow.com/questions/6453779/maintaining-session-by-rewriting-url
- var match = /(?:^|;\s*)(JSESSIONID|PHPSESSID)=([^;]*)/.exec(document.cookie);
-
- switch (match && match[1]) {
- case "JSESSIONID":
- return url.replace(/;jsessionid=[^\?]*|(\?)|$/, ";jsessionid=" + match[2] + "$1");
- case "PHPSESSID":
- return url.replace(/\?PHPSESSID=[^&]*&?|\?|$/, "?PHPSESSID=" + match[2] + "&").replace(/&$/, "");
- }
- return url;
- };
-
- // Handles open and message event
- xdr.onprogress = function () {
- handle(xdr);
- };
- // Handles error event
- xdr.onerror = function () {
- // If the server doesn't send anything back to XDR will fail with polling
- if (rq.transport !== 'polling') {
- _clearState();
- if (_requestCount++ < rq.maxReconnectOnClose) {
- if (rq.reconnectInterval > 0) {
- rq.id = setTimeout(function () {
- _open('re-connecting', request.transport, request);
- _ieXDR(rq);
- }, rq.reconnectInterval);
- } else {
- _open('re-connecting', request.transport, request);
- _ieXDR(rq);
- }
- } else {
- _onError(0, "maxReconnectOnClose reached");
- }
- }
- };
-
- // Handles close event
- xdr.onload = function () {
- };
-
- var handle = function (xdr) {
- clearTimeout(rq.id);
- var message = xdr.responseText;
-
- message = message.substring(lastIndex);
- lastIndex += message.length;
-
- if (transport !== 'polling') {
- _timeout(rq);
-
- var skipCallbackInvocation = _trackMessageSize(message, rq, _response);
-
- if (transport === 'long-polling' && jQuery.trim(message).length === 0)
- return;
-
- if (rq.executeCallbackBeforeReconnect) {
- reconnect();
- }
-
- if (!skipCallbackInvocation) {
- _prepareCallback(_response.responseBody, "messageReceived", 200, transport);
- }
-
- if (!rq.executeCallbackBeforeReconnect) {
- reconnect();
- }
- }
- };
-
- return {
- open: function () {
- var url = rq.url;
- if (rq.dispatchUrl != null) {
- url += rq.dispatchUrl;
- }
- url = _attachHeaders(rq, url);
- xdr.open(rq.method, rewriteURL(url));
- if (rq.method === 'GET') {
- xdr.send();
- } else {
- xdr.send(rq.data);
- }
-
- if (rq.connectTimeout > 0) {
- rq.id = setTimeout(function () {
- if (rq.requestCount === 0) {
- _clearState();
- _prepareCallback("Connect timeout", "closed", 200, rq.transport);
- }
- }, rq.connectTimeout);
- }
- },
- close: function () {
- xdr.abort();
- }
- };
- }
-
- function _ieStreaming(request) {
- _ieStream = _configureIE(request);
- _ieStream.open();
- }
-
- function _configureIE(request) {
- var rq = _request;
- if ((request != null) && (typeof (request) !== 'undefined')) {
- rq = request;
- }
-
- var stop;
- var doc = new window.ActiveXObject("htmlfile");
-
- doc.open();
- doc.close();
-
- var url = rq.url;
- if (rq.dispatchUrl != null) {
- url += rq.dispatchUrl;
- }
-
- if (rq.transport !== 'polling') {
- _response.transport = rq.transport;
- }
-
- return {
- open: function () {
- var iframe = doc.createElement("iframe");
-
- url = _attachHeaders(rq);
- if (rq.data !== '') {
- url += "&X-Atmosphere-Post-Body=" + encodeURIComponent(rq.data);
- }
-
- // Finally attach a timestamp to prevent Android and IE caching.
- url = jQuery.atmosphere.prepareURL(url);
-
- iframe.src = url;
- doc.body.appendChild(iframe);
-
- // For the server to respond in a consistent format regardless of user agent, we polls response text
- var cdoc = iframe.contentDocument || iframe.contentWindow.document;
-
- stop = jQuery.atmosphere.iterate(function () {
- try {
- if (!cdoc.firstChild) {
- return;
- }
-
- // Detects connection failure
- if (cdoc.readyState === "complete") {
- try {
- jQuery.noop(cdoc.fileSize);
- } catch (e) {
- _prepareCallback("Connection Failure", "error", 500, rq.transport);
- return false;
- }
- }
-
- var res = cdoc.body ? cdoc.body.lastChild : cdoc;
- var readResponse = function () {
- // Clones the element not to disturb the original one
- var clone = res.cloneNode(true);
-
- // If the last character is a carriage return or a line feed, IE ignores it in the innerText property
- // therefore, we add another non-newline character to preserve it
- clone.appendChild(cdoc.createTextNode("."));
-
- var text = clone.innerText;
-
- text = text.substring(0, text.length - 1);
- return text;
-
- };
-
- // To support text/html content type
- if (!jQuery.nodeName(res, "pre")) {
- // Injects a plaintext element which renders text without interpreting the HTML and cannot be stopped
- // it is deprecated in HTML5, but still works
- var head = cdoc.head || cdoc.getElementsByTagName("head")[0] || cdoc.documentElement || cdoc;
- var script = cdoc.createElement("script");
-
- script.text = "document.write('')";
-
- head.insertBefore(script, head.firstChild);
- head.removeChild(script);
-
- // The plaintext element will be the response container
- res = cdoc.body.lastChild;
- }
-
- if (rq.closed) {
- rq.isReopen = true;
- }
-
- // Handles message and close event
- stop = jQuery.atmosphere.iterate(function () {
- var text = readResponse();
- if (text.length > rq.lastIndex) {
- _timeout(_request);
-
- _response.status = 200;
- _response.error = null;
-
- // Empties response every time that it is handled
- res.innerText = "";
- var skipCallbackInvocation = _trackMessageSize(text, rq, _response);
- if (skipCallbackInvocation) {
- return "";
- }
-
- _prepareCallback(_response.responseBody, "messageReceived", 200, rq.transport);
- }
-
- rq.lastIndex = 0;
-
- if (cdoc.readyState === "complete") {
- _invokeClose(true);
- _open('re-connecting', rq.transport, rq);
- if (rq.reconnectInterval > 0) {
- rq.id = setTimeout(function () {
- _ieStreaming(rq);
- }, rq.reconnectInterval);
- } else {
- _ieStreaming(rq);
- }
- return false;
- }
- }, null);
-
- return false;
- } catch (err) {
- _response.error = true;
- _open('re-connecting', rq.transport, rq);
- if (_requestCount++ < rq.maxReconnectOnClose) {
- if (rq.reconnectInterval > 0) {
- rq.id = setTimeout(function () {
- _ieStreaming(rq);
- }, rq.reconnectInterval);
- } else {
- _ieStreaming(rq);
- }
- } else {
- _onError(0, "maxReconnectOnClose reached");
- }
- doc.execCommand("Stop");
- doc.close();
- return false;
- }
- });
- },
-
- close: function () {
- if (stop) {
- stop();
- }
-
- doc.execCommand("Stop");
- _invokeClose(true);
- }
- };
- }
-
- /**
- * Send message.
- * Will be automatically dispatch to other connected.
- *
- * @param {Object, string} Message to send.
- * @private
- */
- function _push(message) {
-
- if (_localStorageService != null) {
- _pushLocal(message);
- } else if (_activeRequest != null || _sse != null) {
- _pushAjaxMessage(message);
- } else if (_ieStream != null) {
- _pushIE(message);
- } else if (_jqxhr != null) {
- _pushJsonp(message);
- } else if (_websocket != null) {
- _pushWebSocket(message);
- } else {
- _onError(0, "No suspended connection available");
- jQuery.atmosphere.error("No suspended connection available. Make sure atmosphere.subscribe has been called and request.onOpen invoked before invoking this method");
- }
- }
-
- function _pushOnClose(message) {
- var rq = _getPushRequest(message);
- rq.transport = "ajax";
- rq.method = "GET";
- rq.async = false;
- rq.reconnect = false;
- _executeRequest(rq);
- }
-
- function _pushLocal(message) {
- _localStorageService.send(message);
- }
-
- function _intraPush(message) {
- // IE 9 will crash if not.
- if (message.length === 0)
- return;
-
- try {
- if (_localStorageService) {
- _localStorageService.localSend(message);
- } else if (_storageService) {
- _storageService.signal("localMessage", jQuery.stringifyJSON({
- id: guid,
- event: message
- }));
- }
- } catch (err) {
- jQuery.atmosphere.error(err);
- }
- }
-
- /**
- * Send a message using currently opened ajax request (using http-streaming or long-polling).
- *
- * @param {string, Object} Message to send. This is an object, string message is saved in data member.
- * @private
- */
- function _pushAjaxMessage(message) {
- var rq = _getPushRequest(message);
- _executeRequest(rq);
- }
-
- /**
- * Send a message using currently opened ie streaming (using http-streaming or long-polling).
- *
- * @param {string, Object} Message to send. This is an object, string message is saved in data member.
- * @private
- */
- function _pushIE(message) {
- if (_request.enableXDR && jQuery.atmosphere.checkCORSSupport()) {
- var rq = _getPushRequest(message);
- // Do not reconnect since we are pushing.
- rq.reconnect = false;
- _jsonp(rq);
- } else {
- _pushAjaxMessage(message);
- }
- }
-
- /**
- * Send a message using jsonp transport.
- *
- * @param {string, Object} Message to send. This is an object, string message is saved in data member.
- * @private
- */
- function _pushJsonp(message) {
- _pushAjaxMessage(message);
- }
-
- function _getStringMessage(message) {
- var msg = message;
- if (typeof (msg) === 'object') {
- msg = message.data;
- }
- return msg;
- }
-
- /**
- * Build request use to push message using method 'POST'
. Transport is defined as 'polling' and 'suspend' is set to false.
- *
- * @return {Object} Request object use to push message.
- * @private
- */
- function _getPushRequest(message) {
- var msg = _getStringMessage(message);
-
- var rq = {
- connected: false,
- timeout: 60000,
- method: 'POST',
- url: _request.url,
- contentType: _request.contentType,
- headers: _request.headers,
- reconnect: true,
- callback: null,
- data: msg,
- suspend: false,
- maxRequest: -1,
- logLevel: 'info',
- requestCount: 0,
- withCredentials: _request.withCredentials,
- transport: 'polling',
- isOpen: true,
- attachHeadersAsQueryString: true,
- enableXDR: _request.enableXDR,
- uuid: _request.uuid,
- dispatchUrl: _request.dispatchUrl,
- enableProtocol: false,
- messageDelimiter: '|',
- maxReconnectOnClose: _request.maxReconnectOnClose
- };
-
- if (typeof (message) === 'object') {
- rq = jQuery.extend(rq, message);
- }
-
- return rq;
- }
-
- /**
- * Send a message using currently opened websocket.
- *
- */
- function _pushWebSocket(message) {
- var msg = jQuery.atmosphere.isBinary(message) ? message : _getStringMessage(message);
- var data;
- try {
- if (_request.dispatchUrl != null) {
- data = _request.webSocketPathDelimiter + _request.dispatchUrl + _request.webSocketPathDelimiter + msg;
- } else {
- data = msg;
- }
-
- if (!_websocket.webSocketOpened) {
- jQuery.atmosphere.error("WebSocket not connected.");
- return;
- }
-
- _websocket.send(data);
-
- } catch (e) {
- _websocket.onclose = function (message) {
- };
- _clearState();
-
- _reconnectWithFallbackTransport("Websocket failed. Downgrading to Comet and resending " + data);
- _pushAjaxMessage(message);
- }
- }
-
- function _localMessage(message) {
- var m = jQuery.parseJSON(message);
- if (m.id !== guid) {
- if (typeof (_request.onLocalMessage) !== 'undefined') {
- _request.onLocalMessage(m.event);
- } else if (typeof (jQuery.atmosphere.onLocalMessage) !== 'undefined') {
- jQuery.atmosphere.onLocalMessage(m.event);
- }
- }
- }
-
- function _prepareCallback(messageBody, state, errorCode, transport) {
-
- _response.responseBody = messageBody;
- _response.transport = transport;
- _response.status = errorCode;
- _response.state = state;
-
- _invokeCallback();
- }
-
- function _readHeaders(xdr, request) {
- if (!request.readResponsesHeaders && !request.enableProtocol) {
- request.lastTimestamp = jQuery.now();
- request.uuid = jQuery.atmosphere.guid();
- return;
- }
-
- try {
- var tempDate = xdr.getResponseHeader('X-Cache-Date');
- if (tempDate && tempDate != null && tempDate.length > 0) {
- request.lastTimestamp = tempDate.split(" ").pop();
- }
-
- var tempUUID = xdr.getResponseHeader('X-Atmosphere-tracking-id');
- if (tempUUID && tempUUID != null) {
- request.uuid = tempUUID.split(" ").pop();
- }
-
- // HOTFIX for firefox bug: https://bugzilla.mozilla.org/show_bug.cgi?id=608735
- if (request.headers) {
- jQuery.each(_request.headers, function (name) {
- var v = xdr.getResponseHeader(name);
- if (v) {
- _response.headers[name] = v;
- }
- });
- }
- } catch (e) {
- }
- }
-
- function _invokeFunction(response) {
- _f(response, _request);
- // Global
- _f(response, jQuery.atmosphere);
- }
-
- function _f(response, f) {
- switch (response.state) {
- case "messageReceived":
- _requestCount = 0;
- if (typeof (f.onMessage) !== 'undefined')
- f.onMessage(response);
- break;
- case "error":
- if (typeof (f.onError) !== 'undefined')
- f.onError(response);
- break;
- case "opening":
- if (typeof (f.onOpen) !== 'undefined')
- f.onOpen(response);
- break;
- case "messagePublished":
- if (typeof (f.onMessagePublished) !== 'undefined')
- f.onMessagePublished(response);
- break;
- case "re-connecting":
- if (typeof (f.onReconnect) !== 'undefined')
- f.onReconnect(_request, response);
- break;
- case "closedByClient":
- if (typeof (f.onClientTimeout) !== 'undefined')
- f.onClientTimeout(_request);
- break;
- case "re-opening":
- if (typeof (f.onReopen) !== 'undefined')
- f.onReopen(_request, response);
- break;
- case "fail-to-reconnect":
- if (typeof (f.onFailureToReconnect) !== 'undefined')
- f.onFailureToReconnect(_request, response);
- break;
- case "unsubscribe":
- case "closed":
- var closed = typeof (_request.closed) !== 'undefined' ? _request.closed : false;
- if (typeof (f.onClose) !== 'undefined' && !closed)
- f.onClose(response);
- _request.closed = true;
- break;
- }
- }
-
- function _invokeClose(wasOpen) {
- if (_response.state !== 'closed') {
- _response.state = 'closed';
- _response.responseBody = "";
- _response.messages = [];
- _response.status = !wasOpen ? 501 : 200;
- _invokeCallback();
- }
- }
-
- /**
- * Invoke request callbacks.
- *
- * @private
- */
- function _invokeCallback() {
- var call = function (index, func) {
- func(_response);
- };
-
- if (_localStorageService == null && _localSocketF != null) {
- _localSocketF(_response.responseBody);
- }
-
- _request.reconnect = _request.mrequest;
-
- var isString = typeof (_response.responseBody) === 'string';
- var messages = (isString && _request.trackMessageLength) ? (_response.messages.length > 0 ? _response.messages : ['']) : new Array(
- _response.responseBody);
- for (var i = 0; i < messages.length; i++) {
-
- if (messages.length > 1 && messages[i].length === 0) {
- continue;
- }
- _response.responseBody = (isString) ? jQuery.trim(messages[i]) : messages[i];
-
- if (_localStorageService == null && _localSocketF != null) {
- _localSocketF(_response.responseBody);
- }
-
- if (_response.responseBody.length === 0 && _response.state === "messageReceived") {
- continue;
- }
-
- _invokeFunction(_response);
-
- // Invoke global callbacks
- if (jQuery.atmosphere.callbacks.length > 0) {
- if (_request.logLevel === 'debug') {
- jQuery.atmosphere.debug("Invoking " + jQuery.atmosphere.callbacks.length + " global callbacks: " + _response.state);
- }
- try {
- jQuery.each(jQuery.atmosphere.callbacks, call);
- } catch (e) {
- jQuery.atmosphere.log(_request.logLevel, ["Callback exception" + e]);
- }
- }
-
- // Invoke request callback
- if (typeof (_request.callback) === 'function') {
- if (_request.logLevel === 'debug') {
- jQuery.atmosphere.debug("Invoking request callbacks");
- }
- try {
- _request.callback(_response);
- } catch (e) {
- jQuery.atmosphere.log(_request.logLevel, ["Callback exception" + e]);
- }
- }
- }
-
- }
-
- /**
- *
- * @private
- */
- function _verifyStreamingLength(ajaxRequest, rq) {
- // Wait to be sure we have the full message before closing.
- if (_response.partialMessage === "" && (rq.transport === 'streaming') && (ajaxRequest.responseText.length > rq.maxStreamingLength)) {
- _response.messages = [];
- _invokeClose(true);
- _disconnect();
- _clearState();
- _reconnect(ajaxRequest, rq, 0);
- }
- }
-
- /**
- * Disconnect
- *
- * @private
- */
- function _disconnect() {
- if (_request.enableProtocol && !_request.firstMessage) {
- var query = "X-Atmosphere-Transport=close&X-Atmosphere-tracking-id=" + _request.uuid;
-
- jQuery.each(_request.headers, function (name, value) {
- var h = jQuery.isFunction(value) ? value.call(this, _request, _request, _response) : value;
- if (h != null) {
- query += "&" + encodeURIComponent(name) + "=" + encodeURIComponent(h);
- }
- });
-
- var url = _request.url.replace(/([?&])_=[^&]*/, query);
- url = url + (url === _request.url ? (/\?/.test(_request.url) ? "&" : "?") + query : "");
-
- if (_request.connectTimeout > 0) {
- jQuery.ajax({
- url: url,
- async: false,
- timeout: _request.connectTimeout
- });
- } else {
- jQuery.ajax({
- url: url,
- async: false
- });
- }
- }
- }
-
- /**
- * Close request.
- *
- * @private
- */
- function _close() {
- _request.reconnect = false;
- _abordingConnection = true;
- _response.request = _request;
- _response.state = 'unsubscribe';
- _response.responseBody = "";
- _response.status = 408;
- _invokeCallback();
- _disconnect();
-
- _clearState();
- }
-
- function _clearState() {
- if (_request.id) {
- clearTimeout(_request.id);
- }
-
- if (_ieStream != null) {
- _ieStream.close();
- _ieStream = null;
- }
- if (_jqxhr != null) {
- _jqxhr.abort();
- _jqxhr = null;
- }
- if (_activeRequest != null) {
- _activeRequest.abort();
- _activeRequest = null;
- }
- if (_websocket != null) {
- if (_websocket.webSocketOpened) {
- _websocket.close();
- }
- _websocket = null;
- }
- if (_sse != null) {
- _sse.close();
- _sse = null;
- }
-
- _clearStorage();
- }
-
- function _clearStorage() {
- // Stop sharing a connection
- if (_storageService != null) {
- // Clears trace timer
- clearInterval(_traceTimer);
- // Removes the trace
- document.cookie = _sharingKey + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/";
- // The heir is the parent unless unloading
- _storageService.signal("close", {
- reason: "",
- heir: !_abordingConnection ? guid : (_storageService.get("children") || [])[0]
- });
- _storageService.close();
- }
- if (_localStorageService != null) {
- _localStorageService.close();
- }
- }
-
- this.subscribe = function (options) {
- _subscribe(options);
- _execute();
- };
-
- this.execute = function () {
- _execute();
- };
-
- this.invokeCallback = function () {
- _invokeCallback();
- };
-
- this.close = function () {
- _close();
- };
-
- this.disconnect = function () {
- _disconnect();
- };
-
- this.getUrl = function () {
- return _request.url;
- };
-
- this.push = function (message, dispatchUrl) {
- if (dispatchUrl != null) {
- var originalDispatchUrl = _request.dispatchUrl;
- _request.dispatchUrl = dispatchUrl;
- _push(message);
- _request.dispatchUrl = originalDispatchUrl;
- } else {
- _push(message);
- }
- };
-
- this.getUUID = function () {
- return _request.uuid;
- };
-
- this.pushLocal = function (message) {
- _intraPush(message);
- };
-
- this.enableProtocol = function (message) {
- return _request.enableProtocol;
- };
-
- this.request = _request;
- this.response = _response;
- },
-
- subscribe: function (url, callback, request) {
- if (typeof (callback) === 'function') {
- jQuery.atmosphere.addCallback(callback);
- }
-
- if (typeof (url) !== "string") {
- request = url;
- } else {
- request.url = url;
- }
-
- var rq = new jQuery.atmosphere.AtmosphereRequest(request);
- rq.execute();
-
- jQuery.atmosphere.requests[jQuery.atmosphere.requests.length] = rq;
- return rq;
- },
-
- addCallback: function (func) {
- if (jQuery.inArray(func, jQuery.atmosphere.callbacks) === -1) {
- jQuery.atmosphere.callbacks.push(func);
- }
- },
-
- removeCallback: function (func) {
- var index = jQuery.inArray(func, jQuery.atmosphere.callbacks);
- if (index !== -1) {
- jQuery.atmosphere.callbacks.splice(index, 1);
- }
- },
-
- unsubscribe: function () {
- if (jQuery.atmosphere.requests.length > 0) {
- var requestsClone = [].concat(jQuery.atmosphere.requests);
- for (var i = 0; i < requestsClone.length; i++) {
- var rq = requestsClone[i];
- rq.close();
- clearTimeout(rq.response.request.id);
- }
- }
- jQuery.atmosphere.requests = [];
- jQuery.atmosphere.callbacks = [];
- },
-
- unsubscribeUrl: function (url) {
- var idx = -1;
- if (jQuery.atmosphere.requests.length > 0) {
- for (var i = 0; i < jQuery.atmosphere.requests.length; i++) {
- var rq = jQuery.atmosphere.requests[i];
-
- // Suppose you can subscribe once to an url
- if (rq.getUrl() === url) {
- rq.close();
- clearTimeout(rq.response.request.id);
- idx = i;
- break;
- }
- }
- }
- if (idx >= 0) {
- jQuery.atmosphere.requests.splice(idx, 1);
- }
- },
-
- publish: function (request) {
- if (typeof (request.callback) === 'function') {
- jQuery.atmosphere.addCallback(request.callback);
- }
- request.transport = "polling";
-
- var rq = new jQuery.atmosphere.AtmosphereRequest(request);
- jQuery.atmosphere.requests[jQuery.atmosphere.requests.length] = rq;
- return rq;
- },
-
- checkCORSSupport: function () {
- if (jQuery.browser.msie && !window.XDomainRequest) {
- return true;
- } else if (jQuery.browser.opera && jQuery.browser.version < 12.0) {
- return true;
- }
-
- // KreaTV 4.1 -> 4.4
- else if (jQuery.trim(navigator.userAgent).slice(0, 16) === "KreaTVWebKit/531") {
- return true;
- }
- // KreaTV 3.8
- else if (jQuery.trim(navigator.userAgent).slice(-7).toLowerCase() === "kreatel") {
- return true;
- }
-
- // Force Android to use CORS as some version like 2.2.3 fail otherwise
- var ua = navigator.userAgent.toLowerCase();
- var isAndroid = ua.indexOf("android") > -1;
- if (isAndroid) {
- return true;
- }
- return false;
- },
-
- S4: function () {
- return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
- },
-
- guid: function () {
- return (jQuery.atmosphere.S4() + jQuery.atmosphere.S4() + "-" + jQuery.atmosphere.S4() + "-" + jQuery.atmosphere.S4() + "-"
- + jQuery.atmosphere.S4() + "-" + jQuery.atmosphere.S4() + jQuery.atmosphere.S4() + jQuery.atmosphere.S4());
- },
-
- // From jQuery-Stream
- prepareURL: function (url) {
- // Attaches a time stamp to prevent caching
- var ts = jQuery.now();
- var ret = url.replace(/([?&])_=[^&]*/, "$1_=" + ts);
-
- return ret + (ret === url ? (/\?/.test(url) ? "&" : "?") + "_=" + ts : "");
- },
-
- // From jQuery-Stream
- param: function (data) {
- return jQuery.param(data, jQuery.ajaxSettings.traditional);
- },
-
- supportStorage: function () {
- var storage = window.localStorage;
- if (storage) {
- try {
- storage.setItem("t", "t");
- storage.removeItem("t");
- // The storage event of Internet Explorer and Firefox 3 works strangely
- return window.StorageEvent && !jQuery.browser.msie && !(jQuery.browser.mozilla && jQuery.browser.version.split(".")[0] === "1");
- } catch (e) {
- }
- }
-
- return false;
- },
-
- iterate: function (fn, interval) {
- var timeoutId;
-
- // Though the interval is 0 for real-time application, there is a delay between setTimeout calls
- // For detail, see https://developer.mozilla.org/en/window.setTimeout#Minimum_delay_and_timeout_nesting
- interval = interval || 0;
-
- (function loop() {
- timeoutId = setTimeout(function () {
- if (fn() === false) {
- return;
- }
-
- loop();
- }, interval);
- })();
-
- return function () {
- clearTimeout(timeoutId);
- };
- },
-
- log: function (level, args) {
- if (window.console) {
- var logger = window.console[level];
- if (typeof logger === 'function') {
- logger.apply(window.console, args);
- }
- }
- },
-
- warn: function () {
- jQuery.atmosphere.log('warn', arguments);
- },
-
- info: function () {
- jQuery.atmosphere.log('info', arguments);
- },
-
- debug: function () {
- jQuery.atmosphere.log('debug', arguments);
- },
-
- error: function () {
- jQuery.atmosphere.log('error', arguments);
- },
-
- // TODO extract to utils or something
- isBinary: function (data) {
- var string = Object.prototype.toString.call(data);
- return string === "[object Blob]" || string === "[object ArrayBuffer]";
- }
- };
-}();
-
-// http://stackoverflow.com/questions/9645803/whats-the-replacement-for-browser
-// Limit scope pollution from any deprecated API
-(function () {
-
- var matched, browser;
-
- // Use of jQuery.browser is frowned upon.
- // More details: http://api.jquery.com/jQuery.browser
- // jQuery.uaMatch maintained for back-compat
- jQuery.uaMatch = function (ua) {
- ua = ua.toLowerCase();
-
- var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || /(webkit)[ \/]([\w.]+)/.exec(ua) || /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua)
- || /(msie) ([\w.]+)/.exec(ua) || ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || [];
-
- return {
- browser: match[1] || "",
- version: match[2] || "0"
- };
- };
-
- matched = jQuery.uaMatch(navigator.userAgent);
- browser = {};
-
- if (matched.browser) {
- browser[matched.browser] = true;
- browser.version = matched.version;
- }
-
- // Chrome is Webkit, but Webkit is also Safari.
- if (browser.chrome) {
- browser.webkit = true;
- } else if (browser.webkit) {
- browser.safari = true;
- }
-
- jQuery.browser = browser;
-
- jQuery.sub = function () {
- function jQuerySub(selector, context) {
- return new jQuerySub.fn.init(selector, context);
- }
-
- jQuery.extend(true, jQuerySub, this);
- jQuerySub.superclass = this;
- jQuerySub.fn = jQuerySub.prototype = this();
- jQuerySub.fn.constructor = jQuerySub;
- jQuerySub.sub = this.sub;
- jQuerySub.fn.init = function init(selector, context) {
- if (context && context instanceof jQuery && !(context instanceof jQuerySub)) {
- context = jQuerySub(context);
- }
-
- return jQuery.fn.init.call(this, selector, context, rootjQuerySub);
- };
- jQuerySub.fn.init.prototype = jQuerySub.fn;
- var rootjQuerySub = jQuerySub(document);
- return jQuerySub;
- };
-
-})();
-
-/*
- * jQuery stringifyJSON
- * http://github.com/flowersinthesand/jquery-stringifyJSON
- *
- * Copyright 2011, Donghwan Kim
- * Licensed under the Apache License, Version 2.0
- * http://www.apache.org/licenses/LICENSE-2.0
- */
-// This plugin is heavily based on Douglas Crockford's reference implementation
-(function (jQuery) {
-
- var escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, meta = {
- '\b': '\\b',
- '\t': '\\t',
- '\n': '\\n',
- '\f': '\\f',
- '\r': '\\r',
- '"': '\\"',
- '\\': '\\\\'
- };
-
- function quote(string) {
- return '"' + string.replace(escapable, function (a) {
- var c = meta[a];
- return typeof c === "string" ? c : "\\u" + ("0000" + a.charCodeAt(0).toString(16)).slice(-4);
- }) + '"';
- }
-
- function f(n) {
- return n < 10 ? "0" + n : n;
- }
-
- function str(key, holder) {
- var i, v, len, partial, value = holder[key], type = typeof value;
-
- if (value && typeof value === "object" && typeof value.toJSON === "function") {
- value = value.toJSON(key);
- type = typeof value;
- }
-
- switch (type) {
- case "string":
- return quote(value);
- case "number":
- return isFinite(value) ? String(value) : "null";
- case "boolean":
- return String(value);
- case "object":
- if (!value) {
- return "null";
- }
-
- switch (Object.prototype.toString.call(value)) {
- case "[object Date]":
- return isFinite(value.valueOf()) ? '"' + value.getUTCFullYear() + "-" + f(value.getUTCMonth() + 1) + "-" + f(value.getUTCDate())
- + "T" + f(value.getUTCHours()) + ":" + f(value.getUTCMinutes()) + ":" + f(value.getUTCSeconds()) + "Z" + '"' : "null";
- case "[object Array]":
- len = value.length;
- partial = [];
- for (i = 0; i < len; i++) {
- partial.push(str(i, value) || "null");
- }
-
- return "[" + partial.join(",") + "]";
- default:
- partial = [];
- for (i in value) {
- if (Object.prototype.hasOwnProperty.call(value, i)) {
- v = str(i, value);
- if (v) {
- partial.push(quote(i) + ":" + v);
- }
- }
- }
-
- return "{" + partial.join(",") + "}";
- }
- }
- }
-
- jQuery.stringifyJSON = function (value) {
- if (window.JSON && window.JSON.stringify) {
- return window.JSON.stringify(value);
- }
-
- return str("", {
- "": value
- });
- };
-
-}(jQuery));
-/* jshint noarg:true, noempty:true, eqeqeq:true, evil:true, laxbreak:true, undef:true, browser:true, jquery:true, indent:false, maxerr:50, eqnull:true */
diff --git a/js/api.js b/js/api.js
index 66fba9154..cfb0b8109 100644
--- a/js/api.js
+++ b/js/api.js
@@ -7,8 +7,8 @@ var URL_CALLS = {};
URL_CALLS['accounts'] = "/v1/accounts";
URL_CALLS['devices'] = "/v1/devices";
URL_CALLS['keys'] = "/v1/keys";
-URL_CALLS['push'] = "/v1/messagesocket";
-URL_CALLS['messages'] = "/v1/messages/";
+URL_CALLS['push'] = "/v1/websocket";
+URL_CALLS['messages'] = "/v1/messages";
var API = new function() {
@@ -119,16 +119,34 @@ var API = new function() {
call : 'keys',
httpType : 'GET',
do_auth : true,
- urlParameters : "/" + getNumberFromString(number) + "?multikeys",
- success_callback : success_callback,
+ urlParameters : "/" + getNumberFromString(number) + "/*",
+ success_callback : function(response) {
+ //TODO: Do this conversion somewhere else?
+ var res = response.keys;
+ for (var i = 0; i < res.length; i++) {
+ res[i].identityKey = base64DecToArr(res[i].identityKey);
+ res[i].publicKey = base64DecToArr(res[i].publicKey);
+ if (res[i].keyId === undefined)
+ res[i].keyId = 0;
+ }
+ success_callback(res);
+ },
error_callback : error_callback
});
};
- this.sendMessages = function(jsonData, success_callback, error_callback) {
+ this.sendMessages = function(destination, messageArray, success_callback, error_callback) {
+ //TODO: Do this conversion somewhere else?
+ for (var i = 0; i < messageArray.length; i++)
+ messageArray[i].body = btoa(messageArray[i].body);
+ var jsonData = { messages: messageArray };
+ if (messageArray[0].relay !== undefined)
+ jsonData.relay = messageArray[0].relay;
+
this.doAjax({
call : 'messages',
- httpType : 'POST',
+ httpType : 'PUT',
+ urlParameters : '/' + destination,
do_auth : true,
jsonData : jsonData,
success_callback : success_callback,
diff --git a/js/helpers.js b/js/helpers.js
index 9c0f2d335..db389c657 100644
--- a/js/helpers.js
+++ b/js/helpers.js
@@ -206,6 +206,11 @@ function getEncodedNumber(number) {
return number;
}
+function verifyNumber(string) {
+ //TODO: fancy country-code guessing and number verification
+ return getEncodedNumber(string.trim());
+}
+
function getDeviceId(encodedNumber) {
var split = encodedNumber.split(".");
if (split.length > 1)
@@ -865,51 +870,51 @@ var crypto_tests = {};
function subscribeToPush(message_callback) {
var user = storage.getUnencrypted("number_id");
var password = storage.getEncrypted("password");
- var request = { url: URL_BASE + URL_CALLS['push'] + "/?user=%2B" + getString(user).substring(1) + "&password=" + getString(password),
- method: 'GET',
- fallbackMethod: 'GET',
- transport: 'websocket',
- fallbackTransport: 'websocket',
- logLevel: 'debug', //TODO
- trackMessageLength: false,
- //data: "user=" + getString(user) + "&password=" + getString(password),
- onOpen: function(response) {
- console.log('Connected to server using ' + response.transport);
- },
- onMessage: function(response) {
- try {
- // Some bug in Atmosphere.js is forcing trackMessageLength to true
- var message = JSON.parse(response.responseBody.split("|")[1]);
- } catch (e) {
- console.log('Error parsing server JSON message: ' + response.responseBody.split("|")[1]);
- return;
- }
+ var URL = URL_BASE.replace(/^http:/g, "ws:").replace(/^https:/g, "wss:") + URL_CALLS['push'] + "/?user=%2B" + getString(user).substring(1) + "&password=" + getString(password);
+ var socket = new WebSocket(URL);
- var proto;
- try {
- var plaintext = crypto.decryptWebsocketMessage(message.message);
- var proto = decodeIncomingPushMessageProtobuf(plaintext);
- // After this point, a) decoding errors are not the server's fault, and
- // b) we should handle them gracefully and tell the user they received an invalid message
- API.pushMessage(message.id);
- } catch (e) {
- console.log("Error decoding message: " + e);
- return;
- }
+ //TODO: GUI
+ socket.onerror = function(socketEvent) {
+ console.log('Server is down :(');
+ setTimeout(function() { subscribeToPush(message_callback); }, 1000);
+ };
+ socket.onclose = function(socketEvent) {
+ console.log('Server closed :(');
+ setTimeout(function() { subscribeToPush(message_callback); }, 1000);
+ };
+ socket.onopen = function(socketEvent) {
+ console.log('Connected to server!');
+ };
- try {
- crypto.handleIncomingPushMessageProto(proto, function(decrypted) {
- message_callback(decrypted);
- }); // Decrypts/decodes/fills in fields/etc
- } catch (e) {
- //TODO: Tell the user decryption failed
- }
- },
- onError: function(response) {
- console.log('Server is down :(');
- //TODO: GUI
- }};
- $.atmosphere.subscribe(request);
+ socket.onmessage = function(response) {
+ try {
+ // Some bug in Atmosphere.js is forcing trackMessageLength to true
+ var message = JSON.parse(response.responseBody.split("|")[1]);
+ } catch (e) {
+ console.log('Error parsing server JSON message: ' + response.responseBody.split("|")[1]);
+ return;
+ }
+
+ var proto;
+ try {
+ var plaintext = crypto.decryptWebsocketMessage(message.message);
+ var proto = decodeIncomingPushMessageProtobuf(plaintext);
+ // After this point, a) decoding errors are not the server's fault, and
+ // b) we should handle them gracefully and tell the user they received an invalid message
+ API.pushMessage(message.id);
+ } catch (e) {
+ console.log("Error decoding message: " + e);
+ return;
+ }
+
+ try {
+ crypto.handleIncomingPushMessageProto(proto, function(decrypted) {
+ message_callback(decrypted);
+ }); // Decrypts/decodes/fills in fields/etc
+ } catch (e) {
+ //TODO: Tell the user decryption failed
+ }
+ };
}
// success_callback(identity_key), error_callback(error_msg)
@@ -937,83 +942,98 @@ function getKeysForNumber(number, success_callback, error_callback) {
// success_callback(server success/failure map), error_callback(error_msg)
// message == PushMessageContentProto (NOT STRING)
-function sendMessageToDevices(deviceObjectList, message, success_callback, error_callback) {
+function sendMessageToDevices(number, deviceObjectList, message, success_callback, error_callback) {
var jsonData = [];
- for (var i = 0; i < deviceObjectList.legnth; i++) {
- var encryptedMsg = encryptMessageFor(deviceObjectList[i], message);
- jsonData[jsonData.length] = {
- type: encryptedMsg.type,
- destination: deviceObjectList[i].encodedNumber,
- body: encryptedMsg.body,
- relay: deviceObjectList[i].relay,
- timestamp: new Date().getTime()
- };
-//TODO: need to encrypt with session key?
+ var relay = undefined;
+
+ var doSend = function() {
+ API.sendMessages(number, jsonData,
+ function(result) {
+ success_callback(result);
+ }, function(code) {
+ error_callback(code);
+ }
+ );
}
- API.sendMessages(jsonData,
- function(result) {
- if (result.missingDeviceIds.length > 0) {
- var responsesLeft = result.missingDeviceIds.length;
- var errorThrown = 0;
- for (var i = 0; i < result.missingDeviceIds.length; i++) {
- getKeysForNumber(result.missingDeviceIds[i], function(identity_key) {
- responsesLeft--;
- if (responsesLeft == 0 && errorThrown == 0)
- sendMessageToDevices(deviceObjectList, message, success_callback, error_callback);
- }, function(error_msg) {
- errorThrown++;
- if (errorThrown == 1)
- error_callback("Failed to retreive new device keys for number " + result.missingDeviceIds[i]);
- });
+
+ var addEncryptionFor;
+ addEncryptionFor = function(i) {
+ crypto.encryptMessageFor(deviceObjectList[i], message, function(encryptedMsg) {
+ jsonData[i] = {
+ type: encryptedMsg.type,
+ destination: deviceObjectList[i].encodedNumber,
+ body: encryptedMsg.body,
+ timestamp: new Date().getTime()
+ };
+
+ if (deviceObjectList[i].relay !== undefined) {
+ jsonData[i].relay = deviceObjectList[i].relay;
+ if (relay === undefined)
+ relay = jsonData[i].relay;
+ else if (relay != jsonData[i].relay) {
+ error_callback("Mismatched relays for number " + number);
+ return;
}
} else {
- success_callback(result);
+ if (relay === undefined)
+ relay = "";
+ else if (relay != "") {
+ error_callback("Mismatched relays for number " + number);
+ return;
+ }
}
- }, function(code) {
- error_callback("Failed to conect to data channel: " + code);
- }
- );
+
+ if (i+1 < deviceObjectList.length)
+ addEncryptionFor(i+1);
+ else
+ doSend();
+ });
+//TODO: need to encrypt with session key?
+ }
+ addEncryptionFor(0);
}
-// success_callback(success/failure map, see second-to-last line), error_callback(error_msg)
-function sendMessageToNumbers(numbers, message, success_callback, error_callback) {
- var deviceObjectList = [];
+// callback(success/failure map, see code)
+// message == PushMessageContentProto (NOT STRING)
+function sendMessageToNumbers(numbers, message, callback) {
+ var numbersCompleted = 0;
+ var errors = [];
+ var successfulNumbers = [];
+
+ var numberCompleted = function() {
+ numbersCompleted++;
+ if (numbersCompleted >= numbers.length)
+ callback({success: successfulNumbers, failure: errors});
+ }
+
+ var registerError = function(number, message) {
+ errors[errors.length] = { number: number, reason: message };
+ numberCompleted();
+ }
+
+ var doSendMessage = function(number, devicesForNumber, message) {
+ sendMessageToDevices(number, devicesForNumber, message, function(result) {
+ successfulNumbers[successfulNumbers.length] = number;
+ numberCompleted();
+ }, function(error_code) {
+ //TODO: Re-request keys for number here
+ if (error_code == 410 || error_code == 409) {}
+ registerError(number, message);
+ });
+ }
- var deviceDatasMissing = 0;
- var loopDone = 0;
- var errorThrown = 0;
for (var i = 0; i < numbers.length; i++) {
var devicesForNumber = getDeviceObjectListFromNumber(numbers[i]);
- for (var j = 0; j < devicesForNumber.length; j++)
- deviceObjectList[deviceObjectList.length] = devicesForNumber[j];
if (devicesForNumber.length == 0) {
- deviceDatasMissing++;
getKeysForNumber(numbers[i], function(identity_key) {
- deviceDatasMissing--;
- if (deviceDatasMissing == 0 && loopDone && errorThrown == 0)
- sendMessageToNumbers(numbers, message, success_callback, error_callback);
+ doSendMessage(numbers[i], devicesForNumber, message);
}, function(error_msg) {
- errorThrown++;
- if (errorThrown == 1)
- error_callback("Failed to retreive new device keys for number " + numbers[i]);
+ registerError(numbers[i], "Failed to retreive new device keys for number " + numbers[i]);
});
- }
+ } else
+ doSendMessage(numbers[i], devicesForNumber, message);
}
- if (deviceDatasMissing > 0 || errorThrown > 0) {
- loopDone = 1;
- return;
- }
- return sendMessageToDevices(deviceObjectList, message, function(result) {
- var successNumbers = {};
- var failureNumbers = {};
- for (var i = 0; i < result.success; i++)
- successNumbers[getNumberFromString(result.success[i])] = 1;
- for (var i = 0; i < result.failure; i++)
- failureNumbers[getNumberFromString(result.success[i])] = 1;
-
- success_callback({success: successNumbers, failure: failureNumbers});
- }, error_callback);
}
function requestIdentityPrivKeyFromMasterDevice(number, identityKey) {
diff --git a/js/popup.js b/js/popup.js
index 2fb3d81ec..d895f4d89 100644
--- a/js/popup.js
+++ b/js/popup.js
@@ -41,10 +41,12 @@ registerOnLoadFunction(function() {
var sendDestinations = [conversation[0].sender];
for (var j = 0; j < conversation[0].destinations.length; j++)
sendDestinations[sendDestinations.length] = conversation[0].destinations[j];
- sendMessageToNumbers(sendDestinations, { message: $('#text' + i).val() }, function(result) {
- console.log("Sent message: " + JSON.stringify(result));
- }, function(error_msg) {
- alert(error_msg); //TODO
+
+ var messageProto = new PushMessageContentProtobuf();
+ messageProto.body = $('#text' + i).val();
+
+ sendMessageToNumbers(sendDestinations, messageProto, function(result) {
+ console.log("Sent message: " + result);
});
});
ul.append('');
@@ -59,5 +61,23 @@ registerOnLoadFunction(function() {
fillMessages();
storage.putUnencrypted("unreadCount", 0);
chrome.browserAction.setBadgeText({text: ""});
+
+ $("#popup_send_button").click(function() {
+ var numbers = [];
+ var splitString = $("#popup_send_numbers").val().split(",");
+ for (var i = 0; i < splitString.length; i++) {
+ try {
+ numbers.push(verifyNumber(splitString[i]));
+ } catch (numberError) {
+ //TODO
+ alert(numberError);
+ }
+ }
+ var messageProto = new PushMessageContentProtobuf();
+ messageProto.body = $("#popup_send_message").val();
+ sendMessageToNumbers(numbers, messageProto,
+ //TODO: Handle result
+ function(thing) {console.log(thing);});
+ });
}
});
diff --git a/options.html b/options.html
index fad6e53cc..a995bfc5c 100644
--- a/options.html
+++ b/options.html
@@ -27,7 +27,6 @@
-
diff --git a/popup.html b/popup.html
index da4b9b72b..e028ea13d 100644
--- a/popup.html
+++ b/popup.html
@@ -11,11 +11,16 @@
-
+
+
+
-
@@ -30,7 +35,6 @@
-