@stonyx/sockets 0.1.1-beta.2 → 0.1.1-beta.21
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 +4 -0
- package/config/environment.js +12 -30
- package/config/environment.ts +48 -0
- package/dist/client.d.ts +46 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +184 -0
- package/dist/client.js.map +1 -0
- package/dist/encryption.d.ts +5 -0
- package/dist/encryption.d.ts.map +1 -0
- package/dist/encryption.js +28 -0
- package/dist/encryption.js.map +1 -0
- package/dist/handler.d.ts +6 -0
- package/dist/handler.d.ts.map +1 -0
- package/dist/handler.js +4 -0
- package/dist/handler.js.map +1 -0
- package/dist/main.d.ts +10 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +19 -0
- package/dist/main.js.map +1 -0
- package/dist/server.d.ts +49 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +177 -0
- package/dist/server.js.map +1 -0
- package/package.json +30 -11
- package/src/client.js +0 -197
- package/src/encryption.js +0 -31
- package/src/handler.js +0 -3
- package/src/main.js +0 -19
- package/src/server.js +0 -186
package/README.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
[](https://github.com/abofs/stonyx-sockets/actions/workflows/ci.yml)
|
|
2
|
+
[](https://www.npmjs.com/package/@stonyx/sockets)
|
|
3
|
+
[](https://opensource.org/licenses/Apache-2.0)
|
|
4
|
+
|
|
1
5
|
# @stonyx/sockets
|
|
2
6
|
|
|
3
7
|
WebSocket server and client module for the [Stonyx framework](https://github.com/abofs/stonyx), providing plug-and-play handler discovery, built-in authentication enforcement, AES-256-GCM encryption, and automatic heartbeat keep-alive.
|
package/config/environment.js
CHANGED
|
@@ -1,30 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const port = SOCKET_PORT ?? 2667;
|
|
15
|
-
|
|
16
|
-
export default {
|
|
17
|
-
port,
|
|
18
|
-
address: SOCKET_ADDRESS ?? `ws://localhost:${port}`,
|
|
19
|
-
authKey: SOCKET_AUTH_KEY ?? 'AUTH_KEY',
|
|
20
|
-
authData: {},
|
|
21
|
-
heartBeatInterval: SOCKET_HEARTBEAT_INTERVAL ?? 30000,
|
|
22
|
-
handlerDir: SOCKET_HANDLER_DIR ?? './socket-handlers',
|
|
23
|
-
log: SOCKET_LOG ?? false,
|
|
24
|
-
logColor: 'white',
|
|
25
|
-
logMethod: 'socket',
|
|
26
|
-
encryption: SOCKET_ENCRYPTION ?? 'true',
|
|
27
|
-
reconnectBaseDelay: SOCKET_RECONNECT_BASE_DELAY ?? 1000,
|
|
28
|
-
reconnectMaxDelay: SOCKET_RECONNECT_MAX_DELAY ?? 60000,
|
|
29
|
-
maxReconnectAttempts: SOCKET_MAX_RECONNECT_ATTEMPTS ?? Infinity,
|
|
30
|
-
};
|
|
1
|
+
// project configuration, override-able by listed environment variables
|
|
2
|
+
const {
|
|
3
|
+
DEBUG,
|
|
4
|
+
NODE_ENV,
|
|
5
|
+
} = process.env;
|
|
6
|
+
|
|
7
|
+
const environment = NODE_ENV ?? 'development';
|
|
8
|
+
|
|
9
|
+
export default {
|
|
10
|
+
environment,
|
|
11
|
+
debug: DEBUG ?? environment === 'development',
|
|
12
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const {
|
|
2
|
+
SOCKET_AUTH_KEY,
|
|
3
|
+
SOCKET_PORT,
|
|
4
|
+
SOCKET_ADDRESS,
|
|
5
|
+
SOCKET_HEARTBEAT_INTERVAL,
|
|
6
|
+
SOCKET_HANDLER_DIR,
|
|
7
|
+
SOCKET_LOG,
|
|
8
|
+
SOCKET_ENCRYPTION,
|
|
9
|
+
SOCKET_RECONNECT_BASE_DELAY,
|
|
10
|
+
SOCKET_RECONNECT_MAX_DELAY,
|
|
11
|
+
SOCKET_MAX_RECONNECT_ATTEMPTS,
|
|
12
|
+
} = process.env;
|
|
13
|
+
|
|
14
|
+
const port: string | number = SOCKET_PORT ?? 2667;
|
|
15
|
+
|
|
16
|
+
interface SocketsEnvironmentConfig {
|
|
17
|
+
port: string | number;
|
|
18
|
+
address: string;
|
|
19
|
+
authKey: string;
|
|
20
|
+
authData: Record<string, unknown>;
|
|
21
|
+
heartBeatInterval: string | number;
|
|
22
|
+
handlerDir: string;
|
|
23
|
+
log: string | boolean;
|
|
24
|
+
logColor: string;
|
|
25
|
+
logMethod: string;
|
|
26
|
+
encryption: string;
|
|
27
|
+
reconnectBaseDelay: string | number;
|
|
28
|
+
reconnectMaxDelay: string | number;
|
|
29
|
+
maxReconnectAttempts: string | number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const config: SocketsEnvironmentConfig = {
|
|
33
|
+
port,
|
|
34
|
+
address: SOCKET_ADDRESS ?? `ws://localhost:${port}`,
|
|
35
|
+
authKey: SOCKET_AUTH_KEY ?? 'AUTH_KEY',
|
|
36
|
+
authData: {},
|
|
37
|
+
heartBeatInterval: SOCKET_HEARTBEAT_INTERVAL ?? 30000,
|
|
38
|
+
handlerDir: SOCKET_HANDLER_DIR ?? './socket-handlers',
|
|
39
|
+
log: SOCKET_LOG ?? false,
|
|
40
|
+
logColor: 'white',
|
|
41
|
+
logMethod: 'socket',
|
|
42
|
+
encryption: SOCKET_ENCRYPTION ?? 'true',
|
|
43
|
+
reconnectBaseDelay: SOCKET_RECONNECT_BASE_DELAY ?? 1000,
|
|
44
|
+
reconnectMaxDelay: SOCKET_RECONNECT_MAX_DELAY ?? 60000,
|
|
45
|
+
maxReconnectAttempts: SOCKET_MAX_RECONNECT_ATTEMPTS ?? Infinity,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export default config;
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { WebSocket } from 'ws';
|
|
2
|
+
interface SocketMessage {
|
|
3
|
+
request: string;
|
|
4
|
+
data?: unknown;
|
|
5
|
+
response?: unknown;
|
|
6
|
+
sessionKey?: string;
|
|
7
|
+
}
|
|
8
|
+
interface HandlerInstance {
|
|
9
|
+
_clientRef?: SocketClient;
|
|
10
|
+
client: (response: unknown) => void;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
}
|
|
13
|
+
export default class SocketClient {
|
|
14
|
+
static instance: SocketClient | null;
|
|
15
|
+
handlers: Record<string, HandlerInstance>;
|
|
16
|
+
reconnectCount: number;
|
|
17
|
+
_intentionalClose: boolean;
|
|
18
|
+
socket: WebSocket | null;
|
|
19
|
+
sessionKey: Buffer | null;
|
|
20
|
+
globalKey: Buffer | null;
|
|
21
|
+
encryptionEnabled: boolean;
|
|
22
|
+
_heartBeatTimer: ReturnType<typeof setTimeout> | null;
|
|
23
|
+
promise: {
|
|
24
|
+
resolve: () => void;
|
|
25
|
+
reject: (reason?: unknown) => void;
|
|
26
|
+
} | null;
|
|
27
|
+
onDisconnect: ((code: number, reason: string) => void) | null;
|
|
28
|
+
onReconnecting: ((attempt: number, delay: number) => void) | null;
|
|
29
|
+
onReconnected: (() => void) | null;
|
|
30
|
+
onReconnectFailed: (() => void) | null;
|
|
31
|
+
constructor();
|
|
32
|
+
init(): Promise<void>;
|
|
33
|
+
discoverHandlers(): Promise<void>;
|
|
34
|
+
connect(): Promise<void>;
|
|
35
|
+
onMessage(payload: Buffer | string): void;
|
|
36
|
+
send(payload: SocketMessage, useGlobalKey?: boolean): void;
|
|
37
|
+
heartBeat(): void;
|
|
38
|
+
nextHeartBeat(): void;
|
|
39
|
+
onClose(code?: number, reason?: string): void;
|
|
40
|
+
close(): void;
|
|
41
|
+
getReconnectDelay(): number;
|
|
42
|
+
reconnect(): Promise<void>;
|
|
43
|
+
reset(): void;
|
|
44
|
+
}
|
|
45
|
+
export {};
|
|
46
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAO/B,UAAU,aAAa;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,UAAU,eAAe;IACvB,UAAU,CAAC,EAAE,YAAY,CAAC;IAC1B,MAAM,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAC;IACpC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,CAAC,OAAO,OAAO,YAAY;IAC/B,MAAM,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,CAAC;IAErC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAM;IAC/C,cAAc,SAAK;IACnB,iBAAiB,UAAS;IAC1B,MAAM,EAAE,SAAS,GAAG,IAAI,CAAQ;IAChC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAQ;IACjC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAQ;IAChC,iBAAiB,UAAS;IAC1B,eAAe,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,GAAG,IAAI,CAAQ;IAC7D,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,IAAI,CAAC;QAAC,MAAM,EAAE,CAAC,MAAM,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;KAAE,GAAG,IAAI,CAAQ;IAEnF,YAAY,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,IAAI,CAAQ;IACrE,cAAc,EAAE,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,IAAI,CAAQ;IACzE,aAAa,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAQ;IAC1C,iBAAiB,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAQ;;IAOxC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAarB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAcjC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA0B9B,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAyCzC,IAAI,CAAC,OAAO,EAAE,aAAa,EAAE,YAAY,UAAQ,GAAG,IAAI;IAYxD,SAAS,IAAI,IAAI;IAIjB,aAAa,IAAI,IAAI;IAMrB,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;IAW7C,KAAK,IAAI,IAAI;IAMb,iBAAiB,IAAI,MAAM;IAYrB,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAyBhC,KAAK,IAAI,IAAI;CAYd"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { WebSocket } from 'ws';
|
|
2
|
+
import config from 'stonyx/config';
|
|
3
|
+
import log from 'stonyx/log';
|
|
4
|
+
import { forEachFileImport } from '@stonyx/utils/file';
|
|
5
|
+
import { sleep } from '@stonyx/utils/promise';
|
|
6
|
+
import { encrypt, decrypt, deriveKey } from './encryption.js';
|
|
7
|
+
export default class SocketClient {
|
|
8
|
+
static instance;
|
|
9
|
+
handlers = {};
|
|
10
|
+
reconnectCount = 0;
|
|
11
|
+
_intentionalClose = false;
|
|
12
|
+
socket = null;
|
|
13
|
+
sessionKey = null;
|
|
14
|
+
globalKey = null;
|
|
15
|
+
encryptionEnabled = false;
|
|
16
|
+
_heartBeatTimer = null;
|
|
17
|
+
promise = null;
|
|
18
|
+
onDisconnect = null;
|
|
19
|
+
onReconnecting = null;
|
|
20
|
+
onReconnected = null;
|
|
21
|
+
onReconnectFailed = null;
|
|
22
|
+
constructor() {
|
|
23
|
+
if (SocketClient.instance)
|
|
24
|
+
return SocketClient.instance;
|
|
25
|
+
SocketClient.instance = this;
|
|
26
|
+
}
|
|
27
|
+
async init() {
|
|
28
|
+
await this.discoverHandlers();
|
|
29
|
+
const { encryption, authKey } = config.sockets;
|
|
30
|
+
this.encryptionEnabled = encryption === 'true' || encryption === true;
|
|
31
|
+
if (this.encryptionEnabled) {
|
|
32
|
+
this.globalKey = deriveKey(authKey);
|
|
33
|
+
}
|
|
34
|
+
return this.connect();
|
|
35
|
+
}
|
|
36
|
+
async discoverHandlers() {
|
|
37
|
+
const { handlerDir } = config.sockets;
|
|
38
|
+
await forEachFileImport(handlerDir, (HandlerClassUntyped, { name }) => {
|
|
39
|
+
const HandlerClass = HandlerClassUntyped;
|
|
40
|
+
const instance = new HandlerClass();
|
|
41
|
+
if (typeof instance.client === 'function') {
|
|
42
|
+
instance._clientRef = this;
|
|
43
|
+
this.handlers[name] = instance;
|
|
44
|
+
}
|
|
45
|
+
}, { ignoreAccessFailure: true });
|
|
46
|
+
}
|
|
47
|
+
async connect() {
|
|
48
|
+
if (this.sessionKey)
|
|
49
|
+
log.socket('Clearing stale sessionKey');
|
|
50
|
+
this.sessionKey = null;
|
|
51
|
+
return new Promise((resolve, reject) => {
|
|
52
|
+
const { address, authKey, authData } = config.sockets;
|
|
53
|
+
this.promise = { resolve, reject };
|
|
54
|
+
log.socket(`Connecting to remote server: ${address}`);
|
|
55
|
+
const socket = new WebSocket(address);
|
|
56
|
+
this.socket = socket;
|
|
57
|
+
socket.on('message', (data) => this.onMessage(data));
|
|
58
|
+
socket.on('close', (code, reason) => this.onClose(code, reason.toString()));
|
|
59
|
+
socket.on('error', () => {
|
|
60
|
+
log.socket(`Error connecting to socket server`);
|
|
61
|
+
reject(new Error('Error connecting to socket server'));
|
|
62
|
+
});
|
|
63
|
+
socket.on('open', () => {
|
|
64
|
+
this._intentionalClose = false;
|
|
65
|
+
this.reconnectCount = 0;
|
|
66
|
+
this.send({ request: 'auth', data: { authKey, ...authData } }, true);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
onMessage(payload) {
|
|
71
|
+
try {
|
|
72
|
+
let parsed;
|
|
73
|
+
if (this.encryptionEnabled) {
|
|
74
|
+
const key = this.sessionKey || this.globalKey;
|
|
75
|
+
if (!key)
|
|
76
|
+
throw new Error('Encryption enabled but no key available');
|
|
77
|
+
const raw = Buffer.isBuffer(payload) ? payload : Buffer.from(payload);
|
|
78
|
+
parsed = JSON.parse(decrypt(raw, key));
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
const raw = Buffer.isBuffer(payload) ? payload.toString() : payload;
|
|
82
|
+
parsed = JSON.parse(raw);
|
|
83
|
+
}
|
|
84
|
+
const { request, response, sessionKey } = parsed;
|
|
85
|
+
if (request === 'auth') {
|
|
86
|
+
if (sessionKey && this.encryptionEnabled) {
|
|
87
|
+
this.sessionKey = Buffer.from(sessionKey, 'base64');
|
|
88
|
+
}
|
|
89
|
+
this.nextHeartBeat();
|
|
90
|
+
}
|
|
91
|
+
if (request === 'heartBeat') {
|
|
92
|
+
this.nextHeartBeat();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const handler = this.handlers[request];
|
|
96
|
+
if (!handler) {
|
|
97
|
+
log.socket(`Call to invalid handler: ${request}`);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
handler.client.call({ ...handler, client: this }, response);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
log.socket(`Invalid payload received from remote server`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
send(payload, useGlobalKey = false) {
|
|
107
|
+
if (!this.socket)
|
|
108
|
+
throw new Error('Socket is not connected');
|
|
109
|
+
if (this.encryptionEnabled) {
|
|
110
|
+
const key = useGlobalKey ? this.globalKey : this.sessionKey;
|
|
111
|
+
if (!key)
|
|
112
|
+
throw new Error('Encryption enabled but no key available');
|
|
113
|
+
const data = encrypt(JSON.stringify(payload), key);
|
|
114
|
+
this.socket.send(data);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
this.socket.send(JSON.stringify(payload));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
heartBeat() {
|
|
121
|
+
this.send({ request: 'heartBeat' });
|
|
122
|
+
}
|
|
123
|
+
nextHeartBeat() {
|
|
124
|
+
const { heartBeatInterval } = config.sockets;
|
|
125
|
+
this._heartBeatTimer = setTimeout(() => this.heartBeat(), heartBeatInterval);
|
|
126
|
+
}
|
|
127
|
+
// ws always provides code and reason; params are optional for direct calls and testing
|
|
128
|
+
onClose(code, reason) {
|
|
129
|
+
log.socket(`Disconnected from remote server (code: ${code ?? 'unknown'}, reason: ${reason || 'none'})`);
|
|
130
|
+
if (this._heartBeatTimer)
|
|
131
|
+
clearTimeout(this._heartBeatTimer);
|
|
132
|
+
this.onDisconnect?.(code ?? 1006, reason ?? '');
|
|
133
|
+
if (!this._intentionalClose) {
|
|
134
|
+
this.reconnect();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
close() {
|
|
138
|
+
this._intentionalClose = true;
|
|
139
|
+
if (this._heartBeatTimer)
|
|
140
|
+
clearTimeout(this._heartBeatTimer);
|
|
141
|
+
if (this.socket)
|
|
142
|
+
this.socket.close();
|
|
143
|
+
}
|
|
144
|
+
getReconnectDelay() {
|
|
145
|
+
const { reconnectBaseDelay = 1000, reconnectMaxDelay = 60000, } = config.sockets;
|
|
146
|
+
const exponential = reconnectBaseDelay * Math.pow(2, this.reconnectCount - 1);
|
|
147
|
+
const capped = Math.min(exponential, reconnectMaxDelay);
|
|
148
|
+
const jitter = Math.floor(Math.random() * 1000);
|
|
149
|
+
return capped + jitter;
|
|
150
|
+
}
|
|
151
|
+
async reconnect() {
|
|
152
|
+
const { maxReconnectAttempts = Infinity } = config.sockets;
|
|
153
|
+
this.reconnectCount++;
|
|
154
|
+
if (this.reconnectCount > maxReconnectAttempts) {
|
|
155
|
+
log.socket('Max reconnect attempts exceeded');
|
|
156
|
+
this.onReconnectFailed?.();
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const delay = this.getReconnectDelay();
|
|
160
|
+
this.onReconnecting?.(this.reconnectCount, delay);
|
|
161
|
+
log.socket(`Reconnecting (attempt ${this.reconnectCount}, delay ${delay}ms)`);
|
|
162
|
+
await sleep(delay / 1000);
|
|
163
|
+
try {
|
|
164
|
+
await this.connect();
|
|
165
|
+
this.onReconnected?.();
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
// onClose will fire and trigger the next reconnect attempt
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
reset() {
|
|
172
|
+
this.close();
|
|
173
|
+
this.handlers = {};
|
|
174
|
+
this.sessionKey = null;
|
|
175
|
+
this.reconnectCount = 0;
|
|
176
|
+
this._intentionalClose = false;
|
|
177
|
+
this.onDisconnect = null;
|
|
178
|
+
this.onReconnecting = null;
|
|
179
|
+
this.onReconnected = null;
|
|
180
|
+
this.onReconnectFailed = null;
|
|
181
|
+
SocketClient.instance = null;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function encrypt(data: string, key: Buffer): Buffer;
|
|
2
|
+
export declare function decrypt(buffer: Buffer | string, key: Buffer): string;
|
|
3
|
+
export declare function generateSessionKey(): Buffer;
|
|
4
|
+
export declare function deriveKey(authKey: string): Buffer;
|
|
5
|
+
//# sourceMappingURL=encryption.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"encryption.d.ts","sourceRoot":"","sources":["../src/encryption.ts"],"names":[],"mappings":"AAMA,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAMzD;AAED,wBAAgB,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAQpE;AAED,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEjD"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
const ALGORITHM = 'aes-256-gcm';
|
|
3
|
+
const IV_LENGTH = 12;
|
|
4
|
+
const TAG_LENGTH = 16;
|
|
5
|
+
export function encrypt(data, key) {
|
|
6
|
+
const iv = crypto.randomBytes(IV_LENGTH);
|
|
7
|
+
const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
|
|
8
|
+
const encrypted = Buffer.concat([cipher.update(data, 'utf8'), cipher.final()]);
|
|
9
|
+
const tag = cipher.getAuthTag();
|
|
10
|
+
return Buffer.concat([iv, tag, encrypted]);
|
|
11
|
+
}
|
|
12
|
+
export function decrypt(buffer, key) {
|
|
13
|
+
if (typeof buffer === 'string')
|
|
14
|
+
buffer = Buffer.from(buffer, 'base64');
|
|
15
|
+
const iv = buffer.subarray(0, IV_LENGTH);
|
|
16
|
+
const tag = buffer.subarray(IV_LENGTH, IV_LENGTH + TAG_LENGTH);
|
|
17
|
+
const encrypted = buffer.subarray(IV_LENGTH + TAG_LENGTH);
|
|
18
|
+
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
|
|
19
|
+
decipher.setAuthTag(tag);
|
|
20
|
+
return Buffer.concat([decipher.update(encrypted), decipher.final()]).toString('utf8');
|
|
21
|
+
}
|
|
22
|
+
export function generateSessionKey() {
|
|
23
|
+
return crypto.randomBytes(32);
|
|
24
|
+
}
|
|
25
|
+
export function deriveKey(authKey) {
|
|
26
|
+
return crypto.scryptSync(authKey, 'stonyx-sockets', 32);
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=encryption.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"encryption.js","sourceRoot":"","sources":["../src/encryption.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,aAAa,CAAC;AAEjC,MAAM,SAAS,GAAG,aAAa,CAAC;AAChC,MAAM,SAAS,GAAG,EAAE,CAAC;AACrB,MAAM,UAAU,GAAG,EAAE,CAAC;AAEtB,MAAM,UAAU,OAAO,CAAC,IAAY,EAAE,GAAW;IAC/C,MAAM,EAAE,GAAG,MAAM,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;IACzC,MAAM,MAAM,GAAG,MAAM,CAAC,cAAc,CAAC,SAAS,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;IACzD,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAC/E,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;IAChC,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,GAAG,EAAE,SAAS,CAAC,CAAC,CAAC;AAC7C,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,MAAuB,EAAE,GAAW;IAC1D,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACvE,MAAM,EAAE,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;IACzC,MAAM,GAAG,GAAG,MAAM,CAAC,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,UAAU,CAAC,CAAC;IAC/D,MAAM,SAAS,GAAG,MAAM,CAAC,QAAQ,CAAC,SAAS,GAAG,UAAU,CAAC,CAAC;IAC1D,MAAM,QAAQ,GAAG,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;IAC7D,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IACzB,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AACxF,CAAC;AAED,MAAM,UAAU,kBAAkB;IAChC,OAAO,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;AAChC,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,OAAe;IACvC,OAAO,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,gBAAgB,EAAE,EAAE,CAAC,CAAC;AAC1D,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../src/handler.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,OAAO,OAAO,OAAO;IAC1B,MAAM,CAAC,QAAQ,UAAS;IAEhB,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACxE,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAC;CAC9C"}
|
package/dist/handler.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handler.js","sourceRoot":"","sources":["../src/handler.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,OAAO,OAAO,OAAO;IAC1B,MAAM,CAAC,QAAQ,GAAG,KAAK,CAAC"}
|
package/dist/main.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { default as SocketServer } from './server.js';
|
|
2
|
+
export { default as SocketClient } from './client.js';
|
|
3
|
+
export { default as Handler } from './handler.js';
|
|
4
|
+
export default class Sockets {
|
|
5
|
+
static instance: Sockets | null;
|
|
6
|
+
constructor();
|
|
7
|
+
init(): Promise<void>;
|
|
8
|
+
reset(): void;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=main.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,cAAc,CAAC;AAElD,MAAM,CAAC,OAAO,OAAO,OAAO;IAC1B,MAAM,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAAC;;IAO1B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAK3B,KAAK,IAAI,IAAI;CAGd"}
|
package/dist/main.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export { default as SocketServer } from './server.js';
|
|
2
|
+
export { default as SocketClient } from './client.js';
|
|
3
|
+
export { default as Handler } from './handler.js';
|
|
4
|
+
export default class Sockets {
|
|
5
|
+
static instance;
|
|
6
|
+
constructor() {
|
|
7
|
+
if (Sockets.instance)
|
|
8
|
+
return Sockets.instance;
|
|
9
|
+
Sockets.instance = this;
|
|
10
|
+
}
|
|
11
|
+
async init() {
|
|
12
|
+
// Handler discovery is deferred to SocketServer.init() / SocketClient.init()
|
|
13
|
+
// This entry point satisfies Stonyx module auto-initialization
|
|
14
|
+
}
|
|
15
|
+
reset() {
|
|
16
|
+
Sockets.instance = null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=main.js.map
|
package/dist/main.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,cAAc,CAAC;AAElD,MAAM,CAAC,OAAO,OAAO,OAAO;IAC1B,MAAM,CAAC,QAAQ,CAAiB;IAEhC;QACE,IAAI,OAAO,CAAC,QAAQ;YAAE,OAAO,OAAO,CAAC,QAAQ,CAAC;QAC9C,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAC1B,CAAC;IAED,KAAK,CAAC,IAAI;QACR,6EAA6E;QAC7E,+DAA+D;IACjE,CAAC;IAED,KAAK;QACH,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAC1B,CAAC;CACF"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { WebSocket, WebSocketServer } from 'ws';
|
|
2
|
+
interface SocketMessage {
|
|
3
|
+
request: string;
|
|
4
|
+
data?: unknown;
|
|
5
|
+
response?: unknown;
|
|
6
|
+
sessionKey?: string;
|
|
7
|
+
}
|
|
8
|
+
interface ConnectedClient {
|
|
9
|
+
id: number;
|
|
10
|
+
ip: string;
|
|
11
|
+
__authenticated: boolean;
|
|
12
|
+
__sessionKey?: Buffer;
|
|
13
|
+
meta?: Record<string, unknown>;
|
|
14
|
+
send(payload: SocketMessage, keyOverride?: Buffer): void;
|
|
15
|
+
close(): void;
|
|
16
|
+
terminate(): void;
|
|
17
|
+
on(event: string, handler: (...args: unknown[]) => void): void;
|
|
18
|
+
}
|
|
19
|
+
interface HandlerInstance {
|
|
20
|
+
_serverRef?: SocketServer;
|
|
21
|
+
server: (data: unknown, client: ConnectedClient) => unknown | Promise<unknown>;
|
|
22
|
+
constructor: {
|
|
23
|
+
skipAuth: boolean;
|
|
24
|
+
};
|
|
25
|
+
[key: string]: unknown;
|
|
26
|
+
}
|
|
27
|
+
export default class SocketServer {
|
|
28
|
+
static instance: SocketServer | null;
|
|
29
|
+
clientMap: Map<number, ConnectedClient>;
|
|
30
|
+
handlers: Record<string, HandlerInstance>;
|
|
31
|
+
wss: WebSocketServer | null;
|
|
32
|
+
encryptionEnabled: boolean;
|
|
33
|
+
globalKey: Buffer | null;
|
|
34
|
+
onClientDisconnect: ((client: ConnectedClient, code: number, reason: string) => void) | null;
|
|
35
|
+
constructor();
|
|
36
|
+
init(): Promise<void>;
|
|
37
|
+
discoverHandlers(): Promise<void>;
|
|
38
|
+
validateAuthHandler(): void;
|
|
39
|
+
onMessage(payload: Buffer | string, client: ConnectedClient): Promise<void>;
|
|
40
|
+
prepareSend(client: ConnectedClient, ws: WebSocket): void;
|
|
41
|
+
handleDisconnect(client: ConnectedClient, code?: number, reason?: string): void;
|
|
42
|
+
sendTo(clientId: number, request: string, response: unknown): void;
|
|
43
|
+
sendToByMeta(key: string, value: unknown, request: string, response: unknown): boolean;
|
|
44
|
+
broadcast(request: string, response: unknown): void;
|
|
45
|
+
close(): void;
|
|
46
|
+
reset(): void;
|
|
47
|
+
}
|
|
48
|
+
export {};
|
|
49
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,IAAI,CAAC;AAMhD,UAAU,aAAa;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,UAAU,eAAe;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,eAAe,EAAE,OAAO,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,IAAI,CAAC,OAAO,EAAE,aAAa,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzD,KAAK,IAAI,IAAI,CAAC;IACd,SAAS,IAAI,IAAI,CAAC;IAClB,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,GAAG,IAAI,CAAC;CAChE;AAED,UAAU,eAAe;IACvB,UAAU,CAAC,EAAE,YAAY,CAAC;IAC1B,MAAM,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,eAAe,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/E,WAAW,EAAE;QAAE,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAC;IACnC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAID,MAAM,CAAC,OAAO,OAAO,YAAY;IAC/B,MAAM,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,CAAC;IAErC,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAa;IACpD,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAM;IAC/C,GAAG,EAAE,eAAe,GAAG,IAAI,CAAQ;IACnC,iBAAiB,UAAS;IAC1B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAQ;IAEhC,kBAAkB,EAAE,CAAC,CAAC,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,IAAI,CAAQ;;IAO9F,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA+BrB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAcvC,mBAAmB,IAAI,IAAI;IAMrB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IA2DjF,WAAW,CAAC,MAAM,EAAE,eAAe,EAAE,EAAE,EAAE,SAAS,GAAG,IAAI;IAiBzD,gBAAgB,CAAC,MAAM,EAAE,eAAe,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;IAO/E,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,IAAI;IAMlE,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO;IAUtF,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,IAAI;IAQnD,KAAK,IAAI,IAAI;IAUb,KAAK,IAAI,IAAI;CAOd"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { WebSocketServer } from 'ws';
|
|
2
|
+
import config from 'stonyx/config';
|
|
3
|
+
import log from 'stonyx/log';
|
|
4
|
+
import { forEachFileImport } from '@stonyx/utils/file';
|
|
5
|
+
import { encrypt, decrypt, deriveKey, generateSessionKey } from './encryption.js';
|
|
6
|
+
let clientId = 0;
|
|
7
|
+
export default class SocketServer {
|
|
8
|
+
static instance;
|
|
9
|
+
clientMap = new Map();
|
|
10
|
+
handlers = {};
|
|
11
|
+
wss = null;
|
|
12
|
+
encryptionEnabled = false;
|
|
13
|
+
globalKey = null;
|
|
14
|
+
onClientDisconnect = null;
|
|
15
|
+
constructor() {
|
|
16
|
+
if (SocketServer.instance)
|
|
17
|
+
return SocketServer.instance;
|
|
18
|
+
SocketServer.instance = this;
|
|
19
|
+
}
|
|
20
|
+
async init() {
|
|
21
|
+
await this.discoverHandlers();
|
|
22
|
+
this.validateAuthHandler();
|
|
23
|
+
const { port, encryption, authKey } = config.sockets;
|
|
24
|
+
this.encryptionEnabled = encryption === 'true' || encryption === true;
|
|
25
|
+
if (this.encryptionEnabled) {
|
|
26
|
+
this.globalKey = deriveKey(authKey);
|
|
27
|
+
}
|
|
28
|
+
const wss = new WebSocketServer({ port });
|
|
29
|
+
this.wss = wss;
|
|
30
|
+
log.socket(`WebSocket server is listening on port ${port}`);
|
|
31
|
+
wss.on('connection', (ws, request) => {
|
|
32
|
+
const { remoteAddress } = request.socket;
|
|
33
|
+
log.socket(`[${remoteAddress}] Client connected`);
|
|
34
|
+
const client = ws;
|
|
35
|
+
client.id = ++clientId;
|
|
36
|
+
client.ip = remoteAddress || '';
|
|
37
|
+
client.__authenticated = false;
|
|
38
|
+
this.prepareSend(client, ws);
|
|
39
|
+
ws.on('message', (payload) => this.onMessage(payload, client));
|
|
40
|
+
ws.on('close', (code, reason) => this.handleDisconnect(client, code, reason.toString()));
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
async discoverHandlers() {
|
|
44
|
+
const { handlerDir } = config.sockets;
|
|
45
|
+
await forEachFileImport(handlerDir, (HandlerClassUntyped, { name }) => {
|
|
46
|
+
const HandlerClass = HandlerClassUntyped;
|
|
47
|
+
const instance = new HandlerClass();
|
|
48
|
+
if (typeof instance.server === 'function') {
|
|
49
|
+
instance._serverRef = this;
|
|
50
|
+
this.handlers[name] = instance;
|
|
51
|
+
}
|
|
52
|
+
}, { ignoreAccessFailure: true });
|
|
53
|
+
}
|
|
54
|
+
validateAuthHandler() {
|
|
55
|
+
if (!this.handlers.auth) {
|
|
56
|
+
throw new Error('SocketServer requires an "auth" handler with a server() method');
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async onMessage(payload, client) {
|
|
60
|
+
try {
|
|
61
|
+
let parsed;
|
|
62
|
+
if (this.encryptionEnabled) {
|
|
63
|
+
const key = client.__authenticated ? client.__sessionKey : this.globalKey;
|
|
64
|
+
if (!key)
|
|
65
|
+
throw new Error('Encryption enabled but no key available');
|
|
66
|
+
const raw = Buffer.isBuffer(payload) ? payload : Buffer.from(payload);
|
|
67
|
+
parsed = JSON.parse(decrypt(raw, key));
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
parsed = JSON.parse(typeof payload === 'string' ? payload : payload.toString());
|
|
71
|
+
}
|
|
72
|
+
const { request, data } = parsed;
|
|
73
|
+
// Built-in heartbeat - no handler needed
|
|
74
|
+
if (request === 'heartBeat') {
|
|
75
|
+
if (client.__authenticated)
|
|
76
|
+
client.send({ request: 'heartBeat', response: true });
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const handler = this.handlers[request];
|
|
80
|
+
if (!handler) {
|
|
81
|
+
log.socket(`Invalid request received: ${request}`);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (request !== 'auth' && !handler.constructor.skipAuth && !client.__authenticated) {
|
|
85
|
+
log.socket(`Rejected unauthenticated request: ${request}`);
|
|
86
|
+
client.close();
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const response = await handler.server(data, client);
|
|
90
|
+
if (response === undefined || response === null)
|
|
91
|
+
return;
|
|
92
|
+
if (request === 'auth' && response) {
|
|
93
|
+
client.__authenticated = true;
|
|
94
|
+
if (this.encryptionEnabled) {
|
|
95
|
+
const sessionKey = generateSessionKey();
|
|
96
|
+
client.__sessionKey = sessionKey;
|
|
97
|
+
if (!this.globalKey)
|
|
98
|
+
throw new Error('Encryption enabled but globalKey not initialized');
|
|
99
|
+
client.send({ request, response, sessionKey: sessionKey.toString('base64') }, this.globalKey);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
client.send({ request, response });
|
|
103
|
+
}
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
client.send({ request, response });
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
log.socket(`Invalid payload from client`);
|
|
110
|
+
if (config.debug)
|
|
111
|
+
console.error(error);
|
|
112
|
+
client.close();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
prepareSend(client, ws) {
|
|
116
|
+
const socketSend = ws.send.bind(ws);
|
|
117
|
+
const server = this;
|
|
118
|
+
client.send = (payload, keyOverride) => {
|
|
119
|
+
if (server.encryptionEnabled) {
|
|
120
|
+
const key = keyOverride || client.__sessionKey;
|
|
121
|
+
if (!key)
|
|
122
|
+
throw new Error('Encryption enabled but no key available');
|
|
123
|
+
const data = encrypt(JSON.stringify(payload), key);
|
|
124
|
+
socketSend(data);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
socketSend(JSON.stringify(payload));
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
// ws always provides code and reason; params are optional for direct calls and testing
|
|
132
|
+
handleDisconnect(client, code, reason) {
|
|
133
|
+
const { ip } = client;
|
|
134
|
+
log.socket(`[${ip}] Client disconnected (code: ${code ?? 'unknown'}, reason: ${reason || 'none'})`);
|
|
135
|
+
this.clientMap.delete(client.id);
|
|
136
|
+
this.onClientDisconnect?.(client, code ?? 1006, reason ?? '');
|
|
137
|
+
}
|
|
138
|
+
sendTo(clientId, request, response) {
|
|
139
|
+
const client = this.clientMap.get(clientId);
|
|
140
|
+
if (!client)
|
|
141
|
+
return;
|
|
142
|
+
client.send({ request, response });
|
|
143
|
+
}
|
|
144
|
+
sendToByMeta(key, value, request, response) {
|
|
145
|
+
for (const [, client] of this.clientMap) {
|
|
146
|
+
if (client.meta?.[key] === value && client.__authenticated) {
|
|
147
|
+
client.send({ request, response });
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
broadcast(request, response) {
|
|
154
|
+
for (const [, client] of this.clientMap) {
|
|
155
|
+
if (client.__authenticated) {
|
|
156
|
+
client.send({ request, response });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
close() {
|
|
161
|
+
if (this.wss) {
|
|
162
|
+
for (const client of this.wss.clients) {
|
|
163
|
+
client.terminate();
|
|
164
|
+
}
|
|
165
|
+
this.wss.close();
|
|
166
|
+
this.wss = null;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
reset() {
|
|
170
|
+
this.close();
|
|
171
|
+
this.clientMap.clear();
|
|
172
|
+
this.handlers = {};
|
|
173
|
+
clientId = 0;
|
|
174
|
+
SocketServer.instance = null;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -4,21 +4,34 @@
|
|
|
4
4
|
"stonyx-async",
|
|
5
5
|
"stonyx-module"
|
|
6
6
|
],
|
|
7
|
-
"version": "0.1.1-beta.
|
|
7
|
+
"version": "0.1.1-beta.21",
|
|
8
8
|
"description": "WebSocket server and client module for the Stonyx framework",
|
|
9
|
-
"main": "
|
|
9
|
+
"main": "dist/main.js",
|
|
10
|
+
"types": "dist/main.d.ts",
|
|
10
11
|
"type": "module",
|
|
11
12
|
"files": [
|
|
12
|
-
"
|
|
13
|
+
"dist",
|
|
13
14
|
"config",
|
|
14
15
|
"LICENSE.md",
|
|
15
16
|
"README.md"
|
|
16
17
|
],
|
|
17
18
|
"exports": {
|
|
18
|
-
".":
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/main.d.ts",
|
|
21
|
+
"default": "./dist/main.js"
|
|
22
|
+
},
|
|
23
|
+
"./server": {
|
|
24
|
+
"types": "./dist/server.d.ts",
|
|
25
|
+
"default": "./dist/server.js"
|
|
26
|
+
},
|
|
27
|
+
"./client": {
|
|
28
|
+
"types": "./dist/client.d.ts",
|
|
29
|
+
"default": "./dist/client.js"
|
|
30
|
+
},
|
|
31
|
+
"./handler": {
|
|
32
|
+
"types": "./dist/handler.d.ts",
|
|
33
|
+
"default": "./dist/handler.js"
|
|
34
|
+
}
|
|
22
35
|
},
|
|
23
36
|
"publishConfig": {
|
|
24
37
|
"access": "public",
|
|
@@ -38,15 +51,21 @@
|
|
|
38
51
|
},
|
|
39
52
|
"homepage": "https://github.com/abofs/stonyx-sockets#readme",
|
|
40
53
|
"dependencies": {
|
|
41
|
-
"stonyx": "0.2.3-beta.
|
|
54
|
+
"stonyx": "0.2.3-beta.55",
|
|
42
55
|
"ws": "^8.18.1"
|
|
43
56
|
},
|
|
44
57
|
"devDependencies": {
|
|
45
|
-
"@stonyx/utils": "0.2.3-beta.
|
|
58
|
+
"@stonyx/utils": "0.2.3-beta.23",
|
|
59
|
+
"@types/node": "^25.5.2",
|
|
60
|
+
"@types/ws": "^8.18.1",
|
|
46
61
|
"qunit": "^2.24.1",
|
|
47
|
-
"sinon": "^21.0.0"
|
|
62
|
+
"sinon": "^21.0.0",
|
|
63
|
+
"tsx": "^4.21.0",
|
|
64
|
+
"typescript": "^5.8.3"
|
|
48
65
|
},
|
|
49
66
|
"scripts": {
|
|
50
|
-
"
|
|
67
|
+
"build": "tsc",
|
|
68
|
+
"build:test": "tsc -p tsconfig.test.json",
|
|
69
|
+
"test": "pnpm build && pnpm build:test && NODE_ENV=test node --import tsx/esm --import ./test/setup.ts node_modules/qunit/bin/qunit.js 'test/**/*-test.ts'"
|
|
51
70
|
}
|
|
52
71
|
}
|
package/src/client.js
DELETED
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
import { WebSocket } from 'ws';
|
|
2
|
-
import config from 'stonyx/config';
|
|
3
|
-
import log from 'stonyx/log';
|
|
4
|
-
import { forEachFileImport } from '@stonyx/utils/file';
|
|
5
|
-
import { sleep } from '@stonyx/utils/promise';
|
|
6
|
-
import { encrypt, decrypt, deriveKey } from './encryption.js';
|
|
7
|
-
|
|
8
|
-
export default class SocketClient {
|
|
9
|
-
handlers = {};
|
|
10
|
-
reconnectCount = 0;
|
|
11
|
-
_intentionalClose = false;
|
|
12
|
-
|
|
13
|
-
onDisconnect = null;
|
|
14
|
-
onReconnecting = null;
|
|
15
|
-
onReconnected = null;
|
|
16
|
-
onReconnectFailed = null;
|
|
17
|
-
|
|
18
|
-
constructor() {
|
|
19
|
-
if (SocketClient.instance) return SocketClient.instance;
|
|
20
|
-
SocketClient.instance = this;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async init() {
|
|
24
|
-
await this.discoverHandlers();
|
|
25
|
-
|
|
26
|
-
const { encryption, authKey } = config.sockets;
|
|
27
|
-
this.encryptionEnabled = encryption === 'true' || encryption === true;
|
|
28
|
-
|
|
29
|
-
if (this.encryptionEnabled) {
|
|
30
|
-
this.globalKey = deriveKey(authKey);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return this.connect();
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async discoverHandlers() {
|
|
37
|
-
const { handlerDir } = config.sockets;
|
|
38
|
-
|
|
39
|
-
await forEachFileImport(handlerDir, (HandlerClass, { name }) => {
|
|
40
|
-
const instance = new HandlerClass();
|
|
41
|
-
|
|
42
|
-
if (typeof instance.client === 'function') {
|
|
43
|
-
instance._clientRef = this;
|
|
44
|
-
this.handlers[name] = instance;
|
|
45
|
-
}
|
|
46
|
-
}, { ignoreAccessFailure: true });
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async connect() {
|
|
50
|
-
return new Promise((resolve, reject) => {
|
|
51
|
-
const { address, authKey, authData } = config.sockets;
|
|
52
|
-
this.promise = { resolve, reject };
|
|
53
|
-
|
|
54
|
-
log.socket(`Connecting to remote server: ${address}`);
|
|
55
|
-
const socket = new WebSocket(address);
|
|
56
|
-
this.socket = socket;
|
|
57
|
-
|
|
58
|
-
socket.onmessage = this.onMessage.bind(this);
|
|
59
|
-
socket.onclose = this.onClose.bind(this);
|
|
60
|
-
socket.onerror = event => {
|
|
61
|
-
log.socket(`Error connecting to socket server`);
|
|
62
|
-
reject('Error connecting to socket server');
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
socket.onopen = () => {
|
|
66
|
-
this._intentionalClose = false;
|
|
67
|
-
this.reconnectCount = 0;
|
|
68
|
-
this.send({ request: 'auth', data: { authKey, ...authData } }, true);
|
|
69
|
-
};
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
onMessage({ data: payload }) {
|
|
74
|
-
try {
|
|
75
|
-
let parsed;
|
|
76
|
-
|
|
77
|
-
if (this.encryptionEnabled) {
|
|
78
|
-
const key = this.sessionKey || this.globalKey;
|
|
79
|
-
const raw = Buffer.isBuffer(payload) ? payload : Buffer.from(payload);
|
|
80
|
-
parsed = JSON.parse(decrypt(raw, key));
|
|
81
|
-
} else {
|
|
82
|
-
const raw = Buffer.isBuffer(payload) ? payload.toString() : payload;
|
|
83
|
-
parsed = JSON.parse(raw);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const { request, response, sessionKey } = parsed;
|
|
87
|
-
|
|
88
|
-
if (request === 'auth') {
|
|
89
|
-
if (sessionKey && this.encryptionEnabled) {
|
|
90
|
-
this.sessionKey = Buffer.from(sessionKey, 'base64');
|
|
91
|
-
}
|
|
92
|
-
this.nextHeartBeat();
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (request === 'heartBeat') {
|
|
96
|
-
this.nextHeartBeat();
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const handler = this.handlers[request];
|
|
101
|
-
|
|
102
|
-
if (!handler) {
|
|
103
|
-
log.socket(`Call to invalid handler: ${request}`);
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
handler.client.call({ client: this, ...handler }, response);
|
|
108
|
-
} catch (error) {
|
|
109
|
-
log.socket(`Invalid payload received from remote server`);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
send(payload, useGlobalKey = false) {
|
|
114
|
-
if (this.encryptionEnabled) {
|
|
115
|
-
const key = useGlobalKey ? this.globalKey : this.sessionKey;
|
|
116
|
-
const data = encrypt(JSON.stringify(payload), key);
|
|
117
|
-
this.socket.send(data);
|
|
118
|
-
} else {
|
|
119
|
-
this.socket.send(JSON.stringify(payload));
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
heartBeat() {
|
|
124
|
-
this.send({ request: 'heartBeat' });
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
nextHeartBeat() {
|
|
128
|
-
this._heartBeatTimer = setTimeout(() => this.heartBeat(), config.sockets.heartBeatInterval);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
onClose() {
|
|
132
|
-
log.socket('Disconnected from remote server');
|
|
133
|
-
if (this._heartBeatTimer) clearTimeout(this._heartBeatTimer);
|
|
134
|
-
|
|
135
|
-
this.onDisconnect?.();
|
|
136
|
-
|
|
137
|
-
if (!this._intentionalClose) {
|
|
138
|
-
this.reconnect();
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
close() {
|
|
143
|
-
this._intentionalClose = true;
|
|
144
|
-
if (this._heartBeatTimer) clearTimeout(this._heartBeatTimer);
|
|
145
|
-
if (this.socket) this.socket.close();
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
getReconnectDelay() {
|
|
149
|
-
const {
|
|
150
|
-
reconnectBaseDelay = 1000,
|
|
151
|
-
reconnectMaxDelay = 60000,
|
|
152
|
-
} = config.sockets;
|
|
153
|
-
|
|
154
|
-
const exponential = reconnectBaseDelay * Math.pow(2, this.reconnectCount - 1);
|
|
155
|
-
const capped = Math.min(exponential, reconnectMaxDelay);
|
|
156
|
-
const jitter = Math.floor(Math.random() * 1000);
|
|
157
|
-
return capped + jitter;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
async reconnect() {
|
|
161
|
-
const { maxReconnectAttempts = Infinity } = config.sockets;
|
|
162
|
-
|
|
163
|
-
this.reconnectCount++;
|
|
164
|
-
|
|
165
|
-
if (this.reconnectCount > maxReconnectAttempts) {
|
|
166
|
-
log.socket('Max reconnect attempts exceeded');
|
|
167
|
-
this.onReconnectFailed?.();
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const delay = this.getReconnectDelay();
|
|
172
|
-
this.onReconnecting?.(this.reconnectCount, delay);
|
|
173
|
-
log.socket(`Reconnecting (attempt ${this.reconnectCount}, delay ${delay}ms)`);
|
|
174
|
-
|
|
175
|
-
await sleep(delay / 1000);
|
|
176
|
-
|
|
177
|
-
try {
|
|
178
|
-
await this.connect();
|
|
179
|
-
this.onReconnected?.();
|
|
180
|
-
} catch {
|
|
181
|
-
// onClose will fire and trigger the next reconnect attempt
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
reset() {
|
|
186
|
-
this.close();
|
|
187
|
-
this.handlers = {};
|
|
188
|
-
this.sessionKey = null;
|
|
189
|
-
this.reconnectCount = 0;
|
|
190
|
-
this._intentionalClose = false;
|
|
191
|
-
this.onDisconnect = null;
|
|
192
|
-
this.onReconnecting = null;
|
|
193
|
-
this.onReconnected = null;
|
|
194
|
-
this.onReconnectFailed = null;
|
|
195
|
-
SocketClient.instance = null;
|
|
196
|
-
}
|
|
197
|
-
}
|
package/src/encryption.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import crypto from 'node:crypto';
|
|
2
|
-
|
|
3
|
-
const ALGORITHM = 'aes-256-gcm';
|
|
4
|
-
const IV_LENGTH = 12;
|
|
5
|
-
const TAG_LENGTH = 16;
|
|
6
|
-
|
|
7
|
-
export function encrypt(data, key) {
|
|
8
|
-
const iv = crypto.randomBytes(IV_LENGTH);
|
|
9
|
-
const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
|
|
10
|
-
const encrypted = Buffer.concat([cipher.update(data, 'utf8'), cipher.final()]);
|
|
11
|
-
const tag = cipher.getAuthTag();
|
|
12
|
-
return Buffer.concat([iv, tag, encrypted]);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function decrypt(buffer, key) {
|
|
16
|
-
if (typeof buffer === 'string') buffer = Buffer.from(buffer, 'base64');
|
|
17
|
-
const iv = buffer.subarray(0, IV_LENGTH);
|
|
18
|
-
const tag = buffer.subarray(IV_LENGTH, IV_LENGTH + TAG_LENGTH);
|
|
19
|
-
const encrypted = buffer.subarray(IV_LENGTH + TAG_LENGTH);
|
|
20
|
-
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
|
|
21
|
-
decipher.setAuthTag(tag);
|
|
22
|
-
return Buffer.concat([decipher.update(encrypted), decipher.final()]).toString('utf8');
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function generateSessionKey() {
|
|
26
|
-
return crypto.randomBytes(32);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function deriveKey(authKey) {
|
|
30
|
-
return crypto.scryptSync(authKey, 'stonyx-sockets', 32);
|
|
31
|
-
}
|
package/src/handler.js
DELETED
package/src/main.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
export { default as SocketServer } from './server.js';
|
|
2
|
-
export { default as SocketClient } from './client.js';
|
|
3
|
-
export { default as Handler } from './handler.js';
|
|
4
|
-
|
|
5
|
-
export default class Sockets {
|
|
6
|
-
constructor() {
|
|
7
|
-
if (Sockets.instance) return Sockets.instance;
|
|
8
|
-
Sockets.instance = this;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
async init() {
|
|
12
|
-
// Handler discovery is deferred to SocketServer.init() / SocketClient.init()
|
|
13
|
-
// This entry point satisfies Stonyx module auto-initialization
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
reset() {
|
|
17
|
-
Sockets.instance = null;
|
|
18
|
-
}
|
|
19
|
-
}
|
package/src/server.js
DELETED
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
import { WebSocketServer } from 'ws';
|
|
2
|
-
import config from 'stonyx/config';
|
|
3
|
-
import log from 'stonyx/log';
|
|
4
|
-
import { forEachFileImport } from '@stonyx/utils/file';
|
|
5
|
-
import { encrypt, decrypt, deriveKey, generateSessionKey } from './encryption.js';
|
|
6
|
-
|
|
7
|
-
let clientId = 0;
|
|
8
|
-
|
|
9
|
-
export default class SocketServer {
|
|
10
|
-
clientMap = new Map();
|
|
11
|
-
handlers = {};
|
|
12
|
-
|
|
13
|
-
constructor() {
|
|
14
|
-
if (SocketServer.instance) return SocketServer.instance;
|
|
15
|
-
SocketServer.instance = this;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
async init() {
|
|
19
|
-
await this.discoverHandlers();
|
|
20
|
-
this.validateAuthHandler();
|
|
21
|
-
|
|
22
|
-
const { port, encryption, authKey } = config.sockets;
|
|
23
|
-
this.encryptionEnabled = encryption === 'true' || encryption === true;
|
|
24
|
-
|
|
25
|
-
if (this.encryptionEnabled) {
|
|
26
|
-
this.globalKey = deriveKey(authKey);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const wss = new WebSocketServer({ port });
|
|
30
|
-
this.wss = wss;
|
|
31
|
-
|
|
32
|
-
log.socket(`WebSocket server is listening on port ${port}`);
|
|
33
|
-
|
|
34
|
-
wss.on('connection', (client, request) => {
|
|
35
|
-
const { remoteAddress } = request.socket;
|
|
36
|
-
log.socket(`[${remoteAddress}] Client connected`);
|
|
37
|
-
client.id = ++clientId;
|
|
38
|
-
client.ip = remoteAddress;
|
|
39
|
-
client.__authenticated = false;
|
|
40
|
-
this.prepareSend(client);
|
|
41
|
-
|
|
42
|
-
client.on('message', payload => this.onMessage(payload, client));
|
|
43
|
-
client.on('close', () => this.handleDisconnect(client));
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async discoverHandlers() {
|
|
48
|
-
const { handlerDir } = config.sockets;
|
|
49
|
-
|
|
50
|
-
await forEachFileImport(handlerDir, (HandlerClass, { name }) => {
|
|
51
|
-
const instance = new HandlerClass();
|
|
52
|
-
|
|
53
|
-
if (typeof instance.server === 'function') {
|
|
54
|
-
instance._serverRef = this;
|
|
55
|
-
this.handlers[name] = instance;
|
|
56
|
-
}
|
|
57
|
-
}, { ignoreAccessFailure: true });
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
validateAuthHandler() {
|
|
61
|
-
if (!this.handlers.auth) {
|
|
62
|
-
throw new Error('SocketServer requires an "auth" handler with a server() method');
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
async onMessage(payload, client) {
|
|
67
|
-
try {
|
|
68
|
-
let parsed;
|
|
69
|
-
|
|
70
|
-
if (this.encryptionEnabled) {
|
|
71
|
-
const key = client.__authenticated ? client.__sessionKey : this.globalKey;
|
|
72
|
-
const raw = Buffer.isBuffer(payload) ? payload : Buffer.from(payload);
|
|
73
|
-
parsed = JSON.parse(decrypt(raw, key));
|
|
74
|
-
} else {
|
|
75
|
-
parsed = JSON.parse(payload);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const { request, data } = parsed;
|
|
79
|
-
|
|
80
|
-
// Built-in heartbeat — no handler needed
|
|
81
|
-
if (request === 'heartBeat') {
|
|
82
|
-
if (client.__authenticated) client.send({ request: 'heartBeat', response: true });
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const handler = this.handlers[request];
|
|
87
|
-
|
|
88
|
-
if (!handler) {
|
|
89
|
-
log.socket(`Invalid request received: ${request}`);
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (request !== 'auth' && !handler.constructor.skipAuth && !client.__authenticated) {
|
|
94
|
-
log.socket(`Rejected unauthenticated request: ${request}`);
|
|
95
|
-
client.close();
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const response = await handler.server(data, client);
|
|
100
|
-
if (response === undefined || response === null) return;
|
|
101
|
-
|
|
102
|
-
if (request === 'auth' && response) {
|
|
103
|
-
client.__authenticated = true;
|
|
104
|
-
|
|
105
|
-
if (this.encryptionEnabled) {
|
|
106
|
-
const sessionKey = generateSessionKey();
|
|
107
|
-
client.__sessionKey = sessionKey;
|
|
108
|
-
client.send({ request, response, sessionKey: sessionKey.toString('base64') }, this.globalKey);
|
|
109
|
-
} else {
|
|
110
|
-
client.send({ request, response });
|
|
111
|
-
}
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
client.send({ request, response });
|
|
116
|
-
} catch (error) {
|
|
117
|
-
log.socket(`Invalid payload from client`);
|
|
118
|
-
if (config.debug) console.error(error);
|
|
119
|
-
client.close();
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
prepareSend(client) {
|
|
124
|
-
const { send: socketSend } = client;
|
|
125
|
-
const server = this;
|
|
126
|
-
|
|
127
|
-
client.send = (payload, keyOverride) => {
|
|
128
|
-
if (server.encryptionEnabled) {
|
|
129
|
-
const key = keyOverride || client.__sessionKey;
|
|
130
|
-
const data = encrypt(JSON.stringify(payload), key);
|
|
131
|
-
socketSend.bind(client)(data);
|
|
132
|
-
} else {
|
|
133
|
-
socketSend.bind(client)(JSON.stringify(payload));
|
|
134
|
-
}
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
handleDisconnect(client) {
|
|
139
|
-
const { ip } = client;
|
|
140
|
-
log.socket(`[${ip}] Client disconnected`);
|
|
141
|
-
this.clientMap.delete(client.id);
|
|
142
|
-
this.onClientDisconnect?.(client);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
sendTo(clientId, request, response) {
|
|
146
|
-
const client = this.clientMap.get(clientId);
|
|
147
|
-
if (!client) return;
|
|
148
|
-
client.send({ request, response });
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
sendToByMeta(key, value, request, response) {
|
|
152
|
-
for (const [, client] of this.clientMap) {
|
|
153
|
-
if (client.meta?.[key] === value && client.__authenticated) {
|
|
154
|
-
client.send({ request, response });
|
|
155
|
-
return true;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
return false;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
broadcast(request, response) {
|
|
162
|
-
for (const [, client] of this.clientMap) {
|
|
163
|
-
if (client.__authenticated) {
|
|
164
|
-
client.send({ request, response });
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
close() {
|
|
170
|
-
if (this.wss) {
|
|
171
|
-
for (const client of this.wss.clients) {
|
|
172
|
-
client.terminate();
|
|
173
|
-
}
|
|
174
|
-
this.wss.close();
|
|
175
|
-
this.wss = null;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
reset() {
|
|
180
|
-
this.close();
|
|
181
|
-
this.clientMap.clear();
|
|
182
|
-
this.handlers = {};
|
|
183
|
-
clientId = 0;
|
|
184
|
-
SocketServer.instance = null;
|
|
185
|
-
}
|
|
186
|
-
}
|