wassocketesm 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # WASSocket ESM
2
+
3
+ ESM build for modern Node.js.
4
+
5
+ ## Installation
6
+ ```bash
7
+ npm install wassocket-esm
8
+ ```
9
+ ## Server Example
10
+ ```js
11
+ import { WASSocketServer } from "wassocket-esm"; const server = new
12
+ WASSocketServer({ port: 3000 }); server.start();
13
+ ```
14
+ ## Client Example
15
+ ```js
16
+ import { WASSocketClient } from "wassocket-esm"; const client = new
17
+ WASSocketClient("ws://localhost:3000"); await client.connect();
18
+ ```
19
+ ## Advanced API
20
+
21
+ Same API as CommonJS version: Namespace, Rooms, Middleware, Plugins.
22
+
23
+ ## Cluster & Redis Scaling
24
+ ```js
25
+ import { RedisAdapter } from "wassocket-esm/adapters";
26
+ server.useAdapter(new RedisAdapter({ url: "redis://localhost:6379" }));
27
+ ```
28
+ ## Admin Dashboard JWT
29
+
30
+ Use JWT token to authenticate dashboard access.
@@ -0,0 +1,11 @@
1
+ export type RedisMessageHandler = (channel: string, data: any) => void;
2
+ export declare class RedisAdapter {
3
+ private pub;
4
+ private sub;
5
+ private handlers;
6
+ constructor(url?: string);
7
+ connect(): Promise<void>;
8
+ subscribe(channel: string): Promise<void>;
9
+ onMessage(handler: RedisMessageHandler): void;
10
+ publish(channel: string, data: any): void;
11
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Packet opcode
3
+ */
4
+ export declare enum OpCode {
5
+ PING = 1,
6
+ PONG = 2,
7
+ EVENT = 3,
8
+ JOIN = 4,
9
+ LEAVE = 5,
10
+ ACK = 6
11
+ }
12
+ export interface Packet {
13
+ op: OpCode;
14
+ id?: number;
15
+ ack?: number;
16
+ event?: string;
17
+ room?: string;
18
+ data?: any;
19
+ }
20
+ /**
21
+ * Encryption options
22
+ */
23
+ export interface EncryptionOptions {
24
+ mode: "none" | "xor" | "aes";
25
+ secret: string;
26
+ }
27
+ /**
28
+ * Binary Transport
29
+ * Encode / Decode Packet to Buffer
30
+ */
31
+ export declare class BinaryTransport {
32
+ private encryption?;
33
+ constructor(encryption?: EncryptionOptions);
34
+ encode(packet: Packet): Buffer;
35
+ decode(buffer: Buffer): Packet;
36
+ private encrypt;
37
+ private decrypt;
38
+ private xor;
39
+ private aesEncrypt;
40
+ private aesDecrypt;
41
+ }
@@ -0,0 +1,36 @@
1
+ type Handler = (data: any) => void;
2
+ export interface ClientOptions {
3
+ encryption?: {
4
+ mode: "none" | "xor" | "aes";
5
+ secret: string;
6
+ };
7
+ reconnect?: {
8
+ enabled?: boolean;
9
+ maxDelay?: number;
10
+ };
11
+ }
12
+ export declare class WASSocketClient {
13
+ private options?;
14
+ private socket?;
15
+ private handlers;
16
+ private transport;
17
+ private url;
18
+ private connected;
19
+ private reconnectAttempts;
20
+ private reconnectTimer?;
21
+ private offlineQueue;
22
+ private packetId;
23
+ private pendingAck;
24
+ constructor(options?: ClientOptions | undefined);
25
+ connect(url: string): void;
26
+ private createSocket;
27
+ private scheduleReconnect;
28
+ private flushQueue;
29
+ private send;
30
+ emit(event: string, data?: any, room?: string): Promise<void>;
31
+ join(room: string): void;
32
+ leave(room: string): void;
33
+ on(event: string, handler: Handler): void;
34
+ private handleMessage;
35
+ }
36
+ export {};
@@ -0,0 +1 @@
1
+ export { WASSocketClient } from "./client/WASSocketClient";
@@ -0,0 +1,5 @@
1
+ export interface ClusterOptions {
2
+ workers?: number;
3
+ respawn?: boolean;
4
+ }
5
+ export declare function startCluster(startWorker: () => void, options?: ClusterOptions): void;
@@ -0,0 +1,9 @@
1
+ export interface DashboardOptions {
2
+ port?: number;
3
+ jwtSecret: string;
4
+ adminUser?: {
5
+ username: string;
6
+ password: string;
7
+ };
8
+ }
9
+ export declare function dashboardPlugin(options: DashboardOptions): () => void;
@@ -0,0 +1,3 @@
1
+ export { WASSocketServer } from "./server/WASSocketServer";
2
+ export { WASSocketClient } from "./client/WASSocketClient";
3
+ export * from "./protocol";
package/dist/index.mjs ADDED
@@ -0,0 +1,539 @@
1
+ import WebSocket, { WebSocketServer } from 'ws';
2
+ import https from 'https';
3
+ import fs from 'fs';
4
+ import crypto from 'crypto';
5
+ import http from 'http';
6
+ import greenlock from 'greenlock-express';
7
+ import { createClient } from 'redis';
8
+
9
+ function createAutoTLSServer(handler, options) {
10
+ return new Promise(resolve => {
11
+ const glx = greenlock.init({
12
+ packageRoot: process.cwd(),
13
+ configDir: "./greenlock",
14
+ maintainerEmail: options.email,
15
+ cluster: false,
16
+ staging: options.production === false
17
+ });
18
+ const httpServer = http.createServer(glx.middleware());
19
+ const httpsServer = https.createServer(glx.httpsOptions, handler);
20
+ glx.ready(() => {
21
+ console.log("[AutoTLS] Greenlock ready");
22
+ resolve({
23
+ httpServer,
24
+ httpsServer
25
+ });
26
+ });
27
+ });
28
+ }
29
+
30
+ var OpCode;
31
+ (function (OpCode) {
32
+ OpCode[OpCode["EVENT"] = 1] = "EVENT";
33
+ OpCode[OpCode["ACK"] = 2] = "ACK";
34
+ OpCode[OpCode["JOIN"] = 3] = "JOIN";
35
+ OpCode[OpCode["LEAVE"] = 4] = "LEAVE";
36
+ OpCode[OpCode["PING"] = 5] = "PING";
37
+ OpCode[OpCode["PONG"] = 6] = "PONG";
38
+ })(OpCode || (OpCode = {}));
39
+
40
+ class CryptoBox {
41
+ constructor(options) {
42
+ this.options = options;
43
+ if ((options === null || options === void 0 ? void 0 : options.mode) === "aes") {
44
+ this.key = crypto
45
+ .createHash("sha256")
46
+ .update(options.secret)
47
+ .digest();
48
+ }
49
+ else {
50
+ this.key = Buffer.alloc(0);
51
+ }
52
+ }
53
+ encrypt(data) {
54
+ if (!this.options || this.options.mode === "none") {
55
+ return data;
56
+ }
57
+ if (this.options.mode === "xor") {
58
+ return this.xor(data);
59
+ }
60
+ if (this.options.mode === "aes") {
61
+ const iv = crypto.randomBytes(12);
62
+ const cipher = crypto.createCipheriv("aes-256-gcm", this.key, iv);
63
+ const encrypted = Buffer.concat([
64
+ cipher.update(data),
65
+ cipher.final()
66
+ ]);
67
+ const tag = cipher.getAuthTag();
68
+ return Buffer.concat([
69
+ Buffer.from([iv.length]),
70
+ iv,
71
+ Buffer.from([tag.length]),
72
+ tag,
73
+ encrypted
74
+ ]);
75
+ }
76
+ return data;
77
+ }
78
+ decrypt(data) {
79
+ if (!this.options || this.options.mode === "none") {
80
+ return data;
81
+ }
82
+ if (this.options.mode === "xor") {
83
+ return this.xor(data);
84
+ }
85
+ if (this.options.mode === "aes") {
86
+ let offset = 0;
87
+ const ivLen = data.readUInt8(offset++);
88
+ const iv = data.subarray(offset, offset + ivLen);
89
+ offset += ivLen;
90
+ const tagLen = data.readUInt8(offset++);
91
+ const tag = data.subarray(offset, offset + tagLen);
92
+ offset += tagLen;
93
+ const encrypted = data.subarray(offset);
94
+ const decipher = crypto.createDecipheriv("aes-256-gcm", this.key, iv);
95
+ decipher.setAuthTag(tag);
96
+ return Buffer.concat([
97
+ decipher.update(encrypted),
98
+ decipher.final()
99
+ ]);
100
+ }
101
+ return data;
102
+ }
103
+ xor(buf) {
104
+ var _a;
105
+ const key = Buffer.from(((_a = this.options) === null || _a === void 0 ? void 0 : _a.secret) || "x");
106
+ const out = Buffer.allocUnsafe(buf.length);
107
+ for (let i = 0; i < buf.length; i++) {
108
+ out[i] = buf[i] ^ key[i % key.length];
109
+ }
110
+ return out;
111
+ }
112
+ }
113
+
114
+ class BinaryTransport {
115
+ constructor(encryption) {
116
+ this.crypto = new CryptoBox(encryption);
117
+ }
118
+ encode(packet) {
119
+ const json = JSON.stringify(packet);
120
+ const raw = Buffer.from(json);
121
+ return this.crypto.encrypt(raw);
122
+ }
123
+ decode(buffer) {
124
+ const raw = this.crypto.decrypt(buffer);
125
+ return JSON.parse(raw.toString());
126
+ }
127
+ }
128
+
129
+ class RoomManager {
130
+ constructor() {
131
+ this.rooms = new Map();
132
+ }
133
+ join(room, clientId) {
134
+ if (!this.rooms.has(room)) {
135
+ this.rooms.set(room, new Set());
136
+ }
137
+ this.rooms.get(room).add(clientId);
138
+ }
139
+ leave(room, clientId) {
140
+ var _a;
141
+ (_a = this.rooms.get(room)) === null || _a === void 0 ? void 0 : _a.delete(clientId);
142
+ }
143
+ removeClient(clientId) {
144
+ for (const set of this.rooms.values()) {
145
+ set.delete(clientId);
146
+ }
147
+ }
148
+ getClients(room) {
149
+ var _a;
150
+ return (_a = this.rooms.get(room)) !== null && _a !== void 0 ? _a : new Set();
151
+ }
152
+ }
153
+
154
+ class RateLimiter {
155
+ constructor(options) {
156
+ this.options = options;
157
+ this.buckets = new Map();
158
+ }
159
+ allow(id) {
160
+ const now = Date.now();
161
+ let bucket = this.buckets.get(id);
162
+ if (!bucket) {
163
+ bucket = {
164
+ tokens: this.options.capacity,
165
+ last: now
166
+ };
167
+ this.buckets.set(id, bucket);
168
+ }
169
+ const delta = (now - bucket.last) / 1000;
170
+ bucket.tokens = Math.min(this.options.capacity, bucket.tokens + delta * this.options.refillPerSecond);
171
+ bucket.last = now;
172
+ if (bucket.tokens < 1)
173
+ return false;
174
+ bucket.tokens -= 1;
175
+ return true;
176
+ }
177
+ clear(id) {
178
+ this.buckets.delete(id);
179
+ }
180
+ }
181
+
182
+ class PacketInspector {
183
+ constructor() {
184
+ this.listeners = new Set();
185
+ }
186
+ onPacket(cb) {
187
+ this.listeners.add(cb);
188
+ }
189
+ emit(packet) {
190
+ for (const cb of this.listeners) {
191
+ cb(packet);
192
+ }
193
+ }
194
+ }
195
+ const packetInspector = new PacketInspector();
196
+
197
+ class RedisAdapter {
198
+ constructor(url = "redis://127.0.0.1:6379") {
199
+ this.handlers = new Set();
200
+ this.pub = createClient({ url });
201
+ this.sub = createClient({ url });
202
+ }
203
+ async connect() {
204
+ await this.pub.connect();
205
+ await this.sub.connect();
206
+ console.log("[RedisAdapter] Connected");
207
+ }
208
+ async subscribe(channel) {
209
+ await this.sub.subscribe(channel, message => {
210
+ try {
211
+ const data = JSON.parse(message);
212
+ for (const h of this.handlers) {
213
+ h(channel, data);
214
+ }
215
+ }
216
+ catch { }
217
+ });
218
+ }
219
+ onMessage(handler) {
220
+ this.handlers.add(handler);
221
+ }
222
+ publish(channel, data) {
223
+ this.pub.publish(channel, JSON.stringify(data));
224
+ }
225
+ }
226
+
227
+ class WASSocketServer {
228
+ constructor(port, options) {
229
+ var _a;
230
+ this.namespaces = new Map();
231
+ this.transport = new BinaryTransport(options === null || options === void 0 ? void 0 : options.encryption);
232
+ this.authProvider = (_a = options === null || options === void 0 ? void 0 : options.auth) === null || _a === void 0 ? void 0 : _a.provider;
233
+ if (options === null || options === void 0 ? void 0 : options.rateLimit) {
234
+ this.rateLimiter = new RateLimiter(options.rateLimit);
235
+ }
236
+ if (options === null || options === void 0 ? void 0 : options.redisUrl) {
237
+ this.redis = new RedisAdapter(options.redisUrl);
238
+ this.redis.connect();
239
+ this.redis.subscribe("wassocket:broadcast");
240
+ this.redis.onMessage((_ch, packet) => {
241
+ // broadcast packet from other nodes
242
+ for (const [, ns] of this.namespaces) {
243
+ for (const [, client] of ns.clients) {
244
+ this.send(client, packet);
245
+ }
246
+ }
247
+ });
248
+ }
249
+ if (options === null || options === void 0 ? void 0 : options.autoTLS) {
250
+ console.log("[WASSocket] Starting AutoTLS...");
251
+ createAutoTLSServer((req, res) => {
252
+ // WebSocket upgrade handled by ws internally
253
+ }, {
254
+ email: options.autoTLS.email,
255
+ domain: options.autoTLS.domain,
256
+ production: options.autoTLS.production
257
+ }).then(({ httpsServer }) => {
258
+ this.wss = new WebSocketServer({ server: httpsServer });
259
+ httpsServer.listen(port);
260
+ console.log(`[WASSocket] WSS AutoTLS running on :${port}`);
261
+ });
262
+ }
263
+ else if (options === null || options === void 0 ? void 0 : options.tls) {
264
+ const httpsServer = https.createServer({
265
+ key: fs.readFileSync(options.tls.key),
266
+ cert: fs.readFileSync(options.tls.cert)
267
+ });
268
+ this.wss = new WebSocketServer({ server: httpsServer });
269
+ httpsServer.listen(port);
270
+ console.log(`[WASSocket] WSS running on ${port}`);
271
+ }
272
+ else {
273
+ this.wss = new WebSocketServer({ port });
274
+ console.log(`[WASSocket] WS running on ${port}`);
275
+ }
276
+ this.wss.on("connection", (socket, req) => this.handleConnection(socket, req.url || "/"));
277
+ this.namespace("/");
278
+ }
279
+ // ================= NAMESPACE =================
280
+ namespace(name) {
281
+ if (!name.startsWith("/"))
282
+ name = "/" + name;
283
+ if (!this.namespaces.has(name)) {
284
+ this.namespaces.set(name, {
285
+ name,
286
+ clients: new Map(),
287
+ rooms: new RoomManager()
288
+ });
289
+ }
290
+ return this.namespaces.get(name);
291
+ }
292
+ // ================= CONNECTION =================
293
+ async handleConnection(socket, url) {
294
+ const path = url.split("?")[0] || "/";
295
+ const ns = this.namespace(path);
296
+ // ================= AUTH =================
297
+ const queryString = url.split("?")[1];
298
+ const query = new URLSearchParams(queryString);
299
+ const token = query.get("token") || undefined;
300
+ let user = undefined;
301
+ if (this.authProvider) {
302
+ if (!token) {
303
+ console.warn("[WASSocket] Missing auth token");
304
+ socket.close();
305
+ return;
306
+ }
307
+ try {
308
+ const verified = await this.authProvider.verifyToken(token);
309
+ if (!verified) {
310
+ console.warn("[WASSocket] Invalid token");
311
+ socket.close();
312
+ return;
313
+ }
314
+ user = verified;
315
+ }
316
+ catch (err) {
317
+ console.warn("[WASSocket] Auth error", err);
318
+ socket.close();
319
+ return;
320
+ }
321
+ }
322
+ // ================= CLIENT =================
323
+ const id = crypto.randomUUID();
324
+ const client = {
325
+ id,
326
+ socket,
327
+ lastPing: Date.now(),
328
+ user
329
+ };
330
+ ns.clients.set(id, client);
331
+ // ================= EVENTS =================
332
+ socket.on("message", (data) => {
333
+ if (Buffer.isBuffer(data)) {
334
+ this.handleMessage(ns, client, data);
335
+ }
336
+ });
337
+ socket.on("close", () => {
338
+ ns.clients.delete(id);
339
+ ns.rooms.removeClient(id);
340
+ });
341
+ }
342
+ // ================= MESSAGE =================
343
+ handleMessage(ns, client, buffer) {
344
+ try {
345
+ const packet = this.transport.decode(buffer);
346
+ packetInspector.emit(packet);
347
+ if (packet.id) {
348
+ this.send(client, {
349
+ op: OpCode.ACK,
350
+ ack: packet.id
351
+ });
352
+ }
353
+ switch (packet.op) {
354
+ case OpCode.PING:
355
+ client.lastPing = Date.now();
356
+ this.send(client, { op: OpCode.PONG });
357
+ break;
358
+ case OpCode.JOIN:
359
+ packet.room && ns.rooms.join(packet.room, client.id);
360
+ break;
361
+ case OpCode.LEAVE:
362
+ packet.room && ns.rooms.leave(packet.room, client.id);
363
+ break;
364
+ case OpCode.EVENT:
365
+ if (!packet.event)
366
+ break;
367
+ if (packet.room) {
368
+ this.broadcastRoom(ns, packet.room, packet, client.id);
369
+ }
370
+ else {
371
+ this.broadcast(ns, packet, client.id);
372
+ }
373
+ break;
374
+ }
375
+ }
376
+ catch (err) {
377
+ console.warn("[WASSocket] Message error", err);
378
+ }
379
+ }
380
+ // ================= SEND =================
381
+ send(client, packet) {
382
+ const buffer = this.transport.encode(packet);
383
+ client.socket.send(buffer);
384
+ packetInspector.emit(packet);
385
+ }
386
+ broadcast(ns, packet, exceptId) {
387
+ for (const [id, client] of ns.clients) {
388
+ if (id === exceptId)
389
+ continue;
390
+ this.send(client, packet);
391
+ }
392
+ if (this.redis) {
393
+ this.redis.publish("wassocket:broadcast", packet);
394
+ }
395
+ }
396
+ broadcastRoom(ns, room, packet, exceptId) {
397
+ const ids = ns.rooms.getClients(room);
398
+ for (const id of ids) {
399
+ if (id === exceptId)
400
+ continue;
401
+ const client = ns.clients.get(id);
402
+ if (client)
403
+ this.send(client, packet);
404
+ }
405
+ if (this.redis) {
406
+ this.redis.publish("wassocket:broadcast", packet);
407
+ }
408
+ }
409
+ }
410
+
411
+ class WASSocketClient {
412
+ constructor(options) {
413
+ this.options = options;
414
+ this.handlers = new Map();
415
+ this.url = "";
416
+ this.connected = false;
417
+ this.reconnectAttempts = 0;
418
+ this.offlineQueue = [];
419
+ this.packetId = 1;
420
+ this.pendingAck = new Map();
421
+ this.transport = new BinaryTransport(options === null || options === void 0 ? void 0 : options.encryption);
422
+ }
423
+ // ================= CONNECT =================
424
+ connect(url) {
425
+ this.url = url;
426
+ this.createSocket();
427
+ }
428
+ createSocket() {
429
+ this.socket = new WebSocket(this.url);
430
+ this.socket.on("open", () => {
431
+ console.log("[WASSocket] Connected");
432
+ this.connected = true;
433
+ this.reconnectAttempts = 0;
434
+ this.flushQueue();
435
+ });
436
+ this.socket.on("message", data => {
437
+ if (Buffer.isBuffer(data)) {
438
+ this.handleMessage(data);
439
+ }
440
+ });
441
+ this.socket.on("close", () => {
442
+ console.warn("[WASSocket] Disconnected");
443
+ this.connected = false;
444
+ this.scheduleReconnect();
445
+ });
446
+ }
447
+ // ================= RECONNECT =================
448
+ scheduleReconnect() {
449
+ var _a, _b, _c, _d, _e;
450
+ if (((_b = (_a = this.options) === null || _a === void 0 ? void 0 : _a.reconnect) === null || _b === void 0 ? void 0 : _b.enabled) === false)
451
+ return;
452
+ const maxDelay = (_e = (_d = (_c = this.options) === null || _c === void 0 ? void 0 : _c.reconnect) === null || _d === void 0 ? void 0 : _d.maxDelay) !== null && _e !== void 0 ? _e : 15000;
453
+ const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), maxDelay);
454
+ this.reconnectAttempts++;
455
+ clearTimeout(this.reconnectTimer);
456
+ console.log(`[WASSocket] Reconnect in ${delay}ms`);
457
+ this.reconnectTimer = setTimeout(() => {
458
+ this.createSocket();
459
+ }, delay);
460
+ }
461
+ // ================= QUEUE =================
462
+ flushQueue() {
463
+ while (this.offlineQueue.length > 0) {
464
+ this.send(this.offlineQueue.shift());
465
+ }
466
+ }
467
+ // ================= SEND =================
468
+ send(packet) {
469
+ if (!this.connected || !this.socket) {
470
+ this.offlineQueue.push(packet);
471
+ return;
472
+ }
473
+ const buffer = this.transport.encode(packet);
474
+ this.socket.send(buffer);
475
+ }
476
+ // ================= API =================
477
+ emit(event, data, room) {
478
+ const id = this.packetId++;
479
+ const packet = {
480
+ op: OpCode.EVENT,
481
+ event,
482
+ data,
483
+ room,
484
+ id
485
+ };
486
+ return new Promise(resolve => {
487
+ const timer = setTimeout(() => {
488
+ console.warn("[WASSocket] ACK timeout, retry", id);
489
+ this.send(packet);
490
+ }, 3000);
491
+ this.pendingAck.set(id, {
492
+ resolve,
493
+ timer,
494
+ packet
495
+ });
496
+ this.send(packet);
497
+ });
498
+ }
499
+ join(room) {
500
+ this.send({ op: OpCode.JOIN, room });
501
+ }
502
+ leave(room) {
503
+ this.send({ op: OpCode.LEAVE, room });
504
+ }
505
+ on(event, handler) {
506
+ if (!this.handlers.has(event)) {
507
+ this.handlers.set(event, new Set());
508
+ }
509
+ this.handlers.get(event).add(handler);
510
+ }
511
+ // ================= MESSAGE =================
512
+ handleMessage(buffer) {
513
+ try {
514
+ const packet = this.transport.decode(buffer);
515
+ if (packet.op === OpCode.ACK && packet.ack) {
516
+ const pending = this.pendingAck.get(packet.ack);
517
+ if (pending) {
518
+ clearTimeout(pending.timer);
519
+ pending.resolve();
520
+ this.pendingAck.delete(packet.ack);
521
+ }
522
+ return;
523
+ }
524
+ if (packet.op === OpCode.EVENT && packet.event) {
525
+ const set = this.handlers.get(packet.event);
526
+ if (!set)
527
+ return;
528
+ for (const fn of set)
529
+ fn(packet.data);
530
+ }
531
+ }
532
+ catch (err) {
533
+ console.warn("[WASSocket] Decode error", err);
534
+ }
535
+ }
536
+ }
537
+
538
+ export { OpCode, WASSocketClient, WASSocketServer };
539
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,15 @@
1
+ import { Packet } from "./protocol";
2
+ export interface MiddlewareContext {
3
+ clientId: string;
4
+ packet: Packet;
5
+ }
6
+ export type Middleware = (ctx: MiddlewareContext, next: () => Promise<void>) => Promise<void | boolean>;
7
+ export declare class MiddlewareManager {
8
+ private pre;
9
+ private post;
10
+ usePre(fn: Middleware): void;
11
+ usePost(fn: Middleware): void;
12
+ runPre(ctx: MiddlewareContext): Promise<boolean>;
13
+ runPost(ctx: MiddlewareContext): Promise<boolean>;
14
+ private runChain;
15
+ }
@@ -0,0 +1,13 @@
1
+ export declare class Metrics {
2
+ connections: number;
3
+ packetsIn: number;
4
+ packetsOut: number;
5
+ startTime: number;
6
+ snapshot(): {
7
+ uptime: number;
8
+ connections: number;
9
+ packetsIn: number;
10
+ packetsOut: number;
11
+ };
12
+ }
13
+ export declare const metrics: Metrics;
@@ -0,0 +1,12 @@
1
+ import { Packet } from "../protocol";
2
+ export interface PacketLog {
3
+ time: number;
4
+ direction: "IN" | "OUT";
5
+ clientId: string;
6
+ packet: Packet;
7
+ }
8
+ export declare class PacketInspector {
9
+ private logs;
10
+ record(direction: "IN" | "OUT", clientId: string, packet: Packet): void;
11
+ snapshot(limit?: number): PacketLog[];
12
+ }
@@ -0,0 +1,8 @@
1
+ type PacketListener = (packet: any) => void;
2
+ export declare class PacketInspector {
3
+ private listeners;
4
+ onPacket(cb: PacketListener): void;
5
+ emit(packet: any): void;
6
+ }
7
+ export declare const packetInspector: PacketInspector;
8
+ export {};
@@ -0,0 +1,23 @@
1
+ import { ClientContext } from "./server/WASSocketServer";
2
+ export declare class RoomManager {
3
+ private rooms;
4
+ join(room: string, clientId: string): void;
5
+ leave(room: string, clientId: string): void;
6
+ getClients(room: string): Set<string>;
7
+ }
8
+ export declare class Middleware {
9
+ private pre;
10
+ private post;
11
+ use(fn: Function): void;
12
+ runPre(ctx: any): Promise<boolean>;
13
+ runPost(ctx: any): Promise<void>;
14
+ }
15
+ export declare class Namespace {
16
+ name: string;
17
+ clients: Map<string, ClientContext>;
18
+ rooms: RoomManager;
19
+ middleware: Middleware;
20
+ constructor(name: string);
21
+ addClient(client: ClientContext): void;
22
+ removeClient(id: string): void;
23
+ }
@@ -0,0 +1,15 @@
1
+ import { WASSocketServer } from "./server/WASSocketServer";
2
+ import { Namespace } from "./namespace";
3
+ export interface WASSocketPlugin {
4
+ name: string;
5
+ install(server: WASSocketServer): void;
6
+ uninstall?(server: WASSocketServer): void;
7
+ onNamespaceCreate?(ns: Namespace): void;
8
+ }
9
+ export declare class PluginManager {
10
+ private plugins;
11
+ install(plugin: WASSocketPlugin, server: WASSocketServer): void;
12
+ uninstall(name: string, server: WASSocketServer): void;
13
+ notifyNamespaceCreate(ns: Namespace): void;
14
+ list(): string[];
15
+ }
@@ -0,0 +1,16 @@
1
+ export declare enum OpCode {
2
+ EVENT = 1,
3
+ ACK = 2,
4
+ JOIN = 3,
5
+ LEAVE = 4,
6
+ PING = 5,
7
+ PONG = 6
8
+ }
9
+ export interface Packet {
10
+ op: OpCode;
11
+ id?: number;
12
+ ack?: number;
13
+ event?: string;
14
+ data?: any;
15
+ room?: string;
16
+ }
@@ -0,0 +1,17 @@
1
+ export interface AuthUser {
2
+ id: string;
3
+ name?: string;
4
+ roles?: string[];
5
+ }
6
+ export interface AuthProvider {
7
+ verifyToken(token: string): Promise<AuthUser | null>;
8
+ }
9
+ export interface JwtOptions {
10
+ secret: string;
11
+ }
12
+ export declare class JwtAuthProvider implements AuthProvider {
13
+ private options;
14
+ constructor(options: JwtOptions);
15
+ verifyToken(token: string): Promise<AuthUser | null>;
16
+ static sign(payload: AuthUser, secret: string, expiresIn?: string): any;
17
+ }
@@ -0,0 +1,2 @@
1
+ import { AuthProvider } from "./Auth";
2
+ export declare function authPlugin(provider: AuthProvider): (server: any) => void;
@@ -0,0 +1,13 @@
1
+ import http from "http";
2
+ import https from "https";
3
+ export interface AutoTLSOptions {
4
+ email: string;
5
+ domain: string;
6
+ agreeTos?: boolean;
7
+ production?: boolean;
8
+ }
9
+ export interface AutoTLSServer {
10
+ httpServer: http.Server;
11
+ httpsServer: https.Server;
12
+ }
13
+ export declare function createAutoTLSServer(handler: (req: http.IncomingMessage, res: http.ServerResponse) => void, options: AutoTLSOptions): Promise<AutoTLSServer>;
@@ -0,0 +1,11 @@
1
+ export interface RateLimitOptions {
2
+ capacity: number;
3
+ refillPerSecond: number;
4
+ }
5
+ export declare class RateLimiter {
6
+ private options;
7
+ private buckets;
8
+ constructor(options: RateLimitOptions);
9
+ allow(id: string): boolean;
10
+ clear(id: string): void;
11
+ }
@@ -0,0 +1,7 @@
1
+ export declare class RoomManager {
2
+ private rooms;
3
+ join(room: string, clientId: string): void;
4
+ leave(room: string, clientId: string): void;
5
+ removeClient(clientId: string): void;
6
+ getClients(room: string): Set<string>;
7
+ }
@@ -0,0 +1,56 @@
1
+ import WebSocket from "ws";
2
+ import { Packet } from "../protocol";
3
+ import { BinaryTransport } from "../transport/binary";
4
+ import { RoomManager } from "./RoomManager";
5
+ import { AuthProvider } from "../security/Auth";
6
+ export interface ServerOptions {
7
+ encryption?: {
8
+ mode: "none" | "xor" | "aes";
9
+ secret: string;
10
+ };
11
+ auth?: {
12
+ provider: AuthProvider;
13
+ };
14
+ rateLimit?: {
15
+ capacity: number;
16
+ refillPerSecond: number;
17
+ };
18
+ tls?: {
19
+ key: string;
20
+ cert: string;
21
+ };
22
+ autoTLS?: {
23
+ email: string;
24
+ domain: string;
25
+ production?: boolean;
26
+ };
27
+ }
28
+ export interface ClientContext {
29
+ id: string;
30
+ socket: WebSocket;
31
+ lastPing: number;
32
+ user?: any;
33
+ }
34
+ interface Namespace {
35
+ name: string;
36
+ clients: Map<string, ClientContext>;
37
+ rooms: RoomManager;
38
+ }
39
+ export declare class WASSocketServer {
40
+ private authProvider?;
41
+ private rateLimiter?;
42
+ private redis?;
43
+ private wss;
44
+ private namespaces;
45
+ transport: BinaryTransport;
46
+ constructor(port: number, options?: ServerOptions & {
47
+ redisUrl?: string;
48
+ });
49
+ namespace(name: string): Namespace;
50
+ private handleConnection;
51
+ private handleMessage;
52
+ private send;
53
+ private broadcast;
54
+ protected broadcastRoom(ns: Namespace, room: string, packet: Packet, exceptId?: string): void;
55
+ }
56
+ export {};
@@ -0,0 +1,8 @@
1
+ import { Packet } from "../protocol";
2
+ import { EncryptionOptions } from "./crypto";
3
+ export declare class BinaryTransport {
4
+ private crypto;
5
+ constructor(encryption?: EncryptionOptions);
6
+ encode(packet: Packet): Buffer;
7
+ decode(buffer: Buffer): Packet;
8
+ }
@@ -0,0 +1,13 @@
1
+ export type EncryptionMode = "none" | "xor" | "aes";
2
+ export interface EncryptionOptions {
3
+ mode: EncryptionMode;
4
+ secret: string;
5
+ }
6
+ export declare class CryptoBox {
7
+ private options?;
8
+ private key;
9
+ constructor(options?: EncryptionOptions | undefined);
10
+ encrypt(data: Buffer): Buffer;
11
+ decrypt(data: Buffer): Buffer;
12
+ private xor;
13
+ }
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+
2
+ {
3
+ "name": "wassocketesm",
4
+ "version": "0.1.2",
5
+ "description": "High performance WebSocket framework with rooms, middleware, redis adapter ( ESM VERSION )",
6
+ "type": "module",
7
+ "main": "dist/index.mjs",
8
+ "module": "dist/index.mjs",
9
+ "types": "../core/dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "import": "./dist/index.mjs",
13
+ "types": "./core/dist/index.d.ts"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "sideEffects": false,
20
+ "peerDependencies": {
21
+ "ws": "^8.16.0",
22
+ "redis": "^4.6.0",
23
+ "jsonwebtoken": "^9.0.0",
24
+ "greenlock-express": "^4.0.0"
25
+ }
26
+ }