@ceeblue/web-utils 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1482 @@
1
+ /**
2
+ * Copyright 2023 Ceeblue B.V.
3
+ * This file is part of https://github.com/CeeblueTV/web-utils which is released under GNU Affero General Public License.
4
+ * See file LICENSE or go to https://spdx.org/licenses/AGPL-3.0-or-later.html for full license details.
5
+ */
6
+ /**
7
+ * BinaryReader allows to read binary data
8
+ */
9
+ class BinaryReader {
10
+ constructor(data) {
11
+ this._data =
12
+ 'buffer' in data ? new Uint8Array(data.buffer, data.byteOffset, data.byteLength) : new Uint8Array(data);
13
+ this._size = this._data.byteLength;
14
+ this._position = 0;
15
+ this._view = new DataView(this._data.buffer, this._data.byteOffset, this._size);
16
+ }
17
+ data() {
18
+ return this._data;
19
+ }
20
+ size() {
21
+ return this._size;
22
+ }
23
+ available() {
24
+ return this._size - this._position;
25
+ }
26
+ value(position = this._position) {
27
+ return this._data[position];
28
+ }
29
+ position() {
30
+ return this._position;
31
+ }
32
+ reset(position = 0) {
33
+ this._position = position > this._size ? this._size : position;
34
+ }
35
+ shrink(available) {
36
+ const rest = this._size - this._position;
37
+ if (available > rest) {
38
+ return rest;
39
+ }
40
+ this._size = this._position + available;
41
+ return available;
42
+ }
43
+ next(count = 1) {
44
+ const rest = this._size - this._position;
45
+ if (count > rest) {
46
+ count = rest;
47
+ }
48
+ this._position += count;
49
+ return count;
50
+ }
51
+ read8() {
52
+ return this.next(1) === 1 ? this._view.getUint8(this._position - 1) : 0;
53
+ }
54
+ read16() {
55
+ return this.next(2) === 2 ? this._view.getUint16(this._position - 2) : 0;
56
+ }
57
+ read24() {
58
+ return this.next(3) === 3
59
+ ? (this._view.getUint16(this._position - 3) << 8) | (this._view.getUint8(this._position - 1) & 0xff)
60
+ : 0;
61
+ }
62
+ read32() {
63
+ return this.next(4) === 4 ? this._view.getUint32(this._position - 4) : 0;
64
+ }
65
+ readFloat() {
66
+ return this.next(4) === 4 ? this._view.getFloat32(this._position - 4) : 0;
67
+ }
68
+ readDouble() {
69
+ return this.next(8) === 8 ? this._view.getFloat64(this._position - 8) : 0;
70
+ }
71
+ read7Bit(bytes = 5) {
72
+ if (bytes > 5) {
73
+ throw Error("BinaryReader in JS can't decode more than 32 usefull bits");
74
+ }
75
+ if (!(bytes > 0)) {
76
+ // negation to catch NaN value
77
+ throw Error('Have to indicate a positive number of bytes to decode');
78
+ }
79
+ let result = 0;
80
+ let byte;
81
+ do {
82
+ byte = this.read8();
83
+ if (!--bytes) {
84
+ return ((result << 8) | byte) >>> 0; // Use all 8 bits from the 5th byte
85
+ }
86
+ result = (result << 7) | (byte & 0x7f);
87
+ } while (byte & 0x80);
88
+ return result;
89
+ }
90
+ readString() {
91
+ return String.fromCharCode(...this.read(this.read7Bit()));
92
+ }
93
+ readHex(size) {
94
+ let hex = '';
95
+ while (size--) {
96
+ hex += ('0' + this.read8().toString(16)).slice(-2);
97
+ }
98
+ return hex;
99
+ }
100
+ /**
101
+ * Read bytes, to convert bytes to string use String.fromCharCode(...reader.read(size))
102
+ * @param {UInt32} size
103
+ */
104
+ read(size = this.available()) {
105
+ if (this.available() < size) {
106
+ return new Uint8Array(size); // default value = empty bytearray!
107
+ }
108
+ const value = this._data.subarray(this._position, this._position + size);
109
+ this._position += size;
110
+ return value;
111
+ }
112
+ }/**
113
+ * Copyright 2023 Ceeblue B.V.
114
+ * This file is part of https://github.com/CeeblueTV/web-utils which is released under GNU Affero General Public License.
115
+ * See file LICENSE or go to https://spdx.org/licenses/AGPL-3.0-or-later.html for full license details.
116
+ */
117
+ /**
118
+ * BinaryWriter allows to write data in its binary form
119
+ */
120
+ class BinaryWriter {
121
+ get view() {
122
+ if (!this._view) {
123
+ this._view = new DataView(this._data.buffer, this._data.byteOffset, this._data.byteLength);
124
+ }
125
+ return this._view;
126
+ }
127
+ get capacity() {
128
+ return this._data.byteLength;
129
+ }
130
+ constructor(dataOrSize = 64, offset = 0, length) {
131
+ if (typeof dataOrSize == 'number') {
132
+ // allocate new buffer
133
+ this._data = new Uint8Array(dataOrSize);
134
+ this._size = 0;
135
+ }
136
+ else if ('buffer' in dataOrSize) {
137
+ // append to existing data!
138
+ this._data = new Uint8Array(dataOrSize.buffer, dataOrSize.byteOffset, dataOrSize.byteLength);
139
+ this._size = dataOrSize.byteLength;
140
+ }
141
+ else {
142
+ // overrides data
143
+ this._isConst = true; // better than boolean for memory usage
144
+ if (length == null) {
145
+ // /!\ Safari does not support undefined length, so we need to use byteLength instead
146
+ length = dataOrSize.byteLength;
147
+ }
148
+ this._data = new Uint8Array(dataOrSize, offset, length);
149
+ this._size = 0;
150
+ }
151
+ }
152
+ data() {
153
+ return new Uint8Array(this._data.buffer, this._data.byteOffset, this._size);
154
+ }
155
+ size() {
156
+ return this._size || 0;
157
+ }
158
+ next(count = 1) {
159
+ return this.reserve((this._size += count));
160
+ }
161
+ clear(size = 0) {
162
+ return this.reserve((this._size = size));
163
+ }
164
+ write(data) {
165
+ this.reserve(this._size + data.length);
166
+ if (typeof data === 'string') {
167
+ // beware here support just the 255 first bytes (compatible Latin-1)
168
+ for (let i = 0; i < data.length; ++i) {
169
+ const value = data.charCodeAt(i);
170
+ this._data[this._size++] = value > 255 ? 32 : value;
171
+ }
172
+ return this;
173
+ }
174
+ this._data.set(data, this._size);
175
+ this._size += data.length;
176
+ return this;
177
+ }
178
+ write8(value) {
179
+ if (value > 0xff) {
180
+ // cast to 8bits range
181
+ value = 0xff;
182
+ }
183
+ this.reserve(this._size + 1);
184
+ this._data[this._size++] = value;
185
+ return this;
186
+ }
187
+ write16(value) {
188
+ if (value > 0xffff) {
189
+ // cast to 16bits range
190
+ value = 0xffff;
191
+ }
192
+ this.reserve(this._size + 2);
193
+ this.view.setUint16(this._size, value);
194
+ this._size += 2;
195
+ return this;
196
+ }
197
+ write24(value) {
198
+ if (value > 0xffffff) {
199
+ // cast to 24bits range
200
+ value = 0xffffff;
201
+ }
202
+ this.reserve(this._size + 3);
203
+ this.view.setUint16(this._size, value >> 8);
204
+ this.view.setUint8((this._size += 2), value & 0xff);
205
+ ++this._size;
206
+ return this;
207
+ }
208
+ write32(value) {
209
+ if (value > 0xffffffff) {
210
+ // cast to 32bits range
211
+ value = 0xffffffff;
212
+ }
213
+ this.reserve(this._size + 4);
214
+ this.view.setUint32(this._size, value);
215
+ this._size += 4;
216
+ return this;
217
+ }
218
+ writeFloat(value) {
219
+ this.reserve(this._size + 4);
220
+ this.view.setFloat32(this._size, value);
221
+ this._size += 4;
222
+ return this;
223
+ }
224
+ writeDouble(value) {
225
+ this.reserve(this._size + 8);
226
+ this.view.setFloat64(this._size, value);
227
+ this._size += 8;
228
+ return this;
229
+ }
230
+ write7Bit(value, bytes = 5) {
231
+ if (bytes > 5) {
232
+ throw Error("BinaryWriter in JS can't encode more than 32 usefull bits");
233
+ }
234
+ if (!(bytes > 0)) {
235
+ // negation to catch NaN value
236
+ throw Error('Have to indicate a positive number of bytes to encode');
237
+ }
238
+ let bits = --bytes * 7;
239
+ const front = value > 0xffffffff ? 0x100 : value >>> bits;
240
+ if (front) {
241
+ ++bits;
242
+ if (front > 0xff) {
243
+ value = 0xffffffff;
244
+ }
245
+ }
246
+ else {
247
+ while ((bits -= 7) && !(value >>> bits)) {
248
+ continue;
249
+ }
250
+ }
251
+ while (bits > 1) {
252
+ this.write8(0x80 | ((value >>> bits) & 0xff));
253
+ bits -= 7;
254
+ }
255
+ return this.write8(value & (bits ? 0xff : 0x7f));
256
+ }
257
+ writeString(value) {
258
+ return this.write7Bit(value.length).write(value);
259
+ }
260
+ writeHex(value) {
261
+ for (let i = 0; i < value.length; i += 2) {
262
+ this.write8(parseInt(value.substring(i, i + 2), 16));
263
+ }
264
+ return this;
265
+ }
266
+ reserve(size) {
267
+ if (!this._data) {
268
+ throw new Error('buffer not writable');
269
+ }
270
+ if (size <= this._data.byteLength) {
271
+ return this;
272
+ }
273
+ if (this._isConst) {
274
+ throw new Error('writing exceeds maximum ' + this._data.byteLength + ' bytes limit');
275
+ }
276
+ --size;
277
+ size |= size >> 1;
278
+ size |= size >> 2;
279
+ size |= size >> 4;
280
+ size |= size >> 8;
281
+ size |= size >> 16;
282
+ ++size;
283
+ const data = new Uint8Array(size);
284
+ data.set(this._data); // copy old buffer!
285
+ this._data = data;
286
+ this._view = undefined; // release view
287
+ return this;
288
+ }
289
+ }/**
290
+ * Copyright 2023 Ceeblue B.V.
291
+ * This file is part of https://github.com/CeeblueTV/web-utils which is released under GNU Affero General Public License.
292
+ * See file LICENSE or go to https://spdx.org/licenses/AGPL-3.0-or-later.html for full license details.
293
+ */
294
+ class BitReader {
295
+ constructor(data) {
296
+ if ('buffer' in data) {
297
+ this._data = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
298
+ }
299
+ else {
300
+ this._data = new Uint8Array(data);
301
+ }
302
+ this._size = this._data.byteLength;
303
+ this._position = 0;
304
+ this._bit = 0;
305
+ }
306
+ data() {
307
+ return this._data;
308
+ }
309
+ size() {
310
+ return this._size;
311
+ }
312
+ available() {
313
+ return (this._size - this._position) * 8 - this._bit;
314
+ }
315
+ next(count = 1) {
316
+ let gotten = 0;
317
+ while (this._position !== this._size && count--) {
318
+ ++gotten;
319
+ if (++this._bit === 8) {
320
+ this._bit = 0;
321
+ ++this._position;
322
+ }
323
+ }
324
+ return gotten;
325
+ }
326
+ read(count = 1) {
327
+ let result = 0;
328
+ while (this._position !== this._size && count--) {
329
+ result <<= 1;
330
+ if (this._data[this._position] & (0x80 >> this._bit++)) {
331
+ result |= 1;
332
+ }
333
+ if (this._bit === 8) {
334
+ this._bit = 0;
335
+ ++this._position;
336
+ }
337
+ }
338
+ return result;
339
+ }
340
+ read8() {
341
+ return this.read(8);
342
+ }
343
+ read16() {
344
+ return this.read(16);
345
+ }
346
+ read24() {
347
+ return this.read(24);
348
+ }
349
+ read32() {
350
+ return this.read(32);
351
+ }
352
+ }/**
353
+ * Copyright 2023 Ceeblue B.V.
354
+ * This file is part of https://github.com/CeeblueTV/web-utils which is released under GNU Affero General Public License.
355
+ * See file LICENSE or go to https://spdx.org/licenses/AGPL-3.0-or-later.html for full license details.
356
+ */
357
+ /**
358
+ * Help class to manipulate and parse a net address. The Address can be only the domain field,
359
+ * or a URL format with protocol and path part `(http://)domain(:port/path)`
360
+ * @example
361
+ * const address = new Address('nl-ams-42.live.ceeblue.tv:80');
362
+ * console.log(address.domain) // 'nl-ams-42.live.ceeblue.tv'
363
+ * console.log(address.port) // '80'
364
+ * console.log(address) // 'nl-ams-42.live.ceeblue.tv:80'
365
+ */
366
+ class NetAddress {
367
+ /**
368
+ * Static help function to build an end point from an address `(proto://)domain(:port/path)`
369
+ *
370
+ * Mainly it fix the protocol, in addition if:
371
+ * - the address passed is securized (TLS) and protocol is not => it tries to fix protocol to get its securize version
372
+ * - the address passed is non securized and protocol is (TLS) => it tries to fix protocol to get its unsecurized version
373
+ * @param protocol protocol to set in the end point returned
374
+ * @param address string address to fix with protocol as indicated
375
+ * @returns the end point built
376
+ * @example
377
+ * console.log(NetAddress.fixProtocol('ws','http://domain/path')) // 'ws://domain/path'
378
+ * console.log(NetAddress.fixProtocol('ws','https://domain/path')) // 'wss://domain/path'
379
+ * console.log(NetAddress.fixProtocol('wss','http://domain/path')) // 'ws://domain/path'
380
+ */
381
+ static fixProtocol(protocol, address) {
382
+ const found = address.indexOf('://');
383
+ // isolate protocol is present in address
384
+ if (found >= 0) {
385
+ // In this case replace by protocol in keeping SSL like given in address
386
+ if (found > 2 && address.charAt(found - 1).toLowerCase() === 's') {
387
+ // SSL!
388
+ if (protocol.length <= 2 || !protocol.endsWith('s')) {
389
+ protocol += 's'; // Add SSL
390
+ }
391
+ }
392
+ else {
393
+ // Not SSL!
394
+ if (protocol.length > 2 && protocol.endsWith('s')) {
395
+ protocol = protocol.slice(0, -1); // Remove SSL
396
+ }
397
+ }
398
+ // Build host!
399
+ address = address.substring(found + 3);
400
+ }
401
+ return protocol + '://' + address;
402
+ }
403
+ /**
404
+ * The domain part from address `(http://)domain(:port/path)`
405
+ */
406
+ get domain() {
407
+ return this._domain;
408
+ }
409
+ /**
410
+ * The port part from address `(http://)domain(:port/path)`, or defaultPort if passed in NetAddress constructor
411
+ */
412
+ get port() {
413
+ return this._port;
414
+ }
415
+ /**
416
+ * @returns the string address as passed in the constructor
417
+ */
418
+ toString() {
419
+ return this._address;
420
+ }
421
+ /**
422
+ * @returns the string address as passed in the constructor
423
+ * @override
424
+ */
425
+ valueOf() {
426
+ return this._address;
427
+ }
428
+ /**
429
+ * Build a NetAddress object and parse address
430
+ * @param address string address to parse, accept an url format with protocol and path `(http://)domain(:port/path)`
431
+ * @param defaultPort set a default port to use if there is no port in the string address parsed
432
+ */
433
+ constructor(address, defaultPort) {
434
+ this._address = address;
435
+ // Remove Protocol
436
+ let pos = address.indexOf('/');
437
+ if (pos >= 0) {
438
+ // Remove ://
439
+ if (address.charCodeAt(pos + 1) === 47) {
440
+ // has //
441
+ if (pos > 0) {
442
+ if (address.charCodeAt(pos - 1) === 58) {
443
+ // has ://
444
+ address = address.substring(pos + 2);
445
+ } // something else #//
446
+ }
447
+ else {
448
+ // otherwise starts by //
449
+ address = address.substring(2);
450
+ }
451
+ }
452
+ else if (!pos) {
453
+ // starts by /, remove it
454
+ address = address.substring(1);
455
+ } // else something else #/
456
+ }
457
+ this._domain = address;
458
+ this._port = defaultPort;
459
+ // Parse Port
460
+ pos = address.lastIndexOf(':');
461
+ if (pos >= 0) {
462
+ const port = parseInt(address.substring(pos + 1));
463
+ if (port && port <= 0xffff) {
464
+ this._port = port;
465
+ this._domain = address.substring(0, pos);
466
+ }
467
+ }
468
+ else {
469
+ // Remove Path!
470
+ pos = address.indexOf('/');
471
+ if (pos >= 0) {
472
+ this._domain = address.substring(0, pos);
473
+ }
474
+ }
475
+ }
476
+ }/**
477
+ * Copyright 2023 Ceeblue B.V.
478
+ * This file is part of https://github.com/CeeblueTV/web-utils which is released under GNU Affero General Public License.
479
+ * See file LICENSE or go to https://spdx.org/licenses/AGPL-3.0-or-later.html for full license details.
480
+ */
481
+ /**
482
+ * Type of connection
483
+ */
484
+ var Type;
485
+ (function (Type) {
486
+ Type["HESP"] = "hesp";
487
+ Type["WEBRTS"] = "webrts";
488
+ Type["WEBRTC"] = "webrtc";
489
+ Type["META"] = "meta";
490
+ Type["DATA"] = "data";
491
+ })(Type || (Type = {}));
492
+ /**
493
+ * Some connection utility functions
494
+ */
495
+ /**
496
+ * Build an URL from {@link Type | type} and {@link Params | params}
497
+ * @param type Type of the connection wanted
498
+ * @param params Connection parameters
499
+ * @param protocol Optional parameter to choose the prefered protocol to connect
500
+ * @returns The URL of connection
501
+ */
502
+ function buildURL(type, params, protocol = 'wss') {
503
+ const url = new URL(NetAddress.fixProtocol(protocol, params.host));
504
+ if (url.pathname.length <= 1) {
505
+ // build ceeblue path!
506
+ switch (type) {
507
+ case Type.HESP:
508
+ url.pathname = '/hesp/' + params.streamName + '/index.json';
509
+ break;
510
+ case Type.WEBRTC:
511
+ url.pathname = '/webrtc/' + params.streamName;
512
+ break;
513
+ case Type.WEBRTS:
514
+ url.pathname = '/webrts/' + params.streamName;
515
+ break;
516
+ case Type.META:
517
+ url.pathname = '/json_' + params.streamName + '.js';
518
+ break;
519
+ case Type.DATA:
520
+ url.pathname = '/' + params.streamName + '.json';
521
+ break;
522
+ default:
523
+ console.warn('Unknown url type ' + type);
524
+ break;
525
+ }
526
+ } // Host has already a path! keep it unchanged, it's user intentionnal (used with some other WHIP/WHEP server?)
527
+ if (params.accessToken) {
528
+ url.searchParams.set('id', params.accessToken);
529
+ }
530
+ for (const key in params.query) {
531
+ url.searchParams.set(key, params.query[key]);
532
+ }
533
+ return url;
534
+ }var Connect=/*#__PURE__*/Object.freeze({__proto__:null,get Type(){return Type},buildURL:buildURL});/**
535
+ * Copyright 2023 Ceeblue B.V.
536
+ * This file is part of https://github.com/CeeblueTV/web-utils which is released under GNU Affero General Public License.
537
+ * See file LICENSE or go to https://spdx.org/licenses/AGPL-3.0-or-later.html for full license details.
538
+ */
539
+ /**
540
+ * A advanced EventEmitter which allows to declare event as natural function in the inheriting children class,
541
+ * function must start by `on` prefix to be recognized as an event.
542
+ * The function can define a behavior by default, and user can choose to redefine this behavior,
543
+ * or add an additionnal subscription for this event.
544
+ * In addition you can unsubscribe to multiple events with an `AbortController`
545
+ * @example
546
+ * class Logger extends EventEmitter {
547
+ * onLog(log:string) { console.log(log); } // behavior by default
548
+ *
549
+ * test() {
550
+ * // raise event onLog
551
+ * this.onLog('test');
552
+ * }
553
+ * }
554
+ *
555
+ * const logger = new Logger();
556
+ * logger.test(); // displays a log 'test'
557
+ *
558
+ * // redefine default behavior to display nothing
559
+ * logger.onLog = () => {}
560
+ * logger.test(); // displays nothing
561
+ *
562
+ * // add an additionnal subscription
563
+ * logger.on('log', console.log);
564
+ * logger.test(); // displays a log 'test'
565
+ *
566
+ * // remove the additionnal subscription
567
+ * logger.off('log', console.log);
568
+ * logger.test(); // displays nothing
569
+ *
570
+ * // add two additionnal subscriptions with a AbortController
571
+ * const controller = new AbortController();
572
+ * logger.on('log', log => console.log(log), controller);
573
+ * logger.on('log', log => console.error(log), controller);
574
+ * logger.test(); // displays a log 'test' + an error 'test'
575
+ *
576
+ * // Unsubscribe all the subscription with the AbortController
577
+ * controller.abort();
578
+ * logger.test(); // displays nothing
579
+ */
580
+ class EventEmitter {
581
+ /**
582
+ * Build our EventEmitter, usually call from children class
583
+ */
584
+ constructor() {
585
+ this._events = new Map();
586
+ // Fill events with events as defined!
587
+ let proto = Object.getPrototypeOf(this);
588
+ while (proto && proto !== Object.prototype) {
589
+ for (const name of Object.getOwnPropertyNames(proto)) {
590
+ if (name.length < 3 || !name.startsWith('on')) {
591
+ continue;
592
+ }
593
+ if (proto[name] instanceof Function) {
594
+ const events = new Set();
595
+ this._events.set(name.substring(2).toLowerCase(), events);
596
+ let defaultEvent = proto[name];
597
+ Object.defineProperties(this, {
598
+ [name]: {
599
+ get: () => (...args) => {
600
+ // Call default event if not null (can happen in JS usage)
601
+ if (defaultEvent) {
602
+ defaultEvent.call(this, ...args);
603
+ }
604
+ // Call subscribers
605
+ for (const event of events) {
606
+ event(...args);
607
+ }
608
+ },
609
+ set: (value) => {
610
+ // Assign a default behavior!
611
+ defaultEvent = value;
612
+ }
613
+ }
614
+ });
615
+ }
616
+ }
617
+ proto = Object.getPrototypeOf(proto);
618
+ }
619
+ }
620
+ /**
621
+ * Event subscription
622
+ * @param name Name of event without the `on` prefix (ex: `log` to `onLog` event declared)
623
+ * @param event Subscriber Function
624
+ * @param abort Optional `AbortController` to stop this or multiple subscriptions in same time
625
+ */
626
+ on(name, event, abort) {
627
+ if (!event) {
628
+ throw Error('event to subscribe cannot be null');
629
+ }
630
+ const events = this._event(name);
631
+ events.add(event);
632
+ if (abort) {
633
+ abort.signal.addEventListener('abort', () => {
634
+ events.delete(event);
635
+ });
636
+ }
637
+ }
638
+ /**
639
+ * Event subscription only one time, once time fired it's automatically unsubscribe
640
+ * @param name Name of event without the `on` prefix (ex: `log` to `onLog` event declared)
641
+ * @param event Subscriber Function
642
+ * @param abort Optional `AbortController` to stop this or multiple subscriptions in same time
643
+ */
644
+ once(name, event, abort) {
645
+ if (!event) {
646
+ throw Error('event to subscribe cannot be null');
647
+ }
648
+ const events = this._event(name);
649
+ events.add(() => {
650
+ events.delete(event); // delete from events
651
+ event(); // execute event
652
+ });
653
+ if (abort) {
654
+ abort.signal.addEventListener('abort', () => {
655
+ events.delete(event);
656
+ });
657
+ }
658
+ }
659
+ /**
660
+ * Event unsubscription
661
+ * @param name Name of event without the 'on' prefix (ex: 'log' to 'onLog' event declared)
662
+ * @param event Unsubscriber Function, must be the one passed to {@link on} or {@link once} subscription methods
663
+ */
664
+ off(name, event) {
665
+ if (!event) {
666
+ throw Error('event to unsubscribe cannot be null');
667
+ }
668
+ this._event(name).delete(event);
669
+ }
670
+ _event(name) {
671
+ const events = this._events.get(name.toLowerCase());
672
+ if (!events) {
673
+ throw Error('No event on' + name + ' on class ' + this.constructor.name);
674
+ }
675
+ return events;
676
+ }
677
+ }/**
678
+ * Copyright 2023 Ceeblue B.V.
679
+ * This file is part of https://github.com/CeeblueTV/web-utils which is released under GNU Affero General Public License.
680
+ * See file LICENSE or go to https://spdx.org/licenses/AGPL-3.0-or-later.html for full license details.
681
+ */
682
+ /**
683
+ * Queue typed similar to a {@link https://en.cppreference.com/w/cpp/container/queue | std::queue<Type>} with possibility to limit the capacity like a FIFO
684
+ * @example
685
+ * const queue = new Queue<number>(2);
686
+ * queue.push(1); // [1]
687
+ * queue.push(2); // [1,2]
688
+ * queue.push(3); // [2,3] 1 has been removed to respect 2 capacity
689
+ */
690
+ class Queue {
691
+ /**
692
+ * Number of element in the queue
693
+ */
694
+ get size() {
695
+ return this._queue.length;
696
+ }
697
+ /**
698
+ * Maximum capacity for the queue, if not set queue has unlimited capacity
699
+ */
700
+ get capacity() {
701
+ return this._capacity;
702
+ }
703
+ /**
704
+ * Set a maximum capacity for the queue,
705
+ * if you push new value exceding this capacity the firsts are removed (FIFO)
706
+ * if set to undefined the queue is unlimited
707
+ */
708
+ set capacity(value) {
709
+ this._capacity = value;
710
+ if (value != null && this._queue.length > value) {
711
+ this._queue.splice(0, this._queue.length - value);
712
+ }
713
+ }
714
+ /**
715
+ * The front element
716
+ */
717
+ get front() {
718
+ return this._queue[0];
719
+ }
720
+ /**
721
+ * The back element
722
+ */
723
+ get back() {
724
+ return this._queue[this._queue.length - 1];
725
+ }
726
+ /**
727
+ * Iterator though queue's elements
728
+ */
729
+ [Symbol.iterator]() {
730
+ return this._queue[Symbol.iterator]();
731
+ }
732
+ /**
733
+ * Instanciate a new queue object with the type passed as template
734
+ * @param capacity if set it limits the size of the queue, any exceding element pops the first element pushed (FIFO)
735
+ */
736
+ constructor(capacity) {
737
+ this._capacity = capacity;
738
+ this._queue = new Array();
739
+ }
740
+ /**
741
+ * Push a new element in the queue
742
+ * @param value value of the element
743
+ * @returns this
744
+ */
745
+ push(value) {
746
+ if (this._capacity != null && this._queue.push(value) > this._capacity) {
747
+ this.pop();
748
+ }
749
+ return this;
750
+ }
751
+ /**
752
+ * Pop the first element from the queue
753
+ * @returns The first element removed
754
+ */
755
+ pop() {
756
+ return this._queue.shift();
757
+ }
758
+ /**
759
+ * Clear all the elements, queue becomes empty
760
+ * @returns this
761
+ */
762
+ clear() {
763
+ this._queue.length = 0;
764
+ return this;
765
+ }
766
+ }/**
767
+ * Copyright 2023 Ceeblue B.V.
768
+ * This file is part of https://github.com/CeeblueTV/web-utils which is released under GNU Affero General Public License.
769
+ * See file LICENSE or go to https://spdx.org/licenses/AGPL-3.0-or-later.html for full license details.
770
+ */
771
+ /**
772
+ * A collection of number Queue<number> with the following efficient mathematic computation:
773
+ * - minimum value in the collection
774
+ * - maximum value in the collection
775
+ * - average value of the collection
776
+ */
777
+ class Numbers extends Queue {
778
+ /**
779
+ * minimum value in the collection, or 0 if colleciton is empty
780
+ */
781
+ get minimum() {
782
+ return this._min;
783
+ }
784
+ /**
785
+ * maximum value in the collection, or 0 if colleciton is empty
786
+ */
787
+ get maximum() {
788
+ return this._max;
789
+ }
790
+ /**
791
+ * average value of the collection, or 0 if collection if empty
792
+ */
793
+ get average() {
794
+ if (this._average == null) {
795
+ this._average = this.size ? this._sum / this.size : 0;
796
+ }
797
+ return this._average;
798
+ }
799
+ /**
800
+ * Instantiate the collection of the number
801
+ * @param capacity if set it limits the number of values stored, any exceding number pops the first number pushed (FIFO)
802
+ */
803
+ constructor(capacity) {
804
+ super(capacity);
805
+ this._sum = 0;
806
+ this._min = 0;
807
+ this._max = 0;
808
+ }
809
+ /**
810
+ * Push a value to the back to the collection
811
+ * @param value number to add
812
+ * @returns this
813
+ */
814
+ push(value) {
815
+ if (value > this._max) {
816
+ this._max = value;
817
+ }
818
+ else if (value < this._min) {
819
+ this._min = value;
820
+ }
821
+ this._average = undefined;
822
+ this._sum += value;
823
+ super.push(value);
824
+ return this;
825
+ }
826
+ /**
827
+ * Pop the front number from the collection
828
+ * @returns the front number removed
829
+ */
830
+ pop() {
831
+ const front = super.pop();
832
+ if (front === this._max) {
833
+ this._max = Math.max(0, ...this);
834
+ }
835
+ else if (front === this._min) {
836
+ this._min = Math.min(0, ...this);
837
+ }
838
+ this._average = undefined;
839
+ this._sum -= front || 0;
840
+ return front;
841
+ }
842
+ /**
843
+ * Clear all the numbers, collection becomes empty
844
+ * @returns this
845
+ */
846
+ clear() {
847
+ this._min = this._max = this._sum = 0;
848
+ super.clear();
849
+ return this;
850
+ }
851
+ }/**
852
+ * Copyright 2023 Ceeblue B.V.
853
+ * This file is part of https://github.com/CeeblueTV/web-utils which is released under GNU Affero General Public License.
854
+ * See file LICENSE or go to https://spdx.org/licenses/AGPL-3.0-or-later.html for full license details.
855
+ */
856
+ /* eslint-disable @typescript-eslint/no-explicit-any */
857
+ /**
858
+ * Toolkit to deal with Session Description Protocol (SDP),
859
+ * mainly to tranform a SDP string Offer/Answer to a manipulable JS object representation
860
+ * @example
861
+ * const peerConnection = new RTCPeerConnection();
862
+ * peerConnection.createOffer()
863
+ * .then( offer =>
864
+ * // Change offer.sdp string to a JS manipulable object
865
+ * const sdp = SDP.fromString(offer.sdp || '');
866
+ * // Change a property of SDP
867
+ * sdp.v = 2; // change SDP version
868
+ * // Reserialize to the legal SDP string format
869
+ * offer.sdp = SDP.toString(sdp);
870
+ * // Set SDP offer to peerConnection
871
+ * peerConnection.setLocalDescription(offer);
872
+ * )
873
+ */
874
+ const SDP = {
875
+ /**
876
+ * Unserialize SDP string to a manipulable JS object representation
877
+ * It's an object with:
878
+ * - root SDP properties
879
+ * - media description iterable
880
+ * @param lines SDP string reprensentation
881
+ * @returns SDP object representation, iterable through media description
882
+ * @example
883
+ * [
884
+ * group: "DUNBLE 0 1",
885
+ * o: "- 1699450751193623 0 IN IP4 0.0.0.0",
886
+ * s: "-",
887
+ * t: "0 0",
888
+ * v: "0",
889
+ * ice-lite: "",
890
+ * length: 2,
891
+ * {
892
+ * m: "audio 9 UDP/TLS/RTP/SAVPF 111",
893
+ * c: "IN IP4 0.0.0.0",
894
+ * rtcp: "9",
895
+ * sendonly: "",
896
+ * setup: "passive",
897
+ * fingerprint: "sha-256 51:36:ED:78:A4:9F:25:8C:39:9A:0E:A0:B4:9B:6E:04:37:FF:AD:96:93:71:43:88:2C:0B:0F:AB:6F:9A:52:B8",
898
+ * ice-ufrag: "fa37",
899
+ * ice-pwd: "JncCHryDsbzayy4cBWDxS2",
900
+ * rtcp-mux: "",
901
+ * rtcp-rsize: "",
902
+ * rtpmap: "111 opus/48000/2",
903
+ * rtcp-fb: "111 nack",
904
+ * id: "0",
905
+ * fmtp: "111 minptime=10;useinbandfec=1",
906
+ * candidate: "1 1 udp 2130706431 89.105.221.108 56643 typ host",
907
+ * end-of-candidates: ""
908
+ * },
909
+ * {
910
+ * m: "video 9 UDP/TLS/RTP/SAVPF 106",
911
+ * c: "IN IP4 0.0.0.0",
912
+ * rtcp: "9",
913
+ * sendonly: "",
914
+ * setup: "passive",
915
+ * fingerprint: "sha-256 51:36:ED:78:A4:9F:25:8C:39:9A:0E:A0:B4:9B:6E:04:37:FF:AD:96:93:71:43:88:2C:0B:0F:AB:6F:9A:52:B8",
916
+ * ice-ufrag: "fa37",
917
+ * ice-pwd: "JncCHryDsbzayy4cBWDxS2",
918
+ * rtcp-mux: "",
919
+ * rtcp-rsize: "",
920
+ * rtpmap: "106 H264/90000",
921
+ * rtcp-fb: [
922
+ * "106 nack",
923
+ * "106 goog-remb"
924
+ * ],
925
+ * mid: "1",
926
+ * fmtp: "106 profile-level-id=42e01f;level-asymmetry-allowed=1;packetization-mode=1",
927
+ * candidate: "1 1 udp 2130706431 89.105.221.108 56643 typ host",
928
+ * end-of-candidates: ""
929
+ * }
930
+ * ]
931
+ */
932
+ fromString(lines) {
933
+ if (Array.isArray(lines)) {
934
+ return lines; // already converted
935
+ }
936
+ const sdp = new Array();
937
+ let media = sdp;
938
+ let fingerprint;
939
+ for (let line of lines.toString().split('\n')) {
940
+ line = line.trim();
941
+ if (!line) {
942
+ continue;
943
+ }
944
+ let key = line[0];
945
+ const value = line.substring(line.indexOf('=') + 1).trim();
946
+ switch (key.toLowerCase()) {
947
+ case 'a': {
948
+ if (!value) {
949
+ continue;
950
+ } // empty attribute!
951
+ key = this.addAttribute(media, value);
952
+ // save fingerprint to repeat it in medias
953
+ if (sdp === media && key.toLowerCase() === 'fingerprint') {
954
+ fingerprint = media.fingerprint;
955
+ }
956
+ break;
957
+ }
958
+ case 'm':
959
+ if (sdp.length && fingerprint && !sdp[sdp.length - 1].fingerprint) {
960
+ media.fingerprint = fingerprint;
961
+ }
962
+ sdp.push((media = { m: value }));
963
+ break;
964
+ default:
965
+ media[key] = value;
966
+ }
967
+ }
968
+ if (sdp.length && fingerprint && !sdp[sdp.length - 1].fingerprint) {
969
+ media.fingerprint = fingerprint;
970
+ }
971
+ return sdp;
972
+ },
973
+ /**
974
+ * Serialize SDP JS object to a SDP string legal representation
975
+ * @param sdp SDP object reprensetation
976
+ * @returns SDP string reprensentation
977
+ */
978
+ toString(sdp) {
979
+ if (typeof sdp === 'string') {
980
+ return sdp; // already converted
981
+ }
982
+ const medias = [];
983
+ // https://www.cl.cam.ac.uk/~jac22/books/mm/book/node182.html
984
+ let lines = 'v' in sdp ? 'v=' + sdp.v + '\n' : '';
985
+ if ('o' in sdp) {
986
+ lines += 'o=' + sdp.o + '\n';
987
+ }
988
+ if ('s' in sdp) {
989
+ lines += 's=' + sdp.s + '\n';
990
+ }
991
+ const obj = sdp;
992
+ for (const key of Object.keys(sdp)) {
993
+ if (key === 'v' || key === 'o' || key === 's') {
994
+ continue;
995
+ }
996
+ const value = obj[key];
997
+ if (value == null) {
998
+ continue;
999
+ } // ignore this key/value
1000
+ const index = parseInt(key);
1001
+ if (!isNaN(index)) {
1002
+ // Is a number! Media object!
1003
+ medias[index] = value;
1004
+ continue;
1005
+ }
1006
+ const count = (Array.isArray(value) && value.length) || 1; // value can be numeric!
1007
+ for (let i = 0; i < count; ++i) {
1008
+ const line = Array.isArray(value) && value.length ? value[i] : value;
1009
+ if (key.length > 1) {
1010
+ // when key is superior to 1 letter it's a attribute!
1011
+ lines += 'a=' + key;
1012
+ if (line) {
1013
+ lines += ':';
1014
+ }
1015
+ }
1016
+ else {
1017
+ lines += key + '=';
1018
+ }
1019
+ lines += line + '\n';
1020
+ }
1021
+ }
1022
+ for (const media of medias) {
1023
+ lines += this.toString(media);
1024
+ }
1025
+ return lines;
1026
+ },
1027
+ /**
1028
+ * While set a property to a SDP object representation is possible directly,
1029
+ * we could prefer add a new property without overload a possible already existing value.
1030
+ * This function allows to add a property to our SDP representation:
1031
+ * - if the key's attribute doesn't exists yet it adds it like a simple JS property sdp[key] = value
1032
+ * - if the key's attribute exists already it morphs the value to a Array and push it inside
1033
+ * @param sdp the SDP object representation on which added the attribute
1034
+ * @param attribute the string attribut in a format "key:value" or just "key" to add an attribute without value
1035
+ * @returns the key part of the attribute added (or if value is empty it returns the same attribute as passed in argument)
1036
+ */
1037
+ addAttribute(sdp, attribute) {
1038
+ var _a;
1039
+ const a = SDP.parseAttribute(attribute);
1040
+ const value = (_a = a.value) !== null && _a !== void 0 ? _a : ''; // to allow to test if key exists even without value with if(sdp.key != null)
1041
+ const obj = sdp;
1042
+ const oldValue = obj[a.key];
1043
+ if (!oldValue) {
1044
+ obj[a.key] = value;
1045
+ }
1046
+ else if (Array.isArray(oldValue)) {
1047
+ oldValue.push(value);
1048
+ }
1049
+ else if (value !== oldValue) {
1050
+ obj[a.key] = [oldValue, value];
1051
+ }
1052
+ return a.key;
1053
+ },
1054
+ /**
1055
+ * While it's possible to delete a attribute manually on the SDP object representation with a delete sdp.key,
1056
+ * we could prefer remove only a value in a sdp.key array containing multiple values.
1057
+ * Like opposite to addAttribute this method allows to remove an unique attribute value.
1058
+ * @param sdp the SDP object representation on which removed the attribute
1059
+ * @param attribute the string attribut in a format "key:value" or just "key" to remove the whole attribute and all its values
1060
+ * @returns the key part of the attribute removed (or if value is empty it returns the same attribute as passed in argument)
1061
+ */
1062
+ removeAttribute(sdp, attribute) {
1063
+ const a = SDP.parseAttribute(attribute);
1064
+ const obj = sdp;
1065
+ if (a.value === undefined) {
1066
+ delete obj[attribute];
1067
+ return attribute;
1068
+ }
1069
+ const current = obj[attribute];
1070
+ if (Array.isArray(a.value)) {
1071
+ const i = current.findIndex((current) => current === a.value);
1072
+ if (i >= 0) {
1073
+ current.splice(i, 1);
1074
+ }
1075
+ }
1076
+ else if (current === a.value) {
1077
+ delete obj[attribute];
1078
+ }
1079
+ return a.key;
1080
+ },
1081
+ /**
1082
+ * Parse an attribute in a format "key:value"
1083
+ * @param attribute string attribute to parse
1084
+ * @returns the {key, value} result, with value undefined if attribute was a "key" without value
1085
+ */
1086
+ parseAttribute(attribute) {
1087
+ const found = attribute.indexOf(':');
1088
+ return {
1089
+ key: (found >= 0 ? attribute.substring(0, found) : attribute).trim(),
1090
+ value: found >= 0 ? attribute.substring(found + 1).trim() : undefined
1091
+ };
1092
+ }
1093
+ };
1094
+ Object.freeze(SDP);/**
1095
+ * Copyright 2023 Ceeblue B.V.
1096
+ * This file is part of https://github.com/CeeblueTV/web-utils which is released under GNU Affero General Public License.
1097
+ * See file LICENSE or go to https://spdx.org/licenses/AGPL-3.0-or-later.html for full license details.
1098
+ */
1099
+ const _decoder = new TextDecoder();
1100
+ const _encoder = new TextEncoder();
1101
+ /* eslint-disable @typescript-eslint/no-explicit-any */
1102
+ const _perf = performance; // to increase x10 now performance!
1103
+ /**
1104
+ * Some basic utility functions
1105
+ */
1106
+ /**
1107
+ * Version of the library
1108
+ */
1109
+ const VERSION = '1.0.0';
1110
+ /**
1111
+ * An empty lambda function, pratical to disable default behavior of function or events which are not expected to be null
1112
+ * @example
1113
+ * console.log = Util.EMPTY_FUNCTION; // disable logs without breaking calls
1114
+ */
1115
+ const EMPTY_FUNCTION = () => { };
1116
+ /**
1117
+ * Efficient and high resolution timestamp in milliseconds elapsed since {@link Util.timeOrigin}
1118
+ */
1119
+ function time() {
1120
+ return Math.floor(_perf.now());
1121
+ }
1122
+ /**
1123
+ * Time origin represents the time when the application has started
1124
+ */
1125
+ function timeOrigin() {
1126
+ return Math.floor(_perf.now() + _perf.timeOrigin);
1127
+ }
1128
+ /**
1129
+ * Parse query and returns it in an easy-to-use Javascript object form
1130
+ * @param urlOrQueryOrSearch string, url, or searchParams containing query. If not set it uses `location.search` to determinate query.
1131
+ * @returns An javascript object containing each option
1132
+ */
1133
+ function options(urlOrQueryOrSearch = typeof location === 'undefined'
1134
+ ? undefined
1135
+ : location) {
1136
+ if (!urlOrQueryOrSearch) {
1137
+ return {};
1138
+ }
1139
+ try {
1140
+ const url = urlOrQueryOrSearch;
1141
+ urlOrQueryOrSearch = new URL(url).searchParams;
1142
+ }
1143
+ catch (e) {
1144
+ if (typeof urlOrQueryOrSearch == 'string') {
1145
+ if (urlOrQueryOrSearch.startsWith('?')) {
1146
+ urlOrQueryOrSearch = urlOrQueryOrSearch.substring(1);
1147
+ }
1148
+ urlOrQueryOrSearch = new URLSearchParams(urlOrQueryOrSearch);
1149
+ }
1150
+ }
1151
+ // works same if urlOrQueryOrSearch is null, integer, or a already object etc...
1152
+ return objectFrom(urlOrQueryOrSearch, { withType: true, noEmptyString: true });
1153
+ }
1154
+ /**
1155
+ * Returns an easy-to-use Javascript object something iterable, such as a Map, Set, or Array
1156
+ * @param value iterable input
1157
+ * @param params.withType `false`, if set it tries to cast string value to a JS number/boolean/undefined/null type.
1158
+ * @param params.noEmptyString `false`, if set it converts empty string value to a true boolean, usefull to allow a `if(result.key)` check for example
1159
+ * @returns An javascript object
1160
+ */
1161
+ function objectFrom(value, params) {
1162
+ params = Object.assign({ withType: false, noEmptyString: false }, params);
1163
+ const obj = {};
1164
+ if (!value) {
1165
+ return obj;
1166
+ }
1167
+ for (const [key, val] of objectEntries(value)) {
1168
+ value = val;
1169
+ if (params.withType && value != null && value.substring) {
1170
+ if (value) {
1171
+ const number = Number(value);
1172
+ if (isNaN(number)) {
1173
+ switch (value.toLowerCase()) {
1174
+ case 'true':
1175
+ value = true;
1176
+ break;
1177
+ case 'false':
1178
+ value = false;
1179
+ break;
1180
+ case 'null':
1181
+ value = null;
1182
+ break;
1183
+ case 'undefined':
1184
+ value = undefined;
1185
+ break;
1186
+ }
1187
+ }
1188
+ else {
1189
+ value = number;
1190
+ }
1191
+ }
1192
+ else if (params.noEmptyString) {
1193
+ // if empty string => TRUE to allow a if(options.key) check for example
1194
+ value = true;
1195
+ }
1196
+ }
1197
+ if (obj[key]) {
1198
+ if (!Array.isArray(obj[key])) {
1199
+ obj[key] = new Array(obj[key]);
1200
+ }
1201
+ obj[key].push(value);
1202
+ }
1203
+ else {
1204
+ obj[key] = value;
1205
+ }
1206
+ }
1207
+ return obj;
1208
+ }
1209
+ /**
1210
+ * Returns entries from something iterable, such as a Map, Set, or Array
1211
+ * @param value iterable input
1212
+ * @returns An javascript object
1213
+ */
1214
+ function objectEntries(value) {
1215
+ if (value.entries) {
1216
+ return value.entries();
1217
+ }
1218
+ return Array.from({
1219
+ [Symbol.iterator]: function* () {
1220
+ for (const key in value) {
1221
+ yield [key, value[key]];
1222
+ }
1223
+ }
1224
+ });
1225
+ }
1226
+ /**
1227
+ * Converts various data types, such as objects, strings, exceptions, errors,
1228
+ * or numbers, into a string representation. Since it offers a more comprehensive format,
1229
+ * this function is preferred to `JSON.stringify()`.
1230
+ * @param obj Any objects, strings, exceptions, errors, or number
1231
+ * @param params.space `''`, allows to configure space in the string representation
1232
+ * @param params.decimal `2`, allows to choose the number of decimal to display in the string representation
1233
+ * @param params.recursive `false`, allows to serialize recursively every object value, beware if a value refers to a already parsed value an infinite loop will occur.
1234
+ * @returns the final string representation
1235
+ */
1236
+ // Online Javascript Editor for free
1237
+ // Write, Edit and Run your Javascript code using JS Online Compiler
1238
+ function stringify(obj, params = {}) {
1239
+ params = Object.assign({ space: ' ', decimal: 2, recursive: 1 }, params);
1240
+ if (!obj) {
1241
+ return String(obj);
1242
+ }
1243
+ const error = obj.error || obj.message;
1244
+ if (error) {
1245
+ // is a error!
1246
+ obj = error;
1247
+ }
1248
+ if (obj.toFixed) {
1249
+ return obj.toFixed(Number(params.decimal) || 0);
1250
+ }
1251
+ if (obj.substring || !params.recursive) {
1252
+ // is already a string OR has to be stringified
1253
+ return String(obj);
1254
+ }
1255
+ const space = params.space || '';
1256
+ if (Array.isArray(obj)) {
1257
+ // Array!
1258
+ let res = '';
1259
+ for (const value of obj) {
1260
+ res += (res ? ',' : '[') + space;
1261
+ res += stringify(value, Object.assign(params, { recursive: params.recursive - 1 }));
1262
+ }
1263
+ return (res += space + ']');
1264
+ }
1265
+ if (obj.byteLength != null && (obj === null || obj === void 0 ? void 0 : obj[Symbol.iterator])) {
1266
+ // Binary!
1267
+ return _decoder.decode(obj);
1268
+ }
1269
+ let res = '';
1270
+ for (const name in obj) {
1271
+ res += (res ? ',' : '{') + space + name + ':';
1272
+ res += stringify(obj[name], Object.assign(params, { recursive: params.recursive - 1 }));
1273
+ }
1274
+ return (res += space + '}');
1275
+ }
1276
+ function toBin(value) {
1277
+ return _encoder.encode(value);
1278
+ }var Util=/*#__PURE__*/Object.freeze({__proto__:null,EMPTY_FUNCTION:EMPTY_FUNCTION,VERSION:VERSION,objectFrom:objectFrom,options:options,stringify:stringify,time:time,timeOrigin:timeOrigin,toBin:toBin});/**
1279
+ * Copyright 2023 Ceeblue B.V.
1280
+ * This file is part of https://github.com/CeeblueTV/web-utils which is released under GNU Affero General Public License.
1281
+ * See file LICENSE or go to https://spdx.org/licenses/AGPL-3.0-or-later.html for full license details.
1282
+ */
1283
+ /**
1284
+ * The WebSocketReliable class extends WebSocket to bring up the following improvements:
1285
+ * - Fix all possible unintentional closing ways to get always a related error message, {@link onClose | onClose(error?) event}
1286
+ * - Make possible message sending while connecting. Indeed no need to wait {@link onOpen} before to send message,
1287
+ * you can open the socket and immediately send messages, it will be queue and flushs on connection etablishment
1288
+ * - Make possible a delayed connection, or a reconnection. Indeed you can create an unconnected Websocket instance
1289
+ * without passing any url argument and starts the conneciton more later with {@link WebSocketReliable.open(url) | open(url)} method
1290
+ * - Make possible to control sending/queueing message: send method take an optional queueing=true argument to
1291
+ * queue message rather send it, a futur call to flush will send it. Then queueing getter allows to handle the queue
1292
+ * if need to purge it or remove some queued message. Use it all together can help to prioritize messages or control overload.
1293
+ * @example
1294
+ * const ws = new WebSocketReliable(url);
1295
+ * ws.onClose = (error?:string) => {
1296
+ * if(error) {
1297
+ * console.error(error);
1298
+ * }
1299
+ * // reconnection attempt every seconds
1300
+ * setTimeout(() => ws.open(url), 1000);
1301
+ * }
1302
+ * ws.onMessage = (message:string) => {
1303
+ * console.log(message);
1304
+ * }
1305
+ * ws.send('hello'); // send immediatly a hello message is possible (no need to wait onOpen)
1306
+ */
1307
+ class WebSocketReliable extends EventEmitter {
1308
+ /**
1309
+ * @event `open` fired when socket is connected
1310
+ */
1311
+ onOpen() { }
1312
+ /**
1313
+ * @event `message` fired on message reception
1314
+ * @param message can be binary or string.
1315
+ * If you subscribe to the event with message as string type (and not union),
1316
+ * it means that you know that all your messages are distributed in a string format
1317
+ */
1318
+ onMessage(message) { }
1319
+ /**
1320
+ * @event `close` fired on websocket close
1321
+ * @param error error description on an improper closure
1322
+ */
1323
+ onClose(error) {
1324
+ if (error) {
1325
+ console.error(error);
1326
+ }
1327
+ }
1328
+ /**
1329
+ * binaryType, fix binary type to arrayBuffer
1330
+ */
1331
+ get binaryType() {
1332
+ return 'arraybuffer';
1333
+ }
1334
+ /**
1335
+ * url of connection
1336
+ */
1337
+ get url() {
1338
+ var _a, _b;
1339
+ return (_b = (_a = this._ws) === null || _a === void 0 ? void 0 : _a.url) !== null && _b !== void 0 ? _b : '';
1340
+ }
1341
+ /**
1342
+ * extensions negociated by the server
1343
+ */
1344
+ get extensions() {
1345
+ var _a, _b;
1346
+ return (_b = (_a = this._ws) === null || _a === void 0 ? void 0 : _a.extensions) !== null && _b !== void 0 ? _b : '';
1347
+ }
1348
+ /**
1349
+ * protocol negociated by the server
1350
+ */
1351
+ get protocol() {
1352
+ var _a, _b;
1353
+ return (_b = (_a = this._ws) === null || _a === void 0 ? void 0 : _a.protocol) !== null && _b !== void 0 ? _b : '';
1354
+ }
1355
+ /**
1356
+ * opened equals true when connection is etablished, in other word when onOpen event is fired
1357
+ */
1358
+ get opened() {
1359
+ return this._opened;
1360
+ }
1361
+ /**
1362
+ * {@link https://developer.mozilla.org/docs/Web/API/WebSocket/readyState | Official websocket readyState}
1363
+ */
1364
+ get readyState() {
1365
+ return this._ws ? this._ws.readyState : 3;
1366
+ }
1367
+ /**
1368
+ * True when connection is closed, in other words when {@link onClose} event is fired
1369
+ * or when WebSocketReliable is build without url (disconnected creation)
1370
+ */
1371
+ get closed() {
1372
+ return this._closed;
1373
+ }
1374
+ /**
1375
+ * The number of bytes of data that were queued during calls to send() but not yet transmitted to the network
1376
+ */
1377
+ get bufferedAmount() {
1378
+ var _a;
1379
+ return this._queueingBytes + (((_a = this._ws) === null || _a === void 0 ? void 0 : _a.bufferedAmount) || 0);
1380
+ }
1381
+ /**
1382
+ * Queued messages from a call to send() waiting to be transmit one time websocket connection opened (or with an explicit call to flush() method)
1383
+ */
1384
+ get queueing() {
1385
+ return this._queueing;
1386
+ }
1387
+ /**
1388
+ * Create a WebSocketReliable object, and open it if an url is passed in argument
1389
+ * @param url URL of the WebSocket endpoint or null to start the connection later
1390
+ */
1391
+ constructor(url, protocols) {
1392
+ super();
1393
+ this._queueing = [];
1394
+ this._queueingBytes = 0;
1395
+ this._opened = false;
1396
+ this._closed = true;
1397
+ if (url) {
1398
+ this.open(url, protocols);
1399
+ }
1400
+ }
1401
+ /**
1402
+ * Open a WebSocket connection
1403
+ * @param url url of the websocket endpoint
1404
+ * @returns this
1405
+ */
1406
+ open(url, protocols) {
1407
+ this._closed = false;
1408
+ const ws = (this._ws = new WebSocket(url, protocols));
1409
+ ws.binaryType = this.binaryType;
1410
+ ws.onmessage = e => this.onMessage(e.data);
1411
+ // Add details and fix close ways
1412
+ ws.onclose = (e) => {
1413
+ if (!this._opened) {
1414
+ // close during connection
1415
+ this.close(url.toString() + ' connection failed (' + String(e.reason || e.code) + ')');
1416
+ }
1417
+ else if (e.code === 1000 || e.code === 1005) {
1418
+ // normal disconnection from server (no error code)
1419
+ this.close(url.toString() + ' shutdown');
1420
+ }
1421
+ else {
1422
+ // disconnection from server
1423
+ this.close(url.toString() + ' disconnection (' + String(e.reason || e.code) + ')');
1424
+ }
1425
+ };
1426
+ // Wrap send method to queue messages until connection is established.
1427
+ ws.onopen = _ => {
1428
+ this._opened = true;
1429
+ this.flush();
1430
+ this.onOpen();
1431
+ };
1432
+ return this;
1433
+ }
1434
+ /**
1435
+ * Send a message
1436
+ * @param message
1437
+ * @param queueing When set it reports the sending to a more later call to flush
1438
+ * @returns this
1439
+ */
1440
+ send(message, queueing = false) {
1441
+ if (!this._ws) {
1442
+ throw Error('Open socket before to send data');
1443
+ }
1444
+ if (queueing || !this._opened) {
1445
+ this._queueing.push(message);
1446
+ this._queueingBytes += typeof message === 'string' ? message.length : message.byteLength;
1447
+ }
1448
+ else {
1449
+ this._ws.send(message);
1450
+ }
1451
+ return this;
1452
+ }
1453
+ /**
1454
+ * Send queueing messages
1455
+ */
1456
+ flush() {
1457
+ if (this._ws) {
1458
+ for (const message of this._queueing) {
1459
+ this._ws.send(message);
1460
+ }
1461
+ }
1462
+ this._queueing.length = 0;
1463
+ this._queueingBytes = 0;
1464
+ }
1465
+ /**
1466
+ * Close websocket
1467
+ * @param error the error reason if is not a proper close
1468
+ */
1469
+ close(error) {
1470
+ if (!this._ws || this._closed) {
1471
+ return;
1472
+ }
1473
+ this._closed = true;
1474
+ this._ws.onopen = this._ws.onclose = this._ws.onmessage = null; // otherwise can receive message AFTER close!
1475
+ this._ws.close(); // Don't set to undefined to keep this._ws properties valid!
1476
+ // release resources!
1477
+ this._opened = false;
1478
+ this._queueing.length = 0;
1479
+ this._queueingBytes = 0;
1480
+ this.onClose(error);
1481
+ }
1482
+ }export{BinaryReader,BinaryWriter,BitReader,Connect,EventEmitter,NetAddress,Numbers,Queue,SDP,Util,WebSocketReliable};//# sourceMappingURL=web-utils.js.map