sonic-ws 1.0.6 → 1.1.1-patch

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright 2025 Lily (liwybloc)
1
+ Copyright 2026 Lily (liwybloc)
2
2
 
3
3
  Licensed under the Apache License, Version 2.0 (the "License");
4
4
  You may not use this software except in compliance with the License.
package/README.md CHANGED
@@ -18,9 +18,11 @@ Developer Friendly:
18
18
 
19
19
  Security:
20
20
  - Tamper-proof; any invalid packet instantly causes closure, and tampering becomes incredibly difficult
21
+ - Basic but immensely effective anti-tampering for browser clients
21
22
  - Built-in ability for handshake packets, preventing repetitive initiation checks in listeners (for example, removes if(!init) everywhere)
22
23
  - Built-in rate limiting for packets; ability for global send & receive, alongside per-packet rate limiting
23
24
  - Built-in disabling & enabling of packets to prevent abuse
25
+ - Prevents any race conditions; your callbacks will not be called until they finish.
24
26
 
25
27
  Performance & Scaling:
26
28
  - Can handle very large packets in microseconds
@@ -35,7 +37,7 @@ Developer Experience:
35
37
  - Many data types to maximize speed, clarity, bandwidth, and security
36
38
  - Debug tools for socket ids, byte size, data logging, etc. for troubleshooting
37
39
  - Very minimal learning curve, easy to work in
38
- - JSDoc's for understanding
40
+ - JSDoc's for understanding; immensely intuitive (personally, I took a break for half a year and came back and snapped right back in)
39
41
 
40
42
  Whether you're making a real-time game, a dashboard, a distributed system, or anything else, SonicWS gets you safe, structured packets, fast.
41
43
 
