1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 | (function (exports, io, global) {
|
9 |
|
10 | |
11 |
|
12 |
|
13 |
|
14 | exports.Socket = Socket;
|
15 |
|
16 | |
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 | function Socket (options) {
|
24 | this.options = {
|
25 | port: 80
|
26 | , secure: false
|
27 | , document: 'document' in global ? document : false
|
28 | , resource: 'socket.io'
|
29 | , transports: io.transports
|
30 | , 'connect timeout': 10000
|
31 | , 'try multiple transports': true
|
32 | , 'reconnect': true
|
33 | , 'reconnection delay': 500
|
34 | , 'reconnection limit': Infinity
|
35 | , 'reopen delay': 3000
|
36 | , 'max reconnection attempts': 10
|
37 | , 'sync disconnect on unload': true
|
38 | , 'auto connect': true
|
39 | };
|
40 |
|
41 | io.util.merge(this.options, options);
|
42 |
|
43 | this.connected = false;
|
44 | this.open = false;
|
45 | this.connecting = false;
|
46 | this.reconnecting = false;
|
47 | this.namespaces = {};
|
48 | this.buffer = [];
|
49 | this.doBuffer = false;
|
50 |
|
51 | if (this.options['sync disconnect on unload'] &&
|
52 | (!this.isXDomain() || io.util.ua.hasCORS)) {
|
53 | var self = this;
|
54 |
|
55 | io.util.on(global, 'beforeunload', function () {
|
56 | self.disconnectSync();
|
57 | }, false);
|
58 | }
|
59 |
|
60 | if (this.options['auto connect']) {
|
61 | this.connect();
|
62 | }
|
63 | };
|
64 |
|
65 | |
66 |
|
67 |
|
68 |
|
69 | io.util.mixin(Socket, io.EventEmitter);
|
70 |
|
71 | |
72 |
|
73 |
|
74 |
|
75 |
|
76 |
|
77 | Socket.prototype.of = function (name) {
|
78 | if (!this.namespaces[name]) {
|
79 | this.namespaces[name] = new io.SocketNamespace(this, name);
|
80 |
|
81 | if (name !== '') {
|
82 | this.namespaces[name].packet({ type: 'connect' });
|
83 | }
|
84 | }
|
85 |
|
86 | return this.namespaces[name];
|
87 | };
|
88 |
|
89 | |
90 |
|
91 |
|
92 |
|
93 |
|
94 |
|
95 | Socket.prototype.publish = function () {
|
96 | this.emit.apply(this, arguments);
|
97 |
|
98 | var nsp;
|
99 |
|
100 | for (var i in this.namespaces) {
|
101 | if (this.namespaces.hasOwnProperty(i)) {
|
102 | nsp = this.of(i);
|
103 | nsp.$emit.apply(nsp, arguments);
|
104 | }
|
105 | }
|
106 | };
|
107 |
|
108 | |
109 |
|
110 |
|
111 |
|
112 |
|
113 |
|
114 | function empty () { };
|
115 |
|
116 | Socket.prototype.handshake = function (fn) {
|
117 | var self = this
|
118 | , options = this.options;
|
119 |
|
120 | function complete (data) {
|
121 | if (data instanceof Error) {
|
122 | self.onError(data.message);
|
123 | } else {
|
124 | fn.apply(null, data.split(':'));
|
125 | }
|
126 | };
|
127 |
|
128 | var url = [
|
129 | 'http' + (options.secure ? 's' : '') + ':/'
|
130 | , options.host + ':' + options.port
|
131 | , this.options.resource
|
132 | , io.protocol
|
133 | , io.util.query(this.options.query, 't=' + +new Date)
|
134 | ].join('/');
|
135 |
|
136 | if (this.isXDomain()) {
|
137 | var insertAt = document.getElementsByTagName('script')[0]
|
138 | , script = document.createElement('SCRIPT');
|
139 |
|
140 | script.src = url + '&jsonp=' + io.j.length;
|
141 | insertAt.parentNode.insertBefore(script, insertAt);
|
142 |
|
143 | io.j.push(function (data) {
|
144 | complete(data);
|
145 | script.parentNode.removeChild(script);
|
146 | });
|
147 | } else {
|
148 | var xhr = io.util.request();
|
149 |
|
150 | xhr.open('GET', url, true);
|
151 | xhr.onreadystatechange = function () {
|
152 | if (xhr.readyState == 4) {
|
153 | xhr.onreadystatechange = empty;
|
154 |
|
155 | if (xhr.status == 200) {
|
156 | complete(xhr.responseText);
|
157 | } else {
|
158 | !self.reconnecting && self.onError(xhr.responseText);
|
159 | }
|
160 | }
|
161 | };
|
162 | xhr.send(null);
|
163 | }
|
164 | };
|
165 |
|
166 | |
167 |
|
168 |
|
169 |
|
170 |
|
171 |
|
172 | Socket.prototype.getTransport = function (override) {
|
173 | var transports = override || this.transports, match;
|
174 |
|
175 | for (var i = 0, transport; transport = transports[i]; i++) {
|
176 | if (io.Transport[transport]
|
177 | && io.Transport[transport].check(this)
|
178 | && (!this.isXDomain() || io.Transport[transport].xdomainCheck())) {
|
179 | return new io.Transport[transport](this, this.sessionid);
|
180 | }
|
181 | }
|
182 |
|
183 | return null;
|
184 | };
|
185 |
|
186 | |
187 |
|
188 |
|
189 |
|
190 |
|
191 |
|
192 |
|
193 |
|
194 | Socket.prototype.connect = function (fn) {
|
195 | if (this.connecting) {
|
196 | return this;
|
197 | }
|
198 |
|
199 | var self = this;
|
200 |
|
201 | this.handshake(function (sid, heartbeat, close, transports) {
|
202 | self.sessionid = sid;
|
203 | self.closeTimeout = close * 1000;
|
204 | self.heartbeatTimeout = heartbeat * 1000;
|
205 | self.transports = io.util.intersect(
|
206 | transports.split(',')
|
207 | , self.options.transports
|
208 | );
|
209 |
|
210 | function connect (transports){
|
211 | self.transport = self.getTransport(transports);
|
212 | if (!self.transport) return self.publish('connect_failed');
|
213 |
|
214 |
|
215 | self.transport.ready(self, function () {
|
216 | self.connecting = true;
|
217 | self.publish('connecting', self.transport.name);
|
218 | self.transport.open();
|
219 |
|
220 | if (self.options['connect timeout']) {
|
221 | self.connectTimeoutTimer = setTimeout(function () {
|
222 | if (!self.connected) {
|
223 | self.connecting = false;
|
224 |
|
225 | if (self.options['try multiple transports']) {
|
226 | if (!self.remainingTransports) {
|
227 | self.remainingTransports = self.transports.slice(0);
|
228 | }
|
229 |
|
230 | var remaining = self.remainingTransports;
|
231 |
|
232 | while (remaining.length > 0 && remaining.splice(0,1)[0] !=
|
233 | self.transport.name) {}
|
234 |
|
235 | if (remaining.length){
|
236 | connect(remaining);
|
237 | } else {
|
238 | self.publish('connect_failed');
|
239 | }
|
240 | }
|
241 | }
|
242 | }, self.options['connect timeout']);
|
243 | }
|
244 | });
|
245 | }
|
246 |
|
247 | connect();
|
248 |
|
249 | self.once('connect', function (){
|
250 | clearTimeout(self.connectTimeoutTimer);
|
251 |
|
252 | fn && typeof fn == 'function' && fn();
|
253 | });
|
254 | });
|
255 |
|
256 | return this;
|
257 | };
|
258 |
|
259 | |
260 |
|
261 |
|
262 |
|
263 |
|
264 |
|
265 |
|
266 |
|
267 | Socket.prototype.packet = function (data) {
|
268 | if (this.connected && !this.doBuffer) {
|
269 | this.transport.packet(data);
|
270 | } else {
|
271 | this.buffer.push(data);
|
272 | }
|
273 |
|
274 | return this;
|
275 | };
|
276 |
|
277 | |
278 |
|
279 |
|
280 |
|
281 |
|
282 |
|
283 | Socket.prototype.setBuffer = function (v) {
|
284 | this.doBuffer = v;
|
285 |
|
286 | if (!v && this.connected && this.buffer.length) {
|
287 | this.transport.payload(this.buffer);
|
288 | this.buffer = [];
|
289 | }
|
290 | };
|
291 |
|
292 | |
293 |
|
294 |
|
295 |
|
296 |
|
297 |
|
298 |
|
299 | Socket.prototype.disconnect = function () {
|
300 | if (this.connected) {
|
301 | if (this.open) {
|
302 | this.of('').packet({ type: 'disconnect' });
|
303 | }
|
304 |
|
305 |
|
306 | this.onDisconnect('booted');
|
307 | }
|
308 |
|
309 | return this;
|
310 | };
|
311 |
|
312 | |
313 |
|
314 |
|
315 |
|
316 |
|
317 |
|
318 | Socket.prototype.disconnectSync = function () {
|
319 |
|
320 | var xhr = io.util.request()
|
321 | , uri = this.resource + '/' + io.protocol + '/' + this.sessionid;
|
322 |
|
323 | xhr.open('GET', uri, true);
|
324 |
|
325 |
|
326 | this.onDisconnect('booted');
|
327 | };
|
328 |
|
329 | |
330 |
|
331 |
|
332 |
|
333 |
|
334 |
|
335 |
|
336 |
|
337 | Socket.prototype.isXDomain = function () {
|
338 |
|
339 | return false;
|
340 |
|
341 |
|
342 | var locPort = window.location.port || 80;
|
343 | return this.options.host !== document.domain || this.options.port != locPort;
|
344 | };
|
345 |
|
346 | |
347 |
|
348 |
|
349 |
|
350 |
|
351 |
|
352 | Socket.prototype.onConnect = function () {
|
353 | if (!this.connected) {
|
354 | this.connected = true;
|
355 | this.connecting = false;
|
356 | if (!this.doBuffer) {
|
357 |
|
358 | this.setBuffer(false);
|
359 | }
|
360 | this.emit('connect');
|
361 | }
|
362 | };
|
363 |
|
364 | |
365 |
|
366 |
|
367 |
|
368 |
|
369 |
|
370 | Socket.prototype.onOpen = function () {
|
371 | this.open = true;
|
372 | };
|
373 |
|
374 | |
375 |
|
376 |
|
377 |
|
378 |
|
379 |
|
380 | Socket.prototype.onClose = function () {
|
381 | this.open = false;
|
382 | };
|
383 |
|
384 | |
385 |
|
386 |
|
387 |
|
388 |
|
389 |
|
390 | Socket.prototype.onPacket = function (packet) {
|
391 | this.of(packet.endpoint).onPacket(packet);
|
392 | };
|
393 |
|
394 | |
395 |
|
396 |
|
397 |
|
398 |
|
399 |
|
400 | Socket.prototype.onError = function (err) {
|
401 | if (err && err.advice) {
|
402 | if (err.advice === 'reconnect' && this.connected) {
|
403 | this.disconnect();
|
404 | this.reconnect();
|
405 | }
|
406 | }
|
407 |
|
408 | this.publish('error', err && err.reason ? err.reason : err);
|
409 | };
|
410 |
|
411 | |
412 |
|
413 |
|
414 |
|
415 |
|
416 |
|
417 | Socket.prototype.onDisconnect = function (reason) {
|
418 | var wasConnected = this.connected;
|
419 |
|
420 | this.connected = false;
|
421 | this.connecting = false;
|
422 | this.open = false;
|
423 |
|
424 | if (wasConnected) {
|
425 | this.transport.close();
|
426 | this.transport.clearTimeouts();
|
427 | this.publish('disconnect', reason);
|
428 |
|
429 | if ('booted' != reason && this.options.reconnect && !this.reconnecting) {
|
430 | this.reconnect();
|
431 | }
|
432 | }
|
433 | };
|
434 |
|
435 | |
436 |
|
437 |
|
438 |
|
439 |
|
440 |
|
441 | Socket.prototype.reconnect = function () {
|
442 | this.reconnecting = true;
|
443 | this.reconnectionAttempts = 0;
|
444 | this.reconnectionDelay = this.options['reconnection delay'];
|
445 |
|
446 | var self = this
|
447 | , maxAttempts = this.options['max reconnection attempts']
|
448 | , tryMultiple = this.options['try multiple transports']
|
449 | , limit = this.options['reconnection limit'];
|
450 |
|
451 | function reset () {
|
452 | if (self.connected) {
|
453 | for (var i in self.namespaces) {
|
454 | if (self.namespaces.hasOwnProperty(i) && '' !== i) {
|
455 | self.namespaces[i].packet({ type: 'connect' });
|
456 | }
|
457 | }
|
458 | self.publish('reconnect', self.transport.name, self.reconnectionAttempts);
|
459 | }
|
460 |
|
461 | self.removeListener('connect_failed', maybeReconnect);
|
462 | self.removeListener('connect', maybeReconnect);
|
463 |
|
464 | self.reconnecting = false;
|
465 |
|
466 | delete self.reconnectionAttempts;
|
467 | delete self.reconnectionDelay;
|
468 | delete self.reconnectionTimer;
|
469 | delete self.redoTransports;
|
470 |
|
471 | self.options['try multiple transports'] = tryMultiple;
|
472 | };
|
473 |
|
474 | function maybeReconnect () {
|
475 | if (!self.reconnecting) {
|
476 | return;
|
477 | }
|
478 |
|
479 | if (self.connected) {
|
480 | return reset();
|
481 | };
|
482 |
|
483 | if (self.connecting && self.reconnecting) {
|
484 | return self.reconnectionTimer = setTimeout(maybeReconnect, 1000);
|
485 | }
|
486 |
|
487 | if (self.reconnectionAttempts++ >= maxAttempts) {
|
488 | if (!self.redoTransports) {
|
489 | self.on('connect_failed', maybeReconnect);
|
490 | self.options['try multiple transports'] = true;
|
491 | self.transport = self.getTransport();
|
492 | self.redoTransports = true;
|
493 | self.connect();
|
494 | } else {
|
495 | self.publish('reconnect_failed');
|
496 | reset();
|
497 | }
|
498 | } else {
|
499 | if (self.reconnectionDelay < limit) {
|
500 | self.reconnectionDelay *= 2;
|
501 | }
|
502 |
|
503 | self.connect();
|
504 | self.publish('reconnecting', self.reconnectionDelay, self.reconnectionAttempts);
|
505 | self.reconnectionTimer = setTimeout(maybeReconnect, self.reconnectionDelay);
|
506 | }
|
507 | };
|
508 |
|
509 | this.options['try multiple transports'] = false;
|
510 | this.reconnectionTimer = setTimeout(maybeReconnect, this.reconnectionDelay);
|
511 |
|
512 | this.on('connect', maybeReconnect);
|
513 | };
|
514 |
|
515 | })(
|
516 | 'undefined' != typeof io ? io : module.exports
|
517 | , 'undefined' != typeof io ? io : module.parent.exports
|
518 | , this
|
519 | );
|