sonic-ws 1.3.0 → 1.3.2

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.
Files changed (49) hide show
  1. package/README.md +50 -44
  2. package/dist/index.d.ts +4 -0
  3. package/dist/version.d.ts +5 -1
  4. package/dist/version.js +4 -23
  5. package/dist/ws/Connection.d.ts +52 -2
  6. package/dist/ws/Connection.js +4 -48
  7. package/dist/ws/PacketProcessor.d.ts +11 -3
  8. package/dist/ws/PacketProcessor.js +4 -17
  9. package/dist/ws/client/core/ClientCore.d.ts +17 -35
  10. package/dist/ws/client/core/ClientCore.js +4 -328
  11. package/dist/ws/client/node/ClientNode.d.ts +6 -2
  12. package/dist/ws/client/node/ClientNode.js +4 -34
  13. package/dist/ws/debug/DebugServer.d.ts +11 -0
  14. package/dist/ws/debug/DebugServer.js +6 -0
  15. package/dist/ws/packets/PacketProcessors.d.ts +6 -2
  16. package/dist/ws/packets/PacketProcessors.js +4 -314
  17. package/dist/ws/packets/PacketType.d.ts +4 -0
  18. package/dist/ws/packets/PacketType.js +4 -59
  19. package/dist/ws/packets/Packets.d.ts +12 -9
  20. package/dist/ws/packets/Packets.js +4 -313
  21. package/dist/ws/server/SonicWSConnection.d.ts +8 -35
  22. package/dist/ws/server/SonicWSConnection.js +4 -421
  23. package/dist/ws/server/SonicWSServer.d.ts +11 -10
  24. package/dist/ws/server/SonicWSServer.js +3 -906
  25. package/dist/ws/util/BufferUtil.d.ts +7 -2
  26. package/dist/ws/util/BufferUtil.js +4 -43
  27. package/dist/ws/util/StringUtil.d.ts +7 -4
  28. package/dist/ws/util/StringUtil.js +4 -64
  29. package/dist/ws/util/enums/EnumHandler.d.ts +4 -0
  30. package/dist/ws/util/enums/EnumHandler.js +4 -58
  31. package/dist/ws/util/enums/EnumType.d.ts +4 -12
  32. package/dist/ws/util/enums/EnumType.js +4 -69
  33. package/dist/ws/util/packets/BatchHelper.d.ts +5 -15
  34. package/dist/ws/util/packets/BatchHelper.js +4 -77
  35. package/dist/ws/util/packets/CompressionUtil.d.ts +17 -26
  36. package/dist/ws/util/packets/CompressionUtil.js +4 -533
  37. package/dist/ws/util/packets/HashUtil.d.ts +4 -0
  38. package/dist/ws/util/packets/HashUtil.js +4 -120
  39. package/dist/ws/util/packets/JSONUtil.d.ts +6 -0
  40. package/dist/ws/util/packets/JSONUtil.js +6 -0
  41. package/dist/ws/util/packets/PacketHolder.d.ts +3 -62
  42. package/dist/ws/util/packets/PacketHolder.js +4 -123
  43. package/dist/ws/util/packets/PacketUtils.d.ts +4 -17
  44. package/dist/ws/util/packets/PacketUtils.js +4 -272
  45. package/dist/ws/util/packets/RateHandler.d.ts +5 -14
  46. package/dist/ws/util/packets/RateHandler.js +4 -63
  47. package/package.json +4 -2
  48. package/dist/ws/util/ArrayUtil.d.ts +0 -1
  49. package/dist/ws/util/ArrayUtil.js +0 -24
@@ -1,909 +1,6 @@
1
- "use strict";
2
- /*
1
+ /**
3
2
  * Copyright 2026 Lily (liwybloc)
4
- *
5
- * Licensed under the Apache License, Version 2.0 (the "License");
6
- * you may not use this file except in compliance with the License.
7
- * You may obtain a copy of the License at
8
- *
9
- * http://www.apache.org/licenses/LICENSE-2.0
10
- *
11
- * Unless required by applicable law or agreed to in writing, software
12
- * distributed under the License is distributed on an "AS IS" BASIS,
13
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
- * See the License for the specific language governing permissions and
15
- * limitations under the License.
3
+ * Licensed under the Apache License, Version 2.0.
16
4
  */
