drunkmonkey 0.0.1

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