drunkmonkey 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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 */