All Downloads are FREE. Search and download functionalities are using the official Maven repository.

META-INF.resources.net.java.dev.atmosphere.jquery-atmosphere.js Maven / Gradle / Ivy

There is a newer version: 4.5.17.Final
Show newest version
/*
 * Copyright 2015 Async-IO.org
 *
 * 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.
 */
/**
 * Atmosphere.js
 * https://github.com/Atmosphere/atmosphere-javascript
 *
 * Requires
 * - jQuery 2.0.3 http://jquery.com/
 *
 * API reference
 * https://github.com/Atmosphere/atmosphere/wiki/jQuery.atmosphere.js-API
 *
 * Highly inspired by
 * - Portal by Donghwan Kim http://flowersinthesand.github.io/portal/
 */
(function (factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD
        define(['jquery'], factory);
    } else {
        // Browser globals, Window
        factory(jQuery);
    }
}(function (jQuery) {

    jQuery(window).bind("unload.atmosphere", function () {
        jQuery.atmosphere.debug(new Date() + " Atmosphere: " + "unload event");
        jQuery.atmosphere.unsubscribe();
    });

    jQuery(window).bind("beforeunload.atmosphere", function () {
        jQuery.atmosphere.debug(new Date() + " Atmosphere: " + "beforeunload event");

        // ATMOSPHERE-JAVASCRIPT-143: Delay reconnect to avoid reconnect attempts before an actual unload (we don't know if an unload will happen, yet)
        jQuery.atmosphere._beforeUnloadState = true;
        setTimeout(function () {
            jQuery.atmosphere.debug(new Date() + " Atmosphere: " + "beforeunload event timeout reached. Reset _beforeUnloadState flag");
            jQuery.atmosphere._beforeUnloadState = false;
        }, 5000);
    });

    jQuery(window).bind("offline", function () {
        jQuery.atmosphere.offline = true;
        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);

            if (rq.heartbeatTimer) {
                clearTimeout(rq.heartbeatTimer);
            }
        }
    });

    jQuery(window).bind("online", function () {
        jQuery.atmosphere.offline = false;
        if (jQuery.atmosphere.requests.length > 0) {
            for (var i = 0; i < jQuery.atmosphere.requests.length; i++) {
                jQuery.atmosphere.requests[i].init();
                jQuery.atmosphere.requests[i].execute();
            }
        }
    });

    // 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;
    };

    jQuery.atmosphere = {
        version: "2.2.12-jquery",
        uuid: 0,
        offline: false,
        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) {
        },

        /**
         * Creates an object based on an atmosphere subscription that exposes functions defined by the Websocket interface.
         *
         * @class WebsocketApiAdapter
         * @param {Object} request the request object to build the underlying subscription
         * @constructor
         */
        WebsocketApiAdapter: function (request) {
            var _socket, _adapter;

            /**
             * Overrides the onMessage callback in given request.
             *
             * @method onMessage
             * @param {Object} e the event object
             */
            request.onMessage = function (e) {
                _adapter.onmessage({data: e.responseBody});
            };

            /**
             * Overrides the onMessagePublished callback in given request.
             *
             * @method onMessagePublished
             * @param {Object} e the event object
             */
            request.onMessagePublished = function (e) {
                _adapter.onmessage({data: e.responseBody});
            };

            /**
             * Overrides the onOpen callback in given request to proxy the event to the adapter.
             *
             * @method onOpen
             * @param {Object} e the event object
             */
            request.onOpen = function (e) {
                _adapter.onopen(e);
            };

            _adapter = {
                close: function () {
                    _socket.close();
                },

                send: function (data) {
                    _socket.push(data);
                },

                onmessage: function (e) {
                },

                onopen: function (e) {
                },

                onclose: function (e) {
                },

                onerror: function (e) {

                }
            };
            _socket = new $.atmosphere.subscribe(request);

            return _adapter;
        },

        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,
                withCredentials: false,
                trackMessageLength: false,
                messageDelimiter: '|',
                connectTimeout: -1,
                reconnectInterval: 0,
                dropHeaders: true,
                uuid: 0,
                shared: false,
                readResponsesHeaders: false,
                maxReconnectOnClose: 5,
                enableProtocol: true,
                pollingInterval: 0,
                heartbeat: {
                    client: null,
                    server: null
                },
                ackInterval: 0,
                closeAsync: false,
                reconnectOnServerError: 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,
                ffTryingReconnect: 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;

            /**
             * The Heartbeat interval send by the server.
             * @type {int}
             * @private
             */
            var _heartbeatInterval = 0;

            /**
             * The Heartbeat bytes send by the server.
             * @type {string}
             * @private
             */
            var _heartbeatPadding = 'X';

            /**
             * {boolean} If request is currently aborted.
             *
             * @private
             */
            var _abortingConnection = 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;

            /**
             * {boolean} If window beforeUnload event has been called.
             * Flag will be reset after 5000 ms
             *
             * @private
             */
            var _beforeUnloadState = false;

            // Automatic call to subscribe
            _subscribe(options);

            /**
             * Initialize atmosphere request object.
             *
             * @private
             */
            function _init() {
                _subscribed = true;
                _abortingConnection = false;
                _requestCount = 0;

                _websocket = null;
                _sse = null;
                _activeRequest = null;
                _ieStream = null;
            }

            /**
             * Re-initialize atmosphere object.
             *
             * @private
             */
            function _reinit() {
                _clearState();
                _init();
            }

            /**
             * Returns true if the given level is equal or above the configured log level.
             *
             * @private
             */
            function _canLog(level) {
                if (level == 'debug') {
                    return _request.logLevel === 'debug';
                } else if (level == 'info') {
                    return _request.logLevel === 'info' || _request.logLevel === 'debug';
                } else if (level == 'warn') {
                    return _request.logLevel === 'warn' || _request.logLevel === 'info' || _request.logLevel === 'debug';
                } else if (level == 'error') {
                    return _request.logLevel === 'error' || _request.logLevel === 'warn' || _request.logLevel === 'info' || _request.logLevel === 'debug';
                } else {
                    return false;
                }
            }

            function _debug(msg) {
                if (_canLog('debug')) {
                    jQuery.atmosphere.debug(new Date() + " Atmosphere: " + msg);
                }
            }

            /**
             * 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() { function makeAbsolute(url) { var div = document.createElement("div"); // Uses an innerHTML property to obtain an absolute URL div.innerHTML = ''; // encodeURI and decodeURI are needed to normalize URL between Internet Explorer and non-Internet Explorer, // since Internet Explorer doesn't encode the href property value and return it - http://jsfiddle.net/Yq9M8/1/ return encodeURI(decodeURI(div.firstChild.href)); } // Origin parts var url = makeAbsolute(_request.url.toLowerCase()); var parts = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/.exec(url); var crossOrigin = !!(parts && ( // protocol parts[1] != window.location.protocol || // hostname parts[2] != window.location.hostname || // port (parts[3] || (parts[1] === "http:" ? 80 : 443)) != (window.location.port || (window.location.protocol === "http:" ? 80 : 443)) )); return window.EventSource && (!crossOrigin || !jQuery.browser.safari || jQuery.browser.vmajor >= 7); } /** * 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 (_canLog('debug')) { jQuery.atmosphere.debug("Storage service available. All communication will be local"); } if (_localStorageService.open(_request)) { // Local connection. return; } } if (_canLog('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(); // We carry any UUID set by the user or from a previous connection. if (_request.uuid === 0) { _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 (!_abortingConnection) { 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( '