@@ -109,6 +111,10 @@ ws.on_close((event) => {
109
111
 
110
112
  ## KNOWN ISSUES
111
113
 
114
+ Some weird error messages when invalid inputs are in like CreatePacket() and stuff
115
+
112
116
  ## PLANNED FEATURES
113
117
 
114
- Better error handling
118
+ Better error handling
119
+
120
+ ZLib/gzip compression
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /*
3
- * Copyright 2025 Lily (liwybloc)
3
+ * Copyright 2026 Lily (liwybloc)
4
4
  *
5
5
  * Licensed under the Apache License, Version 2.0 (the "License");
6
6
  * you may not use this file except in compliance with the License.
package/dist/version.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /** Current protocol version */
2
- export declare const VERSION = 13;
2
+ export declare const VERSION = 15;
3
3
  /** Server data suffix */
4
4
  export declare const SERVER_SUFFIX = "SWS";
5
5
  /** Server data suffix in array */
package/dist/version.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /*
3
- * Copyright 2025 Lily (liwybloc)
3
+ * Copyright 2026 Lily (liwybloc)
4
4
  *
5
5
  * Licensed under the Apache License, Version 2.0 (the "License");
6
6
  * you may not use this file except in compliance with the License.
@@ -18,7 +18,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
18
18
  exports.SERVER_SUFFIX_NUMS = exports.SERVER_SUFFIX = exports.VERSION = void 0;
19
19
  const StringUtil_1 = require("./ws/util/StringUtil");
20
20
  /** Current protocol version */
21
- exports.VERSION = 13;
21
+ exports.VERSION = 15;
22
22
  /** Server data suffix */
23
23
  exports.SERVER_SUFFIX = "SWS";
24
24
  /** Server data suffix in array */
@@ -3,17 +3,18 @@
3
3
  */
4
4
  export interface Connection {
5
5
  /**
6
- * List of timers, in object just for efficiency.
6
+ * List of timers.
7
7
  * For internal use only.
8
8
  */
9
- _timers: Record<number, number>;
9
+ _timers: Record<number, [number, (closed: boolean) => void, boolean]>;
10
10
  /**
11
11
  * Sets a timeout that will automatically end when the socket closes
12
12
  * @param call The function to call
13
13
  * @param time The time between now and the call (ms)
14
+ * @param callOnClose If the callback should be fired anyways when the socket closes
14
15
  * @returns The timeout id to be used with socket.clearInterval(id)
15
16
  */
16
- setTimeout(call: () => void, time: number): number;
17
+ setTimeout(call: () => void, time: number, callOnClose: boolean): number;
17
18
  /**
18
19
  * Sets an interval that will automatically end when the socket closes
19
20
  * @param call The function to call
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /*
3
- * Copyright 2025 Lily (liwybloc)
3
+ * Copyright 2026 Lily (liwybloc)
4
4
  *
5
5
  * Licensed under the Apache License, Version 2.0 (the "License");
6
6
  * you may not use this file except in compliance with the License.
@@ -21,12 +21,18 @@ export declare abstract class SonicWSCore implements Connection {
21
21
  private batcher;
22
22
  private bufferHandler;
23
23
  id: number;
24
- _timers: Record<number, number>;
24
+ _timers: Record<number, [number, (closed: boolean) => void, boolean]>;
25
+ private asyncData;
26
+ private asyncMap;
25
27
  constructor(ws: WebSocket, bufferHandler: (val: MessageEvent) => Promise<Uint8Array>);
26
28
  private reading;
27
29
  private serverKeyHandler;
28
30
  private invalidPacket;
29
- private listenPacket;
31
+ private listenLock;
32
+ private packetQueue;
33
+ listenPacket(data: string | [any[], boolean], code: number): void;
34
+ private isAsync;
35
+ private enqueuePacket;
30
36
  private messageHandler;
31
37
  protected listen(key: string, listener: (data: any[]) => void): void;
32
38
  /**
@@ -62,9 +68,8 @@ export declare abstract class SonicWSCore implements Connection {
62
68
  */
63
69
  on(tag: string, listener: (value: any[]) => void): void;
64
70
  raw_send(data: Uint8Array): void;
65
- setTimeout(call: () => void, time: number): number;
66
- setInterval(call: () => void, time: number): number;
67
- private setTimer;
71
+ setTimeout(call: () => void, time: number, callOnClose?: boolean): number;
72
+ setInterval(call: () => void, time: number, callOnClose?: boolean): number;
68
73
  clearTimeout(id: number): void;
69
74
  clearInterval(id: number): void;
70
75
  close(code?: number, reason?: string): void;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /*
3
- * Copyright 2025 Lily (liwybloc)
3
+ * Copyright 2026 Lily (liwybloc)
4
4
  *
5
5
  * Licensed under the Apache License, Version 2.0 (the "License");
6
6
  * you may not use this file except in compliance with the License.
@@ -36,6 +36,8 @@ class SonicWSCore {
36
36
  bufferHandler;
37
37
  id = -1;
38
38
  _timers = {};
39
+ asyncData = {};
40
+ asyncMap = {};
39
41
  constructor(ws, bufferHandler) {
40
42
  this.socket = ws;
41
43
  this.listeners = {
@@ -52,7 +54,11 @@ class SonicWSCore {
52
54
  this.socket.addEventListener('message', this.serverKeyHandler);
53
55
  this.socket.addEventListener('close', (event) => {
54
56
  this.listeners.close.forEach(listener => listener(event));
55
- Object.values(this._timers).forEach(clearTimeout);
57
+ for (const [id, callback, shouldCall] of Object.values(this._timers)) {
58
+ this.clearTimeout(id);
59
+ if (shouldCall)
60
+ callback(true);
61
+ }
56
62
  });
57
63
  this.bufferHandler = bufferHandler;
58
64
  }
@@ -80,6 +86,13 @@ class SonicWSCore {
80
86
  const skData = data.slice(valuesOff + ckLength, data.length);
81
87
  this.serverPackets.holdPackets(Packets_1.Packet.deserializeAll(skData, true));
82
88
  this.batcher.registerSendPackets(this.clientPackets, this);
89
+ for (const p of this.serverPackets.getPackets()) {
90
+ const key = this.serverPackets.getKey(p.tag);
91
+ this.asyncMap[key] = p.async;
92
+ if (p.async) {
93
+ this.asyncData[key] = [false, []];
94
+ }
95
+ }
83
96
  Object.keys(this.preListen).forEach(tag => this.preListen[tag].forEach(listener => {
84
97
  // print the error to console without halting execution
85
98
  if (!this.serverPackets.hasTag(tag))
@@ -97,11 +110,51 @@ class SonicWSCore {
97
110
  console.error(listened);
98
111
  throw new Error("An error occured with data from the server!! This is probably my fault.. make an issue at https://github.com/liwybloc/sonic-ws");
99
112
  }
113
+ listenLock = false;
114
+ packetQueue = [];
100
115
  listenPacket(data, code) {
101
116
  const listeners = this.listeners.event[code];
102
117
  if (!listeners)
103
- return console.warn("Warn: No listener for packet " + this.serverPackets.getTag(code));
104
- (0, PacketUtils_1.listenPacket)(data, listeners, this.invalidPacket);
118
+ return console.warn("Warn: No listener for packet " + code);
119
+ this.enqueuePacket(data, code, listeners);
120
+ }
121
+ isAsync(code) {
122
+ return this.asyncMap[code];
123
+ }
124
+ async enqueuePacket(data, code, listeners) {
125
+ const isAsync = this.isAsync(code);
126
+ let locked;
127
+ let packetQueue;
128
+ let asyncData;
129
+ if (isAsync) {
130
+ asyncData = this.asyncData[code];
131
+ locked = asyncData[0];
132
+ packetQueue = asyncData[1];
133
+ }
134
+ else {
135
+ locked = this.listenLock;
136
+ packetQueue = this.packetQueue;
137
+ }
138
+ if (locked) {
139
+ packetQueue.push([data, code]);
140
+ return;
141
+ }
142
+ if (isAsync)
143
+ asyncData[0] = true;
144
+ else
145
+ this.listenLock = true;
146
+ let currentData = data;
147
+ let currentCode = code;
148
+ while (true) {
149
+ await (0, PacketUtils_1.listenPacket)(currentData, listeners, this.invalidPacket);
150
+ if (packetQueue.length === 0)
151
+ break;
152
+ [currentData, currentCode] = packetQueue.shift();
153
+ }
154
+ if (isAsync)
155
+ asyncData[0] = false;
156
+ else
157
+ this.listenLock = false;
105
158
  }
106
159
  async messageHandler(event) {
107
160
  const data = await this.bufferHandler(event);
@@ -192,22 +245,19 @@ class SonicWSCore {
192
245
  this.listeners.send.forEach(d => d(data));
193
246
  this.socket.send(data);
194
247
  }
195
- setTimeout(call, time) {
248
+ setTimeout(call, time, callOnClose = false) {
196
249
  const timeout = setTimeout(() => {
197
250
  call();
198
251
  this.clearTimeout(timeout);
199
252
  }, time);
200
- this.setTimer(timeout);
253
+ this._timers[timeout] = [timeout, call, callOnClose];
201
254
  return timeout;
202
255
  }
203
- setInterval(call, time) {
256
+ setInterval(call, time, callOnClose = false) {
204
257
  const interval = setInterval(call, time);
205
- this.setTimer(interval);
258
+ this._timers[interval] = [interval, call, callOnClose];
206
259
  return interval;
207
260
  }
208
- setTimer(id) {
209
- this._timers[id] = id;
210
- }
211
261
  clearTimeout(id) {
212
262
  clearTimeout(id);
213
263
  delete this._timers[id];
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /*
3
- * Copyright 2025 Lily (liwybloc)
3
+ * Copyright 2026 Lily (liwybloc)
4
4
  *
5
5
  * Licensed under the Apache License, Version 2.0 (the "License");
6
6
  * you may not use this file except in compliance with the License.
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /*
3
- * Copyright 2025 Lily (liwybloc)
3
+ * Copyright 2026 Lily (liwybloc)
4
4
  *
5
5
  * Licensed under the Apache License, Version 2.0 (the "License");
6
6
  * you may not use this file except in compliance with the License.
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /*
3
- * Copyright 2025 Lily (liwybloc)
3
+ * Copyright 2026 Lily (liwybloc)
4
4
  *
5
5
  * Licensed under the Apache License, Version 2.0 (the "License");
6
6
  * you may not use this file except in compliance with the License.
@@ -5,8 +5,9 @@ export type ValidatorFunction = ((socket: SonicWSConnection, values: any) => boo
5
5
  export type ConvertType<T> = T extends EnumPackage ? PacketType.ENUMS : T;
6
6
  type ImpactType<T extends (PacketType | readonly PacketType[]), K> = T extends PacketType[] ? K[] : K;
7
7
  export declare class Packet<T extends (PacketType | readonly PacketType[])> {
8
- readonly tag: string;
9
8
  defaultEnabled: boolean;
9
+ readonly tag: string;
10
+ readonly async: boolean;
10
11
  readonly maxSize: number;
11
12
  readonly minSize: number;
12
13
  readonly type: T;
@@ -24,7 +25,7 @@ export declare class Packet<T extends (PacketType | readonly PacketType[])> {
24
25
  private sendProcessor;
25
26
  private validator;
26
27
  processReceive: (data: Uint8Array, validationResult: any) => any;
27
- processSend: (data: any[]) => number[];
28
+ processSend: (data: any[]) => Uint8Array;
28
29
  validate: (data: Uint8Array) => boolean;
29
30
  customValidator: ((socket: SonicWSConnection, ...values: any[]) => boolean) | null;
30
31
  constructor(tag: string, schema: PacketSchema<T>, customValidator: ValidatorFunction, enabled: boolean, client: boolean);
@@ -44,10 +45,11 @@ export declare class PacketSchema<T extends (PacketType | readonly PacketType[])
44
45
  enumData: EnumPackage[];
45
46
  dontSpread: boolean;
46
47
  autoFlatten: boolean;
48
+ async: boolean;
47
49
  object: boolean;
48
50
  constructor(object: boolean);
49
51
  testObject(): this is PacketSchema<PacketType[]>;
50
- static single<T extends PacketType | EnumPackage>(type: T, dataMax: number, dataMin: number, dontSpread: boolean, dataBatching: number, maxBatchSize: number, rateLimit: number): PacketSchema<ConvertType<T>>;
51
- static object<T extends readonly (PacketType | EnumPackage)[]>(types: T, dataMaxes: number[], dataMins: number[], dontSpread: boolean, autoFlatten: boolean, dataBatching: number, maxBatchSize: number, rateLimit: number): PacketSchema<ConvertType<T[number]>[]>;
52
+ static single<T extends PacketType | EnumPackage>(type: T, dataMax: number, dataMin: number, dontSpread: boolean, dataBatching: number, maxBatchSize: number, rateLimit: number, async: boolean): PacketSchema<ConvertType<T>>;
53
+ static object<T extends readonly (PacketType | EnumPackage)[]>(types: T, dataMaxes: number[], dataMins: number[], dontSpread: boolean, autoFlatten: boolean, dataBatching: number, maxBatchSize: number, rateLimit: number, async: boolean): PacketSchema<ConvertType<T[number]>[]>;
52
54
  }
53
55
  export {};
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /*
3
- * Copyright 2025 Lily (liwybloc)
3
+ * Copyright 2026 Lily (liwybloc)
4
4
  *
5
5
  * Licensed under the Apache License, Version 2.0 (the "License");
6
6
  * you may not use this file except in compliance with the License.
@@ -25,8 +25,9 @@ const PacketType_1 = require("./PacketType");
25
25
  const BufferUtil_1 = require("../util/BufferUtil");
26
26
  const StringUtil_1 = require("../util/StringUtil");
27
27
  class Packet {
28
- tag;
29
28
  defaultEnabled;
29
+ tag;
30
+ async;
30
31
  maxSize;
31
32
  minSize;
32
33
  type;
@@ -51,6 +52,7 @@ class Packet {
51
52
  this.tag = tag;
52
53
  this.defaultEnabled = enabled;
53
54
  this.client = client;
55
+ this.async = schema.async;
54
56
  this.enumData = schema.enumData;
55
57
  this.rateLimit = schema.rateLimit;
56
58
  this.dontSpread = schema.dontSpread;
@@ -85,7 +87,7 @@ class Packet {
85
87
  this.sendProcessor = (0, PacketProcessors_1.createSendProcessor)(this.type);
86
88
  }
87
89
  this.processReceive = (data, validationResult) => this.receiveProcessor(data, validationResult, 0);
88
- this.processSend = (data) => this.sendProcessor(data);
90
+ this.processSend = (data) => new Uint8Array(this.sendProcessor(data));
89
91
  this.validate = (data) => this.validator(data, 0);
90
92
  this.customValidator = customValidator;
91
93
  }
@@ -119,6 +121,7 @@ class Packet {
119
121
  const sharedData = [
120
122
  this.tag.length, ...(0, StringUtil_1.processCharCodes)(this.tag),
121
123
  this.dontSpread ? 1 : 0,
124
+ this.async ? 1 : 0,
122
125
  this.dataBatching,
123
126
  this.enumData.length, ...this.enumData.map(x => x.serialize()).flat(),
124
127
  ];
@@ -160,6 +163,8 @@ class Packet {
160
163
  const tag = (0, BufferUtil_1.as8String)(data.slice(offset, offset += tagLength));
161
164
  // then read dont spread, go up 1
162
165
  const dontSpread = data[offset++] == 1;
166
+ // read async
167
+ const async = data[offset++] == 1;
163
168
  // read batching, up 1
164
169
  const dataBatching = data[offset++];
165
170
  // read enum length, up 1
@@ -205,7 +210,7 @@ class Packet {
205
210
  let index = 0;
206
211
  const finalTypes = types.map(x => x == PacketType_1.PacketType.ENUMS ? enums[index++] : x); // convert enums to their enum packages
207
212
  // make schema
208
- const schema = PacketSchema.object(finalTypes, dataMaxes, dataMins, dontSpread, autoFlatten, dataBatching, -1, -1);
213
+ const schema = PacketSchema.object(finalTypes, dataMaxes, dataMins, dontSpread, autoFlatten, dataBatching, -1, -1, async);
209
214
  return [
210
215
  new Packet(tag, schema, null, false, client),
211
216
  // +1 to go next
@@ -224,7 +229,7 @@ class Packet {
224
229
  // do enum stuff
225
230
  const finalType = type == PacketType_1.PacketType.ENUMS ? enums[0] : type; // convert enum to enum package
226
231
  // make schema
227
- const schema = PacketSchema.single(finalType, dataMax, dataMin, dontSpread, dataBatching, -1, -1);
232
+ const schema = PacketSchema.single(finalType, dataMax, dataMin, dontSpread, dataBatching, -1, -1, async);
228
233
  return [
229
234
  new Packet(tag, schema, null, false, client),
230
235
  // +1 to go next
@@ -253,6 +258,7 @@ class PacketSchema {
253
258
  enumData = [];
254
259
  dontSpread = false;
255
260
  autoFlatten = false;
261
+ async = false;
256
262
  object;
257
263
  constructor(object) {
258
264
  this.object = object;
@@ -260,7 +266,7 @@ class PacketSchema {
260
266
  testObject() {
261
267
  return this.object;
262
268
  }
263
- static single(type, dataMax, dataMin, dontSpread, dataBatching, maxBatchSize, rateLimit) {
269
+ static single(type, dataMax, dataMin, dontSpread, dataBatching, maxBatchSize, rateLimit, async) {
264
270
  const schema = new PacketSchema(false);
265
271
  if (typeof type == 'number') {
266
272
  schema.type = type;
@@ -277,9 +283,10 @@ class PacketSchema {
277
283
  schema.dataBatching = dataBatching;
278
284
  schema.maxBatchSize = maxBatchSize;
279
285
  schema.rateLimit = rateLimit;
286
+ schema.async = async;
280
287
  return schema;
281
288
  }
282
- static object(types, dataMaxes, dataMins, dontSpread, autoFlatten, dataBatching, maxBatchSize, rateLimit) {
289
+ static object(types, dataMaxes, dataMins, dontSpread, autoFlatten, dataBatching, maxBatchSize, rateLimit, async) {
283
290
  if (types.length != dataMaxes.length || types.length != dataMins.length)
284
291
  throw new Error("There is an inbalance between the amount of types, data maxes, and data mins!");
285
292
  const schema = new PacketSchema(true);
@@ -300,6 +307,7 @@ class PacketSchema {
300
307
  schema.dataBatching = dataBatching;
301
308
  schema.maxBatchSize = maxBatchSize;
302
309
  schema.rateLimit = rateLimit;
310
+ schema.async = async;
303
311
  return schema;
304
312
  }
305
313
  }
@@ -19,11 +19,17 @@ export declare class SonicWSConnection implements Connection {
19
19
  handshakeComplete: boolean;
20
20
  /** The index of the connection; unique for all connected, recycles old disconnected ids. Should be safe for INTS_C unless you have more than 27,647 connected at once. */
21
21
  id: number;
22
- _timers: Record<number, number>;
22
+ _timers: Record<number, [number, (closed: boolean) => void, boolean]>;
23
+ private asyncMap;
24
+ private asyncData;
25
+ private closed;
23
26
  constructor(socket: WS.WebSocket, host: SonicWSServer, id: number, handshakePacket: string | null, clientRateLimit: number, serverRateLimit: number);
24
27
  private parseData;
25
28
  private handshakeHandler;
26
29
  private invalidPacket;
30
+ private isAsync;
31
+ private listenLock;
32
+ private packetQueue;
27
33
  private listenPacket;
28
34
  private messageHandler;
29
35
  /**
@@ -55,7 +61,7 @@ export declare class SonicWSConnection implements Connection {
55
61
  /**
56
62
  * For internal use.
57
63
  */
58
- send_processed(code: number, data: number[], packet: Packet<any>): void;
64
+ send_processed(code: number, data: Uint8Array, packet: Packet<any>): void;
59
65
  /**
60
66
  * Sends a packet with the tag and values
61
67
  * @param tag The tag to send
@@ -80,8 +86,8 @@ export declare class SonicWSConnection implements Connection {
80
86
  togglePrint(): void;
81
87
  raw_send(data: Uint8Array): void;
82
88
  close(code?: number, reason?: string | Buffer): void;
83
- setTimeout(call: () => void, time: number): number;
84
- setInterval(call: () => void, time: number): number;
89
+ setTimeout(call: () => void, time: number, callOnClose?: boolean): number;
90
+ setInterval(call: () => void, time: number, callOnClose?: boolean): number;
85
91
  clearTimeout(id: number): void;
86
92
  clearInterval(id: number): void;
87
93
  /**
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /*
3
- * Copyright 2025 Lily (liwybloc)
3
+ * Copyright 2026 Lily (liwybloc)
4
4
  *
5
5
  * Licensed under the Apache License, Version 2.0 (the "License");
6
6
  * you may not use this file except in compliance with the License.
@@ -80,15 +80,22 @@ class SonicWSConnection {
80
80
  /** The index of the connection; unique for all connected, recycles old disconnected ids. Should be safe for INTS_C unless you have more than 27,647 connected at once. */
81
81
  id;
82
82
  _timers = {};
83
+ asyncMap = {};
84
+ asyncData = {};
85
+ closed = false;
83
86
  constructor(socket, host, id, handshakePacket, clientRateLimit, serverRateLimit) {
84
87
  this.socket = socket;
85
88
  this.host = host;
86
89
  this.id = id;
87
90
  this.handshakePacket = handshakePacket;
88
91
  this.listeners = {};
89
- for (const key of host.clientPackets.getTags()) {
90
- this.listeners[key] = [];
91
- this.enabledPackets[key] = host.clientPackets.getPacket(key).defaultEnabled;
92
+ for (const tag of host.clientPackets.getTags()) {
93
+ this.listeners[tag] = [];
94
+ const pack = host.clientPackets.getPacket(tag);
95
+ this.enabledPackets[tag] = pack.defaultEnabled;
96
+ this.asyncMap[tag] = pack.async;
97
+ if (pack.async)
98
+ this.asyncData[tag] = [false, []];
92
99
  }
93
100
  this.setInterval = this.setInterval.bind(this);
94
101
  this.batcher = new BatchHelper_1.BatchHelper();
@@ -108,7 +115,12 @@ class SonicWSConnection {
108
115
  this.socket.addEventListener('message', this.handshakeLambda);
109
116
  }
110
117
  this.socket.on('close', () => {
111
- Object.values(this._timers).forEach(clearTimeout);
118
+ this.closed = true;
119
+ for (const [id, callback, shouldCall] of Object.values(this._timers)) {
120
+ this.clearTimeout(id);
121
+ if (shouldCall)
122
+ callback(true);
123
+ }
112
124
  });
113
125
  }
114
126
  parseData(event) {
@@ -157,8 +169,45 @@ class SonicWSConnection {
157
169
  console.log("Closure cause", listened);
158
170
  this.socket.close(4003, listened);
159
171
  }
160
- listenPacket(data, tag) {
161
- (0, PacketUtils_1.listenPacket)(data, this.listeners[tag], this.invalidPacket);
172
+ isAsync(tag) {
173
+ return this.asyncMap[tag];
174
+ }
175
+ listenLock = false;
176
+ packetQueue = [];
177
+ async listenPacket(data, tag) {
178
+ if (this.closed)
179
+ return;
180
+ const isAsync = this.isAsync(tag);
181
+ let locked, packetQueue, asyncData;
182
+ if (isAsync) {
183
+ asyncData = this.asyncData[tag];
184
+ locked = asyncData[0];
185
+ packetQueue = asyncData[1];
186
+ }
187
+ else {
188
+ locked = this.listenLock;
189
+ packetQueue = this.packetQueue;
190
+ }
191
+ if (locked) {
192
+ packetQueue.push([data, tag]);
193
+ return;
194
+ }
195
+ if (isAsync)
196
+ asyncData[0] = true;
197
+ else
198
+ this.listenLock = true;
199
+ let currentData = data;
200
+ let currentTag = tag;
201
+ while (true) {
202
+ await (0, PacketUtils_1.listenPacket)(currentData, this.listeners[currentTag], this.invalidPacket);
203
+ if (packetQueue.length === 0)
204
+ break;
205
+ [currentData, currentTag] = packetQueue.shift();
206
+ }
207
+ if (isAsync)
208
+ asyncData[0] = false;
209
+ else
210
+ this.listenLock = false;
162
211
  }
163
212
  messageHandler(data) {
164
213
  if (data == null)
@@ -196,7 +245,7 @@ class SonicWSConnection {
196
245
  * @returns If it's closed or not
197
246
  */
198
247
  isClosed() {
199
- return this.socket.readyState == WS.CLOSED;
248
+ return this.closed || this.socket.readyState == WS.CLOSED;
200
249
  }
201
250
  /**
202
251
  * Listens for when the connection closes
@@ -270,19 +319,20 @@ class SonicWSConnection {
270
319
  this.socket.send(data);
271
320
  }
272
321
  close(code = 1000, reason) {
322
+ this.closed = true;
273
323
  this.socket.close(code, reason);
274
324
  }
275
- setTimeout(call, time) {
325
+ setTimeout(call, time, callOnClose = false) {
276
326
  const timeout = setTimeout(() => {
277
327
  call();
278
328
  this.clearTimeout(timeout);
279
329
  }, time);
280
- this._timers[timeout] = timeout;
330
+ this._timers[timeout] = [timeout, call, callOnClose];
281
331
  return timeout;
282
332
  }
283
- setInterval(call, time) {
333
+ setInterval(call, time, callOnClose = false) {
284
334
  const interval = setInterval(call, time);
285
- this._timers[interval] = interval;
335
+ this._timers[interval] = [interval, call, callOnClose];
286
336
  return interval;
287
337
  }
288
338
  clearTimeout(id) {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /*
3
- * Copyright 2025 Lily (liwybloc)
3
+ * Copyright 2026 Lily (liwybloc)
4
4
  *
5
5
  * Licensed under the Apache License, Version 2.0 (the "License");
6
6
  * you may not use this file except in compliance with the License.
@@ -94,6 +94,11 @@ class SonicWSServer {
94
94
  this.connections.splice(this.connections.indexOf(sonicConnection), 1);
95
95
  delete this.connectionMap[sonicConnection.id];
96
96
  this.availableIds.push(sonicConnection.id);
97
+ if (this.tags.has(sonicConnection)) {
98
+ for (const tag of this.tags.get(sonicConnection))
99
+ this.tagsInv.get(tag)?.delete(sonicConnection);
100
+ this.tags.delete(sonicConnection);
101
+ }
97
102
  });
98
103
  });
99
104
  fetch('https://raw.githubusercontent.com/liwybloc/sonic-ws/refs/heads/main/release/version')
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /*
3
- * Copyright 2025 Lily (liwybloc)
3
+ * Copyright 2026 Lily (liwybloc)
4
4
  *
5
5
  * Licensed under the Apache License, Version 2.0 (the "License");
6
6
  * you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- export declare function toPacketBuffer(code: number, data: number[]): Uint8Array;
1
+ export declare function toPacketBuffer(code: number, data: Uint8Array): Uint8Array;
2
2
  export declare function splitBuffer(arr: Uint8Array, x: number): number[][];
3
3
  export declare function as8String(data: Uint8Array): string;
4
4
  export declare function as16String(data: Uint8Array): string;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /*
3
- * Copyright 2025 Lily (liwybloc)
3
+ * Copyright 2026 Lily (liwybloc)
4
4
  *
5
5
  * Licensed under the Apache License, Version 2.0 (the "License");
6
6
  * you may not use this file except in compliance with the License.
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /*
3
- * Copyright 2025 Lily (liwybloc)
3
+ * Copyright 2026 Lily (liwybloc)
4
4
  *
5
5
  * Licensed under the Apache License, Version 2.0 (the "License");
6
6
  * you may not use this file except in compliance with the License.
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /*
3
- * Copyright 2025 Lily (liwybloc)
3
+ * Copyright 2026 Lily (liwybloc)
4
4
  *
5
5
  * Licensed under the Apache License, Version 2.0 (the "License");
6
6
  * you may not use this file except in compliance with the License.
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /*
3
- * Copyright 2025 Lily (liwybloc)
3
+ * Copyright 2026 Lily (liwybloc)
4
4
  *
5
5
  * Licensed under the Apache License, Version 2.0 (the "License");
6
6
  * you may not use this file except in compliance with the License.
@@ -10,6 +10,6 @@ export declare class BatchHelper {
10
10
  registerSendPackets(packetHolder: PacketHolder, conn: Connection): void;
11
11
  private initiateBatch;
12
12
  private startBatch;
13
- batchPacket(code: number, data: number[]): void;
13
+ batchPacket(code: number, data: Uint8Array): void;
14
14
  static unravelBatch(packet: Packet<any>, data: Uint8Array, socket: SonicWSConnection | null): any[] | string;
15
15
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /*
3
- * Copyright 2025 Lily (liwybloc)
3
+ * Copyright 2026 Lily (liwybloc)
4
4
  *
5
5
  * Licensed under the Apache License, Version 2.0 (the "License");
6
6
  * you may not use this file except in compliance with the License.
@@ -39,10 +39,10 @@ class BatchHelper {
39
39
  }
40
40
  startBatch(code) {
41
41
  this.batchTimeouts[code] = this.conn.setTimeout(() => {
42
- this.conn.raw_send((0, BufferUtil_1.toPacketBuffer)(code, this.batchedData[code]));
42
+ this.conn.raw_send((0, BufferUtil_1.toPacketBuffer)(code, new Uint8Array(this.batchedData[code])));
43
43
  this.batchedData[code] = [];
44
44
  delete this.batchTimeouts[code];
45
- }, this.batchTimes[code]);
45
+ }, this.batchTimes[code], false);
46
46
  }
47
47
  batchPacket(code, data) {
48
48
  const batch = this.batchedData[code];
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /*
3
- * Copyright 2025 Lily (liwybloc)
3
+ * Copyright 2026 Lily (liwybloc)
4
4
  *
5
5
  * Licensed under the Apache License, Version 2.0 (the "License");
6
6
  * you may not use this file except in compliance with the License.
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /*
3
- * Copyright 2025 Lily (liwybloc)
3
+ * Copyright 2026 Lily (liwybloc)
4
4
  *
5
5
  * Licensed under the Apache License, Version 2.0 (the "License");
6
6
  * you may not use this file except in compliance with the License.
@@ -43,7 +43,6 @@ class PacketHolder {
43
43
  this.packetMap = {};
44
44
  if (!packets)
45
45
  return;
46
- this.packets = packets;
47
46
  this.holdPackets(packets);
48
47
  }
49
48
  /** Assigns a new unique key to a tag */
@@ -57,6 +56,7 @@ class PacketHolder {
57
56
  * @param packets Array of packets to register
58
57
  */
59
58
  holdPackets(packets) {
59
+ this.packets = packets;
60
60
  for (const packet of packets)
61
61
  this.createKey(packet.tag), this.packetMap[packet.tag] = packet;
62
62
  }
@@ -9,14 +9,14 @@ import { EnumPackage } from "../enums/EnumType";
9
9
  * @param values The values
10
10
  * @returns The indexed code, the data, and the packet schema
11
11
  */
12
- export declare function processPacket(packets: PacketHolder, tag: string, values: any[]): [code: number, data: number[], packet: Packet<any>];
12
+ export declare function processPacket(packets: PacketHolder, tag: string, values: any[]): [code: number, data: Uint8Array, packet: Packet<any>];
13
13
  /**
14
14
  * Calls the listener for a packet with error callback
15
15
  * @param listened The listened data
16
16
  * @param listeners The listeners to run
17
17
  * @param errorCB The callback if something goes wrong
18
18
  */
19
- export declare function listenPacket(listened: string | [any[], boolean], listeners: ((...values: any) => void)[], errorCB: (data: string) => void): void;
19
+ export declare function listenPacket(listened: string | [any[], boolean], listeners: ((...values: any) => void | Promise<void>)[], errorCB: (data: string) => void): Promise<void>;
20
20
  /** Valid packet type */
21
21
  export type ArguableType = PacketType | EnumPackage;
22
22
  /** Shared packet setting types */
@@ -47,6 +47,8 @@ export type SharedPacketSettings = {
47
47
  enabled?: boolean;
48
48
  /** A validation function that is called whenever data is received. Return true for success, return false to kick socket. */
49
49
  validator?: ValidatorFunction;
50
+ /** If this is true, other packets will be processed even if this one isn't finished; it'll still prevent it from calling twice before this finishes though. Defaults to false. */
51
+ async?: boolean;
50
52
  };
51
53
  /** Settings for single-typed packets */
52
54
  export type SinglePacketSettings = SharedPacketSettings & {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /*
3
- * Copyright 2025 Lily (liwybloc)
3
+ * Copyright 2026 Lily (liwybloc)
4
4
  *
5
5
  * Licensed under the Apache License, Version 2.0 (the "License");
6
6
  * you may not use this file except in compliance with the License.
@@ -65,7 +65,7 @@ function processPacket(packets, tag, values) {
65
65
  }
66
66
  }
67
67
  }
68
- return [code, values.length > 0 ? packet.processSend(values) : [], packet];
68
+ return [code, values.length > 0 ? packet.processSend(values) : new Uint8Array([]), packet];
69
69
  }
70
70
  /**
71
71
  * Calls the listener for a packet with error callback
@@ -73,16 +73,21 @@ function processPacket(packets, tag, values) {
73
73
  * @param listeners The listeners to run
74
74
  * @param errorCB The callback if something goes wrong
75
75
  */
76
- function listenPacket(listened, listeners, errorCB) {
77
- // if invalid then ignore it and call back
78
- if (typeof listened == 'string')
76
+ async function listenPacket(listened, listeners, errorCB) {
77
+ if (typeof listened === 'string')
79
78
  return errorCB(listened);
80
79
  const [processed, flatten] = listened;
81
80
  try {
82
- if (flatten && Array.isArray(processed))
83
- listeners.forEach(l => l(...processed));
84
- else
85
- listeners.forEach(l => l(processed));
81
+ if (flatten && Array.isArray(processed)) {
82
+ for (const l of listeners) {
83
+ await l(...processed);
84
+ }
85
+ }
86
+ else {
87
+ for (const l of listeners) {
88
+ await l(processed);
89
+ }
90
+ }
86
91
  }
87
92
  catch (err) {
88
93
  errorCB(err);
@@ -127,7 +132,9 @@ function clampDataMin(dataMin, dataMax) {
127
132
  * @throws {Error} If the `type` is invalid.
128
133
  */
129
134
  function CreatePacket(settings) {
130
- let { tag, type = PacketType_1.PacketType.NONE, dataMax = 1, dataMin, noDataRange = false, dontSpread = false, validator = null, dataBatching = 0, maxBatchSize = 10, rateLimit = 0, enabled = true } = settings;
135
+ let { tag, type = PacketType_1.PacketType.NONE, dataMax = 1, dataMin = 1, noDataRange = false, dontSpread = false, validator = null, dataBatching = 0, maxBatchSize = 10, rateLimit = 0, enabled = true, async = false } = settings;
136
+ if (!tag)
137
+ throw new Error("Tag not selected!");
131
138
  if (noDataRange) {
132
139
  dataMin = 0;
133
140
  dataMax = MAX_DATA_MAX;
@@ -137,7 +144,7 @@ function CreatePacket(settings) {
137
144
  if (isInvalidType(type)) {
138
145
  throw new Error(`Invalid packet type: ${type}`);
139
146
  }
140
- const schema = Packets_1.PacketSchema.single(type, clampDataMax(dataMax), clampDataMin(dataMin, dataMax), dontSpread, dataBatching, maxBatchSize, rateLimit);
147
+ const schema = Packets_1.PacketSchema.single(type, clampDataMax(dataMax), clampDataMin(dataMin, dataMax), dontSpread, dataBatching, maxBatchSize, rateLimit, async);
141
148
  return new Packets_1.Packet(tag, schema, validator, enabled, false);
142
149
  }
143
150
  /**
@@ -148,7 +155,11 @@ function CreatePacket(settings) {
148
155
  * @throws {Error} If any type in `types` is invalid.
149
156
  */
150
157
  function CreateObjPacket(settings) {
151
- let { tag, types, dataMaxes, dataMins, noDataRange = false, dontSpread = false, autoFlatten = false, validator = null, dataBatching = 0, maxBatchSize = 10, rateLimit = 0, enabled = true } = settings;
158
+ let { tag, types = [], dataMaxes, dataMins, noDataRange = false, dontSpread = false, autoFlatten = false, validator = null, dataBatching = 0, maxBatchSize = 10, rateLimit = 0, enabled = true, async = false } = settings;
159
+ if (!tag)
160
+ throw new Error("Tag not selected!");
161
+ if (types.length == 0)
162
+ throw new Error("Types is set to 0 length");
152
163
  for (const type of types) {
153
164
  if (!isInvalidType(type))
154
165
  continue;
@@ -170,7 +181,7 @@ function CreateObjPacket(settings) {
170
181
  }
171
182
  const clampedDataMaxes = dataMaxes.map(clampDataMax);
172
183
  const clampedDataMins = dataMins.map((m, i) => types[i] == PacketType_1.PacketType.NONE ? 0 : clampDataMin(m, clampedDataMaxes[i]));
173
- const schema = Packets_1.PacketSchema.object(types, clampedDataMaxes, clampedDataMins, dontSpread, autoFlatten, dataBatching, maxBatchSize, rateLimit);
184
+ const schema = Packets_1.PacketSchema.object(types, clampedDataMaxes, clampedDataMins, dontSpread, autoFlatten, dataBatching, maxBatchSize, rateLimit, async);
174
185
  return new Packets_1.Packet(tag, schema, validator, enabled, false);
175
186
  }
176
187
  /**
@@ -180,7 +191,7 @@ function CreateObjPacket(settings) {
180
191
  * @returns The constructed packet structure data.
181
192
  */
182
193
  function CreateEnumPacket(settings) {
183
- const { tag, enumData, dataMax = 1, dataMin = 0, noDataRange = false, dontSpread = false, validator = null, dataBatching = 0, maxBatchSize = 10, rateLimit = 0, enabled = true } = settings;
194
+ const { tag, enumData, dataMax = 1, dataMin = 0, noDataRange = false, dontSpread = false, validator = null, dataBatching = 0, maxBatchSize = 10, rateLimit = 0, enabled = true, async = false } = settings;
184
195
  return CreatePacket({
185
196
  tag: tag,
186
197
  type: enumData,
@@ -193,6 +204,7 @@ function CreateEnumPacket(settings) {
193
204
  maxBatchSize,
194
205
  rateLimit,
195
206
  enabled,
207
+ async,
196
208
  });
197
209
  }
198
210
  /**
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /*
3
- * Copyright 2025 Lily (liwybloc)
3
+ * Copyright 2026 Lily (liwybloc)
4
4
  *
5
5
  * Licensed under the Apache License, Version 2.0 (the "License");
6
6
  * you may not use this file except in compliance with the License.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sonic-ws",
3
- "version": "1.0.6",
3
+ "version": "1.1.1-patch",
4
4
  "description": "Ultra-lightweight, high-performance, and bandwidth efficient websocket library",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -24,7 +24,8 @@
24
24
  "author": "lily",
25
25
  "license": "Apache-2.0",
26
26
  "dependencies": {
27
- "ws": "^8.18.2"
27
+ "ws": "^8.18.2",
28
+ "zstd-codec": "^0.1.5"
28
29
  },
29
30
  "devDependencies": {
30
31
  "@types/node": "^24.2.1",