PageRenderTime 35ms CodeModel.GetById 1ms RepoModel.GetById 0ms app.codeStats 0ms

/files/portal/1.0.1/portal.js

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