playsocketjs 1.0.14 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,6 +7,2163 @@ function getDefaultExportFromCjs (x) {
7
7
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
8
8
  }
9
9
 
10
+ function getAugmentedNamespace(n) {
11
+ if (Object.prototype.hasOwnProperty.call(n, '__esModule')) return n;
12
+ var f = n.default;
13
+ if (typeof f == "function") {
14
+ var a = function a () {
15
+ if (this instanceof a) {
16
+ return Reflect.construct(f, arguments, this.constructor);
17
+ }
18
+ return f.apply(this, arguments);
19
+ };
20
+ a.prototype = f.prototype;
21
+ } else a = {};
22
+ Object.defineProperty(a, '__esModule', {value: true});
23
+ Object.keys(n).forEach(function (k) {
24
+ var d = Object.getOwnPropertyDescriptor(n, k);
25
+ Object.defineProperty(a, k, d.get ? d : {
26
+ enumerable: true,
27
+ get: function () {
28
+ return n[k];
29
+ }
30
+ });
31
+ });
32
+ return a;
33
+ }
34
+
35
+ function utf8Count(str) {
36
+ const strLength = str.length;
37
+ let byteLength = 0;
38
+ let pos = 0;
39
+ while (pos < strLength) {
40
+ let value = str.charCodeAt(pos++);
41
+ if ((value & 0xffffff80) === 0) {
42
+ // 1-byte
43
+ byteLength++;
44
+ continue;
45
+ }
46
+ else if ((value & 0xfffff800) === 0) {
47
+ // 2-bytes
48
+ byteLength += 2;
49
+ }
50
+ else {
51
+ // handle surrogate pair
52
+ if (value >= 0xd800 && value <= 0xdbff) {
53
+ // high surrogate
54
+ if (pos < strLength) {
55
+ const extra = str.charCodeAt(pos);
56
+ if ((extra & 0xfc00) === 0xdc00) {
57
+ ++pos;
58
+ value = ((value & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000;
59
+ }
60
+ }
61
+ }
62
+ if ((value & 0xffff0000) === 0) {
63
+ // 3-byte
64
+ byteLength += 3;
65
+ }
66
+ else {
67
+ // 4-byte
68
+ byteLength += 4;
69
+ }
70
+ }
71
+ }
72
+ return byteLength;
73
+ }
74
+ function utf8EncodeJs(str, output, outputOffset) {
75
+ const strLength = str.length;
76
+ let offset = outputOffset;
77
+ let pos = 0;
78
+ while (pos < strLength) {
79
+ let value = str.charCodeAt(pos++);
80
+ if ((value & 0xffffff80) === 0) {
81
+ // 1-byte
82
+ output[offset++] = value;
83
+ continue;
84
+ }
85
+ else if ((value & 0xfffff800) === 0) {
86
+ // 2-bytes
87
+ output[offset++] = ((value >> 6) & 0x1f) | 0xc0;
88
+ }
89
+ else {
90
+ // handle surrogate pair
91
+ if (value >= 0xd800 && value <= 0xdbff) {
92
+ // high surrogate
93
+ if (pos < strLength) {
94
+ const extra = str.charCodeAt(pos);
95
+ if ((extra & 0xfc00) === 0xdc00) {
96
+ ++pos;
97
+ value = ((value & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000;
98
+ }
99
+ }
100
+ }
101
+ if ((value & 0xffff0000) === 0) {
102
+ // 3-byte
103
+ output[offset++] = ((value >> 12) & 0x0f) | 0xe0;
104
+ output[offset++] = ((value >> 6) & 0x3f) | 0x80;
105
+ }
106
+ else {
107
+ // 4-byte
108
+ output[offset++] = ((value >> 18) & 0x07) | 0xf0;
109
+ output[offset++] = ((value >> 12) & 0x3f) | 0x80;
110
+ output[offset++] = ((value >> 6) & 0x3f) | 0x80;
111
+ }
112
+ }
113
+ output[offset++] = (value & 0x3f) | 0x80;
114
+ }
115
+ }
116
+ // TextEncoder and TextDecoder are standardized in whatwg encoding:
117
+ // https://encoding.spec.whatwg.org/
118
+ // and available in all the modern browsers:
119
+ // https://caniuse.com/textencoder
120
+ // They are available in Node.js since v12 LTS as well:
121
+ // https://nodejs.org/api/globals.html#textencoder
122
+ const sharedTextEncoder = new TextEncoder();
123
+ // This threshold should be determined by benchmarking, which might vary in engines and input data.
124
+ // Run `npx ts-node benchmark/encode-string.ts` for details.
125
+ const TEXT_ENCODER_THRESHOLD = 50;
126
+ function utf8EncodeTE(str, output, outputOffset) {
127
+ sharedTextEncoder.encodeInto(str, output.subarray(outputOffset));
128
+ }
129
+ function utf8Encode(str, output, outputOffset) {
130
+ if (str.length > TEXT_ENCODER_THRESHOLD) {
131
+ utf8EncodeTE(str, output, outputOffset);
132
+ }
133
+ else {
134
+ utf8EncodeJs(str, output, outputOffset);
135
+ }
136
+ }
137
+ const CHUNK_SIZE = 4096;
138
+ function utf8DecodeJs(bytes, inputOffset, byteLength) {
139
+ let offset = inputOffset;
140
+ const end = offset + byteLength;
141
+ const units = [];
142
+ let result = "";
143
+ while (offset < end) {
144
+ const byte1 = bytes[offset++];
145
+ if ((byte1 & 0x80) === 0) {
146
+ // 1 byte
147
+ units.push(byte1);
148
+ }
149
+ else if ((byte1 & 0xe0) === 0xc0) {
150
+ // 2 bytes
151
+ const byte2 = bytes[offset++] & 0x3f;
152
+ units.push(((byte1 & 0x1f) << 6) | byte2);
153
+ }
154
+ else if ((byte1 & 0xf0) === 0xe0) {
155
+ // 3 bytes
156
+ const byte2 = bytes[offset++] & 0x3f;
157
+ const byte3 = bytes[offset++] & 0x3f;
158
+ units.push(((byte1 & 0x1f) << 12) | (byte2 << 6) | byte3);
159
+ }
160
+ else if ((byte1 & 0xf8) === 0xf0) {
161
+ // 4 bytes
162
+ const byte2 = bytes[offset++] & 0x3f;
163
+ const byte3 = bytes[offset++] & 0x3f;
164
+ const byte4 = bytes[offset++] & 0x3f;
165
+ let unit = ((byte1 & 0x07) << 0x12) | (byte2 << 0x0c) | (byte3 << 0x06) | byte4;
166
+ if (unit > 0xffff) {
167
+ unit -= 0x10000;
168
+ units.push(((unit >>> 10) & 0x3ff) | 0xd800);
169
+ unit = 0xdc00 | (unit & 0x3ff);
170
+ }
171
+ units.push(unit);
172
+ }
173
+ else {
174
+ units.push(byte1);
175
+ }
176
+ if (units.length >= CHUNK_SIZE) {
177
+ result += String.fromCharCode(...units);
178
+ units.length = 0;
179
+ }
180
+ }
181
+ if (units.length > 0) {
182
+ result += String.fromCharCode(...units);
183
+ }
184
+ return result;
185
+ }
186
+ const sharedTextDecoder = new TextDecoder();
187
+ // This threshold should be determined by benchmarking, which might vary in engines and input data.
188
+ // Run `npx ts-node benchmark/decode-string.ts` for details.
189
+ const TEXT_DECODER_THRESHOLD = 200;
190
+ function utf8DecodeTD(bytes, inputOffset, byteLength) {
191
+ const stringBytes = bytes.subarray(inputOffset, inputOffset + byteLength);
192
+ return sharedTextDecoder.decode(stringBytes);
193
+ }
194
+ function utf8Decode(bytes, inputOffset, byteLength) {
195
+ if (byteLength > TEXT_DECODER_THRESHOLD) {
196
+ return utf8DecodeTD(bytes, inputOffset, byteLength);
197
+ }
198
+ else {
199
+ return utf8DecodeJs(bytes, inputOffset, byteLength);
200
+ }
201
+ }
202
+
203
+ /**
204
+ * ExtData is used to handle Extension Types that are not registered to ExtensionCodec.
205
+ */
206
+ class ExtData {
207
+ constructor(type, data) {
208
+ this.type = type;
209
+ this.data = data;
210
+ }
211
+ }
212
+
213
+ class DecodeError extends Error {
214
+ constructor(message) {
215
+ super(message);
216
+ // fix the prototype chain in a cross-platform way
217
+ const proto = Object.create(DecodeError.prototype);
218
+ Object.setPrototypeOf(this, proto);
219
+ Object.defineProperty(this, "name", {
220
+ configurable: true,
221
+ enumerable: false,
222
+ value: DecodeError.name,
223
+ });
224
+ }
225
+ }
226
+
227
+ // Integer Utility
228
+ const UINT32_MAX = 4294967295;
229
+ // DataView extension to handle int64 / uint64,
230
+ // where the actual range is 53-bits integer (a.k.a. safe integer)
231
+ function setUint64(view, offset, value) {
232
+ const high = value / 4294967296;
233
+ const low = value; // high bits are truncated by DataView
234
+ view.setUint32(offset, high);
235
+ view.setUint32(offset + 4, low);
236
+ }
237
+ function setInt64(view, offset, value) {
238
+ const high = Math.floor(value / 4294967296);
239
+ const low = value; // high bits are truncated by DataView
240
+ view.setUint32(offset, high);
241
+ view.setUint32(offset + 4, low);
242
+ }
243
+ function getInt64(view, offset) {
244
+ const high = view.getInt32(offset);
245
+ const low = view.getUint32(offset + 4);
246
+ return high * 4294967296 + low;
247
+ }
248
+ function getUint64(view, offset) {
249
+ const high = view.getUint32(offset);
250
+ const low = view.getUint32(offset + 4);
251
+ return high * 4294967296 + low;
252
+ }
253
+
254
+ // https://github.com/msgpack/msgpack/blob/master/spec.md#timestamp-extension-type
255
+ const EXT_TIMESTAMP = -1;
256
+ const TIMESTAMP32_MAX_SEC = 0x100000000 - 1; // 32-bit unsigned int
257
+ const TIMESTAMP64_MAX_SEC = 0x400000000 - 1; // 34-bit unsigned int
258
+ function encodeTimeSpecToTimestamp({ sec, nsec }) {
259
+ if (sec >= 0 && nsec >= 0 && sec <= TIMESTAMP64_MAX_SEC) {
260
+ // Here sec >= 0 && nsec >= 0
261
+ if (nsec === 0 && sec <= TIMESTAMP32_MAX_SEC) {
262
+ // timestamp 32 = { sec32 (unsigned) }
263
+ const rv = new Uint8Array(4);
264
+ const view = new DataView(rv.buffer);
265
+ view.setUint32(0, sec);
266
+ return rv;
267
+ }
268
+ else {
269
+ // timestamp 64 = { nsec30 (unsigned), sec34 (unsigned) }
270
+ const secHigh = sec / 0x100000000;
271
+ const secLow = sec & 0xffffffff;
272
+ const rv = new Uint8Array(8);
273
+ const view = new DataView(rv.buffer);
274
+ // nsec30 | secHigh2
275
+ view.setUint32(0, (nsec << 2) | (secHigh & 0x3));
276
+ // secLow32
277
+ view.setUint32(4, secLow);
278
+ return rv;
279
+ }
280
+ }
281
+ else {
282
+ // timestamp 96 = { nsec32 (unsigned), sec64 (signed) }
283
+ const rv = new Uint8Array(12);
284
+ const view = new DataView(rv.buffer);
285
+ view.setUint32(0, nsec);
286
+ setInt64(view, 4, sec);
287
+ return rv;
288
+ }
289
+ }
290
+ function encodeDateToTimeSpec(date) {
291
+ const msec = date.getTime();
292
+ const sec = Math.floor(msec / 1e3);
293
+ const nsec = (msec - sec * 1e3) * 1e6;
294
+ // Normalizes { sec, nsec } to ensure nsec is unsigned.
295
+ const nsecInSec = Math.floor(nsec / 1e9);
296
+ return {
297
+ sec: sec + nsecInSec,
298
+ nsec: nsec - nsecInSec * 1e9,
299
+ };
300
+ }
301
+ function encodeTimestampExtension(object) {
302
+ if (object instanceof Date) {
303
+ const timeSpec = encodeDateToTimeSpec(object);
304
+ return encodeTimeSpecToTimestamp(timeSpec);
305
+ }
306
+ else {
307
+ return null;
308
+ }
309
+ }
310
+ function decodeTimestampToTimeSpec(data) {
311
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
312
+ // data may be 32, 64, or 96 bits
313
+ switch (data.byteLength) {
314
+ case 4: {
315
+ // timestamp 32 = { sec32 }
316
+ const sec = view.getUint32(0);
317
+ const nsec = 0;
318
+ return { sec, nsec };
319
+ }
320
+ case 8: {
321
+ // timestamp 64 = { nsec30, sec34 }
322
+ const nsec30AndSecHigh2 = view.getUint32(0);
323
+ const secLow32 = view.getUint32(4);
324
+ const sec = (nsec30AndSecHigh2 & 0x3) * 0x100000000 + secLow32;
325
+ const nsec = nsec30AndSecHigh2 >>> 2;
326
+ return { sec, nsec };
327
+ }
328
+ case 12: {
329
+ // timestamp 96 = { nsec32 (unsigned), sec64 (signed) }
330
+ const sec = getInt64(view, 4);
331
+ const nsec = view.getUint32(0);
332
+ return { sec, nsec };
333
+ }
334
+ default:
335
+ throw new DecodeError(`Unrecognized data size for timestamp (expected 4, 8, or 12): ${data.length}`);
336
+ }
337
+ }
338
+ function decodeTimestampExtension(data) {
339
+ const timeSpec = decodeTimestampToTimeSpec(data);
340
+ return new Date(timeSpec.sec * 1e3 + timeSpec.nsec / 1e6);
341
+ }
342
+ const timestampExtension = {
343
+ type: EXT_TIMESTAMP,
344
+ encode: encodeTimestampExtension,
345
+ decode: decodeTimestampExtension,
346
+ };
347
+
348
+ // ExtensionCodec to handle MessagePack extensions
349
+ class ExtensionCodec {
350
+ constructor() {
351
+ // built-in extensions
352
+ this.builtInEncoders = [];
353
+ this.builtInDecoders = [];
354
+ // custom extensions
355
+ this.encoders = [];
356
+ this.decoders = [];
357
+ this.register(timestampExtension);
358
+ }
359
+ register({ type, encode, decode, }) {
360
+ if (type >= 0) {
361
+ // custom extensions
362
+ this.encoders[type] = encode;
363
+ this.decoders[type] = decode;
364
+ }
365
+ else {
366
+ // built-in extensions
367
+ const index = -1 - type;
368
+ this.builtInEncoders[index] = encode;
369
+ this.builtInDecoders[index] = decode;
370
+ }
371
+ }
372
+ tryToEncode(object, context) {
373
+ // built-in extensions
374
+ for (let i = 0; i < this.builtInEncoders.length; i++) {
375
+ const encodeExt = this.builtInEncoders[i];
376
+ if (encodeExt != null) {
377
+ const data = encodeExt(object, context);
378
+ if (data != null) {
379
+ const type = -1 - i;
380
+ return new ExtData(type, data);
381
+ }
382
+ }
383
+ }
384
+ // custom extensions
385
+ for (let i = 0; i < this.encoders.length; i++) {
386
+ const encodeExt = this.encoders[i];
387
+ if (encodeExt != null) {
388
+ const data = encodeExt(object, context);
389
+ if (data != null) {
390
+ const type = i;
391
+ return new ExtData(type, data);
392
+ }
393
+ }
394
+ }
395
+ if (object instanceof ExtData) {
396
+ // to keep ExtData as is
397
+ return object;
398
+ }
399
+ return null;
400
+ }
401
+ decode(data, type, context) {
402
+ const decodeExt = type < 0 ? this.builtInDecoders[-1 - type] : this.decoders[type];
403
+ if (decodeExt) {
404
+ return decodeExt(data, type, context);
405
+ }
406
+ else {
407
+ // decode() does not fail, returns ExtData instead.
408
+ return new ExtData(type, data);
409
+ }
410
+ }
411
+ }
412
+ ExtensionCodec.defaultCodec = new ExtensionCodec();
413
+
414
+ function isArrayBufferLike(buffer) {
415
+ return (buffer instanceof ArrayBuffer || (typeof SharedArrayBuffer !== "undefined" && buffer instanceof SharedArrayBuffer));
416
+ }
417
+ function ensureUint8Array(buffer) {
418
+ if (buffer instanceof Uint8Array) {
419
+ return buffer;
420
+ }
421
+ else if (ArrayBuffer.isView(buffer)) {
422
+ return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
423
+ }
424
+ else if (isArrayBufferLike(buffer)) {
425
+ return new Uint8Array(buffer);
426
+ }
427
+ else {
428
+ // ArrayLike<number>
429
+ return Uint8Array.from(buffer);
430
+ }
431
+ }
432
+
433
+ const DEFAULT_MAX_DEPTH = 100;
434
+ const DEFAULT_INITIAL_BUFFER_SIZE = 2048;
435
+ class Encoder {
436
+ constructor(options) {
437
+ this.entered = false;
438
+ this.extensionCodec = options?.extensionCodec ?? ExtensionCodec.defaultCodec;
439
+ this.context = options?.context; // needs a type assertion because EncoderOptions has no context property when ContextType is undefined
440
+ this.useBigInt64 = options?.useBigInt64 ?? false;
441
+ this.maxDepth = options?.maxDepth ?? DEFAULT_MAX_DEPTH;
442
+ this.initialBufferSize = options?.initialBufferSize ?? DEFAULT_INITIAL_BUFFER_SIZE;
443
+ this.sortKeys = options?.sortKeys ?? false;
444
+ this.forceFloat32 = options?.forceFloat32 ?? false;
445
+ this.ignoreUndefined = options?.ignoreUndefined ?? false;
446
+ this.forceIntegerToFloat = options?.forceIntegerToFloat ?? false;
447
+ this.pos = 0;
448
+ this.view = new DataView(new ArrayBuffer(this.initialBufferSize));
449
+ this.bytes = new Uint8Array(this.view.buffer);
450
+ }
451
+ clone() {
452
+ // Because of slightly special argument `context`,
453
+ // type assertion is needed.
454
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
455
+ return new Encoder({
456
+ extensionCodec: this.extensionCodec,
457
+ context: this.context,
458
+ useBigInt64: this.useBigInt64,
459
+ maxDepth: this.maxDepth,
460
+ initialBufferSize: this.initialBufferSize,
461
+ sortKeys: this.sortKeys,
462
+ forceFloat32: this.forceFloat32,
463
+ ignoreUndefined: this.ignoreUndefined,
464
+ forceIntegerToFloat: this.forceIntegerToFloat,
465
+ });
466
+ }
467
+ reinitializeState() {
468
+ this.pos = 0;
469
+ }
470
+ /**
471
+ * This is almost equivalent to {@link Encoder#encode}, but it returns an reference of the encoder's internal buffer and thus much faster than {@link Encoder#encode}.
472
+ *
473
+ * @returns Encodes the object and returns a shared reference the encoder's internal buffer.
474
+ */
475
+ encodeSharedRef(object) {
476
+ if (this.entered) {
477
+ const instance = this.clone();
478
+ return instance.encodeSharedRef(object);
479
+ }
480
+ try {
481
+ this.entered = true;
482
+ this.reinitializeState();
483
+ this.doEncode(object, 1);
484
+ return this.bytes.subarray(0, this.pos);
485
+ }
486
+ finally {
487
+ this.entered = false;
488
+ }
489
+ }
490
+ /**
491
+ * @returns Encodes the object and returns a copy of the encoder's internal buffer.
492
+ */
493
+ encode(object) {
494
+ if (this.entered) {
495
+ const instance = this.clone();
496
+ return instance.encode(object);
497
+ }
498
+ try {
499
+ this.entered = true;
500
+ this.reinitializeState();
501
+ this.doEncode(object, 1);
502
+ return this.bytes.slice(0, this.pos);
503
+ }
504
+ finally {
505
+ this.entered = false;
506
+ }
507
+ }
508
+ doEncode(object, depth) {
509
+ if (depth > this.maxDepth) {
510
+ throw new Error(`Too deep objects in depth ${depth}`);
511
+ }
512
+ if (object == null) {
513
+ this.encodeNil();
514
+ }
515
+ else if (typeof object === "boolean") {
516
+ this.encodeBoolean(object);
517
+ }
518
+ else if (typeof object === "number") {
519
+ if (!this.forceIntegerToFloat) {
520
+ this.encodeNumber(object);
521
+ }
522
+ else {
523
+ this.encodeNumberAsFloat(object);
524
+ }
525
+ }
526
+ else if (typeof object === "string") {
527
+ this.encodeString(object);
528
+ }
529
+ else if (this.useBigInt64 && typeof object === "bigint") {
530
+ this.encodeBigInt64(object);
531
+ }
532
+ else {
533
+ this.encodeObject(object, depth);
534
+ }
535
+ }
536
+ ensureBufferSizeToWrite(sizeToWrite) {
537
+ const requiredSize = this.pos + sizeToWrite;
538
+ if (this.view.byteLength < requiredSize) {
539
+ this.resizeBuffer(requiredSize * 2);
540
+ }
541
+ }
542
+ resizeBuffer(newSize) {
543
+ const newBuffer = new ArrayBuffer(newSize);
544
+ const newBytes = new Uint8Array(newBuffer);
545
+ const newView = new DataView(newBuffer);
546
+ newBytes.set(this.bytes);
547
+ this.view = newView;
548
+ this.bytes = newBytes;
549
+ }
550
+ encodeNil() {
551
+ this.writeU8(0xc0);
552
+ }
553
+ encodeBoolean(object) {
554
+ if (object === false) {
555
+ this.writeU8(0xc2);
556
+ }
557
+ else {
558
+ this.writeU8(0xc3);
559
+ }
560
+ }
561
+ encodeNumber(object) {
562
+ if (!this.forceIntegerToFloat && Number.isSafeInteger(object)) {
563
+ if (object >= 0) {
564
+ if (object < 0x80) {
565
+ // positive fixint
566
+ this.writeU8(object);
567
+ }
568
+ else if (object < 0x100) {
569
+ // uint 8
570
+ this.writeU8(0xcc);
571
+ this.writeU8(object);
572
+ }
573
+ else if (object < 0x10000) {
574
+ // uint 16
575
+ this.writeU8(0xcd);
576
+ this.writeU16(object);
577
+ }
578
+ else if (object < 0x100000000) {
579
+ // uint 32
580
+ this.writeU8(0xce);
581
+ this.writeU32(object);
582
+ }
583
+ else if (!this.useBigInt64) {
584
+ // uint 64
585
+ this.writeU8(0xcf);
586
+ this.writeU64(object);
587
+ }
588
+ else {
589
+ this.encodeNumberAsFloat(object);
590
+ }
591
+ }
592
+ else {
593
+ if (object >= -32) {
594
+ // negative fixint
595
+ this.writeU8(0xe0 | (object + 0x20));
596
+ }
597
+ else if (object >= -128) {
598
+ // int 8
599
+ this.writeU8(0xd0);
600
+ this.writeI8(object);
601
+ }
602
+ else if (object >= -32768) {
603
+ // int 16
604
+ this.writeU8(0xd1);
605
+ this.writeI16(object);
606
+ }
607
+ else if (object >= -2147483648) {
608
+ // int 32
609
+ this.writeU8(0xd2);
610
+ this.writeI32(object);
611
+ }
612
+ else if (!this.useBigInt64) {
613
+ // int 64
614
+ this.writeU8(0xd3);
615
+ this.writeI64(object);
616
+ }
617
+ else {
618
+ this.encodeNumberAsFloat(object);
619
+ }
620
+ }
621
+ }
622
+ else {
623
+ this.encodeNumberAsFloat(object);
624
+ }
625
+ }
626
+ encodeNumberAsFloat(object) {
627
+ if (this.forceFloat32) {
628
+ // float 32
629
+ this.writeU8(0xca);
630
+ this.writeF32(object);
631
+ }
632
+ else {
633
+ // float 64
634
+ this.writeU8(0xcb);
635
+ this.writeF64(object);
636
+ }
637
+ }
638
+ encodeBigInt64(object) {
639
+ if (object >= BigInt(0)) {
640
+ // uint 64
641
+ this.writeU8(0xcf);
642
+ this.writeBigUint64(object);
643
+ }
644
+ else {
645
+ // int 64
646
+ this.writeU8(0xd3);
647
+ this.writeBigInt64(object);
648
+ }
649
+ }
650
+ writeStringHeader(byteLength) {
651
+ if (byteLength < 32) {
652
+ // fixstr
653
+ this.writeU8(0xa0 + byteLength);
654
+ }
655
+ else if (byteLength < 0x100) {
656
+ // str 8
657
+ this.writeU8(0xd9);
658
+ this.writeU8(byteLength);
659
+ }
660
+ else if (byteLength < 0x10000) {
661
+ // str 16
662
+ this.writeU8(0xda);
663
+ this.writeU16(byteLength);
664
+ }
665
+ else if (byteLength < 0x100000000) {
666
+ // str 32
667
+ this.writeU8(0xdb);
668
+ this.writeU32(byteLength);
669
+ }
670
+ else {
671
+ throw new Error(`Too long string: ${byteLength} bytes in UTF-8`);
672
+ }
673
+ }
674
+ encodeString(object) {
675
+ const maxHeaderSize = 1 + 4;
676
+ const byteLength = utf8Count(object);
677
+ this.ensureBufferSizeToWrite(maxHeaderSize + byteLength);
678
+ this.writeStringHeader(byteLength);
679
+ utf8Encode(object, this.bytes, this.pos);
680
+ this.pos += byteLength;
681
+ }
682
+ encodeObject(object, depth) {
683
+ // try to encode objects with custom codec first of non-primitives
684
+ const ext = this.extensionCodec.tryToEncode(object, this.context);
685
+ if (ext != null) {
686
+ this.encodeExtension(ext);
687
+ }
688
+ else if (Array.isArray(object)) {
689
+ this.encodeArray(object, depth);
690
+ }
691
+ else if (ArrayBuffer.isView(object)) {
692
+ this.encodeBinary(object);
693
+ }
694
+ else if (typeof object === "object") {
695
+ this.encodeMap(object, depth);
696
+ }
697
+ else {
698
+ // symbol, function and other special object come here unless extensionCodec handles them.
699
+ throw new Error(`Unrecognized object: ${Object.prototype.toString.apply(object)}`);
700
+ }
701
+ }
702
+ encodeBinary(object) {
703
+ const size = object.byteLength;
704
+ if (size < 0x100) {
705
+ // bin 8
706
+ this.writeU8(0xc4);
707
+ this.writeU8(size);
708
+ }
709
+ else if (size < 0x10000) {
710
+ // bin 16
711
+ this.writeU8(0xc5);
712
+ this.writeU16(size);
713
+ }
714
+ else if (size < 0x100000000) {
715
+ // bin 32
716
+ this.writeU8(0xc6);
717
+ this.writeU32(size);
718
+ }
719
+ else {
720
+ throw new Error(`Too large binary: ${size}`);
721
+ }
722
+ const bytes = ensureUint8Array(object);
723
+ this.writeU8a(bytes);
724
+ }
725
+ encodeArray(object, depth) {
726
+ const size = object.length;
727
+ if (size < 16) {
728
+ // fixarray
729
+ this.writeU8(0x90 + size);
730
+ }
731
+ else if (size < 0x10000) {
732
+ // array 16
733
+ this.writeU8(0xdc);
734
+ this.writeU16(size);
735
+ }
736
+ else if (size < 0x100000000) {
737
+ // array 32
738
+ this.writeU8(0xdd);
739
+ this.writeU32(size);
740
+ }
741
+ else {
742
+ throw new Error(`Too large array: ${size}`);
743
+ }
744
+ for (const item of object) {
745
+ this.doEncode(item, depth + 1);
746
+ }
747
+ }
748
+ countWithoutUndefined(object, keys) {
749
+ let count = 0;
750
+ for (const key of keys) {
751
+ if (object[key] !== undefined) {
752
+ count++;
753
+ }
754
+ }
755
+ return count;
756
+ }
757
+ encodeMap(object, depth) {
758
+ const keys = Object.keys(object);
759
+ if (this.sortKeys) {
760
+ keys.sort();
761
+ }
762
+ const size = this.ignoreUndefined ? this.countWithoutUndefined(object, keys) : keys.length;
763
+ if (size < 16) {
764
+ // fixmap
765
+ this.writeU8(0x80 + size);
766
+ }
767
+ else if (size < 0x10000) {
768
+ // map 16
769
+ this.writeU8(0xde);
770
+ this.writeU16(size);
771
+ }
772
+ else if (size < 0x100000000) {
773
+ // map 32
774
+ this.writeU8(0xdf);
775
+ this.writeU32(size);
776
+ }
777
+ else {
778
+ throw new Error(`Too large map object: ${size}`);
779
+ }
780
+ for (const key of keys) {
781
+ const value = object[key];
782
+ if (!(this.ignoreUndefined && value === undefined)) {
783
+ this.encodeString(key);
784
+ this.doEncode(value, depth + 1);
785
+ }
786
+ }
787
+ }
788
+ encodeExtension(ext) {
789
+ if (typeof ext.data === "function") {
790
+ const data = ext.data(this.pos + 6);
791
+ const size = data.length;
792
+ if (size >= 0x100000000) {
793
+ throw new Error(`Too large extension object: ${size}`);
794
+ }
795
+ this.writeU8(0xc9);
796
+ this.writeU32(size);
797
+ this.writeI8(ext.type);
798
+ this.writeU8a(data);
799
+ return;
800
+ }
801
+ const size = ext.data.length;
802
+ if (size === 1) {
803
+ // fixext 1
804
+ this.writeU8(0xd4);
805
+ }
806
+ else if (size === 2) {
807
+ // fixext 2
808
+ this.writeU8(0xd5);
809
+ }
810
+ else if (size === 4) {
811
+ // fixext 4
812
+ this.writeU8(0xd6);
813
+ }
814
+ else if (size === 8) {
815
+ // fixext 8
816
+ this.writeU8(0xd7);
817
+ }
818
+ else if (size === 16) {
819
+ // fixext 16
820
+ this.writeU8(0xd8);
821
+ }
822
+ else if (size < 0x100) {
823
+ // ext 8
824
+ this.writeU8(0xc7);
825
+ this.writeU8(size);
826
+ }
827
+ else if (size < 0x10000) {
828
+ // ext 16
829
+ this.writeU8(0xc8);
830
+ this.writeU16(size);
831
+ }
832
+ else if (size < 0x100000000) {
833
+ // ext 32
834
+ this.writeU8(0xc9);
835
+ this.writeU32(size);
836
+ }
837
+ else {
838
+ throw new Error(`Too large extension object: ${size}`);
839
+ }
840
+ this.writeI8(ext.type);
841
+ this.writeU8a(ext.data);
842
+ }
843
+ writeU8(value) {
844
+ this.ensureBufferSizeToWrite(1);
845
+ this.view.setUint8(this.pos, value);
846
+ this.pos++;
847
+ }
848
+ writeU8a(values) {
849
+ const size = values.length;
850
+ this.ensureBufferSizeToWrite(size);
851
+ this.bytes.set(values, this.pos);
852
+ this.pos += size;
853
+ }
854
+ writeI8(value) {
855
+ this.ensureBufferSizeToWrite(1);
856
+ this.view.setInt8(this.pos, value);
857
+ this.pos++;
858
+ }
859
+ writeU16(value) {
860
+ this.ensureBufferSizeToWrite(2);
861
+ this.view.setUint16(this.pos, value);
862
+ this.pos += 2;
863
+ }
864
+ writeI16(value) {
865
+ this.ensureBufferSizeToWrite(2);
866
+ this.view.setInt16(this.pos, value);
867
+ this.pos += 2;
868
+ }
869
+ writeU32(value) {
870
+ this.ensureBufferSizeToWrite(4);
871
+ this.view.setUint32(this.pos, value);
872
+ this.pos += 4;
873
+ }
874
+ writeI32(value) {
875
+ this.ensureBufferSizeToWrite(4);
876
+ this.view.setInt32(this.pos, value);
877
+ this.pos += 4;
878
+ }
879
+ writeF32(value) {
880
+ this.ensureBufferSizeToWrite(4);
881
+ this.view.setFloat32(this.pos, value);
882
+ this.pos += 4;
883
+ }
884
+ writeF64(value) {
885
+ this.ensureBufferSizeToWrite(8);
886
+ this.view.setFloat64(this.pos, value);
887
+ this.pos += 8;
888
+ }
889
+ writeU64(value) {
890
+ this.ensureBufferSizeToWrite(8);
891
+ setUint64(this.view, this.pos, value);
892
+ this.pos += 8;
893
+ }
894
+ writeI64(value) {
895
+ this.ensureBufferSizeToWrite(8);
896
+ setInt64(this.view, this.pos, value);
897
+ this.pos += 8;
898
+ }
899
+ writeBigUint64(value) {
900
+ this.ensureBufferSizeToWrite(8);
901
+ this.view.setBigUint64(this.pos, value);
902
+ this.pos += 8;
903
+ }
904
+ writeBigInt64(value) {
905
+ this.ensureBufferSizeToWrite(8);
906
+ this.view.setBigInt64(this.pos, value);
907
+ this.pos += 8;
908
+ }
909
+ }
910
+
911
+ /**
912
+ * It encodes `value` in the MessagePack format and
913
+ * returns a byte buffer.
914
+ *
915
+ * The returned buffer is a slice of a larger `ArrayBuffer`, so you have to use its `#byteOffset` and `#byteLength` in order to convert it to another typed arrays including NodeJS `Buffer`.
916
+ */
917
+ function encode(value, options) {
918
+ const encoder = new Encoder(options);
919
+ return encoder.encodeSharedRef(value);
920
+ }
921
+
922
+ function prettyByte(byte) {
923
+ return `${byte < 0 ? "-" : ""}0x${Math.abs(byte).toString(16).padStart(2, "0")}`;
924
+ }
925
+
926
+ const DEFAULT_MAX_KEY_LENGTH = 16;
927
+ const DEFAULT_MAX_LENGTH_PER_KEY = 16;
928
+ class CachedKeyDecoder {
929
+ constructor(maxKeyLength = DEFAULT_MAX_KEY_LENGTH, maxLengthPerKey = DEFAULT_MAX_LENGTH_PER_KEY) {
930
+ this.hit = 0;
931
+ this.miss = 0;
932
+ this.maxKeyLength = maxKeyLength;
933
+ this.maxLengthPerKey = maxLengthPerKey;
934
+ // avoid `new Array(N)`, which makes a sparse array,
935
+ // because a sparse array is typically slower than a non-sparse array.
936
+ this.caches = [];
937
+ for (let i = 0; i < this.maxKeyLength; i++) {
938
+ this.caches.push([]);
939
+ }
940
+ }
941
+ canBeCached(byteLength) {
942
+ return byteLength > 0 && byteLength <= this.maxKeyLength;
943
+ }
944
+ find(bytes, inputOffset, byteLength) {
945
+ const records = this.caches[byteLength - 1];
946
+ FIND_CHUNK: for (const record of records) {
947
+ const recordBytes = record.bytes;
948
+ for (let j = 0; j < byteLength; j++) {
949
+ if (recordBytes[j] !== bytes[inputOffset + j]) {
950
+ continue FIND_CHUNK;
951
+ }
952
+ }
953
+ return record.str;
954
+ }
955
+ return null;
956
+ }
957
+ store(bytes, value) {
958
+ const records = this.caches[bytes.length - 1];
959
+ const record = { bytes, str: value };
960
+ if (records.length >= this.maxLengthPerKey) {
961
+ // `records` are full!
962
+ // Set `record` to an arbitrary position.
963
+ records[(Math.random() * records.length) | 0] = record;
964
+ }
965
+ else {
966
+ records.push(record);
967
+ }
968
+ }
969
+ decode(bytes, inputOffset, byteLength) {
970
+ const cachedValue = this.find(bytes, inputOffset, byteLength);
971
+ if (cachedValue != null) {
972
+ this.hit++;
973
+ return cachedValue;
974
+ }
975
+ this.miss++;
976
+ const str = utf8DecodeJs(bytes, inputOffset, byteLength);
977
+ // Ensure to copy a slice of bytes because the bytes may be a NodeJS Buffer and Buffer#slice() returns a reference to its internal ArrayBuffer.
978
+ const slicedCopyOfBytes = Uint8Array.prototype.slice.call(bytes, inputOffset, inputOffset + byteLength);
979
+ this.store(slicedCopyOfBytes, str);
980
+ return str;
981
+ }
982
+ }
983
+
984
+ const STATE_ARRAY = "array";
985
+ const STATE_MAP_KEY = "map_key";
986
+ const STATE_MAP_VALUE = "map_value";
987
+ const mapKeyConverter = (key) => {
988
+ if (typeof key === "string" || typeof key === "number") {
989
+ return key;
990
+ }
991
+ throw new DecodeError("The type of key must be string or number but " + typeof key);
992
+ };
993
+ class StackPool {
994
+ constructor() {
995
+ this.stack = [];
996
+ this.stackHeadPosition = -1;
997
+ }
998
+ get length() {
999
+ return this.stackHeadPosition + 1;
1000
+ }
1001
+ top() {
1002
+ return this.stack[this.stackHeadPosition];
1003
+ }
1004
+ pushArrayState(size) {
1005
+ const state = this.getUninitializedStateFromPool();
1006
+ state.type = STATE_ARRAY;
1007
+ state.position = 0;
1008
+ state.size = size;
1009
+ state.array = new Array(size);
1010
+ }
1011
+ pushMapState(size) {
1012
+ const state = this.getUninitializedStateFromPool();
1013
+ state.type = STATE_MAP_KEY;
1014
+ state.readCount = 0;
1015
+ state.size = size;
1016
+ state.map = {};
1017
+ }
1018
+ getUninitializedStateFromPool() {
1019
+ this.stackHeadPosition++;
1020
+ if (this.stackHeadPosition === this.stack.length) {
1021
+ const partialState = {
1022
+ type: undefined,
1023
+ size: 0,
1024
+ array: undefined,
1025
+ position: 0,
1026
+ readCount: 0,
1027
+ map: undefined,
1028
+ key: null,
1029
+ };
1030
+ this.stack.push(partialState);
1031
+ }
1032
+ return this.stack[this.stackHeadPosition];
1033
+ }
1034
+ release(state) {
1035
+ const topStackState = this.stack[this.stackHeadPosition];
1036
+ if (topStackState !== state) {
1037
+ throw new Error("Invalid stack state. Released state is not on top of the stack.");
1038
+ }
1039
+ if (state.type === STATE_ARRAY) {
1040
+ const partialState = state;
1041
+ partialState.size = 0;
1042
+ partialState.array = undefined;
1043
+ partialState.position = 0;
1044
+ partialState.type = undefined;
1045
+ }
1046
+ if (state.type === STATE_MAP_KEY || state.type === STATE_MAP_VALUE) {
1047
+ const partialState = state;
1048
+ partialState.size = 0;
1049
+ partialState.map = undefined;
1050
+ partialState.readCount = 0;
1051
+ partialState.type = undefined;
1052
+ }
1053
+ this.stackHeadPosition--;
1054
+ }
1055
+ reset() {
1056
+ this.stack.length = 0;
1057
+ this.stackHeadPosition = -1;
1058
+ }
1059
+ }
1060
+ const HEAD_BYTE_REQUIRED = -1;
1061
+ const EMPTY_VIEW = new DataView(new ArrayBuffer(0));
1062
+ const EMPTY_BYTES = new Uint8Array(EMPTY_VIEW.buffer);
1063
+ try {
1064
+ // IE11: The spec says it should throw RangeError,
1065
+ // IE11: but in IE11 it throws TypeError.
1066
+ EMPTY_VIEW.getInt8(0);
1067
+ }
1068
+ catch (e) {
1069
+ if (!(e instanceof RangeError)) {
1070
+ throw new Error("This module is not supported in the current JavaScript engine because DataView does not throw RangeError on out-of-bounds access");
1071
+ }
1072
+ }
1073
+ const MORE_DATA = new RangeError("Insufficient data");
1074
+ const sharedCachedKeyDecoder = new CachedKeyDecoder();
1075
+ class Decoder {
1076
+ constructor(options) {
1077
+ this.totalPos = 0;
1078
+ this.pos = 0;
1079
+ this.view = EMPTY_VIEW;
1080
+ this.bytes = EMPTY_BYTES;
1081
+ this.headByte = HEAD_BYTE_REQUIRED;
1082
+ this.stack = new StackPool();
1083
+ this.entered = false;
1084
+ this.extensionCodec = options?.extensionCodec ?? ExtensionCodec.defaultCodec;
1085
+ this.context = options?.context; // needs a type assertion because EncoderOptions has no context property when ContextType is undefined
1086
+ this.useBigInt64 = options?.useBigInt64 ?? false;
1087
+ this.rawStrings = options?.rawStrings ?? false;
1088
+ this.maxStrLength = options?.maxStrLength ?? UINT32_MAX;
1089
+ this.maxBinLength = options?.maxBinLength ?? UINT32_MAX;
1090
+ this.maxArrayLength = options?.maxArrayLength ?? UINT32_MAX;
1091
+ this.maxMapLength = options?.maxMapLength ?? UINT32_MAX;
1092
+ this.maxExtLength = options?.maxExtLength ?? UINT32_MAX;
1093
+ this.keyDecoder = options?.keyDecoder !== undefined ? options.keyDecoder : sharedCachedKeyDecoder;
1094
+ this.mapKeyConverter = options?.mapKeyConverter ?? mapKeyConverter;
1095
+ }
1096
+ clone() {
1097
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
1098
+ return new Decoder({
1099
+ extensionCodec: this.extensionCodec,
1100
+ context: this.context,
1101
+ useBigInt64: this.useBigInt64,
1102
+ rawStrings: this.rawStrings,
1103
+ maxStrLength: this.maxStrLength,
1104
+ maxBinLength: this.maxBinLength,
1105
+ maxArrayLength: this.maxArrayLength,
1106
+ maxMapLength: this.maxMapLength,
1107
+ maxExtLength: this.maxExtLength,
1108
+ keyDecoder: this.keyDecoder,
1109
+ });
1110
+ }
1111
+ reinitializeState() {
1112
+ this.totalPos = 0;
1113
+ this.headByte = HEAD_BYTE_REQUIRED;
1114
+ this.stack.reset();
1115
+ // view, bytes, and pos will be re-initialized in setBuffer()
1116
+ }
1117
+ setBuffer(buffer) {
1118
+ const bytes = ensureUint8Array(buffer);
1119
+ this.bytes = bytes;
1120
+ this.view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
1121
+ this.pos = 0;
1122
+ }
1123
+ appendBuffer(buffer) {
1124
+ if (this.headByte === HEAD_BYTE_REQUIRED && !this.hasRemaining(1)) {
1125
+ this.setBuffer(buffer);
1126
+ }
1127
+ else {
1128
+ const remainingData = this.bytes.subarray(this.pos);
1129
+ const newData = ensureUint8Array(buffer);
1130
+ // concat remainingData + newData
1131
+ const newBuffer = new Uint8Array(remainingData.length + newData.length);
1132
+ newBuffer.set(remainingData);
1133
+ newBuffer.set(newData, remainingData.length);
1134
+ this.setBuffer(newBuffer);
1135
+ }
1136
+ }
1137
+ hasRemaining(size) {
1138
+ return this.view.byteLength - this.pos >= size;
1139
+ }
1140
+ createExtraByteError(posToShow) {
1141
+ const { view, pos } = this;
1142
+ return new RangeError(`Extra ${view.byteLength - pos} of ${view.byteLength} byte(s) found at buffer[${posToShow}]`);
1143
+ }
1144
+ /**
1145
+ * @throws {@link DecodeError}
1146
+ * @throws {@link RangeError}
1147
+ */
1148
+ decode(buffer) {
1149
+ if (this.entered) {
1150
+ const instance = this.clone();
1151
+ return instance.decode(buffer);
1152
+ }
1153
+ try {
1154
+ this.entered = true;
1155
+ this.reinitializeState();
1156
+ this.setBuffer(buffer);
1157
+ const object = this.doDecodeSync();
1158
+ if (this.hasRemaining(1)) {
1159
+ throw this.createExtraByteError(this.pos);
1160
+ }
1161
+ return object;
1162
+ }
1163
+ finally {
1164
+ this.entered = false;
1165
+ }
1166
+ }
1167
+ *decodeMulti(buffer) {
1168
+ if (this.entered) {
1169
+ const instance = this.clone();
1170
+ yield* instance.decodeMulti(buffer);
1171
+ return;
1172
+ }
1173
+ try {
1174
+ this.entered = true;
1175
+ this.reinitializeState();
1176
+ this.setBuffer(buffer);
1177
+ while (this.hasRemaining(1)) {
1178
+ yield this.doDecodeSync();
1179
+ }
1180
+ }
1181
+ finally {
1182
+ this.entered = false;
1183
+ }
1184
+ }
1185
+ async decodeAsync(stream) {
1186
+ if (this.entered) {
1187
+ const instance = this.clone();
1188
+ return instance.decodeAsync(stream);
1189
+ }
1190
+ try {
1191
+ this.entered = true;
1192
+ let decoded = false;
1193
+ let object;
1194
+ for await (const buffer of stream) {
1195
+ if (decoded) {
1196
+ this.entered = false;
1197
+ throw this.createExtraByteError(this.totalPos);
1198
+ }
1199
+ this.appendBuffer(buffer);
1200
+ try {
1201
+ object = this.doDecodeSync();
1202
+ decoded = true;
1203
+ }
1204
+ catch (e) {
1205
+ if (!(e instanceof RangeError)) {
1206
+ throw e; // rethrow
1207
+ }
1208
+ // fallthrough
1209
+ }
1210
+ this.totalPos += this.pos;
1211
+ }
1212
+ if (decoded) {
1213
+ if (this.hasRemaining(1)) {
1214
+ throw this.createExtraByteError(this.totalPos);
1215
+ }
1216
+ return object;
1217
+ }
1218
+ const { headByte, pos, totalPos } = this;
1219
+ throw new RangeError(`Insufficient data in parsing ${prettyByte(headByte)} at ${totalPos} (${pos} in the current buffer)`);
1220
+ }
1221
+ finally {
1222
+ this.entered = false;
1223
+ }
1224
+ }
1225
+ decodeArrayStream(stream) {
1226
+ return this.decodeMultiAsync(stream, true);
1227
+ }
1228
+ decodeStream(stream) {
1229
+ return this.decodeMultiAsync(stream, false);
1230
+ }
1231
+ async *decodeMultiAsync(stream, isArray) {
1232
+ if (this.entered) {
1233
+ const instance = this.clone();
1234
+ yield* instance.decodeMultiAsync(stream, isArray);
1235
+ return;
1236
+ }
1237
+ try {
1238
+ this.entered = true;
1239
+ let isArrayHeaderRequired = isArray;
1240
+ let arrayItemsLeft = -1;
1241
+ for await (const buffer of stream) {
1242
+ if (isArray && arrayItemsLeft === 0) {
1243
+ throw this.createExtraByteError(this.totalPos);
1244
+ }
1245
+ this.appendBuffer(buffer);
1246
+ if (isArrayHeaderRequired) {
1247
+ arrayItemsLeft = this.readArraySize();
1248
+ isArrayHeaderRequired = false;
1249
+ this.complete();
1250
+ }
1251
+ try {
1252
+ while (true) {
1253
+ yield this.doDecodeSync();
1254
+ if (--arrayItemsLeft === 0) {
1255
+ break;
1256
+ }
1257
+ }
1258
+ }
1259
+ catch (e) {
1260
+ if (!(e instanceof RangeError)) {
1261
+ throw e; // rethrow
1262
+ }
1263
+ // fallthrough
1264
+ }
1265
+ this.totalPos += this.pos;
1266
+ }
1267
+ }
1268
+ finally {
1269
+ this.entered = false;
1270
+ }
1271
+ }
1272
+ doDecodeSync() {
1273
+ DECODE: while (true) {
1274
+ const headByte = this.readHeadByte();
1275
+ let object;
1276
+ if (headByte >= 0xe0) {
1277
+ // negative fixint (111x xxxx) 0xe0 - 0xff
1278
+ object = headByte - 0x100;
1279
+ }
1280
+ else if (headByte < 0xc0) {
1281
+ if (headByte < 0x80) {
1282
+ // positive fixint (0xxx xxxx) 0x00 - 0x7f
1283
+ object = headByte;
1284
+ }
1285
+ else if (headByte < 0x90) {
1286
+ // fixmap (1000 xxxx) 0x80 - 0x8f
1287
+ const size = headByte - 0x80;
1288
+ if (size !== 0) {
1289
+ this.pushMapState(size);
1290
+ this.complete();
1291
+ continue DECODE;
1292
+ }
1293
+ else {
1294
+ object = {};
1295
+ }
1296
+ }
1297
+ else if (headByte < 0xa0) {
1298
+ // fixarray (1001 xxxx) 0x90 - 0x9f
1299
+ const size = headByte - 0x90;
1300
+ if (size !== 0) {
1301
+ this.pushArrayState(size);
1302
+ this.complete();
1303
+ continue DECODE;
1304
+ }
1305
+ else {
1306
+ object = [];
1307
+ }
1308
+ }
1309
+ else {
1310
+ // fixstr (101x xxxx) 0xa0 - 0xbf
1311
+ const byteLength = headByte - 0xa0;
1312
+ object = this.decodeString(byteLength, 0);
1313
+ }
1314
+ }
1315
+ else if (headByte === 0xc0) {
1316
+ // nil
1317
+ object = null;
1318
+ }
1319
+ else if (headByte === 0xc2) {
1320
+ // false
1321
+ object = false;
1322
+ }
1323
+ else if (headByte === 0xc3) {
1324
+ // true
1325
+ object = true;
1326
+ }
1327
+ else if (headByte === 0xca) {
1328
+ // float 32
1329
+ object = this.readF32();
1330
+ }
1331
+ else if (headByte === 0xcb) {
1332
+ // float 64
1333
+ object = this.readF64();
1334
+ }
1335
+ else if (headByte === 0xcc) {
1336
+ // uint 8
1337
+ object = this.readU8();
1338
+ }
1339
+ else if (headByte === 0xcd) {
1340
+ // uint 16
1341
+ object = this.readU16();
1342
+ }
1343
+ else if (headByte === 0xce) {
1344
+ // uint 32
1345
+ object = this.readU32();
1346
+ }
1347
+ else if (headByte === 0xcf) {
1348
+ // uint 64
1349
+ if (this.useBigInt64) {
1350
+ object = this.readU64AsBigInt();
1351
+ }
1352
+ else {
1353
+ object = this.readU64();
1354
+ }
1355
+ }
1356
+ else if (headByte === 0xd0) {
1357
+ // int 8
1358
+ object = this.readI8();
1359
+ }
1360
+ else if (headByte === 0xd1) {
1361
+ // int 16
1362
+ object = this.readI16();
1363
+ }
1364
+ else if (headByte === 0xd2) {
1365
+ // int 32
1366
+ object = this.readI32();
1367
+ }
1368
+ else if (headByte === 0xd3) {
1369
+ // int 64
1370
+ if (this.useBigInt64) {
1371
+ object = this.readI64AsBigInt();
1372
+ }
1373
+ else {
1374
+ object = this.readI64();
1375
+ }
1376
+ }
1377
+ else if (headByte === 0xd9) {
1378
+ // str 8
1379
+ const byteLength = this.lookU8();
1380
+ object = this.decodeString(byteLength, 1);
1381
+ }
1382
+ else if (headByte === 0xda) {
1383
+ // str 16
1384
+ const byteLength = this.lookU16();
1385
+ object = this.decodeString(byteLength, 2);
1386
+ }
1387
+ else if (headByte === 0xdb) {
1388
+ // str 32
1389
+ const byteLength = this.lookU32();
1390
+ object = this.decodeString(byteLength, 4);
1391
+ }
1392
+ else if (headByte === 0xdc) {
1393
+ // array 16
1394
+ const size = this.readU16();
1395
+ if (size !== 0) {
1396
+ this.pushArrayState(size);
1397
+ this.complete();
1398
+ continue DECODE;
1399
+ }
1400
+ else {
1401
+ object = [];
1402
+ }
1403
+ }
1404
+ else if (headByte === 0xdd) {
1405
+ // array 32
1406
+ const size = this.readU32();
1407
+ if (size !== 0) {
1408
+ this.pushArrayState(size);
1409
+ this.complete();
1410
+ continue DECODE;
1411
+ }
1412
+ else {
1413
+ object = [];
1414
+ }
1415
+ }
1416
+ else if (headByte === 0xde) {
1417
+ // map 16
1418
+ const size = this.readU16();
1419
+ if (size !== 0) {
1420
+ this.pushMapState(size);
1421
+ this.complete();
1422
+ continue DECODE;
1423
+ }
1424
+ else {
1425
+ object = {};
1426
+ }
1427
+ }
1428
+ else if (headByte === 0xdf) {
1429
+ // map 32
1430
+ const size = this.readU32();
1431
+ if (size !== 0) {
1432
+ this.pushMapState(size);
1433
+ this.complete();
1434
+ continue DECODE;
1435
+ }
1436
+ else {
1437
+ object = {};
1438
+ }
1439
+ }
1440
+ else if (headByte === 0xc4) {
1441
+ // bin 8
1442
+ const size = this.lookU8();
1443
+ object = this.decodeBinary(size, 1);
1444
+ }
1445
+ else if (headByte === 0xc5) {
1446
+ // bin 16
1447
+ const size = this.lookU16();
1448
+ object = this.decodeBinary(size, 2);
1449
+ }
1450
+ else if (headByte === 0xc6) {
1451
+ // bin 32
1452
+ const size = this.lookU32();
1453
+ object = this.decodeBinary(size, 4);
1454
+ }
1455
+ else if (headByte === 0xd4) {
1456
+ // fixext 1
1457
+ object = this.decodeExtension(1, 0);
1458
+ }
1459
+ else if (headByte === 0xd5) {
1460
+ // fixext 2
1461
+ object = this.decodeExtension(2, 0);
1462
+ }
1463
+ else if (headByte === 0xd6) {
1464
+ // fixext 4
1465
+ object = this.decodeExtension(4, 0);
1466
+ }
1467
+ else if (headByte === 0xd7) {
1468
+ // fixext 8
1469
+ object = this.decodeExtension(8, 0);
1470
+ }
1471
+ else if (headByte === 0xd8) {
1472
+ // fixext 16
1473
+ object = this.decodeExtension(16, 0);
1474
+ }
1475
+ else if (headByte === 0xc7) {
1476
+ // ext 8
1477
+ const size = this.lookU8();
1478
+ object = this.decodeExtension(size, 1);
1479
+ }
1480
+ else if (headByte === 0xc8) {
1481
+ // ext 16
1482
+ const size = this.lookU16();
1483
+ object = this.decodeExtension(size, 2);
1484
+ }
1485
+ else if (headByte === 0xc9) {
1486
+ // ext 32
1487
+ const size = this.lookU32();
1488
+ object = this.decodeExtension(size, 4);
1489
+ }
1490
+ else {
1491
+ throw new DecodeError(`Unrecognized type byte: ${prettyByte(headByte)}`);
1492
+ }
1493
+ this.complete();
1494
+ const stack = this.stack;
1495
+ while (stack.length > 0) {
1496
+ // arrays and maps
1497
+ const state = stack.top();
1498
+ if (state.type === STATE_ARRAY) {
1499
+ state.array[state.position] = object;
1500
+ state.position++;
1501
+ if (state.position === state.size) {
1502
+ object = state.array;
1503
+ stack.release(state);
1504
+ }
1505
+ else {
1506
+ continue DECODE;
1507
+ }
1508
+ }
1509
+ else if (state.type === STATE_MAP_KEY) {
1510
+ if (object === "__proto__") {
1511
+ throw new DecodeError("The key __proto__ is not allowed");
1512
+ }
1513
+ state.key = this.mapKeyConverter(object);
1514
+ state.type = STATE_MAP_VALUE;
1515
+ continue DECODE;
1516
+ }
1517
+ else {
1518
+ // it must be `state.type === State.MAP_VALUE` here
1519
+ state.map[state.key] = object;
1520
+ state.readCount++;
1521
+ if (state.readCount === state.size) {
1522
+ object = state.map;
1523
+ stack.release(state);
1524
+ }
1525
+ else {
1526
+ state.key = null;
1527
+ state.type = STATE_MAP_KEY;
1528
+ continue DECODE;
1529
+ }
1530
+ }
1531
+ }
1532
+ return object;
1533
+ }
1534
+ }
1535
+ readHeadByte() {
1536
+ if (this.headByte === HEAD_BYTE_REQUIRED) {
1537
+ this.headByte = this.readU8();
1538
+ // console.log("headByte", prettyByte(this.headByte));
1539
+ }
1540
+ return this.headByte;
1541
+ }
1542
+ complete() {
1543
+ this.headByte = HEAD_BYTE_REQUIRED;
1544
+ }
1545
+ readArraySize() {
1546
+ const headByte = this.readHeadByte();
1547
+ switch (headByte) {
1548
+ case 0xdc:
1549
+ return this.readU16();
1550
+ case 0xdd:
1551
+ return this.readU32();
1552
+ default: {
1553
+ if (headByte < 0xa0) {
1554
+ return headByte - 0x90;
1555
+ }
1556
+ else {
1557
+ throw new DecodeError(`Unrecognized array type byte: ${prettyByte(headByte)}`);
1558
+ }
1559
+ }
1560
+ }
1561
+ }
1562
+ pushMapState(size) {
1563
+ if (size > this.maxMapLength) {
1564
+ throw new DecodeError(`Max length exceeded: map length (${size}) > maxMapLengthLength (${this.maxMapLength})`);
1565
+ }
1566
+ this.stack.pushMapState(size);
1567
+ }
1568
+ pushArrayState(size) {
1569
+ if (size > this.maxArrayLength) {
1570
+ throw new DecodeError(`Max length exceeded: array length (${size}) > maxArrayLength (${this.maxArrayLength})`);
1571
+ }
1572
+ this.stack.pushArrayState(size);
1573
+ }
1574
+ decodeString(byteLength, headerOffset) {
1575
+ if (!this.rawStrings || this.stateIsMapKey()) {
1576
+ return this.decodeUtf8String(byteLength, headerOffset);
1577
+ }
1578
+ return this.decodeBinary(byteLength, headerOffset);
1579
+ }
1580
+ /**
1581
+ * @throws {@link RangeError}
1582
+ */
1583
+ decodeUtf8String(byteLength, headerOffset) {
1584
+ if (byteLength > this.maxStrLength) {
1585
+ throw new DecodeError(`Max length exceeded: UTF-8 byte length (${byteLength}) > maxStrLength (${this.maxStrLength})`);
1586
+ }
1587
+ if (this.bytes.byteLength < this.pos + headerOffset + byteLength) {
1588
+ throw MORE_DATA;
1589
+ }
1590
+ const offset = this.pos + headerOffset;
1591
+ let object;
1592
+ if (this.stateIsMapKey() && this.keyDecoder?.canBeCached(byteLength)) {
1593
+ object = this.keyDecoder.decode(this.bytes, offset, byteLength);
1594
+ }
1595
+ else {
1596
+ object = utf8Decode(this.bytes, offset, byteLength);
1597
+ }
1598
+ this.pos += headerOffset + byteLength;
1599
+ return object;
1600
+ }
1601
+ stateIsMapKey() {
1602
+ if (this.stack.length > 0) {
1603
+ const state = this.stack.top();
1604
+ return state.type === STATE_MAP_KEY;
1605
+ }
1606
+ return false;
1607
+ }
1608
+ /**
1609
+ * @throws {@link RangeError}
1610
+ */
1611
+ decodeBinary(byteLength, headOffset) {
1612
+ if (byteLength > this.maxBinLength) {
1613
+ throw new DecodeError(`Max length exceeded: bin length (${byteLength}) > maxBinLength (${this.maxBinLength})`);
1614
+ }
1615
+ if (!this.hasRemaining(byteLength + headOffset)) {
1616
+ throw MORE_DATA;
1617
+ }
1618
+ const offset = this.pos + headOffset;
1619
+ const object = this.bytes.subarray(offset, offset + byteLength);
1620
+ this.pos += headOffset + byteLength;
1621
+ return object;
1622
+ }
1623
+ decodeExtension(size, headOffset) {
1624
+ if (size > this.maxExtLength) {
1625
+ throw new DecodeError(`Max length exceeded: ext length (${size}) > maxExtLength (${this.maxExtLength})`);
1626
+ }
1627
+ const extType = this.view.getInt8(this.pos + headOffset);
1628
+ const data = this.decodeBinary(size, headOffset + 1 /* extType */);
1629
+ return this.extensionCodec.decode(data, extType, this.context);
1630
+ }
1631
+ lookU8() {
1632
+ return this.view.getUint8(this.pos);
1633
+ }
1634
+ lookU16() {
1635
+ return this.view.getUint16(this.pos);
1636
+ }
1637
+ lookU32() {
1638
+ return this.view.getUint32(this.pos);
1639
+ }
1640
+ readU8() {
1641
+ const value = this.view.getUint8(this.pos);
1642
+ this.pos++;
1643
+ return value;
1644
+ }
1645
+ readI8() {
1646
+ const value = this.view.getInt8(this.pos);
1647
+ this.pos++;
1648
+ return value;
1649
+ }
1650
+ readU16() {
1651
+ const value = this.view.getUint16(this.pos);
1652
+ this.pos += 2;
1653
+ return value;
1654
+ }
1655
+ readI16() {
1656
+ const value = this.view.getInt16(this.pos);
1657
+ this.pos += 2;
1658
+ return value;
1659
+ }
1660
+ readU32() {
1661
+ const value = this.view.getUint32(this.pos);
1662
+ this.pos += 4;
1663
+ return value;
1664
+ }
1665
+ readI32() {
1666
+ const value = this.view.getInt32(this.pos);
1667
+ this.pos += 4;
1668
+ return value;
1669
+ }
1670
+ readU64() {
1671
+ const value = getUint64(this.view, this.pos);
1672
+ this.pos += 8;
1673
+ return value;
1674
+ }
1675
+ readI64() {
1676
+ const value = getInt64(this.view, this.pos);
1677
+ this.pos += 8;
1678
+ return value;
1679
+ }
1680
+ readU64AsBigInt() {
1681
+ const value = this.view.getBigUint64(this.pos);
1682
+ this.pos += 8;
1683
+ return value;
1684
+ }
1685
+ readI64AsBigInt() {
1686
+ const value = this.view.getBigInt64(this.pos);
1687
+ this.pos += 8;
1688
+ return value;
1689
+ }
1690
+ readF32() {
1691
+ const value = this.view.getFloat32(this.pos);
1692
+ this.pos += 4;
1693
+ return value;
1694
+ }
1695
+ readF64() {
1696
+ const value = this.view.getFloat64(this.pos);
1697
+ this.pos += 8;
1698
+ return value;
1699
+ }
1700
+ }
1701
+
1702
+ /**
1703
+ * It decodes a single MessagePack object in a buffer.
1704
+ *
1705
+ * This is a synchronous decoding function.
1706
+ * See other variants for asynchronous decoding: {@link decodeAsync}, {@link decodeStream}, or {@link decodeArrayStream}.
1707
+ *
1708
+ * @throws {@link RangeError} if the buffer is incomplete, including the case where the buffer is empty.
1709
+ * @throws {@link DecodeError} if the buffer contains invalid data.
1710
+ */
1711
+ function decode(buffer, options) {
1712
+ const decoder = new Decoder(options);
1713
+ return decoder.decode(buffer);
1714
+ }
1715
+ /**
1716
+ * It decodes multiple MessagePack objects in a buffer.
1717
+ * This is corresponding to {@link decodeMultiStream}.
1718
+ *
1719
+ * @throws {@link RangeError} if the buffer is incomplete, including the case where the buffer is empty.
1720
+ * @throws {@link DecodeError} if the buffer contains invalid data.
1721
+ */
1722
+ function decodeMulti(buffer, options) {
1723
+ const decoder = new Decoder(options);
1724
+ return decoder.decodeMulti(buffer);
1725
+ }
1726
+
1727
+ // utility for whatwg streams
1728
+ function isAsyncIterable(object) {
1729
+ return object[Symbol.asyncIterator] != null;
1730
+ }
1731
+ async function* asyncIterableFromStream(stream) {
1732
+ const reader = stream.getReader();
1733
+ try {
1734
+ while (true) {
1735
+ const { done, value } = await reader.read();
1736
+ if (done) {
1737
+ return;
1738
+ }
1739
+ yield value;
1740
+ }
1741
+ }
1742
+ finally {
1743
+ reader.releaseLock();
1744
+ }
1745
+ }
1746
+ function ensureAsyncIterable(streamLike) {
1747
+ if (isAsyncIterable(streamLike)) {
1748
+ return streamLike;
1749
+ }
1750
+ else {
1751
+ return asyncIterableFromStream(streamLike);
1752
+ }
1753
+ }
1754
+
1755
+ /**
1756
+ * @throws {@link RangeError} if the buffer is incomplete, including the case where the buffer is empty.
1757
+ * @throws {@link DecodeError} if the buffer contains invalid data.
1758
+ */
1759
+ async function decodeAsync(streamLike, options) {
1760
+ const stream = ensureAsyncIterable(streamLike);
1761
+ const decoder = new Decoder(options);
1762
+ return decoder.decodeAsync(stream);
1763
+ }
1764
+ /**
1765
+ * @throws {@link RangeError} if the buffer is incomplete, including the case where the buffer is empty.
1766
+ * @throws {@link DecodeError} if the buffer contains invalid data.
1767
+ */
1768
+ function decodeArrayStream(streamLike, options) {
1769
+ const stream = ensureAsyncIterable(streamLike);
1770
+ const decoder = new Decoder(options);
1771
+ return decoder.decodeArrayStream(stream);
1772
+ }
1773
+ /**
1774
+ * @throws {@link RangeError} if the buffer is incomplete, including the case where the buffer is empty.
1775
+ * @throws {@link DecodeError} if the buffer contains invalid data.
1776
+ */
1777
+ function decodeMultiStream(streamLike, options) {
1778
+ const stream = ensureAsyncIterable(streamLike);
1779
+ const decoder = new Decoder(options);
1780
+ return decoder.decodeStream(stream);
1781
+ }
1782
+
1783
+ // Main Functions:
1784
+
1785
+ var dist_esm = /*#__PURE__*/Object.freeze({
1786
+ __proto__: null,
1787
+ DecodeError: DecodeError,
1788
+ Decoder: Decoder,
1789
+ EXT_TIMESTAMP: EXT_TIMESTAMP,
1790
+ Encoder: Encoder,
1791
+ ExtData: ExtData,
1792
+ ExtensionCodec: ExtensionCodec,
1793
+ decode: decode,
1794
+ decodeArrayStream: decodeArrayStream,
1795
+ decodeAsync: decodeAsync,
1796
+ decodeMulti: decodeMulti,
1797
+ decodeMultiStream: decodeMultiStream,
1798
+ decodeTimestampExtension: decodeTimestampExtension,
1799
+ decodeTimestampToTimeSpec: decodeTimestampToTimeSpec,
1800
+ encode: encode,
1801
+ encodeDateToTimeSpec: encodeDateToTimeSpec,
1802
+ encodeTimeSpecToTimestamp: encodeTimeSpecToTimestamp,
1803
+ encodeTimestampExtension: encodeTimestampExtension
1804
+ });
1805
+
1806
+ var require$$2 = /*@__PURE__*/getAugmentedNamespace(dist_esm);
1807
+
1808
+ var crdtManager;
1809
+ var hasRequiredCrdtManager;
1810
+
1811
+ function requireCrdtManager () {
1812
+ if (hasRequiredCrdtManager) return crdtManager;
1813
+ hasRequiredCrdtManager = 1;
1814
+ // Custom conflict-free replicated data type system with vector clocks
1815
+
1816
+ const CONSOLE_PREFIX = "CRDT Manager: ";
1817
+
1818
+ class CRDTManager {
1819
+ // Storage
1820
+ #replicaId;
1821
+ #keyOperations = new Map();
1822
+ #vectorClock = new Map();
1823
+
1824
+ // Local only
1825
+ #propertyStore = {}; // Current local values per key, as object
1826
+ #lastPropertyStore = {}; // Last property store to compare against
1827
+
1828
+ // Local Garbage Collection
1829
+ #lastGCCheck = 0;
1830
+ #opUuidTimestamp = new Map(); // Map every operation to a timestamp for garbage collection
1831
+
1832
+ // Debug
1833
+ #debug = false;
1834
+
1835
+ /**
1836
+ * Create a new instance
1837
+ * @param {string} replicaId - Choose a uuid (falls back to random uuid)
1838
+ */
1839
+ constructor(replicaId, debug) {
1840
+ if (!replicaId) return console.error("No replicaId provided!");
1841
+ if (debug) this.#debug = true;
1842
+ this.#replicaId = replicaId;
1843
+ this.#vectorClock.set(this.#replicaId, 0);
1844
+ }
1845
+
1846
+ /**
1847
+ * Import the entire state of the CRDT manager (this overwrites the old state)
1848
+ * @param {Object} state
1849
+ */
1850
+ importState(state) {
1851
+ try {
1852
+ const { keyOperations, vectorClock } = state;
1853
+ if (this.#debug) console.log(CONSOLE_PREFIX + "Importing state:", state);
1854
+
1855
+ // Resets
1856
+ this.#opUuidTimestamp.clear();
1857
+ this.#propertyStore = {};
1858
+ this.#lastPropertyStore = {};
1859
+
1860
+ this.#keyOperations = new Map(keyOperations); // Rebuild the map
1861
+ this.#vectorClock = new Map(vectorClock); // Also rebuild the map here
1862
+ if (!this.#vectorClock.has(this.#replicaId)) this.#vectorClock.set(this.#replicaId, 0); // Reset own vector clock if it wasn't present in the imported state
1863
+
1864
+ // Map operation uuids to timestamp
1865
+ this.#keyOperations.forEach((value, key) => {
1866
+ value?.forEach((operation) => {
1867
+ this.#opUuidTimestamp.set(operation?.uuid, Date.now());
1868
+ });
1869
+ });
1870
+
1871
+ // Process each key to update local values
1872
+ for (const key of this.#keyOperations.keys()) this.#processLocalProperty(key);
1873
+
1874
+ } catch (error) {
1875
+ console.error(CONSOLE_PREFIX + "Failed to import state:", error);
1876
+ }
1877
+ }
1878
+
1879
+ /**
1880
+ * Import property update
1881
+ * @param {Object} data - Data to import
1882
+ */
1883
+ importPropertyUpdate(data) {
1884
+ try {
1885
+ const { key, operation, vectorClock } = data;
1886
+ if (this.#debug) console.log(CONSOLE_PREFIX + "Importing update:", data); // Debug
1887
+
1888
+ // Get COPY of current ops (or empty array if none yet)
1889
+ const currentOps = [...(this.#keyOperations.get(key) || [])];
1890
+
1891
+ // Merge vector clocks (always take max value)
1892
+ for (const [id, counter] of vectorClock) {
1893
+ if (!this.#vectorClock.has(id) || this.#vectorClock.get(id) < counter) {
1894
+ this.#vectorClock.set(id, counter);
1895
+ }
1896
+ }
1897
+
1898
+ // Saveguard
1899
+ if (this.#vectorClock.size > 1000) this.#vectorClock = new Map([...this.#vectorClock].slice(-100));
1900
+
1901
+ // Add new operation if it's not already added
1902
+ const existingUuids = new Set(currentOps.map(op => op.uuid));
1903
+ if (operation?.uuid && !existingUuids.has(operation.uuid)) {
1904
+ currentOps.push({ ...operation }); // Add operation
1905
+ this.#opUuidTimestamp.set(operation?.uuid, Date.now()); // Add timestamp for gc
1906
+ }
1907
+
1908
+ // Sort, update operations & local value
1909
+ this.#keyOperations.set(key, this.#sortByVectorClock(currentOps));
1910
+ this.#processLocalProperty(key);
1911
+ this.#checkGarbageCollection();
1912
+
1913
+ } catch (error) {
1914
+ console.error(CONSOLE_PREFIX + "Failed to import property:", error);
1915
+ }
1916
+ }
1917
+
1918
+ /**
1919
+ * Update a property
1920
+ * @param {string} key
1921
+ * @param {string} operation
1922
+ * @param {*} value
1923
+ * @param {*} updateValue
1924
+ * @returns {Object} - Returns the property update
1925
+ */
1926
+ updateProperty(key, operation, value, updateValue) {
1927
+ try {
1928
+ // Debug log
1929
+ if (this.#debug) console.log(CONSOLE_PREFIX + `Updating property with key ${key}, operation ${operation}, value ${value} and updateValue ${updateValue}.`);
1930
+
1931
+ // Increment vector clock
1932
+ const counter = this.#vectorClock.get(this.#replicaId) || 0;
1933
+ this.#vectorClock.set(this.#replicaId, counter + 1);
1934
+
1935
+ // Assign shallow COPY of currennt ops or fall back to empty array
1936
+ const currentOps = [...(this.#keyOperations.get(key) || [])];
1937
+
1938
+ // Add operation
1939
+ const newOp = this.#createOperation({ operation, value, updateValue }, Array.from(this.#vectorClock.entries()));
1940
+ currentOps.push(newOp);
1941
+ this.#keyOperations.set(key, currentOps); // Update the operations (no need to sort via vector clock since local updates are always the latest)
1942
+ this.#processLocalProperty(key); // Process local value
1943
+ this.#checkGarbageCollection();
1944
+
1945
+ // Return the property update with the new operation (this can be imported using importPropertyUpdate)
1946
+ return {
1947
+ key,
1948
+ operation: { ...newOp },
1949
+ vectorClock: Array.from(this.#vectorClock.entries())
1950
+ };
1951
+
1952
+ } catch (error) {
1953
+ console.error(CONSOLE_PREFIX + `Failed to add operation for key "${key}":`, error);
1954
+ }
1955
+ }
1956
+
1957
+ #checkGarbageCollection() {
1958
+ const MIN_GC_DELAY = 1000; // Minimum 1s delay between garbage collection runs
1959
+ const MIN_AGE_FOR_GC = 5000; // Garbage collect ops that are older than 5s
1960
+
1961
+ if (Date.now() - this.#lastGCCheck < MIN_GC_DELAY) return;
1962
+ this.#lastGCCheck = Date.now();
1963
+
1964
+ for (const [key, operations] of this.#keyOperations.entries()) {
1965
+ if (operations?.length < 5) continue; // Min op amount per key for garbage collection to run (otherwise not worth it)
1966
+
1967
+ let retainCount = operations.length;
1968
+ operations.forEach((op, index) => {
1969
+ // Count how many ops, from last to latest, are older than the min-age in a row
1970
+ if (op.uuid && this.#opUuidTimestamp.has(op.uuid) && (Date.now() - this.#opUuidTimestamp.get(op.uuid)) > MIN_AGE_FOR_GC) {
1971
+ const removeCount = index + 1;
1972
+ retainCount = operations.length - removeCount;
1973
+ this.#opUuidTimestamp.delete(op.uuid); // Remove from timestamps if set to be deleted
1974
+ }
1975
+ });
1976
+
1977
+ if (retainCount < operations.length) {
1978
+ if (this.#debug) console.log(CONSOLE_PREFIX + `Running garbage collection for key ${key} with current operations:`, operations);
1979
+ const retainOps = operations.slice(-retainCount); // newest ops
1980
+ const removeOps = operations.slice(0, -retainCount); // oldest ops (start = idx 0, end = retainCount counted from right side)
1981
+ const baselineVectorClock = removeOps[removeOps.length - 1]?.vectorClock || []; // Use the vector clock from the last operation that we remove/overwrite
1982
+
1983
+ // Calculate the value at the point where retained operations start
1984
+ let baselineValue = null;
1985
+ for (const op of removeOps) {
1986
+ if (!op.data) continue;
1987
+ if (op.data.operation.startsWith('array') && !Array.isArray(baselineValue)) baselineValue = []; // Initialize array if required
1988
+ baselineValue = this.#handleOperation(
1989
+ baselineValue,
1990
+ op.data.operation,
1991
+ op.data.value,
1992
+ op.data.updateValue
1993
+ );
1994
+ }
1995
+
1996
+ // Create a compact operation with baseline value and appropriate vector clock
1997
+ const compactOp = this.#createOperation({ operation: "set", value: baselineValue }, baselineVectorClock);
1998
+
1999
+ // Debug log
2000
+ if (this.#debug) console.log(CONSOLE_PREFIX + "Operations after garbage collection for this key:", [compactOp, ...retainOps]);
2001
+
2002
+ // Combine compact op with retained operations
2003
+ this.#keyOperations.set(key, [compactOp, ...retainOps]);
2004
+ }
2005
+ }
2006
+ }
2007
+
2008
+ /**
2009
+ * Create an operation object
2010
+ * @param {Object} data
2011
+ * @param {Array} vectorClock
2012
+ * @returns {Object} - Operation object
2013
+ */
2014
+ #createOperation(data, vectorClock) {
2015
+ const newOperation = {
2016
+ data,
2017
+ vectorClock,
2018
+ source: this.#replicaId,
2019
+ uuid: crypto.randomUUID()
2020
+ };
2021
+ this.#opUuidTimestamp.set(newOperation.uuid, Date.now());
2022
+ return newOperation;
2023
+ }
2024
+
2025
+ /**
2026
+ * Process a property's value by applying all operations and set it in the property store
2027
+ * @param {string} key
2028
+ */
2029
+ #processLocalProperty(key) {
2030
+ try {
2031
+ const ops = this.#keyOperations.get(key);
2032
+ if (!ops?.length) return;
2033
+
2034
+ // Apply all operations in order
2035
+ let value = null;
2036
+
2037
+ for (const op of ops) {
2038
+ if (!op.data) continue;
2039
+ if (op.data.operation.startsWith('array') && !Array.isArray(value)) value = []; // Initialize array if required
2040
+ value = this.#handleOperation(
2041
+ value,
2042
+ op.data.operation,
2043
+ op.data.value,
2044
+ op.data.updateValue
2045
+ );
2046
+ }
2047
+
2048
+ this.#propertyStore[key] = value; // Save locally
2049
+ } catch (error) {
2050
+ console.error(CONSOLE_PREFIX + `Failed to process property for "${key}":`, error);
2051
+ }
2052
+ }
2053
+
2054
+ // Sort by vector clock (causal order)
2055
+ #sortByVectorClock(operations) {
2056
+ return [...operations].sort((a, b) => {
2057
+ const clockA = new Map(a.vectorClock || []);
2058
+ const clockB = new Map(b.vectorClock || []);
2059
+
2060
+ // First sort by causal relationship
2061
+ let aGreater = false, bGreater = false;
2062
+
2063
+ const allIds = new Set([...clockA.keys(), ...clockB.keys()]);
2064
+ for (const id of allIds) {
2065
+ const countA = clockA.get(id) || 0;
2066
+ const countB = clockB.get(id) || 0;
2067
+
2068
+ if (countA > countB) aGreater = true;
2069
+ if (countA < countB) bGreater = true;
2070
+ }
2071
+
2072
+ // Determine causal relationship (if one is greater, and the other is smaller, it's clear)
2073
+ if (aGreater && !bGreater) return 1; // a happens after b
2074
+ if (!aGreater && bGreater) return -1; // a happens before b
2075
+
2076
+ // Concurrent (unclear): Compare highest counter in each (sort by which one is ahead)
2077
+ const maxA = Math.max(...Array.from(clockA.values()), 0);
2078
+ const maxB = Math.max(...Array.from(clockB.values()), 0);
2079
+ if (maxA !== maxB) return maxA - maxB;
2080
+
2081
+ // Final tiebreak: Sort by ID of Replica that actually generated the operation
2082
+ return a.source.localeCompare(b.source);
2083
+ });
2084
+ }
2085
+
2086
+ // Handle an operation
2087
+ #handleOperation(curValue, operation, value, updateValue) {
2088
+ try {
2089
+ // Clone to avoid reference issues
2090
+ curValue = JSON.parse(JSON.stringify(curValue || null));
2091
+
2092
+ // Set operation
2093
+ if (operation === "set") return value;
2094
+
2095
+ // Array operations
2096
+ if (operation.startsWith('array')) {
2097
+ if (!Array.isArray(curValue)) curValue = []; // Auto-convert to array if current value isn't one
2098
+
2099
+ // Comparison function
2100
+ const isObject = typeof value === 'object' && value !== null;
2101
+ const compare = (item) => {
2102
+ if (isObject && typeof item === 'object' && item !== null) {
2103
+ return JSON.stringify(item) === JSON.stringify(value); // Deep comparison
2104
+ }
2105
+ return item === value; // Value comparison
2106
+ };
2107
+
2108
+ switch (operation) {
2109
+ case 'array-add':
2110
+ curValue.push(value);
2111
+ break;
2112
+ case 'array-add-unique':
2113
+ if (!curValue.some(compare)) curValue.push(value);
2114
+ break;
2115
+ case 'array-remove-matching':
2116
+ curValue = curValue.filter(item => !compare(item));
2117
+ break;
2118
+ case 'array-update-matching':
2119
+ const index = curValue.findIndex(compare);
2120
+ if (index !== -1) curValue[index] = updateValue;
2121
+ break;
2122
+ }
2123
+ }
2124
+
2125
+ return curValue;
2126
+
2127
+ } catch (error) {
2128
+ console.error(CONSOLE_PREFIX + `Failed to process operation with curValue ${curValue}:`, error);
2129
+ return curValue;
2130
+ }
2131
+ }
2132
+
2133
+ // Get property store
2134
+ get getPropertyStore() {
2135
+ try {
2136
+ return JSON.parse(JSON.stringify(this.#propertyStore)); // Return full copy
2137
+ } catch {
2138
+ console.error(CONSOLE_PREFIX + "Failed to parse property store");
2139
+ return {};
2140
+ }
2141
+ }
2142
+
2143
+ // Check for changes in the local property store
2144
+ get didPropertiesChange() {
2145
+ try {
2146
+ if (JSON.stringify(this.#propertyStore) !== JSON.stringify(this.#lastPropertyStore)) {
2147
+ this.#lastPropertyStore = JSON.parse(JSON.stringify(this.#propertyStore));
2148
+ return true;
2149
+ }
2150
+ } catch { }
2151
+ return false;
2152
+ }
2153
+
2154
+ // Get state (can be imported using importState, converts the maps to arrays for serialization)
2155
+ get getState() {
2156
+ return {
2157
+ keyOperations: [...this.#keyOperations.entries()],
2158
+ vectorClock: [...this.#vectorClock.entries()]
2159
+ };
2160
+ }
2161
+ }
2162
+
2163
+ crdtManager = { CRDTManager };
2164
+ return crdtManager;
2165
+ }
2166
+
10
2167
  var server$1;
11
2168
  var hasRequiredServer;
12
2169
 
@@ -15,6 +2172,8 @@ function requireServer () {
15
2172
  hasRequiredServer = 1;
16
2173
  const WebSocket = require$$0;
17
2174
  const http = require$$1;
2175
+ const { encode, decode } = require$$2;
2176
+ const { CRDTManager } = requireCrdtManager();
18
2177
 
19
2178
  /**
20
2179
  * PlaySocketServer - WebSocket server for PlaySocket multiplayer library
@@ -31,6 +2190,10 @@ function requireServer () {
31
2190
  #heartbeatInterval;
32
2191
  #pendingDisconnects = new Map(); // ClientId -> {timeout, roomId}
33
2192
  #clientTokens = new Map(); // ClientId -> Token
2193
+ #roomVersions = new Map(); // RoomId -> Version
2194
+
2195
+ // Debug
2196
+ #debug = false;
34
2197
 
35
2198
  /**
36
2199
  * Create a new PlaySocketServer instance
@@ -40,7 +2203,9 @@ function requireServer () {
40
2203
  * @param {string} [options.path='/socket'] - WebSocket endpoint path
41
2204
  */
42
2205
  constructor(options = {}) {
43
- const { server, port = 3000, path = '/' } = options;
2206
+ const { server, port = 3000, path = '/', debug = false } = options;
2207
+
2208
+ if (debug) this.#debug = true; // Enable extra logging
44
2209
 
45
2210
  // Handle server creation / usage
46
2211
  if (server) {
@@ -49,7 +2214,7 @@ function requireServer () {
49
2214
  this.#ownsServer = true;
50
2215
  this.#server = http.createServer(); // Create HTTP server and start it
51
2216
  this.#server.listen(port, () => {
52
- console.log(`PlaySocket server running on port ${port}`);
2217
+ console.log(`PlaySocket server running on port ${port}.`);
53
2218
  });
54
2219
  }
55
2220
 
@@ -83,7 +2248,7 @@ function requireServer () {
83
2248
  */
84
2249
  #handleMessage(ws, message) {
85
2250
  try {
86
- const data = JSON.parse(message);
2251
+ const data = decode(message);
87
2252
 
88
2253
  // Apply rate limiting after client is registered
89
2254
  if (ws.clientId && !this.#checkRateLimit(ws.clientId, data.type)) {
@@ -95,14 +2260,14 @@ function requireServer () {
95
2260
  // Register client ID
96
2261
  if (!data.id) return;
97
2262
  if (this.#clients.get(data.id)) {
98
- ws.send(JSON.stringify({ type: 'id_taken' }));
2263
+ ws.send(encode({ type: 'id_taken' }), { binary: true });
99
2264
  return;
100
2265
  }
101
2266
  ws.clientId = data.id; // Adds the provided id to the ws object as clientId
102
2267
  this.#clients.set(data.id, ws);
103
2268
  const sessionToken = this.#generateSessionToken();
104
2269
  this.#clientTokens.set(data.id, sessionToken); // Token is used when reconnecting to prevent impersonation attacks
105
- ws.send(JSON.stringify({ type: 'registered', sessionToken, serverTime: Date.now(), clientTime: data.clientTime }));
2270
+ ws.send(encode({ type: 'registered', sessionToken }), { binary: true });
106
2271
  this.#triggerEvent("clientRegistered", data.id, data.customData);
107
2272
  break;
108
2273
 
@@ -111,7 +2276,10 @@ function requireServer () {
111
2276
  const pd = this.#pendingDisconnects.get(data.id);
112
2277
  if (pd && data.sessionToken) {
113
2278
  if (data.sessionToken !== this.#clientTokens.get(data.id)) {
114
- ws.send(JSON.stringify({ type: 'reconnection_failed', reason: "Session token does not match" }));
2279
+ ws.send(encode({
2280
+ type: 'reconnection_failed',
2281
+ reason: "Session token does not match"
2282
+ }), { binary: true });
115
2283
  return;
116
2284
  }
117
2285
  clearTimeout(pd.timeout);
@@ -123,19 +2291,33 @@ function requireServer () {
123
2291
 
124
2292
  // If they were in a room, provide updated room data
125
2293
  let roomData;
126
- const formerRoom = this.#rooms[this.#clientRooms.get(data.id)];
2294
+ const formerRoomId = this.#clientRooms.get(data.id);
2295
+ const formerRoom = this.#rooms[formerRoomId];
127
2296
  if (formerRoom) {
2297
+ if (this.#debug) console.log(`State sent for reconnection for room ${formerRoomId}:`, formerRoom.crdtManager.getState);
128
2298
  roomData = {
129
- storage: formerRoom.storage,
130
- storageTimestamps: formerRoom.storageTimestamps,
2299
+ state: formerRoom.crdtManager.getState,
131
2300
  participantCount: formerRoom.participants.length,
132
- host: formerRoom.participants[0]
2301
+ host: formerRoom.participants[0],
2302
+ version: this.#roomVersions.get(formerRoomId)
133
2303
  };
134
2304
  }
135
2305
 
136
- ws.send(JSON.stringify({ type: 'reconnected', roomData }));
2306
+ ws.send(encode({ type: 'reconnected', roomData }), { binary: true });
137
2307
  } else {
138
- ws.send(JSON.stringify({ type: 'reconnection_failed', reason: "Client unknown to server" }));
2308
+ ws.send(encode({ type: 'reconnection_failed', reason: "Client unknown to server" }), { binary: true });
2309
+ }
2310
+ break;
2311
+
2312
+ case 'request_state':
2313
+ const stateRoomId = this.#clientRooms.get(ws.clientId);
2314
+ const stateRoom = stateRoomId ? this.#rooms[stateRoomId] : null;
2315
+ if (stateRoom) {
2316
+ ws.send(encode({
2317
+ type: 'state_sync',
2318
+ state: stateRoom.crdtManager.getState,
2319
+ version: this.#roomVersions.get(stateRoomId)
2320
+ }), { binary: true });
139
2321
  }
140
2322
  break;
141
2323
 
@@ -143,22 +2325,29 @@ function requireServer () {
143
2325
  if (!ws.clientId) return;
144
2326
  const newRoomId = ws.clientId;
145
2327
  if (this.#clientRooms.get(ws.clientId) || this.#rooms[newRoomId]) {
146
- ws.send(JSON.stringify({
2328
+ ws.send(encode({
147
2329
  type: 'room_creation_failed',
148
2330
  reason: this.#clientRooms.get(ws.clientId) ? 'Already in a room' : 'Room id is taken'
149
- }));
2331
+ }), { binary: true });
150
2332
  return;
151
2333
  }
152
2334
 
2335
+ // Create room
2336
+ this.#roomVersions.set(newRoomId, 0); // Start with version 0
153
2337
  this.#rooms[newRoomId] = {
154
2338
  participants: [ws.clientId],
155
2339
  maxSize: data.size || null,
156
- storage: data.storage || {},
157
- storageTimestamps: {},
2340
+ crdtManager: new CRDTManager(crypto.randomUUID(), this.#debug)
158
2341
  };
159
2342
 
160
- this.#clientRooms.set(ws.clientId, newRoomId);
161
- ws.send(JSON.stringify({ type: 'room_created' }));
2343
+ // Load state if provided
2344
+ if (data.state) {
2345
+ this.#rooms[newRoomId].crdtManager.importState(data.state);
2346
+ if (this.#debug) console.log("Room created with initial state:", data.state);
2347
+ }
2348
+
2349
+ this.#clientRooms.set(ws.clientId, newRoomId); // Add client to their room
2350
+ ws.send(encode({ type: 'room_created' }), { binary: true });
162
2351
  this.#triggerEvent("roomCreated", ws.clientId); // Client id = room id
163
2352
  break;
164
2353
 
@@ -170,12 +2359,12 @@ function requireServer () {
170
2359
  if (!room ||
171
2360
  (room.maxSize && room.participants.length >= room.maxSize) ||
172
2361
  this.#clientRooms.get(ws.clientId)) {
173
- ws.send(JSON.stringify({
2362
+ ws.send(encode({
174
2363
  type: 'join_rejected',
175
2364
  reason: !room ? 'Room not found' :
176
2365
  room.maxSize && room.participants.length >= room.maxSize ? 'Room full' :
177
2366
  'Already in a room'
178
- }));
2367
+ }), { binary: true });
179
2368
  return;
180
2369
  }
181
2370
 
@@ -183,53 +2372,53 @@ function requireServer () {
183
2372
  this.#clientRooms.set(ws.clientId, roomId);
184
2373
 
185
2374
  // Notify joiner with initial storage
186
- ws.send(JSON.stringify({
2375
+ if (this.#debug) console.log("Room state sent for join:", room.crdtManager.getState);
2376
+ ws.send(encode({
187
2377
  type: 'join_accepted',
188
- storage: room.storage,
189
- storageTimestamps: room.storageTimestamps,
2378
+ state: room.crdtManager.getState,
190
2379
  participantCount: room.participants.length,
191
- host: room.participants[0]
192
- }));
2380
+ host: room.participants[0],
2381
+ version: this.#roomVersions.get(roomId)
2382
+ }), { binary: true });
193
2383
 
194
2384
  // Notify existing participants
195
2385
  room.participants.forEach(p => {
196
2386
  if (p === ws.clientId) return;
197
2387
  const client = this.#clients.get(p);
198
2388
  if (client) {
199
- client.send(JSON.stringify({
2389
+ client.send(encode({
200
2390
  type: 'client_connected',
201
2391
  client: ws.clientId,
202
2392
  participantCount: room.participants.length
203
- }));
2393
+ }), { binary: true });
204
2394
  }
205
2395
  });
206
2396
  this.#triggerEvent("roomJoined", ws.clientId, roomId);
207
2397
  break;
208
2398
 
209
- case 'room_storage_update':
2399
+ case 'property_update':
210
2400
  const updateRoomId = this.#clientRooms.get(ws.clientId);
211
2401
  const updateRoom = updateRoomId ? this.#rooms[updateRoomId] : null;
212
- if (updateRoom && data.key) {
213
- const existingTimestamp = updateRoom.storageTimestamps[data.key] || 0;
214
- if (data.timestamp > existingTimestamp && JSON.stringify(updateRoom.storage[data.key]) !== JSON.stringify(data.value)) {
215
- updateRoom.storage[data.key] = data.value;
216
- updateRoom.storageTimestamps[data.key] = data.timestamp;
217
- this.#syncRoomStorageKey(updateRoom, data.key, data.timestamp);
218
- }
219
- }
220
- break;
221
2402
 
222
- case 'room_storage_array_update':
223
- const arrayRoomId = this.#clientRooms.get(ws.clientId);
224
- const arrayRoom = arrayRoomId ? this.#rooms[arrayRoomId] : null;
225
- if (arrayRoom && data.key) {
226
- // No timestamp check for array operations as they are designed to work unordered
227
- const updatedArray = this.#arrayUpdate(arrayRoom.storage, data.key, data.operation, data.value, data.updateValue);
228
- if (JSON.stringify(arrayRoom.storage[data.key]) !== JSON.stringify(updatedArray)) {
229
- arrayRoom.storage[data.key] = updatedArray;
230
- arrayRoom.storageTimestamps[data.key] = Date.now(); // Assign new date to ensure that this update is always being applied
231
- this.#syncRoomStorageOperation(arrayRoom, ws.clientId, data.key, data.operation, data.value, data.updateValue, Date.now());
232
- }
2403
+ if (updateRoom && data.key && data.update && data.uuid) {
2404
+ updateRoom.crdtManager.importPropertyUpdate(data.update);
2405
+
2406
+ // Increment version for this room
2407
+ const currentVersion = this.#roomVersions.get(updateRoomId) + 1;
2408
+ this.#roomVersions.set(updateRoomId, currentVersion);
2409
+
2410
+ if (this.#debug) console.log("Property update received and imported:", data.update);
2411
+ updateRoom.participants?.forEach(p => {
2412
+ const client = this.#clients.get(p);
2413
+ if (client) {
2414
+ client.send(encode({
2415
+ type: 'property_update',
2416
+ update: data.update,
2417
+ version: currentVersion,
2418
+ uuid: data.uuid
2419
+ }), { binary: true });
2420
+ }
2421
+ });
233
2422
  }
234
2423
  break;
235
2424
 
@@ -281,87 +2470,6 @@ function requireServer () {
281
2470
  return true;
282
2471
  }
283
2472
 
284
- /**
285
- * Sync storage key with all clients in room
286
- * @private
287
- * @param {string} room
288
- * @param {string} requesterId - Client id of whom initiated the operation
289
- * @param {string} key
290
- * @param {number} timestamp - In milliseconds
291
- */
292
- #syncRoomStorageKey(room, key, timestamp) {
293
- room?.participants?.forEach(p => {
294
- const client = this.#clients.get(p);
295
- if (client) {
296
- client.send(JSON.stringify({
297
- type: 'storage_sync',
298
- key,
299
- value: room?.storage[key],
300
- timestamp
301
- }));
302
- }
303
- });
304
- }
305
-
306
- /**
307
- * Sync storage operations with all clients in room
308
- * @private
309
- * @param {string} room
310
- * @param {string} requesterId - Client id of whom initiated the operation
311
- * @param {string} key
312
- * @param {string} operation
313
- * @param {*} value
314
- * @param {*} updateValue
315
- * @param {number} timestamp - In milliseconds
316
- */
317
- #syncRoomStorageOperation(room, requesterId, key, operation, value, updateValue, timestamp) {
318
- room?.participants?.forEach(p => {
319
- if (p == requesterId) return; // Don't send the operation to the requester
320
- const client = this.#clients.get(p);
321
- if (client) {
322
- client.send(JSON.stringify({
323
- type: 'storage_operation',
324
- key,
325
- operation,
326
- value,
327
- updateValue,
328
- timestamp
329
- }));
330
- }
331
- });
332
- }
333
-
334
- /**
335
- * Handle array operations
336
- * @private
337
- */
338
- #arrayUpdate(storage, key, operation, value, updateValue) {
339
- let array = (!storage[key] || !Array.isArray(storage[key])) ? [] : [...storage[key]];
340
- const isObject = typeof value === 'object' && value !== null;
341
- const compare = (item) => isObject ? JSON.stringify(item) === JSON.stringify(value) : item === value;
342
-
343
- switch (operation) {
344
- case 'add':
345
- array.push(value);
346
- break;
347
-
348
- case 'add-unique':
349
- if (!array.some(compare)) array.push(value);
350
- break;
351
-
352
- case 'remove-matching':
353
- array = array.filter(item => !compare(item));
354
- break;
355
-
356
- case 'update-matching':
357
- const index = array.findIndex(compare);
358
- if (index !== -1) array[index] = updateValue;
359
- break;
360
- }
361
-
362
- return array;
363
- }
364
-
365
2473
  /**
366
2474
  * Handle client disconnection
367
2475
  * @private
@@ -400,16 +2508,18 @@ function requireServer () {
400
2508
 
401
2509
  if (room.participants?.length === 0) {
402
2510
  delete this.#rooms[roomId]; // Delete room if now empty
2511
+ this.#roomVersions.delete(roomId); // Delete room version
2512
+ if (this.#debug) console.log("Deleted room with id " + roomId + ".");
403
2513
  } else {
404
2514
  // Notify remaining participants
405
2515
  room.participants.forEach(p => {
406
2516
  const client = this.#clients.get(p);
407
- if (client) client.send(JSON.stringify({
2517
+ if (client) client.send(encode({
408
2518
  type: 'client_disconnected',
409
2519
  client: ws.clientId,
410
2520
  host: room.participants[0],
411
2521
  participantCount: room.participants.length
412
- }));
2522
+ }), { binary: true });
413
2523
  });
414
2524
  }
415
2525
  }
@@ -451,7 +2561,7 @@ function requireServer () {
451
2561
  }
452
2562
  if (this.#server && this.#ownsServer) {
453
2563
  this.#server.close(() => {
454
- console.log('PlaySocket server stopped');
2564
+ console.log('PlaySocket server stopped.');
455
2565
  });
456
2566
  }
457
2567
  this.#pendingDisconnects.forEach((data) => {