From a9693bdb3eae8ff93a36494ce8d7dea69257284f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20L=C3=B6vdahl?= Date: Mon, 3 Jun 2013 15:48:51 +0300 Subject: [PATCH 1/2] Backported javascript module from master. --- modules/javascript/pom.xml | 39 + .../src/main/webapp/WEB-INF/web.xml | 1 + .../src/main/webapp/javascript/atmosphere.js | 2861 +++++++++++++++++ modules/pom.xml | 1 + 4 files changed, 2902 insertions(+) create mode 100644 modules/javascript/pom.xml create mode 100644 modules/javascript/src/main/webapp/WEB-INF/web.xml create mode 100644 modules/javascript/src/main/webapp/javascript/atmosphere.js diff --git a/modules/javascript/pom.xml b/modules/javascript/pom.xml new file mode 100644 index 00000000000..757931c623c --- /dev/null +++ b/modules/javascript/pom.xml @@ -0,0 +1,39 @@ + + + + org.atmosphere + atmosphere-project + 1.0.14-SNAPSHOT + ../../pom.xml + + 4.0.0 + org.atmosphere + atmosphere-javascript + war + 1.0.14-SNAPSHOT + atmosphere-javascript + + + org.slf4j + slf4j-api + ${slf4j-version} + provided + + + + + + net.alchim31.maven + yuicompressor-maven-plugin + 1.1 + + + + compress + + + + + + + \ No newline at end of file diff --git a/modules/javascript/src/main/webapp/WEB-INF/web.xml b/modules/javascript/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000000..c8f1294b9e1 --- /dev/null +++ b/modules/javascript/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1 @@ +DUMMY web.xml diff --git a/modules/javascript/src/main/webapp/javascript/atmosphere.js b/modules/javascript/src/main/webapp/javascript/atmosphere.js new file mode 100644 index 00000000000..20f7e61a4d7 --- /dev/null +++ b/modules/javascript/src/main/webapp/javascript/atmosphere.js @@ -0,0 +1,2861 @@ +/** + * Copyright 2012 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. + */ +/* + * Highly inspired by Portal v1.0 + * http://github.com/flowersinthesand/portal + * + * Copyright 2011-2013, 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 + */ +(function () { + + "use strict"; + + var version = "1.0.14", + atmosphere = {}, + guid, + requests = [], + callbacks = []; + + atmosphere = { + + onError: function (response) { + }, + onClose: function (response) { + }, + onOpen: function (response) { + }, + onReopen: function (response) { + }, + onMessage: function (response) { + }, + onReconnect: function (request, response) { + }, + onMessagePublished: function (response) { + }, + onTransportFailure: function (errorMessage, _request) { + }, + onLocalMessage: function (response) { + }, + 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, + async: true, + 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) { + } + }; + + /** + * {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, + id: 0 + }; + + /** + * {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 = atmosphere.util.now(); + + /** Trace time */ + var _traceTimer; + + // 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(); + } + + + /** + * + * @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, true); + } + }; + + /** + * Disconnect + * @private + */ + function _disconnect() { + if (_request.enableProtocol) { + var query = "X-Atmosphere-Transport=close&X-Atmosphere-tracking-id=" + _request.uuid; + var url = _request.url.replace(/([?&])_=[^&]*/, query); + url = url + (url === _request.url ? (/\?/.test(_request.url) ? "&" : "?") + query : ""); + _request.attachHeadersAsQueryString = false; + _request.dropAtmosphereHeaders = true; + _request.url = url; + _request.transport = 'polling' + _pushOnClose("", _request); + } + } + + /** + * 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 (_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 = encodeURIComponent("atmosphere-" + _request.url) + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT"; + // 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(); + } + } + + /** + * 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 = atmosphere.util.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') { + atmosphere.util.debug("Storage service available. All communication will be local"); + } + + if (_localStorageService.open(_request)) { + // Local connection. + return; + } + } + + if (_request.logLevel == 'debug') { + atmosphere.util.debug("No Storage service available."); + } + // Wasn't local or an error occurred + _localStorageService = null; + } + + // Protocol + _request.firstMessage = true; + _request.isOpen = false; + _request.ctime = atmosphere.util.now(); + + 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 (!atmosphere.util.supportStorage()) { + return; + } + + var storage = window.localStorage, + get = function (key) { + return atmosphere.util.parseJSON(storage.getItem(name + "-" + key)); + }, + set = function (key, value) { + storage.setItem(name + "-" + key, atmosphere.util.stringifyJSON(value)); + }; + + return { + init: function () { + set("children", get("children").concat([guid])); + atmosphere.util.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, atmosphere.util.stringifyJSON({target: "p", type: type, data: data})); + }, + close: function () { + var children = get("children"); + + atmosphere.util.off("storage.socket"); + if (children) { + if (removeFromArray(children, request.id)) { + 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(atmosphere.util.stringifyJSON({target: "p", type: type, data: data})); + } + }, + close: function () { + // Removes traces only if the parent is alive + if (!orphan) { + removeFromArray(win.callbacks, listener); + removeFromArray(win.children, guid); + } + } + + }; + } + }; + + function removeFromArray(array, val) { + var i, + length = array.length; + + for (i = 0; i < length; i++) { + if (array[i] === val) { + array.splice(i, 1); + } + } + + return length !== array.length; + } + + // Receives open, close and message command from the parent + function listener(string) { + var command = atmosphere.util.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 atmosphere.util.parseJSON(decodeURIComponent(matcher[2])); + } + } + + // Finds and validates the parent socket's trace from the cookie + trace = findTrace(); + if (!trace || atmosphere.util.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(atmosphere.util.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", atmosphere.util.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 (!atmosphere.util.supportStorage()) { + return; + } + + var storage = window.localStorage; + + return { + init: function () { + // Handles the storage event + atmosphere.util.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, atmosphere.util.stringifyJSON({target: "c", type: type, data: data})); + }, + get: function (key) { + return atmosphere.util.parseJSON(storage.getItem(name + "-" + key)); + }, + set: function (key, value) { + storage.setItem(name + "-" + key, atmosphere.util.stringifyJSON(value)); + }, + close: function () { + atmosphere.util.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, ""), + container = document.getElementById(neim), + win; + + if (!container) { + container = document.createElement("div"); + container.id = neim; + container.style.display = "none"; + container.innerHTML = '