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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +87 -0
- data/Rakefile +1 -0
- data/drunkmonkey.gemspec +31 -0
- data/lib/drunkmonkey/transport.rb +157 -0
- data/lib/drunkmonkey/version.rb +3 -0
- data/lib/drunkmonkey.rb +86 -0
- data/sample/app.rb +26 -0
- data/sample/public/index.html +54 -0
- data/sample/public/portal.js +1860 -0
- metadata +130 -0
@@ -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 */
|