PageRenderTime 52ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/files/portal/1.0/portal.js

https://gitlab.com/Mirros/jsdelivr
JavaScript | 1786 lines | 1749 code | 16 blank | 21 comment | 23 complexity | ffd035b7d59b80e98325f91786f94e84 MD5 | raw file
  1. /*
  2. * Portal v1.0
  3. * http://github.com/flowersinthesand/portal
  4. *
  5. * Copyright 2011-2013, Donghwan Kim
  6. * Licensed under the Apache License, Version 2.0
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. */
  9. (function() {
  10. "use strict";
  11. var // A global identifier
  12. guid,
  13. // Is the unload event being processed?
  14. unloading,
  15. // Portal
  16. portal = {},
  17. // Socket instances
  18. sockets = {},
  19. // Callback names for JSONP
  20. jsonpCallbacks = [],
  21. // Core prototypes
  22. toString = Object.prototype.toString,
  23. hasOwn = Object.prototype.hasOwnProperty,
  24. slice = Array.prototype.slice;
  25. // Convenience utilities
  26. // Most utility functions are borrowed from jQuery
  27. portal.support = {
  28. now: function() {
  29. return new Date().getTime();
  30. },
  31. isArray: function(array) {
  32. return toString.call(array) === "[object Array]";
  33. },
  34. isBinary: function(data) {
  35. var string = toString.call(data);
  36. return string === "[object Blob]" || string === "[object ArrayBuffer]";
  37. },
  38. isFunction: function(fn) {
  39. return toString.call(fn) === "[object Function]";
  40. },
  41. getAbsoluteURL: function(url) {
  42. var div = document.createElement("div");
  43. // Uses an innerHTML property to obtain an absolute URL
  44. div.innerHTML = '<a href="' + url + '"/>';
  45. // encodeURI and decodeURI are needed to normalize URL between IE and non-IE,
  46. // since IE doesn't encode the href property value and return it - http://jsfiddle.net/Yq9M8/1/
  47. return encodeURI(decodeURI(div.firstChild.href));
  48. },
  49. iterate: function(fn) {
  50. var timeoutId;
  51. // Though the interval is 1ms for real-time application, there is a delay between setTimeout calls
  52. // For detail, see https://developer.mozilla.org/en/window.setTimeout#Minimum_delay_and_timeout_nesting
  53. (function loop() {
  54. timeoutId = setTimeout(function() {
  55. if (fn() === false) {
  56. return;
  57. }
  58. loop();
  59. }, 1);
  60. })();
  61. return function() {
  62. clearTimeout(timeoutId);
  63. };
  64. },
  65. each: function(array, callback) {
  66. var i;
  67. for (i = 0; i < array.length; i++) {
  68. callback(i, array[i]);
  69. }
  70. },
  71. extend: function(target) {
  72. var i, options, name;
  73. for (i = 1; i < arguments.length; i++) {
  74. if ((options = arguments[i]) != null) {
  75. for (name in options) {
  76. target[name] = options[name];
  77. }
  78. }
  79. }
  80. return target;
  81. },
  82. on: function(elem, type, fn) {
  83. if (elem.addEventListener) {
  84. elem.addEventListener(type, fn, false);
  85. } else if (elem.attachEvent) {
  86. elem.attachEvent("on" + type, fn);
  87. }
  88. },
  89. off: function(elem, type, fn) {
  90. if (elem.removeEventListener) {
  91. elem.removeEventListener(type, fn, false);
  92. } else if (elem.detachEvent) {
  93. elem.detachEvent("on" + type, fn);
  94. }
  95. },
  96. param: function(params) {
  97. var prefix,
  98. s = [];
  99. function add(key, value) {
  100. value = portal.support.isFunction(value) ? value() : (value == null ? "" : value);
  101. s.push(encodeURIComponent(key) + "=" + encodeURIComponent(value));
  102. }
  103. function buildParams(prefix, obj) {
  104. var name;
  105. if (portal.support.isArray(obj)) {
  106. portal.support.each(obj, function(i, v) {
  107. if (/\[\]$/.test(prefix)) {
  108. add(prefix, v);
  109. } else {
  110. buildParams(prefix + "[" + (typeof v === "object" ? i : "") + "]", v);
  111. }
  112. });
  113. } else if (toString.call(obj) === "[object Object]") {
  114. for (name in obj) {
  115. buildParams(prefix + "[" + name + "]", obj[name]);
  116. }
  117. } else {
  118. add(prefix, obj);
  119. }
  120. }
  121. for (prefix in params) {
  122. buildParams(prefix, params[prefix]);
  123. }
  124. return s.join("&").replace(/%20/g, "+");
  125. },
  126. xhr: function() {
  127. try {
  128. return new window.XMLHttpRequest();
  129. } catch(e1) {
  130. try {
  131. return new window.ActiveXObject("Microsoft.XMLHTTP");
  132. } catch(e2) {}
  133. }
  134. },
  135. parseJSON: function(data) {
  136. return !data ?
  137. null :
  138. window.JSON && window.JSON.parse ?
  139. window.JSON.parse(data) :
  140. new Function("return " + data)();
  141. },
  142. // http://github.com/flowersinthesand/stringifyJSON
  143. stringifyJSON: function(value) {
  144. var escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
  145. meta = {
  146. '\b' : '\\b',
  147. '\t' : '\\t',
  148. '\n' : '\\n',
  149. '\f' : '\\f',
  150. '\r' : '\\r',
  151. '"' : '\\"',
  152. '\\' : '\\\\'
  153. };
  154. function quote(string) {
  155. return '"' + string.replace(escapable, function(a) {
  156. var c = meta[a];
  157. return typeof c === "string" ? c : "\\u" + ("0000" + a.charCodeAt(0).toString(16)).slice(-4);
  158. }) + '"';
  159. }
  160. function f(n) {
  161. return n < 10 ? "0" + n : n;
  162. }
  163. return window.JSON && window.JSON.stringify ?
  164. window.JSON.stringify(value) :
  165. (function str(key, holder) {
  166. var i, v, len, partial, value = holder[key], type = typeof value;
  167. if (value && typeof value === "object" && typeof value.toJSON === "function") {
  168. value = value.toJSON(key);
  169. type = typeof value;
  170. }
  171. switch (type) {
  172. case "string":
  173. return quote(value);
  174. case "number":
  175. return isFinite(value) ? String(value) : "null";
  176. case "boolean":
  177. return String(value);
  178. case "object":
  179. if (!value) {
  180. return "null";
  181. }
  182. switch (toString.call(value)) {
  183. case "[object Date]":
  184. return isFinite(value.valueOf()) ?
  185. '"' + value.getUTCFullYear() + "-" + f(value.getUTCMonth() + 1) + "-" + f(value.getUTCDate()) +
  186. "T" + f(value.getUTCHours()) + ":" + f(value.getUTCMinutes()) + ":" + f(value.getUTCSeconds()) + "Z" + '"' :
  187. "null";
  188. case "[object Array]":
  189. len = value.length;
  190. partial = [];
  191. for (i = 0; i < len; i++) {
  192. partial.push(str(i, value) || "null");
  193. }
  194. return "[" + partial.join(",") + "]";
  195. default:
  196. partial = [];
  197. for (i in value) {
  198. if (hasOwn.call(value, i)) {
  199. v = str(i, value);
  200. if (v) {
  201. partial.push(quote(i) + ":" + v);
  202. }
  203. }
  204. }
  205. return "{" + partial.join(",") + "}";
  206. }
  207. }
  208. })("", {"": value});
  209. },
  210. browser: {},
  211. storage: !!(window.localStorage && window.StorageEvent)
  212. };
  213. portal.support.corsable = "withCredentials" in portal.support.xhr();
  214. guid = portal.support.now();
  215. // Browser sniffing
  216. (function() {
  217. var ua = navigator.userAgent.toLowerCase(),
  218. match = /(chrome)[ \/]([\w.]+)/.exec(ua) ||
  219. /(webkit)[ \/]([\w.]+)/.exec(ua) ||
  220. /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) ||
  221. /(msie) ([\w.]+)/.exec(ua) ||
  222. ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) ||
  223. [];
  224. portal.support.browser[match[1] || ""] = true;
  225. portal.support.browser.version = match[2] || "0";
  226. // The storage event of Internet Explorer and Firefox 3 works strangely
  227. if (portal.support.browser.msie || (portal.support.browser.mozilla && portal.support.browser.version.split(".")[0] === "1")) {
  228. portal.support.storage = false;
  229. }
  230. })();
  231. // Finds the socket object which is mapped to the given url
  232. portal.find = function(url) {
  233. var i;
  234. // Returns the first socket in the document
  235. if (!arguments.length) {
  236. for (i in sockets) {
  237. if (sockets[i]) {
  238. return sockets[i];
  239. }
  240. }
  241. return null;
  242. }
  243. // The url is a identifier of this socket within the document
  244. return sockets[portal.support.getAbsoluteURL(url)] || null;
  245. };
  246. // Creates a new socket and connects to the given url
  247. portal.open = function(url, options) {
  248. // Makes url absolute to normalize URL
  249. url = portal.support.getAbsoluteURL(url);
  250. sockets[url] = socket(url, options);
  251. return portal.find(url);
  252. };
  253. // Default options
  254. portal.defaults = {
  255. // Socket options
  256. transports: ["ws", "sse", "stream", "longpoll"],
  257. timeout: false,
  258. heartbeat: false,
  259. lastEventId: 0,
  260. sharing: false,
  261. prepare: function(connect) {
  262. connect();
  263. },
  264. reconnect: function(lastDelay) {
  265. return 2 * (lastDelay || 250);
  266. },
  267. idGenerator: function() {
  268. // Generates a random UUID
  269. // Logic borrowed from http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523
  270. return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
  271. var r = Math.random() * 16 | 0,
  272. v = c === "x" ? r : (r & 0x3 | 0x8);
  273. return v.toString(16);
  274. });
  275. },
  276. urlBuilder: function(url, params, when) {
  277. return url + (/\?/.test(url) ? "&" : "?") + "when=" + when + "&" + portal.support.param(params);
  278. },
  279. inbound: portal.support.parseJSON,
  280. outbound: portal.support.stringifyJSON,
  281. // Transport options
  282. credentials: false,
  283. notifyAbort: false,
  284. xdrURL: function(url) {
  285. // Maintaining session by rewriting URL
  286. // http://stackoverflow.com/questions/6453779/maintaining-session-by-rewriting-url
  287. var match = /(?:^|; )(JSESSIONID|PHPSESSID)=([^;]*)/.exec(document.cookie);
  288. switch (match && match[1]) {
  289. case "JSESSIONID":
  290. return url.replace(/;jsessionid=[^\?]*|(\?)|$/, ";jsessionid=" + match[2] + "$1");
  291. case "PHPSESSID":
  292. return url.replace(/\?PHPSESSID=[^&]*&?|\?|$/, "?PHPSESSID=" + match[2] + "&").replace(/&$/, "");
  293. default:
  294. return false;
  295. }
  296. },
  297. streamParser: function(chunk) {
  298. // Chunks are formatted according to the event stream format
  299. // http://www.w3.org/TR/eventsource/#event-stream-interpretation
  300. var reol = /\r\n|[\r\n]/g, lines = [], data = this.data("data"), array = [], i = 0,
  301. match, line;
  302. // Strips off the left padding of the chunk
  303. // the first chunk of some streaming transports and every chunk for Android browser 2 and 3 has padding
  304. chunk = chunk.replace(/^\s+/g, "");
  305. // String.prototype.split is not reliable cross-browser
  306. while (match = reol.exec(chunk)) {
  307. lines.push(chunk.substring(i, match.index));
  308. i = match.index + match[0].length;
  309. }
  310. lines.push(chunk.length === i ? "" : chunk.substring(i));
  311. if (!data) {
  312. data = [];
  313. this.data("data", data);
  314. }
  315. // Processes the data field only
  316. for (i = 0; i < lines.length; i++) {
  317. line = lines[i];
  318. if (!line) {
  319. // Finish
  320. array.push(data.join("\n"));
  321. data = [];
  322. this.data("data", data);
  323. } else if (/^data:\s/.test(line)) {
  324. // A single data field
  325. data.push(line.substring("data: ".length));
  326. } else {
  327. // A fragment of a data field
  328. data[data.length - 1] += line;
  329. }
  330. }
  331. return array;
  332. },
  333. // Undocumented
  334. _heartbeat: 5000,
  335. longpollTest: true
  336. // method: null
  337. // initIframe: null
  338. };
  339. // Callback function
  340. function callbacks(deferred) {
  341. var list = [],
  342. locked,
  343. memory,
  344. firing,
  345. firingStart,
  346. firingLength,
  347. firingIndex,
  348. fire = function(context, args) {
  349. args = args || [];
  350. memory = !deferred || [context, args];
  351. firing = true;
  352. firingIndex = firingStart || 0;
  353. firingStart = 0;
  354. firingLength = list.length;
  355. for (; firingIndex < firingLength; firingIndex++) {
  356. list[firingIndex].apply(context, args);
  357. }
  358. firing = false;
  359. },
  360. self = {
  361. add: function(fn) {
  362. var length = list.length;
  363. list.push(fn);
  364. if (firing) {
  365. firingLength = list.length;
  366. } else if (!locked && memory && memory !== true) {
  367. firingStart = length;
  368. fire(memory[0], memory[1]);
  369. }
  370. },
  371. remove: function(fn) {
  372. var i;
  373. for (i = 0; i < list.length; i++) {
  374. if (fn === list[i] || (fn.guid && fn.guid === list[i].guid)) {
  375. if (firing) {
  376. if (i <= firingLength) {
  377. firingLength--;
  378. if (i <= firingIndex) {
  379. firingIndex--;
  380. }
  381. }
  382. }
  383. list.splice(i--, 1);
  384. }
  385. }
  386. },
  387. fire: function(context, args) {
  388. if (!locked && !firing && !(deferred && memory)) {
  389. fire(context, args);
  390. }
  391. },
  392. lock: function() {
  393. locked = true;
  394. },
  395. locked: function() {
  396. return !!locked;
  397. },
  398. unlock: function() {
  399. locked = memory = firing = firingStart = firingLength = firingIndex = undefined;
  400. }
  401. };
  402. return self;
  403. }
  404. // Socket function
  405. function socket(url, options) {
  406. var // Final options
  407. opts,
  408. // Transport
  409. transport,
  410. // The state of the connection
  411. state,
  412. // Event helpers
  413. events = {},
  414. eventId = 0,
  415. // Reply callbacks
  416. replyCallbacks = {},
  417. // Buffer
  418. buffer = [],
  419. // Reconnection
  420. reconnectTimer,
  421. reconnectDelay,
  422. reconnectTry,
  423. // Map of the connection-scoped values
  424. connection = {},
  425. parts = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/.exec(url.toLowerCase()),
  426. // Socket object
  427. self = {
  428. // Finds the value of an option
  429. option: function(key, /* undocumented */ value) {
  430. if (value === undefined) {
  431. return opts[key];
  432. }
  433. opts[key] = value;
  434. return this;
  435. },
  436. // Gets or sets a connection-scoped value
  437. data: function(key, value) {
  438. if (value === undefined) {
  439. return connection[key];
  440. }
  441. connection[key] = value;
  442. return this;
  443. },
  444. // Returns the state
  445. state: function() {
  446. return state;
  447. },
  448. // Adds event handler
  449. on: function(type, fn) {
  450. var event;
  451. // Handles a map of type and handler
  452. if (typeof type === "object") {
  453. for (event in type) {
  454. self.on(event, type[event]);
  455. }
  456. return this;
  457. }
  458. // For custom event
  459. event = events[type];
  460. if (!event) {
  461. if (events.message.locked()) {
  462. return this;
  463. }
  464. event = events[type] = callbacks();
  465. event.order = events.message.order;
  466. }
  467. event.add(fn);
  468. return this;
  469. },
  470. // Removes event handler
  471. off: function(type, fn) {
  472. var event = events[type];
  473. if (event) {
  474. event.remove(fn);
  475. }
  476. return this;
  477. },
  478. // Adds one time event handler
  479. one: function(type, fn) {
  480. function proxy() {
  481. self.off(type, proxy);
  482. fn.apply(self, arguments);
  483. }
  484. fn.guid = fn.guid || guid++;
  485. proxy.guid = fn.guid;
  486. return self.on(type, proxy);
  487. },
  488. // Fires event handlers
  489. fire: function(type) {
  490. var event = events[type];
  491. if (event) {
  492. event.fire(self, slice.call(arguments, 1));
  493. }
  494. return this;
  495. },
  496. // Establishes a connection
  497. open: function() {
  498. var type,
  499. latch,
  500. connect = function() {
  501. var candidates, type;
  502. if (!latch) {
  503. latch = true;
  504. candidates = connection.candidates = slice.call(opts.transports);
  505. while (!transport && candidates.length) {
  506. type = candidates.shift();
  507. connection.transport = type;
  508. connection.url = self.buildURL("open");
  509. transport = portal.transports[type](self, opts);
  510. }
  511. // Increases the number of reconnection attempts
  512. if (reconnectTry) {
  513. reconnectTry++;
  514. }
  515. // Fires the connecting event and connects
  516. if (transport) {
  517. self.fire("connecting");
  518. transport.open();
  519. } else {
  520. self.fire("close", "notransport");
  521. }
  522. }
  523. },
  524. cancel = function() {
  525. if (!latch) {
  526. latch = true;
  527. self.fire("close", "canceled");
  528. }
  529. };
  530. // Cancels the scheduled connection
  531. if (reconnectTimer) {
  532. clearTimeout(reconnectTimer);
  533. }
  534. // Resets the connection scope and event helpers
  535. connection = {};
  536. for (type in events) {
  537. events[type].unlock();
  538. }
  539. // Chooses transport
  540. transport = undefined;
  541. // From null or waiting state
  542. state = "preparing";
  543. // Check if possible to make use of a shared socket
  544. if (opts.sharing) {
  545. connection.transport = "session";
  546. transport = portal.transports.session(self, opts);
  547. }
  548. // Executes the prepare handler if a physical connection is needed
  549. if (transport) {
  550. connect();
  551. } else {
  552. opts.prepare.call(self, connect, cancel, opts);
  553. }
  554. return this;
  555. },
  556. // Sends an event to the server via the connection
  557. send: function(type, data, doneCallback, failCallback) {
  558. var event;
  559. // Defers sending an event until the state become opened
  560. if (state !== "opened") {
  561. buffer.push(arguments);
  562. return this;
  563. }
  564. // Outbound event
  565. event = {
  566. id: ++eventId,
  567. socket: opts.id,
  568. type: type,
  569. data: data,
  570. reply: !!(doneCallback || failCallback)
  571. };
  572. if (event.reply) {
  573. // Shared socket needs to know the callback event name
  574. // because it fires the callback event directly instead of using reply event
  575. if (connection.transport === "session") {
  576. event.doneCallback = doneCallback;
  577. event.failCallback = failCallback;
  578. } else {
  579. replyCallbacks[eventId] = {done: doneCallback, fail: failCallback};
  580. }
  581. }
  582. // Delegates to the transport
  583. transport.send(portal.support.isBinary(data) ? data : opts.outbound.call(self, event));
  584. return this;
  585. },
  586. // Disconnects the connection
  587. close: function() {
  588. var script, head;
  589. // Prevents reconnection
  590. opts.reconnect = false;
  591. if (reconnectTimer) {
  592. clearTimeout(reconnectTimer);
  593. }
  594. // Fires the close event immediately for transport which doesn't give feedback on disconnection
  595. if (unloading || !transport || !transport.feedback) {
  596. self.fire("close", unloading ? "error" : "aborted");
  597. if (opts.notifyAbort && connection.transport !== "session") {
  598. head = document.head || document.getElementsByTagName("head")[0] || document.documentElement;
  599. script = document.createElement("script");
  600. script.async = false;
  601. script.src = self.buildURL("abort");
  602. script.onload = script.onreadystatechange = function() {
  603. if (!script.readyState || /loaded|complete/.test(script.readyState)) {
  604. script.onload = script.onreadystatechange = null;
  605. if (script.parentNode) {
  606. script.parentNode.removeChild(script);
  607. }
  608. }
  609. };
  610. head.insertBefore(script, head.firstChild);
  611. }
  612. }
  613. // Delegates to the transport
  614. if (transport) {
  615. transport.close();
  616. }
  617. return this;
  618. },
  619. // Broadcasts event to session sockets
  620. broadcast: function(type, data) {
  621. // TODO rename
  622. var broadcastable = connection.broadcastable;
  623. if (broadcastable) {
  624. broadcastable.broadcast({type: "fire", data: {type: type, data: data}});
  625. }
  626. return this;
  627. },
  628. // For internal use only
  629. // fires events from the server
  630. _fire: function(data, isChunk) {
  631. var array;
  632. if (isChunk) {
  633. data = opts.streamParser.call(self, data);
  634. while (data.length) {
  635. self._fire(data.shift());
  636. }
  637. return this;
  638. }
  639. if (portal.support.isBinary(data)) {
  640. array = [{type: "message", data: data}];
  641. } else {
  642. array = opts.inbound.call(self, data);
  643. array = array == null ? [] : !portal.support.isArray(array) ? [array] : array;
  644. }
  645. connection.lastEventIds = [];
  646. portal.support.each(array, function(i, event) {
  647. var latch, args = [event.type, event.data];
  648. opts.lastEventId = event.id;
  649. connection.lastEventIds.push(event.id);
  650. if (event.reply) {
  651. args.push(function(result) {
  652. if (!latch) {
  653. latch = true;
  654. self.send("reply", {id: event.id, data: result});
  655. }
  656. });
  657. }
  658. self.fire.apply(self, args).fire("_message", args);
  659. });
  660. return this;
  661. },
  662. // For internal use only
  663. // builds an effective URL
  664. buildURL: function(when, params) {
  665. var p = when === "open" ?
  666. {
  667. transport: connection.transport,
  668. heartbeat: opts.heartbeat,
  669. lastEventId: opts.lastEventId
  670. } :
  671. when === "poll" ?
  672. {
  673. transport: connection.transport,
  674. lastEventIds: connection.lastEventIds && connection.lastEventIds.join(","),
  675. /* deprecated */lastEventId: opts.lastEventId
  676. } :
  677. {};
  678. portal.support.extend(p, {id: opts.id, _: guid++}, opts.params && opts.params[when], params);
  679. return opts.urlBuilder.call(self, url, p, when);
  680. }
  681. };
  682. // Create the final options
  683. opts = portal.support.extend({}, portal.defaults, options);
  684. if (options) {
  685. // Array should not be deep extended
  686. if (options.transports) {
  687. opts.transports = slice.call(options.transports);
  688. }
  689. }
  690. // Saves original URL
  691. opts.url = url;
  692. // Generates socket id,
  693. opts.id = opts.idGenerator.call(self);
  694. opts.crossDomain = !!(parts &&
  695. // protocol and hostname
  696. (parts[1] != location.protocol || parts[2] != location.hostname ||
  697. // port
  698. (parts[3] || (parts[1] === "http:" ? 80 : 443)) != (location.port || (location.protocol === "http:" ? 80 : 443))));
  699. portal.support.each(["connecting", "open", "message", "close", "waiting"], function(i, type) {
  700. // Creates event helper
  701. events[type] = callbacks(type !== "message");
  702. events[type].order = i;
  703. // Shortcuts for on method
  704. var old = self[type],
  705. on = function(fn) {
  706. return self.on(type, fn);
  707. };
  708. self[type] = !old ? on : function(fn) {
  709. return (portal.support.isFunction(fn) ? on : old).apply(this, arguments);
  710. };
  711. });
  712. // Initializes
  713. self.on({
  714. connecting: function() {
  715. // From preparing state
  716. state = "connecting";
  717. var timeoutTimer;
  718. // Sets timeout timer
  719. function setTimeoutTimer() {
  720. timeoutTimer = setTimeout(function() {
  721. transport.close();
  722. self.fire("close", "timeout");
  723. }, opts.timeout);
  724. }
  725. // Clears timeout timer
  726. function clearTimeoutTimer() {
  727. clearTimeout(timeoutTimer);
  728. }
  729. // Makes the socket sharable
  730. function share() {
  731. var traceTimer,
  732. server,
  733. name = "socket-" + url,
  734. servers = {
  735. // Powered by the storage event and the localStorage
  736. // http://www.w3.org/TR/webstorage/#event-storage
  737. storage: function() {
  738. if (!portal.support.storage) {
  739. return;
  740. }
  741. var storage = window.localStorage;
  742. return {
  743. init: function() {
  744. function onstorage(event) {
  745. // When a deletion, newValue initialized to null
  746. if (event.key === name && event.newValue) {
  747. listener(event.newValue);
  748. }
  749. }
  750. // Handles the storage event
  751. portal.support.on(window, "storage", onstorage);
  752. self.one("close", function() {
  753. portal.support.off(window, "storage", onstorage);
  754. // Defers again to clean the storage
  755. self.one("close", function() {
  756. storage.removeItem(name);
  757. storage.removeItem(name + "-opened");
  758. storage.removeItem(name + "-children");
  759. });
  760. });
  761. },
  762. broadcast: function(obj) {
  763. var string = portal.support.stringifyJSON(obj);
  764. storage.setItem(name, string);
  765. setTimeout(function() {
  766. listener(string);
  767. }, 50);
  768. },
  769. get: function(key) {
  770. return portal.support.parseJSON(storage.getItem(name + "-" + key));
  771. },
  772. set: function(key, value) {
  773. storage.setItem(name + "-" + key, portal.support.stringifyJSON(value));
  774. }
  775. };
  776. },
  777. // Powered by the window.open method
  778. // https://developer.mozilla.org/en/DOM/window.open
  779. windowref: function() {
  780. // Internet Explorer raises an invalid argument error
  781. // when calling the window.open method with the name containing non-word characters
  782. var neim = name.replace(/\W/g, ""),
  783. container = document.getElementById(neim),
  784. win;
  785. if (!container) {
  786. container = document.createElement("div");
  787. container.id = neim;
  788. container.style.display = "none";
  789. container.innerHTML = '<iframe name="' + neim + '" />';
  790. document.body.appendChild(container);
  791. }
  792. win = container.firstChild.contentWindow;
  793. return {
  794. init: function() {
  795. // Callbacks from different windows
  796. win.callbacks = [listener];
  797. // In IE 8 and less, only string argument can be safely passed to the function in other window
  798. win.fire = function(string) {
  799. var i;
  800. for (i = 0; i < win.callbacks.length; i++) {
  801. win.callbacks[i](string);
  802. }
  803. };
  804. },
  805. broadcast: function(obj) {
  806. if (!win.closed && win.fire) {
  807. win.fire(portal.support.stringifyJSON(obj));
  808. }
  809. },
  810. get: function(key) {
  811. return !win.closed ? win[key] : null;
  812. },
  813. set: function(key, value) {
  814. if (!win.closed) {
  815. win[key] = value;
  816. }
  817. }
  818. };
  819. }
  820. };
  821. // Receives send and close command from the children
  822. function listener(string) {
  823. var command = portal.support.parseJSON(string), data = command.data;
  824. if (!command.target) {
  825. if (command.type === "fire") {
  826. self.fire(data.type, data.data);
  827. }
  828. } else if (command.target === "p") {
  829. switch (command.type) {
  830. case "send":
  831. self.send(data.type, data.data, data.doneCallback, data.failCallback);
  832. break;
  833. case "close":
  834. self.close();
  835. break;
  836. }
  837. }
  838. }
  839. function propagateMessageEvent(args) {
  840. server.broadcast({target: "c", type: "message", data: args});
  841. }
  842. function leaveTrace() {
  843. document.cookie = encodeURIComponent(name) + "=" +
  844. // Opera 12.00's parseFloat and JSON.stringify causes a strange bug with a number larger than 10 digit
  845. // JSON.stringify(parseFloat(10000000000) + 1).length === 11;
  846. // JSON.stringify(parseFloat(10000000000 + 1)).length === 10;
  847. encodeURIComponent(portal.support.stringifyJSON({ts: portal.support.now() + 1, heir: (server.get("children") || [])[0]}));
  848. }
  849. // Chooses a server
  850. server = servers.storage() || servers.windowref();
  851. server.init();
  852. // For broadcast method
  853. connection.broadcastable = server;
  854. // List of children sockets
  855. server.set("children", []);
  856. // Flag indicating the parent socket is opened
  857. server.set("opened", false);
  858. // Leaves traces
  859. leaveTrace();
  860. traceTimer = setInterval(leaveTrace, 1000);
  861. self.on("_message", propagateMessageEvent)
  862. .one("open", function() {
  863. server.set("opened", true);
  864. server.broadcast({target: "c", type: "open"});
  865. })
  866. .one("close", function(reason) {
  867. // Clears trace timer
  868. clearInterval(traceTimer);
  869. // Removes the trace
  870. document.cookie = encodeURIComponent(name) + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT";
  871. // The heir is the parent unless unloading
  872. server.broadcast({target: "c", type: "close", data: {reason: reason, heir: !unloading ? opts.id : (server.get("children") || [])[0]}});
  873. self.off("_message", propagateMessageEvent);
  874. });
  875. }
  876. if (opts.timeout > 0) {
  877. setTimeoutTimer();
  878. self.one("open", clearTimeoutTimer).one("close", clearTimeoutTimer);
  879. }
  880. // Share the socket if possible
  881. if (opts.sharing && connection.transport !== "session") {
  882. share();
  883. }
  884. },
  885. open: function() {
  886. // From connecting state
  887. state = "opened";
  888. var heartbeatTimer;
  889. // Sets heartbeat timer
  890. function setHeartbeatTimer() {
  891. heartbeatTimer = setTimeout(function() {
  892. self.send("heartbeat").one("heartbeat", function() {
  893. clearHeartbeatTimer();
  894. setHeartbeatTimer();
  895. });
  896. heartbeatTimer = setTimeout(function() {
  897. transport.close();
  898. self.fire("close", "error");
  899. }, opts._heartbeat);
  900. }, opts.heartbeat - opts._heartbeat);
  901. }
  902. // Clears heartbeat timer
  903. function clearHeartbeatTimer() {
  904. clearTimeout(heartbeatTimer);
  905. }
  906. if (opts.heartbeat > opts._heartbeat) {
  907. setHeartbeatTimer();
  908. self.one("close", clearHeartbeatTimer);
  909. }
  910. // Locks the connecting event
  911. events.connecting.lock();
  912. // Initializes variables related with reconnection
  913. reconnectTimer = reconnectDelay = reconnectTry = null;
  914. // Flushes buffer
  915. while (buffer.length) {
  916. self.send.apply(self, buffer.shift());
  917. }
  918. },
  919. close: function() {
  920. // From preparing, connecting, or opened state
  921. state = "closed";
  922. var type, event, order = events.close.order;
  923. // Locks event whose order is lower than close event
  924. for (type in events) {
  925. event = events[type];
  926. if (event.order < order) {
  927. event.lock();
  928. }
  929. }
  930. // Schedules reconnection
  931. if (opts.reconnect) {
  932. self.one("close", function() {
  933. reconnectTry = reconnectTry || 1;
  934. reconnectDelay = opts.reconnect.call(self, reconnectDelay, reconnectTry);
  935. if (reconnectDelay !== false) {
  936. reconnectTimer = setTimeout(function() {
  937. self.open();
  938. }, reconnectDelay);
  939. self.fire("waiting", reconnectDelay, reconnectTry);
  940. }
  941. });
  942. }
  943. },
  944. waiting: function() {
  945. // From closed state
  946. state = "waiting";
  947. },
  948. reply: function(reply) {
  949. var fn,
  950. id = reply.id,
  951. data = reply.data,
  952. exception = reply.exception,
  953. callback = replyCallbacks[id];
  954. if (callback) {
  955. fn = exception ? callback.fail : callback.done;
  956. if (fn) {
  957. if (portal.support.isFunction(fn)) {
  958. fn.call(self, data);
  959. } else {
  960. self.fire(fn, data).fire("_message", [fn, data]);
  961. }
  962. delete replyCallbacks[id];
  963. }
  964. }
  965. }
  966. });
  967. return self.open();
  968. }
  969. // Transports
  970. portal.transports = {
  971. // Session socket
  972. session: function(socket, options) {
  973. var trace,
  974. orphan,
  975. connector,
  976. name = "socket-" + options.url,
  977. connectors = {
  978. storage: function() {
  979. if (!portal.support.storage) {
  980. return;
  981. }
  982. var storage = window.localStorage;
  983. function get(key) {
  984. return portal.support.parseJSON(storage.getItem(name + "-" + key));
  985. }
  986. function set(key, value) {
  987. storage.setItem(name + "-" + key, portal.support.stringifyJSON(value));
  988. }
  989. return {
  990. init: function() {
  991. function onstorage(event) {
  992. if (event.key === name && event.newValue) {
  993. listener(event.newValue);
  994. }
  995. }
  996. set("children", get("children").concat([options.id]));
  997. portal.support.on(window, "storage", onstorage);
  998. socket.one("close", function() {
  999. var children = get("children");
  1000. portal.support.off(window, "storage", onstorage);
  1001. if (children) {
  1002. if (removeFromArray(children, options.id)) {
  1003. set("children", children);
  1004. }
  1005. }
  1006. });
  1007. return get("opened");
  1008. },
  1009. broadcast: function(obj) {
  1010. var string = portal.support.stringifyJSON(obj);
  1011. storage.setItem(name, string);
  1012. setTimeout(function() {
  1013. listener(string);
  1014. }, 50);
  1015. }
  1016. };
  1017. },
  1018. windowref: function() {
  1019. var win = window.open("", name.replace(/\W/g, ""));
  1020. if (!win || win.closed || !win.callbacks) {
  1021. return;
  1022. }
  1023. return {
  1024. init: function() {
  1025. win.callbacks.push(listener);
  1026. win.children.push(options.id);
  1027. socket.one("close", function() {
  1028. // Removes traces only if the parent is alive
  1029. if (!orphan) {
  1030. removeFromArray(win.callbacks, listener);
  1031. removeFromArray(win.children, options.id);
  1032. }
  1033. });
  1034. return win.opened;
  1035. },
  1036. broadcast: function(obj) {
  1037. if (!win.closed && win.fire) {
  1038. win.fire(portal.support.stringifyJSON(obj));
  1039. }
  1040. }
  1041. };
  1042. }
  1043. };
  1044. function removeFromArray(array, val) {
  1045. var i,
  1046. length = array.length;
  1047. for (i = 0; i < length; i++) {
  1048. if (array[i] === val) {
  1049. array.splice(i, 1);
  1050. }
  1051. }
  1052. return length !== array.length;
  1053. }
  1054. // Receives open, close and message command from the parent
  1055. function listener(string) {
  1056. var command = portal.support.parseJSON(string), data = command.data;
  1057. if (!command.target) {
  1058. if (command.type === "fire") {
  1059. socket.fire(data.type, data.data);
  1060. }
  1061. } else if (command.target === "c") {
  1062. switch (command.type) {
  1063. case "open":
  1064. socket.fire("open");
  1065. break;
  1066. case "close":
  1067. if (!orphan) {
  1068. orphan = true;
  1069. if (data.reason === "aborted") {
  1070. socket.close();
  1071. } else {
  1072. // Gives the heir some time to reconnect
  1073. if (data.heir === options.id) {
  1074. socket.fire("close", data.reason);
  1075. } else {
  1076. setTimeout(function() {
  1077. socket.fire("close", data.reason);
  1078. }, 100);
  1079. }
  1080. }
  1081. }
  1082. break;
  1083. case "message":
  1084. // When using the session transport, message events could be sent before the open event
  1085. if (socket.state() === "connecting") {
  1086. socket.one("open", function() {
  1087. socket.fire.apply(socket, data);
  1088. });
  1089. } else {
  1090. socket.fire.apply(socket, data);
  1091. }
  1092. break;
  1093. }
  1094. }
  1095. }
  1096. function findTrace() {
  1097. var matcher = new RegExp("(?:^|; )(" + encodeURIComponent(name) + ")=([^;]*)").exec(document.cookie);
  1098. if (matcher) {
  1099. return portal.support.parseJSON(decodeURIComponent(matcher[2]));
  1100. }
  1101. }
  1102. // Finds and validates the parent socket's trace from the cookie
  1103. trace = findTrace();
  1104. if (!trace || portal.support.now() - trace.ts > 1000) {
  1105. return;
  1106. }
  1107. // Chooses a connector
  1108. connector = connectors.storage() || connectors.windowref();
  1109. if (!connector) {
  1110. return;
  1111. }
  1112. // For broadcast method
  1113. socket.data("broadcastable", connector);
  1114. return {
  1115. open: function() {
  1116. var traceTimer,
  1117. parentOpened,
  1118. timeout = options.timeout,
  1119. heartbeat = options.heartbeat,
  1120. outbound = options.outbound;
  1121. // Prevents side effects
  1122. options.timeout = options.heartbeat = false;
  1123. options.outbound = function(arg) {
  1124. return arg;
  1125. };
  1126. // Checks the shared one is alive
  1127. traceTimer = setInterval(function() {
  1128. var oldTrace = trace;
  1129. trace = findTrace();
  1130. if (!trace || oldTrace.ts === trace.ts) {
  1131. // Simulates a close signal
  1132. listener(portal.support.stringifyJSON({target: "c", type: "close", data: {reason: "error", heir: oldTrace.heir}}));
  1133. }
  1134. }, 1000);
  1135. // Restores options
  1136. socket.one("close", function() {
  1137. clearInterval(traceTimer);
  1138. options.timeout = timeout;
  1139. options.heartbeat = heartbeat;
  1140. options.outbound = outbound;
  1141. });
  1142. parentOpened = connector.init();
  1143. if (parentOpened) {
  1144. // Gives the user the opportunity to bind connecting event handlers
  1145. setTimeout(function() {
  1146. socket.fire("open");
  1147. }, 50);
  1148. }
  1149. },
  1150. send: function(event) {
  1151. connector.broadcast({target: "p", type: "send", data: event});
  1152. },
  1153. close: function() {
  1154. // Do not signal the parent if this method is executed by the unload event handler
  1155. if (!unloading) {
  1156. connector.broadcast({target: "p", type: "close"});
  1157. }
  1158. }
  1159. };
  1160. },
  1161. // WebSocket
  1162. ws: function(socket) {
  1163. var ws,
  1164. aborted,
  1165. WebSocket = window.WebSocket || window.MozWebSocket;
  1166. if (!WebSocket) {
  1167. return;
  1168. }
  1169. return {
  1170. feedback: true,
  1171. open: function() {
  1172. // Makes an absolute url whose scheme is ws or wss
  1173. var url = portal.support.getAbsoluteURL(socket.data("url")).replace(/^http/, "ws");
  1174. socket.data("url", url);
  1175. ws = new WebSocket(url);
  1176. ws.onopen = function(event) {
  1177. socket.data("event", event).fire("open");
  1178. };
  1179. ws.onmessage = function(event) {
  1180. socket.data("event", event)._fire(event.data);
  1181. };
  1182. ws.onerror = function(event) {
  1183. socket.data("event", event).fire("close", aborted ? "aborted" : "error");
  1184. };
  1185. ws.onclose = function(event) {
  1186. socket.data("event", event).fire("close", aborted ? "aborted" : event.wasClean ? "done" : "error");
  1187. };
  1188. },
  1189. send: function(data) {
  1190. ws.send(data);
  1191. },
  1192. close: function() {
  1193. aborted = true;
  1194. ws.close();
  1195. }
  1196. };
  1197. },
  1198. // HTTP Support
  1199. httpbase: function(socket, options) {
  1200. var send,
  1201. sending,
  1202. queue = [];
  1203. function post() {
  1204. if (queue.length) {
  1205. send(options.url, queue.shift());
  1206. } else {
  1207. sending = false;
  1208. }
  1209. }
  1210. // The Content-Type is not application/x-www-form-urlencoded but text/plain on account of XDomainRequest
  1211. // See the fourth at http://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx
  1212. send = !options.crossDomain || portal.support.corsable ?
  1213. function(url, data) {
  1214. var xhr = portal.support.xhr();
  1215. xhr.onreadystatechange = function() {
  1216. if (xhr.readyState === 4) {
  1217. post();
  1218. }
  1219. };
  1220. xhr.open("POST", url);
  1221. xhr.setRequestHeader("Content-Type", "text/plain; charset=UTF-8");
  1222. if (portal.support.corsable) {
  1223. xhr.withCredentials = options.credentials;
  1224. }
  1225. xhr.send("data=" + data);
  1226. } : window.XDomainRequest && options.xdrURL && options.xdrURL.call(socket, "t") ?
  1227. function(url, data) {
  1228. var xdr = new window.XDomainRequest();
  1229. xdr.onload = xdr.onerror = post;
  1230. xdr.open("POST", options.xdrURL.call(socket, url));
  1231. xdr.send("data=" + data);
  1232. } :
  1233. function(url, data) {
  1234. var iframe,
  1235. textarea,
  1236. form = document.createElement("form");
  1237. form.action = url;
  1238. form.target = "socket-" + (++guid);
  1239. form.method = "POST";
  1240. // IE 6 needs encoding property
  1241. form.enctype = form.encoding = "text/plain";
  1242. form.acceptCharset = "UTF-8";
  1243. form.style.display = "none";
  1244. form.innerHTML = '<textarea name="data"></textarea><iframe name="' + form.target + '"></iframe>';
  1245. textarea = form.firstChild;
  1246. textarea.value = data;
  1247. iframe = form.lastChild;
  1248. portal.support.on(iframe, "load", function() {
  1249. document.body.removeChild(form);
  1250. post();
  1251. });
  1252. document.body.appendChild(form);
  1253. form.submit();
  1254. };
  1255. return {
  1256. send: function(data) {
  1257. queue.push(data);
  1258. if (!sending) {
  1259. sending = true;
  1260. post();
  1261. }
  1262. }
  1263. };
  1264. },
  1265. // Server-Sent Events
  1266. sse: function(socket, options) {
  1267. var es,
  1268. EventSource = window.EventSource;
  1269. if (!EventSource) {
  1270. return;
  1271. } else if (options.crossDomain) {
  1272. try {
  1273. if (!portal.support.corsable || !("withCredentials" in new EventSource("about:blank"))) {
  1274. return;
  1275. }
  1276. } catch(e) {
  1277. return;
  1278. }
  1279. }
  1280. return portal.support.extend(portal.transports.httpbase(socket, options), {
  1281. open: function() {
  1282. var url = socket.data("url");
  1283. // Uses proper constructor for Chrome 10-15
  1284. es = !options.crossDomain ? new EventSource(url) : new EventSource(url, {withCredentials: options.credentials});
  1285. es.onopen = function(event) {
  1286. socket.data("event", event).fire("open");
  1287. };
  1288. es.onmessage = function(event) {
  1289. socket.data("event", event)._fire(event.data);
  1290. };
  1291. es.onerror = function(event) {
  1292. es.close();
  1293. // There is no way to find whether this connection closed normally or not
  1294. socket.data("event", event).fire("close", "done");
  1295. };
  1296. },
  1297. close: function() {
  1298. es.close();
  1299. }
  1300. });
  1301. },
  1302. // Streaming facade
  1303. stream: function(socket) {
  1304. socket.data("candidates").unshift("streamxhr", "streamxdr", "streamiframe");
  1305. },
  1306. // Streaming - XMLHttpRequest
  1307. streamxhr: function(socket, options) {
  1308. var xhr;
  1309. if ((portal.support.browser.msie && +portal.support.browser.version < 10) || (options.crossDomain && !portal.support.corsable)) {
  1310. return;
  1311. }
  1312. return portal.support.extend(portal.transports.httpbase(socket, options), {
  1313. open: function() {
  1314. var stop;
  1315. xhr = portal.support.xhr();
  1316. xhr.onreadystatechange = function() {
  1317. function onprogress() {
  1318. var index = socket.data("index"),
  1319. length = xhr.responseText.length;
  1320. if (!index) {
  1321. socket.fire("open")._fire(xhr.responseText, true);
  1322. } else if (length > index) {
  1323. socket._fire(xhr.responseText.substring(index, length), true);
  1324. }
  1325. socket.data("index", length);
  1326. }
  1327. if (xhr.readyState === 3 && xhr.status === 200) {
  1328. // Despite the change in response, Opera doesn't fire the readystatechange event
  1329. if (portal.support.browser.opera && !stop) {
  1330. stop = portal.support.iterate(onprogress);
  1331. } else {
  1332. onprogress();
  1333. }
  1334. } else if (xhr.readyState === 4) {
  1335. if (stop) {
  1336. stop();
  1337. }
  1338. socket.fire("close", xhr.status === 200 ? "done" : "error");
  1339. }
  1340. };
  1341. xhr.open(options.method || "GET", socket.data("url"));
  1342. if (portal.support.corsable) {
  1343. xhr.withCredentials = options.credentials;
  1344. }
  1345. xhr.send(null);
  1346. },
  1347. close: function() {
  1348. xhr.abort();
  1349. }
  1350. });
  1351. },
  1352. // Streaming - Iframe
  1353. streamiframe: function(socket, options) {
  1354. var doc,
  1355. stop,
  1356. ActiveXObject = window.ActiveXObject;
  1357. if (!ActiveXObject || options.crossDomain) {
  1358. return;
  1359. } else {
  1360. // IE 10 Metro doesn't support ActiveXObject
  1361. try {
  1362. new ActiveXObject("htmlfile");
  1363. } catch(e) {
  1364. return;
  1365. }
  1366. }
  1367. return portal.support.extend(portal.transports.httpbase(socket, options), {
  1368. open: function() {
  1369. var iframe, cdoc;
  1370. doc = new ActiveXObject("htmlfile");
  1371. doc.open();
  1372. doc.close();
  1373. iframe = doc.createElement("iframe");
  1374. iframe.src = socket.data("url");
  1375. doc.body.appendChild(iframe);
  1376. cdoc = iframe.contentDocument || iframe.contentWindow.document;
  1377. stop = portal.support.iterate(function() {
  1378. // Response container
  1379. var container;
  1380. function readDirty() {
  1381. var clone = container.cloneNode(true),
  1382. text;
  1383. // Adds a character not CR and LF to circumvent an Internet Explorer bug
  1384. // If the contents of an element ends with one or more CR or LF, Internet Explorer ignores them in the innerText property
  1385. clone.appendChild(cdoc.createTextNode("."));
  1386. text = clone.innerText;
  1387. return text.substring(0, text.length - 1);
  1388. }
  1389. // Waits the server's container ignorantly
  1390. if (!cdoc.firstChild) {
  1391. return;
  1392. }
  1393. if (options.initIframe) {
  1394. options.initIframe.call(socket, iframe);
  1395. }
  1396. container = cdoc.body.lastChild;
  1397. // Detects connection failure
  1398. if (!container) {
  1399. socket.fire("close", "error");
  1400. return false;
  1401. }
  1402. socket.fire("open")._fire(readDirty(), true);
  1403. container.innerText = "";
  1404. stop = portal.support.iterate(function() {
  1405. var text = readDirty();
  1406. if (text) {
  1407. container.innerText = "";
  1408. socket._fire(text, true);
  1409. }
  1410. if (cdoc.readyState === "complete") {
  1411. socket.fire("close", "done");
  1412. return false;
  1413. }
  1414. });
  1415. return false;
  1416. });
  1417. },
  1418. close: function() {
  1419. stop();
  1420. doc.execCommand("Stop");
  1421. }
  1422. });
  1423. },
  1424. // Streaming - XDomainRequest
  1425. streamxdr: function(socket, options) {
  1426. var xdr,
  1427. XDomainRequest = window.XDomainRequest;
  1428. if (!XDomainRequest || !options.xdrURL || !options.xdrURL.call(socket, "t")) {
  1429. return;
  1430. }
  1431. return portal.support.extend(portal.transports.httpbase(socket, options), {
  1432. open: function() {
  1433. var url = options.xdrURL.call(socket, socket.data("url"));
  1434. socket.data("url", url);
  1435. xdr = new XDomainRequest();
  1436. xdr.onprogress = function() {
  1437. var index = socket.data("index"),
  1438. length = xdr.responseText.length;
  1439. if (!index) {
  1440. socket.fire("open")._fire(xdr.responseText, true);
  1441. } else {
  1442. socket._fire(xdr.responseText.substring(index, length), true);
  1443. }
  1444. socket.data("index", length);
  1445. };
  1446. xdr.onerror = function() {
  1447. socket.fire("close", "error");
  1448. };
  1449. xdr.onload = function() {
  1450. socket.fire("close", "done");
  1451. };
  1452. xdr.open(options.method || "GET", url);
  1453. xdr.send();
  1454. },
  1455. close: function() {
  1456. xdr.abort();
  1457. }
  1458. });
  1459. },
  1460. // Long polling facade
  1461. longpoll: function(socket) {
  1462. socket.data("candidates").unshift("longpollajax", "longpollxdr", "longpolljsonp");
  1463. },
  1464. // Long polling - AJAX
  1465. longpollajax: function(socket, options) {
  1466. var xhr,
  1467. aborted,
  1468. // deprecated
  1469. count = 0;
  1470. if (options.crossDomain && !portal.support.corsable) {
  1471. return;
  1472. }
  1473. return portal.support.extend(portal.transports.httpbase(socket, options), {
  1474. open: function() {
  1475. function poll() {
  1476. var url = socket.buildURL(!count ? "open" : "poll", {count: ++count});
  1477. socket.data("url", url);
  1478. xhr = portal.support.xhr();
  1479. xhr.onreadystatechange = function() {
  1480. var data;
  1481. // Avoids c00c023f error on Internet Explorer 9
  1482. if (!aborted && xhr.readyState === 4) {
  1483. if (xhr.status === 200) {
  1484. data = xhr.responseText;
  1485. if (data || count === 1) {
  1486. if (count === 1) {
  1487. socket.fire("open");
  1488. }
  1489. if (data) {
  1490. socket._fire(data);
  1491. }
  1492. poll();
  1493. } else {
  1494. socket.fire("close", "done");
  1495. }
  1496. } else {
  1497. socket.fire("close", "error");
  1498. }
  1499. }
  1500. };
  1501. xhr.open(options.method || "GET", url);
  1502. if (portal.support.corsable) {
  1503. xhr.withCredentials = options.credentials;
  1504. }
  1505. xhr.send(null);
  1506. }
  1507. if (!options.longpollTest) {
  1508. // Skips the test that checks the server's status
  1509. setTimeout(function() {
  1510. socket.fire("open");
  1511. poll();
  1512. }, 50);
  1513. } else {
  1514. poll();
  1515. }
  1516. },
  1517. close: function() {
  1518. aborted = true;
  1519. xhr.abort();
  1520. }
  1521. });
  1522. },
  1523. // Long polling - XDomainRequest
  1524. longpollxdr: function(socket, options) {
  1525. var xdr,
  1526. // deprecated
  1527. count = 0,
  1528. XDomainRequest = window.XDomainRequest;
  1529. if (!XDomainRequest || !options.xdrURL || !options.xdrURL.call(socket, "t")) {
  1530. return;
  1531. }
  1532. return portal.support.extend(portal.transports.httpbase(socket, options), {
  1533. open: function() {
  1534. function poll() {
  1535. var url = options.xdrURL.call(socket, socket.buildURL(!count ? "open" : "poll", {count: ++count}));
  1536. socket.data("url", url);
  1537. xdr = new XDomainRequest();
  1538. xdr.onload = function() {
  1539. var data = xdr.responseText;
  1540. if (data || count === 1) {
  1541. if (count === 1) {
  1542. socket.fire("open");
  1543. }
  1544. if (data) {
  1545. socket._fire(data);
  1546. }
  1547. poll();
  1548. } else {
  1549. socket.fire("close", "done");
  1550. }
  1551. };
  1552. xdr.onerror = function() {
  1553. socket.fire("close", "error");
  1554. };
  1555. xdr.open(options.method || "GET", url);
  1556. xdr.send();
  1557. }
  1558. if (!options.longpollTest) {
  1559. setTimeout(function() {
  1560. socket.fire("open")