total5 0.0.1 → 0.0.2

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.
package/websocket.js ADDED
@@ -0,0 +1,1944 @@
1
+ // Total.js WebSocket
2
+ // The MIT License
3
+ // Copyright 2016-2023 (c) Peter Širka <petersirka@gmail.com> & Jozef Gula <gula.jozef@gmail.com>
4
+
5
+ const SOCKET_RESPONSE = 'HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: {0}\r\n\r\n';
6
+ const SOCKET_RESPONSE_COMPRESS = 'HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: {0}\r\nSec-WebSocket-Extensions: permessage-deflate\r\n\r\n';
7
+ const SOCKET_RESPONSE_PROTOCOL = 'HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: {0}\r\nSec-WebSocket-Protocol: {1}\r\n\r\n';
8
+ const SOCKET_RESPONSE_PROTOCOL_COMPRESS = 'HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: {0}\r\nSec-WebSocket-Protocol: {1}\r\nSec-WebSocket-Extensions: permessage-deflate\r\n\r\n';
9
+ const SOCKET_HASH = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
10
+ const SOCKET_ALLOW_VERSION = ['13'];
11
+ const SOCKET_COMPRESS = Buffer.from([0x00, 0x00, 0xFF, 0xFF]);
12
+ const SOCKET_COMPRESS_OPTIONS = { windowBits: F.Zlib.Z_DEFAULT_WINDOWBITS };
13
+
14
+ const CACHE_GML1 = [null, null, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
15
+ const CACHE_GML2 = [null, null, null, null, null, null, null, null];
16
+ const CONCAT = [null, null];
17
+
18
+ const REG_WEBSOCKET = /websocket/i;
19
+ const REG_WEBSOCKET_ERROR = /ECONNRESET|EHOSTUNREACH|EPIPE|is closed/i;
20
+ const REG_EMPTYBUFFER = /\0|%00|\\u0000/g;
21
+ const REG_EMPTYBUFFER_TEST = /\0|%00|\\u0000/;
22
+ const REG_ROBOT = /search|agent|bot|crawler|spider/i;
23
+ const REG_MOBILE = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|Mobile|Tablet/i;
24
+
25
+ var WSCLIENTSID = 0;
26
+
27
+ // WSClient
28
+ var CALLBACKS = {};
29
+ var CALLBACKSCOUNTER = 1;
30
+
31
+ function Controller(req, socket, head) {
32
+
33
+ var ctrl = this;
34
+
35
+ ctrl.req = req;
36
+ ctrl.method = 'SOCKET';
37
+ ctrl.res = ctrl.socket = socket;
38
+ ctrl.route = null;
39
+ ctrl.head = head;
40
+ ctrl.uri = F.TUtils.parseURI2(req.url);
41
+ ctrl.headers = req.headers;
42
+ ctrl.query = ctrl.uri.search.parseEncoded();
43
+ ctrl.split = ctrl.uri.split;
44
+ ctrl.split2 = [];
45
+ ctrl.url = ctrl.uri.key;
46
+ ctrl.released = false;
47
+ ctrl.params = {};
48
+ ctrl.current = {};
49
+ ctrl.masking = false;
50
+ ctrl.iswebsocket = true;
51
+
52
+ for (let path of ctrl.split)
53
+ ctrl.split2.push(path.toLowerCase());
54
+
55
+ ctrl.datatype = 'json'; // json|text|binary
56
+ }
57
+
58
+ Controller.prototype = {
59
+
60
+ get mobile() {
61
+ let ua = this.headers['user-agent'];
62
+ return ua ? REG_MOBILE.test(ua) : false;
63
+ },
64
+
65
+ get robot() {
66
+ let ua = this.headers['user-agent'];
67
+ return ua ? REG_ROBOT.test(ua) : false;
68
+ },
69
+
70
+ get ua() {
71
+ if (this.$ua != null)
72
+ return this.$ua;
73
+ let ua = this.headers['user-agent'];
74
+ this.$ua = ua ? ua.parseUA() : '';
75
+ return this.$ua;
76
+ },
77
+
78
+ get ip() {
79
+
80
+ if (this.$ip != null)
81
+ return this.$ip;
82
+
83
+ // x-forwarded-for: client, proxy1, proxy2, ...
84
+ let proxy = this.headers['x-forwarded-for'];
85
+ if (proxy)
86
+ this.$ip = proxy.split(',', 1)[0] || this.req.connection.remoteAddress;
87
+ else if (!this.$ip)
88
+ this.$ip = this.req.connection.remoteAddress;
89
+
90
+ return this.$ip;
91
+ },
92
+
93
+ get referrer() {
94
+ return this.headers.referer;
95
+ }
96
+
97
+ };
98
+
99
+ Controller.prototype.upgrade = function(websocket) {
100
+ var ctrl = this;
101
+ F.stats.performance.online++;
102
+ ctrl.parent = websocket;
103
+ ctrl.socket.$controller = this;
104
+ ctrl.req.on('abort', websocket_onerror);
105
+ ctrl.req.on('aborted', websocket_onerror);
106
+ ctrl.req.on('error', websocket_onerror);
107
+ ctrl.socket.on('data', websocket_ondata);
108
+ ctrl.socket.on('error', websocket_onerror);
109
+ ctrl.socket.on('close', websocket_close);
110
+ ctrl.socket.on('end', websocket_close);
111
+ ctrl.parent.add(ctrl);
112
+ websocket.online++;
113
+ F.$events.websocket_begin && F.emit('websocket_begin', ctrl.parent, ctrl);
114
+ ctrl.parent.$events.open && ctrl.parent.emit('open', ctrl);
115
+ };
116
+
117
+ // Readonly
118
+ Controller.prototype.cookie = function(name) {
119
+
120
+ var ctrl = this;
121
+ var arr;
122
+
123
+ if (ctrl.cookies)
124
+ return F.TUtils.decodeURIComponent(ctrl.cookies[name] || '');
125
+
126
+ var cookie = ctrl.headers.cookie;
127
+ if (!cookie) {
128
+ ctrl.cookies = F.EMPTYOBJECT;
129
+ return '';
130
+ }
131
+
132
+ ctrl.cookies = {};
133
+
134
+ arr = cookie.split(';');
135
+ for (let i = 0; i < arr.length; i++) {
136
+ let line = arr[i].trim();
137
+ let index = line.indexOf('=');
138
+ if (index !== -1)
139
+ ctrl.cookies[line.substring(0, index)] = line.substring(index + 1);
140
+ }
141
+
142
+ return name ? F.TUtils.decodeURIComponent(ctrl.cookies[name] || '') : '';
143
+ };
144
+
145
+ function websocket_ondata(chunk) {
146
+ this.$controller.ondata(chunk);
147
+ }
148
+
149
+ function websocket_onerror(e) {
150
+ this.destroy && this.destroy();
151
+ this.$controller && this.$controller.onerror(e);
152
+ }
153
+
154
+ function websocket_close() {
155
+ this.$controller && this.$controller.onclose();
156
+ this.destroy && this.destroy();
157
+ }
158
+
159
+ Controller.prototype.destroy = function() {
160
+ var ctrl = this;
161
+ ctrl.socket.destroy();
162
+ ctrl.req.destroy();
163
+ F.TUtils.destroystream(ctrl.socket);
164
+ F.TUtils.destroystream(ctrl.req);
165
+ };
166
+
167
+ Controller.prototype.onerror = function(err) {
168
+
169
+ var ctrl = this;
170
+
171
+ if (ctrl.isclosed)
172
+ return;
173
+
174
+ if (REG_WEBSOCKET_ERROR.test(err.stack)) {
175
+ ctrl.isclosed = true;
176
+ ctrl.onclose();
177
+ } else
178
+ ctrl.parent.$events.error && ctrl.parent.emit('error', err, ctrl);
179
+ };
180
+
181
+ Controller.prototype.onclose = function() {
182
+
183
+ var ctrl = this;
184
+
185
+ if (ctrl.isclosed2)
186
+ return;
187
+
188
+ F.stats.performance.online--;
189
+
190
+ ctrl.isclosed = true;
191
+ ctrl.isclosed2 = true;
192
+
193
+ if (ctrl.inflate) {
194
+ ctrl.inflate.removeAllListeners();
195
+ delete ctrl.inflate;
196
+ delete ctrl.inflatechunks;
197
+ }
198
+
199
+ if (ctrl.deflate) {
200
+ ctrl.deflate.removeAllListeners();
201
+ delete ctrl.deflate;
202
+ delete ctrl.deflatechunks;
203
+ }
204
+
205
+ delete ctrl.parent.connections[ctrl.ID];
206
+ ctrl.parent.online--;
207
+ ctrl.parent.$events.close && ctrl.parent.emit('close', ctrl, ctrl.closecode, ctrl.closemessage);
208
+
209
+ ctrl.socket.removeAllListeners();
210
+ F.$events.websocket_end && EMIT('websocket_end', ctrl.parent, ctrl);
211
+
212
+ };
213
+
214
+ Controller.prototype.ondata = function(data) {
215
+
216
+ var ctrl = this;
217
+ var current = ctrl.current;
218
+
219
+ if (data) {
220
+ if (current.buffer) {
221
+ CONCAT[0] = current.buffer;
222
+ CONCAT[1] = data;
223
+ current.buffer = Buffer.concat(CONCAT);
224
+ } else
225
+ current.buffer = data;
226
+ }
227
+
228
+ if (!ctrl.parse())
229
+ return;
230
+
231
+ if (!current.final && current.type !== 0x00)
232
+ current.type2 = current.type;
233
+
234
+ var decompress = current.compressed && ctrl.inflate;
235
+
236
+ switch (current.type === 0x00 ? current.type2 : current.type) {
237
+
238
+ case 0x01:
239
+
240
+ // text
241
+ if (decompress) {
242
+ current.final && ctrl.parseinflate();
243
+ } else {
244
+ if (current.body) {
245
+ CONCAT[0] = current.body;
246
+ CONCAT[1] = current.data;
247
+ current.body = Buffer.concat(CONCAT);
248
+ } else
249
+ current.body = current.data;
250
+ current.final && ctrl.decode();
251
+ }
252
+ break;
253
+
254
+ case 0x02:
255
+
256
+ // binary
257
+ if (decompress) {
258
+ current.final && ctrl.parseinflate();
259
+ } else {
260
+ if (current.body) {
261
+ CONCAT[0] = current.body;
262
+ CONCAT[1] = current.data;
263
+ current.body = Buffer.concat(CONCAT);
264
+ } else
265
+ current.body = current.data;
266
+ current.final && ctrl.decode();
267
+ }
268
+ break;
269
+
270
+ case 0x08:
271
+
272
+ // close
273
+ if (current.data) {
274
+ ctrl.closemessage = current.data.slice(2).toString('utf8');
275
+ ctrl.closecode = current.data[0] << 8 | current.data[1];
276
+ }
277
+
278
+ if (ctrl.closemessage && ctrl.parent.encodedecode)
279
+ ctrl.closemessage = F.TUtils.decodeURIComponent(ctrl.closemessage);
280
+
281
+ ctrl.close();
282
+ current.buffer = null;
283
+ current.inflatedata = null;
284
+ return;
285
+
286
+ case 0x09:
287
+ // ping, response pong
288
+ ctrl.socket.write(getWebSocketFrame(0, 'PONG', 0x0A, false, ctrl.masking));
289
+ current.buffer = null;
290
+ current.inflatedata = null;
291
+ break;
292
+
293
+ case 0x0a:
294
+ // pong
295
+ ctrl.latency = Date.now() - ctrl.$ping;
296
+ current.buffer = null;
297
+ current.inflatedata = null;
298
+ break;
299
+ }
300
+
301
+ if (current.buffer) {
302
+ current.buffer = current.buffer.slice(current.length, current.buffer.length);
303
+ current.buffer.length && setImmediate(ctrl.ondata2);
304
+ }
305
+ };
306
+
307
+ // MIT
308
+ // Written by Jozef Gula
309
+ // Optimized by Peter Sirka
310
+ Controller.prototype.parse = function() {
311
+
312
+ var ctrl = this;
313
+ var current = ctrl.current;
314
+
315
+ // Fixed a problem with parsing of long messages, the code bellow 0x80 still returns 0 when the message is longer
316
+ // if (!current.buffer || current.buffer.length <= 2 || ((current.buffer[0] & 0x80) >> 7) !== 1)
317
+ if (!current.buffer || current.buffer.length <= 2)
318
+ return;
319
+
320
+ // WebSocket - Opcode
321
+ current.type = current.buffer[0] & 0x0f;
322
+
323
+ // Compression
324
+ // Type must be greater than 0
325
+ if (current.type)
326
+ current.compressed = (current.buffer[0] & 0x40) === 0x40;
327
+
328
+ // is final message?
329
+ current.final = ((current.buffer[0] & 0x80) >> 7) === 0x01;
330
+
331
+ // does frame contain mask?
332
+ current.isMask = ((current.buffer[1] & 0xfe) >> 7) === 0x01;
333
+
334
+ // data length
335
+ var length = getMessageLength(current.buffer, F.isLE);
336
+ // index for data
337
+
338
+ // Solving a problem with The value "-1" is invalid for option "size"
339
+ if (length <= 0)
340
+ return current.final;
341
+
342
+ var index = current.buffer[1] & 0x7f;
343
+ index = ((index === 126) ? 4 : (index === 127 ? 10 : 2)) + (current.isMask ? 4 : 0);
344
+
345
+ // total message length (data + header)
346
+ var mlength = index + length;
347
+
348
+ if (mlength > ctrl.route.size) {
349
+ ctrl.close(1009, 'Frame is too large');
350
+ return;
351
+ }
352
+
353
+ // Check length of data
354
+ if (current.buffer.length < mlength)
355
+ return;
356
+
357
+ current.length = mlength;
358
+
359
+ // Not Ping & Pong
360
+ if (current.type !== 0x09 && current.type !== 0x0A) {
361
+
362
+ // does frame contain mask?
363
+ if (current.isMask) {
364
+ current.mask = Buffer.alloc(4);
365
+ current.buffer.copy(current.mask, 0, index - 4, index);
366
+ }
367
+
368
+ if (current.compressed && ctrl.inflate) {
369
+
370
+ var buf = Buffer.alloc(length);
371
+ current.buffer.copy(buf, 0, index, mlength);
372
+
373
+ // does frame contain mask?
374
+ if (current.isMask) {
375
+ for (var i = 0; i < length; i++)
376
+ buf[i] = buf[i] ^ current.mask[i % 4];
377
+ }
378
+
379
+ // Does the buffer continue?
380
+ buf.$continue = current.final === false;
381
+ ctrl.inflatepending.push(buf);
382
+
383
+ } else {
384
+
385
+ current.data = Buffer.alloc(length);
386
+ current.buffer.copy(current.data, 0, index, mlength);
387
+
388
+ if (current.isMask) {
389
+ for (var i = 0; i < length; i++)
390
+ current.data[i] = current.data[i] ^ current.mask[i % 4];
391
+ }
392
+ }
393
+ }
394
+
395
+ return true;
396
+ };
397
+
398
+ Controller.prototype.decode = function() {
399
+
400
+ var ctrl = this;
401
+ var data = ctrl.current.body;
402
+
403
+ F.stats.performance.message++;
404
+ F.stats.performance.download += data.length / 1024 / 1024;
405
+
406
+ switch (ctrl.datatype) {
407
+
408
+ case 'binary':
409
+ ctrl.parent.$events.message && ctrl.parent.emit('message', ctrl, data);
410
+ break;
411
+
412
+ case 'json':
413
+
414
+ if (data instanceof Buffer)
415
+ data = data.toString('utf8');
416
+
417
+ if (ctrl.parent.encodedecode === true)
418
+ data = F.TUtils.decodeURIComponent(data);
419
+
420
+ if (ctrl.parent.encryptdecrypt && F.config.secret_encryption)
421
+ data = F.TUtils.decrypt_data(data, F.config.secret_encryption);
422
+
423
+ if (data.isJSON()) {
424
+
425
+ let tmp = data.parseJSON(true);
426
+
427
+ if (REG_EMPTYBUFFER_TEST.test(tmp))
428
+ tmp = tmp.replace(REG_EMPTYBUFFER, '');
429
+
430
+ if (tmp !== undefined && ctrl.parent.$events.message)
431
+ ctrl.parent.emit('message', this, tmp);
432
+ }
433
+ break;
434
+
435
+ default: // TEXT
436
+
437
+ if (data instanceof Buffer)
438
+ data = data.toString('utf8');
439
+
440
+ if (ctrl.parent.encodedecode === true)
441
+ data = F.TUtils.decodeURIComponent(data);
442
+
443
+ if (ctrl.parent.encryptdecrypt && F.config.secret_encryption)
444
+ data = F.TUtils.decrypt_data(data, F.config.secret_encryption);
445
+
446
+ if (REG_EMPTYBUFFER_TEST.test(data))
447
+ data = data.replace(REG_EMPTYBUFFER, '');
448
+
449
+ ctrl.parent.$events.message && ctrl.parent.emit('message', ctrl, data);
450
+ break;
451
+ }
452
+
453
+ ctrl.current.body = null;
454
+ };
455
+
456
+ Controller.prototype.parseinflate = function() {
457
+
458
+ var ctrl = this;
459
+
460
+ if (ctrl.inflatelock)
461
+ return;
462
+
463
+ var buf = ctrl.inflatepending.shift();
464
+ if (buf) {
465
+ ctrl.inflatechunks = [];
466
+ ctrl.inflatechunkslength = 0;
467
+ ctrl.inflatelock = true;
468
+ ctrl.inflate.write(buf);
469
+
470
+ if (!buf.$continue)
471
+ ctrl.inflate.write(Buffer.from(SOCKET_COMPRESS));
472
+
473
+ ctrl.inflate.flush(function() {
474
+
475
+ if (!ctrl.inflatechunks)
476
+ return;
477
+
478
+ var data = concat(ctrl.inflatechunks, ctrl.inflatechunkslength);
479
+
480
+ ctrl.inflatechunks = null;
481
+ ctrl.inflatelock = false;
482
+
483
+ if (data.length > ctrl.route.size) {
484
+ ctrl.close(1009, 'Frame is too large');
485
+ return;
486
+ }
487
+
488
+ if (ctrl.current.body) {
489
+ CONCAT[0] = ctrl.current.body;
490
+ CONCAT[1] = data;
491
+ ctrl.current.body = Buffer.concat(CONCAT);
492
+ } else
493
+ ctrl.current.body = data;
494
+
495
+ !buf.$continue && ctrl.decode();
496
+ ctrl.parseinflate();
497
+ });
498
+ }
499
+ };
500
+
501
+ Controller.prototype.send = function(message, raw, replacer) {
502
+
503
+ var ctrl = this;
504
+
505
+ if (ctrl.isclosed)
506
+ return ctrl;
507
+
508
+ var buffer;
509
+
510
+ if (ctrl.datatype !== 'binary') {
511
+
512
+ var data = ctrl.datatype === 'text' ? (raw ? message : JSON.stringify(message, replacer == true ? F.TUtils.json2replacer : replacer)) : typeof(message) === 'object' ? JSON.stringify(message, replacer == true ? F.TUtils.json2replacer : replacer) : (message + '');
513
+
514
+ if (ctrl.parent.encryptdecrypt && F.config.secret_encryption)
515
+ data = F.TUtils.encrypt_data(data, F.config.secret_encryption);
516
+
517
+ if (ctrl.parent.encodedecode === true && data)
518
+ data = encodeURIComponent(data);
519
+
520
+ if (ctrl.deflate) {
521
+ buffer = Buffer.from(data, 'utf8');
522
+ ctrl.deflatepending.push(buffer);
523
+ ctrl.senddeflate();
524
+ } else {
525
+ buffer = Buffer.from(data, 'utf8');
526
+ ctrl.socket.write(getWebSocketFrame(0, buffer, 0x01, false, ctrl.masking));
527
+ }
528
+
529
+ } else if (message) {
530
+ buffer = message;
531
+ if (ctrl.deflate) {
532
+ ctrl.deflatepending.push(message);
533
+ ctrl.senddeflate();
534
+ } else
535
+ ctrl.socket.write(getWebSocketFrame(0, message, 0x02, false, ctrl.masking));
536
+ }
537
+
538
+ if (buffer)
539
+ F.stats.performance.upload += buffer.length / 1024 / 1024;
540
+
541
+ };
542
+
543
+ Controller.prototype.senddeflate = function() {
544
+
545
+ var ctrl = this;
546
+
547
+ if (ctrl.deflatelock)
548
+ return;
549
+
550
+ var buf = ctrl.deflatepending.shift();
551
+ if (buf) {
552
+ ctrl.deflatechunks = [];
553
+ ctrl.deflatechunkslength = 0;
554
+ ctrl.deflatelock = true;
555
+ ctrl.deflate.write(buf);
556
+ ctrl.deflate.flush(function() {
557
+ if (ctrl.deflatechunks) {
558
+ var data = concat(ctrl.deflatechunks, ctrl.deflatechunkslength);
559
+ data = data.slice(0, data.length - 4);
560
+ ctrl.deflatelock = false;
561
+ ctrl.deflatechunks = null;
562
+ ctrl.socket.write(getWebSocketFrame(0, data, ctrl.type === 'binary' ? 0x02 : 0x01, true, ctrl.masking));
563
+ ctrl.senddeflate();
564
+ }
565
+ });
566
+ }
567
+ };
568
+
569
+ Controller.prototype.ping = function(ts) {
570
+ var ctrl = this;
571
+ if (!ctrl.isclosed) {
572
+ try {
573
+ ctrl.$ping = ts || Date.now();
574
+ ctrl.socket.write(getWebSocketFrame(0, 'PING', 0x09, false, ctrl.masking));
575
+ } catch (e) {
576
+ // Socket error
577
+ ctrl.onerror(e);
578
+ }
579
+ }
580
+ return ctrl;
581
+ };
582
+
583
+ function websocketclientdestroy(ctrl) {
584
+ ctrl.socket.destroy();
585
+ F.TUtils.destroystream(ctrl.socket);
586
+ F.TUtils.destroystream(ctrl.req);
587
+ }
588
+
589
+ function websocketclientsendfin(ctrl) {
590
+ ctrl.socket.end(getWebSocketFrame(ctrl.closecode, ctrl.closemessage, 0x08, false, ctrl.masking));
591
+ setImmediate(websocketclientdestroy, ctrl);
592
+ }
593
+
594
+ Controller.prototype.close = function(code, message) {
595
+
596
+ var ctrl = this;
597
+
598
+ if (!ctrl.isclosed) {
599
+
600
+ ctrl.isclosed = true;
601
+
602
+ if (ctrl.ready) {
603
+ if (message && ctrl.parent && ctrl.parent.encodedecode)
604
+ message = encodeURIComponent(message);
605
+
606
+ if (ctrl.closecode) {
607
+ setImmediate(websocketclientdestroy, ctrl);
608
+ } else {
609
+ ctrl.closecode = code || 1000;
610
+ ctrl.closemessage = message || '';
611
+ setTimeout(websocketclientsendfin, 1000, ctrl);
612
+ }
613
+
614
+ } else if (!ctrl.closecode) {
615
+ ctrl.socket.end();
616
+ setImmediate(websocketclientdestroy, ctrl);
617
+ }
618
+ }
619
+ };
620
+
621
+ Controller.prototype.sign = function(ctrl) {
622
+ var sha1 = F.Crypto.createHash('sha1');
623
+ sha1.update((ctrl.headers['sec-websocket-key'] || '') + SOCKET_HASH);
624
+ return sha1.digest('base64');
625
+ };
626
+
627
+ function concat(buffers, length) {
628
+ var buffer = Buffer.alloc(length);
629
+ var offset = 0;
630
+ for (var i = 0, n = buffers.length; i < n; i++) {
631
+ buffers[i].copy(buffer, offset);
632
+ offset += buffers[i].length;
633
+ }
634
+ return buffer;
635
+ }
636
+
637
+ function WebSocket(url, route, params) {
638
+ var t = this;
639
+ t.url = url;
640
+ t.online = 0;
641
+ t.connections = {};
642
+ t.route = route;
643
+ t.params = params;
644
+ // t.autocloseid = null;
645
+ F.TUtils.EventEmitter2.extend(t);
646
+ }
647
+
648
+ WebSocket.prototype.encrypt = function(enable) {
649
+ this.encryptdecrypt = enable === true || enable == null;
650
+ };
651
+
652
+ WebSocket.prototype.find = function(fn) {
653
+ var self = this;
654
+ for (var key in self.connections) {
655
+ var ctrl = self.connections[key];
656
+ if (fn(ctrl))
657
+ return ctrl;
658
+ }
659
+ };
660
+
661
+ WebSocket.prototype.send = function(message, comparer, replacer, params) {
662
+
663
+ var self = this;
664
+
665
+ if (message === undefined)
666
+ return self;
667
+
668
+ if (!params && replacer != null && typeof(replacer) !== 'function') {
669
+ params = replacer;
670
+ replacer = null;
671
+ }
672
+
673
+ var raw = false;
674
+ var data = null;
675
+
676
+ for (var key in self.connections) {
677
+ var ctrl = self.connections[key];
678
+ if (data == null) {
679
+ if (ctrl.datatype === 'json') {
680
+ raw = true;
681
+ data = JSON.stringify(message, replacer == true ? F.TUtils.json2replacer : replacer);
682
+ } else
683
+ data = message;
684
+ }
685
+
686
+ if (comparer && !comparer(ctrl, message, params))
687
+ continue;
688
+
689
+ ctrl.send(data, raw);
690
+ F.stats.response.websocket++;
691
+ }
692
+
693
+ return self;
694
+ };
695
+
696
+ // Ping all connections
697
+ WebSocket.prototype.ping = function() {
698
+
699
+ var self = this;
700
+
701
+ self.$ping = true;
702
+ F.stats.other.websocketping++;
703
+
704
+ var ts = Date.now();
705
+ for (var key in self.connections)
706
+ self.connections[key].ping(ts);
707
+
708
+ return self;
709
+ };
710
+
711
+ WebSocket.prototype.api = function(api) {
712
+ var self = this;
713
+
714
+ if (!api.startsWith('/@')) {
715
+ if (api[0] !== '@')
716
+ api = '@' + api;
717
+ api = '/' + api + '/';
718
+ }
719
+
720
+ self.on('message', function(client, msg) {
721
+ if (msg && msg.TYPE === 'api')
722
+ client.exec(api, msg);
723
+ });
724
+
725
+ return self;
726
+ };
727
+
728
+ WebSocket.prototype.close = function(code, message) {
729
+
730
+ var self = this;
731
+
732
+ for (var key in self.connections) {
733
+ self.connections[key].close(code, message);
734
+ delete self.connections[key];
735
+ }
736
+
737
+ self.online = 0;
738
+ return self;
739
+ };
740
+
741
+ WebSocket.prototype.error = function(err) {
742
+ var self = this;
743
+ F.error(typeof(err) === 'string' ? new Error(err) : err, self.name, self.url);
744
+ };
745
+
746
+ WebSocket.prototype.destroy = function() {
747
+
748
+ var self = this;
749
+
750
+ if (!self.connections)
751
+ return self;
752
+
753
+ self.close();
754
+ self.$events.destroy && self.emit('destroy');
755
+ delete F.connections[self.url];
756
+
757
+ setTimeout(function(self) {
758
+
759
+ for (var key in self.connections) {
760
+ var conn = self.connections[key];
761
+ if (conn) {
762
+ conn.isclosed2 = true;
763
+ conn.socket.removeAllListeners();
764
+ }
765
+ }
766
+
767
+ var index = self.route.connections.indexOf(self);
768
+ if (index !== -1)
769
+ self.route.connections.splice(index, 1);
770
+
771
+ }, 1000, self);
772
+
773
+ };
774
+
775
+ WebSocket.prototype.add = function(ctrl) {
776
+ this.connections[ctrl.ID] = ctrl;
777
+ };
778
+
779
+ WebSocket.prototype.check = function() {
780
+ var self = this;
781
+ if (self.$ping) {
782
+ for (var key in self.connections) {
783
+ var ctrl = self.connections[key];
784
+ if (ctrl.$ping && (ctrl.latency == null || ctrl.latency > F.config.$wsmaxlatency)) {
785
+ ctrl.close();
786
+ F.stats.other.websocketcleaner++;
787
+ }
788
+ }
789
+ }
790
+ };
791
+
792
+ function wsdestroy_open() {
793
+ var self = this;
794
+ if (self.autocloseid) {
795
+ clearTimeout(self.autocloseid);
796
+ self.autocloseid = null;
797
+ }
798
+ }
799
+
800
+ function wsdestroy_close(self) {
801
+
802
+ // Checks again online state
803
+ if (self.online) {
804
+ self.autocloseid = null;
805
+ return;
806
+ }
807
+
808
+ if (self.autodestroyitems) {
809
+ for (var fn of self.autodestroyitems)
810
+ fn.call(self);
811
+ self.autodestroyitems = null;
812
+ }
813
+ self.destroy();
814
+ }
815
+
816
+ WebSocket.prototype.autodestroy = function(callback) {
817
+
818
+ var self = this;
819
+
820
+ if (self.autodestroyitems) {
821
+ self.autodestroyitems.push(callback);
822
+ return self;
823
+ }
824
+
825
+ self.autodestroyitems = [];
826
+ callback && self.autodestroyitems.push(callback);
827
+ self.on('open', wsdestroy_open);
828
+ self.on('close', function() {
829
+ if (!self.online)
830
+ self.autocloseid = setTimeout(wsdestroy_close, 5000, self);
831
+ });
832
+
833
+ return self;
834
+ };
835
+
836
+ function authorize(ctrl) {
837
+ if (F.def.onAuthorize) {
838
+ var opt = new F.TBuilders.Options(ctrl);
839
+ opt.TYPE = 'auth'; // important
840
+ opt.query = ctrl.query;
841
+ opt.iswebsocket = true;
842
+ opt.next = opt.callback;
843
+ opt.$callback = function(err, user) {
844
+ let auth = user ? 1 : 2;
845
+ ctrl.user = user;
846
+ if (ctrl.route.auth === auth) {
847
+ execute(ctrl);
848
+ } else {
849
+ ctrl.route = F.TRouting.lookupwebsocket(ctrl, auth);
850
+ if (ctrl.route)
851
+ execute(ctrl);
852
+ else
853
+ ctrl.close(4001);
854
+ }
855
+ };
856
+ F.def.onAuthorize(opt);
857
+ } else {
858
+ ctrl.route = F.TRouting.lookupwebsocket(ctrl, 0);
859
+ if (ctrl.route)
860
+ execute(ctrl);
861
+ else
862
+ ctrl.close(4004);
863
+ }
864
+ }
865
+
866
+ function middleware(ctrl) {
867
+ var run = function(index) {
868
+ let key = ctrl.route.middleware[index];
869
+ if (key) {
870
+ let fn = F.routes.middleware[key];
871
+ if (fn)
872
+ fn(ctrl, () => run(index + 1));
873
+ else
874
+ prepare(ctrl);
875
+ } else
876
+ prepare(ctrl);
877
+ };
878
+ run(0);
879
+ }
880
+
881
+ function prepare(ctrl) {
882
+
883
+ ctrl.ondata2 = () => ctrl.ondata();
884
+
885
+ var compress = (F.config.$wscompress && ctrl.headers['sec-websocket-extensions'] || '').indexOf('permessage-deflate') !== -1;
886
+ var header = ctrl.route.protocols && ctrl.route.protocols.length ? (compress ? SOCKET_RESPONSE_PROTOCOL_COMPRESS : SOCKET_RESPONSE_PROTOCOL).format(ctrl.sign(ctrl), ctrl.route.protocols.join(', ')) : (compress ? SOCKET_RESPONSE_COMPRESS : SOCKET_RESPONSE).format(ctrl.sign(ctrl));
887
+
888
+ ctrl.socket.write(Buffer.from(header, 'binary'));
889
+ ctrl.ready = true;
890
+
891
+ if (compress) {
892
+ ctrl.inflatepending = [];
893
+ ctrl.inflatelock = false;
894
+ ctrl.inflate = F.Zlib.createInflateRaw(SOCKET_COMPRESS_OPTIONS);
895
+ ctrl.inflate.$controller = ctrl;
896
+ ctrl.inflate.on('error', function() {
897
+ if (!ctrl.$uerror) {
898
+ ctrl.$uerror = true;
899
+ ctrl.close(1003, 'Invalid data');
900
+ }
901
+ });
902
+
903
+ ctrl.inflate.on('data', inflate);
904
+ ctrl.deflatepending = [];
905
+ ctrl.deflatelock = false;
906
+ ctrl.deflate = F.Zlib.createDeflateRaw(SOCKET_COMPRESS_OPTIONS);
907
+ ctrl.deflate.$controller = ctrl;
908
+ ctrl.deflate.on('error', function() {
909
+ if (!ctrl.$uerror) {
910
+ ctrl.$uerror = true;
911
+ ctrl.close(1003, 'Invalid data');
912
+ }
913
+ });
914
+ ctrl.deflate.on('data', deflate);
915
+ }
916
+
917
+ if (WSCLIENTSID++ > 999999999)
918
+ WSCLIENTSID = 1;
919
+
920
+ ctrl.ID = F.TUtils.random_text(3) + WSCLIENTSID;
921
+ ctrl.id = ctrl.ID;
922
+
923
+ if (F.connections[ctrl.url]) {
924
+ ctrl.upgrade(F.connections[ctrl.url]);
925
+ return;
926
+ }
927
+
928
+ var websocket = new WebSocket(ctrl.url, ctrl.route, ctrl.params);
929
+ F.connections[ctrl.url] = websocket;
930
+
931
+ websocket.encodedecode = F.config.$wsencodedecode === true;
932
+
933
+ if (!ctrl.route.connections)
934
+ ctrl.route.connections = [];
935
+
936
+ ctrl.route.connections.push(websocket);
937
+ ctrl.route.action.call(websocket, websocket);
938
+
939
+ setImmediate(upgradecontinue, ctrl, websocket);
940
+ }
941
+
942
+ function upgradecontinue(ctrl, websocket) {
943
+ ctrl.upgrade(websocket);
944
+ }
945
+
946
+ function inflate(data) {
947
+ var ctrl = this.$controller;
948
+ if (ctrl && ctrl.inflatechunks) {
949
+ ctrl.inflatechunks.push(data);
950
+ ctrl.inflatechunkslength += data.length;
951
+ }
952
+ }
953
+
954
+ function deflate(data) {
955
+ var ctrl = this.$controller;
956
+ if (ctrl && ctrl.deflatechunks) {
957
+ ctrl.deflatechunks.push(data);
958
+ ctrl.deflatechunkslength += data.length;
959
+ }
960
+ }
961
+
962
+ function execute(ctrl) {
963
+
964
+ for (let param of ctrl.route.params) {
965
+ let value = ctrl.split[param.index];
966
+ ctrl.params[param.name] = value;
967
+ }
968
+
969
+ if (F.def.onLocalize)
970
+ ctrl.language = F.def.onLocalize(ctrl);
971
+
972
+ if (ctrl.route.flags.binary)
973
+ ctrl.datatype = 'binary';
974
+ else if (ctrl.route.flags.text)
975
+ ctrl.datatype = 'text';
976
+
977
+ if (ctrl.route.middleware.length)
978
+ middleware(ctrl);
979
+ else
980
+ prepare(ctrl);
981
+ }
982
+
983
+ // MIT
984
+ // Written by Jozef Gula <gula.jozef@gmail.com>
985
+ function getWebSocketFrame(code, message, type, compress, mask) {
986
+
987
+ if (mask)
988
+ mask = ((Math.random() * 214748364) >> 0) + 1;
989
+
990
+ var messageBuffer = getWebSocketFrameMessageBytes(code, message);
991
+ var lengthBuffer = getWebSocketFrameLengthBytes(messageBuffer.length);
992
+ var lengthMask = mask ? 4 : 0;
993
+ var frameBuffer = Buffer.alloc(1 + lengthBuffer.length + messageBuffer.length + lengthMask);
994
+
995
+ frameBuffer[0] = 0x80 | type;
996
+
997
+ if (compress)
998
+ frameBuffer[0] |= 0x40;
999
+
1000
+ lengthBuffer.copy(frameBuffer, 1, 0, lengthBuffer.length);
1001
+
1002
+ if (mask) {
1003
+ var offset = lengthBuffer.length + 1;
1004
+ frameBuffer[1] |= 0x80;
1005
+ frameBuffer.writeInt32BE(mask, offset);
1006
+ for (var i = 0; i < messageBuffer.length; i++)
1007
+ messageBuffer[i] = messageBuffer[i] ^ frameBuffer[offset + (i % 4)];
1008
+ }
1009
+
1010
+ messageBuffer.copy(frameBuffer, lengthBuffer.length + 1 + lengthMask, 0, messageBuffer.length);
1011
+ return frameBuffer;
1012
+ }
1013
+
1014
+ // MIT
1015
+ // Written by Jozef Gula <gula.jozef@gmail.com>
1016
+ function getWebSocketFrameMessageBytes(code, message) {
1017
+
1018
+ var index = code ? 2 : 0;
1019
+ var binary = message instanceof Int8Array || message instanceof Buffer;
1020
+ var length = message.length;
1021
+
1022
+ var messageBuffer = Buffer.alloc(length + index);
1023
+
1024
+ for (var i = 0; i < length; i++)
1025
+ messageBuffer[i + index] = binary ? message[i] : message.charCodeAt(i);
1026
+
1027
+ if (code) {
1028
+ messageBuffer[0] = code >> 8;
1029
+ messageBuffer[1] = code;
1030
+ }
1031
+
1032
+ return messageBuffer;
1033
+ }
1034
+
1035
+ // MIT
1036
+ // Written by Jozef Gula <gula.jozef@gmail.com>
1037
+ function getWebSocketFrameLengthBytes(length) {
1038
+ var lengthBuffer = null;
1039
+
1040
+ if (length <= 125) {
1041
+ lengthBuffer = Buffer.alloc(1);
1042
+ lengthBuffer[0] = length;
1043
+ return lengthBuffer;
1044
+ }
1045
+
1046
+ if (length <= 65535) {
1047
+ lengthBuffer = Buffer.alloc(3);
1048
+ lengthBuffer[0] = 126;
1049
+ lengthBuffer[1] = (length >> 8) & 255;
1050
+ lengthBuffer[2] = (length) & 255;
1051
+ return lengthBuffer;
1052
+ }
1053
+
1054
+ lengthBuffer = Buffer.alloc(9);
1055
+
1056
+ lengthBuffer[0] = 127;
1057
+ lengthBuffer[1] = 0x00;
1058
+ lengthBuffer[2] = 0x00;
1059
+ lengthBuffer[3] = 0x00;
1060
+ lengthBuffer[4] = 0x00;
1061
+ lengthBuffer[5] = (length >> 24) & 255;
1062
+ lengthBuffer[6] = (length >> 16) & 255;
1063
+ lengthBuffer[7] = (length >> 8) & 255;
1064
+ lengthBuffer[8] = (length) & 255;
1065
+
1066
+ return lengthBuffer;
1067
+ }
1068
+
1069
+ // MIT
1070
+ // Written by Jozef Gula
1071
+ // Optimized by Peter Sirka
1072
+ function getMessageLength(data, isLE) {
1073
+
1074
+ var length = data[1] & 0x7f;
1075
+
1076
+ if (length === 126) {
1077
+ if (data.length < 4)
1078
+ return -1;
1079
+ CACHE_GML1[0] = data[3];
1080
+ CACHE_GML1[1] = data[2];
1081
+ return converBytesToInt64(CACHE_GML1, 0, isLE);
1082
+ }
1083
+
1084
+ if (length === 127) {
1085
+ if (data.Length < 10)
1086
+ return -1;
1087
+ CACHE_GML2[0] = data[9];
1088
+ CACHE_GML2[1] = data[8];
1089
+ CACHE_GML2[2] = data[7];
1090
+ CACHE_GML2[3] = data[6];
1091
+ CACHE_GML2[4] = data[5];
1092
+ CACHE_GML2[5] = data[4];
1093
+ CACHE_GML2[6] = data[3];
1094
+ CACHE_GML2[7] = data[2];
1095
+ return converBytesToInt64(CACHE_GML2, 0, isLE);
1096
+ }
1097
+
1098
+ return length;
1099
+ }
1100
+
1101
+ // MIT
1102
+ // Written by Jozef Gula
1103
+ function converBytesToInt64(data, startIndex, isLE) {
1104
+ return isLE ? (data[startIndex] | (data[startIndex + 1] << 0x08) | (data[startIndex + 2] << 0x10) | (data[startIndex + 3] << 0x18) | (data[startIndex + 4] << 0x20) | (data[startIndex + 5] << 0x28) | (data[startIndex + 6] << 0x30) | (data[startIndex + 7] << 0x38)) : ((data[startIndex + 7] << 0x20) | (data[startIndex + 6] << 0x28) | (data[startIndex + 5] << 0x30) | (data[startIndex + 4] << 0x38) | (data[startIndex + 3]) | (data[startIndex + 2] << 0x08) | (data[startIndex + 1] << 0x10) | (data[startIndex] << 0x18));
1105
+ }
1106
+
1107
+ exports.ping = function() {
1108
+ for (let key in F.connections) {
1109
+ let socket = F.connections[key];
1110
+ socket.check();
1111
+ socket.ping();
1112
+ }
1113
+ };
1114
+
1115
+ exports.listen = function(req, socket, head) {
1116
+
1117
+ if (!req.headers.upgrade || !F.routes.websockets.length || !REG_WEBSOCKET.test(req.headers.upgrade))
1118
+ return;
1119
+
1120
+ var ctrl = new Controller(req, socket, head);
1121
+
1122
+ if (F.paused.length) {
1123
+ ctrl.destroy();
1124
+ return;
1125
+ }
1126
+
1127
+ if (SOCKET_ALLOW_VERSION.indexOf(ctrl.headers['sec-websocket-version'] || '') === -1) {
1128
+ ctrl.destroy();
1129
+ return;
1130
+ }
1131
+
1132
+ if (F.config.$blacklist && F.config.$blacklist.indexOf(ctrl.ip) !== -1) {
1133
+ F.stats.request.blocked++;
1134
+ ctrl.destroy();
1135
+ return;
1136
+ }
1137
+
1138
+ if (F.routes.proxies.length && F.TRouting.lookupproxy(ctrl))
1139
+ return;
1140
+
1141
+ socket.setTimeout(0);
1142
+ socket.on('error', NOOP);
1143
+
1144
+ ctrl.route = F.TRouting.lookupwebsocket(ctrl, 0, true);
1145
+
1146
+ if (!ctrl.route) {
1147
+ ctrl.destroy();
1148
+ return;
1149
+ }
1150
+
1151
+ if (ctrl.route.flags.csrf && !F.def.onCSRFcheck(ctrl)) {
1152
+ ctrl.destroy();
1153
+ return;
1154
+ }
1155
+
1156
+ F.$events.websocket && F.emit('websocket', ctrl);
1157
+ F.stats.request.websocket++;
1158
+ authorize(ctrl);
1159
+ };
1160
+
1161
+ function WebSocketClient() {
1162
+
1163
+ var t = this;
1164
+ t.iswsclient = true;
1165
+ t.current = {};
1166
+ t.pending = [];
1167
+ t.reconnect = 0;
1168
+ t.closed = true;
1169
+
1170
+ // type: json, text, binary
1171
+ t.headers = {};
1172
+ t.options = { type: 'json', size: 0, masking: false, compress: true, reconnect: 3000, encodedecode: false, encryptdecrypt: false, rejectunauthorized: false }; // key: Buffer, cert: Buffer, dhparam: Buffer
1173
+ t.cookies = {};
1174
+
1175
+ t.ondata2 = () => t.ondata();
1176
+ F.TUtils.EventEmitter2.extend(t);
1177
+ }
1178
+
1179
+ WebSocketClient.prototype.connect = function(url, protocol, origin) {
1180
+ setImmediate(this.connectforce, this, url, protocol, origin);
1181
+ };
1182
+
1183
+ WebSocketClient.prototype.connectforce = function(self, url, protocol, origin) {
1184
+
1185
+ var options = {};
1186
+
1187
+ self.url = url;
1188
+ self.origin = origin;
1189
+ self.protocol = protocol;
1190
+ self.secret = F.Crypto.randomBytes(16).toString('base64');
1191
+
1192
+ delete self.isclosed2;
1193
+ delete self.isclosed;
1194
+
1195
+ var secured = false;
1196
+
1197
+ if (typeof(url) === 'string') {
1198
+ url = F.Url.parse(url);
1199
+ options.host = url.hostname;
1200
+ options.path = url.path;
1201
+ options.query = url.query;
1202
+ secured = url.protocol === 'wss:';
1203
+ options.port = url.port || (secured ? 443 : 80);
1204
+ } else {
1205
+ options.socketPath = url.socket;
1206
+ options.path = url.path;
1207
+ // options.query = url.query;
1208
+ }
1209
+
1210
+ options.headers = {};
1211
+ options.headers['User-Agent'] = 'Total.js/v' + F.version_header;
1212
+ options.headers['Sec-WebSocket-Version'] = '13';
1213
+ options.headers['Sec-WebSocket-Key'] = self.secret;
1214
+ options.headers['Sec-Websocket-Extensions'] = (self.options.compress ? 'permessage-deflate, ' : '') + 'client_max_window_bits';
1215
+
1216
+ if (protocol)
1217
+ options.headers['Sec-WebSocket-Protocol'] = protocol;
1218
+
1219
+ if (origin)
1220
+ options.headers['Sec-WebSocket-Origin'] = origin;
1221
+
1222
+ options.headers.Connection = 'Upgrade';
1223
+ options.headers.Upgrade = 'websocket';
1224
+
1225
+ // options.agent = options.port === 443 ? KeepAliveHttps : KeepAlive;
1226
+ // options.agent = options.port === 443 ? new Https.Agent() : new Http.Agent();
1227
+ options.agent = false;
1228
+
1229
+ if (self.options.key)
1230
+ options.key = self.options.key;
1231
+
1232
+ if (self.options.cert)
1233
+ options.cert = self.options.cert;
1234
+
1235
+ if (self.options.dhparam)
1236
+ options.dhparam = self.options.dhparam;
1237
+
1238
+ if (self.options.rejectUnauthorized || self.options.rejectunauthorized)
1239
+ options.rejectUnauthorized = true;
1240
+
1241
+ for (let key in self.headers)
1242
+ options.headers[key] = self.headers[key];
1243
+
1244
+ var tmp = [];
1245
+ for (let key in self.cookies)
1246
+ tmp.push(key + '=' + self.cookies[key]);
1247
+
1248
+ options.headers.Cookie = tmp.join(', ');
1249
+
1250
+ F.stats.performance.online++;
1251
+ self.req = (secured ? F.Https : F.Http).get(options);
1252
+ self.req.$main = self;
1253
+
1254
+ self.req.on('error', function(e) {
1255
+ self.$events.error && self.emit('error', e);
1256
+ self.onclose();
1257
+ self.$api && wsclient_closecallbacks(self, e);
1258
+ self.options.reconnectserver && wsclient_reconnect_timer(self);
1259
+ });
1260
+
1261
+ self.req.on('response', function(res) {
1262
+
1263
+ self.$events.error && self.emit('error', new Error('Unexpected server response (' + res.statusCode + ')'));
1264
+
1265
+ if (self.options.reconnectserver) {
1266
+ self.onclose();
1267
+ wsclient_reconnect_timer(self);
1268
+ }
1269
+
1270
+ self.onclose();
1271
+
1272
+ });
1273
+
1274
+ self.req.on('upgrade', function(response, socket) {
1275
+
1276
+ self.socket = socket;
1277
+ self.socket.$controller = self;
1278
+
1279
+ var compress = self.options.compress && (response.headers['sec-websocket-extensions'] || '').indexOf('-deflate') !== -1;
1280
+ var digest = F.Crypto.createHash('sha1').update(self.secret + SOCKET_HASH, 'binary').digest('base64');
1281
+
1282
+ if (response.headers['sec-websocket-accept'] !== digest) {
1283
+ socket.destroy();
1284
+ self.closed = true;
1285
+ self.$events.error && self.emit('error', new Error('Invalid server key'), response);
1286
+ self.free();
1287
+ return;
1288
+ }
1289
+
1290
+ self.closed = false;
1291
+ self.socket.on('data', websocket_ondata);
1292
+ self.socket.on('error', websocket_onerror);
1293
+ self.socket.on('close', wsclient_close);
1294
+ self.socket.on('end', wsclient_close);
1295
+
1296
+ if (compress) {
1297
+ self.inflatepending = [];
1298
+ self.inflatelock = false;
1299
+ self.inflate = F.Zlib.createInflateRaw(SOCKET_COMPRESS_OPTIONS);
1300
+ self.inflate.$controller = self;
1301
+ self.inflate.on('error', F.error());
1302
+ self.inflate.on('data', inflate);
1303
+ self.deflatepending = [];
1304
+ self.deflatelock = false;
1305
+ self.deflate = F.Zlib.createDeflateRaw(SOCKET_COMPRESS_OPTIONS);
1306
+ self.deflate.$controller = self;
1307
+ self.deflate.on('error', F.error());
1308
+ self.deflate.on('data', deflate);
1309
+ }
1310
+
1311
+ self.$events.open && self.emit('open');
1312
+ });
1313
+ };
1314
+
1315
+ WebSocketClient.prototype.ping = function(timeout) {
1316
+ var self = this;
1317
+ if (!self.isclosed && !self.timeout) {
1318
+ self.timeout = setTimeout(wsclient_timeout, timeout || 3000);
1319
+ self.socket.write(getWebSocketFrame(0, 'PING', 0x09, false, self.options.masking));
1320
+ self.$ping = Date.now();
1321
+ }
1322
+ return self;
1323
+ };
1324
+
1325
+ WebSocketClient.prototype.ondata = function(data) {
1326
+
1327
+ var self = this;
1328
+
1329
+ if (self.isclosed)
1330
+ return;
1331
+
1332
+ if (data)
1333
+ F.stats.performance.download += data.length / 1024 / 1024;
1334
+
1335
+ var current = self.current;
1336
+ if (data) {
1337
+ if (current.buffer) {
1338
+ CONCAT[0] = current.buffer;
1339
+ CONCAT[1] = data;
1340
+ current.buffer = Buffer.concat(CONCAT);
1341
+ } else
1342
+ current.buffer = data;
1343
+ }
1344
+
1345
+ if (!self.parse())
1346
+ return;
1347
+
1348
+ if (!current.final && current.type !== 0x00)
1349
+ current.type2 = current.type;
1350
+
1351
+ var decompress = current.compressed && self.inflate;
1352
+
1353
+ switch (current.type === 0x00 ? current.type2 : current.type) {
1354
+ case 0x01:
1355
+ // text
1356
+ if (decompress) {
1357
+ current.final && self.parseinflate();
1358
+ } else {
1359
+ if (current.body) {
1360
+ CONCAT[0] = current.body;
1361
+ CONCAT[1] = current.data;
1362
+ current.body = Buffer.concat(CONCAT);
1363
+ } else
1364
+ current.body = current.data;
1365
+ current.final && self.decode();
1366
+ }
1367
+
1368
+ break;
1369
+
1370
+ case 0x02:
1371
+
1372
+ // binary
1373
+ if (decompress) {
1374
+ current.final && self.parseinflate();
1375
+ } else {
1376
+ if (current.body) {
1377
+ CONCAT[0] = current.body;
1378
+ CONCAT[1] = current.data;
1379
+ current.body = Buffer.concat(CONCAT);
1380
+ } else
1381
+ current.body = current.data;
1382
+ current.final && self.decode();
1383
+ }
1384
+
1385
+ break;
1386
+
1387
+ case 0x08:
1388
+ // close
1389
+ self.closemessage = current.data.slice(2).toString('utf8');
1390
+ self.closecode = current.data[0] << 8 | current.data[1];
1391
+ if (self.closemessage && self.options.encodedecode)
1392
+ self.closemessage = F.TUtils.decodeURIComponent(self.closemessage);
1393
+ self.$api && wsclient_closecallbacks(self, self.closecode);
1394
+ wsclient_closeforce(self);
1395
+ break;
1396
+
1397
+ case 0x09:
1398
+ // ping, response pong
1399
+ self.socket.write(getWebSocketFrame(0, 'PONG', 0x0A, false, self.options.masking));
1400
+ current.buffer = null;
1401
+ current.inflatedata = null;
1402
+ break;
1403
+
1404
+ case 0x0a:
1405
+ // pong
1406
+ self.timeout && clearTimeout(self.timeout);
1407
+ self.timeout = null;
1408
+ self.latency = Date.now() - self.$ping;
1409
+ current.buffer = null;
1410
+ current.inflatedata = null;
1411
+ break;
1412
+ }
1413
+
1414
+ if (current.buffer) {
1415
+ current.buffer = current.buffer.slice(current.length, current.buffer.length);
1416
+ current.buffer.length && setImmediate(self.ondata2);
1417
+ }
1418
+
1419
+ };
1420
+
1421
+ // MIT
1422
+ // Written by Jozef Gula
1423
+ // Optimized by Peter Sirka
1424
+ WebSocketClient.prototype.parse = function() {
1425
+
1426
+ var self = this;
1427
+ var current = self.current;
1428
+
1429
+ // Fixed a problem with parsing of long messages, the code bellow 0x80 still returns 0 when the message is longer
1430
+ // if (!current.buffer || current.buffer.length <= 2 || ((current.buffer[0] & 0x80) >> 7) !== 1)
1431
+ if (!current.buffer || current.buffer.length <= 2)
1432
+ return;
1433
+
1434
+ // WebSocket - Opcode
1435
+ current.type = current.buffer[0] & 0x0f;
1436
+ current.compressed = (current.buffer[0] & 0x40) === 0x40;
1437
+
1438
+ // is final message?
1439
+ current.final = ((current.buffer[0] & 0x80) >> 7) === 0x01;
1440
+
1441
+ // does frame contain mask?
1442
+ current.isMask = ((current.buffer[1] & 0xfe) >> 7) === 0x01;
1443
+
1444
+ // data length
1445
+ var length = getMessageLength(current.buffer, F.isLE);
1446
+ // index for data
1447
+
1448
+ // Solving a problem with The value "-1" is invalid for option "size"
1449
+ if (length <= 0)
1450
+ return current.final;
1451
+
1452
+ var index = current.buffer[1] & 0x7f;
1453
+ index = ((index === 126) ? 4 : (index === 127 ? 10 : 2)) + (current.isMask ? 4 : 0);
1454
+
1455
+ // total message length (data + header)
1456
+ var mlength = index + length;
1457
+
1458
+ if (self.options.length && mlength > self.options.length) {
1459
+ self.close('Frame is too large', 1009);
1460
+ return;
1461
+ }
1462
+
1463
+ // Check length of data
1464
+ if (current.buffer.length < mlength)
1465
+ return;
1466
+
1467
+ current.length = mlength;
1468
+
1469
+ // Not Ping & Pong
1470
+ if (current.type !== 0x09 && current.type !== 0x0A) {
1471
+
1472
+ // does frame contain mask?
1473
+ if (current.isMask) {
1474
+ current.mask = Buffer.alloc(4);
1475
+ current.buffer.copy(current.mask, 0, index - 4, index);
1476
+ }
1477
+
1478
+ if (current.compressed && self.inflate) {
1479
+
1480
+ var buf = Buffer.alloc(length);
1481
+ current.buffer.copy(buf, 0, index, mlength);
1482
+
1483
+ // does frame contain mask?
1484
+ if (current.isMask) {
1485
+ for (var i = 0; i < length; i++)
1486
+ buf[i] = buf[i] ^ current.mask[i % 4];
1487
+ }
1488
+
1489
+ // Does the buffer continue?
1490
+ buf.$continue = current.final === false;
1491
+ self.inflatepending.push(buf);
1492
+ } else {
1493
+ current.data = Buffer.alloc(length);
1494
+ current.buffer.copy(current.data, 0, index, mlength);
1495
+ }
1496
+ }
1497
+
1498
+ return true;
1499
+ };
1500
+
1501
+ WebSocketClient.prototype.readbody = function() {
1502
+ var self = this;
1503
+ var current = self.current;
1504
+ var length = current.data.length;
1505
+ var buf = Buffer.alloc(length);
1506
+ for (var i = 0; i < length; i++) {
1507
+ // does frame contain mask?
1508
+ if (current.isMask)
1509
+ buf[i] = current.data[i] ^ current.mask[i % 4];
1510
+ else
1511
+ buf[i] = current.data[i];
1512
+ }
1513
+ return buf;
1514
+ };
1515
+
1516
+ WebSocketClient.prototype.decode = function() {
1517
+
1518
+ var self = this;
1519
+ var data = self.current.body;
1520
+
1521
+ F.stats.performance.message++;
1522
+
1523
+ switch (self.options.type) {
1524
+
1525
+ case 'binary':
1526
+ self.emit('message', data);
1527
+ break;
1528
+
1529
+ case 'json':
1530
+
1531
+ if (data instanceof Buffer)
1532
+ data = data.toString('utf8');
1533
+
1534
+ if (self.options.encodedecode === true)
1535
+ data = F.TUtils.decodeURIComponent(data);
1536
+
1537
+ if (self.options.encrypt)
1538
+ data = F.TUtils.decrypt_data(data, self.options.encrypt);
1539
+
1540
+ if (data.isJSON()) {
1541
+ var tmp = data.parseJSON(true);
1542
+ if (tmp !== undefined)
1543
+ self.emit('message', tmp);
1544
+ }
1545
+ break;
1546
+
1547
+ default: // TEXT
1548
+
1549
+ if (data instanceof Buffer)
1550
+ data = data.toString('utf8');
1551
+
1552
+ if (self.options.encodedecode === true)
1553
+ data = F.TUtils.decodeURIComponent(data);
1554
+
1555
+ if (self.options.encrypt)
1556
+ data = F.TUtils.decrypt_data(data, self.options.encrypt);
1557
+
1558
+ self.emit('message', data);
1559
+ break;
1560
+ }
1561
+
1562
+ self.current.body = null;
1563
+ };
1564
+
1565
+ WebSocketClient.prototype.parseinflate = function() {
1566
+
1567
+ var self = this;
1568
+
1569
+ if (self.inflatelock)
1570
+ return;
1571
+
1572
+ var buf = self.inflatepending.shift();
1573
+ if (buf) {
1574
+ self.inflatechunks = [];
1575
+ self.inflatechunkslength = 0;
1576
+ self.inflatelock = true;
1577
+ self.inflate.write(buf);
1578
+ !buf.$continue && self.inflate.write(Buffer.from(SOCKET_COMPRESS));
1579
+ self.inflate.flush(function() {
1580
+
1581
+ if (!self.inflatechunks)
1582
+ return;
1583
+
1584
+ let data = concat(self.inflatechunks, self.inflatechunkslength);
1585
+
1586
+ self.inflatechunks = null;
1587
+ self.inflatelock = false;
1588
+
1589
+ if (self.options.size && data.length > self.options.size) {
1590
+ self.close(1009, 'Frame is too large');
1591
+ return;
1592
+ }
1593
+
1594
+ if (self.current.body) {
1595
+ CONCAT[0] = self.current.body;
1596
+ CONCAT[1] = data;
1597
+ self.current.body = Buffer.concat(CONCAT);
1598
+ } else
1599
+ self.current.body = data;
1600
+
1601
+ if (!buf.$continue)
1602
+ self.decode();
1603
+
1604
+ self.parseinflate();
1605
+ });
1606
+ }
1607
+ };
1608
+
1609
+ WebSocketClient.prototype.onerror = function(err) {
1610
+
1611
+ var self = this;
1612
+
1613
+ self.$events.error && self.emit('error', err);
1614
+
1615
+ if (!self.isclosed) {
1616
+ self.$api && wsclient_closecallbacks(self, err);
1617
+ self.isclosed = true;
1618
+ self.onclose();
1619
+ }
1620
+ };
1621
+
1622
+ WebSocketClient.prototype.onclose = function() {
1623
+
1624
+ var self = this;
1625
+
1626
+ if (self.isclosed2)
1627
+ return;
1628
+
1629
+ self.isclosed = true;
1630
+ self.isclosed2 = true;
1631
+
1632
+ F.stats.performance.online--;
1633
+
1634
+ if (self.inflate) {
1635
+ self.inflate.removeAllListeners();
1636
+ self.inflate = null;
1637
+ self.inflatechunks = null;
1638
+ }
1639
+
1640
+ if (self.deflate) {
1641
+ self.deflate.removeAllListeners();
1642
+ self.deflate = null;
1643
+ self.deflatechunks = null;
1644
+ }
1645
+
1646
+ self.free();
1647
+ };
1648
+
1649
+ WebSocketClient.prototype.destroy = function() {
1650
+ var self = this;
1651
+
1652
+ self.free();
1653
+ self.options.reconnect = 0;
1654
+
1655
+ if (self.reconnecting) {
1656
+ clearTimeout(self.reconnecting);
1657
+ self.reconnecting = null;
1658
+ }
1659
+
1660
+ self.$events.destroy && self.emit('destroy');
1661
+ };
1662
+
1663
+ WebSocketClient.prototype.free = function() {
1664
+
1665
+ var self = this;
1666
+
1667
+ if (self.req) {
1668
+ self.req.connection && self.req.connection.destroy();
1669
+ self.req.removeAllListeners();
1670
+ self.req.destroy();
1671
+ F.cleanup(self.req);
1672
+ }
1673
+
1674
+ if (self.socket) {
1675
+ self.socket.removeAllListeners();
1676
+ self.socket.destroy();
1677
+ F.cleanup(self.socket);
1678
+ }
1679
+
1680
+ self.socket = null;
1681
+ self.req = null;
1682
+ return self;
1683
+ };
1684
+
1685
+ WebSocketClient.prototype.send = function(message, raw, replacer) {
1686
+
1687
+ var self = this;
1688
+
1689
+ if (self.isclosed || self.closed)
1690
+ return false;
1691
+
1692
+ var type = self.options.type;
1693
+
1694
+ if (type != 'binary') {
1695
+
1696
+ var data = type === 'json' ? (raw ? message : JSON.stringify(message, replacer == true ? F.TUtils.json2replacer : replacer)) : typeof(message) === 'object' ? JSON.stringify(message, replacer == true ? F.TUtils.json2replacer : replacer) : (message + '');
1697
+ var buffer;
1698
+
1699
+ if (self.options.encrypt)
1700
+ data = F.TUtils.encrypt_data(data, self.options.encrypt);
1701
+
1702
+ if (self.options.encodedecode === true && data)
1703
+ data = encodeURIComponent(data);
1704
+
1705
+ if (self.deflate) {
1706
+ buffer = Buffer.from(data, 'utf8');
1707
+ self.deflatepending.push(buffer);
1708
+ self.senddeflate();
1709
+ } else {
1710
+ buffer = Buffer.from(data, 'utf8');
1711
+ self.socket.write(getWebSocketFrame(0, buffer, 0x01, false, self.options.masking));
1712
+ }
1713
+
1714
+ F.stats.performance.upload += buffer.length / 1024 / 1024;
1715
+
1716
+ } else if (message) {
1717
+
1718
+ if (!(message instanceof Buffer))
1719
+ message = Buffer.from(message, 'utf8');
1720
+
1721
+ if (self.deflate) {
1722
+ self.deflatepending.push(message);
1723
+ self.senddeflate();
1724
+ } else
1725
+ self.socket.write(getWebSocketFrame(0, message, 0x02, false, self.options.masking));
1726
+
1727
+ F.stats.performance.upload += message.length / 1024 / 1024;
1728
+ }
1729
+
1730
+ F.stats.response.websocket++;
1731
+ return true;
1732
+ };
1733
+
1734
+ WebSocketClient.prototype.sendcustom = function(type, message) {
1735
+
1736
+ var self = this;
1737
+
1738
+ if (self.isclosed || self.closed || !self.socket)
1739
+ return false;
1740
+
1741
+ if (type !== 'binary') {
1742
+ var data = (message == null ? '' : message) + '';
1743
+ if (self.options.encrypt)
1744
+ data = F.TUtils.encrypt_data(data, self.options.encrypt);
1745
+ if (self.options.encodedecode && data)
1746
+ data = encodeURIComponent(data);
1747
+ if (self.deflate) {
1748
+ self.deflatepending.push(Buffer.from(data));
1749
+ self.senddeflate();
1750
+ } else
1751
+ self.socket.write(getWebSocketFrame(0, data, 0x01, false, self.options.masking));
1752
+ } else {
1753
+
1754
+ if (!(message instanceof Buffer))
1755
+ message = Buffer.from(message);
1756
+
1757
+ if (self.deflate) {
1758
+ self.deflatepending.push(message);
1759
+ self.senddeflate();
1760
+ } else
1761
+ self.socket.write(getWebSocketFrame(0, message, 0x02, false, self.options.masking));
1762
+ }
1763
+
1764
+ F.stats.response.websocket++;
1765
+ return true;
1766
+ };
1767
+
1768
+ WebSocketClient.prototype.senddeflate = function() {
1769
+ var self = this;
1770
+
1771
+ if (self.deflatelock)
1772
+ return;
1773
+
1774
+ var buf = self.deflatepending.shift();
1775
+ if (buf) {
1776
+ self.deflatechunks = [];
1777
+ self.deflatechunkslength = 0;
1778
+ self.deflatelock = true;
1779
+ self.deflate.write(buf);
1780
+ self.deflate.flush(function() {
1781
+ if (self.deflatechunks) {
1782
+ var data = concat(self.deflatechunks, self.deflatechunkslength);
1783
+ data = data.slice(0, data.length - 4);
1784
+ self.deflatelock = false;
1785
+ self.deflatechunks = null;
1786
+ if (self.socket) {
1787
+ self.socket.write(getWebSocketFrame(0, data, self.options.type === 'binary' ? 0x02 : 0x01, true, self.options.masking));
1788
+ self.senddeflate();
1789
+ }
1790
+ }
1791
+ });
1792
+ }
1793
+ };
1794
+
1795
+ WebSocketClient.prototype.close = function(code, message) {
1796
+
1797
+ var self = this;
1798
+
1799
+ if (message !== true) {
1800
+ self.options.reconnect = 0;
1801
+ self.reconnecting && clearTimeout(self.reconnecting);
1802
+ self.reconnecting = null;
1803
+ } else
1804
+ message = undefined;
1805
+
1806
+ if (!self.isclosed && self.socket) {
1807
+ self.isclosed = true;
1808
+ if (message && self.options.encodedecode)
1809
+ message = encodeURIComponent(message);
1810
+ self.socket.end(getWebSocketFrame(code || 1000, message || '', 0x08, false, self.options.masking));
1811
+ }
1812
+
1813
+ return self;
1814
+ };
1815
+
1816
+ function registerapi(client) {
1817
+
1818
+ if (client.$api)
1819
+ return;
1820
+
1821
+ client.$api = true;
1822
+ client.on('message', function(msg) {
1823
+ if (msg.TYPE === 'api') {
1824
+ var obj = CALLBACKS[msg.callbackid];
1825
+ if (obj) {
1826
+ delete CALLBACKS[msg.callbackid];
1827
+ clearTimeout(obj.timeout);
1828
+ if (msg.error)
1829
+ obj.callback(msg.data instanceof Array ? ErrorBuilder.assign(msg.data) : msg.data);
1830
+ else
1831
+ obj.callback(null, msg.data);
1832
+ }
1833
+ }
1834
+ });
1835
+ }
1836
+
1837
+ function timeoutapi(id) {
1838
+ var obj = CALLBACKS[id];
1839
+ if (obj) {
1840
+ obj.callback(408);
1841
+ delete CALLBACKS[id];
1842
+ }
1843
+ }
1844
+
1845
+ WebSocketClient.prototype.api = function(schema, data, callback, timeout) {
1846
+
1847
+ var self = this;
1848
+
1849
+ if (!schema) {
1850
+ registerapi(self);
1851
+ return self;
1852
+ }
1853
+
1854
+ if (!self.$api)
1855
+ self.api();
1856
+
1857
+ if (typeof(data) === 'function') {
1858
+ timeout = callback;
1859
+ callback = data;
1860
+ data = null;
1861
+ }
1862
+
1863
+ if (callback == null)
1864
+ return new Promise((resolve, reject) => self.api(schema, data, (err, response) => err ? reject(err) : resolve(response), timeout));
1865
+
1866
+ var msg = { TYPE: 'api', data: { schema: schema, data: data }};
1867
+ msg.callbackid = (CALLBACKSCOUNTER++) + '';
1868
+
1869
+ if (CALLBACKSCOUNTER > 9999999999)
1870
+ CALLBACKSCOUNTER = 1;
1871
+
1872
+ var obj = {};
1873
+ obj.client = self;
1874
+ obj.callback = callback;
1875
+ obj.timeout = setTimeout(timeoutapi, timeout || 5000, msg.callbackid);
1876
+ CALLBACKS[msg.callbackid] = obj;
1877
+ self.send(msg);
1878
+ };
1879
+
1880
+ function wsclient_timeout(self) {
1881
+ self.timeout = null;
1882
+ self.onerror('Timeout');
1883
+ }
1884
+
1885
+ function wsclient_close() {
1886
+ wsclient_closeforce(this.$controller);
1887
+ }
1888
+
1889
+ function wsclient_closeforce(client) {
1890
+ if (!client.closed) {
1891
+ client.$events.close && client.emit('close', client.closecode, client.closemessage);
1892
+ client.closed = true;
1893
+ client.onclose();
1894
+ wsclient_reconnect_timer(client);
1895
+ }
1896
+ }
1897
+
1898
+ function inflate(data) {
1899
+ var ctrl = this.$controller;
1900
+ if (ctrl && ctrl.inflatechunks) {
1901
+ ctrl.inflatechunks.push(data);
1902
+ ctrl.inflatechunkslength += data.length;
1903
+ }
1904
+ }
1905
+
1906
+ function deflate(data) {
1907
+ var ctrl = this.$controller;
1908
+ if (ctrl && ctrl.deflatechunks) {
1909
+ ctrl.deflatechunks.push(data);
1910
+ ctrl.deflatechunkslength += data.length;
1911
+ }
1912
+ }
1913
+
1914
+ function wsclient_reconnect_client(client) {
1915
+ client.isclosed = false;
1916
+ client.isclosed2 = false;
1917
+ client.reconnecting = null;
1918
+ client.reconnect++;
1919
+ client.connect(client.url, client.protocol, client.origin);
1920
+ }
1921
+
1922
+ function wsclient_reconnect_timer(client) {
1923
+ if (client.options.reconnect) {
1924
+ client.reconnecting && clearTimeout(client.reconnecting);
1925
+ client.reconnecting = setTimeout(wsclient_reconnect_client, client.options.reconnect, client);
1926
+ }
1927
+ }
1928
+
1929
+ function wsclient_closecallbacks(client, e) {
1930
+ for (var key in CALLBACKS) {
1931
+ var obj = CALLBACKS[key];
1932
+ if (obj.client === client) {
1933
+ clearTimeout(obj.timeout);
1934
+ obj.callback(e);
1935
+ delete CALLBACKS[key];
1936
+ }
1937
+ }
1938
+ }
1939
+
1940
+ exports.createclient = function(callback) {
1941
+ var client = new WebSocketClient();
1942
+ callback && callback(client);
1943
+ return client;
1944
+ };