sonic-ws 2.1.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -6
- package/bin/sonicws.mjs +159 -0
- package/bundled/bundle.js +1 -1
- package/bundled/bundle.wasm +0 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +12 -1
- package/dist/native/wasm/node/sonic_ws_core_bg.wasm +0 -0
- package/dist/native/wrapper.d.ts +3 -3
- package/dist/native/wrapper.js +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/ws/Connection.d.ts +16 -3
- package/dist/ws/Connection.js +1 -1
- package/dist/ws/PacketProcessor.js +1 -1
- package/dist/ws/client/core/ClientCore.d.ts +70 -9
- package/dist/ws/client/core/ClientCore.js +1 -1
- package/dist/ws/client/node/ClientNode.d.ts +9 -3
- package/dist/ws/client/node/ClientNode.js +1 -1
- package/dist/ws/debug/PacketLogger.d.ts +30 -0
- package/dist/ws/debug/PacketLogger.js +7 -0
- package/dist/ws/packets/PacketType.d.ts +4 -3
- package/dist/ws/packets/PacketType.js +1 -1
- package/dist/ws/packets/Packets.d.ts +21 -2
- package/dist/ws/packets/Packets.js +1 -1
- package/dist/ws/server/Adapter.d.ts +21 -0
- package/dist/ws/server/Adapter.js +7 -0
- package/dist/ws/server/SonicWSConnection.d.ts +28 -2
- package/dist/ws/server/SonicWSConnection.js +1 -1
- package/dist/ws/server/SonicWSServer.d.ts +46 -8
- package/dist/ws/server/SonicWSServer.js +1 -1
- package/dist/ws/util/packets/BatchHelper.js +1 -1
- package/dist/ws/util/packets/ConstructorRegistry.d.ts +10 -0
- package/dist/ws/util/packets/ConstructorRegistry.js +7 -0
- package/dist/ws/util/packets/ControlProtocol.d.ts +47 -0
- package/dist/ws/util/packets/ControlProtocol.js +7 -0
- package/dist/ws/util/packets/PacketHolder.js +1 -1
- package/dist/ws/util/packets/PacketManifest.d.ts +17 -0
- package/dist/ws/util/packets/PacketManifest.js +7 -0
- package/dist/ws/util/packets/PacketUtils.d.ts +45 -15
- package/dist/ws/util/packets/PacketUtils.js +1 -1
- package/dist/ws/util/packets/SchemaValidation.d.ts +17 -0
- package/dist/ws/util/packets/SchemaValidation.js +7 -0
- package/package.json +13 -7
- package/dist/native/wasm/pkg/README.md +0 -6
- package/dist/native/wasm/pkg/package.json +0 -19
- package/dist/native/wasm/pkg/sonic_ws_core.d.ts +0 -51
- package/dist/native/wasm/pkg/sonic_ws_core.js +0 -7
- package/dist/native/wasm/pkg/sonic_ws_core_bg.js +0 -7
- package/dist/native/wasm/pkg/sonic_ws_core_bg.wasm +0 -0
- package/dist/native/wasm/pkg/sonic_ws_core_bg.wasm.d.ts +0 -36
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* See LICENSE for personal, non-commercial license terms.
|
|
5
5
|
*/
|
|
6
6
|
import * as WS from 'ws';
|
|
7
|
+
import type { IncomingMessage } from 'node:http';
|
|
7
8
|
import { SonicWSServer } from "./SonicWSServer";
|
|
8
9
|
import { Packet } from "../packets/Packets";
|
|
9
10
|
import { Connection } from "../Connection";
|
|
@@ -20,14 +21,24 @@ export declare class SonicWSConnection extends Connection<WS.WebSocket, Buffer>
|
|
|
20
21
|
handshakeComplete: boolean;
|
|
21
22
|
private asyncMap;
|
|
22
23
|
private asyncData;
|
|
23
|
-
|
|
24
|
+
private nextRequestId;
|
|
25
|
+
private pendingRequests;
|
|
26
|
+
private responders;
|
|
27
|
+
sessionId: string;
|
|
28
|
+
/** Provides the HTTP upgrade request for authentication headers and cookies. */
|
|
29
|
+
readonly upgradeRequest: IncomingMessage;
|
|
30
|
+
constructor(socket: WS.WebSocket, host: SonicWSServer, id: number, handshakePacket: string | null, clientRateLimit: number, serverRateLimit: number, sessionId: string, state: Record<string, unknown>, handshakeTimeoutMs: number, upgradeRequest: IncomingMessage);
|
|
24
31
|
private parseData;
|
|
32
|
+
private isControl;
|
|
33
|
+
private routeMessage;
|
|
34
|
+
private handleControl;
|
|
25
35
|
private handshakeHandler;
|
|
26
36
|
private invalidPacket;
|
|
27
37
|
private isAsync;
|
|
28
38
|
private listenLock;
|
|
29
39
|
private packetQueue;
|
|
30
|
-
private
|
|
40
|
+
private deliverPacket;
|
|
41
|
+
private releasePacketLock;
|
|
31
42
|
private messageHandler;
|
|
32
43
|
/**
|
|
33
44
|
* Enables a packet for the client.
|
|
@@ -62,7 +73,15 @@ export declare class SonicWSConnection extends Connection<WS.WebSocket, Buffer>
|
|
|
62
73
|
*/
|
|
63
74
|
send(tag: string, ...values: any[]): Promise<void>;
|
|
64
75
|
sendVariant(parent: string, variant: string, ...values: any[]): Promise<void>;
|
|
76
|
+
/** Sends a validated server packet as an RPC request to this client. */
|
|
77
|
+
request(tag: string, ...valuesAndOptions: any[]): Promise<any>;
|
|
78
|
+
/** Registers the server-side responder for client requests using this packet tag. */
|
|
79
|
+
respond(tag: string, handler: (...values: any[]) => any): void;
|
|
65
80
|
sendSafe(tag: string, ...values: any[]): Promise<boolean>;
|
|
81
|
+
/** Drops this update before encoding when the transport is backpressured. */
|
|
82
|
+
sendVolatile(tag: string, ...values: any[]): Promise<boolean>;
|
|
83
|
+
/** Sends a packet without applying the volatile backpressure drop policy. */
|
|
84
|
+
sendReliable(tag: string, ...values: any[]): Promise<void>;
|
|
66
85
|
/**
|
|
67
86
|
* Broadcasts a packet to all other users connected
|
|
68
87
|
* @param tag The tag to send
|
|
@@ -75,6 +94,13 @@ export declare class SonicWSConnection extends Connection<WS.WebSocket, Buffer>
|
|
|
75
94
|
* @param values The values to send
|
|
76
95
|
*/
|
|
77
96
|
broadcast(tag: string, ...values: any[]): void;
|
|
97
|
+
/** Adds this connection to a server-side room. */
|
|
98
|
+
join(room: string): void;
|
|
99
|
+
/** Removes this connection from a server-side room. */
|
|
100
|
+
leave(room: string): void;
|
|
101
|
+
/** Returns the server-side rooms currently assigned to this connection. */
|
|
102
|
+
getRooms(): ReadonlySet<string>;
|
|
103
|
+
broadcastRoom(room: string, tag: string, ...values: any[]): Promise<void>;
|
|
78
104
|
/**
|
|
79
105
|
* Toggles printing all sent and received messages
|
|
80
106
|
*/
|
|
@@ -4,4 +4,4 @@
|
|
|
4
4
|
* See LICENSE for personal, non-commercial license terms.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
Object.defineProperty(exports,"__esModule",{value:!0}),exports.SonicWSConnection=void 0;const e=require("../util/packets/PacketUtils"),t=require("../util/packets/BatchHelper"),s=require("../util/packets/RateHandler"),
|
|
7
|
+
Object.defineProperty(exports,"__esModule",{value:!0}),exports.SonicWSConnection=void 0;const e=require("../util/packets/PacketUtils"),t=require("../util/packets/BatchHelper"),s=require("../util/packets/RateHandler"),i=require("../util/BufferUtil"),a=require("../Connection"),r=require("../util/packets/ControlProtocol");class n extends a.Connection{host;print=!1;handshakePacket;handshakeLambda;messageLambda=e=>this.routeMessage(e);handshakedMessageLambda=e=>{if(this.isControl(e))return void this.handleControl(new Uint8Array(e.data));const t=this.parseData(e);return null==t?this.socket.close(a.CloseCodes.INVALID_DATA):t[0]===this.handshakePacket?this.socket.close(a.CloseCodes.REPEATED_HANDSHAKE):void this.messageHandler(t)};rater;enabledPackets={};handshakeComplete=!1;asyncMap={};asyncData={};nextRequestId=1;pendingRequests=new Map;responders=new Map;sessionId;upgradeRequest;constructor(e,t,i,r,n,o,h,c,l,d){super(e,i,`Socket ${i}`,e.addEventListener.bind(e),e.removeEventListener.bind(e)),this.host=t,this.sessionId=h,this.upgradeRequest=d,this.state=c,this.handshakePacket=r;for(const e of t.clientPackets.getPackets()){const t=e.tag;this.listeners[t]=[],e.lastReceived[this.id]=void 0,this.enabledPackets[t]=e.defaultEnabled,this.asyncMap[t]=e.async,e.async&&(this.asyncData[t]=[!1,[]])}this.setInterval=this.setInterval.bind(this),this.batcher.registerSendPackets(this.host.serverPackets,this),this.invalidPacket=this.invalidPacket.bind(this),this.rater=new s.RateHandler(this),this.rater.registerRate("C",n),this.rater.registerRate("S",o),this.rater.registerAll(t.clientPackets,"client"),this.rater.registerAll(t.serverPackets,"server"),this.rater.start(),null==this.handshakePacket?this.socket.addEventListener("message",this.messageLambda):(this.handshakeLambda=e=>this.handshakeHandler(e),this.socket.addEventListener("message",this.handshakeLambda),this.setTimeout(()=>{this.handshakeComplete||this.close(a.CloseCodes.INVALID_DATA,"Application handshake timed out")},l)),this.socket.on("close",()=>{for(const e of t.clientPackets.getPackets())delete e.lastReceived[this.id];for(const e of t.serverPackets.getPackets())delete e.lastSent[this.id],e.clearQuantizationState(this.id)})}parseData(e){if(this.rater.trigger("C"))return null;if(!(e.data instanceof Buffer))return null;const t=new Uint8Array(e.data);if(this.print&&console.log(`[31m⬇ [38;5;245m(${this.id},${t.byteLength})[0m`,t.length>0&&this.host.clientPackets.getTag(t[0])||"<INVALID>",(0,i.stringifyBuffer)(t)),t.byteLength<1)return this.socket.close(a.CloseCodes.SMALL),null;const s=t[0],r=t.slice(1);if(!this.host.clientPackets.hasKey(s))return this.socket.close(a.CloseCodes.INVALID_KEY),null;const n=this.host.clientPackets.getTag(s);return this.enabledPackets[n]?this.rater.trigger(`client${s}`)?null:[n,r]:(this.socket.close(a.CloseCodes.DISABLED_PACKET),null)}isControl(e){return e.data instanceof Buffer&&e.data.length>0&&0===e.data[0]}routeMessage(e){this.isControl(e)?this.handleControl(new Uint8Array(e.data)):this.messageHandler(this.parseData(e))}async handleControl(e){if(this.rater.trigger("C"))return;let t;try{t=(0,r.decodeControl)(e)}catch{return void this.close(a.CloseCodes.INVALID_DATA,"Malformed SonicWS control frame")}if(t.type!==r.ControlType.RESUME)if(this.handshakeComplete||null===this.handshakePacket){if(t.type===r.ControlType.REPLAY||t.type===r.ControlType.RESUMED)throw new Error("A server cannot receive replay delivery frames");if(t.type===r.ControlType.RESPONSE){const e=this.pendingRequests.get(t.id);if(!e)return;return clearTimeout(e.timer),this.pendingRequests.delete(t.id),void(t.ok?e.resolve(t.value):e.reject(new Error(String(t.value))))}try{const e=this.host.clientPackets.getTag(t.packetKey);if(!e)throw new Error(`Unknown RPC packet key ${t.packetKey}`);if(!this.enabledPackets[e])return this.close(a.CloseCodes.DISABLED_PACKET,`Packet "${e}" is disabled`);if(this.rater.trigger(`client${t.packetKey}`))return this.close(a.CloseCodes.RATELIMIT,`Packet "${e}" exceeded its rate limit`);if(await this.callMiddleware("onReceive_pre",e,t.payload,t.payload.length))throw new Error(`Packet "${e}" was rejected by middleware`);const s=this.responders.get(e);if(!s)throw new Error(`No responder registered for packet "${e}"`);const i=await this.host.clientPackets.getPacket(e).listen(t.payload,this);if("string"==typeof i)throw new Error(i);const[n,o]=i,h=o?await s(...n):await s(n);this.raw_send((0,r.encodeControlResponse)(t.id,!0,h??null))}catch(e){this.raw_send((0,r.encodeControlResponse)(t.id,!1,e instanceof Error?e.message:String(e)))}}else this.close(a.CloseCodes.INVALID_DATA,"Handshake required before control requests");else await this.host.resumeSession(this,t.sessionId,t.lastSequence)}handshakeHandler(e){if(this.isControl(e))return void this.handleControl(new Uint8Array(e.data));const t=this.parseData(e);if(null==t)return this.socket.close(a.CloseCodes.INVALID_DATA);t[0]===this.handshakePacket?(this.messageHandler(t),this.socket.removeEventListener("message",this.handshakeLambda),this.socket.addEventListener("message",this.handshakedMessageLambda),this.handshakeComplete=!0):this.socket.close(a.CloseCodes.INVALID_DATA)}invalidPacket(e){console.warn("Closing connection after an invalid packet",e),this.socket.close(a.CloseCodes.INVALID_PACKET,e)}isAsync(e){return this.asyncMap[e]}listenLock=!1;packetQueue=[];async deliverPacket(t,s,i,a,r){if(this.closed)return;await(0,e.listenPacket)(t,this.listeners[s],this.invalidPacket);const n=this.host.clientPackets.getPacket(s);if("string"!=typeof t&&n.parent&&n.variant)for(const e of this.listeners[n.parent]??[])await e({variant:n.variant,payload:t[0]});await this.callMiddleware("onReceive_post",s,"string"==typeof t?[t]:t[0]),this.releasePacketLock(i,a,r)}releasePacketLock(e,t,s){t?s[0]=!1:this.listenLock=!1,e.length>0&&this.messageHandler(e.shift())}async messageHandler(e,s=!1){if(null==e)return;const[i,a]=e,r=this.isAsync(i);let n,o,h;if(r?(h=this.asyncData[i],n=h[0],o=h[1]):(n=this.listenLock,o=this.packetQueue),n)return void o.push(e);r?h[0]=!0:this.listenLock=!0;const c=this.host.clientPackets.getPacket(i);if(!s&&await this.callMiddleware("onReceive_pre",c.tag,a,a.length))return void this.releasePacketLock(o,r,h);if(c.rereference&&0===a.length){const e=c.lastReceived[this.id];return void 0===e?void this.invalidPacket("No previous value to rereference"):void await this.deliverPacket(e,i,o,r,h)}if(0===c.dataBatching){const e=await c.listen(a,this);return c.lastReceived[this.id]=e,void await this.deliverPacket(e,i,o,r,h)}const l=await t.BatchHelper.unravelBatch(c,a,this);if("string"!=typeof l)for(const e of l)r?h[0]=!0:this.listenLock=!0,await this.deliverPacket(e,i,o,r,h);else this.invalidPacket(l)}enablePacket(e){this.enabledPackets[e]=!0}disablePacket(e){this.enabledPackets[e]=!1}on_close(e){this.socket.on("close",(t,s)=>e(t,String(s)))}on(e,t){if(!this.host.clientPackets.hasTag(e))throw new Error(`Packet tag "${e}" has not been created`);const s=this.host.clientPackets.resolveTag(e);this.listeners[s]??=[],this.listeners[s].push(t)}send_processed(e,t,s){if(!this.rater.trigger(`server${e}`))if(0===s.dataBatching){const a=(0,i.toPacketBuffer)(e,t);this.raw_send(s.replay?this.host.replayFrame(this,a):a)}else this.batcher.batchPacket(e,t)}sendQueue=[!1,[],void 0];async send(t,...s){if(await this.callMiddleware("onSend_pre",t,s,Date.now(),performance.now()))return;const[i,a,r]=await(0,e.processPacket)(this.host.serverPackets,t,s,this.sendQueue,this.id);await this.callMiddleware("onSend_post",t,a,a.length)||this.send_processed(i,a,r)}sendVariant(e,t,...s){return this.send(this.host.serverPackets.getVariantTag(e,t),...s)}async request(t,...s){const i=s.at(-1),a=s.length>1&&i&&"object"==typeof i&&!Array.isArray(i)&&Object.keys(i).every(e=>"timeoutMs"===e)?s.pop():{},[n,o]=await(0,e.processPacket)(this.host.serverPackets,t,s,this.sendQueue,this.id);if(this.rater.trigger(`server${n}`))throw new Error(`Packet "${t}" exceeded its rate limit`);const h=this.nextRequestId++;return this.nextRequestId>2147483647&&(this.nextRequestId=1),new Promise((e,s)=>{const i=setTimeout(()=>{this.pendingRequests.delete(h),s(new Error(`RPC request "${t}" timed out`))},a.timeoutMs??5e3);this.pendingRequests.set(h,{resolve:e,reject:s,timer:i}),this.raw_send((0,r.encodeControlRequest)(h,n,o))})}respond(e,t){this.responders.set(this.host.clientPackets.resolveTag(e),t)}async sendSafe(e,...t){try{return await this.send(e,...t),!0}catch(t){return this.host.handleSendError(t,{packetTag:e,connection:this}),!1}}async sendVolatile(e,...t){return!!this.canSendVolatile()&&(await this.send(e,...t),!0)}sendReliable(e,...t){return this.send(e,...t)}broadcastFiltered(e,t,...s){this.host.broadcastFiltered(e,e=>e!==this&&t(e),...s)}broadcast(e,...t){this.broadcastFiltered(e,()=>!0,...t)}join(e){this.host.join(this,e)}leave(e){this.host.leave(this,e)}getRooms(){return this.host.rooms(this)}broadcastRoom(e,t,...s){return this.host.broadcastRoomExcept(this,e,t,...s)}togglePrint(){this.print=!this.print}raw_send(e){this.isClosed()?console.warn("Cannot send through a closed connection",this.id,(0,i.stringifyBuffer)(e)):this.rater.trigger("S")||(this.print&&console.log(`[32m⬆ [38;5;245m(${this.id},${e.byteLength})[0m`,e.length>0&&this.host.serverPackets.getTag(e[0])||"<INVALID>",(0,i.stringifyBuffer)(e)),super.raw_send(e))}tag(e,t=!0){this.host.tag(this,e,t)}}exports.SonicWSConnection=n;
|
|
@@ -9,23 +9,30 @@ import { PacketHolder } from "../util/packets/PacketHolder";
|
|
|
9
9
|
import { Packet } from "../packets/Packets";
|
|
10
10
|
import { PacketType } from "../packets/PacketType";
|
|
11
11
|
import { MiddlewareHolder, ServerMiddleware } from "../PacketProcessor";
|
|
12
|
+
import type { SonicWSAdapter } from "./Adapter";
|
|
12
13
|
export type SonicServerSettings = {
|
|
13
|
-
/**
|
|
14
|
+
/** Checks for protocol updates when enabled. Defaults to true. */
|
|
14
15
|
readonly checkForUpdates?: boolean;
|
|
15
16
|
/** 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. */
|
|
16
17
|
readonly bit64Hash?: boolean;
|
|
17
|
-
/**
|
|
18
|
+
/** Serves browser assets from `/SonicWS` when enabled. Defaults to true. */
|
|
18
19
|
readonly serveBrowserClient?: boolean;
|
|
20
|
+
/** Limits a required application handshake. Defaults to 10 seconds. */
|
|
21
|
+
readonly handshakeTimeoutMs?: number;
|
|
22
|
+
/** Controls the WebSocket ping interval. Zero disables it. */
|
|
23
|
+
readonly heartbeatIntervalMs?: number;
|
|
24
|
+
/** Limits how long the server waits for pong before terminating. */
|
|
25
|
+
readonly heartbeatTimeoutMs?: number;
|
|
19
26
|
};
|
|
20
27
|
/**
|
|
21
|
-
*
|
|
28
|
+
* Configures a SonicWS server.
|
|
22
29
|
*/
|
|
23
30
|
export type SonicServerOptions = {
|
|
24
|
-
/**
|
|
31
|
+
/** Declares packets accepted from clients. */
|
|
25
32
|
readonly clientPackets?: PacketTypings;
|
|
26
|
-
/**
|
|
33
|
+
/** Declares packets sent to clients. */
|
|
27
34
|
readonly serverPackets?: PacketTypings;
|
|
28
|
-
/**
|
|
35
|
+
/** Forwards options to the underlying `ws` server. */
|
|
29
36
|
readonly websocketOptions?: WS.ServerOptions;
|
|
30
37
|
readonly sonicServerSettings?: SonicServerSettings;
|
|
31
38
|
readonly onSendError?: (error: unknown, context: {
|
|
@@ -33,6 +40,14 @@ export type SonicServerOptions = {
|
|
|
33
40
|
connection?: SonicWSConnection;
|
|
34
41
|
operation?: "broadcast";
|
|
35
42
|
}) => void;
|
|
43
|
+
/** Optional cross-process adapter used for room membership and room broadcasts. */
|
|
44
|
+
readonly adapter?: SonicWSAdapter;
|
|
45
|
+
/** Bounded replay storage used by reconnecting clients. */
|
|
46
|
+
readonly recovery?: {
|
|
47
|
+
maxDisconnectionMs?: number;
|
|
48
|
+
maxPackets?: number;
|
|
49
|
+
authorize?: (previousState: Record<string, unknown>, currentState: Record<string, unknown>, connection: SonicWSConnection) => boolean | Promise<boolean>;
|
|
50
|
+
};
|
|
36
51
|
};
|
|
37
52
|
export type PacketTypings = readonly Packet<PacketType | readonly PacketType[]>[];
|
|
38
53
|
export declare class SonicWSServer extends MiddlewareHolder<ServerMiddleware> {
|
|
@@ -40,6 +55,7 @@ export declare class SonicWSServer extends MiddlewareHolder<ServerMiddleware> {
|
|
|
40
55
|
private availableIds;
|
|
41
56
|
private lastId;
|
|
42
57
|
private connectListeners;
|
|
58
|
+
private recoveredListeners;
|
|
43
59
|
clientPackets: PacketHolder;
|
|
44
60
|
serverPackets: PacketHolder;
|
|
45
61
|
connections: SonicWSConnection[];
|
|
@@ -47,10 +63,16 @@ export declare class SonicWSServer extends MiddlewareHolder<ServerMiddleware> {
|
|
|
47
63
|
private clientRateLimit;
|
|
48
64
|
private serverRateLimit;
|
|
49
65
|
private handshakePacket;
|
|
50
|
-
tags: Map<SonicWSConnection, Set<
|
|
51
|
-
tagsInv: Map<
|
|
66
|
+
tags: Map<SonicWSConnection, Set<string>>;
|
|
67
|
+
tagsInv: Map<string, Set<SonicWSConnection>>;
|
|
52
68
|
private serverwideSendQueue;
|
|
53
69
|
private readonly sendErrorHandler?;
|
|
70
|
+
private readonly adapter?;
|
|
71
|
+
private readonly serverId;
|
|
72
|
+
private readonly sessions;
|
|
73
|
+
private readonly recoveryMaxDisconnectionMs;
|
|
74
|
+
private readonly recoveryMaxPackets;
|
|
75
|
+
private readonly recoveryAuthorize?;
|
|
54
76
|
/**
|
|
55
77
|
* Initializes and hosts a websocket with sonic protocol
|
|
56
78
|
* Rate limits can be set with wss.setClientRateLimit(x) and wss.setServerRateLimit(x); it is defaulted at 500/second per both
|
|
@@ -105,6 +127,8 @@ export declare class SonicWSServer extends MiddlewareHolder<ServerMiddleware> {
|
|
|
105
127
|
* @param runner Called when ready
|
|
106
128
|
*/
|
|
107
129
|
on_connect(runner: (client: SonicWSConnection) => void): void;
|
|
130
|
+
/** Runs after a reconnecting client has restored its state and rooms. */
|
|
131
|
+
on_recovered(runner: (client: SonicWSConnection, replayed: number) => void | Promise<void>): void;
|
|
108
132
|
/**
|
|
109
133
|
* Listens for whenever the server is ready
|
|
110
134
|
* @param runner Called when ready
|
|
@@ -115,8 +139,17 @@ export declare class SonicWSServer extends MiddlewareHolder<ServerMiddleware> {
|
|
|
115
139
|
* @param callback Called when server closes
|
|
116
140
|
*/
|
|
117
141
|
shutdown(callback: (err?: Error) => void): void;
|
|
142
|
+
private receiveAdapterBroadcast;
|
|
143
|
+
/** Wraps and stores an opted-in packet for bounded replay. */
|
|
144
|
+
replayFrame(connection: SonicWSConnection, packetFrame: Uint8Array): Uint8Array;
|
|
145
|
+
/** Restores state, rooms, and missed replayable packets onto a replacement connection. */
|
|
146
|
+
resumeSession(connection: SonicWSConnection, sessionId: string, lastSequence: number): Promise<void>;
|
|
118
147
|
private broadcastInternal;
|
|
119
148
|
broadcastTagged(tag: string, packetTag: string, ...values: any): Promise<void>;
|
|
149
|
+
/** Sends a packet to every local and adapter-connected room member. */
|
|
150
|
+
broadcastRoom(room: string, packetTag: string, ...values: any[]): Promise<void>;
|
|
151
|
+
/** Sends a room packet except to one connection. */
|
|
152
|
+
broadcastRoomExcept(connection: SonicWSConnection, room: string, packetTag: string, ...values: any[]): Promise<void>;
|
|
120
153
|
broadcastFiltered(tag: string, filter: (socket: SonicWSConnection) => boolean, ...values: any): Promise<void>;
|
|
121
154
|
broadcast(tag: string, ...values: any): Promise<void>;
|
|
122
155
|
broadcastSafe(tag: string, ...values: any[]): Promise<boolean>;
|
|
@@ -147,6 +180,11 @@ export declare class SonicWSServer extends MiddlewareHolder<ServerMiddleware> {
|
|
|
147
180
|
* @param replace If it should replace a previous tag; defaults to true. If using false, you can add multiple tags.
|
|
148
181
|
*/
|
|
149
182
|
tag(socket: SonicWSConnection, tag: string, replace?: boolean): void;
|
|
183
|
+
/** Adds a connection to a server-side room without removing its other rooms. */
|
|
184
|
+
join(socket: SonicWSConnection, room: string): void;
|
|
185
|
+
/** Removes a connection from a server-side room. */
|
|
186
|
+
leave(socket: SonicWSConnection, room: string): void;
|
|
187
|
+
rooms(socket: SonicWSConnection): ReadonlySet<string>;
|
|
150
188
|
private debugServer;
|
|
151
189
|
/**
|
|
152
190
|
* Opens a debug menu; this launches the browser and starts a subserver
|
|
@@ -4,4 +4,4 @@
|
|
|
4
4
|
* See LICENSE for personal, non-commercial license terms.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
var e,t=this&&this.__createBinding||(Object.create?function(e,t,s,n){void 0===n&&(n=s);var r=Object.getOwnPropertyDescriptor(t,s);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[s]}}),Object.defineProperty(e,n,r)}: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 r={};if(null!=n)for(var a=e(n),i=0;i<a.length;i++)"default"!==a[i]&&t(r,n,a[i]);return s(r,n),r});Object.defineProperty(exports,"__esModule",{value:!0}),exports.SonicWSServer=void 0;const r=n(require("ws")),a=require("node:fs/promises"),i=require("node:path"),o=require("./SonicWSConnection"),c=require("../util/packets/PacketHolder"),l=require("../util/packets/CompressionUtil"),d=require("../../native/wrapper"),h=require("../../version"),u=require("../util/packets/PacketUtils"),g=require("../PacketProcessor"),p=require("../util/packets/HashUtil"),b=require("../Connection"),w=require("../debug/DebugServer"),v=e=>{if(!Number.isFinite(e)||e<0)throw new Error("Rate limit must be a non-negative finite number.");return(e=Math.floor(e))>l.MAX_USHORT?(console.warn(`A rate limit above ${l.MAX_USHORT} is considered infinite.`),0):e};class f extends g.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];sendErrorHandler;constructor(e){super();const{clientPackets:t=[],serverPackets:s=[],websocketOptions:n={}}=e;if(this.sendErrorHandler=e.onSendError,this.wss=new r.WebSocketServer(n),e.sonicServerSettings?.serveBrowserClient??1){const e=n.server??this.wss._server;e&&this.installBrowserAssetRoutes(e)}this.clientPackets=new c.PacketHolder(t),this.serverPackets=new c.PacketHolder(s);const a=this.clientPackets.serialize(),i=this.serverPackets.serialize(),u=[...h.SERVER_SUFFIX_NUMS,h.VERSION],g=[...(0,l.convertVarInt)(a.length),...a,...i];(0,p.setHashFunc)(e.sonicServerSettings?.bit64Hash??!0),this.wss.on("connection",async e=>{const t=new o.SonicWSConnection(e,this,this.generateSocketID(),this.handshakePacket,this.clientRateLimit,this.serverRateLimit);if(await this.callMiddleware("onClientConnect",t))return t.close(b.CloseCodes.MIDDLEWARE,"Connection blocked by middleware."),this.callMiddleware("onClientDisconnect",t,b.CloseCodes.MIDDLEWARE,Buffer.from("Connection blocked by middleware.")),void this.availableIds.push(t.id);const s=new Uint8Array([...(0,l.convertVarInt)(t.id),...g]);e.send([...u,...(0,d.deflateNative)(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)>h.VERSION&&console.warn(`SonicWS is currently running outdated! (current: ${h.VERSION}, latest: ${e}) Update with "npm update sonic-ws"`)}).catch(e=>{console.error(e),console.warn("Could not check SonicWS version.")})}installBrowserAssetRoutes(e){const t=e.listeners("request");e.removeAllListeners("request"),e.on("request",(s,n)=>{const r=new URL(s.url??"/","http://localhost").pathname,o="/SonicWS/bundle.js"===r?["bundle.js","text/javascript; charset=utf-8"]:"/SonicWS/bundle.wasm"===r?["bundle.wasm","application/wasm"]:void 0;if(!o){for(const r of t)Reflect.apply(r,e,[s,n]);return}const c=(0,i.resolve)(__dirname,"../../../bundled",o[0]);(0,a.readFile)(c).then(e=>{n.writeHead(200,{"content-type":o[1],"content-length":e.byteLength,"cache-control":"public, max-age=3600"}),n.end(e)}).catch(e=>{n.writeHead(500,{"content-type":"text/plain; charset=utf-8"}),n.end(`Unable to load SonicWS browser asset: ${e instanceof Error?e.message:String(e)}`)})})}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){this.clientRateLimit=v(e)}setServerRateLimit(e){this.serverRateLimit=v(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[r,a,i]=await(0,u.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(r,a,i))}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)}async broadcastSafe(e,...t){try{return await this.broadcast(e,...t),!0}catch(t){return this.handleSendError(t,{packetTag:e,operation:"broadcast"}),!1}}broadcastVariant(e,t,...s){return this.broadcast(this.serverPackets.getVariantTag(e,t),...s)}handleSendError(e,t){this.sendErrorHandler?this.sendErrorHandler(e,t):console.error(`Failed to send packet "${t.packetTag}"`,e)}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 w.DebugServer(this,e)}}exports.SonicWSServer=f;
|
|
7
|
+
var e,t=this&&this.__createBinding||(Object.create?function(e,t,s,r){void 0===r&&(r=s);var n=Object.getOwnPropertyDescriptor(t,s);n&&!("get"in n?!t.__esModule:n.writable||n.configurable)||(n={enumerable:!0,get:function(){return t[s]}}),Object.defineProperty(e,r,n)}:function(e,t,s,r){void 0===r&&(r=s),e[r]=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}),r=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(r){if(r&&r.__esModule)return r;var n={};if(null!=r)for(var a=e(r),i=0;i<a.length;i++)"default"!==a[i]&&t(n,r,a[i]);return s(n,r),n});Object.defineProperty(exports,"__esModule",{value:!0}),exports.SonicWSServer=void 0;const n=r(require("ws")),a=require("node:fs/promises"),i=require("node:path"),o=require("./SonicWSConnection"),c=require("../util/packets/PacketHolder"),d=require("../util/packets/CompressionUtil"),h=require("../../native/wrapper"),l=require("../../version"),u=require("../util/packets/PacketUtils"),v=require("../PacketProcessor"),g=require("../util/packets/HashUtil"),p=require("../Connection"),f=require("../debug/DebugServer"),w=require("node:crypto"),b=require("../util/packets/ControlProtocol");function m(e){if(!Number.isFinite(e)||e<0)throw new Error("Rate limit must be a non-negative finite number");return(e=Math.floor(e))>d.MAX_USHORT?(console.warn(`A rate limit above ${d.MAX_USHORT} is considered infinite`),0):e}class S extends v.MiddlewareHolder{wss;availableIds=[];lastId=0;connectListeners=[];recoveredListeners=[];clientPackets;serverPackets;connections=[];connectionMap={};clientRateLimit=500;serverRateLimit=500;handshakePacket=null;tags=new Map;tagsInv=new Map;serverwideSendQueue=[!1,[],void 0];sendErrorHandler;adapter;serverId=(0,w.randomUUID)();sessions=new Map;recoveryMaxDisconnectionMs;recoveryMaxPackets;recoveryAuthorize;constructor(e){super();const{clientPackets:t=[],serverPackets:s=[],websocketOptions:r={}}=e;if(this.sendErrorHandler=e.onSendError,this.adapter=e.adapter,this.recoveryMaxDisconnectionMs=e.recovery?.maxDisconnectionMs??12e4,this.recoveryMaxPackets=e.recovery?.maxPackets??1e3,this.recoveryAuthorize=e.recovery?.authorize,!Number.isFinite(this.recoveryMaxDisconnectionMs)||this.recoveryMaxDisconnectionMs<0)throw new Error("recovery.maxDisconnectionMs must be a non-negative finite number");if(!Number.isInteger(this.recoveryMaxPackets)||this.recoveryMaxPackets<0)throw new Error("recovery.maxPackets must be a non-negative integer");const a=e.sonicServerSettings?.handshakeTimeoutMs??1e4,i=e.sonicServerSettings?.heartbeatIntervalMs??3e4,u=e.sonicServerSettings?.heartbeatTimeoutMs??1e4;if(!Number.isFinite(a)||a<=0)throw new Error("handshakeTimeoutMs must be positive");if(!Number.isFinite(i)||i<0||!Number.isFinite(u)||u<=0)throw new Error("Invalid heartbeat timing options");if(this.wss=new n.WebSocketServer({maxPayload:8388608,...r}),e.sonicServerSettings?.serveBrowserClient??1){const e=r.server??this.wss._server;e&&this.installBrowserAssetRoutes(e)}this.clientPackets=new c.PacketHolder(t),this.serverPackets=new c.PacketHolder(s),Promise.resolve(this.adapter?.start?.(this.serverId,e=>this.receiveAdapterBroadcast(e))).catch(e=>this.handleSendError(e,{packetTag:"<adapter>",operation:"broadcast"}));const v=this.clientPackets.serialize(),f=this.serverPackets.serialize(),b=[...l.SERVER_SUFFIX_NUMS,l.VERSION],m=[...(0,d.convertVarInt)(v.length),...v,...f];(0,g.setHashFunc)(e.sonicServerSettings?.bit64Hash??!0),this.wss.on("connection",async(t,s)=>{const r=e.sonicServerSettings?.heartbeatIntervalMs??3e4,a=e.sonicServerSettings?.heartbeatTimeoutMs??1e4;let i;const c=r>0?setInterval(()=>{t.readyState===n.WebSocket.OPEN&&(t.ping(),i&&clearTimeout(i),i=setTimeout(()=>t.terminate(),a),i.unref?.())},r):void 0;c?.unref?.(),t.on("pong",()=>{i&&clearTimeout(i),i=void 0});const l=(0,w.randomUUID)(),u={state:{},rooms:new Set,sequence:0,frames:[],expiresAt:1/0};this.sessions.set(l,u);const v=new o.SonicWSConnection(t,this,this.generateSocketID(),this.handshakePacket,this.clientRateLimit,this.serverRateLimit,l,u.state,e.sonicServerSettings?.handshakeTimeoutMs??1e4,s);if(await this.callMiddleware("onClientConnect",v)){const e="Connection blocked by middleware";return v.close(p.CloseCodes.MIDDLEWARE,e),this.callMiddleware("onClientDisconnect",v,p.CloseCodes.MIDDLEWARE,Buffer.from(e)),this.availableIds.push(v.id),void this.sessions.delete(l)}const g=(new TextEncoder).encode(l),f=new Uint8Array([...(0,d.convertVarInt)(v.id),...(0,d.convertVarInt)(g.length),...g,...m]);t.send([...b,...(0,h.deflateNative)(f)]),this.connections.push(v),this.connectionMap[v.id]=v,this.connectListeners.forEach(e=>e(v)),t.on("close",(e,t)=>{c&&clearInterval(c),i&&clearTimeout(i);const s=new Set(this.tags.get(v)??[]);if(this.connections.splice(this.connections.indexOf(v),1),delete this.connectionMap[v.id],this.availableIds.push(v.id),this.tags.has(v)){for(const e of this.tags.get(v))this.tagsInv.get(e)?.delete(v);this.tags.delete(v)}Promise.resolve(this.adapter?.disconnect(v.id)).catch(e=>this.handleSendError(e,{packetTag:"<adapter>",connection:v}));const r=this.sessions.get(v.sessionId);if(r){r.rooms=s,r.expiresAt=Date.now()+this.recoveryMaxDisconnectionMs;const e=setTimeout(()=>{const e=this.sessions.get(v.sessionId);e===r&&e.expiresAt<=Date.now()&&this.sessions.delete(v.sessionId)},this.recoveryMaxDisconnectionMs+1);e.unref?.()}this.callMiddleware("onClientDisconnect",v,e,t)})}),(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)>l.VERSION&&console.warn(`SonicWS protocol ${l.VERSION} is outdated, latest is ${e}. Update with "npm update sonic-ws"`)}).catch(e=>{console.error(e),console.warn("Could not check the SonicWS protocol version")})}installBrowserAssetRoutes(e){const t=e.listeners("request");e.removeAllListeners("request"),e.on("request",(s,r)=>{const n=new URL(s.url??"/","http://localhost").pathname,o="/SonicWS/bundle.js"===n?["bundle.js","text/javascript; charset=utf-8"]:"/SonicWS/bundle.wasm"===n?["bundle.wasm","application/wasm"]:void 0;if(!o){for(const n of t)Reflect.apply(n,e,[s,r]);return}const c=(0,i.resolve)(__dirname,"../../../bundled",o[0]);(0,a.readFile)(c).then(e=>{r.writeHead(200,{"content-type":o[1],"content-length":e.byteLength,"cache-control":"public, max-age=3600"}),r.end(e)}).catch(e=>{r.writeHead(500,{"content-type":"text/plain; charset=utf-8"}),r.end(`Unable to load SonicWS browser asset: ${e instanceof Error?e.message:String(e)}`)})})}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 define "${e}" so it cannot be used as a handshake`);if(0!==this.clientPackets.getPacket(e).dataBatching)throw new Error(`The batched packet "${e}" cannot be used as a handshake`);this.handshakePacket=e}setClientRateLimit(e){this.clientRateLimit=m(e)}setServerRateLimit(e){this.serverRateLimit=m(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_recovered(e){this.recoveredListeners.push(e)}on_ready(e){this.wss.on("listening",e)}shutdown(e){this.sessions.clear(),Promise.resolve(this.adapter?.close?.()).catch(e=>this.handleSendError(e,{packetTag:"<adapter>",operation:"broadcast"})),this.wss.close(e)}async receiveAdapterBroadcast(e){e.origin!==this.serverId&&await this.broadcastInternal(e.packetTag,{type:"filter",filter:t=>!0===this.tags.get(t)?.has(e.room)&&t.id!==e.exceptConnectionId},e.values)}replayFrame(e,t){const s=this.sessions.get(e.sessionId);if(!s)return t;const r=++s.sequence,n=(0,b.encodeReplay)(r,t);return s.frames.push({sequence:r,data:n}),s.frames.length>this.recoveryMaxPackets&&s.frames.splice(0,s.frames.length-this.recoveryMaxPackets),n}async resumeSession(e,t,s){const r=this.sessions.get(t);if(!r||r.expiresAt<Date.now())return void e.raw_send((0,b.encodeResumed)(!1,0));if(!(this.recoveryAuthorize?await this.recoveryAuthorize(r.state,e.state,e):void 0===r.state.userId||r.state.userId===e.state.userId))return void e.raw_send((0,b.encodeResumed)(!1,0));this.sessions.delete(e.sessionId),e.sessionId=t,e.state=r.state,r.expiresAt=1/0,this.sessions.set(t,r);for(const t of r.rooms)this.join(e,t);const n=r.frames.filter(e=>e.sequence>s);for(const t of n)e.raw_send(t.data);e.raw_send((0,b.encodeResumed)(!0,n.length));for(const t of this.recoveredListeners)await t(e,n.length)}async broadcastInternal(e,t,s){let r;if("all"===t.type)r=this.connections;else if("tagged"===t.type){if(!this.tagsInv.has(t.tag))return;r=Array.from(this.tagsInv.get(t.tag))}else r=this.connections.filter(t.filter);if(await this.callMiddleware("onPacketBroadcast_pre",e,{recipients:r,...t},s))return;if(0===r.length)return;const[n,a,i]=await(0,u.processPacket)(this.serverPackets,e,s,this.serverwideSendQueue,-1);await this.callMiddleware("onPacketBroadcast_post",e,{recipients:r,...t},a,a.length)||r.forEach(e=>e.send_processed(n,a,i))}async broadcastTagged(e,t,...s){await this.broadcastInternal(t,{type:"tagged",tag:e},s)}async broadcastRoom(e,t,...s){await this.broadcastInternal(t,{type:"tagged",tag:e},s),await(this.adapter?.publish({origin:this.serverId,room:e,packetTag:t,values:s}))}async broadcastRoomExcept(e,t,s,...r){await this.broadcastInternal(s,{type:"filter",filter:s=>s!==e&&!0===this.tags.get(s)?.has(t)},r),await(this.adapter?.publish({origin:this.serverId,room:t,packetTag:s,values:r,exceptConnectionId:e.id}))}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)}async broadcastSafe(e,...t){try{return await this.broadcast(e,...t),!0}catch(t){return this.handleSendError(t,{packetTag:e,operation:"broadcast"}),!1}}broadcastVariant(e,t,...s){return this.broadcast(this.serverPackets.getVariantTag(e,t),...s)}handleSendError(e,t){this.sendErrorHandler?this.sendErrorHandler(e,t):console.error(`Failed to send packet "${t.packetTag}"`,e)}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.has(e)||this.tags.set(e,new Set),this.tagsInv.has(t)||this.tagsInv.set(t,new Set),s&&(this.tags.get(e).forEach(t=>{this.tagsInv.get(t)?.delete(e),Promise.resolve(this.adapter?.leave(e.id,t)).catch(t=>this.handleSendError(t,{packetTag:"<adapter>",connection:e}))}),this.tags.get(e).clear()),this.tags.get(e).add(t),this.tagsInv.get(t).add(e),Promise.resolve(this.adapter?.join(e.id,t)).catch(t=>this.handleSendError(t,{packetTag:"<adapter>",connection:e}))}join(e,t){if(!t)throw new Error("Room name cannot be empty");this.tag(e,t,!1)}leave(e,t){this.tags.get(e)?.delete(t);const s=this.tagsInv.get(t);s?.delete(e),0===s?.size&&this.tagsInv.delete(t),Promise.resolve(this.adapter?.leave(e.id,t)).catch(t=>this.handleSendError(t,{packetTag:"<adapter>",connection:e}))}rooms(e){return this.tags.get(e)??new Set}debugServer=null;OpenDebug(e={}){if(null!=this.debugServer)throw new Error("A debug server is already running");this.debugServer=new f.DebugServer(this,e)}}exports.SonicWSServer=S;
|
|
@@ -4,4 +4,4 @@
|
|
|
4
4
|
* See LICENSE for personal, non-commercial license terms.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
Object.defineProperty(exports,"__esModule",{value:!0}),exports.BatchHelper=void 0;const t=require("../BufferUtil"),e=require("../../../native/wrapper");exports.BatchHelper=class{batchInfo={};batchTimeouts={};batchedData={};conn;registerSendPackets(t,e){this.conn=e,t.getTags().forEach(e=>{const a=t.getPacket(e);if(0
|
|
7
|
+
Object.defineProperty(exports,"__esModule",{value:!0}),exports.BatchHelper=void 0;const t=require("../BufferUtil"),e=require("../../../native/wrapper");exports.BatchHelper=class{batchInfo={};batchTimeouts={};batchedData={};conn;registerSendPackets(t,e){this.conn=e,t.getTags().forEach(e=>{const a=t.getPacket(e);if(0===a.dataBatching)return;const c=t.getKey(e);this.initiateBatch(c,a.dataBatching,a.gzipCompression)})}initiateBatch(t,e,a){this.batchedData[t]=[],this.batchInfo[t]=[e,a]}startBatch(a){const[c,i]=this.batchInfo[a];this.batchTimeouts[a]=this.conn.setInterval(()=>{if(0===this.batchedData[a].length)return;const c=(0,e.encodeNativeBatch)(this.batchedData[a],i);this.conn.raw_send((0,t.toPacketBuffer)(a,c)),this.batchedData[a]=[],delete this.batchTimeouts[a]},c)}batchPacket(t,e){this.batchedData[t].push(e),this.batchTimeouts[t]||this.startBatch(t)}static async unravelBatch(t,a,c){let i;try{i=(0,e.decodeNativeBatch)(a,t.gzipCompression,t.maxBatchSize)}catch(t){return`Invalid batch: ${String(t)}`}const s=[];for(const e of i){const a=await t.listen(e,c);if("string"==typeof a)return`Batched packet: ${a}`;s.push(a)}return s}};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 Lily (liwybloc)
|
|
3
|
+
* License-Identifier: LicenseRef-Lily-Personal-NonCommercial-2026
|
|
4
|
+
* See LICENSE for personal, non-commercial license terms.
|
|
5
|
+
*/
|
|
6
|
+
export type PacketConstructor<T = any> = new (values: Record<string, any>) => T;
|
|
7
|
+
/** Registers a local class that packet schema metadata may reference by name. */
|
|
8
|
+
export declare function RegisterPacketConstructor<T extends PacketConstructor>(constructor: T): T;
|
|
9
|
+
/** Removes a registered packet class, primarily for tests and hot reload. */
|
|
10
|
+
export declare function UnregisterPacketConstructor(name: string): void;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 Lily (liwybloc)
|
|
3
|
+
* License-Identifier: LicenseRef-Lily-Personal-NonCommercial-2026
|
|
4
|
+
* See LICENSE for personal, non-commercial license terms.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
Object.defineProperty(exports,"__esModule",{value:!0}),exports.RegisterPacketConstructor=function(t){const r=t.name;if(!r)throw new Error("Packet constructors must have a stable class name");const o=e.get(r);if(o&&o!==t)throw new Error(`A different packet constructor named "${r}" is already registered`);return e.set(r,t),t},exports.UnregisterPacketConstructor=function(t){e.delete(t)},exports.resolvePacketConstructor=function(t){const r=e.get(t);if(!r)throw new Error(`Packet constructor "${t}" is not registered locally. Call RegisterPacketConstructor(${t}) before decoding.`);return r};const e=new Map;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 Lily (liwybloc)
|
|
3
|
+
* License-Identifier: LicenseRef-Lily-Personal-NonCommercial-2026
|
|
4
|
+
* See LICENSE for personal, non-commercial license terms.
|
|
5
|
+
*/
|
|
6
|
+
export declare const CONTROL_KEY = 0;
|
|
7
|
+
export declare enum ControlType {
|
|
8
|
+
REQUEST = 1,
|
|
9
|
+
RESPONSE = 2,
|
|
10
|
+
REPLAY = 3,
|
|
11
|
+
RESUME = 4,
|
|
12
|
+
RESUMED = 5
|
|
13
|
+
}
|
|
14
|
+
export type ControlRequest = {
|
|
15
|
+
type: ControlType.REQUEST;
|
|
16
|
+
id: number;
|
|
17
|
+
packetKey: number;
|
|
18
|
+
payload: Uint8Array;
|
|
19
|
+
};
|
|
20
|
+
export type ControlResponse = {
|
|
21
|
+
type: ControlType.RESPONSE;
|
|
22
|
+
id: number;
|
|
23
|
+
ok: boolean;
|
|
24
|
+
value: any;
|
|
25
|
+
};
|
|
26
|
+
export type ControlReplay = {
|
|
27
|
+
type: ControlType.REPLAY;
|
|
28
|
+
sequence: number;
|
|
29
|
+
payload: Uint8Array;
|
|
30
|
+
};
|
|
31
|
+
export type ControlResume = {
|
|
32
|
+
type: ControlType.RESUME;
|
|
33
|
+
sessionId: string;
|
|
34
|
+
lastSequence: number;
|
|
35
|
+
};
|
|
36
|
+
export type ControlResumed = {
|
|
37
|
+
type: ControlType.RESUMED;
|
|
38
|
+
recovered: boolean;
|
|
39
|
+
replayed: number;
|
|
40
|
+
};
|
|
41
|
+
export type ControlMessage = ControlRequest | ControlResponse | ControlReplay | ControlResume | ControlResumed;
|
|
42
|
+
export declare function encodeControlRequest(id: number, packetKey: number, payload: Uint8Array): Uint8Array;
|
|
43
|
+
export declare function encodeControlResponse(id: number, ok: boolean, value: any): Uint8Array;
|
|
44
|
+
export declare const encodeReplay: (sequence: number, payload: Uint8Array) => Uint8Array<ArrayBuffer>;
|
|
45
|
+
export declare function encodeResume(sessionId: string, lastSequence: number): Uint8Array;
|
|
46
|
+
export declare const encodeResumed: (recovered: boolean, replayed: number) => Uint8Array<ArrayBuffer>;
|
|
47
|
+
export declare function decodeControl(data: Uint8Array): ControlMessage;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 Lily (liwybloc)
|
|
3
|
+
* License-Identifier: LicenseRef-Lily-Personal-NonCommercial-2026
|
|
4
|
+
* See LICENSE for personal, non-commercial license terms.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
Object.defineProperty(exports,"__esModule",{value:!0}),exports.encodeResumed=exports.encodeReplay=exports.ControlType=exports.CONTROL_KEY=void 0,exports.encodeControlRequest=function(r,o,n){return Uint8Array.from([exports.CONTROL_KEY,t.REQUEST,...(0,e.convertVarInt)(r),o,...n])},exports.encodeControlResponse=function(o,n,s){return Uint8Array.from([exports.CONTROL_KEY,t.RESPONSE,...(0,e.convertVarInt)(o),Number(n),...(0,r.compressJSON)(s)])},exports.encodeResume=function(r,o){const n=(new TextEncoder).encode(r);return Uint8Array.from([exports.CONTROL_KEY,t.RESUME,...(0,e.convertVarInt)(n.length),...n,...(0,e.convertVarInt)(o)])},exports.decodeControl=function(o){if(o[0]!==exports.CONTROL_KEY||o.length<3)throw new Error("Invalid SonicWS control frame");const n=o[1];switch(n){case t.REPLAY:{const[r,t]=(0,e.readVarInt)(o,2);return{type:n,sequence:t,payload:o.slice(r)}}case t.RESUME:{const[r,t]=(0,e.readVarInt)(o,2),s=r+t;if(s>o.length)throw new Error("Recovery frame has an invalid session id");const[c,a]=(0,e.readVarInt)(o,s);return{type:n,sessionId:(new TextDecoder).decode(o.slice(r,s)),lastSequence:a}}case t.RESUMED:{if(o.length<4)throw new Error("Invalid recovery result frame");const[r,t]=(0,e.readVarInt)(o,3);return{type:n,recovered:0!==o[2],replayed:t}}case t.REQUEST:{const[r,t]=(0,e.readVarInt)(o,2);if(r>=o.length)throw new Error("RPC request is missing its packet key");return{type:n,id:t,packetKey:o[r],payload:o.slice(r+1)}}case t.RESPONSE:{const[t,s]=(0,e.readVarInt)(o,2);if(t>=o.length)throw new Error("RPC response is missing its status");return{type:n,id:s,ok:0!==o[t],value:(0,r.decompressJSON)(o.slice(t+1))}}default:throw new Error(`Unknown SonicWS control frame type: ${n}`)}};const e=require("./CompressionUtil"),r=require("./JSONUtil");var t;exports.CONTROL_KEY=0,function(e){e[e.REQUEST=1]="REQUEST",e[e.RESPONSE=2]="RESPONSE",e[e.REPLAY=3]="REPLAY",e[e.RESUME=4]="RESUME",e[e.RESUMED=5]="RESUMED"}(t||(exports.ControlType=t={}));exports.encodeReplay=(r,o)=>Uint8Array.from([exports.CONTROL_KEY,t.REPLAY,...(0,e.convertVarInt)(r),...o]);exports.encodeResumed=(r,o)=>Uint8Array.from([exports.CONTROL_KEY,t.RESUMED,Number(r),...(0,e.convertVarInt)(o)]);
|
|
@@ -4,4 +4,4 @@
|
|
|
4
4
|
* See LICENSE for personal, non-commercial license terms.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
Object.defineProperty(exports,"__esModule",{value:!0}),exports.PacketHolder=void 0;exports.PacketHolder=class{key;keys;tags;packetMap;packets;variants={};parents=new Set;constructor(t){this.key=1,this.keys={},this.tags={},this.packetMap={},t&&this.holdPackets(t)}createKey(t){this.keys[t]=this.key,this.tags[this.key]=t,this.key++}holdPackets(t)
|
|
7
|
+
Object.defineProperty(exports,"__esModule",{value:!0}),exports.PacketHolder=void 0;const t=require("./SchemaValidation");exports.PacketHolder=class{key;keys;tags;packetMap;packets;variants={};parents=new Set;constructor(t){this.key=1,this.keys={},this.tags={},this.packetMap={},t&&this.holdPackets(t)}createKey(t){this.keys[t]=this.key,this.tags[this.key]=t,this.key++}holdPackets(e){(0,t.AssertPacketSchema)(e),this.packets=e;for(const t of e)this.createKey(t.tag),this.packetMap[t.tag]=t,t.parent&&t.variant&&(this.variants[`${t.parent}.${t.variant}`]=t.tag,this.parents.add(t.parent)),t.isParent&&this.parents.add(t.tag)}getKey(t){if(!((t=this.resolveTag(t))in this.keys))throw new Error(`Not a valid tag: ${t}`);return this.keys[t]}getTag(t){if(t in this.tags)return this.tags[t]}getPacket(t){if(!((t=this.resolveTag(t))in this.packetMap))throw new Error("Unknown packet tag: "+t);return this.packetMap[t]}hasKey(t){return t in this.tags}hasTag(t){return this.resolveTag(t)in this.keys||this.parents.has(t)}resolveTag(t){return this.variants[t]??t}getVariantTag(t,e){const a=this.variants[`${t}.${e}`];if(!a)throw new Error(`Unknown packet variant: ${t}.${e}`);return a}getKeys(){return this.keys}getTagMap(){return this.tags}getTags(){return Object.values(this.tags)}getPackets(){return this.packets}serialize(){return this.packets.map(t=>t.serialize()).flat()}};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 Lily (liwybloc)
|
|
3
|
+
* License-Identifier: LicenseRef-Lily-Personal-NonCommercial-2026
|
|
4
|
+
* See LICENSE for personal, non-commercial license terms.
|
|
5
|
+
*/
|
|
6
|
+
import { Packet } from "../../packets/Packets";
|
|
7
|
+
export type PacketManifest = {
|
|
8
|
+
clientPackets: readonly Packet<any>[];
|
|
9
|
+
serverPackets: readonly Packet<any>[];
|
|
10
|
+
};
|
|
11
|
+
/** Serializes both directional packet tables into a portable, versioned manifest. */
|
|
12
|
+
export declare function CreatePacketManifest(manifest: PacketManifest): Uint8Array;
|
|
13
|
+
/** Loads a manifest produced by TypeScript or Python for the current protocol version. */
|
|
14
|
+
export declare function LoadPacketManifest(data: Uint8Array): {
|
|
15
|
+
clientPackets: Packet<any>[];
|
|
16
|
+
serverPackets: Packet<any>[];
|
|
17
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 Lily (liwybloc)
|
|
3
|
+
* License-Identifier: LicenseRef-Lily-Personal-NonCommercial-2026
|
|
4
|
+
* See LICENSE for personal, non-commercial license terms.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
Object.defineProperty(exports,"__esModule",{value:!0}),exports.CreatePacketManifest=function(t){const i=t.clientPackets.flatMap(e=>e.serialize()),n=t.serverPackets.flatMap(e=>e.serialize());return Uint8Array.from([...a,e.VERSION,...(0,r.convertVarInt)(i.length),...i,...n])},exports.LoadPacketManifest=function(i){if(i.length<6||a.some((e,t)=>i[t]!==e))throw new Error("Invalid SonicWS packet manifest");if(i[4]!==e.VERSION)throw new Error(`Packet manifest protocol mismatch: ${i[4]} != ${e.VERSION}`);const[n,s]=(0,r.readVarInt)(i,5),c=n+s;if(c>i.length)throw new Error("Truncated SonicWS packet manifest");return{clientPackets:t.Packet.deserializeAll(i.slice(n,c),!1),serverPackets:t.Packet.deserializeAll(i.slice(c),!1)}};const e=require("../../../version"),t=require("../../packets/Packets"),r=require("./CompressionUtil"),a=[83,87,83,77];
|
|
@@ -25,7 +25,10 @@ export type SharedPacketSettings = {
|
|
|
25
25
|
* Each batched packet is counted towards the rate limit.
|
|
26
26
|
*/
|
|
27
27
|
dataBatching?: number;
|
|
28
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* Limits packets in one batch on the receiving client.
|
|
30
|
+
* Defaults to 10. Zero disables the limit.
|
|
31
|
+
*/
|
|
29
32
|
maxBatchSize?: number;
|
|
30
33
|
/** The amount of times this packet can be sent every second, or 0 for infinite. */
|
|
31
34
|
rateLimit?: number;
|
|
@@ -37,10 +40,15 @@ export type SharedPacketSettings = {
|
|
|
37
40
|
enabled?: boolean;
|
|
38
41
|
/** A validation function that is called whenever data is received. Return true for success, return false to kick socket. */
|
|
39
42
|
validator?: ValidatorFunction;
|
|
40
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* Allows other packet types to run while this packet is processing.
|
|
45
|
+
* Repeated calls for this packet remain serialized.
|
|
46
|
+
*/
|
|
41
47
|
async?: boolean;
|
|
42
48
|
/** If this is true, the packet will be Gzip compressed. Defaults to false on all types but JSON. */
|
|
43
49
|
gzipCompression?: boolean;
|
|
50
|
+
/** Retain this server-to-client packet briefly for connection-state recovery. */
|
|
51
|
+
replay?: boolean;
|
|
44
52
|
};
|
|
45
53
|
/** Settings for single-typed packets */
|
|
46
54
|
export type SinglePacketSettings = SharedPacketSettings & {
|
|
@@ -50,7 +58,10 @@ export type SinglePacketSettings = SharedPacketSettings & {
|
|
|
50
58
|
dataMax?: number;
|
|
51
59
|
/** The minimum amount of values that can be sent through this packet; defaults to the max */
|
|
52
60
|
dataMin?: number;
|
|
53
|
-
/**
|
|
61
|
+
/**
|
|
62
|
+
* Reuses the previous value when an empty payload arrives.
|
|
63
|
+
* This is incompatible with a zero data minimum.
|
|
64
|
+
*/
|
|
54
65
|
rereference?: boolean;
|
|
55
66
|
/** Field names used to map positional values to and from an object. */
|
|
56
67
|
schema?: readonly string[];
|
|
@@ -65,26 +76,38 @@ export type SinglePacketSettings = SharedPacketSettings & {
|
|
|
65
76
|
min?: number;
|
|
66
77
|
/** Inclusive application-level numeric maximum. */
|
|
67
78
|
max?: number;
|
|
79
|
+
/**
|
|
80
|
+
* Constructs decoded schema fields with a local class.
|
|
81
|
+
* Peers exchange the class name and register their own matching class.
|
|
82
|
+
*/
|
|
83
|
+
constructor?: Function;
|
|
68
84
|
};
|
|
69
85
|
/** Settings for multi-typed packets */
|
|
70
86
|
export type MultiPacketSettings = SharedPacketSettings & {
|
|
71
87
|
/** The data types of the packet */
|
|
72
88
|
readonly types: readonly ArguableType[];
|
|
73
|
-
/**
|
|
89
|
+
/** Sets the maximum value count per field. A scalar applies to every field. */
|
|
74
90
|
dataMaxes?: number[] | number;
|
|
75
|
-
/**
|
|
91
|
+
/** Sets the minimum value count per field. A scalar applies to every field. */
|
|
76
92
|
dataMins?: number[] | number;
|
|
77
|
-
/**
|
|
93
|
+
/** Transposes repeated rows into columns before encoding. */
|
|
78
94
|
autoFlatten?: boolean;
|
|
79
95
|
/** Field names for records transposed into object-packet columns. */
|
|
80
96
|
schema?: readonly string[];
|
|
81
97
|
/** Column-major repeated-record mapping. autoFlatten remains a deprecated alias. */
|
|
82
98
|
autoTranspose?: boolean;
|
|
99
|
+
/** Local class constructed for every decoded schema record. */
|
|
100
|
+
constructor?: Function;
|
|
83
101
|
};
|
|
84
102
|
export type PacketGroupSettings = {
|
|
85
103
|
tag: string;
|
|
86
104
|
variants: Record<string, Omit<SinglePacketSettings, "tag">>;
|
|
87
105
|
};
|
|
106
|
+
type GroupMetadata = {
|
|
107
|
+
parent: string;
|
|
108
|
+
variant: string;
|
|
109
|
+
isParent: boolean;
|
|
110
|
+
};
|
|
88
111
|
/** Settings for single-typed enum packets */
|
|
89
112
|
export type EnumPacketSettings = SharedPacketSettings & {
|
|
90
113
|
/** The tag of the enum; used for WrapEnum(enumTag) */
|
|
@@ -94,24 +117,21 @@ export type EnumPacketSettings = SharedPacketSettings & {
|
|
|
94
117
|
/** The minimum amount of values that can be sent through this packet; defaults to the max */
|
|
95
118
|
dataMin?: number;
|
|
96
119
|
};
|
|
97
|
-
export type KeyEffectivePacketSettings = SharedPacketSettings & {
|
|
98
|
-
/** Amount of keys to consume in order to have differing values; defaults to 2. Must be 2+ */
|
|
99
|
-
count?: number;
|
|
100
|
-
};
|
|
101
120
|
/**
|
|
102
121
|
* Creates a structure for a simple single-typed packet.
|
|
103
122
|
* This packet can be sent and received with the specified tag, type, and data cap.
|
|
104
|
-
* @param settings
|
|
123
|
+
* @param settings Packet type, limits, mapping, validation, and batching settings.
|
|
105
124
|
* @returns The constructed packet structure data.
|
|
106
125
|
* @throws {Error} If the `type` is invalid.
|
|
107
126
|
*/
|
|
108
127
|
export declare function CreatePacket<T extends ArguableType>(settings: SinglePacketSettings & {
|
|
109
128
|
type?: T;
|
|
129
|
+
_group?: GroupMetadata;
|
|
110
130
|
}): Packet<ConvertType<T>>;
|
|
111
131
|
/**
|
|
112
132
|
* Creates a structure for an object (multi-typed) packet.
|
|
113
133
|
* This packet allows multiple types and their associated data caps.
|
|
114
|
-
* @param settings
|
|
134
|
+
* @param settings Field types, limits, transposition, validation, and batching settings.
|
|
115
135
|
* @returns The constructed packet structure data.
|
|
116
136
|
* @throws {Error} If any type in `types` is invalid.
|
|
117
137
|
*/
|
|
@@ -120,16 +140,25 @@ export declare function CreateObjPacket<T extends readonly ArguableType[], V ext
|
|
|
120
140
|
}>(settings: MultiPacketSettings & {
|
|
121
141
|
readonly types: T;
|
|
122
142
|
}): Packet<V>;
|
|
123
|
-
/**
|
|
143
|
+
/**
|
|
144
|
+
* Creates a parent packet and its named variants as ordinary packet definitions.
|
|
145
|
+
*
|
|
146
|
+
* A group named `movement` with `look`, `move`, and `both` variants returns four
|
|
147
|
+
* packets: `movement` (`PacketType.NONE`), `movement.look`, `movement.move`, and
|
|
148
|
+
* `movement.both`. Spread the returned array into `clientPackets` or
|
|
149
|
+
* `serverPackets`. Send a child with `sendVariant("movement", "move", value)`;
|
|
150
|
+
* listeners on `movement.move` receive the child payload, while listeners on
|
|
151
|
+
* `movement` also receive `{ variant: "move", payload }`. Sending `movement`
|
|
152
|
+
* directly represents the parent/empty variant (`variant: ""`).
|
|
153
|
+
*/
|
|
124
154
|
export declare function CreatePacketGroup(settings: PacketGroupSettings): Packet<any>[];
|
|
125
155
|
/**
|
|
126
156
|
* Creates and defines an enum packet. This can be used to create an enum-based packet
|
|
127
157
|
* with a specific tag and possible values.
|
|
128
|
-
* @param settings
|
|
158
|
+
* @param settings Enum package, limits, validation, and batching settings.
|
|
129
159
|
* @returns The constructed packet structure data.
|
|
130
160
|
*/
|
|
131
161
|
export declare function CreateEnumPacket(settings: EnumPacketSettings): Packet<PacketType.ENUMS>;
|
|
132
|
-
export declare function CreateKeyEffective(settings: KeyEffectivePacketSettings): Packet<PacketType.KEY_EFFECTIVE>;
|
|
133
162
|
/**
|
|
134
163
|
* Flattens a 2-depth array for efficient wire transfer
|
|
135
164
|
* Turns [[x,y,z],[x,y,z]...] to [[x,x...],[y,y...],[z,z...]]
|
|
@@ -142,3 +171,4 @@ export declare function FlattenData(arr: any[][]): any[][];
|
|
|
142
171
|
* @param array A flattened array
|
|
143
172
|
*/
|
|
144
173
|
export declare function UnFlattenData(arr: any[][]): any[][];
|
|
174
|
+
export {};
|
|
@@ -4,4 +4,4 @@
|
|
|
4
4
|
* See LICENSE for personal, non-commercial license terms.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
Object.defineProperty(exports,"__esModule",{value:!0}),exports.processPacket=
|
|
7
|
+
Object.defineProperty(exports,"__esModule",{value:!0}),exports.processPacket=i,exports.listenPacket=async function(e,t,a){if("string"==typeof e)return void a(e);const[r,n]=e;try{if(n&&Array.isArray(r))for(const e of t)await e(...r);else for(const e of t)await e(r)}catch(e){console.error(e),a(String(e))}},exports.CreatePacket=f,exports.CreateObjPacket=function(a){const r=Object.prototype.hasOwnProperty.call(a,"constructor")?a.constructor:void 0;let{tag:n,types:i=[],dataMaxes:f,dataMins:m,noDataRange:d=!1,dontSpread:w=!1,autoFlatten:y=!1,autoTranspose:g,schema:k,validator:P=null,dataBatching:v=0,maxBatchSize:E=10,rateLimit:S=0,enabled:$=!0,async:T=!1,gzipCompression:b=i&&i.includes(t.PacketType.JSON)}=a;if(!n)throw new Error("Packet tag is required");if(!i||0===i.length)throw new Error(`Packet "${n}" requires at least one type`);if(a.replay&&v)throw new Error(`Packet "${n}" cannot combine replay with batching`);if(h(k,n),r&&!k)throw new Error(`Packet "${n}" constructor requires schema`);r&&(0,o.RegisterPacketConstructor)(r);if(k&&k.length!==i.length)throw new Error(`Packet "${n}" schema length must match types length`);if(void 0!==g&&y&&g!==y)throw new Error(`Packet "${n}" has conflicting autoFlatten and autoTranspose options`);const x=g??y;for(const e of i)if(c(e))throw new Error(`Invalid packet type in "${n}" packet: ${e}`);if(d)f=Array.from({length:i.length},()=>s),m=Array.from({length:i.length},()=>0);else if(void 0===f?f=Array.from({length:i.length},()=>1):Array.isArray(f)||(f=Array.from({length:i.length},()=>f)),void 0===m){const e=f;m=Array.from({length:i.length},(t,a)=>e[a])}else Array.isArray(m)||(m=Array.from({length:i.length},()=>m));const A=m,q=f.map(l),M=A.map((e,a)=>i[a]===t.PacketType.NONE?0:u(e,q[a])),O=new e.PacketSchema(!0,i,T,M,q,p(S),w,x,!1,v,E,b,k,void 0,void 0,void 0,void 0,r?.name,a.replay??!1);return new e.Packet(n,O,P,$,!1)},exports.CreatePacketGroup=function(e){if(!e.tag||e.tag.includes("$"))throw new Error("Packet group tag is required and cannot contain '$'");const a=Object.entries(e.variants);if(!a.length)throw new Error(`Packet group "${e.tag}" requires at least one variant`);const r=f({tag:e.tag,type:t.PacketType.NONE,dataMin:0,dataMax:0,_group:{parent:e.tag,variant:"",isParent:!0}}),n=a.map(([t,a])=>{if(!t||t.includes("$"))throw new Error("Packet variant names cannot be empty or contain '$'");return f({...a,tag:`${e.tag}.${t}`,_group:{parent:e.tag,variant:t,isParent:!1}})});return[r,...n]},exports.CreateEnumPacket=function(e){const{tag:t,enumData:a,dataMax:r=1,dataMin:n=0,noDataRange:o=!1,dontSpread:i=!1,validator:c=null,dataBatching:s=0,maxBatchSize:l=10,rateLimit:u=0,enabled:p=!0,async:h=!1}=e;return f({tag:t,type:a,dataMax:r,dataMin:n,noDataRange:o,dontSpread:i,validator:c,dataBatching:s,maxBatchSize:l,rateLimit:u,enabled:p,async:h})},exports.FlattenData=d,exports.UnFlattenData=function(e){return e[0]?.map((t,a)=>e.map(e=>e[a]))??[]};const e=require("../../packets/Packets"),t=require("../../packets/PacketType"),a=require("../enums/EnumType"),r=require("./CompressionUtil"),n=require("./HashUtil"),o=require("./ConstructorRegistry");async function i(e,a,o,c,s,l=!1){const u=e.getKey(a),p=e.getPacket(a);return async function(e,t,a,r,n,o,c){if(e[0]&&!o)return new Promise(t=>e[1].push([t,r,n]));e[0]=!0;const s=await c();if(e[1].length>0){const[r,n,o]=e[1].shift();queueMicrotask(async()=>{r(await i(t,n,o,e,a,!0))})}else e[0]=!1;return s}(c,e,s,a,o,l,async()=>{if(p.rereference){if(-1===s)throw new Error("Cannot send a re-referenced packet from the server-wide sender");const e=(0,n.hashValue)(o);if(p.lastSent[s]===e)return[u,r.EMPTY_UINT8,p];p.lastSent[s]=e}if(o=p.prepareSend(o,s),p.autoFlatten&&p.object&&!p.fields)o=d(o[0]);else{if(o.length>p.maxSize)throw new Error(`Packet "${a}" only allows ${p.maxSize} values`);if(o.length<p.minSize)throw new Error(`Packet "${a}" requires at least ${p.minSize} values`)}if(p.object||p.type===t.PacketType.JSON){if(p.object&&(o=o.map(e=>Array.isArray(e)?e:[e]),!p.autoFlatten)){const e=p.dataMin,t=p.dataMax;for(let r=0;r<e.length;r++){if(o[r].length<e[r])throw new Error(`Section ${r+1} of packet "${a}" requires at least ${e[r]} values`);if(o[r].length>t[r])throw new Error(`Section ${r+1} of packet "${a}" only allows ${t[r]} values`)}}}else{const e=o.find(e=>"object"==typeof e&&null!=e);e&&console.warn(`Passing a nested value may produce undefined behavior (${JSON.stringify(e)}). Spread arrays into positional packet arguments`)}let e=r.EMPTY_UINT8;if(o.length>0){const t=p.processSend(o);e=t instanceof Promise?await t:t}return[u,e,p]})}function c(e){return!("number"==typeof e&&e in t.PacketType||e instanceof a.EnumPackage)}const s=2048383;function l(e){return e<0?(console.warn("A data maximum below zero is treated as zero"),0):e>s?(console.warn(`A packet type can contain at most ${s} values`),s):e}function u(e,t){return e<0?(console.warn("A data minimum below zero is treated as zero"),0):e>t?(console.warn("A data minimum above the data maximum is clamped to the maximum"),t):e}function p(e){if(!Number.isFinite(e)||e<0)throw new Error("Rate limit must be a non-negative finite number");return(e=Math.floor(e))>r.MAX_USHORT?(console.warn(`A rate limit above ${r.MAX_USHORT} is considered infinite.`),0):e}function f(a){const r=!0===a.autoFlatten&&void 0===a.dataMax&&void 0===a.dataMin,n=Object.prototype.hasOwnProperty.call(a,"constructor")?a.constructor:void 0;let{tag:i,type:f=t.PacketType.NONE,dataMax:d=1,dataMin:w,noDataRange:y=!1,dontSpread:g=!1,validator:k=null,dataBatching:P=0,maxBatchSize:v=10,rateLimit:E=0,enabled:S=!0,async:$=!1,gzipCompression:T=f===t.PacketType.JSON,rereference:b=!1,schema:x,autoFlatten:A=!1,quantized:q,min:M,max:O}=a;if(!i)throw new Error("Packet tag is required");if(y||r?(w=b?1:0,d=s):void 0===w&&(w=f===t.PacketType.NONE?0:d),b&&0===w)throw new Error("Rereference requires a data minimum above zero");if(a.replay&&P)throw new Error(`Packet "${i}" cannot combine replay with batching`);if(c(f))throw new Error(`Invalid packet type: ${f}`);if(h(x,i),n&&!x)throw new Error(`Packet "${i}" constructor requires schema`);if(n&&(0,o.RegisterPacketConstructor)(n),A&&(!x||0===x.length))throw new Error(`Packet "${i}" autoFlatten requires schema`);if(x&&!A&&w===d&&x.length!==d)throw new Error(`Packet "${i}" schema length must match its fixed value count (${d})`);!function(e,t,a,r,n){if(void 0!==a&&void 0!==r&&a>r)throw new Error(`Packet "${n}" min cannot exceed max`);if((t||void 0!==a||void 0!==r)&&!m.has(e))throw new Error(`Packet "${n}" numeric options require a numeric packet type`);if(t&&(!Number.isFinite(t.scale)||t.scale<=0))throw new Error(`Packet "${n}" quantization scale must be positive and finite`)}(f,q,M,O,i);const z=new e.PacketSchema(!1,f,$,u(w,d),l(d),p(E),g,A,b,P,v,T,x,q,M,O,a._group,n?.name,a.replay??!1);return new e.Packet(i,z,k,S,!1)}function h(e,t){if(e){if(!e.length||e.some(e=>"string"!=typeof e||!e))throw new Error(`Packet "${t}" schema must contain non-empty field names`);if(new Set(e).size!==e.length)throw new Error(`Packet "${t}" schema fields must be unique`)}}const m=new Set([t.PacketType.BYTES,t.PacketType.UBYTES,t.PacketType.SHORTS,t.PacketType.USHORTS,t.PacketType.VARINT,t.PacketType.UVARINT,t.PacketType.DELTAS,t.PacketType.FLOATS,t.PacketType.DOUBLES]);function d(e){if(null==e)return[];const t=e[0];if(null==t)return[];if(!Array.isArray(t))throw new Error(`Cannot flatten array: ${e}`);return t.map((t,a)=>e.map(e=>e[a]))}
|