@hazeljs/websocket 0.2.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +530 -0
  2. package/dist/decorators/realtime.decorator.d.ts +113 -0
  3. package/dist/decorators/realtime.decorator.d.ts.map +1 -0
  4. package/dist/decorators/realtime.decorator.js +202 -0
  5. package/dist/index.d.ts +10 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +32 -0
  8. package/dist/room/room.manager.d.ts +81 -0
  9. package/dist/room/room.manager.d.ts.map +1 -0
  10. package/dist/room/room.manager.js +209 -0
  11. package/dist/src/decorators/realtime.decorator.d.ts +113 -0
  12. package/dist/src/decorators/realtime.decorator.d.ts.map +1 -0
  13. package/dist/src/decorators/realtime.decorator.js +202 -0
  14. package/dist/src/index.d.ts +10 -0
  15. package/dist/src/index.d.ts.map +1 -0
  16. package/dist/src/index.js +32 -0
  17. package/dist/src/room/room.manager.d.ts +81 -0
  18. package/dist/src/room/room.manager.d.ts.map +1 -0
  19. package/dist/src/room/room.manager.js +209 -0
  20. package/dist/src/sse/sse.handler.d.ts +61 -0
  21. package/dist/src/sse/sse.handler.d.ts.map +1 -0
  22. package/dist/src/sse/sse.handler.js +209 -0
  23. package/dist/src/websocket.gateway.d.ts +94 -0
  24. package/dist/src/websocket.gateway.d.ts.map +1 -0
  25. package/dist/src/websocket.gateway.js +309 -0
  26. package/dist/src/websocket.module.d.ts +57 -0
  27. package/dist/src/websocket.module.d.ts.map +1 -0
  28. package/dist/src/websocket.module.js +88 -0
  29. package/dist/src/websocket.types.d.ts +258 -0
  30. package/dist/src/websocket.types.d.ts.map +1 -0
  31. package/dist/src/websocket.types.js +2 -0
  32. package/dist/sse/sse.handler.d.ts +61 -0
  33. package/dist/sse/sse.handler.d.ts.map +1 -0
  34. package/dist/sse/sse.handler.js +209 -0
  35. package/dist/tsconfig.tsbuildinfo +1 -0
  36. package/dist/websocket.gateway.d.ts +79 -0
  37. package/dist/websocket.gateway.d.ts.map +1 -0
  38. package/dist/websocket.gateway.js +214 -0
  39. package/dist/websocket.module.d.ts +57 -0
  40. package/dist/websocket.module.d.ts.map +1 -0
  41. package/dist/websocket.module.js +88 -0
  42. package/dist/websocket.types.d.ts +258 -0
  43. package/dist/websocket.types.d.ts.map +1 -0
  44. package/dist/websocket.types.js +2 -0
  45. package/package.json +48 -0