17
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
18
- if (k2 === undefined) k2 = k;
19
- var desc = Object.getOwnPropertyDescriptor(m, k);
20
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
21
- desc = { enumerable: true, get: function() { return m[k]; } };
22
- }
23
- Object.defineProperty(o, k2, desc);
24
- }) : (function(o, m, k, k2) {
25
- if (k2 === undefined) k2 = k;
26
- o[k2] = m[k];
27
- }));
28
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
29
- Object.defineProperty(o, "default", { enumerable: true, value: v });
30
- }) : function(o, v) {
31
- o["default"] = v;
32
- });
33
- var __importStar = (this && this.__importStar) || (function () {
34
- var ownKeys = function(o) {
35
- ownKeys = Object.getOwnPropertyNames || function (o) {
36
- var ar = [];
37
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
38
- return ar;
39
- };
40
- return ownKeys(o);
41
- };
42
- return function (mod) {
43
- if (mod && mod.__esModule) return mod;
44
- var result = {};
45
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
46
- __setModuleDefault(result, mod);
47
- return result;
48
- };
49
- })();
50
- var __importDefault = (this && this.__importDefault) || function (mod) {
51
- return (mod && mod.__esModule) ? mod : { "default": mod };
52
- };
53
- Object.defineProperty(exports, "__esModule", { value: true });
54
- exports.SonicWSServer = void 0;
55
- const WS = __importStar(require("ws"));
56
- const http_1 = __importDefault(require("http"));
57
- const open_1 = __importDefault(require("open"));
58
- const SonicWSConnection_1 = require("./SonicWSConnection");
59
- const PacketHolder_1 = require("../util/packets/PacketHolder");
60
- const CompressionUtil_1 = require("../util/packets/CompressionUtil");
61
- const version_1 = require("../../version");
62
- const PacketUtils_1 = require("../util/packets/PacketUtils");
63
- const PacketType_1 = require("../packets/PacketType");
64
- const HashUtil_1 = require("../util/packets/HashUtil");
65
- const Connection_1 = require("../Connection");
66
- class SonicWSServer {
67
- wss;
68
- availableIds = [];
69
- lastId = 0;
70
- connectListeners = [];
71
- clientPackets;
72
- serverPackets;
73
- connections = [];
74
- connectionMap = {};
75
- clientRateLimit = 500;
76
- serverRateLimit = 500;
77
- handshakePacket = null;
78
- tags = new Map();
79
- tagsInv = new Map();
80
- serverwideSendQueue = [false, [], undefined];
81
- /**
82
- * Initializes and hosts a websocket with sonic protocol
83
- * Rate limits can be set with wss.setClientRateLimit(x) and wss.setServerRateLimit(x); it is defaulted at 500/second per both
84
- * @param settings Sonic Server Options such as schema data for client and server packets, alongside websocket options
85
- */
86
- constructor(settings) {
87
- const { clientPackets = [], serverPackets = [], websocketOptions = {} } = settings;
88
- this.wss = new WS.WebSocketServer(websocketOptions);
89
- this.clientPackets = new PacketHolder_1.PacketHolder(clientPackets);
90
- this.serverPackets = new PacketHolder_1.PacketHolder(serverPackets);
91
- const s_clientPackets = this.clientPackets.serialize();
92
- const s_serverPackets = this.serverPackets.serialize();
93
- const serverData = [...version_1.SERVER_SUFFIX_NUMS, version_1.VERSION];
94
- const keyData = [...(0, CompressionUtil_1.convertVarInt)(s_clientPackets.length), ...s_clientPackets, ...s_serverPackets];
95
- (0, HashUtil_1.setHashFunc)(settings.sonicServerSettings?.bit64Hash ?? true);
96
- this.wss.on('connection', async (socket) => {
97
- const sonicConnection = new SonicWSConnection_1.SonicWSConnection(socket, this, this.generateSocketID(), this.handshakePacket, this.clientRateLimit, this.serverRateLimit);
98
- if (await this.callMiddleware("onClientConnect", sonicConnection)) {
99
- sonicConnection.close(Connection_1.CloseCodes.MIDDLEWARE, "Connection blocked by middleware.");
100
- this.callMiddleware("onClientDisconnect", sonicConnection, Connection_1.CloseCodes.MIDDLEWARE, Buffer.from("Connection blocked by middleware."));
101
- this.availableIds.push(sonicConnection.id);
102
- return;
103
- }
104
- // send tags to the client so it doesn't have to hard code them in
105
- const data = new Uint8Array([...(0, CompressionUtil_1.convertVarInt)(sonicConnection.id), ...keyData]);
106
- socket.send([...serverData, ...await (0, CompressionUtil_1.compressGzip)(data)]);
107
- this.connections.push(sonicConnection);
108
- this.connectionMap[sonicConnection.id] = sonicConnection;
109
- this.connectListeners.forEach(l => l(sonicConnection));
110
- socket.on('close', (code, reason) => {
111
- this.connections.splice(this.connections.indexOf(sonicConnection), 1);
112
- delete this.connectionMap[sonicConnection.id];
113
- this.availableIds.push(sonicConnection.id);
114
- if (this.tags.has(sonicConnection)) {
115
- for (const tag of this.tags.get(sonicConnection))
116
- this.tagsInv.get(tag)?.delete(sonicConnection);
117
- this.tags.delete(sonicConnection);
118
- }
119
- this.callMiddleware("onClientDisconnect", sonicConnection, code, reason);
120
- });
121
- });
122
- if (settings.sonicServerSettings?.checkForUpdates ?? true) {
123
- fetch('https://raw.githubusercontent.com/liwybloc/sonic-ws/refs/heads/main/release/version')
124
- .then((res) => res.text())
125
- .then((ver) => {
126
- if (parseInt(ver) > version_1.VERSION) {
127
- console.warn(`SonicWS is currently running outdated! (current: ${version_1.VERSION}, latest: ${ver}) Update with "npm update sonic-ws"`);
128
- }
129
- })
130
- .catch((err) => {
131
- console.error(err);
132
- console.warn(`Could not check SonicWS version.`);
133
- });
134
- }
135
- }
136
- middlewares = [];
137
- addMiddleware(middleware) {
138
- this.middlewares.push(middleware);
139
- const m = middleware;
140
- try {
141
- if (typeof m.init === 'function')
142
- m.init(this);
143
- }
144
- catch (e) {
145
- console.warn('Middleware init threw an error:', e);
146
- }
147
- }
148
- async callMiddleware(method, ...values) {
149
- let cancelled = false;
150
- for (const middleware of this.middlewares) {
151
- const fn = middleware[method];
152
- if (!fn)
153
- continue;
154
- try {
155
- if (await fn(...values)) {
156
- cancelled = true;
157
- }
158
- }
159
- catch (e) {
160
- console.warn(`Middleware ${String(method)} threw an error:`, e);
161
- }
162
- }
163
- return cancelled;
164
- }
165
- generateSocketID() {
166
- if (this.availableIds.length == 0)
167
- this.availableIds.push(this.lastId + 1);
168
- this.lastId = this.availableIds.shift();
169
- return this.lastId;
170
- }
171
- /**
172
- * Requires each client to send this packet upon initialization
173
- *
174
- * Recreates this:
175
- * ```js
176
- * let initiated = false;
177
- * socket.on('init', () => {
178
- * if(initiated) return socket.close();
179
- * initiated = true;
180
- * // process
181
- * });
182
- *
183
- * socket.on('otherPacket', () => {
184
- * if(!initiated) return socket.close();
185
- * // process
186
- * })
187
- * ```
188
- *
189
- * @param packet The tag of the packet to require as a handshake
190
- */
191
- requireHandshake(packet) {
192
- if (!this.clientPackets.hasTag(packet))
193
- throw new Error(`The client does not send "${packet}" and so it cannot use it as a handshake!`);
194
- if (this.clientPackets.getPacket(packet).dataBatching != 0)
195
- throw new Error(`The packet "${packet}" is a batched packet, and cannot be used as a handshake!`);
196
- this.handshakePacket = packet;
197
- }
198
- /**
199
- * Sets the rate limit for all client-side packets
200
- * @param limit Amount of packets the sockets can send every second, or 0 for infinite
201
- */
202
- setClientRateLimit(limit) {
203
- // so that i can store limits in 1 packet
204
- if (limit > CompressionUtil_1.MAX_BYTE) {
205
- limit = 0;
206
- console.warn(`A rate limit above ${CompressionUtil_1.MAX_BYTE} is considered infinite.`);
207
- }
208
- this.clientRateLimit = limit;
209
- }
210
- /**
211
- * Sets the rate limit for server-side packets per-socket
212
- * @param limit Amount of packets the server can send every second, or 0 for infinite
213
- */
214
- setServerRateLimit(limit) {
215
- // so that i can store limits in 1 packet
216
- if (limit > CompressionUtil_1.MAX_BYTE) {
217
- limit = 0;
218
- console.warn(`A rate limit above ${CompressionUtil_1.MAX_BYTE} is considered infinite.`);
219
- }
220
- this.serverRateLimit = limit;
221
- }
222
- /**
223
- * Enables a packet for all current & new clients.
224
- * @param tag The tag of the packet
225
- */
226
- enablePacket(tag) {
227
- this.clientPackets.getPacket(tag).defaultEnabled = true;
228
- this.connections.forEach(socket => socket.enablePacket(tag));
229
- }
230
- /**
231
- * Disables a packet for all current & new clients.
232
- * @param tag The tag of the packet
233
- */
234
- disablePacket(tag) {
235
- this.clientPackets.getPacket(tag).defaultEnabled = false;
236
- this.connections.forEach(socket => socket.disablePacket(tag));
237
- }
238
- /**
239
- * Listens for whenever a client connects
240
- * @param runner Called when ready
241
- */
242
- on_connect(runner) {
243
- this.connectListeners.push(runner);
244
- }
245
- /**
246
- * Listens for whenever the server is ready
247
- * @param runner Called when ready
248
- */
249
- on_ready(runner) {
250
- this.wss.on('listening', runner);
251
- }
252
- /**
253
- * Closes the server
254
- * @param callback Called when server closes
255
- */
256
- shutdown(callback) {
257
- this.wss.close(callback);
258
- }
259
- async broadcastInternal(packetTag, target, values) {
260
- let recipients;
261
- if (target.type === "all") {
262
- recipients = this.connections;
263
- }
264
- else if (target.type === "tagged") {
265
- if (!this.tagsInv.has(target.tag))
266
- return;
267
- recipients = Array.from(this.tagsInv.get(target.tag));
268
- }
269
- else {
270
- recipients = this.connections.filter(target.filter);
271
- }
272
- if (await this.callMiddleware("onPacketBroadcast_pre", packetTag, { recipients, ...target }, values))
273
- return;
274
- if (recipients.length === 0)
275
- return;
276
- const [code, data, packet] = await (0, PacketUtils_1.processPacket)(this.serverPackets, packetTag, values, this.serverwideSendQueue, -1);
277
- if (await this.callMiddleware("onPacketBroadcast_post", packetTag, { recipients, ...target }, data, data.length))
278
- return;
279
- recipients.forEach(conn => conn.send_processed(code, data, packet));
280
- }
281
- async broadcastTagged(tag, packetTag, ...values) {
282
- await this.broadcastInternal(packetTag, { type: "tagged", tag }, values);
283
- }
284
- async broadcastFiltered(tag, filter, ...values) {
285
- await this.broadcastInternal(tag, { type: "filter", filter }, values);
286
- }
287
- async broadcast(tag, ...values) {
288
- await this.broadcastInternal(tag, { type: "all" }, values);
289
- }
290
- /**
291
- * @returns All users connected to the socket
292
- */
293
- getConnected() {
294
- return this.connections;
295
- }
296
- /**
297
- * @param id The socket id
298
- * @returns The socket
299
- */
300
- getSocket(id) {
301
- return this.connectionMap[id];
302
- }
303
- /**
304
- * Closes a socket by id
305
- * @param id The socket id
306
- */
307
- closeSocket(id, code = 1000, reason) {
308
- this.getSocket(id).close(code, reason);
309
- }
310
- /**
311
- * Tags the socket with a key
312
- * @param socket The socket to tag
313
- * @param tag The tag to add
314
- * @param replace If it should replace a previous tag; defaults to true. If using false, you can add multiple tags.
315
- */
316
- tag(socket, tag, replace = true) {
317
- if (!this.tags.get(socket))
318
- this.tags.set(socket, new Set());
319
- if (!this.tagsInv.get(tag))
320
- this.tagsInv.set(tag, new Set());
321
- if (replace) {
322
- this.tags.get(socket).forEach(v => this.tagsInv.get(v)?.delete(socket));
323
- }
324
- this.tags.get(socket).add(tag);
325
- this.tagsInv.get(tag).add(socket);
326
- }
327
- debugServer = null;
328
- /**
329
- * Opens a debug menu; this launches the browser and starts a subserver
330
- * @param port Port of the server/http, defaults to 0 which finds an open port
331
- * @param password Toggles the requirement of a password to access the server. Defaults to empty, which doesn't ask for a password.
332
- */
333
- OpenDebug(data) {
334
- if (this.debugServer != null)
335
- throw new Error("Attempted to open a debug server when one has already been opened.");
336
- data.port ??= 0;
337
- data.password ??= "";
338
- if (data.port < 0 || data.port >= 65536)
339
- throw new Error("Port out of range!");
340
- // there's no `` inside of it because i wanna use lib-htnml or whatever to make it hihglihted
341
- const server = http_1.default.createServer((req, res) => {
342
- res.writeHead(200, { 'Content-Type': 'text/html' });
343
- const html = String.raw;
344
- res.end(`
345
- <!DOCTYPE html>
346
- <html>
347
- <head>
348
- <meta charset="UTF-8">
349
- <script src="https://cdn.jsdelivr.net/gh/liwybloc/sonic-ws/bundled/SonicWS_bundle.js"></script>
350
- <title>SonicWS Debug Menu</title>
351
- <style>
352
- body {
353
- margin: 0;
354
- font-family: Inter, Arial, sans-serif;
355
- background: #0f1115;
356
- color: #e6e6e6;
357
- height: 100vh;
358
- display: flex;
359
- }
360
5
 
361
- #sidebar {
362
- width: 260px;
363
- background: #141821;
364
- border-right: 1px solid #1f2533;
365
- display: flex;
366
- flex-direction: column;
367
- }
368
-
369
- #sidebar-header {
370
- padding: 16px;
371
- font-weight: 600;
372
- font-size: 18px;
373
- border-bottom: 1px solid #1f2533;
374
- cursor: pointer;
375
- transition: color 0.2s;
376
- }
377
- #sidebar-header:hover {
378
- color: #dddddd;
379
- }
380
-
381
- #socket-list {
382
- flex: 1;
383
- overflow-y: auto;
384
- }
385
-
386
- .socket-item {
387
- padding: 10px 14px;
388
- cursor: pointer;
389
- border-bottom: 1px solid #1f2533;
390
- }
391
-
392
- .socket-item:hover {
393
- background: #1b2030;
394
- }
395
-
396
- .socket-item.active {
397
- background: #22294a;
398
- }
399
-
400
- .socket-id {
401
- font-size: 12px;
402
- opacity: 0.7;
403
- }
404
-
405
- #main {
406
- flex: 1;
407
- display: flex;
408
- flex-direction: column;
409
- }
410
-
411
- #main-header {
412
- padding: 17px;
413
- border-bottom: 1px solid #1f2533;
414
- display: flex;
415
- justify-content: space-between;
416
- align-items: center;
417
- }
418
-
419
- #stats {
420
- display: flex;
421
- gap: 20px;
422
- font-size: 13px;
423
- }
424
-
425
- #packets {
426
- flex: 1;
427
- overflow-y: auto;
428
- padding: 12px;
429
- }
430
-
431
- .packet {
432
- background: #1a1f2e;
433
- border-radius: 6px;
434
- padding: 6px 8px;
435
- margin-bottom: 4px;
436
- font-size: 12px;
437
- cursor: pointer;
438
- }
439
-
440
- .packet.sent { border-left: 3px solid #3cff7a; }
441
- .packet.recv { border-left: 3px solid #ff5a5a; }
442
-
443
- .packet-details {
444
- display: none;
445
- margin-top: 4px;
446
- opacity: 0.75;
447
- font-size: 11px;
448
- }
449
-
450
- .packet.expanded .packet-details {
451
- display: block;
452
- }
453
-
454
- button {
455
- background: none;
456
- }
457
- </style>
458
- </head>
459
- <body>
460
- <div id="sidebar">
461
- <div id="sidebar-header">Sonic WS Debug Menu</div>
462
- <div id="socket-list"></div>
463
- </div>
464
-
465
- <div id="main">
466
- <div id="main-header">
467
- <button id="close-socket" style="display:none;">❌</button>
468
- <div id="socket-title">Server Home</div>
469
- <div id="stats"></div>
470
- </div>
471
- <div id="home" style="display: block; padding: 16px;">
472
- <h2>Server Dashboard</h2>
473
- <div id="global-stats" style="margin-bottom:16px;"></div>
474
- <h3>Connection Logs</h3>
475
- <ul id="connection-logs" style="max-height:200px; overflow-y:auto; padding-left:16px;"></ul>
476
- <div style="margin-top:16px;">
477
- <table style="width:100%; border-collapse:collapse;">
478
- <thead>
479
- <tr>
480
- <th style="border-bottom:1px solid #444; text-align:left;">Socket ID</th>
481
- <th style="border-bottom:1px solid #444; text-align:left;">Name</th>
482
- <th style="border-bottom:1px solid #444; text-align:left;">Status</th>
483
- </tr>
484
- </thead>
485
- <tbody id="connection-table"></tbody>
486
- </table>
487
- </div>
488
- </div>
489
- <div id="packets"></div>
490
- </div>
491
-
492
- <script>
493
- const socketList = document.getElementById('socket-list');
494
- const packetsDiv = document.getElementById('packets');
495
- const socketTitle = document.getElementById('socket-title');
496
- const debugTitle = document.getElementById('sidebar-header');
497
- const statsDiv = document.getElementById('stats');
498
- const home = document.getElementById('home');
499
- const closeSocketBtn = document.getElementById('close-socket');
500
-
501
- debugTitle.onclick = () => {
502
- if(activeId !== null) {
503
- activeId = null;
504
- packetsDiv.style.display = 'none';
505
- home.style.display = 'block';
506
- closeSocketBtn.style.display = 'none';
507
- renderGlobalStats();
508
- }
509
- };
510
-
511
- const globalStats = {
512
- totalSockets: 0,
513
- totalSent: 0,
514
- totalRecv: 0,
515
- totalSentBytes: 0,
516
- totalRecvBytes: 0,
517
- totalSaved: 0,
518
- startTime: 0,
519
- };
520
-
521
- function formatMilliseconds(ms) {
522
- if (ms < 1) return '0.0s';
523
-
524
- const units = [
525
- { label: 'week', value: 7 * 24 * 60 * 60 * 1000 },
526
- { label: 'day', value: 24 * 60 * 60 * 1000 },
527
- { label: 'hour', value: 60 * 60 * 1000 },
528
- { label: 'minute', value: 60 * 1000 },
529
- ];
530
-
531
- const parts = [];
532
-
533
- for (const { label, value } of units) {
534
- const amount = Math.floor(ms / value);
535
- if (amount > 0) {
536
- parts.push(amount + ' ' + label + (amount !== 1 ? 's' : ''));
537
- ms -= amount * value;
538
- }
539
- }
540
-
541
- if (ms > 0 || parts.length === 0) {
542
- const seconds = (ms / 1000).toFixed(1);
543
- parts.push(seconds + 's');
544
- }
545
-
546
- if (parts.length === 1) return parts[0];
547
- const last = parts.pop();
548
- return parts.join(', ') + ' and ' + last;
549
- }
550
-
551
-
552
- const globalStatsDiv = document.getElementById('global-stats');
553
- function renderGlobalStats() {
554
- const stats = globalStats;
555
- const uptime = Date.now() - stats.startTime;
556
- const formattedUptime = formatMilliseconds(uptime);
557
-
558
- globalStatsDiv.innerHTML = '<div><strong>Total Sockets:</strong> ' + stats.totalSockets + '</div><div><strong>Total Sent Packets:</strong> ' + stats.totalSent + '</div><div><strong>Total Received Packets:</strong> ' + stats.totalRecv + '</div><div><strong>Total Sent Bytes:</strong> ' + stats.totalSentBytes + ' B</div><div><strong>Total Received Bytes:</strong> ' + stats.totalRecvBytes + ' B</div><div><strong>Total Bandwidth Saved:</strong> ' + stats.totalSaved + ' B</div><div><strong>Uptime:</strong> ' + formattedUptime + '</div>';
559
- }
560
- setInterval(renderGlobalStats, 50);
561
-
562
- function updateConnectionTable() {
563
- const tbody = document.getElementById('connection-table');
564
- tbody.innerHTML = '';
565
- sockets.forEach(s => {
566
- const row = document.createElement('tr');
567
- row.innerHTML = '<td>' + s.id + '</td><td>' + s.name + '</td><td style="color:' + (s.el.style.color || '#0f0') + '">' + (s.el.style.color === '#f00' ? 'Disconnected' : 'Connected') + '</td>';
568
- tbody.appendChild(row);
569
- });
570
- }
571
- setInterval(updateConnectionTable, 1000);
572
-
573
- const sockets = new Map();
574
- let activeId = null;
575
-
576
- function selectSocket(id) {
577
- activeId = id;
578
- [...socketList.children].forEach(e => e.classList.toggle('active', e.dataset.id == id));
579
-
580
- const s = sockets.get(id);
581
- socketTitle.textContent = s.name;
582
- packetsDiv.innerHTML = '';
583
- s.packets.forEach(p => packetsDiv.appendChild(p.el));
584
- renderStats(s);
585
- home.style.display = 'none';
586
- packetsDiv.style.display = 'block';
587
- closeSocketBtn.style.display = 'block';
588
- }
589
-
590
- closeSocketBtn.onclick = () => {
591
- if(activeId === null) return;
592
- ws.send("close", Number(activeId));
593
- }
594
-
595
- function renderStats(s) {
596
- statsDiv.innerHTML = "<div>Sent: " + s.sent + "</div><div>Recv: " + s.recv + "</div><div>Sent bytes: " + s.sentBytes + "</div><div>Recv bytes: " + s.recvBytes + "</div><div>Saved: " + s.saved + "</div>";
597
- }
598
-
599
- function addSocket(id, name) {
600
- const el = document.createElement('div');
601
- el.className = 'socket-item';
602
- el.dataset.id = id;
603
- el.innerHTML = "<div>" + name + "</div><div class=\\"socket-id\\">#" + id + "</div>";
604
- el.onclick = () => selectSocket(id);
605
- socketList.appendChild(el);
606
-
607
- sockets.set(id, {
608
- id,
609
- name,
610
- el,
611
- packets: [],
612
- sent: 0,
613
- recv: 0,
614
- sentBytes: 0,
615
- recvBytes: 0,
616
- saved: 0
617
- });
618
- }
619
-
620
- function removeSocket(id, code, reason, codeReason) {
621
- const s = sockets.get(id);
622
- if (!s) return console.error("Unknown socket!!", id);
623
-
624
- s.el.dataset.id = id + "-closed";
625
- s.el.onclick = () => selectSocket(id + "-closed");
626
- sockets.set(id + "-closed", s);
627
- sockets.delete(id);
628
-
629
- if(activeId == id) activeId = id + "-closed";
630
-
631
- const nameNode = s.el.childNodes[0];
632
- nameNode.style.color = "#f00";
633
-
634
- let circle = document.createElement('span');
635
- circle.style.display = 'inline-block';
636
- circle.style.width = '10px';
637
- circle.style.height = '10px';
638
- circle.style.borderRadius = '50%';
639
- circle.style.background = '#ff5a5a';
640
- circle.style.marginLeft = '8px';
641
- nameNode.appendChild(circle);
642
-
643
- // add disconnection info to home page logs
644
- const logItem = document.createElement('li');
645
- logItem.textContent = 'Socket #' + id + ' disconnected — Code: ' + code + ', Reason: ' + reason + ', Closure Cause: ' + codeReason;
646
- document.getElementById('connection-logs').appendChild(logItem);
647
-
648
- requestAnimationFrame(() => circle.style.width = '100%');
649
- setTimeout(() => {
650
- circle.remove();
651
- s.el.remove();
652
- if(activeId == id + "-closed") {
653
- packetsDiv.style.display = 'none';
654
- home.style.display = 'block';
655
- closeSocketBtn.style.display = 'none';
656
- }
657
- }, 30000);
658
- }
659
-
660
- function addPacket(id, dir, tag, rawSize, saved, info, date, processTime) {
661
- const s = sockets.get(id);
662
- if (!s) return console.error("Unknown socket!!", id);
663
-
664
- const el = document.createElement('div');
665
- el.className = 'packet ' + (dir === 'sent' ? 'sent' : 'recv');
666
- el.innerHTML = '<div>' + tag + (info !== "undefined" ? ' — ' + info : '') + '</div><div class="packet-details">Raw Bytes: ' + rawSize + 'b (saved: ~' + saved + 'b)<br>Processed At: ' + new Date(date).toISOString() + '<br>Processing Time: ' + processTime.toFixed(2) + 'ms</div>';
667
-
668
- el.onclick = () => el.classList.toggle('expanded');
669
-
670
- s.packets.push({ el });
671
- if (dir === 'sent') {
672
- s.sent++;
673
- s.sentBytes += rawSize;
674
- } else {
675
- s.recv++;
676
- s.recvBytes += rawSize;
677
- }
678
- s.saved += saved;
679
-
680
- if (activeId === id) {
681
- packetsDiv.appendChild(el);
682
- renderStats(s);
683
- }
684
- }
685
-
686
- function setStat(i, v) {
687
- globalStats[Object.keys(globalStats)[i]] = v;
688
- if (activeId === null) renderGlobalStats();
689
- }
690
-
691
- const ws = new SonicWS('ws://' + location.host);
692
-
693
- ws.on("connection", id => addSocket(id, "Socket " + id));
694
- ws.on("disconnection", ([id, code], [reason, codeReason]) => removeSocket(id, code, reason, codeReason));
695
- ws.on("nameChange", ([id], [name]) => {
696
- const s = sockets.get(id);
697
- if (!s) return console.error("Unknown socket!!", id);
698
- s.name = name;
699
- s.el.firstChild.textContent = name;
700
- if (activeId === id) socketTitle.textContent = name;
701
- });
702
- ws.on("packet", ([id, size, saved], [dir], [tag], [values], [time, processTime]) => {
703
- console.log("Received packet", { id, size, saved, dir, tag, values, time, processTime });
704
- addPacket(id, dir, tag, size, saved, values, time, processTime);
705
- });
706
- ws.on("stats", (stats) => {
707
- console.log("Received stats", stats);
708
- stats.forEach((v, i) => setStat(i, v));
709
- });
710
- ws.on("stat", (i, v) => setStat(i, v));
711
-
712
- const lastKnownPassword = localStorage.getItem("password");
713
- const empty = !localStorage.getItem("req");
714
- let usedPass = "";
715
- ws.on_ready(() => {
716
- if(empty) ws.send("password", "");
717
- else ws.send("password", usedPass = (lastKnownPassword ?? prompt("Please enter password")));
718
- });
719
-
720
- ws.on_close(() => {
721
- localStorage.setItem("req", true);
722
- localStorage.removeItem("password");
723
- setTimeout(() => window.location.reload(), 1000);
724
- });
725
-
726
- ws.on("authenticated", (success) => {
727
- console.log("Auth status:", success);
728
- if(!success) {
729
- } else {
730
- localStorage.setItem("req", usedPass.length > 0);
731
- localStorage.setItem("password", usedPass);
732
- }
733
- })
734
- </script>
735
- </body>
736
- </html>
737
- `);
738
- });
739
- const wss = new SonicWSServer({
740
- clientPackets: [
741
- (0, PacketUtils_1.CreatePacket)({ tag: "password", type: PacketType_1.PacketType.STRINGS_UTF16 }),
742
- (0, PacketUtils_1.CreatePacket)({ tag: "close", type: PacketType_1.PacketType.UVARINT }),
743
- ],
744
- serverPackets: [
745
- (0, PacketUtils_1.CreatePacket)({ tag: "authenticated", type: PacketType_1.PacketType.BOOLEANS }),
746
- (0, PacketUtils_1.CreatePacket)({ tag: "connection", type: PacketType_1.PacketType.UVARINT }),
747
- (0, PacketUtils_1.CreateObjPacket)({ tag: "disconnection", types: [PacketType_1.PacketType.UVARINT, PacketType_1.PacketType.STRINGS], noDataRange: true }),
748
- (0, PacketUtils_1.CreateObjPacket)({ tag: "nameChange", types: [PacketType_1.PacketType.UVARINT, PacketType_1.PacketType.STRINGS_UTF16], noDataRange: true }),
749
- (0, PacketUtils_1.CreateObjPacket)({
750
- tag: "packet",
751
- types: [
752
- PacketType_1.PacketType.VARINT,
753
- PacketType_1.PacketType.STRINGS,
754
- PacketType_1.PacketType.STRINGS_UTF16,
755
- PacketType_1.PacketType.STRINGS_UTF16,
756
- PacketType_1.PacketType.FLOATS,
757
- ],
758
- gzipCompression: true,
759
- noDataRange: true
760
- }),
761
- (0, PacketUtils_1.CreatePacket)({ tag: "stats", type: PacketType_1.PacketType.UVARINT, noDataRange: true, dontSpread: true }),
762
- (0, PacketUtils_1.CreatePacket)({ tag: "stat", type: PacketType_1.PacketType.UVARINT, dataMax: 2 }),
763
- ],
764
- websocketOptions: { server },
765
- });
766
- const globalStats = new Proxy({
767
- totalSockets: 0,
768
- totalSent: 0,
769
- totalRecv: 0,
770
- totalSentBytes: 0,
771
- totalRecvBytes: 0,
772
- totalSaved: 0,
773
- startTime: Date.now(),
774
- }, {
775
- set(target, prop, value) {
776
- const key = String(prop);
777
- if (target[key] !== value) {
778
- target[key] = value;
779
- wss.broadcast("stat", Object.keys(globalStats).indexOf(key), value);
780
- }
781
- return true;
782
- }
783
- });
784
- // TODO: i think this is fucked by async
785
- const storedPacketData = {};
786
- wss.on_connect(ws => {
787
- let authenticated = false;
788
- const ogs = ws.send.bind(ws);
789
- let queue = [];
790
- ws.send = async (tag, ...values) => {
791
- if (!authenticated)
792
- queue.push([tag, values]);
793
- else
794
- ogs(tag, ...values);
795
- };
796
- ws.send("stats", ...Object.values(globalStats));
797
- this.connections.forEach(conn => {
798
- ws.send("connection", conn.id);
799
- ws.send("nameChange", conn.id, conn.getName());
800
- storedPacketData[conn.id]?.forEach((data) => {
801
- ws.send("packet", ...data);
802
- });
803
- });
804
- ws.on("password", (pword) => {
805
- if (data.password != pword) {
806
- ws.send("authenticated", false);
807
- setTimeout(() => ws.close(1008), 1000);
808
- }
809
- else {
810
- authenticated = true;
811
- ws.send("authenticated", true);
812
- queue.forEach(([tag, values]) => ogs(tag, ...values));
813
- }
814
- });
815
- ws.on("close", (id) => {
816
- if (!authenticated)
817
- return;
818
- this.connectionMap[id]?.close(Connection_1.CloseCodes.MANUAL_SHUTDOWN);
819
- });
820
- });
821
- const innerConns = [];
822
- const broadcastSends = {};
823
- const textEncoder = new TextEncoder();
824
- const length = (values) => textEncoder.encode(JSON.stringify(values) ?? "[]").length;
825
- this.addMiddleware(new (class {
826
- onClientConnect(connection) {
827
- globalStats.totalSockets++;
828
- storedPacketData[connection.id] = [];
829
- innerConns.push(connection);
830
- wss.broadcast("connection", connection.id);
831
- const packetsSend = {};
832
- const packetsRecv = {};
833
- connection.addMiddleware(new (class {
834
- onNameChange(name) {
835
- wss.broadcast("nameChange", connection.id, name);
836
- }
837
- onSend_pre(tag, values, date, perfTime) {
838
- packetsSend[tag] ??= [];
839
- packetsSend[tag].push([values, perfTime, date]);
840
- }
841
- onSend_post(tag, data, sendSize) {
842
- globalStats.totalSentBytes += sendSize;
843
- globalStats.totalSent++;
844
- const [values, perfTime, date] = packetsSend[tag].shift();
845
- const jsonLength = length(values);
846
- const saved = jsonLength - sendSize;
847
- globalStats.totalSaved += saved;
848
- const record = [
849
- [connection.id, sendSize + 1, saved],
850
- "sent",
851
- tag,
852
- JSON.stringify(values),
853
- [date, performance.now() - perfTime],
854
- ];
855
- storedPacketData[connection.id].push(record);
856
- wss.broadcast("packet", ...record);
857
- }
858
- onReceive_pre(tag, data, recvSize) {
859
- globalStats.totalRecvBytes += recvSize;
860
- globalStats.totalRecv++;
861
- packetsRecv[tag] ??= [];
862
- packetsRecv[tag].push([data, performance.now(), Date.now()]);
863
- }
864
- onReceive_post(tag, values) {
865
- const [data, time, date] = packetsRecv[tag].shift();
866
- const jsonLength = length(values);
867
- const saved = jsonLength - data.length;
868
- globalStats.totalSaved += saved;
869
- const record = [
870
- [connection.id, data.length + 1, saved],
871
- "recv",
872
- tag,
873
- JSON.stringify(values),
874
- [date, performance.now() - time],
875
- ];
876
- storedPacketData[connection.id].push(record);
877
- wss.broadcast("packet", ...record);
878
- }
879
- })());
880
- }
881
- onClientDisconnect(connection, code, reason) {
882
- globalStats.totalSockets--;
883
- wss.broadcast("disconnection", [connection.id, code], [reason?.toString() ?? "UNKNOWN", (0, Connection_1.getClosureCause)(code)]);
884
- delete storedPacketData[connection.id];
885
- innerConns.splice(innerConns.indexOf(connection), 1);
886
- }
887
- onPacketBroadcast_pre(tag, info, ...values) {
888
- broadcastSends[tag] ??= [];
889
- broadcastSends[tag].push([values, performance.now(), Date.now()]);
890
- }
891
- onPacketBroadcast_post(tag, info, data, sendSize) {
892
- const [values, time, date] = broadcastSends[tag].shift();
893
- info.recipients.forEach(k => {
894
- if (innerConns.includes(k)) {
895
- k.callMiddleware("onSend_pre", tag, values, date, time);
896
- k.callMiddleware("onSend_post", tag, data, sendSize);
897
- }
898
- });
899
- }
900
- })());
901
- this.debugServer = server;
902
- server.listen(data.port, () => {
903
- const address = server.address();
904
- console.log(`SWS Debug server running at http://localhost:${address.port}`);
905
- (0, open_1.default)(`http://localhost:${address.port}`);
906
- });
907
- }
908
- }
909
- exports.SonicWSServer = SonicWSServer;
6
+ var e,t=this&&this.__createBinding||(Object.create?function(e,t,s,n){void 0===n&&(n=s);var i=Object.getOwnPropertyDescriptor(t,s);i&&!("get"in i?!t.__esModule:i.writable||i.configurable)||(i={enumerable:!0,get:function(){return t[s]}}),Object.defineProperty(e,n,i)}:function(e,t,s,n){void 0===n&&(n=s),e[n]=t[s]}),s=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__importStar||(e=function(t){return e=Object.getOwnPropertyNames||function(e){var t=[];for(var s in e)Object.prototype.hasOwnProperty.call(e,s)&&(t[t.length]=s);return t},e(t)},function(n){if(n&&n.__esModule)return n;var i={};if(null!=n)for(var a=e(n),r=0;r<a.length;r++)"default"!==a[r]&&t(i,n,a[r]);return s(i,n),i});Object.defineProperty(exports,"__esModule",{value:!0}),exports.SonicWSServer=void 0;const i=n(require("ws")),a=require("./SonicWSConnection"),r=require("../util/packets/PacketHolder"),c=require("../util/packets/CompressionUtil"),o=require("../../version"),l=require("../util/packets/PacketUtils"),h=require("../PacketProcessor"),d=require("../util/packets/HashUtil"),u=require("../Connection"),g=require("../debug/DebugServer");class p extends h.MiddlewareHolder{wss;availableIds=[];lastId=0;connectListeners=[];clientPackets;serverPackets;connections=[];connectionMap={};clientRateLimit=500;serverRateLimit=500;handshakePacket=null;tags=new Map;tagsInv=new Map;serverwideSendQueue=[!1,[],void 0];constructor(e){super();const{clientPackets:t=[],serverPackets:s=[],websocketOptions:n={}}=e;this.wss=new i.WebSocketServer(n),this.clientPackets=new r.PacketHolder(t),this.serverPackets=new r.PacketHolder(s);const l=this.clientPackets.serialize(),h=this.serverPackets.serialize(),g=[...o.SERVER_SUFFIX_NUMS,o.VERSION],p=[...(0,c.convertVarInt)(l.length),...l,...h];(0,d.setHashFunc)(e.sonicServerSettings?.bit64Hash??!0),this.wss.on("connection",async e=>{const t=new a.SonicWSConnection(e,this,this.generateSocketID(),this.handshakePacket,this.clientRateLimit,this.serverRateLimit);if(await this.callMiddleware("onClientConnect",t))return t.close(u.CloseCodes.MIDDLEWARE,"Connection blocked by middleware."),this.callMiddleware("onClientDisconnect",t,u.CloseCodes.MIDDLEWARE,Buffer.from("Connection blocked by middleware.")),void this.availableIds.push(t.id);const s=new Uint8Array([...(0,c.convertVarInt)(t.id),...p]);e.send([...g,...await(0,c.compressGzip)(s)]),this.connections.push(t),this.connectionMap[t.id]=t,this.connectListeners.forEach(e=>e(t)),e.on("close",(e,s)=>{if(this.connections.splice(this.connections.indexOf(t),1),delete this.connectionMap[t.id],this.availableIds.push(t.id),this.tags.has(t)){for(const e of this.tags.get(t))this.tagsInv.get(e)?.delete(t);this.tags.delete(t)}this.callMiddleware("onClientDisconnect",t,e,s)})}),(e.sonicServerSettings?.checkForUpdates??1)&&fetch("https://raw.githubusercontent.com/liwybloc/sonic-ws/refs/heads/main/release/version").then(e=>e.text()).then(e=>{parseInt(e)>o.VERSION&&console.warn(`SonicWS is currently running outdated! (current: ${o.VERSION}, latest: ${e}) Update with "npm update sonic-ws"`)}).catch(e=>{console.error(e),console.warn("Could not check SonicWS version.")})}generateSocketID(){return 0==this.availableIds.length&&this.availableIds.push(this.lastId+1),this.lastId=this.availableIds.shift(),this.lastId}requireHandshake(e){if(!this.clientPackets.hasTag(e))throw new Error(`The client does not send "${e}" and so it cannot use it as a handshake!`);if(0!=this.clientPackets.getPacket(e).dataBatching)throw new Error(`The packet "${e}" is a batched packet, and cannot be used as a handshake!`);this.handshakePacket=e}setClientRateLimit(e){e>c.MAX_BYTE&&(e=0,console.warn(`A rate limit above ${c.MAX_BYTE} is considered infinite.`)),this.clientRateLimit=e}setServerRateLimit(e){e>c.MAX_BYTE&&(e=0,console.warn(`A rate limit above ${c.MAX_BYTE} is considered infinite.`)),this.serverRateLimit=e}enablePacket(e){this.clientPackets.getPacket(e).defaultEnabled=!0,this.connections.forEach(t=>t.enablePacket(e))}disablePacket(e){this.clientPackets.getPacket(e).defaultEnabled=!1,this.connections.forEach(t=>t.disablePacket(e))}on_connect(e){this.connectListeners.push(e)}on_ready(e){this.wss.on("listening",e)}shutdown(e){this.wss.close(e)}async broadcastInternal(e,t,s){let n;if("all"===t.type)n=this.connections;else if("tagged"===t.type){if(!this.tagsInv.has(t.tag))return;n=Array.from(this.tagsInv.get(t.tag))}else n=this.connections.filter(t.filter);if(await this.callMiddleware("onPacketBroadcast_pre",e,{recipients:n,...t},s))return;if(0===n.length)return;const[i,a,r]=await(0,l.processPacket)(this.serverPackets,e,s,this.serverwideSendQueue,-1);await this.callMiddleware("onPacketBroadcast_post",e,{recipients:n,...t},a,a.length)||n.forEach(e=>e.send_processed(i,a,r))}async broadcastTagged(e,t,...s){await this.broadcastInternal(t,{type:"tagged",tag:e},s)}async broadcastFiltered(e,t,...s){await this.broadcastInternal(e,{type:"filter",filter:t},s)}async broadcast(e,...t){await this.broadcastInternal(e,{type:"all"},t)}getConnected(){return this.connections}getSocket(e){return this.connectionMap[e]}closeSocket(e,t=1e3,s){this.getSocket(e).close(t,s)}tag(e,t,s=!0){this.tags.get(e)||this.tags.set(e,new Set),this.tagsInv.get(t)||this.tagsInv.set(t,new Set),s&&this.tags.get(e).forEach(t=>this.tagsInv.get(t)?.delete(e)),this.tags.get(e).add(t),this.tagsInv.get(t).add(e)}debugServer=null;OpenDebug(e){if(null!=this.debugServer)throw new Error("Attempted to open a debug server when one has already been opened.");this.debugServer=new g.DebugServer(this,e)}}exports.SonicWSServer=p;