sonic-ws 1.2.2 → 1.3.0-min

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.
@@ -54,6 +54,7 @@ const PacketUtils_1 = require("../util/packets/PacketUtils");
54
54
  const BatchHelper_1 = require("../util/packets/BatchHelper");
55
55
  const RateHandler_1 = require("../util/packets/RateHandler");
56
56
  const BufferUtil_1 = require("../util/BufferUtil");
57
+ const Connection_1 = require("../Connection");
57
58
  const CLIENT_RATELIMIT_TAG = "C", SERVER_RATELIMIT_TAG = "S";
58
59
  class SonicWSConnection {
59
60
  /** Raw 'ws' library socket */
@@ -61,15 +62,16 @@ class SonicWSConnection {
61
62
  host;
62
63
  listeners;
63
64
  print = false;
65
+ name;
64
66
  handshakePacket;
65
67
  handshakeLambda;
66
68
  messageLambda = (data) => this.messageHandler(this.parseData(data));
67
69
  handshakedMessageLambda = (data) => {
68
70
  const parsed = this.parseData(data);
69
71
  if (parsed == null)
70
- return this.socket.close(4004);
72
+ return this.socket.close(Connection_1.CloseCodes.INVALID_DATA);
71
73
  if (parsed[0] == this.handshakePacket)
72
- return this.socket.close(4005);
74
+ return this.socket.close(Connection_1.CloseCodes.REPEATED_HANDSHAKE);
73
75
  this.messageHandler(parsed);
74
76
  };
75
77
  batcher;
@@ -83,11 +85,11 @@ class SonicWSConnection {
83
85
  asyncMap = {};
84
86
  asyncData = {};
85
87
  closed = false;
86
- _state = WebSocket.OPEN;
87
88
  constructor(socket, host, id, handshakePacket, clientRateLimit, serverRateLimit) {
88
89
  this.socket = socket;
89
90
  this.host = host;
90
91
  this.id = id;
92
+ this.name = "Socket " + id;
91
93
  this.handshakePacket = handshakePacket;
92
94
  this.listeners = {};
93
95
  for (const tag of host.clientPackets.getTags()) {
@@ -128,6 +130,9 @@ class SonicWSConnection {
128
130
  for (const packet of host.clientPackets.getPackets()) {
129
131
  delete packet.lastReceived[this.id];
130
132
  }
133
+ for (const packet of host.serverPackets.getPackets()) {
134
+ delete packet.lastSent[this.id];
135
+ }
131
136
  });
132
137
  }
133
138
  parseData(event) {
@@ -137,22 +142,22 @@ class SonicWSConnection {
137
142
  return null;
138
143
  const message = new Uint8Array(event.data);
139
144
  if (this.print)
140
- console.log(`\x1b[31m⬇ \x1b[38;5;245m(${this.id},${message.byteLength})\x1b[0m`, (0, BufferUtil_1.stringifyBuffer)(message));
145
+ console.log(`\x1b[31m⬇ \x1b[38;5;245m(${this.id},${message.byteLength})\x1b[0m`, (message.length > 0 && this.host.clientPackets.getTag(message[0])) || "<INVALID>", (0, BufferUtil_1.stringifyBuffer)(message));
141
146
  if (message.byteLength < 1) {
142
- this.socket.close(4001);
147
+ this.socket.close(Connection_1.CloseCodes.SMALL);
143
148
  return null;
144
149
  }
145
150
  const key = message[0];
146
151
  const value = message.slice(1);
147
152
  // not a key, bye bye
148
153
  if (!this.host.clientPackets.hasKey(key)) {
149
- this.socket.close(4002);
154
+ this.socket.close(Connection_1.CloseCodes.INVALID_KEY);
150
155
  return null;
151
156
  }
152
157
  const tag = this.host.clientPackets.getTag(key);
153
158
  // disabled, bye bye
154
159
  if (!this.enabledPackets[tag]) {
155
- this.socket.close(4006);
160
+ this.socket.close(Connection_1.CloseCodes.DISABLED_PACKET);
156
161
  return null;
157
162
  }
158
163
  if (this.rater.trigger("client" + key))
@@ -162,9 +167,9 @@ class SonicWSConnection {
162
167
  handshakeHandler(data) {
163
168
  const parsed = this.parseData(data);
164
169
  if (parsed == null)
165
- return this.socket.close(4004);
170
+ return this.socket.close(Connection_1.CloseCodes.INVALID_DATA);
166
171
  if (parsed[0] != this.handshakePacket) {
167
- this.socket.close(4004);
172
+ this.socket.close(Connection_1.CloseCodes.INVALID_DATA);
168
173
  return;
169
174
  }
170
175
  this.messageHandler(parsed);
@@ -174,18 +179,31 @@ class SonicWSConnection {
174
179
  }
175
180
  invalidPacket(listened) {
176
181
  console.log("Closure cause", listened);
177
- this.socket.close(4003, listened);
182
+ this.socket.close(Connection_1.CloseCodes.INVALID_PACKET, listened);
178
183
  }
179
184
  isAsync(tag) {
180
185
  return this.asyncMap[tag];
181
186
  }
182
187
  listenLock = false;
183
188
  packetQueue = [];
184
- async listenPacket(data, tag) {
189
+ async listenPacket(data, tag, packetQueue, isAsync, asyncData) {
185
190
  if (this.closed)
186
191
  return;
187
- if (await this.callMiddleware('onReceive_post', tag, typeof data == 'string' ? [data] : data[0]))
192
+ await (0, PacketUtils_1.listenPacket)(data, this.listeners[tag], this.invalidPacket);
193
+ await this.callMiddleware('onReceive_post', tag, typeof data == 'string' ? [data] : data[0]);
194
+ if (isAsync)
195
+ asyncData[0] = false;
196
+ else
197
+ this.listenLock = false;
198
+ if (packetQueue.length != 0) {
199
+ this.messageHandler(packetQueue.shift());
188
200
  return;
201
+ }
202
+ }
203
+ async messageHandler(data, recall = false) {
204
+ if (data == null)
205
+ return;
206
+ const [tag, value] = data;
189
207
  const isAsync = this.isAsync(tag);
190
208
  let locked, packetQueue, asyncData;
191
209
  if (isAsync) {
@@ -198,54 +216,39 @@ class SonicWSConnection {
198
216
  packetQueue = this.packetQueue;
199
217
  }
200
218
  if (locked) {
201
- packetQueue.push([data, tag]);
219
+ packetQueue.push(data);
202
220
  return;
203
221
  }
204
222
  if (isAsync)
205
223
  asyncData[0] = true;
206
224
  else
207
225
  this.listenLock = true;
208
- let currentData = data;
209
- let currentTag = tag;
210
- while (true) {
211
- await (0, PacketUtils_1.listenPacket)(currentData, this.listeners[currentTag], this.invalidPacket);
212
- if (packetQueue.length === 0)
213
- break;
214
- [currentData, currentTag] = packetQueue.shift();
215
- }
216
- if (isAsync)
217
- asyncData[0] = false;
218
- else
219
- this.listenLock = false;
220
- }
221
- async messageHandler(data) {
222
- if (data == null)
223
- return;
224
- const [tag, value] = data;
225
226
  const packet = this.host.clientPackets.getPacket(tag);
226
- if (await this.callMiddleware('onReceive_pre', packet.tag, value))
227
+ if (!recall && await this.callMiddleware('onReceive_pre', packet.tag, value, value.length))
227
228
  return;
228
229
  if (packet.rereference && value.length == 0) {
229
- if (packet.lastReceived[this.id] === undefined)
230
+ const lastRecv = packet.lastReceived[this.id];
231
+ if (lastRecv === undefined)
230
232
  return this.invalidPacket("No previous value to rereference");
231
- this.listenPacket(packet.lastReceived[this.id], tag);
233
+ this.listenPacket(lastRecv, tag, packetQueue, isAsync, asyncData);
232
234
  return;
233
235
  }
234
236
  if (packet.dataBatching == 0) {
235
- const res = packet.lastReceived[this.id] = await packet.listen(value, this);
236
- this.listenPacket(res, tag);
237
+ const res = await packet.listen(value, this);
238
+ packet.lastReceived[this.id] = res;
239
+ this.listenPacket(res, tag, packetQueue, isAsync, asyncData);
237
240
  return;
238
241
  }
239
242
  const batchData = await BatchHelper_1.BatchHelper.unravelBatch(packet, value, this);
240
243
  if (typeof batchData == 'string')
241
244
  return this.invalidPacket(batchData);
242
245
  for (const data of batchData) {
243
- this.listenPacket(data, tag);
246
+ this.listenPacket(data, tag, packetQueue, isAsync, asyncData);
244
247
  }
245
248
  }
246
- basicMiddlewares = [];
247
- addBasicMiddleware(middleware) {
248
- this.basicMiddlewares.push(middleware);
249
+ middlewares = [];
250
+ addMiddleware(middleware) {
251
+ this.middlewares.push(middleware);
249
252
  const m = middleware;
250
253
  try {
251
254
  if (typeof m.init === 'function')
@@ -257,12 +260,12 @@ class SonicWSConnection {
257
260
  }
258
261
  async callMiddleware(method, ...values) {
259
262
  let cancelled = false;
260
- for (const middleware of this.basicMiddlewares) {
263
+ for (const middleware of this.middlewares) {
261
264
  const fn = middleware[method];
262
265
  if (!fn)
263
266
  continue;
264
267
  try {
265
- if (!await fn(...values)) {
268
+ if (await fn(...values)) {
266
269
  cancelled = true;
267
270
  }
268
271
  }
@@ -322,16 +325,17 @@ class SonicWSConnection {
322
325
  else
323
326
  this.batcher.batchPacket(code, data);
324
327
  }
328
+ sendQueue = [false, [], undefined];
325
329
  /**
326
330
  * Sends a packet with the tag and values
327
331
  * @param tag The tag to send
328
332
  * @param values The values to send
329
333
  */
330
334
  async send(tag, ...values) {
331
- if (await this.callMiddleware('onSend_pre', tag, values))
335
+ if (await this.callMiddleware('onSend_pre', tag, values, Date.now(), performance.now()))
332
336
  return;
333
- const [code, data, packet] = await (0, PacketUtils_1.processPacket)(this.host.serverPackets, tag, values, this.id);
334
- if (await this.callMiddleware('onSend_post', tag, data))
337
+ const [code, data, packet] = await (0, PacketUtils_1.processPacket)(this.host.serverPackets, tag, values, this.sendQueue, this.id);
338
+ if (await this.callMiddleware('onSend_post', tag, data, data.length))
335
339
  return;
336
340
  this.send_processed(code, data, packet);
337
341
  }
@@ -366,7 +370,7 @@ class SonicWSConnection {
366
370
  if (this.rater.trigger(SERVER_RATELIMIT_TAG))
367
371
  return;
368
372
  if (this.print)
369
- console.log(`\x1b[32m⬆ \x1b[38;5;245m(${this.id},${data.byteLength})\x1b[0m`, (0, BufferUtil_1.stringifyBuffer)(data));
373
+ console.log(`\x1b[32m⬆ \x1b[38;5;245m(${this.id},${data.byteLength})\x1b[0m`, (data.length > 0 && this.host.serverPackets.getTag(data[0])) || "<INVALID>", (0, BufferUtil_1.stringifyBuffer)(data));
370
374
  this.socket.send(data);
371
375
  }
372
376
  close(code = 1000, reason) {
@@ -401,5 +405,19 @@ class SonicWSConnection {
401
405
  tag(tag, replace = true) {
402
406
  this.host.tag(this, tag, replace);
403
407
  }
408
+ /**
409
+ * Sets the name of this connection for the debug menu; good for setting e.g. usernames on games
410
+ */
411
+ async setName(name) {
412
+ if (await this.callMiddleware("onNameChange", name))
413
+ return;
414
+ this.name = name;
415
+ }
416
+ /**
417
+ * @returns Name of the socket, defaults to Socket [ID] unless set with setName()
418
+ */
419
+ getName() {
420
+ return this.name;
421
+ }
404
422
  }
405
423
  exports.SonicWSConnection = SonicWSConnection;
@@ -3,6 +3,13 @@ import { SonicWSConnection } from './SonicWSConnection';
3
3
  import { PacketHolder } from '../util/packets/PacketHolder';
4
4
  import { Packet } from '../packets/Packets';
5
5
  import { PacketType } from '../packets/PacketType';
6
+ import { FuncKeys, MiddlewareHolder, ServerMiddleware } from '../PacketProcessor';
7
+ export type SonicServerSettings = {
8
+ /** If it should check for updates; defaults to true. */
9
+ readonly checkForUpdates?: boolean;
10
+ /** If the rereference should use a 64 bit hash which is less prone to collision (1% after ~600 million) or a 32 bit hash. Defaults to true. */
11
+ readonly bit64Hash?: boolean;
12
+ };
6
13
  /**
7
14
  * Sonic WS Server Options
8
15
  */
@@ -13,11 +20,10 @@ export type SonicServerOptions = {
13
20
  readonly serverPackets?: PacketTypings;
14
21
  /** Default WS Options */
15
22
  readonly websocketOptions?: WS.ServerOptions;
16
- /** If it should check for updates; defaults to true. */
17
- readonly checkForUpdates?: boolean;
23
+ readonly sonicServerSettings?: SonicServerSettings;
18
24
  };
19
25
  export type PacketTypings = readonly Packet<PacketType | readonly PacketType[]>[];
20
- export declare class SonicWSServer {
26
+ export declare class SonicWSServer implements MiddlewareHolder<ServerMiddleware> {
21
27
  private wss;
22
28
  private availableIds;
23
29
  private lastId;
@@ -31,12 +37,16 @@ export declare class SonicWSServer {
31
37
  private handshakePacket;
32
38
  tags: Map<SonicWSConnection, Set<String>>;
33
39
  tagsInv: Map<String, Set<SonicWSConnection>>;
40
+ private serverwideSendQueue;
34
41
  /**
35
42
  * Initializes and hosts a websocket with sonic protocol
36
43
  * Rate limits can be set with wss.setClientRateLimit(x) and wss.setServerRateLimit(x); it is defaulted at 500/second per both
37
44
  * @param settings Sonic Server Options such as schema data for client and server packets, alongside websocket options
38
45
  */
39
46
  constructor(settings: SonicServerOptions);
47
+ private middlewares;
48
+ addMiddleware(middleware: ServerMiddleware): void;
49
+ callMiddleware<K extends FuncKeys<ServerMiddleware> & keyof ServerMiddleware>(method: K, ...values: Parameters<NonNullable<Extract<ServerMiddleware[K], (...args: any[]) => any>>>): Promise<boolean>;
40
50
  private generateSocketID;
41
51
  /**
42
52
  * Requires each client to send this packet upon initialization
@@ -94,26 +104,10 @@ export declare class SonicWSServer {
94
104
  * @param callback Called when server closes
95
105
  */
96
106
  shutdown(callback: (err?: Error) => void): void;
97
- /**
98
- * Broadcasts a packet to tagged users; this is fast as it is a record rather than looping and filtering
99
- * @param tag The tag to send packets to
100
- * @param packetTag Packet tag to send
101
- * @param values Values to send
102
- */
107
+ private broadcastInternal;
103
108
  broadcastTagged(tag: string, packetTag: string, ...values: any): Promise<void>;
104
- /**
105
- * Broadcasts a packet to all users connected, but with a filter
106
- * @param tag The tag to send
107
- * @param filter The filter for who to send to
108
- * @param values The values to send
109
- */
110
109
  broadcastFiltered(tag: string, filter: (socket: SonicWSConnection) => boolean, ...values: any): Promise<void>;
111
- /**
112
- * Broadcasts a packet to all users connected
113
- * @param tag The tag to send
114
- * @param values The values to send
115
- */
116
- broadcast(tag: string, ...values: any): void;
110
+ broadcast(tag: string, ...values: any): Promise<void>;
117
111
  /**
118
112
  * @returns All users connected to the socket
119
113
  */
@@ -135,4 +129,14 @@ export declare class SonicWSServer {
135
129
  * @param replace If it should replace a previous tag; defaults to true. If using false, you can add multiple tags.
136
130
  */
137
131
  tag(socket: SonicWSConnection, tag: string, replace?: boolean): void;
132
+ private debugServer;
133
+ /**
134
+ * Opens a debug menu; this launches the browser and starts a subserver
135
+ * @param port Port of the server/http, defaults to 0 which finds an open port
136
+ * @param password Toggles the requirement of a password to access the server. Defaults to empty, which doesn't ask for a password.
137
+ */
138
+ OpenDebug(data: {
139
+ port?: number;
140
+ password?: string;
141
+ }): void;
138
142
  }