From 56d146d084650aa8fd38a9b8a832ebe8b5dc6acf Mon Sep 17 00:00:00 2001 From: Donghwan Kim Date: Sat, 12 Oct 2013 02:40:19 +0900 Subject: [PATCH 1/2] Update portal to 1.0.1 --- ajax/libs/portal/1.0.1/portal.js | 1884 ++++++++++++++++++++++++++ ajax/libs/portal/1.0.1/portal.min.js | 42 + ajax/libs/portal/package.json | 54 +- 3 files changed, 1953 insertions(+), 27 deletions(-) create mode 100644 ajax/libs/portal/1.0.1/portal.js create mode 100644 ajax/libs/portal/1.0.1/portal.min.js diff --git a/ajax/libs/portal/1.0.1/portal.js b/ajax/libs/portal/1.0.1/portal.js new file mode 100644 index 000000000..470aad158 --- /dev/null +++ b/ajax/libs/portal/1.0.1/portal.js @@ -0,0 +1,1884 @@ +/* + * Portal v1.0.1 + * 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 + */ +(function() { + + "use strict"; + + var // A global identifier + guid, + // Is the unload event being processed? + unloading, + // Portal + portal = {}, + // Socket instances + sockets = {}, + // Callback names for JSONP + jsonpCallbacks = [], + // Core prototypes + toString = Object.prototype.toString, + hasOwn = Object.prototype.hasOwnProperty, + slice = Array.prototype.slice; + + // Convenience utilities + // Most utility functions are borrowed from jQuery + portal.support = { + now: function() { + return new Date().getTime(); + }, + isArray: function(array) { + return toString.call(array) === "[object Array]"; + }, + isBinary: function(data) { + var string = toString.call(data); + return string === "[object Blob]" || string === "[object ArrayBuffer]"; + }, + isFunction: function(fn) { + return toString.call(fn) === "[object Function]"; + }, + getAbsoluteURL: function(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 IE and non-IE, + // since IE doesn't encode the href property value and return it - http://jsfiddle.net/Yq9M8/1/ + return encodeURI(decodeURI(div.firstChild.href)); + }, + iterate: function(fn) { + var timeoutId; + + // Though the interval is 1ms 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 + (function loop() { + timeoutId = setTimeout(function() { + if (fn() === false) { + return; + } + + loop(); + }, 1); + })(); + + return function() { + clearTimeout(timeoutId); + }; + }, + each: function(array, callback) { + var i; + + for (i = 0; i < array.length; i++) { + callback(i, array[i]); + } + }, + extend: function(target) { + var i, options, name; + + for (i = 1; i < arguments.length; i++) { + if ((options = arguments[i]) != null) { + for (name in options) { + target[name] = options[name]; + } + } + } + + return target; + }, + on: function(elem, type, fn) { + if (elem.addEventListener) { + elem.addEventListener(type, fn, false); + } else if (elem.attachEvent) { + elem.attachEvent("on" + type, fn); + } + }, + off: function(elem, type, fn) { + if (elem.removeEventListener) { + elem.removeEventListener(type, fn, false); + } else if (elem.detachEvent) { + elem.detachEvent("on" + type, fn); + } + }, + param: function(params) { + var prefix, s = []; + + function add(key, value) { + value = portal.support.isFunction(value) ? value() : (value == null ? "" : value); + s.push(encodeURIComponent(key) + "=" + encodeURIComponent(value)); + } + + function buildParams(prefix, obj) { + var name; + + if (portal.support.isArray(obj)) { + portal.support.each(obj, function(i, v) { + if (/\[\]$/.test(prefix)) { + add(prefix, v); + } else { + buildParams(prefix + "[" + (typeof v === "object" ? i : "") + "]", v); + } + }); + } else if (obj != null && toString.call(obj) === "[object Object]") { + for (name in obj) { + buildParams(prefix + "[" + name + "]", obj[name]); + } + } else { + add(prefix, obj); + } + } + + for (prefix in params) { + buildParams(prefix, params[prefix]); + } + + return s.join("&").replace(/%20/g, "+"); + }, + xhr: function() { + try { + return new window.XMLHttpRequest(); + } catch (e1) { + try { + return new window.ActiveXObject("Microsoft.XMLHTTP"); + } catch (e2) {} + } + }, + parseJSON: function(data) { + return !data ? + null : + window.JSON && window.JSON.parse ? + window.JSON.parse(data) : + new Function("return " + data)(); + }, + // http://github.com/flowersinthesand/stringifyJSON + stringifyJSON: function(value) { + 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; + } + + return window.JSON && window.JSON.stringify ? + window.JSON.stringify(value) : + (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 (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 (hasOwn.call(value, i)) { + v = str(i, value); + if (v) { + partial.push(quote(i) + ":" + v); + } + } + } + + return "{" + partial.join(",") + "}"; + } + } + })("", {"": value}); + }, + browser: {}, + storage: !!(window.localStorage && window.StorageEvent) + }; + portal.support.corsable = "withCredentials" in portal.support.xhr(); + guid = portal.support.now(); + + // Browser sniffing + (function() { + var ua = navigator.userAgent.toLowerCase(), + 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) || + []; + + portal.support.browser[match[1] || ""] = true; + portal.support.browser.version = match[2] || "0"; + + // The storage event of Internet Explorer and Firefox 3 works strangely + if (portal.support.browser.msie || (portal.support.browser.mozilla && portal.support.browser.version.split(".")[0] === "1")) { + portal.support.storage = false; + } + })(); + + // Finds the socket object which is mapped to the given url + portal.find = function(url) { + var i; + + // Returns the first socket in the document + if (!arguments.length) { + for (i in sockets) { + if (sockets[i]) { + return sockets[i]; + } + } + return null; + } + + // The url is a identifier of this socket within the document + return sockets[portal.support.getAbsoluteURL(url)] || null; + }; + // Creates a new socket and connects to the given url + portal.open = function(url, options) { + // Makes url absolute to normalize URL + url = portal.support.getAbsoluteURL(url); + sockets[url] = socket(url, options); + + return portal.find(url); + }; + // Default options + portal.defaults = { + // Socket options + transports: ["ws", "sse", "stream", "longpoll"], + timeout: false, + heartbeat: false, + _heartbeat: 5000, + lastEventId: 0, + sharing: false, + prepare: function(connect) { + connect(); + }, + reconnect: function(lastDelay) { + return 2 * (lastDelay || 250); + }, + idGenerator: function() { + // Generates a random UUID + // Logic borrowed from http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523 + return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) { + var r = Math.random() * 16 | 0, v = c === "x" ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); + }, + urlBuilder: function(url, params, when) { + return url + (/\?/.test(url) ? "&" : "?") + "when=" + when + "&" + portal.support.param(params); + }, + inbound: portal.support.parseJSON, + outbound: portal.support.stringifyJSON, + + // Transport options + credentials: false, + notifyAbort: false, + xdrURL: function(url) { + // Maintaining session by rewriting URL + // http://stackoverflow.com/questions/6453779/maintaining-session-by-rewriting-url + var match = /(?:^|; )(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(/&$/, ""); + default: + return false; + } + }, + streamParser: function(chunk) { + // Chunks are formatted according to the event stream format + // http://www.w3.org/TR/eventsource/#event-stream-interpretation + var reol = /\r\n|[\r\n]/g, lines = [], data = this.data("data"), array = [], i = 0, + match, line; + + // Strips off the left padding of the chunk + // the first chunk of some streaming transports and every chunk for Android browser 2 and 3 has padding + chunk = chunk.replace(/^\s+/g, ""); + + // String.prototype.split is not reliable cross-browser + while (match = reol.exec(chunk)) { + lines.push(chunk.substring(i, match.index)); + i = match.index + match[0].length; + } + lines.push(chunk.length === i ? "" : chunk.substring(i)); + + if (!data) { + data = []; + this.data("data", data); + } + + // Processes the data field only + for (i = 0; i < lines.length; i++) { + line = lines[i]; + if (!line) { + // Finish + array.push(data.join("\n")); + data = []; + this.data("data", data); + } else if (/^data:\s/.test(line)) { + // A single data field + data.push(line.substring("data: ".length)); + } else { + // A fragment of a data field + data[data.length - 1] += line; + } + } + + return array; + } + }; + + // Callback function + function callbacks(deferred) { + var list = [], + locked, + memory, + firing, + firingStart, + firingLength, + firingIndex, + fire = function(context, args) { + args = args || []; + memory = !deferred || [context, args]; + firing = true; + firingIndex = firingStart || 0; + firingStart = 0; + firingLength = list.length; + for (; firingIndex < firingLength; firingIndex++) { + list[firingIndex].apply(context, args); + } + firing = false; + }, + self = { + add: function(fn) { + var length = list.length; + + list.push(fn); + if (firing) { + firingLength = list.length; + } else if (!locked && memory && memory !== true) { + firingStart = length; + fire(memory[0], memory[1]); + } + }, + remove: function(fn) { + var i; + + for (i = 0; i < list.length; i++) { + if (fn === list[i] || (fn.guid && fn.guid === list[i].guid)) { + if (firing) { + if (i <= firingLength) { + firingLength--; + if (i <= firingIndex) { + firingIndex--; + } + } + } + list.splice(i--, 1); + } + } + }, + fire: function(context, args) { + if (!locked && !firing && !(deferred && memory)) { + fire(context, args); + } + }, + lock: function() { + locked = true; + }, + locked: function() { + return !!locked; + }, + unlock: function() { + locked = memory = firing = firingStart = firingLength = firingIndex = undefined; + } + }; + + return self; + } + + // Socket function + function socket(url, options) { + var // Final options + opts, + // Transport + transport, + // The state of the connection + state, + // Event helpers + events = {}, + eventId = 0, + // Reply callbacks + replyCallbacks = {}, + // Buffer + buffer = [], + // Reconnection + reconnectTimer, + reconnectDelay, + reconnectTry, + // Map of the connection-scoped values + connection = {}, + parts = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/.exec(url.toLowerCase()), + // Socket object + self = { + // Finds the value of an option + option: function(key, /* undocumented */ value) { + if (value === undefined) { + return opts[key]; + } + + opts[key] = value; + + return this; + }, + // Gets or sets a connection-scoped value + data: function(key, value) { + if (value === undefined) { + return connection[key]; + } + + connection[key] = value; + + return this; + }, + // Returns the state + state: function() { + return state; + }, + // Adds event handler + on: function(type, fn) { + var event; + + // Handles a map of type and handler + if (typeof type === "object") { + for (event in type) { + self.on(event, type[event]); + } + return this; + } + + // For custom event + event = events[type]; + if (!event) { + if (events.message.locked()) { + return this; + } + + event = events[type] = callbacks(); + event.order = events.message.order; + } + + event.add(fn); + + return this; + }, + // Removes event handler + off: function(type, fn) { + var event = events[type]; + + if (event) { + event.remove(fn); + } + + return this; + }, + // Adds one time event handler + one: function(type, fn) { + function proxy() { + self.off(type, proxy); + fn.apply(self, arguments); + } + + fn.guid = fn.guid || guid++; + proxy.guid = fn.guid; + + return self.on(type, proxy); + }, + // Fires event handlers + fire: function(type) { + var event = events[type]; + + if (event) { + event.fire(self, slice.call(arguments, 1)); + } + + return this; + }, + // Establishes a connection + open: function() { + var type, + latch, + connect = function() { + var candidates, type; + + if (!latch) { + latch = true; + candidates = connection.candidates = slice.call(opts.transports); + while (!transport && candidates.length) { + type = candidates.shift(); + connection.transport = type; + connection.url = self.buildURL("open"); + transport = portal.transports[type](self, opts); + } + + // Increases the number of reconnection attempts + if (reconnectTry) { + reconnectTry++; + } + + // Fires the connecting event and connects + if (transport) { + self.fire("connecting"); + transport.open(); + } else { + self.fire("close", "notransport"); + } + } + }, + cancel = function() { + if (!latch) { + latch = true; + self.fire("close", "canceled"); + } + }; + + // Cancels the scheduled connection + if (reconnectTimer) { + clearTimeout(reconnectTimer); + } + + // Resets the connection scope and event helpers + connection = {}; + for (type in events) { + events[type].unlock(); + } + + // Chooses transport + transport = undefined; + + // From null or waiting state + state = "preparing"; + + // Check if possible to make use of a shared socket + if (opts.sharing) { + connection.transport = "session"; + transport = portal.transports.session(self, opts); + } + + // Executes the prepare handler if a physical connection is needed + if (transport) { + connect(); + } else { + opts.prepare.call(self, connect, cancel, opts); + } + + return this; + }, + // Sends an event to the server via the connection + send: function(type, data, doneCallback, failCallback) { + var event; + + // Defers sending an event until the state become opened + if (state !== "opened") { + buffer.push(arguments); + return this; + } + + // Outbound event + event = { + id: ++eventId, + socket: opts.id, + type: type, + data: data, + reply: !!(doneCallback || failCallback) + }; + + if (event.reply) { + // Shared socket needs to know the callback event name + // because it fires the callback event directly instead of using reply event + if (connection.transport === "session") { + event.doneCallback = doneCallback; + event.failCallback = failCallback; + } else { + replyCallbacks[eventId] = {done: doneCallback, fail: failCallback}; + } + } + + // Delegates to the transport + transport.send(portal.support.isBinary(data) ? data : opts.outbound.call(self, event)); + + return this; + }, + // Disconnects the connection + close: function() { + var script, head; + + // Prevents reconnection + opts.reconnect = false; + if (reconnectTimer) { + clearTimeout(reconnectTimer); + } + + // Fires the close event immediately for transport which doesn't give feedback on disconnection + if (unloading || !transport || !transport.feedback) { + self.fire("close", unloading ? "error" : "aborted"); + if (opts.notifyAbort && connection.transport !== "session") { + head = document.head || document.getElementsByTagName("head")[0] || document.documentElement; + script = document.createElement("script"); + script.async = false; + script.src = self.buildURL("abort"); + script.onload = script.onreadystatechange = function() { + if (!script.readyState || /loaded|complete/.test(script.readyState)) { + script.onload = script.onreadystatechange = null; + if (script.parentNode) { + script.parentNode.removeChild(script); + } + } + }; + head.insertBefore(script, head.firstChild); + } + } + + // Delegates to the transport + if (transport) { + transport.close(); + } + + return this; + }, + // Broadcasts event to session sockets + broadcast: function(type, data) { + // TODO rename + var broadcastable = connection.broadcastable; + if (broadcastable) { + broadcastable.broadcast({type: "fire", data: {type: type, data: data}}); + } + + return this; + }, + // For internal use only + // fires events from the server + _fire: function(data, isChunk) { + var array; + + if (isChunk) { + data = opts.streamParser.call(self, data); + while (data.length) { + self._fire(data.shift()); + } + return this; + } + + if (portal.support.isBinary(data)) { + array = [{type: "message", data: data}]; + } else { + array = opts.inbound.call(self, data); + array = array == null ? [] : !portal.support.isArray(array) ? [array] : array; + } + + connection.lastEventIds = []; + portal.support.each(array, function(i, event) { + var latch, args = [event.type, event.data]; + + opts.lastEventId = event.id; + connection.lastEventIds.push(event.id); + if (event.reply) { + args.push(function(result) { + if (!latch) { + latch = true; + self.send("reply", {id: event.id, data: result}); + } + }); + } + + self.fire.apply(self, args).fire("_message", args); + }); + + return this; + }, + // For internal use only + // builds an effective URL + buildURL: function(when, params) { + var p = when === "open" ? + { + transport: connection.transport, + heartbeat: opts.heartbeat, + lastEventId: opts.lastEventId + } : + when === "poll" ? + { + transport: connection.transport, + lastEventIds: connection.lastEventIds && connection.lastEventIds.join(","), + /* deprecated */lastEventId: opts.lastEventId + } : + {}; + + portal.support.extend(p, {id: opts.id, _: guid++}, opts.params && opts.params[when], params); + return opts.urlBuilder.call(self, url, p, when); + } + }; + + // Create the final options + opts = portal.support.extend({}, portal.defaults, options); + if (options) { + // Array should not be deep extended + if (options.transports) { + opts.transports = slice.call(options.transports); + } + } + // Saves original URL + opts.url = url; + // Generates socket id, + opts.id = opts.idGenerator.call(self); + opts.crossDomain = !!(parts && + // protocol and hostname + (parts[1] != location.protocol || parts[2] != location.hostname || + // port + (parts[3] || (parts[1] === "http:" ? 80 : 443)) != (location.port || (location.protocol === "http:" ? 80 : 443)))); + + portal.support.each(["connecting", "open", "message", "close", "waiting"], function(i, type) { + // Creates event helper + events[type] = callbacks(type !== "message"); + events[type].order = i; + + // Shortcuts for on method + var old = self[type], + on = function(fn) { + return self.on(type, fn); + }; + + self[type] = !old ? on : function(fn) { + return (portal.support.isFunction(fn) ? on : old).apply(this, arguments); + }; + }); + + // Initializes + self.on({ + connecting: function() { + // From preparing state + state = "connecting"; + + var timeoutTimer; + + // Sets timeout timer + function setTimeoutTimer() { + timeoutTimer = setTimeout(function() { + transport.close(); + self.fire("close", "timeout"); + }, opts.timeout); + } + + // Clears timeout timer + function clearTimeoutTimer() { + clearTimeout(timeoutTimer); + } + + // Makes the socket sharable + function share() { + var traceTimer, + server, + name = "socket-" + url, + servers = { + // Powered by the storage event and the localStorage + // http://www.w3.org/TR/webstorage/#event-storage + storage: function() { + if (!portal.support.storage) { + return; + } + + var storage = window.localStorage; + + return { + init: function() { + function onstorage(event) { + // When a deletion, newValue initialized to null + if (event.key === name && event.newValue) { + listener(event.newValue); + } + } + + // Handles the storage event + portal.support.on(window, "storage", onstorage); + self.one("close", function() { + portal.support.off(window, "storage", onstorage); + // Defers again to clean the storage + self.one("close", function() { + storage.removeItem(name); + storage.removeItem(name + "-opened"); + storage.removeItem(name + "-children"); + }); + }); + }, + broadcast: function(obj) { + var string = portal.support.stringifyJSON(obj); + storage.setItem(name, string); + setTimeout(function() { + listener(string); + }, 50); + }, + get: function(key) { + return portal.support.parseJSON(storage.getItem(name + "-" + key)); + }, + set: function(key, value) { + storage.setItem(name + "-" + key, portal.support.stringifyJSON(value)); + } + }; + }, + // 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 = ''; + + textarea = form.firstChild; + textarea.value = data; + + iframe = form.lastChild; + portal.support.on(iframe, "load", function() { + document.body.removeChild(form); + post(); + }); + + document.body.appendChild(form); + form.submit(); + }; + + return { + send: function(data) { + queue.push(data); + + if (!sending) { + sending = true; + post(); + } + } + }; + }, + // Server-Sent Events + sse: function(socket, options) { + var es, + EventSource = window.EventSource; + + if (!EventSource) { + return; + } + + return portal.support.extend(portal.transports.httpbase(socket, options), { + open: function() { + var url = socket.data("url"); + + // Uses proper constructor for Chrome 10-15 + es = !options.crossDomain ? new EventSource(url) : new EventSource(url, {withCredentials: options.credentials}); + es.onopen = function(event) { + socket.data("event", event).fire("open"); + }; + es.onmessage = function(event) { + socket.data("event", event)._fire(event.data); + }; + es.onerror = function(event) { + es.close(); + + // There is no way to find whether this connection closed normally or not + socket.data("event", event).fire("close", "done"); + }; + }, + close: function() { + es.close(); + } + }); + }, + // Streaming facade + stream: function(socket) { + socket.data("candidates").unshift("streamxhr", "streamxdr", "streamiframe"); + }, + // Streaming - XMLHttpRequest + streamxhr: function(socket, options) { + var xhr; + + if ((portal.support.browser.msie && +portal.support.browser.version < 10) || (options.crossDomain && !portal.support.corsable)) { + return; + } + + return portal.support.extend(portal.transports.httpbase(socket, options), { + open: function() { + var stop; + + xhr = portal.support.xhr(); + xhr.onreadystatechange = function() { + function onprogress() { + var index = socket.data("index"), + length = xhr.responseText.length; + + if (!index) { + socket.fire("open")._fire(xhr.responseText, true); + } else if (length > index) { + socket._fire(xhr.responseText.substring(index, length), true); + } + + socket.data("index", length); + } + + if (xhr.readyState === 3 && xhr.status === 200) { + // Despite the change in response, Opera doesn't fire the readystatechange event + if (portal.support.browser.opera && !stop) { + stop = portal.support.iterate(onprogress); + } else { + onprogress(); + } + } else if (xhr.readyState === 4) { + if (stop) { + stop(); + } + + socket.fire("close", xhr.status === 200 ? "done" : "error"); + } + }; + + xhr.open("GET", socket.data("url")); + if (portal.support.corsable) { + xhr.withCredentials = options.credentials; + } + + xhr.send(null); + }, + close: function() { + xhr.abort(); + } + }); + }, + // Streaming - Iframe + streamiframe: function(socket, options) { + var doc, + stop, + ActiveXObject = window.ActiveXObject; + + if (!ActiveXObject || options.crossDomain) { + return; + } else { + // IE 10 Metro doesn't support ActiveXObject + try { + new ActiveXObject("htmlfile"); + } catch (e) { + return; + } + } + + return portal.support.extend(portal.transports.httpbase(socket, options), { + open: function() { + var iframe, cdoc; + + doc = new ActiveXObject("htmlfile"); + doc.open(); + doc.close(); + + iframe = doc.createElement("iframe"); + iframe.src = socket.data("url"); + doc.body.appendChild(iframe); + + cdoc = iframe.contentDocument || iframe.contentWindow.document; + stop = portal.support.iterate(function() { + // Response container + var container; + + function readDirty() { + var clone = container.cloneNode(true), + text; + + // Adds a character not CR and LF to circumvent an Internet Explorer bug + // If the contents of an element ends with one or more CR or LF, Internet Explorer ignores them in the innerText property + clone.appendChild(cdoc.createTextNode(".")); + text = clone.innerText; + + return text.substring(0, text.length - 1); + } + + // Waits the server's container ignorantly + if (!cdoc.firstChild) { + return; + } + + container = cdoc.body.lastChild; + + // Detects connection failure + if (!container) { + socket.fire("close", "error"); + return false; + } + + socket.fire("open")._fire(readDirty(), true); + container.innerText = ""; + + stop = portal.support.iterate(function() { + var text = readDirty(); + + if (text) { + container.innerText = ""; + socket._fire(text, true); + } + + if (cdoc.readyState === "complete") { + socket.fire("close", "done"); + return false; + } + }); + + return false; + }); + }, + close: function() { + stop(); + doc.execCommand("Stop"); + } + }); + }, + // Streaming - XDomainRequest + streamxdr: function(socket, options) { + var xdr, + XDomainRequest = window.XDomainRequest; + + if (!XDomainRequest || !options.xdrURL || !options.xdrURL.call(socket, "t")) { + return; + } + + return portal.support.extend(portal.transports.httpbase(socket, options), { + open: function() { + var url = options.xdrURL.call(socket, socket.data("url")); + + socket.data("url", url); + + xdr = new XDomainRequest(); + xdr.onprogress = function() { + var index = socket.data("index"), + length = xdr.responseText.length; + + if (!index) { + socket.fire("open")._fire(xdr.responseText, true); + } else { + socket._fire(xdr.responseText.substring(index, length), true); + } + + socket.data("index", length); + }; + xdr.onerror = function() { + socket.fire("close", "error"); + }; + xdr.onload = function() { + socket.fire("close", "done"); + }; + + xdr.open("GET", url); + xdr.send(); + }, + close: function() { + xdr.abort(); + } + }); + }, + // Long polling facade + longpoll: function(socket) { + socket.data("candidates").unshift("longpollajax", "longpollxdr", "longpolljsonp"); + }, + // Long polling - AJAX + longpollajax: function(socket, options) { + var xhr, + aborted, + // deprecated + count = 0; + + if (options.crossDomain && !portal.support.corsable) { + return; + } + + return portal.support.extend(portal.transports.httpbase(socket, options), { + open: function() { + function poll() { + var url = socket.buildURL(!count ? "open" : "poll", {count: ++count}); + + socket.data("url", url); + + xhr = portal.support.xhr(); + xhr.onreadystatechange = function() { + var data; + + // Avoids c00c023f error on Internet Explorer 9 + if (!aborted && xhr.readyState === 4) { + if (xhr.status === 200) { + data = xhr.responseText; + if (data || count === 1) { + if (count === 1) { + socket.fire("open"); + } + if (data) { + socket._fire(data); + } + poll(); + } else { + socket.fire("close", "done"); + } + } else { + socket.fire("close", "error"); + } + } + }; + + xhr.open("GET", url); + if (portal.support.corsable) { + xhr.withCredentials = options.credentials; + } + + xhr.send(null); + } + + poll(); + }, + close: function() { + aborted = true; + xhr.abort(); + } + }); + }, + // Long polling - XDomainRequest + longpollxdr: function(socket, options) { + var xdr, + // deprecated + count = 0, + XDomainRequest = window.XDomainRequest; + + if (!XDomainRequest || !options.xdrURL || !options.xdrURL.call(socket, "t")) { + return; + } + + return portal.support.extend(portal.transports.httpbase(socket, options), { + open: function() { + function poll() { + var url = options.xdrURL.call(socket, socket.buildURL(!count ? "open" : "poll", {count: ++count})); + + socket.data("url", url); + + xdr = new XDomainRequest(); + xdr.onload = function() { + var data = xdr.responseText; + + if (data || count === 1) { + if (count === 1) { + socket.fire("open"); + } + if (data) { + socket._fire(data); + } + poll(); + } else { + socket.fire("close", "done"); + } + }; + xdr.onerror = function() { + socket.fire("close", "error"); + }; + + xdr.open("GET", url); + xdr.send(); + } + + poll(); + }, + close: function() { + xdr.abort(); + } + }); + }, + // Long polling - JSONP + longpolljsonp: function(socket, options) { + var script, + called, + // deprecated + count = 0, + callback = jsonpCallbacks.pop() || ("socket_" + (++guid)); + + return portal.support.extend(portal.transports.httpbase(socket, options), { + open: function() { + function poll() { + var url = socket.buildURL(!count ? "open" : "poll", {callback: callback, count: ++count}), + head = document.head || document.getElementsByTagName("head")[0] || document.documentElement; + + socket.data("url", url); + + script = document.createElement("script"); + script.async = true; + script.src = url; + script.clean = function() { + script.clean = script.onerror = script.onload = script.onreadystatechange = null; + if (script.parentNode) { + script.parentNode.removeChild(script); + } + }; + script.onload = script.onreadystatechange = function() { + if (!script.readyState || /loaded|complete/.test(script.readyState)) { + script.clean(); + if (called) { + called = false; + poll(); + } else if (count === 1) { + socket.fire("open"); + poll(); + } else { + socket.fire("close", "done"); + } + } + }; + script.onerror = function() { + script.clean(); + socket.fire("close", "error"); + }; + + head.insertBefore(script, head.firstChild); + } + + // Attaches callback + window[callback] = function(data) { + called = true; + if (count === 1) { + socket.fire("open"); + } + socket._fire(data); + }; + socket.one("close", function() { + // Assings an empty function for browsers which are not able to cancel a request made from script tag + window[callback] = function() {}; + jsonpCallbacks.push(callback); + }); + + poll(); + }, + close: function() { + if (script.clean) { + script.clean(); + } + } + }); + } + }; + + // Closes all sockets + portal.finalize = function() { + var url, socket; + + for (url in sockets) { + socket = sockets[url]; + if (socket.state() !== "closed") { + socket.close(); + } + + // To run the test suite + delete sockets[url]; + } + }; + + portal.support.on(window, "unload", function() { + // Check the unload event is fired by the browser + unloading = true; + // Closes all sockets when the document is unloaded + portal.finalize(); + }); + portal.support.on(window, "online", function() { + var url, socket; + + for (url in sockets) { + socket = sockets[url]; + // There is no reason to wait + if (socket.state() === "waiting") { + socket.open(); + } + } + }); + portal.support.on(window, "offline", function() { + var url, socket; + + for (url in sockets) { + socket = sockets[url]; + // Closes sockets which cannot detect disconnection manually + if (socket.state() === "opened") { + socket.fire("close", "error"); + } + } + }); + + // Exposes portal to the global object + window.portal = portal; + +})(); +/* jshint noarg:true, noempty:true, eqeqeq:true, evil:true, laxbreak:true, undef:true, browser:true, jquery:true, indent:4, maxerr:50 */ diff --git a/ajax/libs/portal/1.0.1/portal.min.js b/ajax/libs/portal/1.0.1/portal.min.js new file mode 100644 index 000000000..1570c7921 --- /dev/null +++ b/ajax/libs/portal/1.0.1/portal.min.js @@ -0,0 +1,42 @@ +/* Portal v1.0.1 | http://github.com/flowersinthesand/portal | (c) 2011-2013, Donghwan Kim | http://www.apache.org/licenses/LICENSE-2.0 */ +(function(){function y(a){var c=[],b,d,g,e,l,m,q=function(b,n){n=n||[];d=!a||[b,n];g=!0;m=e||0;e=0;for(l=c.length;m',document.body.appendChild(b));e=b.firstChild.contentWindow;return{init:function(){e.callbacks=[c];e.fire=function(a){var b;for(b=0;bb._heartbeat&&(a(),h.one("close",c));e.connecting.lock();for(s=n=p=null;q.length;)h.send.apply(h, +q.shift())},close:function(){g="closed";var a,c,d=e.close.order;for(a in e)c=e[a],c.order';return encodeURI(decodeURI(c.firstChild.href))},iterate:function(a){var c;(function f(){c= +setTimeout(function(){!1!==a()&&f()},1)})();return function(){clearTimeout(c)}},each:function(a,c){var b;for(b=0;ba?"0"+a:a}var d=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,g={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f", +"\r":"\\r",'"':'\\"',"\\":"\\\\"};return window.JSON&&window.JSON.stringify?window.JSON.stringify(a):function l(a,d){var f,g,p,k=d[a];p=typeof k;k&&"object"===typeof k&&"function"===typeof k.toJSON&&(k=k.toJSON(a),p=typeof k);switch(p){case "string":return c(k);case "number":return isFinite(k)?String(k):"null";case "boolean":return String(k);case "object":if(!k)return"null";switch(w.call(k)){case "[object Date]":return isFinite(k.valueOf())?'"'+k.getUTCFullYear()+"-"+b(k.getUTCMonth()+1)+"-"+b(k.getUTCDate())+ +"T"+b(k.getUTCHours())+":"+b(k.getUTCMinutes())+":"+b(k.getUTCSeconds())+'Z"':"null";case "[object Array]":g=k.length;p=[];for(f=0;fa.indexOf("compatible")&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(a)||[];d.support.browser[a[1]||""]=!0;d.support.browser.version=a[2]||"0";if(d.support.browser.msie||d.support.browser.mozilla&&"1"===d.support.browser.version.split(".")[0])d.support.storage=!1})();d.find=function(a){var c;if(!arguments.length){for(c in r)if(r[c])return r[c];return null}return r[d.support.getAbsoluteURL(a)]||null}; +d.open=function(a,c){a=d.support.getAbsoluteURL(a);r[a]=A(a,c);return d.find(a)};d.defaults={transports:["ws","sse","stream","longpoll"],timeout:!1,heartbeat:!1,_heartbeat:5E3,lastEventId:0,sharing:!1,prepare:function(a){a()},reconnect:function(a){return 2*(a||250)},idGenerator:function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(a){var c=16*Math.random()|0;return("x"===a?c:c&3|8).toString(16)})},urlBuilder:function(a,c,b){return a+(/\?/.test(a)?"&":"?")+"when="+b+"&"+ +d.support.param(c)},inbound:d.support.parseJSON,outbound:d.support.stringifyJSON,credentials:!1,notifyAbort:!1,xdrURL:function(a){var c=/(?:^|; )(JSESSIONID|PHPSESSID)=([^;]*)/.exec(document.cookie);switch(c&&c[1]){case "JSESSIONID":return a.replace(/;jsessionid=[^\?]*|(\?)|$/,";jsessionid="+c[2]+"$1");case "PHPSESSID":return a.replace(/\?PHPSESSID=[^&]*&?|\?|$/,"?PHPSESSID="+c[2]+"&").replace(/&$/,"");default:return!1}},streamParser:function(a){var c=/\r\n|[\r\n]/g,b=[],d=this.data("data"),g=[], +e=0,l;for(a=a.replace(/^\s+/g,"");l=c.exec(a);)b.push(a.substring(e,l.index)),e=l.index+l[0].length;b.push(a.length===e?"":a.substring(e));d||(d=[],this.data("data",d));for(e=0;e';e.firstChild.value=c;d.support.on(e.lastChild,"load",function(){document.body.removeChild(e);b()});document.body.appendChild(e);e.submit()};return{send:function(a){e.push(a); +g||(g=!0,b())}}},sse:function(a,c){var b,f=window.EventSource;if(f)return d.support.extend(d.transports.httpbase(a,c),{open:function(){var d=a.data("url");b=c.crossDomain?new f(d,{withCredentials:c.credentials}):new f(d);b.onopen=function(b){a.data("event",b).fire("open")};b.onmessage=function(b){a.data("event",b)._fire(b.data)};b.onerror=function(c){b.close();a.data("event",c).fire("close","done")}},close:function(){b.close()}})},stream:function(a){a.data("candidates").unshift("streamxhr","streamxdr", +"streamiframe")},streamxhr:function(a,c){var b;if(!(d.support.browser.msie&&10>+d.support.browser.version||c.crossDomain&&!d.support.corsable))return d.support.extend(d.transports.httpbase(a,c),{open:function(){var f;b=d.support.xhr();b.onreadystatechange=function(){function c(){var e=a.data("index"),d=b.responseText.length;e?d>e&&a._fire(b.responseText.substring(e,d),!0):a.fire("open")._fire(b.responseText,!0);a.data("index",d)}3===b.readyState&&200===b.status?d.support.browser.opera&&!f?f=d.support.iterate(c): +c():4===b.readyState&&(f&&f(),a.fire("close",200===b.status?"done":"error"))};b.open("GET",a.data("url"));d.support.corsable&&(b.withCredentials=c.credentials);b.send(null)},close:function(){b.abort()}})},streamiframe:function(a,c){var b,f,g=window.ActiveXObject;if(g&&!c.crossDomain){try{new g("htmlfile")}catch(e){return}return d.support.extend(d.transports.httpbase(a,c),{open:function(){var c,e;b=new g("htmlfile");b.open();b.close();c=b.createElement("iframe");c.src=a.data("url");b.body.appendChild(c); +e=c.contentDocument||c.contentWindow.document;f=d.support.iterate(function(){function b(){var a=c.cloneNode(!0);a.appendChild(e.createTextNode("."));a=a.innerText;return a.substring(0,a.length-1)}var c;if(e.firstChild){c=e.body.lastChild;if(!c)return a.fire("close","error"),!1;a.fire("open")._fire(b(),!0);c.innerText="";f=d.support.iterate(function(){var d=b();d&&(c.innerText="",a._fire(d,!0));if("complete"===e.readyState)return a.fire("close","done"),!1});return!1}})},close:function(){f();b.execCommand("Stop")}})}}, +streamxdr:function(a,c){var b,f=window.XDomainRequest;if(f&&c.xdrURL&&c.xdrURL.call(a,"t"))return d.support.extend(d.transports.httpbase(a,c),{open:function(){var d=c.xdrURL.call(a,a.data("url"));a.data("url",d);b=new f;b.onprogress=function(){var c=a.data("index"),d=b.responseText.length;c?a._fire(b.responseText.substring(c,d),!0):a.fire("open")._fire(b.responseText,!0);a.data("index",d)};b.onerror=function(){a.fire("close","error")};b.onload=function(){a.fire("close","done")};b.open("GET",d);b.send()}, +close:function(){b.abort()}})},longpoll:function(a){a.data("candidates").unshift("longpollajax","longpollxdr","longpolljsonp")},longpollajax:function(a,c){var b,f,g=0;if(!c.crossDomain||d.support.corsable)return d.support.extend(d.transports.httpbase(a,c),{open:function(){function e(){var l=a.buildURL(g?"poll":"open",{count:++g});a.data("url",l);b=d.support.xhr();b.onreadystatechange=function(){var c;f||4!==b.readyState||(200===b.status?(c=b.responseText)||1===g?(1===g&&a.fire("open"),c&&a._fire(c), +e()):a.fire("close","done"):a.fire("close","error"))};b.open("GET",l);d.support.corsable&&(b.withCredentials=c.credentials);b.send(null)}e()},close:function(){f=!0;b.abort()}})},longpollxdr:function(a,c){var b,f=0,g=window.XDomainRequest;if(g&&c.xdrURL&&c.xdrURL.call(a,"t"))return d.support.extend(d.transports.httpbase(a,c),{open:function(){function d(){var l=c.xdrURL.call(a,a.buildURL(f?"poll":"open",{count:++f}));a.data("url",l);b=new g;b.onload=function(){var c=b.responseText;c||1===f?(1===f&& +a.fire("open"),c&&a._fire(c),d()):a.fire("close","done")};b.onerror=function(){a.fire("close","error")};b.open("GET",l);b.send()}d()},close:function(){b.abort()}})},longpolljsonp:function(a,c){var b,f,g=0,e=z.pop()||"socket_"+ ++u;return d.support.extend(d.transports.httpbase(a,c),{open:function(){function c(){var d=a.buildURL(g?"poll":"open",{callback:e,count:++g}),q=document.head||document.getElementsByTagName("head")[0]||document.documentElement;a.data("url",d);b=document.createElement("script"); +b.async=!0;b.src=d;b.clean=function(){b.clean=b.onerror=b.onload=b.onreadystatechange=null;b.parentNode&&b.parentNode.removeChild(b)};b.onload=b.onreadystatechange=function(){if(!b.readyState||/loaded|complete/.test(b.readyState))b.clean(),f?(f=!1,c()):1===g?(a.fire("open"),c()):a.fire("close","done")};b.onerror=function(){b.clean();a.fire("close","error")};q.insertBefore(b,q.firstChild)}window[e]=function(b){f=!0;1===g&&a.fire("open");a._fire(b)};a.one("close",function(){window[e]=function(){};z.push(e)}); +c()},close:function(){b.clean&&b.clean()}})}};d.finalize=function(){var a,c;for(a in r)c=r[a],"closed"!==c.state()&&c.close(),delete r[a]};d.support.on(window,"unload",function(){v=!0;d.finalize()});d.support.on(window,"online",function(){var a,c;for(a in r)c=r[a],"waiting"===c.state()&&c.open()});d.support.on(window,"offline",function(){var a,c;for(a in r)c=r[a],"opened"===c.state()&&c.fire("close","error")});window.portal=d})(); \ No newline at end of file diff --git a/ajax/libs/portal/package.json b/ajax/libs/portal/package.json index ff87196f0..c035dd2e4 100644 --- a/ajax/libs/portal/package.json +++ b/ajax/libs/portal/package.json @@ -1,27 +1,27 @@ -{ - "name": "portal", - "filename": "portal.min.js", - "version": "1.0", - "description": "The Portal is a server agnostic JavaScript library that not just provides a socket for browser-based applications that need two-way communication with servers, but also aims to utilize a full duplex connection for modern web application development.", - "homepage": "http://github.com/flowersinthesand/portal", - "keywords": [ - "ws", - "websocket", - "sse", - "serversentevents", - "comet", - "streaming", - "longpolling" - ], - "maintainers": [ - { - "name": "Donghwan Kim" - } - ], - "repositories": [ - { - "type": "git", - "url": "https://github.com/flowersinthesand/portal.git" - } - ] -} +{ + "name": "portal", + "filename": "portal.min.js", + "version": "1.0.1", + "description": "The Portal is a server agnostic JavaScript library that not just provides a socket for browser-based applications that need two-way communication with servers, but also aims to utilize a full duplex connection for modern web application development.", + "homepage": "http://github.com/flowersinthesand/portal", + "keywords": [ + "ws", + "websocket", + "sse", + "serversentevents", + "comet", + "streaming", + "longpolling" + ], + "maintainers": [ + { + "name": "Donghwan Kim" + } + ], + "repositories": [ + { + "type": "git", + "url": "https://github.com/flowersinthesand/portal.git" + } + ] +} From 45c487aff70db6c13616934289315b1b9e2e9105 Mon Sep 17 00:00:00 2001 From: Donghwan Kim Date: Sat, 12 Oct 2013 02:40:19 +0900 Subject: [PATCH 2/2] Update portal to 1.0.1 --- ajax/libs/portal/1.0.1/portal.js | 1884 ++++++++++++++++++++++++++ ajax/libs/portal/1.0.1/portal.min.js | 42 + ajax/libs/portal/package.json | 54 +- 3 files changed, 1953 insertions(+), 27 deletions(-) create mode 100644 ajax/libs/portal/1.0.1/portal.js create mode 100644 ajax/libs/portal/1.0.1/portal.min.js diff --git a/ajax/libs/portal/1.0.1/portal.js b/ajax/libs/portal/1.0.1/portal.js new file mode 100644 index 000000000..470aad158 --- /dev/null +++ b/ajax/libs/portal/1.0.1/portal.js @@ -0,0 +1,1884 @@ +/* + * Portal v1.0.1 + * 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 + */ +(function() { + + "use strict"; + + var // A global identifier + guid, + // Is the unload event being processed? + unloading, + // Portal + portal = {}, + // Socket instances + sockets = {}, + // Callback names for JSONP + jsonpCallbacks = [], + // Core prototypes + toString = Object.prototype.toString, + hasOwn = Object.prototype.hasOwnProperty, + slice = Array.prototype.slice; + + // Convenience utilities + // Most utility functions are borrowed from jQuery + portal.support = { + now: function() { + return new Date().getTime(); + }, + isArray: function(array) { + return toString.call(array) === "[object Array]"; + }, + isBinary: function(data) { + var string = toString.call(data); + return string === "[object Blob]" || string === "[object ArrayBuffer]"; + }, + isFunction: function(fn) { + return toString.call(fn) === "[object Function]"; + }, + getAbsoluteURL: function(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 IE and non-IE, + // since IE doesn't encode the href property value and return it - http://jsfiddle.net/Yq9M8/1/ + return encodeURI(decodeURI(div.firstChild.href)); + }, + iterate: function(fn) { + var timeoutId; + + // Though the interval is 1ms 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 + (function loop() { + timeoutId = setTimeout(function() { + if (fn() === false) { + return; + } + + loop(); + }, 1); + })(); + + return function() { + clearTimeout(timeoutId); + }; + }, + each: function(array, callback) { + var i; + + for (i = 0; i < array.length; i++) { + callback(i, array[i]); + } + }, + extend: function(target) { + var i, options, name; + + for (i = 1; i < arguments.length; i++) { + if ((options = arguments[i]) != null) { + for (name in options) { + target[name] = options[name]; + } + } + } + + return target; + }, + on: function(elem, type, fn) { + if (elem.addEventListener) { + elem.addEventListener(type, fn, false); + } else if (elem.attachEvent) { + elem.attachEvent("on" + type, fn); + } + }, + off: function(elem, type, fn) { + if (elem.removeEventListener) { + elem.removeEventListener(type, fn, false); + } else if (elem.detachEvent) { + elem.detachEvent("on" + type, fn); + } + }, + param: function(params) { + var prefix, s = []; + + function add(key, value) { + value = portal.support.isFunction(value) ? value() : (value == null ? "" : value); + s.push(encodeURIComponent(key) + "=" + encodeURIComponent(value)); + } + + function buildParams(prefix, obj) { + var name; + + if (portal.support.isArray(obj)) { + portal.support.each(obj, function(i, v) { + if (/\[\]$/.test(prefix)) { + add(prefix, v); + } else { + buildParams(prefix + "[" + (typeof v === "object" ? i : "") + "]", v); + } + }); + } else if (obj != null && toString.call(obj) === "[object Object]") { + for (name in obj) { + buildParams(prefix + "[" + name + "]", obj[name]); + } + } else { + add(prefix, obj); + } + } + + for (prefix in params) { + buildParams(prefix, params[prefix]); + } + + return s.join("&").replace(/%20/g, "+"); + }, + xhr: function() { + try { + return new window.XMLHttpRequest(); + } catch (e1) { + try { + return new window.ActiveXObject("Microsoft.XMLHTTP"); + } catch (e2) {} + } + }, + parseJSON: function(data) { + return !data ? + null : + window.JSON && window.JSON.parse ? + window.JSON.parse(data) : + new Function("return " + data)(); + }, + // http://github.com/flowersinthesand/stringifyJSON + stringifyJSON: function(value) { + 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; + } + + return window.JSON && window.JSON.stringify ? + window.JSON.stringify(value) : + (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 (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 (hasOwn.call(value, i)) { + v = str(i, value); + if (v) { + partial.push(quote(i) + ":" + v); + } + } + } + + return "{" + partial.join(",") + "}"; + } + } + })("", {"": value}); + }, + browser: {}, + storage: !!(window.localStorage && window.StorageEvent) + }; + portal.support.corsable = "withCredentials" in portal.support.xhr(); + guid = portal.support.now(); + + // Browser sniffing + (function() { + var ua = navigator.userAgent.toLowerCase(), + 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) || + []; + + portal.support.browser[match[1] || ""] = true; + portal.support.browser.version = match[2] || "0"; + + // The storage event of Internet Explorer and Firefox 3 works strangely + if (portal.support.browser.msie || (portal.support.browser.mozilla && portal.support.browser.version.split(".")[0] === "1")) { + portal.support.storage = false; + } + })(); + + // Finds the socket object which is mapped to the given url + portal.find = function(url) { + var i; + + // Returns the first socket in the document + if (!arguments.length) { + for (i in sockets) { + if (sockets[i]) { + return sockets[i]; + } + } + return null; + } + + // The url is a identifier of this socket within the document + return sockets[portal.support.getAbsoluteURL(url)] || null; + }; + // Creates a new socket and connects to the given url + portal.open = function(url, options) { + // Makes url absolute to normalize URL + url = portal.support.getAbsoluteURL(url); + sockets[url] = socket(url, options); + + return portal.find(url); + }; + // Default options + portal.defaults = { + // Socket options + transports: ["ws", "sse", "stream", "longpoll"], + timeout: false, + heartbeat: false, + _heartbeat: 5000, + lastEventId: 0, + sharing: false, + prepare: function(connect) { + connect(); + }, + reconnect: function(lastDelay) { + return 2 * (lastDelay || 250); + }, + idGenerator: function() { + // Generates a random UUID + // Logic borrowed from http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523 + return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) { + var r = Math.random() * 16 | 0, v = c === "x" ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); + }, + urlBuilder: function(url, params, when) { + return url + (/\?/.test(url) ? "&" : "?") + "when=" + when + "&" + portal.support.param(params); + }, + inbound: portal.support.parseJSON, + outbound: portal.support.stringifyJSON, + + // Transport options + credentials: false, + notifyAbort: false, + xdrURL: function(url) { + // Maintaining session by rewriting URL + // http://stackoverflow.com/questions/6453779/maintaining-session-by-rewriting-url + var match = /(?:^|; )(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(/&$/, ""); + default: + return false; + } + }, + streamParser: function(chunk) { + // Chunks are formatted according to the event stream format + // http://www.w3.org/TR/eventsource/#event-stream-interpretation + var reol = /\r\n|[\r\n]/g, lines = [], data = this.data("data"), array = [], i = 0, + match, line; + + // Strips off the left padding of the chunk + // the first chunk of some streaming transports and every chunk for Android browser 2 and 3 has padding + chunk = chunk.replace(/^\s+/g, ""); + + // String.prototype.split is not reliable cross-browser + while (match = reol.exec(chunk)) { + lines.push(chunk.substring(i, match.index)); + i = match.index + match[0].length; + } + lines.push(chunk.length === i ? "" : chunk.substring(i)); + + if (!data) { + data = []; + this.data("data", data); + } + + // Processes the data field only + for (i = 0; i < lines.length; i++) { + line = lines[i]; + if (!line) { + // Finish + array.push(data.join("\n")); + data = []; + this.data("data", data); + } else if (/^data:\s/.test(line)) { + // A single data field + data.push(line.substring("data: ".length)); + } else { + // A fragment of a data field + data[data.length - 1] += line; + } + } + + return array; + } + }; + + // Callback function + function callbacks(deferred) { + var list = [], + locked, + memory, + firing, + firingStart, + firingLength, + firingIndex, + fire = function(context, args) { + args = args || []; + memory = !deferred || [context, args]; + firing = true; + firingIndex = firingStart || 0; + firingStart = 0; + firingLength = list.length; + for (; firingIndex < firingLength; firingIndex++) { + list[firingIndex].apply(context, args); + } + firing = false; + }, + self = { + add: function(fn) { + var length = list.length; + + list.push(fn); + if (firing) { + firingLength = list.length; + } else if (!locked && memory && memory !== true) { + firingStart = length; + fire(memory[0], memory[1]); + } + }, + remove: function(fn) { + var i; + + for (i = 0; i < list.length; i++) { + if (fn === list[i] || (fn.guid && fn.guid === list[i].guid)) { + if (firing) { + if (i <= firingLength) { + firingLength--; + if (i <= firingIndex) { + firingIndex--; + } + } + } + list.splice(i--, 1); + } + } + }, + fire: function(context, args) { + if (!locked && !firing && !(deferred && memory)) { + fire(context, args); + } + }, + lock: function() { + locked = true; + }, + locked: function() { + return !!locked; + }, + unlock: function() { + locked = memory = firing = firingStart = firingLength = firingIndex = undefined; + } + }; + + return self; + } + + // Socket function + function socket(url, options) { + var // Final options + opts, + // Transport + transport, + // The state of the connection + state, + // Event helpers + events = {}, + eventId = 0, + // Reply callbacks + replyCallbacks = {}, + // Buffer + buffer = [], + // Reconnection + reconnectTimer, + reconnectDelay, + reconnectTry, + // Map of the connection-scoped values + connection = {}, + parts = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/.exec(url.toLowerCase()), + // Socket object + self = { + // Finds the value of an option + option: function(key, /* undocumented */ value) { + if (value === undefined) { + return opts[key]; + } + + opts[key] = value; + + return this; + }, + // Gets or sets a connection-scoped value + data: function(key, value) { + if (value === undefined) { + return connection[key]; + } + + connection[key] = value; + + return this; + }, + // Returns the state + state: function() { + return state; + }, + // Adds event handler + on: function(type, fn) { + var event; + + // Handles a map of type and handler + if (typeof type === "object") { + for (event in type) { + self.on(event, type[event]); + } + return this; + } + + // For custom event + event = events[type]; + if (!event) { + if (events.message.locked()) { + return this; + } + + event = events[type] = callbacks(); + event.order = events.message.order; + } + + event.add(fn); + + return this; + }, + // Removes event handler + off: function(type, fn) { + var event = events[type]; + + if (event) { + event.remove(fn); + } + + return this; + }, + // Adds one time event handler + one: function(type, fn) { + function proxy() { + self.off(type, proxy); + fn.apply(self, arguments); + } + + fn.guid = fn.guid || guid++; + proxy.guid = fn.guid; + + return self.on(type, proxy); + }, + // Fires event handlers + fire: function(type) { + var event = events[type]; + + if (event) { + event.fire(self, slice.call(arguments, 1)); + } + + return this; + }, + // Establishes a connection + open: function() { + var type, + latch, + connect = function() { + var candidates, type; + + if (!latch) { + latch = true; + candidates = connection.candidates = slice.call(opts.transports); + while (!transport && candidates.length) { + type = candidates.shift(); + connection.transport = type; + connection.url = self.buildURL("open"); + transport = portal.transports[type](self, opts); + } + + // Increases the number of reconnection attempts + if (reconnectTry) { + reconnectTry++; + } + + // Fires the connecting event and connects + if (transport) { + self.fire("connecting"); + transport.open(); + } else { + self.fire("close", "notransport"); + } + } + }, + cancel = function() { + if (!latch) { + latch = true; + self.fire("close", "canceled"); + } + }; + + // Cancels the scheduled connection + if (reconnectTimer) { + clearTimeout(reconnectTimer); + } + + // Resets the connection scope and event helpers + connection = {}; + for (type in events) { + events[type].unlock(); + } + + // Chooses transport + transport = undefined; + + // From null or waiting state + state = "preparing"; + + // Check if possible to make use of a shared socket + if (opts.sharing) { + connection.transport = "session"; + transport = portal.transports.session(self, opts); + } + + // Executes the prepare handler if a physical connection is needed + if (transport) { + connect(); + } else { + opts.prepare.call(self, connect, cancel, opts); + } + + return this; + }, + // Sends an event to the server via the connection + send: function(type, data, doneCallback, failCallback) { + var event; + + // Defers sending an event until the state become opened + if (state !== "opened") { + buffer.push(arguments); + return this; + } + + // Outbound event + event = { + id: ++eventId, + socket: opts.id, + type: type, + data: data, + reply: !!(doneCallback || failCallback) + }; + + if (event.reply) { + // Shared socket needs to know the callback event name + // because it fires the callback event directly instead of using reply event + if (connection.transport === "session") { + event.doneCallback = doneCallback; + event.failCallback = failCallback; + } else { + replyCallbacks[eventId] = {done: doneCallback, fail: failCallback}; + } + } + + // Delegates to the transport + transport.send(portal.support.isBinary(data) ? data : opts.outbound.call(self, event)); + + return this; + }, + // Disconnects the connection + close: function() { + var script, head; + + // Prevents reconnection + opts.reconnect = false; + if (reconnectTimer) { + clearTimeout(reconnectTimer); + } + + // Fires the close event immediately for transport which doesn't give feedback on disconnection + if (unloading || !transport || !transport.feedback) { + self.fire("close", unloading ? "error" : "aborted"); + if (opts.notifyAbort && connection.transport !== "session") { + head = document.head || document.getElementsByTagName("head")[0] || document.documentElement; + script = document.createElement("script"); + script.async = false; + script.src = self.buildURL("abort"); + script.onload = script.onreadystatechange = function() { + if (!script.readyState || /loaded|complete/.test(script.readyState)) { + script.onload = script.onreadystatechange = null; + if (script.parentNode) { + script.parentNode.removeChild(script); + } + } + }; + head.insertBefore(script, head.firstChild); + } + } + + // Delegates to the transport + if (transport) { + transport.close(); + } + + return this; + }, + // Broadcasts event to session sockets + broadcast: function(type, data) { + // TODO rename + var broadcastable = connection.broadcastable; + if (broadcastable) { + broadcastable.broadcast({type: "fire", data: {type: type, data: data}}); + } + + return this; + }, + // For internal use only + // fires events from the server + _fire: function(data, isChunk) { + var array; + + if (isChunk) { + data = opts.streamParser.call(self, data); + while (data.length) { + self._fire(data.shift()); + } + return this; + } + + if (portal.support.isBinary(data)) { + array = [{type: "message", data: data}]; + } else { + array = opts.inbound.call(self, data); + array = array == null ? [] : !portal.support.isArray(array) ? [array] : array; + } + + connection.lastEventIds = []; + portal.support.each(array, function(i, event) { + var latch, args = [event.type, event.data]; + + opts.lastEventId = event.id; + connection.lastEventIds.push(event.id); + if (event.reply) { + args.push(function(result) { + if (!latch) { + latch = true; + self.send("reply", {id: event.id, data: result}); + } + }); + } + + self.fire.apply(self, args).fire("_message", args); + }); + + return this; + }, + // For internal use only + // builds an effective URL + buildURL: function(when, params) { + var p = when === "open" ? + { + transport: connection.transport, + heartbeat: opts.heartbeat, + lastEventId: opts.lastEventId + } : + when === "poll" ? + { + transport: connection.transport, + lastEventIds: connection.lastEventIds && connection.lastEventIds.join(","), + /* deprecated */lastEventId: opts.lastEventId + } : + {}; + + portal.support.extend(p, {id: opts.id, _: guid++}, opts.params && opts.params[when], params); + return opts.urlBuilder.call(self, url, p, when); + } + }; + + // Create the final options + opts = portal.support.extend({}, portal.defaults, options); + if (options) { + // Array should not be deep extended + if (options.transports) { + opts.transports = slice.call(options.transports); + } + } + // Saves original URL + opts.url = url; + // Generates socket id, + opts.id = opts.idGenerator.call(self); + opts.crossDomain = !!(parts && + // protocol and hostname + (parts[1] != location.protocol || parts[2] != location.hostname || + // port + (parts[3] || (parts[1] === "http:" ? 80 : 443)) != (location.port || (location.protocol === "http:" ? 80 : 443)))); + + portal.support.each(["connecting", "open", "message", "close", "waiting"], function(i, type) { + // Creates event helper + events[type] = callbacks(type !== "message"); + events[type].order = i; + + // Shortcuts for on method + var old = self[type], + on = function(fn) { + return self.on(type, fn); + }; + + self[type] = !old ? on : function(fn) { + return (portal.support.isFunction(fn) ? on : old).apply(this, arguments); + }; + }); + + // Initializes + self.on({ + connecting: function() { + // From preparing state + state = "connecting"; + + var timeoutTimer; + + // Sets timeout timer + function setTimeoutTimer() { + timeoutTimer = setTimeout(function() { + transport.close(); + self.fire("close", "timeout"); + }, opts.timeout); + } + + // Clears timeout timer + function clearTimeoutTimer() { + clearTimeout(timeoutTimer); + } + + // Makes the socket sharable + function share() { + var traceTimer, + server, + name = "socket-" + url, + servers = { + // Powered by the storage event and the localStorage + // http://www.w3.org/TR/webstorage/#event-storage + storage: function() { + if (!portal.support.storage) { + return; + } + + var storage = window.localStorage; + + return { + init: function() { + function onstorage(event) { + // When a deletion, newValue initialized to null + if (event.key === name && event.newValue) { + listener(event.newValue); + } + } + + // Handles the storage event + portal.support.on(window, "storage", onstorage); + self.one("close", function() { + portal.support.off(window, "storage", onstorage); + // Defers again to clean the storage + self.one("close", function() { + storage.removeItem(name); + storage.removeItem(name + "-opened"); + storage.removeItem(name + "-children"); + }); + }); + }, + broadcast: function(obj) { + var string = portal.support.stringifyJSON(obj); + storage.setItem(name, string); + setTimeout(function() { + listener(string); + }, 50); + }, + get: function(key) { + return portal.support.parseJSON(storage.getItem(name + "-" + key)); + }, + set: function(key, value) { + storage.setItem(name + "-" + key, portal.support.stringifyJSON(value)); + } + }; + }, + // 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 = ''; + + textarea = form.firstChild; + textarea.value = data; + + iframe = form.lastChild; + portal.support.on(iframe, "load", function() { + document.body.removeChild(form); + post(); + }); + + document.body.appendChild(form); + form.submit(); + }; + + return { + send: function(data) { + queue.push(data); + + if (!sending) { + sending = true; + post(); + } + } + }; + }, + // Server-Sent Events + sse: function(socket, options) { + var es, + EventSource = window.EventSource; + + if (!EventSource) { + return; + } + + return portal.support.extend(portal.transports.httpbase(socket, options), { + open: function() { + var url = socket.data("url"); + + // Uses proper constructor for Chrome 10-15 + es = !options.crossDomain ? new EventSource(url) : new EventSource(url, {withCredentials: options.credentials}); + es.onopen = function(event) { + socket.data("event", event).fire("open"); + }; + es.onmessage = function(event) { + socket.data("event", event)._fire(event.data); + }; + es.onerror = function(event) { + es.close(); + + // There is no way to find whether this connection closed normally or not + socket.data("event", event).fire("close", "done"); + }; + }, + close: function() { + es.close(); + } + }); + }, + // Streaming facade + stream: function(socket) { + socket.data("candidates").unshift("streamxhr", "streamxdr", "streamiframe"); + }, + // Streaming - XMLHttpRequest + streamxhr: function(socket, options) { + var xhr; + + if ((portal.support.browser.msie && +portal.support.browser.version < 10) || (options.crossDomain && !portal.support.corsable)) { + return; + } + + return portal.support.extend(portal.transports.httpbase(socket, options), { + open: function() { + var stop; + + xhr = portal.support.xhr(); + xhr.onreadystatechange = function() { + function onprogress() { + var index = socket.data("index"), + length = xhr.responseText.length; + + if (!index) { + socket.fire("open")._fire(xhr.responseText, true); + } else if (length > index) { + socket._fire(xhr.responseText.substring(index, length), true); + } + + socket.data("index", length); + } + + if (xhr.readyState === 3 && xhr.status === 200) { + // Despite the change in response, Opera doesn't fire the readystatechange event + if (portal.support.browser.opera && !stop) { + stop = portal.support.iterate(onprogress); + } else { + onprogress(); + } + } else if (xhr.readyState === 4) { + if (stop) { + stop(); + } + + socket.fire("close", xhr.status === 200 ? "done" : "error"); + } + }; + + xhr.open("GET", socket.data("url")); + if (portal.support.corsable) { + xhr.withCredentials = options.credentials; + } + + xhr.send(null); + }, + close: function() { + xhr.abort(); + } + }); + }, + // Streaming - Iframe + streamiframe: function(socket, options) { + var doc, + stop, + ActiveXObject = window.ActiveXObject; + + if (!ActiveXObject || options.crossDomain) { + return; + } else { + // IE 10 Metro doesn't support ActiveXObject + try { + new ActiveXObject("htmlfile"); + } catch (e) { + return; + } + } + + return portal.support.extend(portal.transports.httpbase(socket, options), { + open: function() { + var iframe, cdoc; + + doc = new ActiveXObject("htmlfile"); + doc.open(); + doc.close(); + + iframe = doc.createElement("iframe"); + iframe.src = socket.data("url"); + doc.body.appendChild(iframe); + + cdoc = iframe.contentDocument || iframe.contentWindow.document; + stop = portal.support.iterate(function() { + // Response container + var container; + + function readDirty() { + var clone = container.cloneNode(true), + text; + + // Adds a character not CR and LF to circumvent an Internet Explorer bug + // If the contents of an element ends with one or more CR or LF, Internet Explorer ignores them in the innerText property + clone.appendChild(cdoc.createTextNode(".")); + text = clone.innerText; + + return text.substring(0, text.length - 1); + } + + // Waits the server's container ignorantly + if (!cdoc.firstChild) { + return; + } + + container = cdoc.body.lastChild; + + // Detects connection failure + if (!container) { + socket.fire("close", "error"); + return false; + } + + socket.fire("open")._fire(readDirty(), true); + container.innerText = ""; + + stop = portal.support.iterate(function() { + var text = readDirty(); + + if (text) { + container.innerText = ""; + socket._fire(text, true); + } + + if (cdoc.readyState === "complete") { + socket.fire("close", "done"); + return false; + } + }); + + return false; + }); + }, + close: function() { + stop(); + doc.execCommand("Stop"); + } + }); + }, + // Streaming - XDomainRequest + streamxdr: function(socket, options) { + var xdr, + XDomainRequest = window.XDomainRequest; + + if (!XDomainRequest || !options.xdrURL || !options.xdrURL.call(socket, "t")) { + return; + } + + return portal.support.extend(portal.transports.httpbase(socket, options), { + open: function() { + var url = options.xdrURL.call(socket, socket.data("url")); + + socket.data("url", url); + + xdr = new XDomainRequest(); + xdr.onprogress = function() { + var index = socket.data("index"), + length = xdr.responseText.length; + + if (!index) { + socket.fire("open")._fire(xdr.responseText, true); + } else { + socket._fire(xdr.responseText.substring(index, length), true); + } + + socket.data("index", length); + }; + xdr.onerror = function() { + socket.fire("close", "error"); + }; + xdr.onload = function() { + socket.fire("close", "done"); + }; + + xdr.open("GET", url); + xdr.send(); + }, + close: function() { + xdr.abort(); + } + }); + }, + // Long polling facade + longpoll: function(socket) { + socket.data("candidates").unshift("longpollajax", "longpollxdr", "longpolljsonp"); + }, + // Long polling - AJAX + longpollajax: function(socket, options) { + var xhr, + aborted, + // deprecated + count = 0; + + if (options.crossDomain && !portal.support.corsable) { + return; + } + + return portal.support.extend(portal.transports.httpbase(socket, options), { + open: function() { + function poll() { + var url = socket.buildURL(!count ? "open" : "poll", {count: ++count}); + + socket.data("url", url); + + xhr = portal.support.xhr(); + xhr.onreadystatechange = function() { + var data; + + // Avoids c00c023f error on Internet Explorer 9 + if (!aborted && xhr.readyState === 4) { + if (xhr.status === 200) { + data = xhr.responseText; + if (data || count === 1) { + if (count === 1) { + socket.fire("open"); + } + if (data) { + socket._fire(data); + } + poll(); + } else { + socket.fire("close", "done"); + } + } else { + socket.fire("close", "error"); + } + } + }; + + xhr.open("GET", url); + if (portal.support.corsable) { + xhr.withCredentials = options.credentials; + } + + xhr.send(null); + } + + poll(); + }, + close: function() { + aborted = true; + xhr.abort(); + } + }); + }, + // Long polling - XDomainRequest + longpollxdr: function(socket, options) { + var xdr, + // deprecated + count = 0, + XDomainRequest = window.XDomainRequest; + + if (!XDomainRequest || !options.xdrURL || !options.xdrURL.call(socket, "t")) { + return; + } + + return portal.support.extend(portal.transports.httpbase(socket, options), { + open: function() { + function poll() { + var url = options.xdrURL.call(socket, socket.buildURL(!count ? "open" : "poll", {count: ++count})); + + socket.data("url", url); + + xdr = new XDomainRequest(); + xdr.onload = function() { + var data = xdr.responseText; + + if (data || count === 1) { + if (count === 1) { + socket.fire("open"); + } + if (data) { + socket._fire(data); + } + poll(); + } else { + socket.fire("close", "done"); + } + }; + xdr.onerror = function() { + socket.fire("close", "error"); + }; + + xdr.open("GET", url); + xdr.send(); + } + + poll(); + }, + close: function() { + xdr.abort(); + } + }); + }, + // Long polling - JSONP + longpolljsonp: function(socket, options) { + var script, + called, + // deprecated + count = 0, + callback = jsonpCallbacks.pop() || ("socket_" + (++guid)); + + return portal.support.extend(portal.transports.httpbase(socket, options), { + open: function() { + function poll() { + var url = socket.buildURL(!count ? "open" : "poll", {callback: callback, count: ++count}), + head = document.head || document.getElementsByTagName("head")[0] || document.documentElement; + + socket.data("url", url); + + script = document.createElement("script"); + script.async = true; + script.src = url; + script.clean = function() { + script.clean = script.onerror = script.onload = script.onreadystatechange = null; + if (script.parentNode) { + script.parentNode.removeChild(script); + } + }; + script.onload = script.onreadystatechange = function() { + if (!script.readyState || /loaded|complete/.test(script.readyState)) { + script.clean(); + if (called) { + called = false; + poll(); + } else if (count === 1) { + socket.fire("open"); + poll(); + } else { + socket.fire("close", "done"); + } + } + }; + script.onerror = function() { + script.clean(); + socket.fire("close", "error"); + }; + + head.insertBefore(script, head.firstChild); + } + + // Attaches callback + window[callback] = function(data) { + called = true; + if (count === 1) { + socket.fire("open"); + } + socket._fire(data); + }; + socket.one("close", function() { + // Assings an empty function for browsers which are not able to cancel a request made from script tag + window[callback] = function() {}; + jsonpCallbacks.push(callback); + }); + + poll(); + }, + close: function() { + if (script.clean) { + script.clean(); + } + } + }); + } + }; + + // Closes all sockets + portal.finalize = function() { + var url, socket; + + for (url in sockets) { + socket = sockets[url]; + if (socket.state() !== "closed") { + socket.close(); + } + + // To run the test suite + delete sockets[url]; + } + }; + + portal.support.on(window, "unload", function() { + // Check the unload event is fired by the browser + unloading = true; + // Closes all sockets when the document is unloaded + portal.finalize(); + }); + portal.support.on(window, "online", function() { + var url, socket; + + for (url in sockets) { + socket = sockets[url]; + // There is no reason to wait + if (socket.state() === "waiting") { + socket.open(); + } + } + }); + portal.support.on(window, "offline", function() { + var url, socket; + + for (url in sockets) { + socket = sockets[url]; + // Closes sockets which cannot detect disconnection manually + if (socket.state() === "opened") { + socket.fire("close", "error"); + } + } + }); + + // Exposes portal to the global object + window.portal = portal; + +})(); +/* jshint noarg:true, noempty:true, eqeqeq:true, evil:true, laxbreak:true, undef:true, browser:true, jquery:true, indent:4, maxerr:50 */ diff --git a/ajax/libs/portal/1.0.1/portal.min.js b/ajax/libs/portal/1.0.1/portal.min.js new file mode 100644 index 000000000..1570c7921 --- /dev/null +++ b/ajax/libs/portal/1.0.1/portal.min.js @@ -0,0 +1,42 @@ +/* Portal v1.0.1 | http://github.com/flowersinthesand/portal | (c) 2011-2013, Donghwan Kim | http://www.apache.org/licenses/LICENSE-2.0 */ +(function(){function y(a){var c=[],b,d,g,e,l,m,q=function(b,n){n=n||[];d=!a||[b,n];g=!0;m=e||0;e=0;for(l=c.length;m',document.body.appendChild(b));e=b.firstChild.contentWindow;return{init:function(){e.callbacks=[c];e.fire=function(a){var b;for(b=0;bb._heartbeat&&(a(),h.one("close",c));e.connecting.lock();for(s=n=p=null;q.length;)h.send.apply(h, +q.shift())},close:function(){g="closed";var a,c,d=e.close.order;for(a in e)c=e[a],c.order';return encodeURI(decodeURI(c.firstChild.href))},iterate:function(a){var c;(function f(){c= +setTimeout(function(){!1!==a()&&f()},1)})();return function(){clearTimeout(c)}},each:function(a,c){var b;for(b=0;ba?"0"+a:a}var d=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,g={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f", +"\r":"\\r",'"':'\\"',"\\":"\\\\"};return window.JSON&&window.JSON.stringify?window.JSON.stringify(a):function l(a,d){var f,g,p,k=d[a];p=typeof k;k&&"object"===typeof k&&"function"===typeof k.toJSON&&(k=k.toJSON(a),p=typeof k);switch(p){case "string":return c(k);case "number":return isFinite(k)?String(k):"null";case "boolean":return String(k);case "object":if(!k)return"null";switch(w.call(k)){case "[object Date]":return isFinite(k.valueOf())?'"'+k.getUTCFullYear()+"-"+b(k.getUTCMonth()+1)+"-"+b(k.getUTCDate())+ +"T"+b(k.getUTCHours())+":"+b(k.getUTCMinutes())+":"+b(k.getUTCSeconds())+'Z"':"null";case "[object Array]":g=k.length;p=[];for(f=0;fa.indexOf("compatible")&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(a)||[];d.support.browser[a[1]||""]=!0;d.support.browser.version=a[2]||"0";if(d.support.browser.msie||d.support.browser.mozilla&&"1"===d.support.browser.version.split(".")[0])d.support.storage=!1})();d.find=function(a){var c;if(!arguments.length){for(c in r)if(r[c])return r[c];return null}return r[d.support.getAbsoluteURL(a)]||null}; +d.open=function(a,c){a=d.support.getAbsoluteURL(a);r[a]=A(a,c);return d.find(a)};d.defaults={transports:["ws","sse","stream","longpoll"],timeout:!1,heartbeat:!1,_heartbeat:5E3,lastEventId:0,sharing:!1,prepare:function(a){a()},reconnect:function(a){return 2*(a||250)},idGenerator:function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(a){var c=16*Math.random()|0;return("x"===a?c:c&3|8).toString(16)})},urlBuilder:function(a,c,b){return a+(/\?/.test(a)?"&":"?")+"when="+b+"&"+ +d.support.param(c)},inbound:d.support.parseJSON,outbound:d.support.stringifyJSON,credentials:!1,notifyAbort:!1,xdrURL:function(a){var c=/(?:^|; )(JSESSIONID|PHPSESSID)=([^;]*)/.exec(document.cookie);switch(c&&c[1]){case "JSESSIONID":return a.replace(/;jsessionid=[^\?]*|(\?)|$/,";jsessionid="+c[2]+"$1");case "PHPSESSID":return a.replace(/\?PHPSESSID=[^&]*&?|\?|$/,"?PHPSESSID="+c[2]+"&").replace(/&$/,"");default:return!1}},streamParser:function(a){var c=/\r\n|[\r\n]/g,b=[],d=this.data("data"),g=[], +e=0,l;for(a=a.replace(/^\s+/g,"");l=c.exec(a);)b.push(a.substring(e,l.index)),e=l.index+l[0].length;b.push(a.length===e?"":a.substring(e));d||(d=[],this.data("data",d));for(e=0;e';e.firstChild.value=c;d.support.on(e.lastChild,"load",function(){document.body.removeChild(e);b()});document.body.appendChild(e);e.submit()};return{send:function(a){e.push(a); +g||(g=!0,b())}}},sse:function(a,c){var b,f=window.EventSource;if(f)return d.support.extend(d.transports.httpbase(a,c),{open:function(){var d=a.data("url");b=c.crossDomain?new f(d,{withCredentials:c.credentials}):new f(d);b.onopen=function(b){a.data("event",b).fire("open")};b.onmessage=function(b){a.data("event",b)._fire(b.data)};b.onerror=function(c){b.close();a.data("event",c).fire("close","done")}},close:function(){b.close()}})},stream:function(a){a.data("candidates").unshift("streamxhr","streamxdr", +"streamiframe")},streamxhr:function(a,c){var b;if(!(d.support.browser.msie&&10>+d.support.browser.version||c.crossDomain&&!d.support.corsable))return d.support.extend(d.transports.httpbase(a,c),{open:function(){var f;b=d.support.xhr();b.onreadystatechange=function(){function c(){var e=a.data("index"),d=b.responseText.length;e?d>e&&a._fire(b.responseText.substring(e,d),!0):a.fire("open")._fire(b.responseText,!0);a.data("index",d)}3===b.readyState&&200===b.status?d.support.browser.opera&&!f?f=d.support.iterate(c): +c():4===b.readyState&&(f&&f(),a.fire("close",200===b.status?"done":"error"))};b.open("GET",a.data("url"));d.support.corsable&&(b.withCredentials=c.credentials);b.send(null)},close:function(){b.abort()}})},streamiframe:function(a,c){var b,f,g=window.ActiveXObject;if(g&&!c.crossDomain){try{new g("htmlfile")}catch(e){return}return d.support.extend(d.transports.httpbase(a,c),{open:function(){var c,e;b=new g("htmlfile");b.open();b.close();c=b.createElement("iframe");c.src=a.data("url");b.body.appendChild(c); +e=c.contentDocument||c.contentWindow.document;f=d.support.iterate(function(){function b(){var a=c.cloneNode(!0);a.appendChild(e.createTextNode("."));a=a.innerText;return a.substring(0,a.length-1)}var c;if(e.firstChild){c=e.body.lastChild;if(!c)return a.fire("close","error"),!1;a.fire("open")._fire(b(),!0);c.innerText="";f=d.support.iterate(function(){var d=b();d&&(c.innerText="",a._fire(d,!0));if("complete"===e.readyState)return a.fire("close","done"),!1});return!1}})},close:function(){f();b.execCommand("Stop")}})}}, +streamxdr:function(a,c){var b,f=window.XDomainRequest;if(f&&c.xdrURL&&c.xdrURL.call(a,"t"))return d.support.extend(d.transports.httpbase(a,c),{open:function(){var d=c.xdrURL.call(a,a.data("url"));a.data("url",d);b=new f;b.onprogress=function(){var c=a.data("index"),d=b.responseText.length;c?a._fire(b.responseText.substring(c,d),!0):a.fire("open")._fire(b.responseText,!0);a.data("index",d)};b.onerror=function(){a.fire("close","error")};b.onload=function(){a.fire("close","done")};b.open("GET",d);b.send()}, +close:function(){b.abort()}})},longpoll:function(a){a.data("candidates").unshift("longpollajax","longpollxdr","longpolljsonp")},longpollajax:function(a,c){var b,f,g=0;if(!c.crossDomain||d.support.corsable)return d.support.extend(d.transports.httpbase(a,c),{open:function(){function e(){var l=a.buildURL(g?"poll":"open",{count:++g});a.data("url",l);b=d.support.xhr();b.onreadystatechange=function(){var c;f||4!==b.readyState||(200===b.status?(c=b.responseText)||1===g?(1===g&&a.fire("open"),c&&a._fire(c), +e()):a.fire("close","done"):a.fire("close","error"))};b.open("GET",l);d.support.corsable&&(b.withCredentials=c.credentials);b.send(null)}e()},close:function(){f=!0;b.abort()}})},longpollxdr:function(a,c){var b,f=0,g=window.XDomainRequest;if(g&&c.xdrURL&&c.xdrURL.call(a,"t"))return d.support.extend(d.transports.httpbase(a,c),{open:function(){function d(){var l=c.xdrURL.call(a,a.buildURL(f?"poll":"open",{count:++f}));a.data("url",l);b=new g;b.onload=function(){var c=b.responseText;c||1===f?(1===f&& +a.fire("open"),c&&a._fire(c),d()):a.fire("close","done")};b.onerror=function(){a.fire("close","error")};b.open("GET",l);b.send()}d()},close:function(){b.abort()}})},longpolljsonp:function(a,c){var b,f,g=0,e=z.pop()||"socket_"+ ++u;return d.support.extend(d.transports.httpbase(a,c),{open:function(){function c(){var d=a.buildURL(g?"poll":"open",{callback:e,count:++g}),q=document.head||document.getElementsByTagName("head")[0]||document.documentElement;a.data("url",d);b=document.createElement("script"); +b.async=!0;b.src=d;b.clean=function(){b.clean=b.onerror=b.onload=b.onreadystatechange=null;b.parentNode&&b.parentNode.removeChild(b)};b.onload=b.onreadystatechange=function(){if(!b.readyState||/loaded|complete/.test(b.readyState))b.clean(),f?(f=!1,c()):1===g?(a.fire("open"),c()):a.fire("close","done")};b.onerror=function(){b.clean();a.fire("close","error")};q.insertBefore(b,q.firstChild)}window[e]=function(b){f=!0;1===g&&a.fire("open");a._fire(b)};a.one("close",function(){window[e]=function(){};z.push(e)}); +c()},close:function(){b.clean&&b.clean()}})}};d.finalize=function(){var a,c;for(a in r)c=r[a],"closed"!==c.state()&&c.close(),delete r[a]};d.support.on(window,"unload",function(){v=!0;d.finalize()});d.support.on(window,"online",function(){var a,c;for(a in r)c=r[a],"waiting"===c.state()&&c.open()});d.support.on(window,"offline",function(){var a,c;for(a in r)c=r[a],"opened"===c.state()&&c.fire("close","error")});window.portal=d})(); \ No newline at end of file diff --git a/ajax/libs/portal/package.json b/ajax/libs/portal/package.json index ff87196f0..c035dd2e4 100644 --- a/ajax/libs/portal/package.json +++ b/ajax/libs/portal/package.json @@ -1,27 +1,27 @@ -{ - "name": "portal", - "filename": "portal.min.js", - "version": "1.0", - "description": "The Portal is a server agnostic JavaScript library that not just provides a socket for browser-based applications that need two-way communication with servers, but also aims to utilize a full duplex connection for modern web application development.", - "homepage": "http://github.com/flowersinthesand/portal", - "keywords": [ - "ws", - "websocket", - "sse", - "serversentevents", - "comet", - "streaming", - "longpolling" - ], - "maintainers": [ - { - "name": "Donghwan Kim" - } - ], - "repositories": [ - { - "type": "git", - "url": "https://github.com/flowersinthesand/portal.git" - } - ] -} +{ + "name": "portal", + "filename": "portal.min.js", + "version": "1.0.1", + "description": "The Portal is a server agnostic JavaScript library that not just provides a socket for browser-based applications that need two-way communication with servers, but also aims to utilize a full duplex connection for modern web application development.", + "homepage": "http://github.com/flowersinthesand/portal", + "keywords": [ + "ws", + "websocket", + "sse", + "serversentevents", + "comet", + "streaming", + "longpolling" + ], + "maintainers": [ + { + "name": "Donghwan Kim" + } + ], + "repositories": [ + { + "type": "git", + "url": "https://github.com/flowersinthesand/portal.git" + } + ] +}