@xuzhiyang/syncvar 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.
@@ -0,0 +1,2944 @@
1
+ /*! Waterbear v1.1.1 (2026-02-09T11:56:01.356Z)
2
+ 该SDK提供一套基于 WebSocket 的变量同步、锁、RPC 与事件订阅能力,帮助前端快速搭建协同编辑、状态共享与后台任务管理场景。
3
+ 主页: https://varserver.popx.com
4
+ */
5
+
6
+ (function (global, factory) {
7
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
8
+ typeof define === 'function' && define.amd ? define(['exports'], factory) :
9
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.WaterBear = {}));
10
+ })(this, (function (exports) { 'use strict';
11
+
12
+ // 版本信息模块
13
+ class VersionInfo {
14
+ static Version = "1.1.1"; // 由构建时注入
15
+ static BuildTime = "2026-02-09T11:56:01.356Z";
16
+ static printInfo() {
17
+ globalThis.console.group('%c📦 Waterbear Info', 'color: #35495e; font-size: 14px;');
18
+ // 只在开发环境显示调试信息
19
+ {
20
+ globalThis.console.log('%c 🚀 当前为开发模式, 生产环境请使用 syncvar.min.js', 'color: #FF9800;');
21
+ globalThis.console.log('%c 🛠️ 该SDK提供一套基于 WebSocket 的变量同步、锁、RPC 与事件订阅能力,帮助前端快速搭建协同编辑、状态共享与后台任务管理场景。', 'color: #FF9800;');
22
+ globalThis.console.log(`%cDemo: https://varserver.popx.com/waterbear/syncvar.html`, 'color: #FF9800;');
23
+ globalThis.console.log(`%cDoc: https://varserver.popx.com/waterbear/api.md`, 'color: #FF9800;');
24
+ globalThis.console.log(`Build: ${new Date(VersionInfo.BuildTime).toLocaleString()}`);
25
+ }
26
+ globalThis.console.log(`%cVersion: v${VersionInfo.Version}`, 'color: #41b883;');
27
+ globalThis.console.groupEnd();
28
+ }
29
+ }
30
+
31
+ VersionInfo.printInfo();
32
+
33
+ function utf8Count(str) {
34
+ const strLength = str.length;
35
+ let byteLength = 0;
36
+ let pos = 0;
37
+ while (pos < strLength) {
38
+ let value = str.charCodeAt(pos++);
39
+ if ((value & 0xffffff80) === 0) {
40
+ // 1-byte
41
+ byteLength++;
42
+ continue;
43
+ }
44
+ else if ((value & 0xfffff800) === 0) {
45
+ // 2-bytes
46
+ byteLength += 2;
47
+ }
48
+ else {
49
+ // handle surrogate pair
50
+ if (value >= 0xd800 && value <= 0xdbff) {
51
+ // high surrogate
52
+ if (pos < strLength) {
53
+ const extra = str.charCodeAt(pos);
54
+ if ((extra & 0xfc00) === 0xdc00) {
55
+ ++pos;
56
+ value = ((value & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000;
57
+ }
58
+ }
59
+ }
60
+ if ((value & 0xffff0000) === 0) {
61
+ // 3-byte
62
+ byteLength += 3;
63
+ }
64
+ else {
65
+ // 4-byte
66
+ byteLength += 4;
67
+ }
68
+ }
69
+ }
70
+ return byteLength;
71
+ }
72
+ function utf8EncodeJs(str, output, outputOffset) {
73
+ const strLength = str.length;
74
+ let offset = outputOffset;
75
+ let pos = 0;
76
+ while (pos < strLength) {
77
+ let value = str.charCodeAt(pos++);
78
+ if ((value & 0xffffff80) === 0) {
79
+ // 1-byte
80
+ output[offset++] = value;
81
+ continue;
82
+ }
83
+ else if ((value & 0xfffff800) === 0) {
84
+ // 2-bytes
85
+ output[offset++] = ((value >> 6) & 0x1f) | 0xc0;
86
+ }
87
+ else {
88
+ // handle surrogate pair
89
+ if (value >= 0xd800 && value <= 0xdbff) {
90
+ // high surrogate
91
+ if (pos < strLength) {
92
+ const extra = str.charCodeAt(pos);
93
+ if ((extra & 0xfc00) === 0xdc00) {
94
+ ++pos;
95
+ value = ((value & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000;
96
+ }
97
+ }
98
+ }
99
+ if ((value & 0xffff0000) === 0) {
100
+ // 3-byte
101
+ output[offset++] = ((value >> 12) & 0x0f) | 0xe0;
102
+ output[offset++] = ((value >> 6) & 0x3f) | 0x80;
103
+ }
104
+ else {
105
+ // 4-byte
106
+ output[offset++] = ((value >> 18) & 0x07) | 0xf0;
107
+ output[offset++] = ((value >> 12) & 0x3f) | 0x80;
108
+ output[offset++] = ((value >> 6) & 0x3f) | 0x80;
109
+ }
110
+ }
111
+ output[offset++] = (value & 0x3f) | 0x80;
112
+ }
113
+ }
114
+ // TextEncoder and TextDecoder are standardized in whatwg encoding:
115
+ // https://encoding.spec.whatwg.org/
116
+ // and available in all the modern browsers:
117
+ // https://caniuse.com/textencoder
118
+ // They are available in Node.js since v12 LTS as well:
119
+ // https://nodejs.org/api/globals.html#textencoder
120
+ const sharedTextEncoder = new TextEncoder();
121
+ // This threshold should be determined by benchmarking, which might vary in engines and input data.
122
+ // Run `npx ts-node benchmark/encode-string.ts` for details.
123
+ const TEXT_ENCODER_THRESHOLD = 50;
124
+ function utf8EncodeTE(str, output, outputOffset) {
125
+ sharedTextEncoder.encodeInto(str, output.subarray(outputOffset));
126
+ }
127
+ function utf8Encode(str, output, outputOffset) {
128
+ if (str.length > TEXT_ENCODER_THRESHOLD) {
129
+ utf8EncodeTE(str, output, outputOffset);
130
+ }
131
+ else {
132
+ utf8EncodeJs(str, output, outputOffset);
133
+ }
134
+ }
135
+ const CHUNK_SIZE = 4096;
136
+ function utf8DecodeJs(bytes, inputOffset, byteLength) {
137
+ let offset = inputOffset;
138
+ const end = offset + byteLength;
139
+ const units = [];
140
+ let result = "";
141
+ while (offset < end) {
142
+ const byte1 = bytes[offset++];
143
+ if ((byte1 & 0x80) === 0) {
144
+ // 1 byte
145
+ units.push(byte1);
146
+ }
147
+ else if ((byte1 & 0xe0) === 0xc0) {
148
+ // 2 bytes
149
+ const byte2 = bytes[offset++] & 0x3f;
150
+ units.push(((byte1 & 0x1f) << 6) | byte2);
151
+ }
152
+ else if ((byte1 & 0xf0) === 0xe0) {
153
+ // 3 bytes
154
+ const byte2 = bytes[offset++] & 0x3f;
155
+ const byte3 = bytes[offset++] & 0x3f;
156
+ units.push(((byte1 & 0x1f) << 12) | (byte2 << 6) | byte3);
157
+ }
158
+ else if ((byte1 & 0xf8) === 0xf0) {
159
+ // 4 bytes
160
+ const byte2 = bytes[offset++] & 0x3f;
161
+ const byte3 = bytes[offset++] & 0x3f;
162
+ const byte4 = bytes[offset++] & 0x3f;
163
+ let unit = ((byte1 & 0x07) << 0x12) | (byte2 << 0x0c) | (byte3 << 0x06) | byte4;
164
+ if (unit > 0xffff) {
165
+ unit -= 0x10000;
166
+ units.push(((unit >>> 10) & 0x3ff) | 0xd800);
167
+ unit = 0xdc00 | (unit & 0x3ff);
168
+ }
169
+ units.push(unit);
170
+ }
171
+ else {
172
+ units.push(byte1);
173
+ }
174
+ if (units.length >= CHUNK_SIZE) {
175
+ result += String.fromCharCode(...units);
176
+ units.length = 0;
177
+ }
178
+ }
179
+ if (units.length > 0) {
180
+ result += String.fromCharCode(...units);
181
+ }
182
+ return result;
183
+ }
184
+ const sharedTextDecoder = new TextDecoder();
185
+ // This threshold should be determined by benchmarking, which might vary in engines and input data.
186
+ // Run `npx ts-node benchmark/decode-string.ts` for details.
187
+ const TEXT_DECODER_THRESHOLD = 200;
188
+ function utf8DecodeTD(bytes, inputOffset, byteLength) {
189
+ const stringBytes = bytes.subarray(inputOffset, inputOffset + byteLength);
190
+ return sharedTextDecoder.decode(stringBytes);
191
+ }
192
+ function utf8Decode(bytes, inputOffset, byteLength) {
193
+ if (byteLength > TEXT_DECODER_THRESHOLD) {
194
+ return utf8DecodeTD(bytes, inputOffset, byteLength);
195
+ }
196
+ else {
197
+ return utf8DecodeJs(bytes, inputOffset, byteLength);
198
+ }
199
+ }
200
+
201
+ /**
202
+ * ExtData is used to handle Extension Types that are not registered to ExtensionCodec.
203
+ */
204
+ class ExtData {
205
+ type;
206
+ data;
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
+ static defaultCodec = new ExtensionCodec();
351
+ // ensures ExtensionCodecType<X> matches ExtensionCodec<X>
352
+ // this will make type errors a lot more clear
353
+ // eslint-disable-next-line @typescript-eslint/naming-convention
354
+ __brand;
355
+ // built-in extensions
356
+ builtInEncoders = [];
357
+ builtInDecoders = [];
358
+ // custom extensions
359
+ encoders = [];
360
+ decoders = [];
361
+ constructor() {
362
+ this.register(timestampExtension);
363
+ }
364
+ register({ type, encode, decode, }) {
365
+ if (type >= 0) {
366
+ // custom extensions
367
+ this.encoders[type] = encode;
368
+ this.decoders[type] = decode;
369
+ }
370
+ else {
371
+ // built-in extensions
372
+ const index = -1 - type;
373
+ this.builtInEncoders[index] = encode;
374
+ this.builtInDecoders[index] = decode;
375
+ }
376
+ }
377
+ tryToEncode(object, context) {
378
+ // built-in extensions
379
+ for (let i = 0; i < this.builtInEncoders.length; i++) {
380
+ const encodeExt = this.builtInEncoders[i];
381
+ if (encodeExt != null) {
382
+ const data = encodeExt(object, context);
383
+ if (data != null) {
384
+ const type = -1 - i;
385
+ return new ExtData(type, data);
386
+ }
387
+ }
388
+ }
389
+ // custom extensions
390
+ for (let i = 0; i < this.encoders.length; i++) {
391
+ const encodeExt = this.encoders[i];
392
+ if (encodeExt != null) {
393
+ const data = encodeExt(object, context);
394
+ if (data != null) {
395
+ const type = i;
396
+ return new ExtData(type, data);
397
+ }
398
+ }
399
+ }
400
+ if (object instanceof ExtData) {
401
+ // to keep ExtData as is
402
+ return object;
403
+ }
404
+ return null;
405
+ }
406
+ decode(data, type, context) {
407
+ const decodeExt = type < 0 ? this.builtInDecoders[-1 - type] : this.decoders[type];
408
+ if (decodeExt) {
409
+ return decodeExt(data, type, context);
410
+ }
411
+ else {
412
+ // decode() does not fail, returns ExtData instead.
413
+ return new ExtData(type, data);
414
+ }
415
+ }
416
+ }
417
+
418
+ function isArrayBufferLike(buffer) {
419
+ return (buffer instanceof ArrayBuffer || (typeof SharedArrayBuffer !== "undefined" && buffer instanceof SharedArrayBuffer));
420
+ }
421
+ function ensureUint8Array(buffer) {
422
+ if (buffer instanceof Uint8Array) {
423
+ return buffer;
424
+ }
425
+ else if (ArrayBuffer.isView(buffer)) {
426
+ return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
427
+ }
428
+ else if (isArrayBufferLike(buffer)) {
429
+ return new Uint8Array(buffer);
430
+ }
431
+ else {
432
+ // ArrayLike<number>
433
+ return Uint8Array.from(buffer);
434
+ }
435
+ }
436
+
437
+ const DEFAULT_MAX_DEPTH = 100;
438
+ const DEFAULT_INITIAL_BUFFER_SIZE = 2048;
439
+ class Encoder {
440
+ extensionCodec;
441
+ context;
442
+ useBigInt64;
443
+ maxDepth;
444
+ initialBufferSize;
445
+ sortKeys;
446
+ forceFloat32;
447
+ ignoreUndefined;
448
+ forceIntegerToFloat;
449
+ pos;
450
+ view;
451
+ bytes;
452
+ entered = false;
453
+ constructor(options) {
454
+ this.extensionCodec = options?.extensionCodec ?? ExtensionCodec.defaultCodec;
455
+ this.context = options?.context; // needs a type assertion because EncoderOptions has no context property when ContextType is undefined
456
+ this.useBigInt64 = options?.useBigInt64 ?? false;
457
+ this.maxDepth = options?.maxDepth ?? DEFAULT_MAX_DEPTH;
458
+ this.initialBufferSize = options?.initialBufferSize ?? DEFAULT_INITIAL_BUFFER_SIZE;
459
+ this.sortKeys = options?.sortKeys ?? false;
460
+ this.forceFloat32 = options?.forceFloat32 ?? false;
461
+ this.ignoreUndefined = options?.ignoreUndefined ?? false;
462
+ this.forceIntegerToFloat = options?.forceIntegerToFloat ?? false;
463
+ this.pos = 0;
464
+ this.view = new DataView(new ArrayBuffer(this.initialBufferSize));
465
+ this.bytes = new Uint8Array(this.view.buffer);
466
+ }
467
+ clone() {
468
+ // Because of slightly special argument `context`,
469
+ // type assertion is needed.
470
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
471
+ return new Encoder({
472
+ extensionCodec: this.extensionCodec,
473
+ context: this.context,
474
+ useBigInt64: this.useBigInt64,
475
+ maxDepth: this.maxDepth,
476
+ initialBufferSize: this.initialBufferSize,
477
+ sortKeys: this.sortKeys,
478
+ forceFloat32: this.forceFloat32,
479
+ ignoreUndefined: this.ignoreUndefined,
480
+ forceIntegerToFloat: this.forceIntegerToFloat,
481
+ });
482
+ }
483
+ reinitializeState() {
484
+ this.pos = 0;
485
+ }
486
+ /**
487
+ * 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}.
488
+ *
489
+ * @returns Encodes the object and returns a shared reference the encoder's internal buffer.
490
+ */
491
+ encodeSharedRef(object) {
492
+ if (this.entered) {
493
+ const instance = this.clone();
494
+ return instance.encodeSharedRef(object);
495
+ }
496
+ try {
497
+ this.entered = true;
498
+ this.reinitializeState();
499
+ this.doEncode(object, 1);
500
+ return this.bytes.subarray(0, this.pos);
501
+ }
502
+ finally {
503
+ this.entered = false;
504
+ }
505
+ }
506
+ /**
507
+ * @returns Encodes the object and returns a copy of the encoder's internal buffer.
508
+ */
509
+ encode(object) {
510
+ if (this.entered) {
511
+ const instance = this.clone();
512
+ return instance.encode(object);
513
+ }
514
+ try {
515
+ this.entered = true;
516
+ this.reinitializeState();
517
+ this.doEncode(object, 1);
518
+ return this.bytes.slice(0, this.pos);
519
+ }
520
+ finally {
521
+ this.entered = false;
522
+ }
523
+ }
524
+ doEncode(object, depth) {
525
+ if (depth > this.maxDepth) {
526
+ throw new Error(`Too deep objects in depth ${depth}`);
527
+ }
528
+ if (object == null) {
529
+ this.encodeNil();
530
+ }
531
+ else if (typeof object === "boolean") {
532
+ this.encodeBoolean(object);
533
+ }
534
+ else if (typeof object === "number") {
535
+ if (!this.forceIntegerToFloat) {
536
+ this.encodeNumber(object);
537
+ }
538
+ else {
539
+ this.encodeNumberAsFloat(object);
540
+ }
541
+ }
542
+ else if (typeof object === "string") {
543
+ this.encodeString(object);
544
+ }
545
+ else if (this.useBigInt64 && typeof object === "bigint") {
546
+ this.encodeBigInt64(object);
547
+ }
548
+ else {
549
+ this.encodeObject(object, depth);
550
+ }
551
+ }
552
+ ensureBufferSizeToWrite(sizeToWrite) {
553
+ const requiredSize = this.pos + sizeToWrite;
554
+ if (this.view.byteLength < requiredSize) {
555
+ this.resizeBuffer(requiredSize * 2);
556
+ }
557
+ }
558
+ resizeBuffer(newSize) {
559
+ const newBuffer = new ArrayBuffer(newSize);
560
+ const newBytes = new Uint8Array(newBuffer);
561
+ const newView = new DataView(newBuffer);
562
+ newBytes.set(this.bytes);
563
+ this.view = newView;
564
+ this.bytes = newBytes;
565
+ }
566
+ encodeNil() {
567
+ this.writeU8(0xc0);
568
+ }
569
+ encodeBoolean(object) {
570
+ if (object === false) {
571
+ this.writeU8(0xc2);
572
+ }
573
+ else {
574
+ this.writeU8(0xc3);
575
+ }
576
+ }
577
+ encodeNumber(object) {
578
+ if (!this.forceIntegerToFloat && Number.isSafeInteger(object)) {
579
+ if (object >= 0) {
580
+ if (object < 0x80) {
581
+ // positive fixint
582
+ this.writeU8(object);
583
+ }
584
+ else if (object < 0x100) {
585
+ // uint 8
586
+ this.writeU8(0xcc);
587
+ this.writeU8(object);
588
+ }
589
+ else if (object < 0x10000) {
590
+ // uint 16
591
+ this.writeU8(0xcd);
592
+ this.writeU16(object);
593
+ }
594
+ else if (object < 0x100000000) {
595
+ // uint 32
596
+ this.writeU8(0xce);
597
+ this.writeU32(object);
598
+ }
599
+ else if (!this.useBigInt64) {
600
+ // uint 64
601
+ this.writeU8(0xcf);
602
+ this.writeU64(object);
603
+ }
604
+ else {
605
+ this.encodeNumberAsFloat(object);
606
+ }
607
+ }
608
+ else {
609
+ if (object >= -32) {
610
+ // negative fixint
611
+ this.writeU8(0xe0 | (object + 0x20));
612
+ }
613
+ else if (object >= -128) {
614
+ // int 8
615
+ this.writeU8(0xd0);
616
+ this.writeI8(object);
617
+ }
618
+ else if (object >= -32768) {
619
+ // int 16
620
+ this.writeU8(0xd1);
621
+ this.writeI16(object);
622
+ }
623
+ else if (object >= -2147483648) {
624
+ // int 32
625
+ this.writeU8(0xd2);
626
+ this.writeI32(object);
627
+ }
628
+ else if (!this.useBigInt64) {
629
+ // int 64
630
+ this.writeU8(0xd3);
631
+ this.writeI64(object);
632
+ }
633
+ else {
634
+ this.encodeNumberAsFloat(object);
635
+ }
636
+ }
637
+ }
638
+ else {
639
+ this.encodeNumberAsFloat(object);
640
+ }
641
+ }
642
+ encodeNumberAsFloat(object) {
643
+ if (this.forceFloat32) {
644
+ // float 32
645
+ this.writeU8(0xca);
646
+ this.writeF32(object);
647
+ }
648
+ else {
649
+ // float 64
650
+ this.writeU8(0xcb);
651
+ this.writeF64(object);
652
+ }
653
+ }
654
+ encodeBigInt64(object) {
655
+ if (object >= BigInt(0)) {
656
+ // uint 64
657
+ this.writeU8(0xcf);
658
+ this.writeBigUint64(object);
659
+ }
660
+ else {
661
+ // int 64
662
+ this.writeU8(0xd3);
663
+ this.writeBigInt64(object);
664
+ }
665
+ }
666
+ writeStringHeader(byteLength) {
667
+ if (byteLength < 32) {
668
+ // fixstr
669
+ this.writeU8(0xa0 + byteLength);
670
+ }
671
+ else if (byteLength < 0x100) {
672
+ // str 8
673
+ this.writeU8(0xd9);
674
+ this.writeU8(byteLength);
675
+ }
676
+ else if (byteLength < 0x10000) {
677
+ // str 16
678
+ this.writeU8(0xda);
679
+ this.writeU16(byteLength);
680
+ }
681
+ else if (byteLength < 0x100000000) {
682
+ // str 32
683
+ this.writeU8(0xdb);
684
+ this.writeU32(byteLength);
685
+ }
686
+ else {
687
+ throw new Error(`Too long string: ${byteLength} bytes in UTF-8`);
688
+ }
689
+ }
690
+ encodeString(object) {
691
+ const maxHeaderSize = 1 + 4;
692
+ const byteLength = utf8Count(object);
693
+ this.ensureBufferSizeToWrite(maxHeaderSize + byteLength);
694
+ this.writeStringHeader(byteLength);
695
+ utf8Encode(object, this.bytes, this.pos);
696
+ this.pos += byteLength;
697
+ }
698
+ encodeObject(object, depth) {
699
+ // try to encode objects with custom codec first of non-primitives
700
+ const ext = this.extensionCodec.tryToEncode(object, this.context);
701
+ if (ext != null) {
702
+ this.encodeExtension(ext);
703
+ }
704
+ else if (Array.isArray(object)) {
705
+ this.encodeArray(object, depth);
706
+ }
707
+ else if (ArrayBuffer.isView(object)) {
708
+ this.encodeBinary(object);
709
+ }
710
+ else if (typeof object === "object") {
711
+ this.encodeMap(object, depth);
712
+ }
713
+ else {
714
+ // symbol, function and other special object come here unless extensionCodec handles them.
715
+ throw new Error(`Unrecognized object: ${Object.prototype.toString.apply(object)}`);
716
+ }
717
+ }
718
+ encodeBinary(object) {
719
+ const size = object.byteLength;
720
+ if (size < 0x100) {
721
+ // bin 8
722
+ this.writeU8(0xc4);
723
+ this.writeU8(size);
724
+ }
725
+ else if (size < 0x10000) {
726
+ // bin 16
727
+ this.writeU8(0xc5);
728
+ this.writeU16(size);
729
+ }
730
+ else if (size < 0x100000000) {
731
+ // bin 32
732
+ this.writeU8(0xc6);
733
+ this.writeU32(size);
734
+ }
735
+ else {
736
+ throw new Error(`Too large binary: ${size}`);
737
+ }
738
+ const bytes = ensureUint8Array(object);
739
+ this.writeU8a(bytes);
740
+ }
741
+ encodeArray(object, depth) {
742
+ const size = object.length;
743
+ if (size < 16) {
744
+ // fixarray
745
+ this.writeU8(0x90 + size);
746
+ }
747
+ else if (size < 0x10000) {
748
+ // array 16
749
+ this.writeU8(0xdc);
750
+ this.writeU16(size);
751
+ }
752
+ else if (size < 0x100000000) {
753
+ // array 32
754
+ this.writeU8(0xdd);
755
+ this.writeU32(size);
756
+ }
757
+ else {
758
+ throw new Error(`Too large array: ${size}`);
759
+ }
760
+ for (const item of object) {
761
+ this.doEncode(item, depth + 1);
762
+ }
763
+ }
764
+ countWithoutUndefined(object, keys) {
765
+ let count = 0;
766
+ for (const key of keys) {
767
+ if (object[key] !== undefined) {
768
+ count++;
769
+ }
770
+ }
771
+ return count;
772
+ }
773
+ encodeMap(object, depth) {
774
+ const keys = Object.keys(object);
775
+ if (this.sortKeys) {
776
+ keys.sort();
777
+ }
778
+ const size = this.ignoreUndefined ? this.countWithoutUndefined(object, keys) : keys.length;
779
+ if (size < 16) {
780
+ // fixmap
781
+ this.writeU8(0x80 + size);
782
+ }
783
+ else if (size < 0x10000) {
784
+ // map 16
785
+ this.writeU8(0xde);
786
+ this.writeU16(size);
787
+ }
788
+ else if (size < 0x100000000) {
789
+ // map 32
790
+ this.writeU8(0xdf);
791
+ this.writeU32(size);
792
+ }
793
+ else {
794
+ throw new Error(`Too large map object: ${size}`);
795
+ }
796
+ for (const key of keys) {
797
+ const value = object[key];
798
+ if (!(this.ignoreUndefined && value === undefined)) {
799
+ this.encodeString(key);
800
+ this.doEncode(value, depth + 1);
801
+ }
802
+ }
803
+ }
804
+ encodeExtension(ext) {
805
+ if (typeof ext.data === "function") {
806
+ const data = ext.data(this.pos + 6);
807
+ const size = data.length;
808
+ if (size >= 0x100000000) {
809
+ throw new Error(`Too large extension object: ${size}`);
810
+ }
811
+ this.writeU8(0xc9);
812
+ this.writeU32(size);
813
+ this.writeI8(ext.type);
814
+ this.writeU8a(data);
815
+ return;
816
+ }
817
+ const size = ext.data.length;
818
+ if (size === 1) {
819
+ // fixext 1
820
+ this.writeU8(0xd4);
821
+ }
822
+ else if (size === 2) {
823
+ // fixext 2
824
+ this.writeU8(0xd5);
825
+ }
826
+ else if (size === 4) {
827
+ // fixext 4
828
+ this.writeU8(0xd6);
829
+ }
830
+ else if (size === 8) {
831
+ // fixext 8
832
+ this.writeU8(0xd7);
833
+ }
834
+ else if (size === 16) {
835
+ // fixext 16
836
+ this.writeU8(0xd8);
837
+ }
838
+ else if (size < 0x100) {
839
+ // ext 8
840
+ this.writeU8(0xc7);
841
+ this.writeU8(size);
842
+ }
843
+ else if (size < 0x10000) {
844
+ // ext 16
845
+ this.writeU8(0xc8);
846
+ this.writeU16(size);
847
+ }
848
+ else if (size < 0x100000000) {
849
+ // ext 32
850
+ this.writeU8(0xc9);
851
+ this.writeU32(size);
852
+ }
853
+ else {
854
+ throw new Error(`Too large extension object: ${size}`);
855
+ }
856
+ this.writeI8(ext.type);
857
+ this.writeU8a(ext.data);
858
+ }
859
+ writeU8(value) {
860
+ this.ensureBufferSizeToWrite(1);
861
+ this.view.setUint8(this.pos, value);
862
+ this.pos++;
863
+ }
864
+ writeU8a(values) {
865
+ const size = values.length;
866
+ this.ensureBufferSizeToWrite(size);
867
+ this.bytes.set(values, this.pos);
868
+ this.pos += size;
869
+ }
870
+ writeI8(value) {
871
+ this.ensureBufferSizeToWrite(1);
872
+ this.view.setInt8(this.pos, value);
873
+ this.pos++;
874
+ }
875
+ writeU16(value) {
876
+ this.ensureBufferSizeToWrite(2);
877
+ this.view.setUint16(this.pos, value);
878
+ this.pos += 2;
879
+ }
880
+ writeI16(value) {
881
+ this.ensureBufferSizeToWrite(2);
882
+ this.view.setInt16(this.pos, value);
883
+ this.pos += 2;
884
+ }
885
+ writeU32(value) {
886
+ this.ensureBufferSizeToWrite(4);
887
+ this.view.setUint32(this.pos, value);
888
+ this.pos += 4;
889
+ }
890
+ writeI32(value) {
891
+ this.ensureBufferSizeToWrite(4);
892
+ this.view.setInt32(this.pos, value);
893
+ this.pos += 4;
894
+ }
895
+ writeF32(value) {
896
+ this.ensureBufferSizeToWrite(4);
897
+ this.view.setFloat32(this.pos, value);
898
+ this.pos += 4;
899
+ }
900
+ writeF64(value) {
901
+ this.ensureBufferSizeToWrite(8);
902
+ this.view.setFloat64(this.pos, value);
903
+ this.pos += 8;
904
+ }
905
+ writeU64(value) {
906
+ this.ensureBufferSizeToWrite(8);
907
+ setUint64(this.view, this.pos, value);
908
+ this.pos += 8;
909
+ }
910
+ writeI64(value) {
911
+ this.ensureBufferSizeToWrite(8);
912
+ setInt64(this.view, this.pos, value);
913
+ this.pos += 8;
914
+ }
915
+ writeBigUint64(value) {
916
+ this.ensureBufferSizeToWrite(8);
917
+ this.view.setBigUint64(this.pos, value);
918
+ this.pos += 8;
919
+ }
920
+ writeBigInt64(value) {
921
+ this.ensureBufferSizeToWrite(8);
922
+ this.view.setBigInt64(this.pos, value);
923
+ this.pos += 8;
924
+ }
925
+ }
926
+
927
+ /**
928
+ * It encodes `value` in the MessagePack format and
929
+ * returns a byte buffer.
930
+ *
931
+ * 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`.
932
+ */
933
+ function encode(value, options) {
934
+ const encoder = new Encoder(options);
935
+ return encoder.encodeSharedRef(value);
936
+ }
937
+
938
+ function prettyByte(byte) {
939
+ return `${byte < 0 ? "-" : ""}0x${Math.abs(byte).toString(16).padStart(2, "0")}`;
940
+ }
941
+
942
+ const DEFAULT_MAX_KEY_LENGTH = 16;
943
+ const DEFAULT_MAX_LENGTH_PER_KEY = 16;
944
+ class CachedKeyDecoder {
945
+ hit = 0;
946
+ miss = 0;
947
+ caches;
948
+ maxKeyLength;
949
+ maxLengthPerKey;
950
+ constructor(maxKeyLength = DEFAULT_MAX_KEY_LENGTH, maxLengthPerKey = DEFAULT_MAX_LENGTH_PER_KEY) {
951
+ this.maxKeyLength = maxKeyLength;
952
+ this.maxLengthPerKey = maxLengthPerKey;
953
+ // avoid `new Array(N)`, which makes a sparse array,
954
+ // because a sparse array is typically slower than a non-sparse array.
955
+ this.caches = [];
956
+ for (let i = 0; i < this.maxKeyLength; i++) {
957
+ this.caches.push([]);
958
+ }
959
+ }
960
+ canBeCached(byteLength) {
961
+ return byteLength > 0 && byteLength <= this.maxKeyLength;
962
+ }
963
+ find(bytes, inputOffset, byteLength) {
964
+ const records = this.caches[byteLength - 1];
965
+ FIND_CHUNK: for (const record of records) {
966
+ const recordBytes = record.bytes;
967
+ for (let j = 0; j < byteLength; j++) {
968
+ if (recordBytes[j] !== bytes[inputOffset + j]) {
969
+ continue FIND_CHUNK;
970
+ }
971
+ }
972
+ return record.str;
973
+ }
974
+ return null;
975
+ }
976
+ store(bytes, value) {
977
+ const records = this.caches[bytes.length - 1];
978
+ const record = { bytes, str: value };
979
+ if (records.length >= this.maxLengthPerKey) {
980
+ // `records` are full!
981
+ // Set `record` to an arbitrary position.
982
+ records[(Math.random() * records.length) | 0] = record;
983
+ }
984
+ else {
985
+ records.push(record);
986
+ }
987
+ }
988
+ decode(bytes, inputOffset, byteLength) {
989
+ const cachedValue = this.find(bytes, inputOffset, byteLength);
990
+ if (cachedValue != null) {
991
+ this.hit++;
992
+ return cachedValue;
993
+ }
994
+ this.miss++;
995
+ const str = utf8DecodeJs(bytes, inputOffset, byteLength);
996
+ // 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.
997
+ const slicedCopyOfBytes = Uint8Array.prototype.slice.call(bytes, inputOffset, inputOffset + byteLength);
998
+ this.store(slicedCopyOfBytes, str);
999
+ return str;
1000
+ }
1001
+ }
1002
+
1003
+ const STATE_ARRAY = "array";
1004
+ const STATE_MAP_KEY = "map_key";
1005
+ const STATE_MAP_VALUE = "map_value";
1006
+ const mapKeyConverter = (key) => {
1007
+ if (typeof key === "string" || typeof key === "number") {
1008
+ return key;
1009
+ }
1010
+ throw new DecodeError("The type of key must be string or number but " + typeof key);
1011
+ };
1012
+ class StackPool {
1013
+ stack = [];
1014
+ stackHeadPosition = -1;
1015
+ get length() {
1016
+ return this.stackHeadPosition + 1;
1017
+ }
1018
+ top() {
1019
+ return this.stack[this.stackHeadPosition];
1020
+ }
1021
+ pushArrayState(size) {
1022
+ const state = this.getUninitializedStateFromPool();
1023
+ state.type = STATE_ARRAY;
1024
+ state.position = 0;
1025
+ state.size = size;
1026
+ state.array = new Array(size);
1027
+ }
1028
+ pushMapState(size) {
1029
+ const state = this.getUninitializedStateFromPool();
1030
+ state.type = STATE_MAP_KEY;
1031
+ state.readCount = 0;
1032
+ state.size = size;
1033
+ state.map = {};
1034
+ }
1035
+ getUninitializedStateFromPool() {
1036
+ this.stackHeadPosition++;
1037
+ if (this.stackHeadPosition === this.stack.length) {
1038
+ const partialState = {
1039
+ type: undefined,
1040
+ size: 0,
1041
+ array: undefined,
1042
+ position: 0,
1043
+ readCount: 0,
1044
+ map: undefined,
1045
+ key: null,
1046
+ };
1047
+ this.stack.push(partialState);
1048
+ }
1049
+ return this.stack[this.stackHeadPosition];
1050
+ }
1051
+ release(state) {
1052
+ const topStackState = this.stack[this.stackHeadPosition];
1053
+ if (topStackState !== state) {
1054
+ throw new Error("Invalid stack state. Released state is not on top of the stack.");
1055
+ }
1056
+ if (state.type === STATE_ARRAY) {
1057
+ const partialState = state;
1058
+ partialState.size = 0;
1059
+ partialState.array = undefined;
1060
+ partialState.position = 0;
1061
+ partialState.type = undefined;
1062
+ }
1063
+ if (state.type === STATE_MAP_KEY || state.type === STATE_MAP_VALUE) {
1064
+ const partialState = state;
1065
+ partialState.size = 0;
1066
+ partialState.map = undefined;
1067
+ partialState.readCount = 0;
1068
+ partialState.type = undefined;
1069
+ }
1070
+ this.stackHeadPosition--;
1071
+ }
1072
+ reset() {
1073
+ this.stack.length = 0;
1074
+ this.stackHeadPosition = -1;
1075
+ }
1076
+ }
1077
+ const HEAD_BYTE_REQUIRED = -1;
1078
+ const EMPTY_VIEW = new DataView(new ArrayBuffer(0));
1079
+ const EMPTY_BYTES = new Uint8Array(EMPTY_VIEW.buffer);
1080
+ try {
1081
+ // IE11: The spec says it should throw RangeError,
1082
+ // IE11: but in IE11 it throws TypeError.
1083
+ EMPTY_VIEW.getInt8(0);
1084
+ }
1085
+ catch (e) {
1086
+ if (!(e instanceof RangeError)) {
1087
+ throw new Error("This module is not supported in the current JavaScript engine because DataView does not throw RangeError on out-of-bounds access");
1088
+ }
1089
+ }
1090
+ const MORE_DATA = new RangeError("Insufficient data");
1091
+ const sharedCachedKeyDecoder = new CachedKeyDecoder();
1092
+ class Decoder {
1093
+ extensionCodec;
1094
+ context;
1095
+ useBigInt64;
1096
+ rawStrings;
1097
+ maxStrLength;
1098
+ maxBinLength;
1099
+ maxArrayLength;
1100
+ maxMapLength;
1101
+ maxExtLength;
1102
+ keyDecoder;
1103
+ mapKeyConverter;
1104
+ totalPos = 0;
1105
+ pos = 0;
1106
+ view = EMPTY_VIEW;
1107
+ bytes = EMPTY_BYTES;
1108
+ headByte = HEAD_BYTE_REQUIRED;
1109
+ stack = new StackPool();
1110
+ entered = false;
1111
+ constructor(options) {
1112
+ this.extensionCodec = options?.extensionCodec ?? ExtensionCodec.defaultCodec;
1113
+ this.context = options?.context; // needs a type assertion because EncoderOptions has no context property when ContextType is undefined
1114
+ this.useBigInt64 = options?.useBigInt64 ?? false;
1115
+ this.rawStrings = options?.rawStrings ?? false;
1116
+ this.maxStrLength = options?.maxStrLength ?? UINT32_MAX;
1117
+ this.maxBinLength = options?.maxBinLength ?? UINT32_MAX;
1118
+ this.maxArrayLength = options?.maxArrayLength ?? UINT32_MAX;
1119
+ this.maxMapLength = options?.maxMapLength ?? UINT32_MAX;
1120
+ this.maxExtLength = options?.maxExtLength ?? UINT32_MAX;
1121
+ this.keyDecoder = options?.keyDecoder !== undefined ? options.keyDecoder : sharedCachedKeyDecoder;
1122
+ this.mapKeyConverter = options?.mapKeyConverter ?? mapKeyConverter;
1123
+ }
1124
+ clone() {
1125
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
1126
+ return new Decoder({
1127
+ extensionCodec: this.extensionCodec,
1128
+ context: this.context,
1129
+ useBigInt64: this.useBigInt64,
1130
+ rawStrings: this.rawStrings,
1131
+ maxStrLength: this.maxStrLength,
1132
+ maxBinLength: this.maxBinLength,
1133
+ maxArrayLength: this.maxArrayLength,
1134
+ maxMapLength: this.maxMapLength,
1135
+ maxExtLength: this.maxExtLength,
1136
+ keyDecoder: this.keyDecoder,
1137
+ });
1138
+ }
1139
+ reinitializeState() {
1140
+ this.totalPos = 0;
1141
+ this.headByte = HEAD_BYTE_REQUIRED;
1142
+ this.stack.reset();
1143
+ // view, bytes, and pos will be re-initialized in setBuffer()
1144
+ }
1145
+ setBuffer(buffer) {
1146
+ const bytes = ensureUint8Array(buffer);
1147
+ this.bytes = bytes;
1148
+ this.view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
1149
+ this.pos = 0;
1150
+ }
1151
+ appendBuffer(buffer) {
1152
+ if (this.headByte === HEAD_BYTE_REQUIRED && !this.hasRemaining(1)) {
1153
+ this.setBuffer(buffer);
1154
+ }
1155
+ else {
1156
+ const remainingData = this.bytes.subarray(this.pos);
1157
+ const newData = ensureUint8Array(buffer);
1158
+ // concat remainingData + newData
1159
+ const newBuffer = new Uint8Array(remainingData.length + newData.length);
1160
+ newBuffer.set(remainingData);
1161
+ newBuffer.set(newData, remainingData.length);
1162
+ this.setBuffer(newBuffer);
1163
+ }
1164
+ }
1165
+ hasRemaining(size) {
1166
+ return this.view.byteLength - this.pos >= size;
1167
+ }
1168
+ createExtraByteError(posToShow) {
1169
+ const { view, pos } = this;
1170
+ return new RangeError(`Extra ${view.byteLength - pos} of ${view.byteLength} byte(s) found at buffer[${posToShow}]`);
1171
+ }
1172
+ /**
1173
+ * @throws {@link DecodeError}
1174
+ * @throws {@link RangeError}
1175
+ */
1176
+ decode(buffer) {
1177
+ if (this.entered) {
1178
+ const instance = this.clone();
1179
+ return instance.decode(buffer);
1180
+ }
1181
+ try {
1182
+ this.entered = true;
1183
+ this.reinitializeState();
1184
+ this.setBuffer(buffer);
1185
+ const object = this.doDecodeSync();
1186
+ if (this.hasRemaining(1)) {
1187
+ throw this.createExtraByteError(this.pos);
1188
+ }
1189
+ return object;
1190
+ }
1191
+ finally {
1192
+ this.entered = false;
1193
+ }
1194
+ }
1195
+ *decodeMulti(buffer) {
1196
+ if (this.entered) {
1197
+ const instance = this.clone();
1198
+ yield* instance.decodeMulti(buffer);
1199
+ return;
1200
+ }
1201
+ try {
1202
+ this.entered = true;
1203
+ this.reinitializeState();
1204
+ this.setBuffer(buffer);
1205
+ while (this.hasRemaining(1)) {
1206
+ yield this.doDecodeSync();
1207
+ }
1208
+ }
1209
+ finally {
1210
+ this.entered = false;
1211
+ }
1212
+ }
1213
+ async decodeAsync(stream) {
1214
+ if (this.entered) {
1215
+ const instance = this.clone();
1216
+ return instance.decodeAsync(stream);
1217
+ }
1218
+ try {
1219
+ this.entered = true;
1220
+ let decoded = false;
1221
+ let object;
1222
+ for await (const buffer of stream) {
1223
+ if (decoded) {
1224
+ this.entered = false;
1225
+ throw this.createExtraByteError(this.totalPos);
1226
+ }
1227
+ this.appendBuffer(buffer);
1228
+ try {
1229
+ object = this.doDecodeSync();
1230
+ decoded = true;
1231
+ }
1232
+ catch (e) {
1233
+ if (!(e instanceof RangeError)) {
1234
+ throw e; // rethrow
1235
+ }
1236
+ // fallthrough
1237
+ }
1238
+ this.totalPos += this.pos;
1239
+ }
1240
+ if (decoded) {
1241
+ if (this.hasRemaining(1)) {
1242
+ throw this.createExtraByteError(this.totalPos);
1243
+ }
1244
+ return object;
1245
+ }
1246
+ const { headByte, pos, totalPos } = this;
1247
+ throw new RangeError(`Insufficient data in parsing ${prettyByte(headByte)} at ${totalPos} (${pos} in the current buffer)`);
1248
+ }
1249
+ finally {
1250
+ this.entered = false;
1251
+ }
1252
+ }
1253
+ decodeArrayStream(stream) {
1254
+ return this.decodeMultiAsync(stream, true);
1255
+ }
1256
+ decodeStream(stream) {
1257
+ return this.decodeMultiAsync(stream, false);
1258
+ }
1259
+ async *decodeMultiAsync(stream, isArray) {
1260
+ if (this.entered) {
1261
+ const instance = this.clone();
1262
+ yield* instance.decodeMultiAsync(stream, isArray);
1263
+ return;
1264
+ }
1265
+ try {
1266
+ this.entered = true;
1267
+ let isArrayHeaderRequired = isArray;
1268
+ let arrayItemsLeft = -1;
1269
+ for await (const buffer of stream) {
1270
+ if (isArray && arrayItemsLeft === 0) {
1271
+ throw this.createExtraByteError(this.totalPos);
1272
+ }
1273
+ this.appendBuffer(buffer);
1274
+ if (isArrayHeaderRequired) {
1275
+ arrayItemsLeft = this.readArraySize();
1276
+ isArrayHeaderRequired = false;
1277
+ this.complete();
1278
+ }
1279
+ try {
1280
+ while (true) {
1281
+ yield this.doDecodeSync();
1282
+ if (--arrayItemsLeft === 0) {
1283
+ break;
1284
+ }
1285
+ }
1286
+ }
1287
+ catch (e) {
1288
+ if (!(e instanceof RangeError)) {
1289
+ throw e; // rethrow
1290
+ }
1291
+ // fallthrough
1292
+ }
1293
+ this.totalPos += this.pos;
1294
+ }
1295
+ }
1296
+ finally {
1297
+ this.entered = false;
1298
+ }
1299
+ }
1300
+ doDecodeSync() {
1301
+ DECODE: while (true) {
1302
+ const headByte = this.readHeadByte();
1303
+ let object;
1304
+ if (headByte >= 0xe0) {
1305
+ // negative fixint (111x xxxx) 0xe0 - 0xff
1306
+ object = headByte - 0x100;
1307
+ }
1308
+ else if (headByte < 0xc0) {
1309
+ if (headByte < 0x80) {
1310
+ // positive fixint (0xxx xxxx) 0x00 - 0x7f
1311
+ object = headByte;
1312
+ }
1313
+ else if (headByte < 0x90) {
1314
+ // fixmap (1000 xxxx) 0x80 - 0x8f
1315
+ const size = headByte - 0x80;
1316
+ if (size !== 0) {
1317
+ this.pushMapState(size);
1318
+ this.complete();
1319
+ continue DECODE;
1320
+ }
1321
+ else {
1322
+ object = {};
1323
+ }
1324
+ }
1325
+ else if (headByte < 0xa0) {
1326
+ // fixarray (1001 xxxx) 0x90 - 0x9f
1327
+ const size = headByte - 0x90;
1328
+ if (size !== 0) {
1329
+ this.pushArrayState(size);
1330
+ this.complete();
1331
+ continue DECODE;
1332
+ }
1333
+ else {
1334
+ object = [];
1335
+ }
1336
+ }
1337
+ else {
1338
+ // fixstr (101x xxxx) 0xa0 - 0xbf
1339
+ const byteLength = headByte - 0xa0;
1340
+ object = this.decodeString(byteLength, 0);
1341
+ }
1342
+ }
1343
+ else if (headByte === 0xc0) {
1344
+ // nil
1345
+ object = null;
1346
+ }
1347
+ else if (headByte === 0xc2) {
1348
+ // false
1349
+ object = false;
1350
+ }
1351
+ else if (headByte === 0xc3) {
1352
+ // true
1353
+ object = true;
1354
+ }
1355
+ else if (headByte === 0xca) {
1356
+ // float 32
1357
+ object = this.readF32();
1358
+ }
1359
+ else if (headByte === 0xcb) {
1360
+ // float 64
1361
+ object = this.readF64();
1362
+ }
1363
+ else if (headByte === 0xcc) {
1364
+ // uint 8
1365
+ object = this.readU8();
1366
+ }
1367
+ else if (headByte === 0xcd) {
1368
+ // uint 16
1369
+ object = this.readU16();
1370
+ }
1371
+ else if (headByte === 0xce) {
1372
+ // uint 32
1373
+ object = this.readU32();
1374
+ }
1375
+ else if (headByte === 0xcf) {
1376
+ // uint 64
1377
+ if (this.useBigInt64) {
1378
+ object = this.readU64AsBigInt();
1379
+ }
1380
+ else {
1381
+ object = this.readU64();
1382
+ }
1383
+ }
1384
+ else if (headByte === 0xd0) {
1385
+ // int 8
1386
+ object = this.readI8();
1387
+ }
1388
+ else if (headByte === 0xd1) {
1389
+ // int 16
1390
+ object = this.readI16();
1391
+ }
1392
+ else if (headByte === 0xd2) {
1393
+ // int 32
1394
+ object = this.readI32();
1395
+ }
1396
+ else if (headByte === 0xd3) {
1397
+ // int 64
1398
+ if (this.useBigInt64) {
1399
+ object = this.readI64AsBigInt();
1400
+ }
1401
+ else {
1402
+ object = this.readI64();
1403
+ }
1404
+ }
1405
+ else if (headByte === 0xd9) {
1406
+ // str 8
1407
+ const byteLength = this.lookU8();
1408
+ object = this.decodeString(byteLength, 1);
1409
+ }
1410
+ else if (headByte === 0xda) {
1411
+ // str 16
1412
+ const byteLength = this.lookU16();
1413
+ object = this.decodeString(byteLength, 2);
1414
+ }
1415
+ else if (headByte === 0xdb) {
1416
+ // str 32
1417
+ const byteLength = this.lookU32();
1418
+ object = this.decodeString(byteLength, 4);
1419
+ }
1420
+ else if (headByte === 0xdc) {
1421
+ // array 16
1422
+ const size = this.readU16();
1423
+ if (size !== 0) {
1424
+ this.pushArrayState(size);
1425
+ this.complete();
1426
+ continue DECODE;
1427
+ }
1428
+ else {
1429
+ object = [];
1430
+ }
1431
+ }
1432
+ else if (headByte === 0xdd) {
1433
+ // array 32
1434
+ const size = this.readU32();
1435
+ if (size !== 0) {
1436
+ this.pushArrayState(size);
1437
+ this.complete();
1438
+ continue DECODE;
1439
+ }
1440
+ else {
1441
+ object = [];
1442
+ }
1443
+ }
1444
+ else if (headByte === 0xde) {
1445
+ // map 16
1446
+ const size = this.readU16();
1447
+ if (size !== 0) {
1448
+ this.pushMapState(size);
1449
+ this.complete();
1450
+ continue DECODE;
1451
+ }
1452
+ else {
1453
+ object = {};
1454
+ }
1455
+ }
1456
+ else if (headByte === 0xdf) {
1457
+ // map 32
1458
+ const size = this.readU32();
1459
+ if (size !== 0) {
1460
+ this.pushMapState(size);
1461
+ this.complete();
1462
+ continue DECODE;
1463
+ }
1464
+ else {
1465
+ object = {};
1466
+ }
1467
+ }
1468
+ else if (headByte === 0xc4) {
1469
+ // bin 8
1470
+ const size = this.lookU8();
1471
+ object = this.decodeBinary(size, 1);
1472
+ }
1473
+ else if (headByte === 0xc5) {
1474
+ // bin 16
1475
+ const size = this.lookU16();
1476
+ object = this.decodeBinary(size, 2);
1477
+ }
1478
+ else if (headByte === 0xc6) {
1479
+ // bin 32
1480
+ const size = this.lookU32();
1481
+ object = this.decodeBinary(size, 4);
1482
+ }
1483
+ else if (headByte === 0xd4) {
1484
+ // fixext 1
1485
+ object = this.decodeExtension(1, 0);
1486
+ }
1487
+ else if (headByte === 0xd5) {
1488
+ // fixext 2
1489
+ object = this.decodeExtension(2, 0);
1490
+ }
1491
+ else if (headByte === 0xd6) {
1492
+ // fixext 4
1493
+ object = this.decodeExtension(4, 0);
1494
+ }
1495
+ else if (headByte === 0xd7) {
1496
+ // fixext 8
1497
+ object = this.decodeExtension(8, 0);
1498
+ }
1499
+ else if (headByte === 0xd8) {
1500
+ // fixext 16
1501
+ object = this.decodeExtension(16, 0);
1502
+ }
1503
+ else if (headByte === 0xc7) {
1504
+ // ext 8
1505
+ const size = this.lookU8();
1506
+ object = this.decodeExtension(size, 1);
1507
+ }
1508
+ else if (headByte === 0xc8) {
1509
+ // ext 16
1510
+ const size = this.lookU16();
1511
+ object = this.decodeExtension(size, 2);
1512
+ }
1513
+ else if (headByte === 0xc9) {
1514
+ // ext 32
1515
+ const size = this.lookU32();
1516
+ object = this.decodeExtension(size, 4);
1517
+ }
1518
+ else {
1519
+ throw new DecodeError(`Unrecognized type byte: ${prettyByte(headByte)}`);
1520
+ }
1521
+ this.complete();
1522
+ const stack = this.stack;
1523
+ while (stack.length > 0) {
1524
+ // arrays and maps
1525
+ const state = stack.top();
1526
+ if (state.type === STATE_ARRAY) {
1527
+ state.array[state.position] = object;
1528
+ state.position++;
1529
+ if (state.position === state.size) {
1530
+ object = state.array;
1531
+ stack.release(state);
1532
+ }
1533
+ else {
1534
+ continue DECODE;
1535
+ }
1536
+ }
1537
+ else if (state.type === STATE_MAP_KEY) {
1538
+ if (object === "__proto__") {
1539
+ throw new DecodeError("The key __proto__ is not allowed");
1540
+ }
1541
+ state.key = this.mapKeyConverter(object);
1542
+ state.type = STATE_MAP_VALUE;
1543
+ continue DECODE;
1544
+ }
1545
+ else {
1546
+ // it must be `state.type === State.MAP_VALUE` here
1547
+ state.map[state.key] = object;
1548
+ state.readCount++;
1549
+ if (state.readCount === state.size) {
1550
+ object = state.map;
1551
+ stack.release(state);
1552
+ }
1553
+ else {
1554
+ state.key = null;
1555
+ state.type = STATE_MAP_KEY;
1556
+ continue DECODE;
1557
+ }
1558
+ }
1559
+ }
1560
+ return object;
1561
+ }
1562
+ }
1563
+ readHeadByte() {
1564
+ if (this.headByte === HEAD_BYTE_REQUIRED) {
1565
+ this.headByte = this.readU8();
1566
+ // console.log("headByte", prettyByte(this.headByte));
1567
+ }
1568
+ return this.headByte;
1569
+ }
1570
+ complete() {
1571
+ this.headByte = HEAD_BYTE_REQUIRED;
1572
+ }
1573
+ readArraySize() {
1574
+ const headByte = this.readHeadByte();
1575
+ switch (headByte) {
1576
+ case 0xdc:
1577
+ return this.readU16();
1578
+ case 0xdd:
1579
+ return this.readU32();
1580
+ default: {
1581
+ if (headByte < 0xa0) {
1582
+ return headByte - 0x90;
1583
+ }
1584
+ else {
1585
+ throw new DecodeError(`Unrecognized array type byte: ${prettyByte(headByte)}`);
1586
+ }
1587
+ }
1588
+ }
1589
+ }
1590
+ pushMapState(size) {
1591
+ if (size > this.maxMapLength) {
1592
+ throw new DecodeError(`Max length exceeded: map length (${size}) > maxMapLengthLength (${this.maxMapLength})`);
1593
+ }
1594
+ this.stack.pushMapState(size);
1595
+ }
1596
+ pushArrayState(size) {
1597
+ if (size > this.maxArrayLength) {
1598
+ throw new DecodeError(`Max length exceeded: array length (${size}) > maxArrayLength (${this.maxArrayLength})`);
1599
+ }
1600
+ this.stack.pushArrayState(size);
1601
+ }
1602
+ decodeString(byteLength, headerOffset) {
1603
+ if (!this.rawStrings || this.stateIsMapKey()) {
1604
+ return this.decodeUtf8String(byteLength, headerOffset);
1605
+ }
1606
+ return this.decodeBinary(byteLength, headerOffset);
1607
+ }
1608
+ /**
1609
+ * @throws {@link RangeError}
1610
+ */
1611
+ decodeUtf8String(byteLength, headerOffset) {
1612
+ if (byteLength > this.maxStrLength) {
1613
+ throw new DecodeError(`Max length exceeded: UTF-8 byte length (${byteLength}) > maxStrLength (${this.maxStrLength})`);
1614
+ }
1615
+ if (this.bytes.byteLength < this.pos + headerOffset + byteLength) {
1616
+ throw MORE_DATA;
1617
+ }
1618
+ const offset = this.pos + headerOffset;
1619
+ let object;
1620
+ if (this.stateIsMapKey() && this.keyDecoder?.canBeCached(byteLength)) {
1621
+ object = this.keyDecoder.decode(this.bytes, offset, byteLength);
1622
+ }
1623
+ else {
1624
+ object = utf8Decode(this.bytes, offset, byteLength);
1625
+ }
1626
+ this.pos += headerOffset + byteLength;
1627
+ return object;
1628
+ }
1629
+ stateIsMapKey() {
1630
+ if (this.stack.length > 0) {
1631
+ const state = this.stack.top();
1632
+ return state.type === STATE_MAP_KEY;
1633
+ }
1634
+ return false;
1635
+ }
1636
+ /**
1637
+ * @throws {@link RangeError}
1638
+ */
1639
+ decodeBinary(byteLength, headOffset) {
1640
+ if (byteLength > this.maxBinLength) {
1641
+ throw new DecodeError(`Max length exceeded: bin length (${byteLength}) > maxBinLength (${this.maxBinLength})`);
1642
+ }
1643
+ if (!this.hasRemaining(byteLength + headOffset)) {
1644
+ throw MORE_DATA;
1645
+ }
1646
+ const offset = this.pos + headOffset;
1647
+ const object = this.bytes.subarray(offset, offset + byteLength);
1648
+ this.pos += headOffset + byteLength;
1649
+ return object;
1650
+ }
1651
+ decodeExtension(size, headOffset) {
1652
+ if (size > this.maxExtLength) {
1653
+ throw new DecodeError(`Max length exceeded: ext length (${size}) > maxExtLength (${this.maxExtLength})`);
1654
+ }
1655
+ const extType = this.view.getInt8(this.pos + headOffset);
1656
+ const data = this.decodeBinary(size, headOffset + 1 /* extType */);
1657
+ return this.extensionCodec.decode(data, extType, this.context);
1658
+ }
1659
+ lookU8() {
1660
+ return this.view.getUint8(this.pos);
1661
+ }
1662
+ lookU16() {
1663
+ return this.view.getUint16(this.pos);
1664
+ }
1665
+ lookU32() {
1666
+ return this.view.getUint32(this.pos);
1667
+ }
1668
+ readU8() {
1669
+ const value = this.view.getUint8(this.pos);
1670
+ this.pos++;
1671
+ return value;
1672
+ }
1673
+ readI8() {
1674
+ const value = this.view.getInt8(this.pos);
1675
+ this.pos++;
1676
+ return value;
1677
+ }
1678
+ readU16() {
1679
+ const value = this.view.getUint16(this.pos);
1680
+ this.pos += 2;
1681
+ return value;
1682
+ }
1683
+ readI16() {
1684
+ const value = this.view.getInt16(this.pos);
1685
+ this.pos += 2;
1686
+ return value;
1687
+ }
1688
+ readU32() {
1689
+ const value = this.view.getUint32(this.pos);
1690
+ this.pos += 4;
1691
+ return value;
1692
+ }
1693
+ readI32() {
1694
+ const value = this.view.getInt32(this.pos);
1695
+ this.pos += 4;
1696
+ return value;
1697
+ }
1698
+ readU64() {
1699
+ const value = getUint64(this.view, this.pos);
1700
+ this.pos += 8;
1701
+ return value;
1702
+ }
1703
+ readI64() {
1704
+ const value = getInt64(this.view, this.pos);
1705
+ this.pos += 8;
1706
+ return value;
1707
+ }
1708
+ readU64AsBigInt() {
1709
+ const value = this.view.getBigUint64(this.pos);
1710
+ this.pos += 8;
1711
+ return value;
1712
+ }
1713
+ readI64AsBigInt() {
1714
+ const value = this.view.getBigInt64(this.pos);
1715
+ this.pos += 8;
1716
+ return value;
1717
+ }
1718
+ readF32() {
1719
+ const value = this.view.getFloat32(this.pos);
1720
+ this.pos += 4;
1721
+ return value;
1722
+ }
1723
+ readF64() {
1724
+ const value = this.view.getFloat64(this.pos);
1725
+ this.pos += 8;
1726
+ return value;
1727
+ }
1728
+ }
1729
+
1730
+ /**
1731
+ * It decodes a single MessagePack object in a buffer.
1732
+ *
1733
+ * This is a synchronous decoding function.
1734
+ * See other variants for asynchronous decoding: {@link decodeAsync}, {@link decodeMultiStream}, or {@link decodeArrayStream}.
1735
+ *
1736
+ * @throws {@link RangeError} if the buffer is incomplete, including the case where the buffer is empty.
1737
+ * @throws {@link DecodeError} if the buffer contains invalid data.
1738
+ */
1739
+ function decode(buffer, options) {
1740
+ const decoder = new Decoder(options);
1741
+ return decoder.decode(buffer);
1742
+ }
1743
+
1744
+ const NetPackFormat = Object.freeze({
1745
+ JsonString: 0,
1746
+ MessagePack: 1,
1747
+ });
1748
+
1749
+ /**
1750
+ * 递归设置嵌套对象属性
1751
+ * @param {Object} obj - 目标对象
1752
+ * @param {Array} path - 属性路径
1753
+ * @param {string} type - 操作类型,可选值:'set'、'del'、'SET'、'DEL'
1754
+ * @param {*} value - 属性值
1755
+ * @returns {boolean} - 是否成功设置属性
1756
+ */
1757
+ function setNestedProperty(obj, path, type, value) {
1758
+ if (!obj) throw new Error('obj is empty');
1759
+ if (!path?.length) throw new Error('path is empty');
1760
+ const TypeTable = { s: 'set', d: 'del', S: 'SET', D: 'DEL' };
1761
+ type = TypeTable[type] || type;
1762
+ // 递归函数,用于根据数组路径访问属性
1763
+ function _accessNestedProp(curObj, curPath) {
1764
+ const key = curPath.shift();
1765
+ if (curPath.length === 0) {
1766
+ switch (type) {
1767
+ case 'set':
1768
+ case 'SET':
1769
+ curObj[key] = value;
1770
+ break;
1771
+ case 'del':
1772
+ delete curObj[key];
1773
+ break;
1774
+ case 'DEL':
1775
+ curObj.splice(Number(key), 1);
1776
+ break;
1777
+ default:
1778
+ throw new Error(`Invalid type: ${type}`);
1779
+ }
1780
+ return true;
1781
+ } else {
1782
+ if (!curObj[key]) {
1783
+ const isArray = type === 'SET' || type === 'DEL';
1784
+ if (curPath.length === 1 && isArray) {
1785
+ curObj[key] = [];
1786
+ } else {
1787
+ curObj[key] = {};
1788
+ }
1789
+ }
1790
+ return _accessNestedProp(curObj[key], curPath);
1791
+ }
1792
+ }
1793
+ return _accessNestedProp(obj, path.slice());
1794
+ }
1795
+
1796
+ async function fetchPost(url, data) {
1797
+ const res = await fetch(url, {
1798
+ method: 'POST',
1799
+ body: JSON.stringify(data),
1800
+ headers: { 'Content-Type': 'application/json' },
1801
+ });
1802
+ const result = await res.json();
1803
+ if (result.err) throw new Error(result.err);
1804
+ return result;
1805
+ }
1806
+
1807
+ const DefaultHost$1 = 'wss://varserver.popx.com/ws/syncvar';
1808
+
1809
+ /**
1810
+ * 创建可复用的 VarServer WebSocket 客户端,负责认证、消息分发、错误与断线处理。
1811
+ * 包含排队发送、认证期间超时、重连策略以及对绑定 server 实例的事件转发。
1812
+ * 支持 JSON 和 MessagePack 两种数据传输格式,默认使用 MessagePack 以获得更高传输效率。
1813
+ *
1814
+ * @param {(string|Object)} arg1 - 第一个参数,可以是 host 字符串或配置对象
1815
+ * @param {Object} [arg2] - 第二个参数,配置对象(当 arg1 为 host 字符串时使用)
1816
+ * @param {string} [arg2.host] - WebSocket 服务器地址,可选。
1817
+ * @param {string} [arg2.namespace=''] - 命名空间,用于隔离不同应用的数据
1818
+ * @param {string} [arg2.guid] - 客户端唯一标识符,用于认证
1819
+ * @param {number} [arg2.packFormat=NetPackFormat.MessagePack] - 数据传输格式,默认 MessagePack
1820
+ * @param {function} [arg2.onopen] - 连接建立时的回调函数,签名: (ws, event) => void
1821
+ * @param {function} [arg2.onclose] - 连接关闭时的回调函数,签名: (event) => void
1822
+ * @param {function} [arg2.onerror] - 错误处理回调函数,签名: (err) => void,默认: console.error
1823
+ * @param {number} [arg2.timeout=5000] - 连接超时时间(毫秒),默认: 5000ms
1824
+ * @param {number} [arg2.maxReconnectCount=100] - 最大重连次数,默认: 100
1825
+ * @returns {Promise<WebSocket>} - 返回 WebSocket 实例的 Promise,包含扩展方法 sendMsg 和 bind
1826
+ */
1827
+ async function WebSocketClient(...args) {
1828
+ let host, params;
1829
+ if (typeof arguments[0] === 'string') {
1830
+ host = arguments[0];
1831
+ params = arguments[1];
1832
+ } else {
1833
+ params = arguments[0];
1834
+ params?.host && (host = params.host);
1835
+ }
1836
+ host = host || DefaultHost$1;
1837
+ const { namespace = '',
1838
+ guid, onopen, onclose,
1839
+ onerror = (err) => console.error('WebSocket error:', err?.message || err),
1840
+ packFormat = NetPackFormat.MessagePack,
1841
+ timeout = 5000,
1842
+ maxReconnectCount = 100 } = params || {};
1843
+ // 记录所有绑定的 server,连接建立后统一分发消息与状态变更
1844
+ const servers = new Set();
1845
+ console.log(`Create WebSocket: ${host}`);
1846
+ let retryConnectCount = 0;
1847
+ let hasHandledError = false;
1848
+
1849
+ // 抖动指数退避:避免短时间内多次重连导致的资源浪费
1850
+ function reconnect() {
1851
+ const base = 1000;
1852
+ const exp = Math.min(base * 2 ** retryConnectCount, 30000);
1853
+ // 抖动:在 50% - 100% * exp 范围内随机
1854
+ const delay = Math.min(Math.floor(exp * (0.5 + Math.random() * 0.5)), 30000);
1855
+ retryConnectCount++;
1856
+ console.log(`Retry connect: ${host}, delay=${(delay / 1000).toFixed(1)}s, count=${retryConnectCount}`);
1857
+ setTimeout(() => {
1858
+ connect().catch(err => console.error('WebSocket 重连失败:', err?.message || err));
1859
+ }, delay);
1860
+ }
1861
+
1862
+ // connect 返回 Promise,便于统一处理 onopen/close/error 的异步顺序
1863
+ async function connect() {
1864
+ return new Promise((resolve, reject) => {
1865
+ hasHandledError = false;
1866
+ const params = new URLSearchParams({ namespace });
1867
+ let ws = new WebSocket(`${host}?${params}`);
1868
+ ws.binaryType = 'arraybuffer';
1869
+ ws._authed = false;
1870
+ // 发送队列:连接就绪前缓存 outbound 消息,认证完成后统一 flush
1871
+ ws._sendQueue = [];
1872
+ ws.sendMsg = (msg, { format = packFormat } = {}) => {
1873
+ if (ws.readyState !== WebSocket.OPEN) {
1874
+ ws._sendQueue.push(msg);
1875
+ console.warn('WebSocket is not ready, queued message. Queue size:', ws._sendQueue.length);
1876
+ return;
1877
+ }
1878
+ try {
1879
+ if (format === NetPackFormat.MessagePack) {
1880
+ ws.send(encode(msg), { binary: true });
1881
+ } else {
1882
+ ws.send(JSON.stringify(msg));
1883
+ }
1884
+ } catch (err) {
1885
+ onerror?.(err);
1886
+ }
1887
+ };
1888
+
1889
+ let timeoutId = null;
1890
+ // 如果在约定时间内没有收到服务端的登录回应,则视为超时
1891
+ timeoutId = setTimeout(() => {
1892
+ if (!ws._authed && ws.readyState === WebSocket.CONNECTING) {
1893
+ ws.close();
1894
+ const timeoutError = new Error('WebSocket connection timeout');
1895
+ onerror?.(timeoutError);
1896
+ reject(timeoutError);
1897
+ hasHandledError = true;
1898
+ }
1899
+ }, timeout);
1900
+
1901
+ // 发起登录请求 - 登录消息总是使用JSON格式确保兼容
1902
+ function login() {
1903
+ const $login = { namespace, guid, packFormat };
1904
+ // 登录消息使用JSON格式,服务端会处理格式选择
1905
+ ws.send(JSON.stringify({ $login }));
1906
+ }
1907
+
1908
+ // 处理服务端登录结果
1909
+ function onLogin({ id, error }) {
1910
+ ws.id = id;
1911
+ ws._authed = true;
1912
+ clearTimeout(timeoutId);
1913
+ if (error) {
1914
+ reject(error);
1915
+ hasHandledError = true;
1916
+ return;
1917
+ }
1918
+ if (retryConnectCount > 0) {
1919
+ servers.forEach(server => server.bind(ws));
1920
+ retryConnectCount = 0;
1921
+ }
1922
+ // 认证通过后,flush 掉之前排队的消息
1923
+ if (ws._sendQueue && ws._sendQueue.length) {
1924
+ const queued = ws._sendQueue.splice(0);
1925
+ queued.forEach(m => ws.sendMsg(m));
1926
+ }
1927
+ console.log(`Client connected ${host}`, id);
1928
+ resolve(ws);
1929
+ }
1930
+
1931
+ // 连接成功:触发外部回调并启动认证流程
1932
+ ws.onopen = (event) => {
1933
+ console.log("WebSocket connected!");
1934
+ onopen?.(ws, event);
1935
+ login();
1936
+ };
1937
+
1938
+ // 消息接收:统一做 JSON 解析、格式校验、分发逻辑
1939
+ ws.onmessage = async (event) => {
1940
+ try {
1941
+ const { data } = event;
1942
+ let msg;
1943
+ if (typeof data === 'string') {
1944
+ msg = JSON.parse(data);
1945
+ } else {
1946
+ let buffer;
1947
+ if (data instanceof Blob) {
1948
+ buffer = await data.arrayBuffer();
1949
+ } else {
1950
+ buffer = data;
1951
+ }
1952
+ if (packFormat === NetPackFormat.MessagePack) {
1953
+ msg = decode(buffer);
1954
+ } else {
1955
+ throw new Error('不支持的消息格式');
1956
+ }
1957
+ }
1958
+ // 严谨的类型检查
1959
+ if (!msg || typeof msg !== 'object') {
1960
+ throw new Error(`Invalid message format: ${data}`);
1961
+ }
1962
+ if (msg.$error) throw new Error(msg.$error.err || '未描述的错误');
1963
+ if (msg.$login) return onLogin(msg.$login);
1964
+ servers.forEach(server => server.onMessage?.(msg));
1965
+ } catch (err) {
1966
+ onerror?.(err);
1967
+ }
1968
+ };
1969
+
1970
+ // WebSocket 报错:如果尚未认证完成,则需要 reject
1971
+ ws.onerror = (event) => {
1972
+ if (hasHandledError) return;
1973
+ clearTimeout(timeoutId);
1974
+ const err = event instanceof Error ? event : new Error(event?.message || 'WebSocket error');
1975
+ onerror?.(err);
1976
+ if (!ws._authed) {
1977
+ reject(err);
1978
+ hasHandledError = true;
1979
+ }
1980
+ };
1981
+
1982
+ // 连接关闭:清理状态、通知 server,并尝试重连或彻底销毁
1983
+ ws.onclose = (event) => {
1984
+ console.error(`Connection closed: code=${event.code}, reason=${event.reason}, clean=${event.wasClean}`);
1985
+ clearTimeout(timeoutId);
1986
+ onclose?.(event);
1987
+ servers.forEach(server => server.close?.(event));
1988
+ if (retryConnectCount < maxReconnectCount) {
1989
+ reconnect();
1990
+ } else {
1991
+ servers.forEach(server => server.destroy?.(event));
1992
+ servers.clear();
1993
+ }
1994
+ };
1995
+
1996
+ ws.bind = (server) => {
1997
+ if (servers.has(server)) return server;
1998
+ servers.add(server);
1999
+ return server.bind(ws);
2000
+ };
2001
+ });
2002
+ }
2003
+
2004
+ return connect();
2005
+ }
2006
+
2007
+ const DEFAULT_PROMISE_TIMEOUT = 10000;
2008
+
2009
+ class PromiseMgr {
2010
+ constructor() {
2011
+ this._promises = new Map();
2012
+ this._latestPromiseId = 0;
2013
+ }
2014
+
2015
+ call(cb, { timeout = DEFAULT_PROMISE_TIMEOUT, data } = {}) {
2016
+ if (!cb) throw new Error('未指定回调函数');
2017
+ return new Promise((resolve, reject) => {
2018
+ const pid = ++this._latestPromiseId;
2019
+ const timeoutId = setTimeout(() => {
2020
+ this._promises.delete(pid);
2021
+ reject(new Error(`Promise等待超时`));
2022
+ }, timeout);
2023
+ this._promises.set(pid, { resolve, reject, timeoutId, data });
2024
+ cb(pid);
2025
+ });
2026
+ }
2027
+
2028
+ process(pid, { result, error }) {
2029
+ const promise = this._promises.get(pid);
2030
+ if (!promise) {
2031
+ console.error(`Promise process unexpected. pid:${pid}`);
2032
+ return false;
2033
+ }
2034
+ clearTimeout(promise.timeoutId);
2035
+ this._promises.delete(pid);
2036
+ const { resolve, reject } = promise;
2037
+ error ? reject(new Error(error)) : resolve(result);
2038
+ return !error;
2039
+ }
2040
+
2041
+ getData(pid) {
2042
+ const promise = this._promises.get(pid);
2043
+ return promise?.data;
2044
+ }
2045
+
2046
+ }
2047
+
2048
+ const mgr = new PromiseMgr();
2049
+
2050
+ /**
2051
+ * SyncVar - 客户端变量同步工具
2052
+ *
2053
+ * 说明:此文件同时支持两种接收更新的方式:
2054
+ * - 基于 WebSocket 的双向同步(默认,用于 sync/lock/set 等交互)
2055
+ */
2056
+ class SyncVar {
2057
+ /**
2058
+ * SyncVar - 客户端变量同步工具
2059
+ *
2060
+ * 支持:
2061
+ * - 基于 WebSocket 的双向同步(默认,用于 sync/lock/set 等交互)
2062
+ *
2063
+ * @param {Object} options
2064
+ * @param {number} options.delay - 变更打包延迟(ms)
2065
+ */
2066
+ constructor({ delay = 50 } = {}) {
2067
+ this._watchs = new Map();
2068
+ this._waitUpdated = new Set();
2069
+ this._delay = delay;
2070
+ this._destroyed = false;
2071
+ this._needUpdateVars = new Map();
2072
+ this._updateVarTimer = null;
2073
+ this._bindFuncs = new Map();
2074
+ }
2075
+ get ws() { return this._ws }
2076
+ get id() { return this._ws?.id }
2077
+ /**
2078
+ * 绑定并初始化与 WebSocket 的连接。
2079
+ * 会在绑定时同步当前已注册的变量与已注册的 RPC 函数到服务器。
2080
+ * @param {WebSocket|Object} ws - 支持具有 `send` 或 `sendMsg` 的连接对象
2081
+ * @returns {Promise<SyncVar>} 返回自身实例以便链式调用
2082
+ */
2083
+ async bind(ws) {
2084
+ if (this._ws === ws) return this;
2085
+ if (!ws?.bind) throw new Error('请绑定WebSocketClient创建的连接实例');
2086
+ console.log('SyncVar bind', ws.id);
2087
+ this._ws = ws;
2088
+ ws.bind(this);
2089
+ // 同步所有已注册的变量
2090
+ if (this._watchs.size) {
2091
+ const names = Array.from(this._watchs.keys());
2092
+ await mgr.call((pid) => {
2093
+ this._sendMsg({ SyncVar: { $sync: { pid, names } } });
2094
+ }, { data: { names } });
2095
+ }
2096
+ // 同步所有已注册的函数
2097
+ if (this._bindFuncs.size) {
2098
+ await mgr.call((pid) => {
2099
+ const names = Array.from(this._bindFuncs.keys());
2100
+ this._sendMsg({ SyncVar: { $bind: { pid, names } } });
2101
+ });
2102
+ }
2103
+ return this;
2104
+ }
2105
+ /**
2106
+ * 关闭本地 WebSocket 引用(不向服务器发送通知)
2107
+ */
2108
+ close() { this._ws = null; }
2109
+
2110
+ /**
2111
+ * 销毁 SyncVar 实例,取消所有挂起的 Promise 并清理内部状态。
2112
+ * 调用后实例将不可再用。
2113
+ */
2114
+ destroy() {
2115
+ // 清理所有待处理的Promise,防止内存泄漏
2116
+ if (this._destroyed) return;
2117
+ this._destroyed = true;
2118
+ const error = new Error('SyncVar已销毁');
2119
+ this._waitUpdated.forEach(({ reject }) => reject(error));
2120
+ this._watchs.clear();
2121
+ this._waitUpdated.clear();
2122
+ this._ws = null;
2123
+ }
2124
+
2125
+ /**
2126
+ * 等待最近一次写入($set)的服务器回执。
2127
+ * 返回一个 Promise,在服务端回执时 resolve,或在出错/超时时 reject。
2128
+ * 用法:await syncVar.waitUpdated();
2129
+ */
2130
+ async waitUpdated() {
2131
+ if (this._destroyed) throw new Error('SyncVar已销毁,无法执行锁定');
2132
+ return new Promise((resolve, reject) => this._waitUpdated.add({ resolve, reject }));
2133
+ }
2134
+
2135
+ // 绑定RPC函数
2136
+ /**
2137
+ * 将本地函数注册为可被服务端调用的 RPC 函数。
2138
+ * 要求传入的函数必须有名称(不能为匿名函数)。
2139
+ * @param {Function} func
2140
+ * @returns {Promise<void>}
2141
+ */
2142
+ async rpcBind(func) {
2143
+ if (typeof func !== 'function') throw new Error('rpcBind 回调必须是函数');
2144
+ const name = func.name;
2145
+ if (!name) throw new Error('rpcBind 回调必须指定名称,不能使用匿名函数');
2146
+ if (this._bindFuncs.has(name)) throw new Error(`已存在同名函数: ${name}`);
2147
+ this._bindFuncs.set(name, func);
2148
+ return mgr.call((pid) => {
2149
+ this._sendMsg({ SyncVar: { $bind: { pid, names: [name] } } });
2150
+ });
2151
+ }
2152
+
2153
+ /**
2154
+ * 调用远端已绑定的 RPC 函数
2155
+ * @param {string} name - 函数名
2156
+ * @param {any} args - 传递给远端函数的参数(任意类型)
2157
+ * @returns {Promise<any>} 返回远端函数的执行结果或抛出错误
2158
+ */
2159
+ async rpcCall(name, args) {
2160
+ if (!name) throw new Error('rpcCall 参数错误:name 不能为空');
2161
+ return mgr.call((pid) => {
2162
+ this._sendMsg({ SyncVar: { $call: { pid, name, args } } });
2163
+ });
2164
+ }
2165
+
2166
+ onMessage(msg) {
2167
+ const { SyncVar } = msg;
2168
+ if (!SyncVar) return;
2169
+ try {
2170
+ const { $sync, $set, $update, $lock,
2171
+ $bind, $call, $callRet } = SyncVar;
2172
+ $sync && this._onSync($sync);
2173
+ $set && this._onSet($set);
2174
+ $update && this._onUpdate($update);
2175
+ $lock && this._onLock($lock);
2176
+ $bind && this._onBind($bind);
2177
+ $call && this._onCall($call);
2178
+ $callRet && this._onCallRet($callRet);
2179
+ } catch (e) {
2180
+ console.error('SyncVar.onMessage error:', SyncVar, e.message);
2181
+ }
2182
+ }
2183
+
2184
+
2185
+ static _clone(val) {
2186
+ if (typeof globalThis.structuredClone === 'function') return globalThis.structuredClone(val);
2187
+ try {
2188
+ return JSON.parse(JSON.stringify(val));
2189
+ } catch (e) {
2190
+ console.error('无法克隆对象', val, e.message || e);
2191
+ return val;
2192
+ }
2193
+ }
2194
+
2195
+ static _getDataKeys(obj) { return Object.keys(obj).filter(k => typeof obj[k] !== 'function'); }
2196
+
2197
+ static _shallowDataEqual(a, b) {
2198
+ if (Object.is(a, b)) return true;
2199
+ if (!a || !b) return false;
2200
+ const keysA = SyncVar._getDataKeys(a);
2201
+ const keysB = SyncVar._getDataKeys(b);
2202
+ if (keysA.length !== keysB.length) return false;
2203
+ else if (keysA.length === 0) return a === b;
2204
+ return keysA.every(k => a[k] === b[k]);
2205
+ }
2206
+
2207
+ static _copyData(oldObj, newObj) {
2208
+ if (SyncVar._shallowDataEqual(oldObj, newObj)) return false;
2209
+ const keysToDelete = SyncVar._getDataKeys(oldObj);
2210
+ for (const key of keysToDelete) delete oldObj[key];
2211
+ Object.assign(oldObj, newObj);
2212
+ return true;
2213
+ }
2214
+
2215
+ static _createBatchNotifier(onFlush) {
2216
+ // 将多次变更在微任务内合并,再一次性触发 onFlush,降低发送频率
2217
+ let queue = [];
2218
+ let scheduled = false;
2219
+ function flush() {
2220
+ if (queue.length === 0) return;
2221
+ const changes = queue;
2222
+ queue = [];
2223
+ onFlush(changes);
2224
+ }
2225
+ function schedule() {
2226
+ if (scheduled) return;
2227
+ scheduled = true;
2228
+ Promise.resolve().then(() => {
2229
+ scheduled = false;
2230
+ flush();
2231
+ });
2232
+ }
2233
+ return {
2234
+ push(change) {
2235
+ queue.push(change);
2236
+ schedule();
2237
+ },
2238
+ flush,
2239
+ };
2240
+ }
2241
+
2242
+ /**
2243
+ * 同步变量并返回 proxy 对象。
2244
+ *
2245
+ * 用法示例:
2246
+ * // 单个变量
2247
+ * const position = await syncVar.sync('position');
2248
+ * // 多个变量
2249
+ * const { position, user } = await syncVar.sync(['position','user']);
2250
+ *
2251
+ * 可选参数 `params.reset` 在第一次 sync 时用于本地初始化变量值:
2252
+ * await syncVar.sync(['user'], { reset: { user: { name: '张三' } } });
2253
+ *
2254
+ * 返回:resolve 时返回已存在或新建的 proxy(单个或对象映射)。
2255
+ * 错误场景:未连接 WebSocket、names 为空、超时或服务端返回错误会 reject。
2256
+ */
2257
+ async sync(names, params) {
2258
+ if (this._destroyed) throw new Error('SyncVar已销毁,无法执行同步');
2259
+ if (typeof names === 'string') names = [names];
2260
+ if (!names?.length) throw new Error('names不能为空');
2261
+ if (!this._ws || this._ws.readyState !== WebSocket.OPEN) {
2262
+ throw new Error('WebSocket未连接');
2263
+ }
2264
+ const needSyncNames = names.filter(name => !this._watchs.has(name));
2265
+ // 处理 reset 值
2266
+ const { reset } = params || {};
2267
+ reset && names.forEach(name => {
2268
+ const value = reset[name];
2269
+ if (value !== undefined) this[name] = value;
2270
+ });
2271
+ if (!needSyncNames.length) {
2272
+ const result = {};
2273
+ names.forEach(name => {
2274
+ const watch = this._watchs.get(name);
2275
+ if (!watch) throw new Error('变量异常:' + name);
2276
+ result[name] = watch.proxy;
2277
+ });
2278
+ return result;
2279
+ }
2280
+ return mgr.call((pid) => {
2281
+ this._sendMsg({ SyncVar: { $sync: { pid, names: needSyncNames, ...params } } });
2282
+ }, { data: { names } });
2283
+ }
2284
+
2285
+ _onSync({ datas, pid, error }) {
2286
+ datas?.forEach(({ name, value }) => {
2287
+ let watch = this._watchs.get(name);
2288
+ if (!watch) {
2289
+ watch = this._observe(name, value);
2290
+ this._watchs.set(name, watch);
2291
+ } else {
2292
+ watch.setValue(value);
2293
+ }
2294
+ });
2295
+ const data = mgr.getData(pid);
2296
+ const result = {};
2297
+ data?.names?.forEach(name => {
2298
+ const watch = this._watchs.get(name);
2299
+ if (!watch) throw new Error('同步变量异常:' + name);
2300
+ result[name] = watch.proxy;
2301
+ });
2302
+ mgr.process(pid, { result, error });
2303
+ }
2304
+
2305
+ _onSet({ error, lockeds }) {
2306
+ lockeds?.forEach(({ name, value }) => {
2307
+ const watch = this._watchs.get(name);
2308
+ if (!watch) {
2309
+ console.error('变量异常:' + name);
2310
+ return;
2311
+ }
2312
+ watch.setValue(value);
2313
+ });
2314
+ this._waitUpdated.forEach(({ resolve, reject }) => {
2315
+ if (error) {
2316
+ reject(`数据写入失败:` + error);
2317
+ } else {
2318
+ resolve();
2319
+ }
2320
+ });
2321
+ this._waitUpdated.clear();
2322
+ }
2323
+
2324
+ _onLock({ name, value, promiseIds, error }) {
2325
+ //console.log('locked:', this._ws.id, !error);
2326
+ const watch = this._watchs.get(name);
2327
+ if (watch) {
2328
+ watch.setValue(value);
2329
+ watch.setLocked();
2330
+ }
2331
+ let resolved = false;
2332
+ promiseIds?.forEach(pid => {
2333
+ let result;
2334
+ if (watch) {
2335
+ result = watch.proxy;
2336
+ } else {
2337
+ error = error || ('找不到变量:' + name);
2338
+ }
2339
+ if (mgr.process(pid, { result, error })) {
2340
+ resolved = true;
2341
+ }
2342
+ });
2343
+ // 没人处理这个lock则通知服务器unlock
2344
+ if (!resolved) {
2345
+ watch?.unlock();
2346
+ this._sendMsg({ SyncVar: { $unlock: { name } } });
2347
+ }
2348
+ }
2349
+
2350
+ _onUpdate({ vars }) {
2351
+ vars?.forEach(({ name, value, changes }) => {
2352
+ //console.log('$update', this._ws.id, name, diff);
2353
+ const watch = this._watchs.get(name);
2354
+ if (!watch) return console.error('找不到变量:' + name);
2355
+ if (changes) {
2356
+ watch.setDiffs(changes);
2357
+ } else {
2358
+ watch.setValue(value);
2359
+ }
2360
+ });
2361
+ }
2362
+
2363
+ _onBind({ pid, error }) { mgr.process(pid, { error }); }
2364
+
2365
+ async _onCall({ pid, name, args }) {
2366
+ try {
2367
+ const func = this._bindFuncs.get(name);
2368
+ if (!func) throw new Error(`函数不存在: ${name}`);
2369
+ const result = await func(args);
2370
+ this._sendMsg({ SyncVar: { $callRet: { pid, name, result } } });
2371
+ } catch (e) {
2372
+ this._sendMsg({ SyncVar: { $callRet: { pid, error: e.message || e } } });
2373
+ console.error(e);
2374
+ }
2375
+ }
2376
+
2377
+ _onCallRet({ pid, result, error }) { mgr.process(pid, { result, error }); }
2378
+
2379
+
2380
+ _packUpdateVar(name, changes) {
2381
+ if (!this._needUpdateVars.has(name)) {
2382
+ this._needUpdateVars.set(name, []);
2383
+ }
2384
+ const oldChanges = this._needUpdateVars.get(name);
2385
+ changes.forEach(change => {
2386
+ const { type, path } = change;
2387
+ // 删除重叠的旧变更
2388
+ for (let index = oldChanges.length - 1; index >= 0; index--) {
2389
+ const { path: oldPath } = oldChanges[index];
2390
+ if (!path.every((p, i) => oldPath[i] === p)) continue;
2391
+ oldChanges.splice(index, 1);
2392
+ }
2393
+ oldChanges.push(change);
2394
+ });
2395
+ if (this._updateVarTimer) return;
2396
+ this._updateVarTimer = setTimeout(() => {
2397
+ this._updateVarTimer = null;
2398
+ //
2399
+ if (!this._needUpdateVars.size) return;
2400
+ const diffs = [];
2401
+ this._needUpdateVars.forEach((changes, name) => {
2402
+ if (!changes.length) return;
2403
+ const watch = this._watchs.get(name);
2404
+ if (!watch) return console.error('找不到变量:' + name);
2405
+
2406
+ // 压缩 changes 格式
2407
+ const TypeTable = { set: 's', del: 'd', SET: 'S', DEL: 'D' };
2408
+ const compressedChanges = changes.map(({ type, path, val }) =>
2409
+ ({ t: TypeTable[type], p: path, v: val }));
2410
+ // 比较 JSON 序列化后的大小,选择更小的发送方式
2411
+ const changesSize = JSON.stringify(compressedChanges).length;
2412
+ const fullValueSize = JSON.stringify(watch.value).length;
2413
+
2414
+ if (changesSize >= fullValueSize) {
2415
+ const saved = changesSize - fullValueSize;
2416
+ console.log(`[SyncVar优化] 变量 "${name}" 使用完整值发送 (changes: ${changesSize}B → value: ${fullValueSize}B, 节省 ${saved}B)`);
2417
+ diffs.push({ name, value: watch.value });
2418
+ } else {
2419
+ console.log(`[SyncVar优化] 变量 "${name}" 使用差异发送 (changes: ${changesSize}B, value: ${fullValueSize}B)`);
2420
+ diffs.push({ name, changes: compressedChanges });
2421
+ }
2422
+ watch.unlock();
2423
+ });
2424
+ if (!diffs.length) return;
2425
+ // 通知服务器
2426
+ const $set = { diffs };
2427
+ this._waitUpdated.size > 0 && ($set.receipt = true);
2428
+ this._sendMsg({ SyncVar: { $set } });
2429
+ //console.log('update var:', diffs);
2430
+ this._needUpdateVars.clear();
2431
+ }, this._delay);
2432
+ }
2433
+
2434
+ _observe(name, value) {
2435
+ const that = this;
2436
+ const proxyCache = new WeakMap();
2437
+ let locked = false;
2438
+ let watchCB;
2439
+ const notifier = SyncVar._createBatchNotifier((changes) => {
2440
+ this._packUpdateVar(name, changes);
2441
+ });
2442
+ function _createProxy(obj, path = []) {
2443
+ if (typeof obj !== 'object' || obj === null) return obj;
2444
+ if (proxyCache.has(obj)) return proxyCache.get(obj);
2445
+ const proxy = new Proxy(obj, {
2446
+ get(target, key, receiver) {
2447
+ const val = Reflect.get(target, key, receiver);
2448
+ // 解决 Set 和 Map 的方法
2449
+ const _isCollection = (obj) => obj instanceof Set || obj instanceof Map;
2450
+ if (typeof val === 'function' && _isCollection(target)) {
2451
+ return val.bind(target);
2452
+ }
2453
+ return _createProxy(val, path.concat(key));
2454
+ },
2455
+ set(target, key, val, receiver) {
2456
+ const old = target[key];
2457
+ if (SyncVar._shallowDataEqual(old, val)) return true;
2458
+ notifier.push({
2459
+ type: Array.isArray(target) ? 'SET' : 'set',
2460
+ path: path.concat(key),
2461
+ val: SyncVar._clone(val),
2462
+ });
2463
+ return Reflect.set(target, key, val, receiver);
2464
+ },
2465
+ deleteProperty(target, key) {
2466
+ notifier.push({ type: Array.isArray(target) ? 'DEL' : 'del', path: path.concat(key) });
2467
+ return Reflect.deleteProperty(target, key);
2468
+ }
2469
+ });
2470
+ proxyCache.set(obj, proxy);
2471
+ return proxy;
2472
+ }
2473
+ const proxy = _createProxy(value);
2474
+ /**
2475
+ * 请求对该变量的独占锁(写入保护)。
2476
+ *
2477
+ * 示例:
2478
+ * await syncVar.sync('user');
2479
+ * const proxy = (await syncVar.sync('user')).position || (await syncVar.sync('user'));
2480
+ * const p = await proxy.lock({ timeout: 5000 });
2481
+ *
2482
+ * 返回:resolve 为 `proxy`(代表锁已被授予),reject 表示超时或服务端错误。
2483
+ */
2484
+ Object.defineProperty(proxy, 'lock', {
2485
+ value: async ({ timeout } = {}) => {
2486
+ if (that._destroyed) throw new Error('SyncVar已销毁,无法执行锁定');
2487
+ if (locked) return proxy; // 已被自己锁住,则立马返回成功
2488
+ return mgr.call((promiseId) => {
2489
+ that._sendMsg({ SyncVar: { $lock: { promiseId, name } } });
2490
+ }, { timeout });
2491
+ }
2492
+ });
2493
+ /**
2494
+ * 释放当前变量的锁(若持有)。
2495
+ * 用法示例:await proxy.unlock();
2496
+ */
2497
+ Object.defineProperty(proxy, 'unlock', {
2498
+ value: async () => {
2499
+ if (that._destroyed) throw new Error('SyncVar已销毁,无法执行锁定');
2500
+ if (!locked) return;
2501
+ locked = false;
2502
+ try {
2503
+ that._sendMsg({ SyncVar: { $unlock: { name } } });
2504
+ } catch (err) {
2505
+ console.error('unlock notify failed:', err && err.message ? err.message : err);
2506
+ }
2507
+ }
2508
+ });
2509
+ /**
2510
+ * 注册变量变化回调:`cb(value, name)`。
2511
+ * 当接收到服务端的 `$update` 或 `$lock` 时会触发。
2512
+ * 示例:proxy.watch((value, name) => { console.log('updated', name, value); });
2513
+ */
2514
+ Object.defineProperty(proxy, 'watch', {
2515
+ value: (cb) => {
2516
+ if (typeof cb !== 'function') throw new Error('watch 回调必须是函数');
2517
+ watchCB = cb;
2518
+ }
2519
+ });
2520
+ Object.defineProperty(this, name, {
2521
+ enumerable: true,
2522
+ configurable: true,
2523
+ get() { return proxy },
2524
+ set(val) { SyncVar._copyData(proxy, val); }
2525
+ });
2526
+ return {
2527
+ proxy,
2528
+ value,
2529
+ setLocked() { locked = true; },
2530
+ unlock() { locked = false; },
2531
+ setValue(val) {
2532
+ SyncVar._copyData(value, val);
2533
+ try {
2534
+ watchCB?.(value, name);
2535
+ } catch (err) {
2536
+ console.error(`watch 回调执行失败 (${name}):`, err);
2537
+ }
2538
+ },
2539
+ setDiffs(diffs) {
2540
+ diffs.forEach(({ t: type, p: path, v: val }) =>
2541
+ setNestedProperty(value, path, type, val)
2542
+ );
2543
+ try {
2544
+ watchCB?.(value, name);
2545
+ } catch (err) {
2546
+ console.error(`watch 回调执行失败 (${name}):`, err);
2547
+ }
2548
+ },
2549
+ };
2550
+ }
2551
+
2552
+ _sendMsg(msg) {
2553
+ try {
2554
+ if (!this._ws || this._ws.readyState !== WebSocket.OPEN) {
2555
+ throw new Error('WebSocket未连接');
2556
+ }
2557
+ if (typeof this._ws.sendMsg === 'function') {
2558
+ this._ws.sendMsg(msg);
2559
+ } else if (typeof this._ws.send === 'function') {
2560
+ this._ws.send(JSON.stringify(msg));
2561
+ } else {
2562
+ console.error('_sendMsg: ws has no send/sendMsg method');
2563
+ }
2564
+ } catch (e) {
2565
+ console.error('_sendMsg failed:', e && e.message ? e.message : e);
2566
+ }
2567
+ }
2568
+ }
2569
+
2570
+ /**
2571
+ * Worker 负责通过 websocket 与后端的 worker manager 交换指令并管理请求的生命周期。
2572
+ */
2573
+ class Worker {
2574
+ /**
2575
+ * @param {Object} options
2576
+ * @param {Function} options.onlog 接收子进程日志事件。
2577
+ * @param {Function} options.onerror 接收子进程错误堆栈。
2578
+ * @param {Function} options.onexit 接收子进程退出事件。
2579
+ */
2580
+ constructor({ onlog, onerror, onexit } = {}) {
2581
+ console.warn('Worker init', { version: '2025-12-24' });
2582
+ this._onlog = onlog;
2583
+ this._onerror = onerror;
2584
+ this._onexit = onexit;
2585
+ }
2586
+
2587
+ /**
2588
+ * 为当前 Worker 实例绑定一个 websocket 连接,用于发送/接收命令。
2589
+ */
2590
+ async bind(ws) {
2591
+ if (this._ws === ws) return this;
2592
+ if (!ws?.bind) throw new Error('请绑定WebSocketClient创建的连接实例');
2593
+ console.log('Worker bind', ws.id);
2594
+ this._ws = ws;
2595
+ ws.bind(this);
2596
+ return this;
2597
+ }
2598
+
2599
+ /**
2600
+ * 处理 websocket 推送的 Worker 消息,分发到不同的回调或 promise。
2601
+ */
2602
+ onMessage(msg) {
2603
+ const { Worker } = msg;
2604
+ if (!Worker) return;
2605
+ try {
2606
+ const { $run, $kill, $attach, $lists,
2607
+ $log, $error, $exit } = Worker;
2608
+ $run && this._onPromise($run);
2609
+ $kill && this._onPromise($kill);
2610
+ $attach && this._onPromise($attach);
2611
+ $lists && this._onPromise($lists);
2612
+ $log && this._onlog?.(...$log);
2613
+ $error && this._onerror?.($error);
2614
+ $exit && this._onexit?.($exit);
2615
+ } catch (e) {
2616
+ console.error('Worker.onMessage error:', Worker, e.message);
2617
+ }
2618
+ }
2619
+
2620
+ _onPromise({ pid, error, ...other }) { mgr.process(pid, { error, result: { ...other } }); }
2621
+
2622
+ /**
2623
+ * 请求后端执行指定脚本。
2624
+ */
2625
+ async run({ code, name, workerData }) {
2626
+ return mgr.call((pid) => {
2627
+ this._ws.sendMsg({ Worker: { $run: { pid, code, name, workerData } } });
2628
+ });
2629
+ }
2630
+
2631
+ /**
2632
+ * 请求终止指定名称的 worker。
2633
+ */
2634
+ async kill({ name }) {
2635
+ return mgr.call((pid) => {
2636
+ this._ws.sendMsg({ Worker: { $kill: { pid, name } } });
2637
+ });
2638
+ }
2639
+
2640
+ /**
2641
+ * 关联到一个已存在的 worker,用于接收日志与事件。
2642
+ */
2643
+ async attach({ name }) {
2644
+ return mgr.call((pid) => {
2645
+ this._ws.sendMsg({ Worker: { $attach: { pid, name } } });
2646
+ });
2647
+ }
2648
+
2649
+ /**
2650
+ * 获取当前所有的 worker 列表。
2651
+ */
2652
+ async lists() {
2653
+ return mgr.call((pid) => {
2654
+ this._ws.sendMsg({ Worker: { $lists: { pid } } });
2655
+ });
2656
+ }
2657
+ }
2658
+
2659
+ const DefaultHost = 'https://varserver.popx.com';
2660
+ const INITIAL_RECONNECT_DELAY = 1000;
2661
+ const MAX_RECONNECT_DELAY = 30000;
2662
+
2663
+ /**
2664
+ * 简化并安全地使用 SSE 订阅变量更新的辅助类。
2665
+ *
2666
+ * 示例:
2667
+ * const sse = new SSE();
2668
+ * await sse.watch({ vars: { myVar: (update) => { ... } }, events: { myEvent: () => { ... } } });
2669
+ */
2670
+ class SSE {
2671
+ constructor({ host = DefaultHost, namespace = '', onError = null } = {}) {
2672
+ this._host = host;
2673
+ this._namespace = namespace;
2674
+ // 保存当前接收到的数据快照,按变量名索引
2675
+ this._data = new Map();
2676
+ // 存放按变量名注册的回调
2677
+ this._cbFuncs = new Map();
2678
+ // 错误回调:默认打印到控制台
2679
+ this._onError = onError || ((err) => console.error('SSE error:', err && err.message ? err.message : err));
2680
+ this._sse = null;
2681
+ this._eventListeners = [];
2682
+ this._watchConfig = null;
2683
+ this._reconnectDelay = INITIAL_RECONNECT_DELAY;
2684
+ this._reconnectTimer = null;
2685
+ this._closing = false;
2686
+ this._connectPromise = null;
2687
+ }
2688
+
2689
+ // 通过 HTTP POST 将事件发送到服务器
2690
+ static emit(event, data, { host = DefaultHost, namespace = '' } = {}) {
2691
+ const params = new URLSearchParams();
2692
+ if (namespace) params.append('namespace', namespace);
2693
+ const url = `${host}/api/emit?${params.toString()}`;
2694
+ return fetchPost(url, { event, data });
2695
+ }
2696
+
2697
+ async _getEventSource() {
2698
+ // 检查是否在浏览器环境
2699
+ const isBrowser = typeof window !== 'undefined' &&
2700
+ typeof document !== 'undefined';
2701
+ if (isBrowser) {
2702
+ // 浏览器环境
2703
+ if (!window.EventSource) {
2704
+ throw new Error('EventSource is not supported in this browser');
2705
+ }
2706
+ return window.EventSource;
2707
+ } else {
2708
+ // Node.js 环境
2709
+ try {
2710
+ // 动态导入
2711
+ const module = await import('eventsource');
2712
+ const { EventSource } = module.default || module;
2713
+ return EventSource;
2714
+ } catch (error) {
2715
+ throw new Error('Failed to import eventsource module. Make sure it is installed: npm install eventsource');
2716
+ }
2717
+ }
2718
+ }
2719
+
2720
+ async _connect() {
2721
+ if (!this._watchConfig) throw new Error('vars 和 events 不能同时为空');
2722
+ if (this._connectPromise) return this._connectPromise;
2723
+ const EventSource = await this._getEventSource();
2724
+ if (this._sse && this._sse.readyState === EventSource.OPEN) return this;
2725
+ const { varNames, eventNames } = this._watchConfig;
2726
+ const params = new URLSearchParams();
2727
+ if (this._namespace) params.append('namespace', this._namespace);
2728
+ if (varNames.length) params.append('vars', varNames);
2729
+ if (eventNames.length) params.append('events', eventNames);
2730
+ const url = `${this._host}/sse/syncvar?${params.toString()}`;
2731
+ const sse = new EventSource(url);
2732
+ this._sse = sse;
2733
+ this._closing = false;
2734
+ this._attachSyncHandler(sse);
2735
+ this._attachEventListeners(sse);
2736
+ console.log('SSE 连接中...');
2737
+ this._connectPromise = new Promise((resolve, reject) => {
2738
+ let settled = false;
2739
+ const handleOpen = () => {
2740
+ if (settled) return;
2741
+ settled = true;
2742
+ this._reconnectDelay = INITIAL_RECONNECT_DELAY;
2743
+ this._clearReconnectTimer();
2744
+ this._connectPromise = null;
2745
+ console.log('SSE 已连接');
2746
+ resolve(this);
2747
+ };
2748
+ const handleError = () => {
2749
+ const err = new Error('SSE 连接错误');
2750
+ this._onError(err);
2751
+ if (!settled) {
2752
+ settled = true;
2753
+ this._connectPromise = null;
2754
+ reject(err);
2755
+ return
2756
+ }
2757
+ if (this._closing) return;
2758
+ this._connectPromise = null;
2759
+ this._scheduleReconnect();
2760
+ };
2761
+ sse.onopen = handleOpen;
2762
+ sse.onerror = handleError;
2763
+ sse.onmessage = ({ data }) => this._onError(new Error('sseWatch 未处理消息: ' + data));
2764
+ });
2765
+ return this._connectPromise
2766
+ }
2767
+
2768
+ _attachSyncHandler(sse) {
2769
+ sse.addEventListener('$sync', (evt) => {
2770
+ try {
2771
+ const parsed = JSON.parse(evt.data || '{}');
2772
+ const { name, value, changes } = parsed || {};
2773
+ if (!name) throw new Error('名字为空: ' + evt.data);
2774
+ let current = this._data.get(name);
2775
+ if (changes) {
2776
+ if (!current || typeof current !== 'object') throw new Error('数据为空或不可变更: ' + evt.data);
2777
+ changes.forEach(({ t: type, p: path, v: val }) => setNestedProperty(current, path, type, val));
2778
+ } else if (value !== undefined) {
2779
+ current = value;
2780
+ this._data.set(name, current);
2781
+ } else {
2782
+ throw new Error('值为空: ' + evt.data);
2783
+ }
2784
+ const namedCb = this._cbFuncs.get(name);
2785
+ if (namedCb) {
2786
+ try { namedCb({ name, value: current }); } catch (err) { this._onError(err); }
2787
+ } else {
2788
+ const allCb = this._cbFuncs.get('$all');
2789
+ if (allCb) {
2790
+ try { allCb({ name, value: current }); } catch (err) { this._onError(err); }
2791
+ } else {
2792
+ throw new Error('SSE 未处理消息: ' + evt.data);
2793
+ }
2794
+ }
2795
+ } catch (err) {
2796
+ this._onError(err);
2797
+ }
2798
+ });
2799
+ }
2800
+
2801
+ _attachEventListeners(sse) {
2802
+ if (!this._eventListeners.length) return
2803
+ for (const { name, fn } of this._eventListeners) {
2804
+ sse.addEventListener(name, fn);
2805
+ }
2806
+ }
2807
+
2808
+ _scheduleReconnect() {
2809
+ if (this._closing || this._reconnectTimer) return
2810
+ this._reconnectTimer = setTimeout(async () => {
2811
+ this._reconnectTimer = null;
2812
+ try {
2813
+ this._sse?.close();
2814
+ this._sse = null;
2815
+ await this._connect();
2816
+ } catch (err) {
2817
+ this._onError(err);
2818
+ this._scheduleReconnect();
2819
+ }
2820
+ }, this._reconnectDelay);
2821
+ this._reconnectDelay = Math.min(MAX_RECONNECT_DELAY, this._reconnectDelay * 2);
2822
+ }
2823
+
2824
+ _clearReconnectTimer() {
2825
+ if (this._reconnectTimer) {
2826
+ clearTimeout(this._reconnectTimer);
2827
+ this._reconnectTimer = null;
2828
+ }
2829
+ }
2830
+
2831
+ close() {
2832
+ this._closing = true;
2833
+ this._clearReconnectTimer();
2834
+ this._connectPromise = null;
2835
+ try { this._sse?.close(); } catch (e) { /* ignore */ }
2836
+ this._sse = null;
2837
+ }
2838
+
2839
+ /**
2840
+ * 开始监听指定的变量与事件。
2841
+ *
2842
+ * vars: 数组或对象
2843
+ * - 数组:元素可以是字符串(仅订阅)或命名函数(函数名为变量名,回调签名 ({name,value}) )
2844
+ * - 对象:键为变量名,值为回调函数,回调签名 ({name,value})
2845
+ *
2846
+ * events: 数组或对象
2847
+ * - 数组:元素可以是字符串(仅订阅)或命名函数(函数名为事件名,会被注册为 listener)
2848
+ * - 对象:键为事件名,值为回调函数
2849
+ *
2850
+ * 示例:
2851
+ * await sse.watch({ vars: [function myVar(update) { ... }], events: [] });
2852
+ * await sse.watch({ vars: { myVar: (update) => { ... } }, events: { fun1: () => {}, fun2: () => {} } });
2853
+ */
2854
+ async watch({ vars = [], events = [] } = {}) {
2855
+ const varNames = [];
2856
+ const eventNames = [];
2857
+ const eventListeners = [];
2858
+
2859
+ if (Array.isArray(vars)) {
2860
+ for (const item of vars) {
2861
+ if (typeof item === 'function') {
2862
+ const name = item.name;
2863
+ if (!name) throw new Error('vars 回调必须是有名字的函数');
2864
+ varNames.push(name);
2865
+ this._cbFuncs.set(name, item);
2866
+ } else if (typeof item === 'string') {
2867
+ varNames.push(item);
2868
+ } else {
2869
+ throw new Error('vars 数组元素仅支持 string 或 function');
2870
+ }
2871
+ }
2872
+ } else if (typeof vars === 'object' && vars !== null) {
2873
+ for (const [name, fn] of Object.entries(vars)) {
2874
+ if (typeof fn !== 'function') throw new Error(`vars 对象中 "${name}" 的值必须是函数`);
2875
+ varNames.push(name);
2876
+ this._cbFuncs.set(name, fn);
2877
+ }
2878
+ } else {
2879
+ throw new Error('vars 仅支持数组或对象');
2880
+ }
2881
+
2882
+ if (Array.isArray(events)) {
2883
+ for (const item of events) {
2884
+ if (typeof item === 'function') {
2885
+ const name = item.name;
2886
+ if (!name) throw new Error('events 回调必须是有名字的函数');
2887
+ eventNames.push(name);
2888
+ eventListeners.push({ name, fn: item });
2889
+ } else if (typeof item === 'string') {
2890
+ eventNames.push(item);
2891
+ } else {
2892
+ throw new Error('events 数组元素仅支持 string 或 function');
2893
+ }
2894
+ }
2895
+ } else if (typeof events === 'object' && events !== null) {
2896
+ for (const [name, fn] of Object.entries(events)) {
2897
+ if (typeof fn !== 'function') throw new Error(`events 对象中 "${name}" 的值必须是函数`);
2898
+ eventNames.push(name);
2899
+ eventListeners.push({ name, fn });
2900
+ }
2901
+ } else {
2902
+ throw new Error('events 仅支持数组或对象');
2903
+ }
2904
+
2905
+ this._eventListeners = eventListeners;
2906
+ this._watchConfig = { varNames, eventNames };
2907
+ this._closing = false;
2908
+ await this._connect();
2909
+ return this;
2910
+ }
2911
+
2912
+ // 注册事件监听器的别名
2913
+ onEvent(event, listener, options) {
2914
+ if (!this._sse) throw new Error('未连接');
2915
+ this._eventListeners.push({ name: event, fn: listener });
2916
+ return this._sse.addEventListener(event, listener, options);
2917
+ }
2918
+
2919
+ // 移除 SSE 事件监听器
2920
+ offEvent(event, listener, options) {
2921
+ if (!this._sse) throw new Error('未连接');
2922
+ return this._sse.removeEventListener(event, listener, options);
2923
+ }
2924
+
2925
+ // 通过 HTTP POST 将事件发送到服务器
2926
+ emit(event, data) {
2927
+ return SSE.emit(event, data, { host: this._host, namespace: this._namespace });
2928
+ }
2929
+
2930
+ // 获取当前变量快照(Map)。如需单个变量,请使用 get(name)
2931
+ getAll() { return this._data; }
2932
+
2933
+ // 读取单个变量的当前值(如果不存在返回 undefined)
2934
+ get(name) { return this._data.get(name); }
2935
+
2936
+ }
2937
+
2938
+ exports.SSE = SSE;
2939
+ exports.SyncVar = SyncVar;
2940
+ exports.VersionInfo = VersionInfo;
2941
+ exports.WebSocketClient = WebSocketClient;
2942
+ exports.Worker = Worker;
2943
+
2944
+ }));