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