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 = '';
+ document.body.appendChild(container);
+ }
+
+ win = container.firstChild.contentWindow;
+
+ return {
+ init: function() {
+ // Callbacks from different windows
+ win.callbacks = [listener];
+ // In IE 8 and less, only string argument can be safely passed to the function in other window
+ win.fire = function(string) {
+ var i;
+
+ for (i = 0; i < win.callbacks.length; i++) {
+ win.callbacks[i](string);
+ }
+ };
+ },
+ broadcast: function(obj) {
+ if (!win.closed && win.fire) {
+ win.fire(portal.support.stringifyJSON(obj));
+ }
+ },
+ get: function(key) {
+ return !win.closed ? win[key] : null;
+ },
+ set: function(key, value) {
+ if (!win.closed) {
+ win[key] = value;
+ }
+ }
+ };
+ }
+ };
+
+ // Receives send and close command from the children
+ function listener(string) {
+ var command = portal.support.parseJSON(string), data = command.data;
+
+ if (!command.target) {
+ if (command.type === "fire") {
+ self.fire(data.type, data.data);
+ }
+ } else if (command.target === "p") {
+ switch (command.type) {
+ case "send":
+ self.send(data.type, data.data, data.doneCallback, data.failCallback);
+ break;
+ case "close":
+ self.close();
+ break;
+ }
+ }
+ }
+
+ function propagateMessageEvent(args) {
+ server.broadcast({target: "c", type: "message", data: args});
+ }
+
+ function leaveTrace() {
+ document.cookie = encodeURIComponent(name) + "=" +
+ // Opera 12.00's parseFloat and JSON.stringify causes a strange bug with a number larger than 10 digit
+ // JSON.stringify(parseFloat(10000000000) + 1).length === 11;
+ // JSON.stringify(parseFloat(10000000000 + 1)).length === 10;
+ encodeURIComponent(portal.support.stringifyJSON({ts: portal.support.now() + 1, heir: (server.get("children") || [])[0]})) +
+ "; path=/";
+ }
+
+ // Chooses a server
+ server = servers.storage() || servers.windowref();
+ server.init();
+
+ // For broadcast method
+ connection.broadcastable = server;
+
+ // List of children sockets
+ server.set("children", []);
+ // Flag indicating the parent socket is opened
+ server.set("opened", false);
+
+ // Leaves traces
+ leaveTrace();
+ traceTimer = setInterval(leaveTrace, 1000);
+
+ self.on("_message", propagateMessageEvent)
+ .one("open", function() {
+ server.set("opened", true);
+ server.broadcast({target: "c", type: "open"});
+ })
+ .one("close", function(reason) {
+ // Clears trace timer
+ clearInterval(traceTimer);
+ // Removes the trace
+ document.cookie = encodeURIComponent(name) + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/";
+ // The heir is the parent unless unloading
+ server.broadcast({target: "c", type: "close", data: {reason: reason, heir: !unloading ? opts.id : (server.get("children") || [])[0]}});
+ self.off("_message", propagateMessageEvent);
+ });
+ }
+
+ if (opts.timeout > 0) {
+ setTimeoutTimer();
+ self.one("open", clearTimeoutTimer).one("close", clearTimeoutTimer);
+ }
+
+ // Share the socket if possible
+ if (opts.sharing && connection.transport !== "session") {
+ share();
+ }
+ },
+ open: function() {
+ // From connecting state
+ state = "opened";
+
+ var heartbeatTimer;
+
+ // Sets heartbeat timer
+ function setHeartbeatTimer() {
+ heartbeatTimer = setTimeout(function() {
+ self.send("heartbeat").one("heartbeat", function() {
+ clearHeartbeatTimer();
+ setHeartbeatTimer();
+ });
+
+ heartbeatTimer = setTimeout(function() {
+ transport.close();
+ self.fire("close", "error");
+ }, opts._heartbeat);
+ }, opts.heartbeat - opts._heartbeat);
+ }
+
+ // Clears heartbeat timer
+ function clearHeartbeatTimer() {
+ clearTimeout(heartbeatTimer);
+ }
+
+ if (opts.heartbeat > opts._heartbeat) {
+ setHeartbeatTimer();
+ self.one("close", clearHeartbeatTimer);
+ }
+
+ // Locks the connecting event
+ events.connecting.lock();
+
+ // Initializes variables related with reconnection
+ reconnectTimer = reconnectDelay = reconnectTry = null;
+
+ // Flushes buffer
+ while (buffer.length) {
+ self.send.apply(self, buffer.shift());
+ }
+ },
+ close: function() {
+ // From preparing, connecting, or opened state
+ state = "closed";
+
+ var type, event, order = events.close.order;
+
+ // Locks event whose order is lower than close event
+ for (type in events) {
+ event = events[type];
+ if (event.order < order) {
+ event.lock();
+ }
+ }
+
+ // Schedules reconnection
+ if (opts.reconnect) {
+ self.one("close", function() {
+ reconnectTry = reconnectTry || 1;
+ reconnectDelay = opts.reconnect.call(self, reconnectDelay, reconnectTry);
+
+ if (reconnectDelay !== false) {
+ reconnectTimer = setTimeout(function() {
+ self.open();
+ }, reconnectDelay);
+ self.fire("waiting", reconnectDelay, reconnectTry);
+ }
+ });
+ }
+ },
+ waiting: function() {
+ // From closed state
+ state = "waiting";
+ },
+ reply: function(reply) {
+ var fn,
+ id = reply.id,
+ data = reply.data,
+ exception = reply.exception,
+ callback = replyCallbacks[id];
+
+ if (callback) {
+ fn = exception ? callback.fail : callback.done;
+ if (fn) {
+ if (portal.support.isFunction(fn)) {
+ fn.call(self, data);
+ } else {
+ self.fire(fn, data).fire("_message", [fn, data]);
+ }
+
+ delete replyCallbacks[id];
+ }
+ }
+ }
+ });
+
+ return self.open();
+ }
+
+ // Transports
+ portal.transports = {
+ // Session socket
+ session: function(socket, options) {
+ var trace,
+ orphan,
+ connector,
+ name = "socket-" + options.url,
+ connectors = {
+ storage: function() {
+ if (!portal.support.storage) {
+ return;
+ }
+
+ var storage = window.localStorage;
+
+ function get(key) {
+ return portal.support.parseJSON(storage.getItem(name + "-" + key));
+ }
+
+ function set(key, value) {
+ storage.setItem(name + "-" + key, portal.support.stringifyJSON(value));
+ }
+
+ return {
+ init: function() {
+ function onstorage(event) {
+ if (event.key === name && event.newValue) {
+ listener(event.newValue);
+ }
+ }
+
+ set("children", get("children").concat([options.id]));
+ portal.support.on(window, "storage", onstorage);
+
+ socket.one("close", function() {
+ var children = get("children");
+
+ portal.support.off(window, "storage", onstorage);
+ if (children) {
+ if (removeFromArray(children, options.id)) {
+ set("children", children);
+ }
+ }
+ });
+
+ return get("opened");
+ },
+ broadcast: function(obj) {
+ var string = portal.support.stringifyJSON(obj);
+
+ storage.setItem(name, string);
+ setTimeout(function() {
+ listener(string);
+ }, 50);
+ }
+ };
+ },
+ 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(options.id);
+
+ socket.one("close", function() {
+ // Removes traces only if the parent is alive
+ if (!orphan) {
+ removeFromArray(win.callbacks, listener);
+ removeFromArray(win.children, options.id);
+ }
+ });
+
+ return win.opened;
+ },
+ broadcast: function(obj) {
+ if (!win.closed && win.fire) {
+ win.fire(portal.support.stringifyJSON(obj));
+ }
+ }
+ };
+ }
+ };
+
+ function removeFromArray(array, val) {
+ var i,
+ length = array.length;
+
+ for (i = 0; i < length; i++) {
+ if (array[i] === val) {
+ array.splice(i, 1);
+ }
+ }
+
+ return length !== array.length;
+ }
+
+ // Receives open, close and message command from the parent
+ function listener(string) {
+ var command = portal.support.parseJSON(string), data = command.data;
+
+ if (!command.target) {
+ if (command.type === "fire") {
+ socket.fire(data.type, data.data);
+ }
+ } else if (command.target === "c") {
+ switch (command.type) {
+ case "open":
+ socket.fire("open");
+ break;
+ case "close":
+ if (!orphan) {
+ orphan = true;
+ if (data.reason === "aborted") {
+ socket.close();
+ } else {
+ // Gives the heir some time to reconnect
+ if (data.heir === options.id) {
+ socket.fire("close", data.reason);
+ } else {
+ setTimeout(function() {
+ socket.fire("close", data.reason);
+ }, 100);
+ }
+ }
+ }
+ break;
+ case "message":
+ // When using the session transport, message events could be sent before the open event
+ if (socket.state() === "connecting") {
+ socket.one("open", function() {
+ socket.fire.apply(socket, data);
+ });
+ } else {
+ socket.fire.apply(socket, data);
+ }
+ break;
+ }
+ }
+ }
+
+ function findTrace() {
+ var matcher = new RegExp("(?:^|; )(" + encodeURIComponent(name) + ")=([^;]*)").exec(document.cookie);
+ if (matcher) {
+ return portal.support.parseJSON(decodeURIComponent(matcher[2]));
+ }
+ }
+
+ // Finds and validates the parent socket's trace from the cookie
+ trace = findTrace();
+ if (!trace || portal.support.now() - trace.ts > 1000) {
+ return;
+ }
+
+ // Chooses a connector
+ connector = connectors.storage() || connectors.windowref();
+ if (!connector) {
+ return;
+ }
+
+ // For broadcast method
+ socket.data("broadcastable", connector);
+
+ return {
+ open: function() {
+ var traceTimer,
+ parentOpened,
+ timeout = options.timeout,
+ heartbeat = options.heartbeat,
+ outbound = options.outbound;
+
+ // Prevents side effects
+ options.timeout = options.heartbeat = false;
+ options.outbound = function(arg) {
+ return arg;
+ };
+
+ // 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(portal.support.stringifyJSON({target: "c", type: "close", data: {reason: "error", heir: oldTrace.heir}}));
+ }
+ }, 1000);
+
+ // Restores options
+ socket.one("close", function() {
+ clearInterval(traceTimer);
+ options.timeout = timeout;
+ options.heartbeat = heartbeat;
+ options.outbound = outbound;
+ });
+
+ parentOpened = connector.init();
+ if (parentOpened) {
+ // Gives the user the opportunity to bind connecting event handlers
+ setTimeout(function() {
+ socket.fire("open");
+ }, 50);
+ }
+ },
+ send: function(event) {
+ connector.broadcast({target: "p", type: "send", data: event});
+ },
+ close: function() {
+ // Do not signal the parent if this method is executed by the unload event handler
+ if (!unloading) {
+ connector.broadcast({target: "p", type: "close"});
+ }
+ }
+ };
+ },
+ // WebSocket
+ ws: function(socket) {
+ var ws,
+ aborted,
+ WebSocket = window.WebSocket || window.MozWebSocket;
+
+ if (!WebSocket) {
+ return;
+ }
+
+ return {
+ feedback: true,
+ open: function() {
+ // Makes an absolute url whose scheme is ws or wss
+ var url = portal.support.getAbsoluteURL(socket.data("url")).replace(/^http/, "ws");
+
+ socket.data("url", url);
+
+ ws = new WebSocket(url);
+ ws.onopen = function(event) {
+ socket.data("event", event).fire("open");
+ };
+ ws.onmessage = function(event) {
+ socket.data("event", event)._fire(event.data);
+ };
+ ws.onerror = function(event) {
+ socket.data("event", event).fire("close", aborted ? "aborted" : "error");
+ };
+ ws.onclose = function(event) {
+ socket.data("event", event).fire("close", aborted ? "aborted" : event.wasClean ? "done" : "error");
+ };
+ },
+ send: function(data) {
+ ws.send(data);
+ },
+ close: function() {
+ aborted = true;
+ ws.close();
+ }
+ };
+ },
+ // HTTP Support
+ httpbase: function(socket, options) {
+ var send,
+ sending,
+ queue = [];
+
+ function post() {
+ if (queue.length) {
+ send(options.url, queue.shift());
+ } else {
+ sending = false;
+ }
+ }
+
+ // The Content-Type is not application/x-www-form-urlencoded but text/plain on account of XDomainRequest
+ // See the fourth at http://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx
+ send = !options.crossDomain || portal.support.corsable ?
+ function(url, data) {
+ var xhr = portal.support.xhr();
+
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState === 4) {
+ post();
+ }
+ };
+
+ xhr.open("POST", url);
+ xhr.setRequestHeader("Content-Type", "text/plain; charset=UTF-8");
+ if (portal.support.corsable) {
+ xhr.withCredentials = options.credentials;
+ }
+
+ xhr.send("data=" + data);
+ } : window.XDomainRequest && options.xdrURL && options.xdrURL.call(socket, "t") ?
+ function(url, data) {
+ var xdr = new window.XDomainRequest();
+
+ xdr.onload = xdr.onerror = post;
+ xdr.open("POST", options.xdrURL.call(socket, url));
+ xdr.send("data=" + data);
+ } :
+ function(url, data) {
+ var iframe,
+ textarea,
+ form = document.createElement("form");
+
+ form.action = url;
+ form.target = "socket-" + (++guid);
+ form.method = "POST";
+ // IE 6 needs encoding property
+ form.enctype = form.encoding = "text/plain";
+ form.acceptCharset = "UTF-8";
+ form.style.display = "none";
+ form.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"
+ }
+ ]
+}