@@ -0,0 +1,209 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SSEHandler = void 0;
7
+ exports.createSSEResponse = createSSEResponse;
8
+ exports.sendSSEMessage = sendSSEMessage;
9
+ const core_1 = __importDefault(require("@hazeljs/core"));
10
+ /**
11
+ * Server-Sent Events (SSE) handler
12
+ */
13
+ class SSEHandler {
14
+ constructor() {
15
+ this.connections = new Map();
16
+ this.keepAliveIntervals = new Map();
17
+ }
18
+ /**
19
+ * Initialize SSE connection
20
+ */
21
+ initConnection(req, res, options = {}) {
22
+ const connectionId = this.generateConnectionId();
23
+ // Set SSE headers
24
+ res.writeHead(200, {
25
+ 'Content-Type': 'text/event-stream',
26
+ 'Cache-Control': 'no-cache',
27
+ Connection: 'keep-alive',
28
+ 'Access-Control-Allow-Origin': '*',
29
+ 'X-Accel-Buffering': 'no', // Disable nginx buffering
30
+ });
31
+ // Send initial retry interval
32
+ if (options.retry) {
33
+ res.write(`retry: ${options.retry}\n\n`);
34
+ }
35
+ // Store connection
36
+ this.connections.set(connectionId, res);
37
+ // Setup keep-alive
38
+ const keepAliveInterval = options.keepAlive || 30000;
39
+ const interval = setInterval(() => {
40
+ if (this.connections.has(connectionId)) {
41
+ res.write(': keep-alive\n\n');
42
+ }
43
+ else {
44
+ clearInterval(interval);
45
+ }
46
+ }, keepAliveInterval);
47
+ this.keepAliveIntervals.set(connectionId, interval);
48
+ // Handle connection close
49
+ req.on('close', () => {
50
+ this.closeConnection(connectionId);
51
+ });
52
+ core_1.default.debug(`SSE connection initialized: ${connectionId}`);
53
+ return connectionId;
54
+ }
55
+ /**
56
+ * Send message to a specific connection
57
+ */
58
+ send(connectionId, message) {
59
+ const res = this.connections.get(connectionId);
60
+ if (!res) {
61
+ return false;
62
+ }
63
+ try {
64
+ let output = '';
65
+ // Add event type
66
+ if (message.event) {
67
+ output += `event: ${message.event}\n`;
68
+ }
69
+ // Add ID
70
+ if (message.id) {
71
+ output += `id: ${message.id}\n`;
72
+ }
73
+ // Add retry
74
+ if (message.retry) {
75
+ output += `retry: ${message.retry}\n`;
76
+ }
77
+ // Add data (can be multi-line)
78
+ const data = typeof message.data === 'string' ? message.data : JSON.stringify(message.data);
79
+ const lines = data.split('\n');
80
+ for (const line of lines) {
81
+ output += `data: ${line}\n`;
82
+ }
83
+ output += '\n';
84
+ res.write(output);
85
+ return true;
86
+ }
87
+ catch (error) {
88
+ core_1.default.error(`Failed to send SSE message to ${connectionId}:`, error);
89
+ return false;
90
+ }
91
+ }
92
+ /**
93
+ * Broadcast message to all connections
94
+ */
95
+ broadcast(message) {
96
+ for (const connectionId of this.connections.keys()) {
97
+ this.send(connectionId, message);
98
+ }
99
+ core_1.default.debug(`SSE broadcast: ${message.event || 'message'}`);
100
+ }
101
+ /**
102
+ * Close a specific connection
103
+ */
104
+ closeConnection(connectionId) {
105
+ const res = this.connections.get(connectionId);
106
+ if (res) {
107
+ res.end();
108
+ this.connections.delete(connectionId);
109
+ }
110
+ const interval = this.keepAliveIntervals.get(connectionId);
111
+ if (interval) {
112
+ clearInterval(interval);
113
+ this.keepAliveIntervals.delete(connectionId);
114
+ }
115
+ core_1.default.debug(`SSE connection closed: ${connectionId}`);
116
+ }
117
+ /**
118
+ * Close all connections
119
+ */
120
+ closeAll() {
121
+ for (const connectionId of this.connections.keys()) {
122
+ this.closeConnection(connectionId);
123
+ }
124
+ core_1.default.info('All SSE connections closed');
125
+ }
126
+ /**
127
+ * Get active connection count
128
+ */
129
+ getConnectionCount() {
130
+ return this.connections.size;
131
+ }
132
+ /**
133
+ * Check if connection exists
134
+ */
135
+ hasConnection(connectionId) {
136
+ return this.connections.has(connectionId);
137
+ }
138
+ /**
139
+ * Get all connection IDs
140
+ */
141
+ getConnectionIds() {
142
+ return Array.from(this.connections.keys());
143
+ }
144
+ /**
145
+ * Generate unique connection ID
146
+ */
147
+ generateConnectionId() {
148
+ return `sse-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
149
+ }
150
+ /**
151
+ * Create a stream for continuous data
152
+ */
153
+ async *createStream(connectionId, dataSource, options = {}) {
154
+ try {
155
+ for await (const data of dataSource) {
156
+ const transformedData = options.transform ? options.transform(data) : data;
157
+ const sent = this.send(connectionId, {
158
+ event: options.event,
159
+ data: transformedData,
160
+ });
161
+ yield sent;
162
+ if (!sent) {
163
+ break;
164
+ }
165
+ }
166
+ }
167
+ catch (error) {
168
+ core_1.default.error(`SSE stream error for ${connectionId}:`, error);
169
+ }
170
+ }
171
+ }
172
+ exports.SSEHandler = SSEHandler;
173
+ /**
174
+ * Helper function to create SSE response
175
+ */
176
+ function createSSEResponse(res, options = {}) {
177
+ res.writeHead(200, {
178
+ 'Content-Type': 'text/event-stream',
179
+ 'Cache-Control': 'no-cache',
180
+ Connection: 'keep-alive',
181
+ 'Access-Control-Allow-Origin': '*',
182
+ 'X-Accel-Buffering': 'no',
183
+ });
184
+ if (options.retry) {
185
+ res.write(`retry: ${options.retry}\n\n`);
186
+ }
187
+ }
188
+ /**
189
+ * Helper function to send SSE message
190
+ */
191
+ function sendSSEMessage(res, message) {
192
+ let output = '';
193
+ if (message.event) {
194
+ output += `event: ${message.event}\n`;
195
+ }
196
+ if (message.id) {
197
+ output += `id: ${message.id}\n`;
198
+ }
199
+ if (message.retry) {
200
+ output += `retry: ${message.retry}\n`;
201
+ }
202
+ const data = typeof message.data === 'string' ? message.data : JSON.stringify(message.data);
203
+ const lines = data.split('\n');
204
+ for (const line of lines) {
205
+ output += `data: ${line}\n`;
206
+ }
207
+ output += '\n';
208
+ res.write(output);
209
+ }
@@ -0,0 +1,94 @@
1
+ import { WebSocketClient, WebSocketMessage, WebSocketStats, WebSocketServerOptions } from './websocket.types';
2
+ import { RoomManager } from './room/room.manager';
3
+ import { Server as HttpServer } from 'http';
4
+ import { WebSocketServer } from 'ws';
5
+ /**
6
+ * Base WebSocket Gateway class
7
+ */
8
+ export declare class WebSocketGateway {
9
+ protected clients: Map<string, WebSocketClient>;
10
+ protected roomManager: RoomManager;
11
+ protected wss: WebSocketServer | null;
12
+ protected stats: WebSocketStats;
13
+ /**
14
+ * Attach WebSocket server to an existing HTTP server
15
+ */
16
+ attachToServer(server: HttpServer, options?: WebSocketServerOptions): WebSocketServer;
17
+ /**
18
+ * Create a standalone WebSocket server (without HTTP server)
19
+ */
20
+ listen(port: number, options?: WebSocketServerOptions): WebSocketServer;
21
+ /**
22
+ * Close the WebSocket server
23
+ */
24
+ close(): Promise<void>;
25
+ /**
26
+ * Handle client connection
27
+ */
28
+ protected handleConnection(client: WebSocketClient): void;
29
+ /**
30
+ * Handle client disconnection
31
+ */
32
+ protected handleDisconnection(clientId: string): void;
33
+ /**
34
+ * Handle incoming message
35
+ */
36
+ protected handleMessage(clientId: string, message: WebSocketMessage): void;
37
+ /**
38
+ * Send message to a specific client
39
+ */
40
+ sendToClient(clientId: string, event: string, data: unknown): boolean;
41
+ /**
42
+ * Broadcast message to all clients
43
+ */
44
+ broadcast(event: string, data: unknown, excludeClientId?: string): void;
45
+ /**
46
+ * Broadcast to a specific room
47
+ */
48
+ broadcastToRoom(roomName: string, event: string, data: unknown, excludeClientId?: string): void;
49
+ /**
50
+ * Add client to room
51
+ */
52
+ joinRoom(clientId: string, roomName: string): void;
53
+ /**
54
+ * Remove client from room
55
+ */
56
+ leaveRoom(clientId: string, roomName: string): void;
57
+ /**
58
+ * Get all clients
59
+ */
60
+ getClients(): WebSocketClient[];
61
+ /**
62
+ * Get client by ID
63
+ */
64
+ getClient(clientId: string): WebSocketClient | undefined;
65
+ /**
66
+ * Get connected client count
67
+ */
68
+ getClientCount(): number;
69
+ /**
70
+ * Get room clients
71
+ */
72
+ getRoomClients(roomName: string): string[];
73
+ /**
74
+ * Get client rooms
75
+ */
76
+ getClientRooms(clientId: string): string[];
77
+ /**
78
+ * Get statistics
79
+ */
80
+ getStats(): WebSocketStats;
81
+ /**
82
+ * Disconnect a client
83
+ */
84
+ disconnectClient(clientId: string): void;
85
+ /**
86
+ * Disconnect all clients
87
+ */
88
+ disconnectAll(): void;
89
+ }
90
+ /**
91
+ * Create a WebSocket client wrapper
92
+ */
93
+ export declare function createWebSocketClient(socket: unknown, id: string): WebSocketClient;
94
+ //# sourceMappingURL=websocket.gateway.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"websocket.gateway.d.ts","sourceRoot":"","sources":["../../src/websocket.gateway.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAC9G,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAElD,OAAO,EAAE,MAAM,IAAI,UAAU,EAAmB,MAAM,MAAM,CAAC;AAC7D,OAAkB,EAAE,eAAe,EAAE,MAAM,IAAI,CAAC;AAGhD;;GAEG;AACH,qBAAa,gBAAgB;IAC3B,SAAS,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAa;IAC5D,SAAS,CAAC,WAAW,EAAE,WAAW,CAAqB;IACvD,SAAS,CAAC,GAAG,EAAE,eAAe,GAAG,IAAI,CAAQ;IAC7C,SAAS,CAAC,KAAK,EAAE,cAAc,CAQ7B;IAEF;;OAEG;IACH,cAAc,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,GAAE,sBAA2B,GAAG,eAAe;IAyCzF;;OAEG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,sBAA2B,GAAG,eAAe;IAqC3E;;OAEG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAkBtB;;OAEG;IACH,SAAS,CAAC,gBAAgB,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI;IAMzD;;OAEG;IACH,SAAS,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAWrD;;OAEG;IACH,SAAS,CAAC,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,GAAG,IAAI;IAM1E;;OAEG;IACH,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO;IAYrE;;OAEG;IACH,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI;IAavE;;OAEG;IACH,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI;IAK/F;;OAEG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IASlD;;OAEG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IASnD;;OAEG;IACH,UAAU,IAAI,eAAe,EAAE;IAI/B;;OAEG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS;IAIxD;;OAEG;IACH,cAAc,IAAI,MAAM;IAIxB;;OAEG;IACH,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE;IAI1C;;OAEG;IACH,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE;IAI1C;;OAEG;IACH,QAAQ,IAAI,cAAc;IAQ1B;;OAEG;IACH,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAQxC;;OAEG;IACH,aAAa,IAAI,IAAI;CAUtB;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,GAAG,eAAe,CAsClF"}
@@ -0,0 +1,309 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.WebSocketGateway = void 0;
7
+ exports.createWebSocketClient = createWebSocketClient;
8
+ const room_manager_1 = require("./room/room.manager");
9
+ const core_1 = __importDefault(require("@hazeljs/core"));
10
+ const ws_1 = require("ws");
11
+ const crypto_1 = require("crypto");
12
+ /**
13
+ * Base WebSocket Gateway class
14
+ */
15
+ class WebSocketGateway {
16
+ constructor() {
17
+ this.clients = new Map();
18
+ this.roomManager = new room_manager_1.RoomManager();
19
+ this.wss = null;
20
+ this.stats = {
21
+ connectedClients: 0,
22
+ totalRooms: 0,
23
+ messagesSent: 0,
24
+ messagesReceived: 0,
25
+ bytesSent: 0,
26
+ bytesReceived: 0,
27
+ uptime: Date.now(),
28
+ };
29
+ }
30
+ /**
31
+ * Attach WebSocket server to an existing HTTP server
32
+ */
33
+ attachToServer(server, options = {}) {
34
+ this.wss = new ws_1.WebSocketServer({
35
+ server,
36
+ path: options.path || '/ws',
37
+ perMessageDeflate: options.perMessageDeflate ?? false,
38
+ maxPayload: options.maxPayload || 1048576, // 1MB default
39
+ clientTracking: options.clientTracking ?? true,
40
+ });
41
+ this.wss.on('connection', (socket, request) => {
42
+ const clientId = (0, crypto_1.randomUUID)();
43
+ const client = createWebSocketClient(socket, clientId);
44
+ this.handleConnection(client);
45
+ socket.on('message', (data) => {
46
+ try {
47
+ const parsed = JSON.parse(data.toString());
48
+ this.handleMessage(clientId, parsed);
49
+ }
50
+ catch {
51
+ core_1.default.warn(`Invalid WebSocket message from ${clientId}`);
52
+ }
53
+ });
54
+ socket.on('close', () => {
55
+ this.handleDisconnection(clientId);
56
+ });
57
+ socket.on('error', (error) => {
58
+ core_1.default.error(`WebSocket error for client ${clientId}:`, error);
59
+ });
60
+ });
61
+ this.wss.on('error', (error) => {
62
+ core_1.default.error('WebSocket server error:', error);
63
+ });
64
+ core_1.default.info(`WebSocket server attached at path: ${options.path || '/ws'}`);
65
+ return this.wss;
66
+ }
67
+ /**
68
+ * Create a standalone WebSocket server (without HTTP server)
69
+ */
70
+ listen(port, options = {}) {
71
+ this.wss = new ws_1.WebSocketServer({
72
+ port,
73
+ path: options.path,
74
+ perMessageDeflate: options.perMessageDeflate ?? false,
75
+ maxPayload: options.maxPayload || 1048576,
76
+ clientTracking: options.clientTracking ?? true,
77
+ });
78
+ this.wss.on('connection', (socket, request) => {
79
+ const clientId = (0, crypto_1.randomUUID)();
80
+ const client = createWebSocketClient(socket, clientId);
81
+ this.handleConnection(client);
82
+ socket.on('message', (data) => {
83
+ try {
84
+ const parsed = JSON.parse(data.toString());
85
+ this.handleMessage(clientId, parsed);
86
+ }
87
+ catch {
88
+ core_1.default.warn(`Invalid WebSocket message from ${clientId}`);
89
+ }
90
+ });
91
+ socket.on('close', () => {
92
+ this.handleDisconnection(clientId);
93
+ });
94
+ socket.on('error', (error) => {
95
+ core_1.default.error(`WebSocket error for client ${clientId}:`, error);
96
+ });
97
+ });
98
+ core_1.default.info(`WebSocket server listening on port ${port}`);
99
+ return this.wss;
100
+ }
101
+ /**
102
+ * Close the WebSocket server
103
+ */
104
+ close() {
105
+ return new Promise((resolve, reject) => {
106
+ if (!this.wss) {
107
+ resolve();
108
+ return;
109
+ }
110
+ this.disconnectAll();
111
+ this.wss.close((err) => {
112
+ if (err)
113
+ reject(err);
114
+ else {
115
+ this.wss = null;
116
+ core_1.default.info('WebSocket server closed');
117
+ resolve();
118
+ }
119
+ });
120
+ });
121
+ }
122
+ /**
123
+ * Handle client connection
124
+ */
125
+ handleConnection(client) {
126
+ this.clients.set(client.id, client);
127
+ this.stats.connectedClients = this.clients.size;
128
+ core_1.default.info(`WebSocket client connected: ${client.id}`);
129
+ }
130
+ /**
131
+ * Handle client disconnection
132
+ */
133
+ handleDisconnection(clientId) {
134
+ const client = this.clients.get(clientId);
135
+ if (client) {
136
+ // Remove from all rooms
137
+ this.roomManager.removeClientFromAllRooms(clientId);
138
+ this.clients.delete(clientId);
139
+ this.stats.connectedClients = this.clients.size;
140
+ core_1.default.info(`WebSocket client disconnected: ${clientId}`);
141
+ }
142
+ }
143
+ /**
144
+ * Handle incoming message
145
+ */
146
+ handleMessage(clientId, message) {
147
+ this.stats.messagesReceived++;
148
+ this.stats.bytesReceived += JSON.stringify(message).length;
149
+ core_1.default.debug(`Message received from ${clientId}: ${message.event}`);
150
+ }
151
+ /**
152
+ * Send message to a specific client
153
+ */
154
+ sendToClient(clientId, event, data) {
155
+ const client = this.clients.get(clientId);
156
+ if (!client) {
157
+ return false;
158
+ }
159
+ client.send(event, data);
160
+ this.stats.messagesSent++;
161
+ this.stats.bytesSent += JSON.stringify(data).length;
162
+ return true;
163
+ }
164
+ /**
165
+ * Broadcast message to all clients
166
+ */
167
+ broadcast(event, data, excludeClientId) {
168
+ for (const [clientId, client] of this.clients) {
169
+ if (excludeClientId && clientId === excludeClientId) {
170
+ continue;
171
+ }
172
+ client.send(event, data);
173
+ }
174
+ this.stats.messagesSent += this.clients.size;
175
+ this.stats.bytesSent += JSON.stringify(data).length * this.clients.size;
176
+ core_1.default.debug(`Broadcast: ${event} to ${this.clients.size} clients`);
177
+ }
178
+ /**
179
+ * Broadcast to a specific room
180
+ */
181
+ broadcastToRoom(roomName, event, data, excludeClientId) {
182
+ this.roomManager.broadcastToRoom(roomName, event, data, this.clients, excludeClientId);
183
+ this.stats.messagesSent++;
184
+ }
185
+ /**
186
+ * Add client to room
187
+ */
188
+ joinRoom(clientId, roomName) {
189
+ const client = this.clients.get(clientId);
190
+ if (client) {
191
+ client.join(roomName);
192
+ this.roomManager.addClientToRoom(clientId, roomName);
193
+ this.stats.totalRooms = this.roomManager.getRoomCount();
194
+ }
195
+ }
196
+ /**
197
+ * Remove client from room
198
+ */
199
+ leaveRoom(clientId, roomName) {
200
+ const client = this.clients.get(clientId);
201
+ if (client) {
202
+ client.leave(roomName);
203
+ this.roomManager.removeClientFromRoom(clientId, roomName);
204
+ this.stats.totalRooms = this.roomManager.getRoomCount();
205
+ }
206
+ }
207
+ /**
208
+ * Get all clients
209
+ */
210
+ getClients() {
211
+ return Array.from(this.clients.values());
212
+ }
213
+ /**
214
+ * Get client by ID
215
+ */
216
+ getClient(clientId) {
217
+ return this.clients.get(clientId);
218
+ }
219
+ /**
220
+ * Get connected client count
221
+ */
222
+ getClientCount() {
223
+ return this.clients.size;
224
+ }
225
+ /**
226
+ * Get room clients
227
+ */
228
+ getRoomClients(roomName) {
229
+ return this.roomManager.getRoomClients(roomName);
230
+ }
231
+ /**
232
+ * Get client rooms
233
+ */
234
+ getClientRooms(clientId) {
235
+ return this.roomManager.getClientRooms(clientId);
236
+ }
237
+ /**
238
+ * Get statistics
239
+ */
240
+ getStats() {
241
+ return {
242
+ ...this.stats,
243
+ uptime: Date.now() - this.stats.uptime,
244
+ totalRooms: this.roomManager.getRoomCount(),
245
+ };
246
+ }
247
+ /**
248
+ * Disconnect a client
249
+ */
250
+ disconnectClient(clientId) {
251
+ const client = this.clients.get(clientId);
252
+ if (client) {
253
+ client.disconnect();
254
+ this.handleDisconnection(clientId);
255
+ }
256
+ }
257
+ /**
258
+ * Disconnect all clients
259
+ */
260
+ disconnectAll() {
261
+ for (const client of this.clients.values()) {
262
+ client.disconnect();
263
+ }
264
+ this.clients.clear();
265
+ this.roomManager.clear();
266
+ this.stats.connectedClients = 0;
267
+ this.stats.totalRooms = 0;
268
+ core_1.default.info('All WebSocket clients disconnected');
269
+ }
270
+ }
271
+ exports.WebSocketGateway = WebSocketGateway;
272
+ /**
273
+ * Create a WebSocket client wrapper
274
+ */
275
+ function createWebSocketClient(socket, id) {
276
+ const client = {
277
+ id,
278
+ socket,
279
+ metadata: new Map(),
280
+ rooms: new Set(),
281
+ send(event, data) {
282
+ try {
283
+ const message = JSON.stringify({ event, data, timestamp: Date.now() });
284
+ socket.send(message);
285
+ }
286
+ catch (error) {
287
+ core_1.default.error(`Failed to send message to client ${id}:`, error);
288
+ }
289
+ },
290
+ disconnect() {
291
+ try {
292
+ socket.close();
293
+ }
294
+ catch (error) {
295
+ core_1.default.error(`Failed to disconnect client ${id}:`, error);
296
+ }
297
+ },
298
+ join(room) {
299
+ this.rooms.add(room);
300
+ },
301
+ leave(room) {
302
+ this.rooms.delete(room);
303
+ },
304
+ inRoom(room) {
305
+ return this.rooms.has(room);
306
+ },
307
+ };
308
+ return client;
309
+ }
@@ -0,0 +1,57 @@
1
+ import { WebSocketServerOptions } from './websocket.types';
2
+ import { SSEHandler } from './sse/sse.handler';
3
+ import { RoomManager } from './room/room.manager';
4
+ /**
5
+ * WebSocket module options
6
+ */
7
+ export interface WebSocketModuleOptions extends WebSocketServerOptions {
8
+ /**
9
+ * Whether this is a global module
10
+ * @default true
11
+ */
12
+ isGlobal?: boolean;
13
+ /**
14
+ * Enable SSE support
15
+ * @default true
16
+ */
17
+ enableSSE?: boolean;
18
+ /**
19
+ * Enable room management
20
+ * @default true
21
+ */
22
+ enableRooms?: boolean;
23
+ }
24
+ /**
25
+ * WebSocket module for HazelJS
26
+ */
27
+ export declare class WebSocketModule {
28
+ /**
29
+ * Configure WebSocket module
30
+ */
31
+ static forRoot(options?: WebSocketModuleOptions): {
32
+ module: typeof WebSocketModule;
33
+ providers: Array<{
34
+ provide: typeof SSEHandler | typeof RoomManager;
35
+ useValue: SSEHandler | RoomManager;
36
+ }>;
37
+ exports: Array<typeof SSEHandler | typeof RoomManager>;
38
+ global: boolean;
39
+ };
40
+ /**
41
+ * Configure WebSocket module asynchronously
42
+ */
43
+ static forRootAsync(options: {
44
+ useFactory: (...args: unknown[]) => Promise<WebSocketModuleOptions> | WebSocketModuleOptions;
45
+ inject?: unknown[];
46
+ }): {
47
+ module: typeof WebSocketModule;
48
+ providers: Array<{
49
+ provide: string | typeof SSEHandler | typeof RoomManager;
50
+ useFactory: unknown;
51
+ inject?: unknown[];
52
+ }>;
53
+ exports: Array<typeof SSEHandler | typeof RoomManager>;
54
+ global: boolean;
55
+ };
56
+ }
57
+ //# sourceMappingURL=websocket.module.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"websocket.module.d.ts","sourceRoot":"","sources":["../../src/websocket.module.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAGlD;;GAEG;AACH,MAAM,WAAW,sBAAuB,SAAQ,sBAAsB;IACpE;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;GAEG;AACH,qBAIa,eAAe;IAC1B;;OAEG;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,GAAE,sBAA2B,GAAG;QACpD,MAAM,EAAE,OAAO,eAAe,CAAC;QAC/B,SAAS,EAAE,KAAK,CAAC;YACf,OAAO,EAAE,OAAO,UAAU,GAAG,OAAO,WAAW,CAAC;YAChD,QAAQ,EAAE,UAAU,GAAG,WAAW,CAAC;SACpC,CAAC,CAAC;QACH,OAAO,EAAE,KAAK,CAAC,OAAO,UAAU,GAAG,OAAO,WAAW,CAAC,CAAC;QACvD,MAAM,EAAE,OAAO,CAAC;KACjB;IAkCD;;OAEG;IACH,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE;QAC3B,UAAU,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,sBAAsB,CAAC,GAAG,sBAAsB,CAAC;QAC7F,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC;KACpB,GAAG;QACF,MAAM,EAAE,OAAO,eAAe,CAAC;QAC/B,SAAS,EAAE,KAAK,CAAC;YACf,OAAO,EAAE,MAAM,GAAG,OAAO,UAAU,GAAG,OAAO,WAAW,CAAC;YACzD,UAAU,EAAE,OAAO,CAAC;YACpB,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC;SACpB,CAAC,CAAC;QACH,OAAO,EAAE,KAAK,CAAC,OAAO,UAAU,GAAG,OAAO,WAAW,CAAC,CAAC;QACvD,MAAM,EAAE,OAAO,CAAC;KACjB;CA4BF"}