sonic-ws 1.0.5 → 1.0.6
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/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/ws/client/core/ClientCore.d.ts +2 -2
- package/dist/ws/packets/PacketProcessors.js +30 -32
- package/dist/ws/packets/Packets.d.ts +3 -3
- package/dist/ws/packets/Packets.js +2 -1
- package/dist/ws/server/SonicWSConnection.d.ts +13 -8
- package/dist/ws/server/SonicWSConnection.js +16 -5
- package/dist/ws/server/SonicWSServer.d.ts +28 -15
- package/dist/ws/server/SonicWSServer.js +31 -1
- package/dist/ws/util/enums/EnumHandler.d.ts +2 -1
- package/dist/ws/util/enums/EnumHandler.js +4 -0
- package/dist/ws/util/enums/EnumType.d.ts +8 -2
- package/dist/ws/util/enums/EnumType.js +10 -0
- package/dist/ws/util/packets/BatchHelper.d.ts +3 -3
- package/dist/ws/util/packets/CompressionUtil.d.ts +4 -0
- package/dist/ws/util/packets/CompressionUtil.js +36 -6
- package/dist/ws/util/packets/PacketHolder.d.ts +8 -7
- package/dist/ws/util/packets/PacketUtils.d.ts +5 -4
- package/dist/ws/util/packets/RateHandler.d.ts +3 -3
- package/package.json +1 -1
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
|
@@ -18,7 +18,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
18
18
|
exports.SERVER_SUFFIX_NUMS = exports.SERVER_SUFFIX = exports.VERSION = void 0;
|
|
19
19
|
const StringUtil_1 = require("./ws/util/StringUtil");
|
|
20
20
|
/** Current protocol version */
|
|
21
|
-
exports.VERSION =
|
|
21
|
+
exports.VERSION = 13;
|
|
22
22
|
/** Server data suffix */
|
|
23
23
|
exports.SERVER_SUFFIX = "SWS";
|
|
24
24
|
/** Server data suffix in array */
|
|
@@ -14,8 +14,8 @@ export declare abstract class SonicWSCore implements Connection {
|
|
|
14
14
|
protected preListen: {
|
|
15
15
|
[key: string]: Array<(data: any[]) => void>;
|
|
16
16
|
} | null;
|
|
17
|
-
protected clientPackets: PacketHolder
|
|
18
|
-
protected serverPackets: PacketHolder
|
|
17
|
+
protected clientPackets: PacketHolder;
|
|
18
|
+
protected serverPackets: PacketHolder;
|
|
19
19
|
private pastKeys;
|
|
20
20
|
private readyListeners;
|
|
21
21
|
private batcher;
|
|
@@ -37,7 +37,7 @@ function SHORT_LEN(cap, min) {
|
|
|
37
37
|
function VARINT_VERIF(cap, min) {
|
|
38
38
|
return (data) => {
|
|
39
39
|
if (data.length == 0)
|
|
40
|
-
return false;
|
|
40
|
+
return min <= 0 ? [] : false;
|
|
41
41
|
let sectors = 0, i = 0, computed = [];
|
|
42
42
|
while (i < data.length) {
|
|
43
43
|
const [off, varint] = (0, CompressionUtil_1.readVarInt)(data, i);
|
|
@@ -96,20 +96,22 @@ function createValidator(type, dataCap, dataMin, packet) {
|
|
|
96
96
|
}
|
|
97
97
|
;
|
|
98
98
|
case PacketType_1.PacketType.STRINGS_ASCII: return (data) => {
|
|
99
|
-
let
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
99
|
+
let index = 0;
|
|
100
|
+
const [offCount, stringCount] = (0, CompressionUtil_1.readVarInt)(data, index);
|
|
101
|
+
index = offCount;
|
|
102
|
+
if (stringCount < dataMin || stringCount > dataCap)
|
|
103
|
+
return false;
|
|
104
|
+
const lengths = [];
|
|
105
|
+
let totalLength = 0;
|
|
106
|
+
for (let i = 0; i < stringCount; i++) {
|
|
107
|
+
const [offLen, strLen] = (0, CompressionUtil_1.readVarInt)(data, index);
|
|
108
|
+
index = offLen;
|
|
109
|
+
lengths.push(strLen);
|
|
110
|
+
totalLength += strLen;
|
|
109
111
|
}
|
|
110
|
-
if (
|
|
112
|
+
if (index + Math.ceil(totalLength / 8) > data.length)
|
|
111
113
|
return false;
|
|
112
|
-
return
|
|
114
|
+
return [stringCount, lengths, index];
|
|
113
115
|
};
|
|
114
116
|
case PacketType_1.PacketType.STRINGS_UTF16: return (data) => {
|
|
115
117
|
let sectors = 0, index = 0, computed = [];
|
|
@@ -154,14 +156,17 @@ function createReceiveProcessor(type, enumData, cap) {
|
|
|
154
156
|
const pkg = enumData[index];
|
|
155
157
|
return Array.from(data).map(code => pkg.values[code]);
|
|
156
158
|
};
|
|
157
|
-
case PacketType_1.PacketType.STRINGS_ASCII: return (data,
|
|
159
|
+
case PacketType_1.PacketType.STRINGS_ASCII: return (data, validationResult) => {
|
|
160
|
+
const [stringCount, lengths, dataStart] = validationResult;
|
|
161
|
+
const bitString = (0, CompressionUtil_1.bytesToBits)(data.subarray(dataStart));
|
|
162
|
+
const decoded = (0, CompressionUtil_1.decodeHuffman)(bitString);
|
|
163
|
+
if (!decoded)
|
|
164
|
+
return [];
|
|
158
165
|
const strings = [];
|
|
159
|
-
let
|
|
160
|
-
for (let i = 0; i <
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const str = (0, BufferUtil_1.as8String)(data.subarray(index, index += strLen));
|
|
164
|
-
strings.push(str);
|
|
166
|
+
let offset = 0;
|
|
167
|
+
for (let i = 0; i < stringCount; i++) {
|
|
168
|
+
strings.push(decoded.slice(offset, offset + lengths[i]));
|
|
169
|
+
offset += lengths[i];
|
|
165
170
|
}
|
|
166
171
|
return strings;
|
|
167
172
|
};
|
|
@@ -188,18 +193,11 @@ function createSendProcessor(type) {
|
|
|
188
193
|
case PacketType_1.PacketType.DOUBLES: return (doubles) => doubles.map(CompressionUtil_1.convertDouble).flat();
|
|
189
194
|
case PacketType_1.PacketType.BOOLEANS: return (bools) => (0, ArrayUtil_1.splitArray)(bools, 8).map((bools) => (0, CompressionUtil_1.compressBools)(bools)).flat();
|
|
190
195
|
case PacketType_1.PacketType.STRINGS_ASCII: return (strings) => {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const codes = (0, StringUtil_1.processCharCodes)(string);
|
|
197
|
-
const highCode = codes.find(x => x > CompressionUtil_1.MAX_BYTE);
|
|
198
|
-
if (highCode)
|
|
199
|
-
throw new Error(`Cannot store code ${highCode} (${String.fromCharCode(highCode)}) in a UTF-8 String! Use STRINGS_UTF16.`);
|
|
200
|
-
res.push(...codes);
|
|
201
|
-
}
|
|
202
|
-
return res;
|
|
196
|
+
return [
|
|
197
|
+
...(0, CompressionUtil_1.convertVarInt)(strings.length),
|
|
198
|
+
...strings.map(str => (0, CompressionUtil_1.convertVarInt)(str.length)).flat(),
|
|
199
|
+
...(0, CompressionUtil_1.encodeHuffman)(strings.reduce((a, b) => a + String(b), "")),
|
|
200
|
+
];
|
|
203
201
|
};
|
|
204
202
|
case PacketType_1.PacketType.STRINGS_UTF16: return (strings) => {
|
|
205
203
|
const res = [];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { EnumPackage } from "../util/enums/EnumType";
|
|
2
2
|
import { SonicWSConnection } from "../server/SonicWSConnection";
|
|
3
3
|
import { PacketType } from "./PacketType";
|
|
4
|
-
export type ValidatorFunction = ((socket: SonicWSConnection
|
|
4
|
+
export type ValidatorFunction = ((socket: SonicWSConnection, values: any) => boolean) | null;
|
|
5
5
|
export type ConvertType<T> = T extends EnumPackage ? PacketType.ENUMS : T;
|
|
6
6
|
type ImpactType<T extends (PacketType | readonly PacketType[]), K> = T extends PacketType[] ? K[] : K;
|
|
7
7
|
export declare class Packet<T extends (PacketType | readonly PacketType[])> {
|
|
@@ -26,9 +26,9 @@ export declare class Packet<T extends (PacketType | readonly PacketType[])> {
|
|
|
26
26
|
processReceive: (data: Uint8Array, validationResult: any) => any;
|
|
27
27
|
processSend: (data: any[]) => number[];
|
|
28
28
|
validate: (data: Uint8Array) => boolean;
|
|
29
|
-
customValidator: ((socket: SonicWSConnection
|
|
29
|
+
customValidator: ((socket: SonicWSConnection, ...values: any[]) => boolean) | null;
|
|
30
30
|
constructor(tag: string, schema: PacketSchema<T>, customValidator: ValidatorFunction, enabled: boolean, client: boolean);
|
|
31
|
-
listen(value: Uint8Array, socket: SonicWSConnection
|
|
31
|
+
listen(value: Uint8Array, socket: SonicWSConnection | null): [processed: any, flatten: boolean] | string;
|
|
32
32
|
serialize(): number[];
|
|
33
33
|
private static readVarInts;
|
|
34
34
|
static deserialize(data: Uint8Array, offset: number, client: boolean): [packet: Packet<any>, offset: number];
|
|
@@ -92,7 +92,8 @@ class Packet {
|
|
|
92
92
|
listen(value, socket) {
|
|
93
93
|
try {
|
|
94
94
|
const validationResult = this.validate(value);
|
|
95
|
-
|
|
95
|
+
// holy shit i used === to fix another bug
|
|
96
|
+
if (!this.client && validationResult === false)
|
|
96
97
|
return "Invalid packet";
|
|
97
98
|
const processed = this.processReceive(value, validationResult);
|
|
98
99
|
const useableData = this.autoFlatten ? (0, PacketUtils_1.UnFlattenData)(processed) : processed;
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import * as WS from 'ws';
|
|
2
|
-
import {
|
|
2
|
+
import { SonicWSServer } from './SonicWSServer';
|
|
3
3
|
import { Packet } from '../packets/Packets';
|
|
4
4
|
import { Connection } from '../Connection';
|
|
5
|
-
|
|
6
|
-
export declare class SonicWSConnection<ClientTypes extends PacketTypings, ServerTypes extends PacketTypings> implements Connection {
|
|
5
|
+
export declare class SonicWSConnection implements Connection {
|
|
7
6
|
/** Raw 'ws' library socket */
|
|
8
7
|
socket: WS.WebSocket;
|
|
9
8
|
private host;
|
|
@@ -21,7 +20,7 @@ export declare class SonicWSConnection<ClientTypes extends PacketTypings, Server
|
|
|
21
20
|
/** The index of the connection; unique for all connected, recycles old disconnected ids. Should be safe for INTS_C unless you have more than 27,647 connected at once. */
|
|
22
21
|
id: number;
|
|
23
22
|
_timers: Record<number, number>;
|
|
24
|
-
constructor(socket: WS.WebSocket, host: SonicWSServer
|
|
23
|
+
constructor(socket: WS.WebSocket, host: SonicWSServer, id: number, handshakePacket: string | null, clientRateLimit: number, serverRateLimit: number);
|
|
25
24
|
private parseData;
|
|
26
25
|
private handshakeHandler;
|
|
27
26
|
private invalidPacket;
|
|
@@ -46,17 +45,17 @@ export declare class SonicWSConnection<ClientTypes extends PacketTypings, Server
|
|
|
46
45
|
* Listens for when the connection closes
|
|
47
46
|
* @param listener Called when it closes
|
|
48
47
|
*/
|
|
49
|
-
on_close(listener: (code: number, reason:
|
|
48
|
+
on_close(listener: (code: number, reason: string) => void): void;
|
|
50
49
|
/**
|
|
51
50
|
* Listens for a packet
|
|
52
51
|
* @param tag The tag of the key to listen for
|
|
53
52
|
* @param listener A function to listen for it
|
|
54
53
|
*/
|
|
55
|
-
on
|
|
54
|
+
on(tag: string, listener: (...values: any) => void): void;
|
|
56
55
|
/**
|
|
57
56
|
* For internal use.
|
|
58
57
|
*/
|
|
59
|
-
send_processed(code: number, data: number[], packet: Packet<
|
|
58
|
+
send_processed(code: number, data: number[], packet: Packet<any>): void;
|
|
60
59
|
/**
|
|
61
60
|
* Sends a packet with the tag and values
|
|
62
61
|
* @param tag The tag to send
|
|
@@ -68,7 +67,7 @@ export declare class SonicWSConnection<ClientTypes extends PacketTypings, Server
|
|
|
68
67
|
* @param tag The tag to send
|
|
69
68
|
* @param values The values to send
|
|
70
69
|
*/
|
|
71
|
-
broadcastFiltered(tag: string, filter: (socket: SonicWSConnection
|
|
70
|
+
broadcastFiltered(tag: string, filter: (socket: SonicWSConnection) => boolean, ...values: any[]): void;
|
|
72
71
|
/**
|
|
73
72
|
* Broadcasts a packet to all other users connected
|
|
74
73
|
* @param tag The tag to send
|
|
@@ -85,4 +84,10 @@ export declare class SonicWSConnection<ClientTypes extends PacketTypings, Server
|
|
|
85
84
|
setInterval(call: () => void, time: number): number;
|
|
86
85
|
clearTimeout(id: number): void;
|
|
87
86
|
clearInterval(id: number): void;
|
|
87
|
+
/**
|
|
88
|
+
* Tags the socket with a key
|
|
89
|
+
* @param tag The tag to add
|
|
90
|
+
* @param replace If it should replace a previous tag; defaults to true. If using false, you can add multiple tags.
|
|
91
|
+
*/
|
|
92
|
+
tag(tag: string, replace?: boolean): void;
|
|
88
93
|
}
|
|
@@ -67,7 +67,7 @@ class SonicWSConnection {
|
|
|
67
67
|
handshakedMessageLambda = (data) => {
|
|
68
68
|
const parsed = this.parseData(data);
|
|
69
69
|
if (parsed == null)
|
|
70
|
-
return;
|
|
70
|
+
return this.socket.close(4004);
|
|
71
71
|
if (parsed[0] == this.handshakePacket)
|
|
72
72
|
return this.socket.close(4005);
|
|
73
73
|
this.messageHandler(parsed);
|
|
@@ -143,7 +143,7 @@ class SonicWSConnection {
|
|
|
143
143
|
handshakeHandler(data) {
|
|
144
144
|
const parsed = this.parseData(data);
|
|
145
145
|
if (parsed == null)
|
|
146
|
-
return;
|
|
146
|
+
return this.socket.close(4004);
|
|
147
147
|
if (parsed[0] != this.handshakePacket) {
|
|
148
148
|
this.socket.close(4004);
|
|
149
149
|
return;
|
|
@@ -154,6 +154,7 @@ class SonicWSConnection {
|
|
|
154
154
|
this.handshakeComplete = true;
|
|
155
155
|
}
|
|
156
156
|
invalidPacket(listened) {
|
|
157
|
+
console.log("Closure cause", listened);
|
|
157
158
|
this.socket.close(4003, listened);
|
|
158
159
|
}
|
|
159
160
|
listenPacket(data, tag) {
|
|
@@ -202,7 +203,7 @@ class SonicWSConnection {
|
|
|
202
203
|
* @param listener Called when it closes
|
|
203
204
|
*/
|
|
204
205
|
on_close(listener) {
|
|
205
|
-
this.socket.on('close', listener);
|
|
206
|
+
this.socket.on('close', (code, reason) => listener(code, String(reason)));
|
|
206
207
|
}
|
|
207
208
|
/**
|
|
208
209
|
* Listens for a packet
|
|
@@ -258,8 +259,10 @@ class SonicWSConnection {
|
|
|
258
259
|
}
|
|
259
260
|
/* JSDocs in Connection.ts class */
|
|
260
261
|
raw_send(data) {
|
|
261
|
-
if (this.isClosed())
|
|
262
|
-
|
|
262
|
+
if (this.isClosed()) {
|
|
263
|
+
console.warn("WARN! Connection already closed when trying to send message!", this.id, (0, BufferUtil_1.stringifyBuffer)(data));
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
263
266
|
if (this.rater.trigger(SERVER_RATELIMIT_TAG))
|
|
264
267
|
return;
|
|
265
268
|
if (this.print)
|
|
@@ -289,5 +292,13 @@ class SonicWSConnection {
|
|
|
289
292
|
clearInterval(id) {
|
|
290
293
|
this.clearTimeout(id);
|
|
291
294
|
}
|
|
295
|
+
/**
|
|
296
|
+
* Tags the socket with a key
|
|
297
|
+
* @param tag The tag to add
|
|
298
|
+
* @param replace If it should replace a previous tag; defaults to true. If using false, you can add multiple tags.
|
|
299
|
+
*/
|
|
300
|
+
tag(tag, replace = true) {
|
|
301
|
+
this.host.tag(this, tag, replace);
|
|
302
|
+
}
|
|
292
303
|
}
|
|
293
304
|
exports.SonicWSConnection = SonicWSConnection;
|
|
@@ -6,33 +6,35 @@ import { PacketType } from '../packets/PacketType';
|
|
|
6
6
|
/**
|
|
7
7
|
* Sonic WS Server Options
|
|
8
8
|
*/
|
|
9
|
-
export type SonicServerOptions
|
|
9
|
+
export type SonicServerOptions = {
|
|
10
10
|
/** An array of packets the client can send and server can listen for; using CreatePacket(), CreateObjPacket(), and CreateEnumPacket() */
|
|
11
|
-
readonly clientPackets?:
|
|
11
|
+
readonly clientPackets?: PacketTypings;
|
|
12
12
|
/** An array of packets the server can send and client can listen for; using CreatePacket(), CreateObjPacket(), and CreateEnumPacket() */
|
|
13
|
-
readonly serverPackets?:
|
|
13
|
+
readonly serverPackets?: PacketTypings;
|
|
14
14
|
/** Default WS Options */
|
|
15
15
|
readonly websocketOptions?: WS.ServerOptions;
|
|
16
16
|
};
|
|
17
17
|
export type PacketTypings = readonly Packet<PacketType | readonly PacketType[]>[];
|
|
18
|
-
export declare class SonicWSServer
|
|
18
|
+
export declare class SonicWSServer {
|
|
19
19
|
private wss;
|
|
20
20
|
private availableIds;
|
|
21
21
|
private lastId;
|
|
22
22
|
private connectListeners;
|
|
23
|
-
clientPackets: PacketHolder
|
|
24
|
-
serverPackets: PacketHolder
|
|
25
|
-
connections:
|
|
23
|
+
clientPackets: PacketHolder;
|
|
24
|
+
serverPackets: PacketHolder;
|
|
25
|
+
connections: SonicWSConnection[];
|
|
26
26
|
private connectionMap;
|
|
27
27
|
private clientRateLimit;
|
|
28
28
|
private serverRateLimit;
|
|
29
29
|
private handshakePacket;
|
|
30
|
+
tags: Map<SonicWSConnection, Set<String>>;
|
|
31
|
+
tagsInv: Map<String, Set<SonicWSConnection>>;
|
|
30
32
|
/**
|
|
31
33
|
* Initializes and hosts a websocket with sonic protocol
|
|
32
34
|
* Rate limits can be set with wss.setClientRateLimit(x) and wss.setServerRateLimit(x); it is defaulted at 500/second per both
|
|
33
35
|
* @param settings Sonic Server Options such as schema data for client and server packets, alongside websocket options
|
|
34
36
|
*/
|
|
35
|
-
constructor(settings: SonicServerOptions
|
|
37
|
+
constructor(settings: SonicServerOptions);
|
|
36
38
|
private generateSocketID;
|
|
37
39
|
/**
|
|
38
40
|
* Requires each client to send this packet upon initialization
|
|
@@ -79,7 +81,7 @@ export declare class SonicWSServer<ClientTypes extends PacketTypings, ServerType
|
|
|
79
81
|
* Listens for whenever a client connects
|
|
80
82
|
* @param runner Called when ready
|
|
81
83
|
*/
|
|
82
|
-
on_connect(runner: (client: SonicWSConnection
|
|
84
|
+
on_connect(runner: (client: SonicWSConnection) => void): void;
|
|
83
85
|
/**
|
|
84
86
|
* Listens for whenever the server is ready
|
|
85
87
|
* @param runner Called when ready
|
|
@@ -90,13 +92,20 @@ export declare class SonicWSServer<ClientTypes extends PacketTypings, ServerType
|
|
|
90
92
|
* @param callback Called when server closes
|
|
91
93
|
*/
|
|
92
94
|
shutdown(callback: (err?: Error) => void): void;
|
|
95
|
+
/**
|
|
96
|
+
* Broadcasts a packet to tagged users; this is fast as it is a record rather than looping and filtering
|
|
97
|
+
* @param tag The tag to send packets to
|
|
98
|
+
* @param packetTag Packet tag to send
|
|
99
|
+
* @param values Values to send
|
|
100
|
+
*/
|
|
101
|
+
broadcastTagged(tag: string, packetTag: string, ...values: any): void;
|
|
93
102
|
/**
|
|
94
103
|
* Broadcasts a packet to all users connected, but with a filter
|
|
95
104
|
* @param tag The tag to send
|
|
96
105
|
* @param filter The filter for who to send to
|
|
97
106
|
* @param values The values to send
|
|
98
107
|
*/
|
|
99
|
-
broadcastFiltered(tag: string, filter: (socket: SonicWSConnection
|
|
108
|
+
broadcastFiltered(tag: string, filter: (socket: SonicWSConnection) => boolean, ...values: any): void;
|
|
100
109
|
/**
|
|
101
110
|
* Broadcasts a packet to all users connected
|
|
102
111
|
* @param tag The tag to send
|
|
@@ -106,18 +115,22 @@ export declare class SonicWSServer<ClientTypes extends PacketTypings, ServerType
|
|
|
106
115
|
/**
|
|
107
116
|
* @returns All users connected to the socket
|
|
108
117
|
*/
|
|
109
|
-
getConnected():
|
|
118
|
+
getConnected(): SonicWSConnection[];
|
|
110
119
|
/**
|
|
111
120
|
* @param id The socket id
|
|
112
121
|
* @returns The socket
|
|
113
122
|
*/
|
|
114
|
-
getSocket(id: number): SonicWSConnection
|
|
123
|
+
getSocket(id: number): SonicWSConnection;
|
|
115
124
|
/**
|
|
116
125
|
* Closes a socket by id
|
|
117
126
|
* @param id The socket id
|
|
118
127
|
*/
|
|
119
128
|
closeSocket(id: number, code?: number, reason?: string | Buffer): void;
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
129
|
+
/**
|
|
130
|
+
* Tags the socket with a key
|
|
131
|
+
* @param socket The socket to tag
|
|
132
|
+
* @param tag The tag to add
|
|
133
|
+
* @param replace If it should replace a previous tag; defaults to true. If using false, you can add multiple tags.
|
|
134
|
+
*/
|
|
135
|
+
tag(socket: SonicWSConnection, tag: string, replace?: boolean): void;
|
|
123
136
|
}
|
|
@@ -67,6 +67,8 @@ class SonicWSServer {
|
|
|
67
67
|
clientRateLimit = 500;
|
|
68
68
|
serverRateLimit = 500;
|
|
69
69
|
handshakePacket = null;
|
|
70
|
+
tags = new Map();
|
|
71
|
+
tagsInv = new Map();
|
|
70
72
|
/**
|
|
71
73
|
* Initializes and hosts a websocket with sonic protocol
|
|
72
74
|
* Rate limits can be set with wss.setClientRateLimit(x) and wss.setServerRateLimit(x); it is defaulted at 500/second per both
|
|
@@ -200,6 +202,18 @@ class SonicWSServer {
|
|
|
200
202
|
shutdown(callback) {
|
|
201
203
|
this.wss.close(callback);
|
|
202
204
|
}
|
|
205
|
+
/**
|
|
206
|
+
* Broadcasts a packet to tagged users; this is fast as it is a record rather than looping and filtering
|
|
207
|
+
* @param tag The tag to send packets to
|
|
208
|
+
* @param packetTag Packet tag to send
|
|
209
|
+
* @param values Values to send
|
|
210
|
+
*/
|
|
211
|
+
broadcastTagged(tag, packetTag, ...values) {
|
|
212
|
+
if (!this.tagsInv.has(tag))
|
|
213
|
+
return;
|
|
214
|
+
const data = (0, PacketUtils_1.processPacket)(this.serverPackets, packetTag, values);
|
|
215
|
+
this.tagsInv.get(tag).forEach(conn => conn.send_processed(...data));
|
|
216
|
+
}
|
|
203
217
|
/**
|
|
204
218
|
* Broadcasts a packet to all users connected, but with a filter
|
|
205
219
|
* @param tag The tag to send
|
|
@@ -208,7 +222,6 @@ class SonicWSServer {
|
|
|
208
222
|
*/
|
|
209
223
|
broadcastFiltered(tag, filter, ...values) {
|
|
210
224
|
const data = (0, PacketUtils_1.processPacket)(this.serverPackets, tag, values);
|
|
211
|
-
// weird type bug here so i make it as any
|
|
212
225
|
this.connections.filter(filter).forEach(conn => conn.send_processed(...data));
|
|
213
226
|
}
|
|
214
227
|
/**
|
|
@@ -239,5 +252,22 @@ class SonicWSServer {
|
|
|
239
252
|
closeSocket(id, code = 1000, reason) {
|
|
240
253
|
this.getSocket(id).close(code, reason);
|
|
241
254
|
}
|
|
255
|
+
/**
|
|
256
|
+
* Tags the socket with a key
|
|
257
|
+
* @param socket The socket to tag
|
|
258
|
+
* @param tag The tag to add
|
|
259
|
+
* @param replace If it should replace a previous tag; defaults to true. If using false, you can add multiple tags.
|
|
260
|
+
*/
|
|
261
|
+
tag(socket, tag, replace = true) {
|
|
262
|
+
if (!this.tags.get(socket))
|
|
263
|
+
this.tags.set(socket, new Set());
|
|
264
|
+
if (!this.tagsInv.get(tag))
|
|
265
|
+
this.tagsInv.set(tag, new Set());
|
|
266
|
+
if (replace) {
|
|
267
|
+
this.tags.get(socket).forEach(v => this.tagsInv.get(v)?.delete(socket));
|
|
268
|
+
}
|
|
269
|
+
this.tags.get(socket).add(tag);
|
|
270
|
+
this.tagsInv.get(tag).add(socket);
|
|
271
|
+
}
|
|
242
272
|
}
|
|
243
273
|
exports.SonicWSServer = SonicWSServer;
|
|
@@ -9,7 +9,7 @@ export declare const SET_PACKAGES: Record<string, EnumPackage>;
|
|
|
9
9
|
* @param values The possible values of the enum
|
|
10
10
|
* @returns A packaged enum
|
|
11
11
|
*/
|
|
12
|
-
export declare function DefineEnum(tag: string, values: any[]): EnumPackage;
|
|
12
|
+
export declare function DefineEnum(tag: string, values: any[] | readonly any[]): EnumPackage;
|
|
13
13
|
/**
|
|
14
14
|
* Wraps an enum into a transmittable format
|
|
15
15
|
* @param tag The tag of the enum
|
|
@@ -17,3 +17,4 @@ export declare function DefineEnum(tag: string, values: any[]): EnumPackage;
|
|
|
17
17
|
* @returns A transmittable enum value
|
|
18
18
|
*/
|
|
19
19
|
export declare function WrapEnum(tag: string, value: any): number;
|
|
20
|
+
export declare function DeWrapEnum(tag: string, value: number): any;
|
|
@@ -18,6 +18,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
18
18
|
exports.SET_PACKAGES = exports.ENUM_KEY_TO_TAG = exports.ENUM_TAG_TO_KEY = exports.MAX_ENUM_SIZE = void 0;
|
|
19
19
|
exports.DefineEnum = DefineEnum;
|
|
20
20
|
exports.WrapEnum = WrapEnum;
|
|
21
|
+
exports.DeWrapEnum = DeWrapEnum;
|
|
21
22
|
const CompressionUtil_1 = require("../packets/CompressionUtil");
|
|
22
23
|
const EnumType_1 = require("./EnumType");
|
|
23
24
|
exports.MAX_ENUM_SIZE = CompressionUtil_1.MAX_BYTE;
|
|
@@ -54,3 +55,6 @@ function WrapEnum(tag, value) {
|
|
|
54
55
|
throw new Error(`Value "${value}" does not exist in enum "${tag}"`);
|
|
55
56
|
return exports.ENUM_TAG_TO_KEY[tag][value];
|
|
56
57
|
}
|
|
58
|
+
function DeWrapEnum(tag, value) {
|
|
59
|
+
return exports.ENUM_KEY_TO_TAG[tag][value];
|
|
60
|
+
}
|
|
@@ -2,7 +2,13 @@ export declare const TYPE_CONVERSION_MAP: Record<number, (data: string) => strin
|
|
|
2
2
|
export type EnumValue = string | number | boolean | undefined | null;
|
|
3
3
|
export declare class EnumPackage {
|
|
4
4
|
tag: string;
|
|
5
|
-
values: EnumValue[];
|
|
6
|
-
constructor(tag: string, values: any[]);
|
|
5
|
+
values: EnumValue[] | readonly EnumValue[];
|
|
6
|
+
constructor(tag: string, values: any[] | readonly any[]);
|
|
7
7
|
serialize(): number[];
|
|
8
|
+
/**
|
|
9
|
+
* Wraps a value with this enum package
|
|
10
|
+
* @param value Value to wrap
|
|
11
|
+
* @returns Network encoded value
|
|
12
|
+
*/
|
|
13
|
+
wrap(value: any): number;
|
|
8
14
|
}
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
18
|
exports.EnumPackage = exports.TYPE_CONVERSION_MAP = void 0;
|
|
19
19
|
const StringUtil_1 = require("../StringUtil");
|
|
20
|
+
const EnumHandler_1 = require("./EnumHandler");
|
|
20
21
|
const TYPE_INDEX_MAP = {
|
|
21
22
|
'string': 0,
|
|
22
23
|
'number': 1,
|
|
@@ -43,6 +44,7 @@ class EnumPackage {
|
|
|
43
44
|
constructor(tag, values) {
|
|
44
45
|
this.tag = tag;
|
|
45
46
|
this.values = values;
|
|
47
|
+
this.wrap = this.wrap.bind(this);
|
|
46
48
|
}
|
|
47
49
|
serialize() {
|
|
48
50
|
const tag = (0, StringUtil_1.processCharCodes)(this.tag);
|
|
@@ -57,5 +59,13 @@ class EnumPackage {
|
|
|
57
59
|
]).flat(),
|
|
58
60
|
];
|
|
59
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Wraps a value with this enum package
|
|
64
|
+
* @param value Value to wrap
|
|
65
|
+
* @returns Network encoded value
|
|
66
|
+
*/
|
|
67
|
+
wrap(value) {
|
|
68
|
+
return (0, EnumHandler_1.WrapEnum)(this.tag, value);
|
|
69
|
+
}
|
|
60
70
|
}
|
|
61
71
|
exports.EnumPackage = EnumPackage;
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { Connection } from "../../Connection";
|
|
2
2
|
import { Packet } from "../../packets/Packets";
|
|
3
|
-
import {
|
|
3
|
+
import { SonicWSConnection } from "../../server/SonicWSConnection";
|
|
4
4
|
import { PacketHolder } from "./PacketHolder";
|
|
5
5
|
export declare class BatchHelper {
|
|
6
6
|
private batchTimes;
|
|
7
7
|
private batchTimeouts;
|
|
8
8
|
private batchedData;
|
|
9
9
|
private conn;
|
|
10
|
-
registerSendPackets(packetHolder: PacketHolder
|
|
10
|
+
registerSendPackets(packetHolder: PacketHolder, conn: Connection): void;
|
|
11
11
|
private initiateBatch;
|
|
12
12
|
private startBatch;
|
|
13
13
|
batchPacket(code: number, data: number[]): void;
|
|
14
|
-
static unravelBatch(packet: Packet<any>, data: Uint8Array, socket:
|
|
14
|
+
static unravelBatch(packet: Packet<any>, data: Uint8Array, socket: SonicWSConnection | null): any[] | string;
|
|
15
15
|
}
|
|
@@ -35,3 +35,7 @@ export declare function mapShort_ZZ(short: number): SHORT_BITS;
|
|
|
35
35
|
export declare function convertVarInt(num: number): number[];
|
|
36
36
|
export declare function readVarInt(arr: number[] | Uint8Array, off: number): [offset: number, number: number];
|
|
37
37
|
export declare function deconvertVarInts(arr: Uint8Array | number[]): number[];
|
|
38
|
+
export declare function bytesToBits(bytes: ArrayLike<number>): string;
|
|
39
|
+
export declare function bitsToBytes(bitString: string): Uint8Array;
|
|
40
|
+
export declare function encodeHuffman(text: string): Uint8Array;
|
|
41
|
+
export declare function decodeHuffman(bits: string): string;
|
|
@@ -32,6 +32,10 @@ exports.mapShort_ZZ = mapShort_ZZ;
|
|
|
32
32
|
exports.convertVarInt = convertVarInt;
|
|
33
33
|
exports.readVarInt = readVarInt;
|
|
34
34
|
exports.deconvertVarInts = deconvertVarInts;
|
|
35
|
+
exports.bytesToBits = bytesToBits;
|
|
36
|
+
exports.bitsToBytes = bitsToBytes;
|
|
37
|
+
exports.encodeHuffman = encodeHuffman;
|
|
38
|
+
exports.decodeHuffman = decodeHuffman;
|
|
35
39
|
const ArrayUtil_1 = require("../ArrayUtil");
|
|
36
40
|
// this shit is so complex so i commented it...
|
|
37
41
|
// the highest 8-bit
|
|
@@ -259,14 +263,10 @@ function deconvertDouble(bytes) {
|
|
|
259
263
|
}
|
|
260
264
|
// zig_zag
|
|
261
265
|
function mapZigZag(n) {
|
|
262
|
-
return (n << 1)
|
|
263
|
-
^
|
|
264
|
-
(n >> 15); // then xor the sign away
|
|
266
|
+
return ((n << 1) ^ (n >> 31));
|
|
265
267
|
}
|
|
266
268
|
function demapZigZag(n) {
|
|
267
|
-
return (n >>> 1)
|
|
268
|
-
^
|
|
269
|
-
-(n & 1); // flips bits to give negative back
|
|
269
|
+
return (n >>> 1) ^ -((n & 1));
|
|
270
270
|
}
|
|
271
271
|
function demapShort_ZZ(short) {
|
|
272
272
|
return demapZigZag(fromShort(short));
|
|
@@ -302,3 +302,33 @@ function deconvertVarInts(arr) {
|
|
|
302
302
|
}
|
|
303
303
|
return res;
|
|
304
304
|
}
|
|
305
|
+
function bytesToBits(bytes) {
|
|
306
|
+
return Array.from(bytes).map(b => b.toString(2).padStart(8, '0')).join('');
|
|
307
|
+
}
|
|
308
|
+
function bitsToBytes(bitString) {
|
|
309
|
+
const bytes = [];
|
|
310
|
+
for (let i = 0; i < bitString.length; i += 8) {
|
|
311
|
+
const byte = bitString.slice(i, i + 8).padEnd(8, '0');
|
|
312
|
+
bytes.push(parseInt(byte, 2));
|
|
313
|
+
}
|
|
314
|
+
return new Uint8Array(bytes);
|
|
315
|
+
}
|
|
316
|
+
const codeToChar = { "1000001": "w", "1000010": "m", "1000100": "u", "1000101": "c", "1000110": "l", "1000111": "d", "1001001": "r", "1001010": "h", "1001100": "s", "1001101": "n", "1001110": "i", "1001111": "o", "1010001": "a", "1010010": "t", "1010100": "e", "1010101": "", "10000000": "", "10000001": "", "10000111": "", "10010000": "
", "10010001": "", "10010111": "", "10100000": "", "10100001": "", "10100111": "", "10101100": "", "10101101": "~", "10101111": "}", "10110000": "|", "10110001": "{", "10110011": "`", "10110100": "_", "10110101": "^", "10110111": "]", "10111000": "", "10111001": "[", "10111011": "@", "10111100": "?", "10111101": ">", "10111111": "=", "11000000": "<", "11000001": ";", "11000011": ":", "11000100": "9", "11000101": "8", "11000111": "7", "11001000": "6", "11001001": "5", "11001011": "4", "11001100": "3", "11001101": "2", "11001111": "1", "11010000": "0", "11010001": "/", "11010011": ".", "11010100": "-", "11010101": ",", "11010111": "+", "11011000": "*", "11011001": ")", "11011011": "(", "11011100": "'", "11011101": "&", "11011111": "$", "11100001": "#", "11100010": "\"", "11100100": "!", "11100101": "\u001f", "11100110": "\u001e", "11100111": "\u001d", "11101001": "\u001c", "11101010": "\u001b", "11101100": "\u001a", "11101101": "\u0019", "11101110": "\u0018", "11101111": "\u0017", "11110001": "\u0016", "11110010": "\u0015", "11110100": "\u0014", "11110101": "\u0013", "11110110": "\u0012", "11110111": "\u0011", "11111001": "\u0010", "11111010": "\u000f", "11111100": "\u000e", "11111101": "", "11111110": "", "11111111": "", "100001100": "Ã", "100101100": "Â", "100101101": "Á", "101001100": "À", "101011100": "¿", "101011101": "¾", "101100100": "½", "101101100": "¼", "101101101": "»", "101110100": "º", "101111100": "¹", "101111101": "¸", "110000100": "·", "110001100": "¶", "110001101": "µ", "110010100": "´", "110011100": "³", "110011101": "²", "110100100": "±", "110101100": "°", "110101101": "¯", "110110100": "®", "110111100": "", "110111101": "%", "111000000": "¬", "111000001": "«", "111000111": "ª", "111010000": "©", "111010001": "¨", "111010111": "§", "111100000": "¦", "111100001": "¥", "111100111": "¤", "111110000": "£", "111110001": "¢", "111110111": "¡", "1000011010": "á", "1010011010": "à", "1010011011": "ß", "1011001010": "Þ", "1011101010": "Ý", "1011101011": "Ü", "1100001010": "Û", "1100101010": "Ú", "1100101011": "Ù", "1101001010": "Ø", "1101101010": "×", "1101101011": "Ö", "1110001100": "Õ", "1110101100": "Ô", "1110101101": "Ó", "1111001100": "Ò", "1111101100": "Ñ", "1111101101": "Ð", "00000000": " ", "00000001": "", "0000001": "\n", "0000010": "", "000001100": "Ï", "0000011010": "ç", "00000110110": "ó", "000001101110": "ù", "0000011011110": "ü", "00000110111110": "þ", "00000110111111": "ý", "00000111": "", "0000100": "\b", "0000101": "\u0007", "0000110": "\u0006", "0000111": "\u0005", "00010000": "", "00010001": "", "0001001": "\u0004", "0001010": "\u0003", "000101100": "Î", "000101101": "Í", "00010111": "", "0001100": "\u0002", "0001101": "\u0001", "0001110": "", "0001111": "Z", "00100000": "", "00100001": "", "0010001": "Q", "0010010": "X", "001001100": "Ì", "0010011010": "æ", "0010011011": "å", "00100111": "", "0010100": "J", "0010101": "K", "0010110": "V", "0010111": "B", "00110000": "", "00110001": "", "0011001": "P", "0011010": "Y", "001101100": "Ë", "001101101": "Ê", "00110111": "", "0011100": "G", "0011101": "F", "0011110": "W", "0011111": "M", "01000000": "", "01000001": "", "0100001": "U", "0100010": "C", "010001100": "É", "0100011010": "ä", "01000110110": "ò", "01000110111": "ñ", "01000111": "", "0100100": "L", "0100101": "D", "0100110": "R", "0100111": "H", "01010000": "", "01010001": "", "0101001": "S", "0101010": "N", "010101100": "È", "010101101": "Ç", "01010111": "", "0101100": "I", "0101101": "O", "0101110": "A", "0101111": "T", "01100000": "", "01100001": "", "0110001": "E", "0110010": "z", "011001100": "Æ", "0110011010": "ã", "0110011011": "â", "01100111": "", "0110100": "q", "0110101": "x", "0110110": "j", "0110111": "k", "01110000": "", "01110001": "", "0111001": "v", "0111010": "b", "011101100": "Å", "011101101": "Ä", "01110111": "", "0111100": "p", "0111101": "y", "0111110": "g", "0111111": "f", "10000110110": "ð", "100001101110": "ø", "100001101111": "÷", "10110010110": "ï", "10110010111": "î", "11000010110": "í", "110000101110": "ö", "1100001011110": "û", "1100001011111": "ú", "11010010110": "ì", "11010010111": "ë", "11100011010": "ê", "111000110110": "õ", "111000110111": "ô", "11110011010": "é", "11110011011": "è" };
|
|
317
|
+
const charToCode = Object.fromEntries(Object.entries(codeToChar).map(([code, char]) => [char, code]));
|
|
318
|
+
function encodeHuffman(text) {
|
|
319
|
+
return bitsToBytes(Array.from(text).map(char => charToCode[char]).join(""));
|
|
320
|
+
}
|
|
321
|
+
;
|
|
322
|
+
function decodeHuffman(bits) {
|
|
323
|
+
let result = '';
|
|
324
|
+
let buffer = '';
|
|
325
|
+
for (const bit of bits) {
|
|
326
|
+
buffer += bit;
|
|
327
|
+
if (codeToChar[buffer]) {
|
|
328
|
+
result += codeToChar[buffer];
|
|
329
|
+
buffer = '';
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return result;
|
|
333
|
+
}
|
|
334
|
+
;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Packet } from "../../packets/Packets";
|
|
2
|
+
import { PacketTypings } from "../../server/SonicWSServer";
|
|
2
3
|
/**
|
|
3
4
|
* Holds and maps packets to indexed keys and tags for serialization and lookup
|
|
4
5
|
*/
|
|
5
|
-
export declare class PacketHolder
|
|
6
|
+
export declare class PacketHolder {
|
|
6
7
|
/** Current key index for packet tags */
|
|
7
8
|
private key;
|
|
8
9
|
/** Maps tags to keys */
|
|
@@ -17,19 +18,19 @@ export declare class PacketHolder<Typings extends readonly Packet<any>[], K exte
|
|
|
17
18
|
* Creates a new PacketHolder with an array of packets
|
|
18
19
|
* @param packets Array of packets to register
|
|
19
20
|
*/
|
|
20
|
-
constructor(packets?:
|
|
21
|
+
constructor(packets?: PacketTypings);
|
|
21
22
|
/** Assigns a new unique key to a tag */
|
|
22
|
-
createKey(tag:
|
|
23
|
+
createKey(tag: string): void;
|
|
23
24
|
/**
|
|
24
25
|
* Registers an array of packets and assigns them keys
|
|
25
26
|
* @param packets Array of packets to register
|
|
26
27
|
*/
|
|
27
|
-
holdPackets(packets:
|
|
28
|
+
holdPackets(packets: PacketTypings): void;
|
|
28
29
|
/**
|
|
29
30
|
* Returns the numeric key for a given tag
|
|
30
31
|
* @param tag The packet tag
|
|
31
32
|
*/
|
|
32
|
-
getKey(tag:
|
|
33
|
+
getKey(tag: string): number;
|
|
33
34
|
/**
|
|
34
35
|
* Returns the tag associated with a given character key
|
|
35
36
|
* @param key A 1-character string key
|
|
@@ -39,7 +40,7 @@ export declare class PacketHolder<Typings extends readonly Packet<any>[], K exte
|
|
|
39
40
|
* Returns the packet instance associated with a tag
|
|
40
41
|
* @param tag The packet tag
|
|
41
42
|
*/
|
|
42
|
-
getPacket(tag: string): Packet<
|
|
43
|
+
getPacket(tag: string): Packet<any>;
|
|
43
44
|
/**
|
|
44
45
|
* Checks if a given character key exists
|
|
45
46
|
* @param key A string index
|
|
@@ -57,7 +58,7 @@ export declare class PacketHolder<Typings extends readonly Packet<any>[], K exte
|
|
|
57
58
|
/** Returns an array of all registered tags */
|
|
58
59
|
getTags(): string[];
|
|
59
60
|
/** Returns the list of all registered packets */
|
|
60
|
-
getPackets():
|
|
61
|
+
getPackets(): PacketTypings;
|
|
61
62
|
/** Serializes all registered packets into a string */
|
|
62
63
|
serialize(): number[];
|
|
63
64
|
}
|
|
@@ -2,7 +2,6 @@ import { PacketHolder } from "./PacketHolder";
|
|
|
2
2
|
import { ConvertType, Packet, ValidatorFunction } from "../../packets/Packets";
|
|
3
3
|
import { PacketType } from "../../packets/PacketType";
|
|
4
4
|
import { EnumPackage } from "../enums/EnumType";
|
|
5
|
-
import { PacketTypings } from "../../server/SonicWSServer";
|
|
6
5
|
/**
|
|
7
6
|
* Processes and verifies values into a sendable format
|
|
8
7
|
* @param packets Packet holder
|
|
@@ -10,7 +9,7 @@ import { PacketTypings } from "../../server/SonicWSServer";
|
|
|
10
9
|
* @param values The values
|
|
11
10
|
* @returns The indexed code, the data, and the packet schema
|
|
12
11
|
*/
|
|
13
|
-
export declare function processPacket
|
|
12
|
+
export declare function processPacket(packets: PacketHolder, tag: string, values: any[]): [code: number, data: number[], packet: Packet<any>];
|
|
14
13
|
/**
|
|
15
14
|
* Calls the listener for a packet with error callback
|
|
16
15
|
* @param listened The listened data
|
|
@@ -95,9 +94,11 @@ export declare function CreatePacket<T extends ArguableType>(settings: SinglePac
|
|
|
95
94
|
* @returns The constructed packet structure data.
|
|
96
95
|
* @throws {Error} If any type in `types` is invalid.
|
|
97
96
|
*/
|
|
98
|
-
export declare function CreateObjPacket<T extends readonly PacketType[]
|
|
97
|
+
export declare function CreateObjPacket<T extends readonly ArguableType[], V extends readonly PacketType[] = {
|
|
98
|
+
[K in keyof T]: ConvertType<T[K]>;
|
|
99
|
+
}>(settings: MultiPacketSettings & {
|
|
99
100
|
readonly types: T;
|
|
100
|
-
}): Packet<
|
|
101
|
+
}): Packet<V>;
|
|
101
102
|
/**
|
|
102
103
|
* Creates and defines an enum packet. This can be used to create an enum-based packet
|
|
103
104
|
* with a specific tag and possible values.
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SonicWSConnection } from "../../server/SonicWSConnection";
|
|
2
2
|
import { PacketHolder } from "./PacketHolder";
|
|
3
3
|
export declare class RateHandler {
|
|
4
4
|
private rates;
|
|
5
5
|
private limits;
|
|
6
6
|
private setInterval;
|
|
7
7
|
private socket;
|
|
8
|
-
constructor(host:
|
|
8
|
+
constructor(host: SonicWSConnection);
|
|
9
9
|
start(): void;
|
|
10
10
|
registerRate(tag: string, limit: number): void;
|
|
11
|
-
registerAll(packetHolder: PacketHolder
|
|
11
|
+
registerAll(packetHolder: PacketHolder, prefix: string): void;
|
|
12
12
|
trigger(tag: string | number): boolean;
|
|
13
13
|
subtract(tag: string | number): void;
|
|
14
14
|
}
|