@hazeljs/websocket 0.2.0-alpha.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.
package/README.md ADDED
@@ -0,0 +1,531 @@
1
+ # @hazeljs/websocket
2
+
3
+ **Real-time, the HazelJS way.**
4
+
5
+ WebSockets and SSE. Rooms, broadcasting, decorator-based handlers. `@WebSocketGateway`, `@OnMessage`, `@Subscribe`. Chat, notifications, live dashboards — without the socket.io complexity.
6
+
7
+ [![npm version](https://img.shields.io/npm/v/@hazeljs/websocket.svg)](https://www.npmjs.com/package/@hazeljs/websocket)
8
+ [![npm downloads](https://img.shields.io/npm/dm/@hazeljs/websocket)](https://www.npmjs.com/package/@hazeljs/websocket)
9
+ [![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0)
10
+
11
+ ## Features
12
+
13
+ - 🔌 **WebSocket Support** - Full-duplex real-time communication
14
+ - 🏠 **Room Management** - Group clients into rooms
15
+ - 📡 **Event-Driven** - Decorator-based event handling
16
+ - 🎨 **Decorator API** - `@WebSocketGateway`, `@OnMessage`, `@Subscribe`
17
+ - 🔐 **Authentication** - Integrate with auth guards
18
+ - 📊 **Broadcasting** - Send messages to multiple clients
19
+ - 🎯 **Namespaces** - Separate WebSocket endpoints
20
+ - 💾 **State Management** - Per-client state storage
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ npm install @hazeljs/websocket
26
+ ```
27
+
28
+ ## Quick Start
29
+
30
+ ### 1. Create WebSocket Gateway
31
+
32
+ ```typescript
33
+ import { Injectable } from '@hazeljs/core';
34
+ import {
35
+ WebSocketGateway,
36
+ OnConnect,
37
+ OnDisconnect,
38
+ OnMessage,
39
+ WebSocketClient,
40
+ Data,
41
+ } from '@hazeljs/websocket';
42
+
43
+ @Injectable()
44
+ @WebSocketGateway({ path: '/ws' })
45
+ export class ChatGateway {
46
+ @OnConnect()
47
+ handleConnection(client: WebSocketClient) {
48
+ console.log('Client connected:', client.id);
49
+ client.emit('welcome', { message: 'Welcome to the chat!' });
50
+ }
51
+
52
+ @OnDisconnect()
53
+ handleDisconnect(client: WebSocketClient) {
54
+ console.log('Client disconnected:', client.id);
55
+ }
56
+
57
+ @OnMessage('message')
58
+ handleMessage(client: WebSocketClient, @Data() data: { text: string }) {
59
+ console.log('Received message:', data.text);
60
+
61
+ // Broadcast to all clients
62
+ client.broadcast('message', {
63
+ from: client.id,
64
+ text: data.text,
65
+ timestamp: new Date(),
66
+ });
67
+ }
68
+ }
69
+ ```
70
+
71
+ ### 2. Register Gateway
72
+
73
+ ```typescript
74
+ import { HazelModule } from '@hazeljs/core';
75
+ import { WebSocketModule } from '@hazeljs/websocket';
76
+ import { ChatGateway } from './chat.gateway';
77
+
78
+ @HazelModule({
79
+ imports: [WebSocketModule],
80
+ providers: [ChatGateway],
81
+ })
82
+ export class AppModule {}
83
+ ```
84
+
85
+ ### 3. Connect from Client
86
+
87
+ ```javascript
88
+ // Browser client
89
+ const ws = new WebSocket('ws://localhost:3000/ws');
90
+
91
+ ws.onopen = () => {
92
+ console.log('Connected');
93
+ ws.send(JSON.stringify({ event: 'message', data: { text: 'Hello!' } }));
94
+ };
95
+
96
+ ws.onmessage = (event) => {
97
+ const message = JSON.parse(event.data);
98
+ console.log('Received:', message);
99
+ };
100
+ ```
101
+
102
+ ## Decorators
103
+
104
+ ### @WebSocketGateway()
105
+
106
+ Define a WebSocket gateway:
107
+
108
+ ```typescript
109
+ @WebSocketGateway({
110
+ path: '/chat',
111
+ namespace: '/chat',
112
+ cors: {
113
+ origin: '*',
114
+ credentials: true,
115
+ },
116
+ })
117
+ export class ChatGateway {}
118
+ ```
119
+
120
+ ### @OnConnect()
121
+
122
+ Handle client connections:
123
+
124
+ ```typescript
125
+ @OnConnect()
126
+ handleConnection(client: WebSocketClient) {
127
+ console.log('New client:', client.id);
128
+
129
+ // Send welcome message
130
+ client.emit('welcome', { message: 'Hello!' });
131
+
132
+ // Store client data
133
+ client.data.username = 'Guest';
134
+ }
135
+ ```
136
+
137
+ ### @OnDisconnect()
138
+
139
+ Handle client disconnections:
140
+
141
+ ```typescript
142
+ @OnDisconnect()
143
+ handleDisconnect(client: WebSocketClient) {
144
+ console.log('Client left:', client.id);
145
+
146
+ // Notify others
147
+ client.broadcast('user-left', {
148
+ userId: client.id,
149
+ username: client.data.username,
150
+ });
151
+ }
152
+ ```
153
+
154
+ ### @OnMessage()
155
+
156
+ Handle specific messages:
157
+
158
+ ```typescript
159
+ @OnMessage('chat-message')
160
+ handleChatMessage(client: WebSocketClient, @Data() data: ChatMessage) {
161
+ console.log('Chat message from', client.id, ':', data.text);
162
+
163
+ // Broadcast to all
164
+ client.broadcast('chat-message', {
165
+ from: client.data.username,
166
+ text: data.text,
167
+ timestamp: new Date(),
168
+ });
169
+ }
170
+ ```
171
+
172
+ ### @Subscribe()
173
+
174
+ Subscribe to events:
175
+
176
+ ```typescript
177
+ @Subscribe('join-room')
178
+ handleJoinRoom(client: WebSocketClient, @Data() data: { room: string }) {
179
+ client.join(data.room);
180
+
181
+ // Notify room members
182
+ client.to(data.room).emit('user-joined', {
183
+ userId: client.id,
184
+ username: client.data.username,
185
+ });
186
+ }
187
+ ```
188
+
189
+ ## Room Management
190
+
191
+ ### Join Room
192
+
193
+ ```typescript
194
+ @Subscribe('join-room')
195
+ handleJoinRoom(client: WebSocketClient, @Data() data: { room: string }) {
196
+ client.join(data.room);
197
+ client.emit('joined', { room: data.room });
198
+ }
199
+ ```
200
+
201
+ ### Leave Room
202
+
203
+ ```typescript
204
+ @Subscribe('leave-room')
205
+ handleLeaveRoom(client: WebSocketClient, @Data() data: { room: string }) {
206
+ client.leave(data.room);
207
+ client.emit('left', { room: data.room });
208
+ }
209
+ ```
210
+
211
+ ### Send to Room
212
+
213
+ ```typescript
214
+ @Subscribe('room-message')
215
+ handleRoomMessage(
216
+ client: WebSocketClient,
217
+ @Data() data: { room: string; text: string }
218
+ ) {
219
+ // Send to all clients in room except sender
220
+ client.to(data.room).emit('room-message', {
221
+ from: client.id,
222
+ text: data.text,
223
+ });
224
+ }
225
+ ```
226
+
227
+ ### Broadcast to Room
228
+
229
+ ```typescript
230
+ @Subscribe('room-announcement')
231
+ handleAnnouncement(
232
+ client: WebSocketClient,
233
+ @Data() data: { room: string; text: string }
234
+ ) {
235
+ // Send to all clients in room including sender
236
+ client.in(data.room).emit('announcement', {
237
+ text: data.text,
238
+ });
239
+ }
240
+ ```
241
+
242
+ ## Broadcasting
243
+
244
+ ### Broadcast to All
245
+
246
+ ```typescript
247
+ @OnMessage('global-message')
248
+ handleGlobalMessage(client: WebSocketClient, @Data() data: any) {
249
+ // Send to all connected clients except sender
250
+ client.broadcast('global-message', data);
251
+ }
252
+ ```
253
+
254
+ ### Emit to All
255
+
256
+ ```typescript
257
+ @OnMessage('system-message')
258
+ handleSystemMessage(client: WebSocketClient, @Data() data: any) {
259
+ // Send to all connected clients including sender
260
+ this.server.emit('system-message', data);
261
+ }
262
+ ```
263
+
264
+ ### Emit to Specific Client
265
+
266
+ ```typescript
267
+ @Subscribe('private-message')
268
+ handlePrivateMessage(
269
+ client: WebSocketClient,
270
+ @Data() data: { to: string; text: string }
271
+ ) {
272
+ const targetClient = this.server.getClient(data.to);
273
+
274
+ if (targetClient) {
275
+ targetClient.emit('private-message', {
276
+ from: client.id,
277
+ text: data.text,
278
+ });
279
+ }
280
+ }
281
+ ```
282
+
283
+ ## Authentication
284
+
285
+ ### With Guards
286
+
287
+ ```typescript
288
+ import { UseGuard } from '@hazeljs/core';
289
+ import { AuthGuard } from '@hazeljs/auth';
290
+
291
+ @WebSocketGateway({ path: '/secure' })
292
+ @UseGuard(AuthGuard)
293
+ export class SecureGateway {
294
+ @OnConnect()
295
+ handleConnection(client: WebSocketClient) {
296
+ // Only authenticated clients reach here
297
+ console.log('Authenticated user:', client.data.user);
298
+ }
299
+ }
300
+ ```
301
+
302
+ ### Custom Authentication
303
+
304
+ ```typescript
305
+ @WebSocketGateway({ path: '/chat' })
306
+ export class ChatGateway {
307
+ @OnConnect()
308
+ async handleConnection(client: WebSocketClient) {
309
+ const token = client.handshake.query.token;
310
+
311
+ try {
312
+ const user = await this.authService.verifyToken(token);
313
+ client.data.user = user;
314
+ client.emit('authenticated', { user });
315
+ } catch (error) {
316
+ client.disconnect();
317
+ }
318
+ }
319
+ }
320
+ ```
321
+
322
+ ## Complete Example: Chat Application
323
+
324
+ ```typescript
325
+ import { Injectable } from '@hazeljs/core';
326
+ import {
327
+ WebSocketGateway,
328
+ OnConnect,
329
+ OnDisconnect,
330
+ Subscribe,
331
+ WebSocketClient,
332
+ Data,
333
+ WebSocketServer,
334
+ } from '@hazeljs/websocket';
335
+
336
+ interface ChatMessage {
337
+ text: string;
338
+ room?: string;
339
+ }
340
+
341
+ interface JoinRoomData {
342
+ room: string;
343
+ username: string;
344
+ }
345
+
346
+ @Injectable()
347
+ @WebSocketGateway({ path: '/chat' })
348
+ export class ChatGateway {
349
+ @WebSocketServer()
350
+ server: WebSocketServer;
351
+
352
+ @OnConnect()
353
+ handleConnection(client: WebSocketClient) {
354
+ console.log(`Client connected: ${client.id}`);
355
+
356
+ client.emit('connected', {
357
+ clientId: client.id,
358
+ message: 'Welcome to the chat!',
359
+ });
360
+ }
361
+
362
+ @OnDisconnect()
363
+ handleDisconnect(client: WebSocketClient) {
364
+ console.log(`Client disconnected: ${client.id}`);
365
+
366
+ // Notify rooms
367
+ const rooms = client.rooms;
368
+ rooms.forEach(room => {
369
+ client.to(room).emit('user-left', {
370
+ userId: client.id,
371
+ username: client.data.username,
372
+ });
373
+ });
374
+ }
375
+
376
+ @Subscribe('join-room')
377
+ handleJoinRoom(client: WebSocketClient, @Data() data: JoinRoomData) {
378
+ client.join(data.room);
379
+ client.data.username = data.username;
380
+
381
+ // Notify room members
382
+ client.to(data.room).emit('user-joined', {
383
+ userId: client.id,
384
+ username: data.username,
385
+ room: data.room,
386
+ });
387
+
388
+ // Confirm to sender
389
+ client.emit('joined-room', {
390
+ room: data.room,
391
+ members: this.getRoomMembers(data.room),
392
+ });
393
+ }
394
+
395
+ @Subscribe('leave-room')
396
+ handleLeaveRoom(client: WebSocketClient, @Data() data: { room: string }) {
397
+ client.leave(data.room);
398
+
399
+ // Notify room
400
+ client.to(data.room).emit('user-left', {
401
+ userId: client.id,
402
+ username: client.data.username,
403
+ });
404
+ }
405
+
406
+ @Subscribe('chat-message')
407
+ handleMessage(client: WebSocketClient, @Data() data: ChatMessage) {
408
+ const message = {
409
+ from: client.id,
410
+ username: client.data.username,
411
+ text: data.text,
412
+ timestamp: new Date(),
413
+ };
414
+
415
+ if (data.room) {
416
+ // Send to specific room
417
+ client.to(data.room).emit('chat-message', message);
418
+ } else {
419
+ // Broadcast to all
420
+ client.broadcast('chat-message', message);
421
+ }
422
+ }
423
+
424
+ @Subscribe('typing')
425
+ handleTyping(client: WebSocketClient, @Data() data: { room?: string }) {
426
+ const typingData = {
427
+ userId: client.id,
428
+ username: client.data.username,
429
+ };
430
+
431
+ if (data.room) {
432
+ client.to(data.room).emit('typing', typingData);
433
+ } else {
434
+ client.broadcast('typing', typingData);
435
+ }
436
+ }
437
+
438
+ private getRoomMembers(room: string): any[] {
439
+ const clients = this.server.getClientsInRoom(room);
440
+ return clients.map(c => ({
441
+ id: c.id,
442
+ username: c.data.username,
443
+ }));
444
+ }
445
+ }
446
+ ```
447
+
448
+ ## Client State
449
+
450
+ Store per-client data:
451
+
452
+ ```typescript
453
+ @OnConnect()
454
+ handleConnection(client: WebSocketClient) {
455
+ // Store custom data
456
+ client.data.username = 'Guest';
457
+ client.data.score = 0;
458
+ client.data.joinedAt = new Date();
459
+ }
460
+
461
+ @Subscribe('update-profile')
462
+ handleUpdateProfile(client: WebSocketClient, @Data() data: any) {
463
+ client.data.username = data.username;
464
+ client.data.avatar = data.avatar;
465
+ }
466
+ ```
467
+
468
+ ## Server-Sent Events (SSE)
469
+
470
+ ```typescript
471
+ import { Controller, Get, Res } from '@hazeljs/core';
472
+ import { Response } from 'express';
473
+
474
+ @Controller('/events')
475
+ export class EventsController {
476
+ @Get('/stream')
477
+ streamEvents(@Res() res: Response) {
478
+ res.setHeader('Content-Type', 'text/event-stream');
479
+ res.setHeader('Cache-Control', 'no-cache');
480
+ res.setHeader('Connection', 'keep-alive');
481
+
482
+ // Send event every second
483
+ const interval = setInterval(() => {
484
+ res.write(`data: ${JSON.stringify({ time: new Date() })}\n\n`);
485
+ }, 1000);
486
+
487
+ // Cleanup on close
488
+ res.on('close', () => {
489
+ clearInterval(interval);
490
+ res.end();
491
+ });
492
+ }
493
+ }
494
+ ```
495
+
496
+ ## Best Practices
497
+
498
+ 1. **Handle Disconnections** - Always implement `@OnDisconnect()`
499
+ 2. **Validate Messages** - Validate incoming data
500
+ 3. **Use Rooms** - Group related clients
501
+ 4. **Authentication** - Verify clients on connection
502
+ 5. **Error Handling** - Catch and handle errors gracefully
503
+ 6. **Rate Limiting** - Prevent message flooding
504
+ 7. **Heartbeat** - Implement ping/pong for connection health
505
+ 8. **Clean Up** - Remove client data on disconnect
506
+
507
+ ## Examples
508
+
509
+ See the [examples](../../example/src/websocket) directory for complete working examples.
510
+
511
+ ## Testing
512
+
513
+ ```bash
514
+ npm test
515
+ ```
516
+
517
+ ## Contributing
518
+
519
+ Contributions are welcome! Please read our [Contributing Guide](../../CONTRIBUTING.md) for details.
520
+
521
+ ## License
522
+
523
+ Apache 2.0 © [HazelJS](https://hazeljs.com)
524
+
525
+ ## Links
526
+
527
+ - [Documentation](https://hazeljs.com/docs/packages/websocket)
528
+ - [WebSocket API](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
529
+ - [GitHub](https://github.com/hazel-js/hazeljs)
530
+ - [Issues](https://github.com/hazel-js/hazeljs/issues)
531
+ - [Discord](https://discord.com/channels/1448263814238965833/1448263814859456575)
@@ -0,0 +1,113 @@
1
+ import 'reflect-metadata';
2
+ import { WebSocketGatewayOptions } from '../websocket.types';
3
+ /**
4
+ * Realtime decorator for WebSocket gateways
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * @Realtime('/notifications')
9
+ * export class NotificationGateway {
10
+ * // Gateway methods
11
+ * }
12
+ * ```
13
+ */
14
+ export declare function Realtime(pathOrOptions?: string | WebSocketGatewayOptions): ClassDecorator;
15
+ /**
16
+ * Get realtime metadata from a class
17
+ */
18
+ export declare function getRealtimeMetadata(target: object | (new (...args: unknown[]) => object)): WebSocketGatewayOptions | undefined;
19
+ /**
20
+ * Check if a class is a realtime gateway
21
+ */
22
+ export declare function isRealtimeGateway(target: object | (new (...args: unknown[]) => object)): boolean;
23
+ /**
24
+ * Subscribe decorator for event handlers
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * @Subscribe('user-{userId}')
29
+ * onUserEvent(@Param('userId') userId: string, @Data() data: any) {
30
+ * // Handle event
31
+ * }
32
+ * ```
33
+ */
34
+ export declare function Subscribe(event: string): MethodDecorator;
35
+ /**
36
+ * Get subscribe metadata from a method
37
+ */
38
+ export declare function getSubscribeMetadata(target: object, propertyKey: string | symbol): string | undefined;
39
+ /**
40
+ * OnConnect decorator for connection handlers
41
+ *
42
+ * @example
43
+ * ```typescript
44
+ * @OnConnect()
45
+ * handleConnection(@Client() client: WebSocketClient) {
46
+ * console.log('Client connected:', client.id);
47
+ * }
48
+ * ```
49
+ */
50
+ export declare function OnConnect(): MethodDecorator;
51
+ /**
52
+ * Get OnConnect metadata
53
+ */
54
+ export declare function getOnConnectMetadata(target: object, propertyKey: string | symbol): boolean;
55
+ /**
56
+ * OnDisconnect decorator for disconnection handlers
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * @OnDisconnect()
61
+ * handleDisconnect(@Client() client: WebSocketClient) {
62
+ * console.log('Client disconnected:', client.id);
63
+ * }
64
+ * ```
65
+ */
66
+ export declare function OnDisconnect(): MethodDecorator;
67
+ /**
68
+ * Get OnDisconnect metadata
69
+ */
70
+ export declare function getOnDisconnectMetadata(target: object, propertyKey: string | symbol): boolean;
71
+ /**
72
+ * OnMessage decorator for message handlers
73
+ *
74
+ * @example
75
+ * ```typescript
76
+ * @OnMessage('chat')
77
+ * handleMessage(@Client() client: WebSocketClient, @Data() data: any) {
78
+ * // Handle message
79
+ * }
80
+ * ```
81
+ */
82
+ export declare function OnMessage(event: string): MethodDecorator;
83
+ /**
84
+ * Get OnMessage metadata
85
+ */
86
+ export declare function getOnMessageMetadata(target: object, propertyKey: string | symbol): string | undefined;
87
+ /**
88
+ * Client parameter decorator
89
+ *
90
+ * @example
91
+ * ```typescript
92
+ * handleConnection(@Client() client: WebSocketClient) {
93
+ * // client is injected
94
+ * }
95
+ * ```
96
+ */
97
+ export declare function Client(): ParameterDecorator;
98
+ /**
99
+ * Data parameter decorator
100
+ *
101
+ * @example
102
+ * ```typescript
103
+ * handleMessage(@Data() data: any) {
104
+ * // data is injected
105
+ * }
106
+ * ```
107
+ */
108
+ export declare function Data(): ParameterDecorator;
109
+ /**
110
+ * Get parameter metadata
111
+ */
112
+ export declare function getParameterMetadata(target: object, propertyKey: string | symbol): string[];
113
+ //# sourceMappingURL=realtime.decorator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"realtime.decorator.d.ts","sourceRoot":"","sources":["../../src/decorators/realtime.decorator.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAS7D;;;;;;;;;;GAUG;AACH,wBAAgB,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,uBAAuB,GAAG,cAAc,CAmBzF;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,GAAG,CAAC,KAAK,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,MAAM,CAAC,GACpD,uBAAuB,GAAG,SAAS,CAErC;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,CAAC,KAAK,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,MAAM,CAAC,GAAG,OAAO,CAEhG;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,eAAe,CAQxD;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,GAAG,MAAM,GAC3B,MAAM,GAAG,SAAS,CAEpB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,IAAI,eAAe,CAQ3C;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAE1F;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,IAAI,eAAe,CAQ9C;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAE7F;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,eAAe,CAQxD;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,GAAG,MAAM,GAC3B,MAAM,GAAG,SAAS,CAEpB;AAED;;;;;;;;;GASG;AACH,wBAAgB,MAAM,IAAI,kBAAkB,CAM3C;AAED;;;;;;;;;GASG;AACH,wBAAgB,IAAI,IAAI,kBAAkB,CAMzC;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,EAAE,CAE3F"}