@eleven-am/pondsocket-nest 0.0.129 → 0.0.130

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 (2) hide show
  1. package/README.md +431 -443
  2. package/package.json +2 -2
package/README.md CHANGED
@@ -1,514 +1,502 @@
1
- # PondSocket
1
+ # PondSocket NestJS Integration
2
2
 
3
- PondSocket is a high-performance, minimalist, and bidirectional socket framework designed for Node.js. It provides a seamless way to handle real-time communication between server and client applications, making it an ideal choice for building WebSocket-based projects.
3
+ This package provides a NestJS integration layer for PondSocket, making it easy to use PondSocket's real-time WebSocket functionality within NestJS applications.
4
4
 
5
5
  ## Installation
6
6
 
7
- To integrate PondSocket into your Node.js project, simply install it via npm:
8
-
9
7
  ```bash
10
- npm install @eleven-am/pondsocket-client
8
+ npm install @eleven-am/pondsocket-nest
11
9
  ```
12
10
 
13
11
  ## Overview
14
12
 
15
- PondSocket simplifies the complexity of handling WebSocket connections by abstracting the communication process into individual requests rather than dealing with intricate callbacks within the connection event. It offers a lightweight yet powerful solution for managing bidirectional communication channels, enabling real-time updates and collaboration between server and client components.
13
+ This package integrates PondSocket's powerful WebSocket capabilities with NestJS's architecture and dependency injection system. It provides decorators and services that make it natural to use WebSocket functionality in a NestJS application while maintaining all of PondSocket's features.
16
14
 
17
- ## Server-side Usage
15
+ ## Key Features
18
16
 
19
- When setting up the server, PondSocket allows you to create multiple endpoints, each serving as a gateway for sockets to connect and communicate. Each endpoint operates independently, ensuring that sockets from one endpoint cannot interact with sockets from another. This isolation enhances security and simplifies resource management.
17
+ - **NestJS Integration**: Seamless integration with NestJS's module system and dependency injection
18
+ - **Decorator-based API**: Use familiar NestJS-style decorators for WebSocket endpoints and channels
19
+ - **Guard Support**: Full integration with NestJS guards for authentication and authorization
20
+ - **Pipe Support**: Use NestJS pipes for data transformation and validation
21
+ - **Type Safety**: Complete TypeScript support with proper type definitions
22
+ - **Distributed Support**: Maintains PondSocket's distributed backend capabilities
23
+ - **Automatic Discovery**: Uses NestJS's discovery service to automatically find and manage WebSocket endpoints
24
+
25
+ ## Basic Usage
26
+
27
+ ### Module Setup
28
+
29
+ ```typescript
30
+ import { Module } from '@nestjs/common';
31
+ import { PondSocketModule } from '@eleven-am/pondsocket-nest';
32
+
33
+ @Module({
34
+ imports: [
35
+ PondSocketModule.forRoot({
36
+ guards: [AuthGuard], // Optional: Global guards
37
+ pipes: [ValidationPipe], // Optional: Global pipes
38
+ isGlobal: true, // Optional: Make the module global
39
+ })
40
+ ]
41
+ })
42
+ export class AppModule {}
43
+ ```
20
44
 
21
- ```javascript
22
- import PondSocket from "@eleven-am/pondsocket";
45
+ ### Creating WebSocket Endpoints
23
46
 
24
- const pond = new PondSocket();
47
+ ```typescript
48
+ import { Controller } from '@nestjs/common';
49
+ import { PondSocketEndpoint, PondSocketConnection, Context } from '@eleven-am/pondsocket-nest';
25
50
 
26
- // Create an endpoint for handling socket connections
27
- const endpoint = pond.createEndpoint('/api/socket', (req, res) => {
28
- // Handle socket connection and authentication
29
- });
30
-
31
- // Start the server
32
- pond.listen(3000);
51
+ @PondSocketEndpoint('/api/socket')
52
+ export class SocketController {
53
+ @PondSocketConnection()
54
+ async handleConnection(ctx: Context) {
55
+ const token = ctx.request.query.token;
56
+
57
+ if (isValidToken(token)) {
58
+ const role = getRoleFromToken(token);
59
+ ctx.accept({ role });
60
+ } else {
61
+ ctx.reject('Invalid token', 401);
62
+ }
63
+ }
64
+ }
65
+ ```
33
66
 
34
- // Or alternatively, working with express
35
- import pondSocket from "@eleven-am/pondsocket/express";
36
- import express from "express";
67
+ ### Creating Channels
37
68
 
38
- const app = pondSocket(express());
69
+ ```typescript
70
+ import { Controller } from '@nestjs/common';
71
+ import { PondSocketChannel, PondSocketJoin, Context } from '@eleven-am/pondsocket-nest';
39
72
 
40
- const endpoint = app.upgrade('/api/socket', (req, res) => {
41
- // Handle socket connection and authentication
42
- });
73
+ @PondSocketChannel('/channel/:id')
74
+ export class ChannelController {
75
+ @PondSocketJoin()
76
+ async handleJoin(ctx: Context) {
77
+ const { role } = ctx.user.assigns;
78
+ const { username } = ctx.joinParams;
79
+ const { id } = ctx.event.params;
43
80
 
44
- app.listen(3000);
81
+ if (role === 'admin') {
82
+ ctx.accept({ username })
83
+ .trackPresence({
84
+ username,
85
+ role,
86
+ status: 'online',
87
+ onlineSince: Date.now(),
88
+ });
89
+ } else {
90
+ ctx.decline('Insufficient permissions', 403);
91
+ }
92
+ }
93
+ }
45
94
  ```
46
95
 
47
- Within each endpoint, sockets interact through channels. Channels provide an organized way to group users and manage efficient communication among them. When users join a channel, they can participate in real-time events and exchange information with other users in the same channel.
48
-
49
- ```javascript
50
- const channel = endpoint.createChannel('/channel/:id', (req, res) => {
51
- // Handle the join request, which is sent when a user attempts to join the channel
52
- });
96
+ ### Handling Channel Events
97
+
98
+ ```typescript
99
+ import { Controller } from '@nestjs/common';
100
+ import { PondSocketEvent, Context } from '@eleven-am/pondsocket-nest';
101
+
102
+ @PondSocketChannel('/channel/:id')
103
+ export class ChannelController {
104
+ @PondSocketEvent('message')
105
+ async handleMessage(ctx: Context) {
106
+ const { text } = ctx.event.payload;
107
+
108
+ // Broadcast to all users in the channel
109
+ ctx.broadcast('message', { text });
110
+
111
+ // Broadcast to specific users
112
+ ctx.broadcastTo(['user1', 'user2'], 'message', { text });
113
+
114
+ // Broadcast to all except sender
115
+ ctx.broadcastFrom('message', { text });
116
+ }
117
+ }
53
118
  ```
54
119
 
55
- ## Client-side Usage
120
+ ## Advanced Features
56
121
 
57
- On the client-side, PondSocket provides the PondClient class to establish connections with the server. Clients can easily initiate connections, join channels, and participate in real-time interactions.
122
+ ### Presence Management
58
123
 
59
- ```javascript
60
- import PondClient from "@eleven-am/pondsocket-client";
61
-
62
- const socket = new PondClient('/api/socket', {});
63
- socket.connect();
124
+ ```typescript
125
+ @PondSocketChannel('/channel/:id')
126
+ export class ChannelController {
127
+ @PondSocketEvent('presence')
128
+ async handlePresence(ctx: Context) {
129
+ ctx.trackPresence({
130
+ username: ctx.user.assigns.username,
131
+ status: 'online',
132
+ lastSeen: Date.now()
133
+ });
134
+ }
135
+ }
64
136
  ```
65
137
 
66
- Once connected, clients can create and join channels to engage in real-time communication with other users and the server.
138
+ ### User Assigns
67
139
 
68
- ```javascript
69
- const channel = socket.createChannel('/channel/123');
70
- channel.join();
140
+ ```typescript
141
+ @PondSocketChannel('/channel/:id')
142
+ export class ChannelController {
143
+ @PondSocketEvent('update-profile')
144
+ async handleProfileUpdate(ctx: Context) {
145
+ ctx.assign({
146
+ ...ctx.user.assigns,
147
+ profile: ctx.event.payload
148
+ });
149
+ }
150
+ }
71
151
  ```
72
152
 
73
- ### Node Client
74
-
75
- PondSocket also offers a Node.js client, which can be imported using:
76
-
77
- ```javascript
78
- import PondClient from "@eleven-am/pondsocket-client";
153
+ ### Error Handling
154
+
155
+ ```typescript
156
+ @PondSocketChannel('/channel/:id')
157
+ export class ChannelController {
158
+ @PondSocketEvent('message')
159
+ async handleMessage(ctx: Context) {
160
+ try {
161
+ // Your logic here
162
+ ctx.accept();
163
+ } catch (error) {
164
+ ctx.decline(error.message, 400);
165
+ }
166
+ }
167
+ }
79
168
  ```
80
169
 
81
- This node client allows you to turn another server into a client, enabling easy communication between different server instances.
82
-
83
- ## Key Features
84
-
85
- - **Simple and Efficient API**: PondSocket offers an easy-to-use API, making WebSocket communication straightforward and hassle-free.
86
- - **Organized Channels**: Channels provide a structured approach for grouping users and facilitating efficient communication.
87
- - **Assigns**: PondSocket allows the storage of private information for users and channels, enhancing data security.
88
- - **Presence**: The presence feature keeps track of users' current states and notifies other users about any changes.
89
- - **Broadcasting**: PondSocket enables broadcasting messages to all users or specific groups within a channel, facilitating real-time updates.
90
- - **Typed and Well-documented**: The codebase is thoroughly documented and typed, providing a seamless development experience with improved IDE suggestions.
170
+ ## Distributed Deployment
171
+
172
+ The package maintains PondSocket's distributed deployment capabilities:
173
+
174
+ ```typescript
175
+ import { Module } from '@nestjs/common';
176
+ import { PondSocketModule } from '@eleven-am/pondsocket-nest';
177
+ import { RedisBackend } from '@eleven-am/pondsocket';
178
+
179
+ @Module({
180
+ imports: [
181
+ PondSocketModule.forRoot({
182
+ backend: new RedisBackend({
183
+ host: 'localhost',
184
+ port: 6379
185
+ })
186
+ })
187
+ ]
188
+ })
189
+ export class AppModule {}
190
+ ```
91
191
 
92
- ## Examples
192
+ ### Distributed Mode Features
193
+
194
+ The distributed mode enables you to scale your WebSocket application across multiple server instances while maintaining state synchronization. Here are the key features:
195
+
196
+ 1. **State Synchronization**
197
+ - Channel presence is synchronized across all instances
198
+ - User assigns are shared between instances
199
+ - Channel events are broadcasted to all instances
200
+
201
+ 2. **Load Balancing**
202
+ - Multiple server instances can handle WebSocket connections
203
+ - Connections are distributed across available instances
204
+ - Automatic failover if an instance goes down
205
+
206
+ 3. **Backend Options**
207
+ ```typescript
208
+ // Redis Backend (Recommended for production)
209
+ import { RedisBackend } from '@eleven-am/pondsocket';
210
+
211
+ PondSocketModule.forRoot({
212
+ backend: new RedisBackend({
213
+ host: 'localhost',
214
+ port: 6379,
215
+ password: 'optional-password',
216
+ db: 0,
217
+ keyPrefix: 'pondsocket:', // Optional prefix for Redis keys
218
+ })
219
+ })
220
+
221
+ // Memory Backend (For development/testing)
222
+ import { MemoryBackend } from '@eleven-am/pondsocket';
223
+
224
+ PondSocketModule.forRoot({
225
+ backend: new MemoryBackend()
226
+ })
227
+ ```
228
+
229
+ 4. **Configuration Options**
230
+ ```typescript
231
+ interface DistributedBackendOptions {
232
+ // Redis specific options
233
+ host?: string;
234
+ port?: number;
235
+ password?: string;
236
+ db?: number;
237
+ keyPrefix?: string;
238
+
239
+ // General options
240
+ reconnectInterval?: number; // Time between reconnection attempts
241
+ maxRetries?: number; // Maximum number of reconnection attempts
242
+ timeout?: number; // Operation timeout in milliseconds
243
+ }
244
+ ```
245
+
246
+ 5. **Error Handling**
247
+ ```typescript
248
+ @PondSocketChannel('/channel/:id')
249
+ export class ChannelController {
250
+ @PondSocketEvent('message')
251
+ async handleMessage(ctx: Context) {
252
+ try {
253
+ // Your logic here
254
+ ctx.accept();
255
+ } catch (error) {
256
+ // Handle distributed backend errors
257
+ if (error instanceof DistributedBackendError) {
258
+ // Handle specific distributed backend errors
259
+ ctx.decline('Backend error occurred', 500);
260
+ } else {
261
+ ctx.decline(error.message, 400);
262
+ }
263
+ }
264
+ }
265
+ }
266
+ ```
267
+
268
+ 6. **Health Checks**
269
+ ```typescript
270
+ import { PondSocketService } from '@eleven-am/pondsocket-nest';
271
+
272
+ @Controller('health')
273
+ export class HealthController {
274
+ constructor(private readonly pondSocketService: PondSocketService) {}
275
+
276
+ @Get('websocket')
277
+ async checkWebSocketHealth() {
278
+ const isHealthy = await this.pondSocketService.isHealthy();
279
+ return {
280
+ status: isHealthy ? 'healthy' : 'unhealthy',
281
+ timestamp: new Date().toISOString()
282
+ };
283
+ }
284
+ }
285
+ ```
286
+
287
+ 7. **Best Practices**
288
+ - Use Redis backend in production environments
289
+ - Implement proper error handling for distributed operations
290
+ - Monitor backend connection health
291
+ - Use appropriate Redis configuration for your scale
292
+ - Consider using Redis Cluster for high availability
293
+ - Implement proper logging for distributed operations
294
+
295
+ 8. **Scaling Considerations**
296
+ - Monitor Redis memory usage
297
+ - Implement proper cleanup of stale data
298
+ - Consider using Redis Cluster for larger deployments
299
+ - Implement proper error handling and retry mechanisms
300
+ - Monitor network latency between instances
301
+ - Implement proper logging and monitoring
302
+
303
+ ## Configuration Options
304
+
305
+ The `PondSocketModule.forRoot()` method accepts the following options:
306
+
307
+ ```typescript
308
+ interface PondSocketOptions {
309
+ guards?: any[]; // Global guards
310
+ pipes?: any[]; // Global pipes
311
+ providers?: any[]; // Additional providers
312
+ imports?: any[]; // Additional imports
313
+ exports?: any[]; // Additional exports
314
+ isGlobal?: boolean; // Make the module global
315
+ isExclusiveSocketServer?: boolean; // Use exclusive socket server
316
+ backend?: IDistributedBackend; // Distributed backend
317
+ }
318
+ ```
93
319
 
94
- ### Client-side Example with Authentication
320
+ ## Client Usage
95
321
 
96
- To connect to the PondSocket server and send messages while associating a username with the client connection, follow the steps below:
322
+ The client-side usage remains the same as the core PondSocket package:
97
323
 
98
- ```javascript
324
+ ```typescript
99
325
  import PondClient from "@eleven-am/pondsocket-client";
100
326
 
101
- // Your server URL
102
- const serverUrl = 'ws://your-server-url/api/socket';
103
-
104
- // Your authenticated user's token (replace with actual token)
105
- const authToken = 'your-auth-token';
106
-
107
- // Your username (replace with actual username)
108
- const username = 'user123';
109
-
110
- // Create a new PondClient instance
111
- const socket = new PondClient(serverUrl, { token: authToken });
327
+ const socket = new PondClient('ws://your-server/api/socket', {
328
+ token: 'your-auth-token'
329
+ });
112
330
 
113
- // Connect to the server
114
331
  socket.connect();
115
332
 
116
- // Add event listeners to handle various scenarios
117
- socket.onConnectionChange((connected) => {
118
- if (connected) {
119
- console.log('Connected to the server.');
120
- } else {
121
- console.log('Disconnected from the server.');
122
- }
333
+ const channel = socket.createChannel('/channel/123', {
334
+ username: 'user123'
123
335
  });
124
336
 
125
- // Create a channel and join it
126
- const channel = socket.createChannel('/channel/123', { username });
127
337
  channel.join();
128
338
 
129
- // Send a message to the server
130
- const message = "Hello, PondSocket!";
131
- channel.broadcast('message', { text: message });
132
-
133
- // Handle received messages
134
- // Certain methods in the channel instance returns a subscription function, which can be used to unsubscribe from the event
135
- const subscription = channel.onMessage((event, message) => {
136
- console.log(`Received message from server: ${message.text}`);
339
+ channel.onMessage((event, message) => {
340
+ console.log(`Received message: ${message.text}`);
137
341
  });
138
342
 
139
- // Unsubscribe from the event
140
- subscription();
343
+ channel.broadcast('message', { text: 'Hello, PondSocket!' });
141
344
  ```
142
345
 
143
- The client will now connect to the server, and the server will receive the necessary headers automatically, including any authentication tokens or cookies, as required by the browser.
144
-
145
- ### Server-side Example with Authentication and check for profanity before broadcasting
146
-
147
- To create a PondSocket server that accepts authenticated connections and checks for profanity before broadcasting messages, follow the steps below:
148
-
149
- ```javascript
150
- import PondSocket from "@eleven-am/pondsocket";
151
-
152
- // Helper functions for token validation
153
- function isValidToken(token) {
154
- // Implement your token validation logic here
155
- // Return true if the token is valid, false otherwise
156
- return true;
157
- }
158
-
159
- function getRoleFromToken(token) {
160
- // Implement the logic to extract the user's role from the token
161
- // Return the user's role
162
- return 'user';
163
- }
164
-
165
- function isTextProfane(text) {
166
- // Implement your profanity check logic here
167
- // Return true if the text is profane, false otherwise
168
- return false;
169
- }
170
-
171
- function getMessagesFromDatabase(channelId) {
172
- // Implement your logic to retrieve messages from the database
173
- // Return an array of messages
174
- return [];
175
- }
346
+ ## Contributing
176
347
 
177
- const pond = new PondSocket();
348
+ Contributions are welcome! Please feel free to submit a Pull Request.
178
349
 
179
- // Create an endpoint for handling socket connections
180
- const endpoint = pond.createEndpoint('/api/socket', (req, res) => {
181
- // Depending if the user already has cookies set, they can be accessed from the request headers or the request address
182
- const token = req.query.token; // If the token is passed as a query parameter
183
-
184
- // Perform token validation here
185
- if (isValidToken(token)) {
186
- // Extract the authenticated user's username
187
- const role = getRoleFromToken(token);
188
-
189
- // Handle socket connection and authentication for valid users
190
- res.accept({role}); // Assign the user's role to the socket
191
- } else {
192
- // Reject the connection for invalid users or without a token
193
- res.decline('Invalid token', 401);
194
- }
195
- });
196
-
197
- // Create a channel, providing a callback that is called when a user attempts to join the channel
198
- const profanityChannel = endpoint.createChannel('/channel/:id', async (req, res) => {
199
- // When joining the channel, any joinParams passed from the client will be available in the request payload
200
- // Also any previous assigns on the socket will be available in the request payload as well
201
- const {role} = req.user.assigns;
202
- const {username} = req.joinParams;
203
- const {id} = req.event.params;
350
+ ## License
204
351
 
205
- // maybe retrieve the previous messages from the database
206
- const messages = await getMessagesFromDatabase(id);
352
+ This project is licensed under the GPL-3.0 License - see the LICENSE file for details.
353
+
354
+ ## Return Type Functionality
355
+
356
+ The NestJS integration provides a powerful return type system that allows you to declaratively specify actions to be taken when handling WebSocket events. Instead of using the context object directly, you can return an object with specific properties to trigger various actions.
357
+
358
+ ### Return Type Interface
359
+
360
+ ```typescript
361
+ type NestFuncType<Event extends string, Payload extends PondMessage, Presence extends PondPresence, Assigns extends PondAssigns = PondAssigns> = {
362
+ // Send an event to the user
363
+ event?: Event;
364
+
365
+ // Broadcast to all users in the channel
366
+ broadcast?: Event;
367
+
368
+ // Broadcast to all users except the sender
369
+ broadcastFrom?: Event;
370
+
371
+ // Update user assigns
372
+ assigns?: Partial<Assigns>;
373
+
374
+ // Update user presence
375
+ presence?: Presence;
376
+ } & Payload;
377
+ ```
207
378
 
208
- // Check if the user has the required role to join the channel
209
- if (role === 'admin') {
210
- // Accept the join request
211
- res.accept({username, profanityCount: 0})
212
- // optionally you can track the presence of the user in the channel
213
- .trackPresence({
379
+ ### Usage Examples
380
+
381
+ #### Channel Join with Multiple Actions
382
+
383
+ ```typescript
384
+ @PondSocketChannel('/chat/:roomId')
385
+ export class ChatController {
386
+ @PondSocketJoin()
387
+ async handleJoin(ctx: Context) {
388
+ const { username } = ctx.joinParams;
389
+
390
+ return {
391
+ // Send welcome message to the joining user
392
+ event: 'welcome',
393
+ message: 'Welcome to the chat!',
394
+
395
+ // Broadcast join notification to all users
396
+ broadcast: 'user_joined',
397
+ username,
398
+ timestamp: Date.now(),
399
+
400
+ // Update user's assigns
401
+ assigns: {
402
+ username,
403
+ joinedAt: Date.now(),
404
+ role: 'member'
405
+ },
406
+
407
+ // Update user's presence
408
+ presence: {
214
409
  username,
215
- role,
216
410
  status: 'online',
217
- onlineSince: Date.now(),
218
- })
219
- // and send the user the channel history
220
- .reply('history', {messages});
221
- } else {
222
- // Reject the join request
223
- res.decline('You do not have the required role to join this channel', 403);
411
+ lastSeen: Date.now()
412
+ }
413
+ };
224
414
  }
225
- });
226
-
227
- // Attach message event listener to the profanityChannel
228
- profanityChannel.onEvent('message', (req, res) => {
229
- const {text} = req.event.payload;
230
-
231
- // Check for profanity
232
- if (isTextProfane(text)) {
233
- // Reject the message if it contains profanity
234
- res.decline('Profanity is not allowed', 400, {
235
- profanityCount: req.user.assigns.profanityCount + 1
236
- });
415
+ }
416
+ ```
237
417
 
238
- // note that profanityCount is updated so req.user.assigns.profanityCount will be updated
239
- if (req.user.assigns.profanityCount >= 3) {
240
- // Kick the user from the channel if they have used profanity more than 3 times
241
- res.evictUser('You have been kicked from the channel for using profanity');
242
- } else {
243
- // you can broadcast a message to all users or In the channel that profanity is not allowed
244
- res.broadcast('profanity-warning', {message: 'Profanity is not allowed'})
245
- // or you can send a message to the user that profanity is not allowed
246
- .sendToUsers('profanity-warning', {message: `You have used profanity ${profanityCount} times. You will be kicked from the channel if you use profanity more than 3 times.`}, [req.user.id]);
247
- }
248
- } else {
249
- // Accept the message to allow broadcasting to other clients in the channel
250
- res.accept();
418
+ #### Message Handling
419
+
420
+ ```typescript
421
+ @PondSocketChannel('/chat/:roomId')
422
+ export class ChatController {
423
+ @PondSocketEvent('message')
424
+ async handleMessage(ctx: Context) {
425
+ const { text } = ctx.event.payload;
426
+ const { username } = ctx.user.assigns;
427
+
428
+ return {
429
+ // Broadcast the message to all users
430
+ broadcast: 'message',
431
+ text,
432
+ username,
433
+ timestamp: Date.now(),
434
+
435
+ // Update user's last message timestamp
436
+ assigns: {
437
+ lastMessageAt: Date.now()
438
+ }
439
+ };
251
440
  }
252
-
253
- // for more complete access to the channel, you can use the channel instance
254
- // const channel = req.channel;
255
- });
256
-
257
- profanityChannel.onEvent('presence/:presence', (req, res) => {
258
- const {presence} = req.event.params;
259
- const {username} = req.user.assigns;
260
-
261
- // Handle presence events
262
- res.updatePresence({
263
- username,
264
- role,
265
- onlineSince: Date.now(),
266
- status: presence,
267
- });
268
- });
269
-
270
- profanityChannel.onLeave((event) => {
271
- const {username} = event.assigns;
272
-
273
- // When a user leaves the channel, PondSocket will automatically remove the user from the presence list and inform other users in the channel
274
-
275
- // perform a cleanup operation here
276
- });
277
-
278
- // Start the server
279
- pond.listen(3000, () => {
280
- console.log('PondSocket server listening on port 3000');
281
- });
441
+ }
282
442
  ```
283
443
 
284
- ## API Documentation
285
-
286
- ### PondSocket
287
-
288
- The `PondSocket` class is the core class that represents the socket server.
289
-
290
- **Constructor:**
291
-
292
- - `constructor(server?: HTTPServer, socketServer?: WebSocketServer)`: Creates a new instance of the PondSocket with an optional HTTP server and WebSocket server.
293
-
294
- **Methods:**
295
-
296
- - `listen(...args: any[]): HTTPServer`: Specifies the port to listen on with the provided arguments.
297
-
298
- - `close(callback?: () => void): HTTPServer`: Closes the server, and an optional callback can be provided.
299
-
300
- - `createEndpoint<Path extends string>(path: PondPath<Path>, handler: (request: IncomingConnection<Path>, response: ConnectionResponse) => void | Promise<void>): Endpoint`: Accepts a new socket upgrade request on the provided endpoint using the handler function to authenticate the socket.
301
-
302
- ### ConnectionResponse
303
-
304
- The `ConnectionResponse` class represents the response object for the incoming connection.
305
-
306
- **Methods:**
307
-
308
- - `accept(assigns?: PondAssigns): void`: Accepts the request and optionally assigns data to the client.
309
-
310
- - `reject(message?: string, errorCode?: number): void`: Rejects the request with the given error message and optional error code.
311
-
312
- - `send(event: string, payload: PondMessage, assigns?: PondAssigns): void`: Emits a direct message to the client with the specified event and payload.
313
-
314
- ### Endpoint
315
-
316
- The `Endpoint` class represents an endpoint in the PondSocket server where channels can be created.
317
-
318
- **Methods:**
319
-
320
- - `createChannel<Path extends string>(path: PondPath<Path>, handler: (request: JoinRequest<Path>, response: JoinResponse) => void | Promise<void>): PondChannel`: Adds a new PondChannel to this path on this endpoint with the provided handler function to authenticate the client.
321
-
322
- - `broadcast(event: string, payload: PondMessage): void`: Broadcasts a message to all clients connected to this endpoint with the specified event and payload.
323
-
324
- - `closeConnection(clientIds: string | string[]): void`: Closes specific clients connected to this endpoint identified by the provided clientIds.
325
-
326
- ### JoinRequest
327
-
328
- The `JoinRequest` class represents the request object when a client joins a channel.
329
-
330
- **Properties:**
331
-
332
- - `event: PondEvent<Path>`: The event associated with the request.
333
-
334
- - `channelName: string`: The name of the channel.
335
-
336
- - `assigns: UserAssigns`: The assigns data for the client.
337
-
338
- - `presence: UserPresences`: The presence data for the client.
339
-
340
- - `joinParams: JoinParams`: The join parameters for the client.
341
-
342
- - `user: UserData`: The user data associated with the client.
343
-
344
- - `channel: Channel`: The Channel instance associated with the request.
345
-
346
- ### JoinResponse
347
-
348
- The `JoinResponse` class represents the response object for the join request.
349
-
350
- **Methods:**
351
-
352
- - `accept(assigns?: PondAssigns): JoinResponse`: Accepts the join request and optionally assigns data to the client.
353
-
354
- - `reject(message?: string, errorCode?: number): JoinResponse`: Rejects the join request with the given error message and optional error code.
355
-
356
- - `send(event: string, payload: PondMessage, assigns?: PondAssigns): JoinResponse`: Emits a direct message to the client with the specified event, payload, and optional assigns data.
357
-
358
- - `broadcast(event: string, payload: PondMessage): JoinResponse`: Emits a message to all clients in the channel with the specified event and payload.
359
-
360
- - `broadcastFromUser(event: string, payload: PondMessage): JoinResponse`: Emits a message to all clients in the channel except the sender with the specified event and payload.
361
-
362
- - `sendToUsers(event: string, payload: PondMessage, userIds: string[]): JoinResponse`: Emits a message to a specific set of clients identified by the provided userIds with the specified event and payload.
363
-
364
- - `trackPresence(presence: PondPresence): JoinResponse`: Tracks the presence of the client in the channel.
365
-
366
- ### PondChannel
367
-
368
- The `PondChannel` class represents a Generic channel in the PondSocket server. It is used to create a channel whose path matches the provided PondPath.
369
-
370
- **Methods:**
371
-
372
- - `onEvent<Event extends string>(event: PondPath<Event>, handler: (request: EventRequest<Event>, response: EventResponse) => void | Promise<void>): void`: Handles an event request made by a user for the specified event with the provided handler function.
373
-
374
- - `broadcast(event: string, payload: PondMessage, channelName?: string): void`: Broadcasts a message to all users in the channel with the specified event and payload. Optionally, a specific channel name can be provided to broadcast the message only to users in that channel.
375
-
376
- - `onLeave(handler: (event: LeaveEvent) => void | Promise<void>): void`: Handles a leave event for the channel with the provided handler function when a user leaves the channel.
377
-
378
- ### EventRequest
379
-
380
- The `EventRequest` class represents the request object when an event is received from a client.
381
-
382
- **Properties:**
383
-
384
- - `event: PondEvent<Path>`: The event associated with the request.
385
-
386
- - `channelName: string`: The name of the channel.
387
-
388
- - `assigns: UserAssigns`: The assigns data for the client.
389
-
390
- - `presence: UserPresences`: The presence data for the client.
391
-
392
- - `user: UserData`: The user data associated with the client.
393
-
394
- - `channel: Channel`: The Channel instance associated with the request.
395
-
396
- ### EventResponse
397
-
398
- The `EventResponse` class represents the response object for handling events from clients.
399
-
400
- **Methods:**
401
-
402
- - `accept(assigns?: PondAssigns): EventResponse`: Accepts the request and optionally assigns data to the client.
403
-
404
- - `reject(message?: string, errorCode?: number, assigns?: PondAssigns): EventResponse`: Rejects the request with the given error message, optional error code, and optional assigns data.
405
-
406
- - `send(event: string, payload: PondMessage, assigns?: PondAssigns): void`: Emits a direct message to the client with the specified event, payload, and optional assigns data.
407
-
408
- - `broadcast(event: string, payload: PondMessage): EventResponse`: Sends a message to all clients in the channel with the specified event and payload.
409
-
410
- - `broadcastFromUser(event: string, payload: PondMessage): EventResponse`: Sends a message to all clients in the channel except the sender with the specified event and payload.
411
-
412
- - `sendToUsers(event: string, payload: PondMessage, userIds: string[]): EventResponse`: Sends a message to a specific set of clients identified by the provided userIds with the specified event and payload.
413
-
414
- - `trackPresence(presence: PondPresence, userId?: string): EventResponse`: Tracks a user's presence in the channel.
415
-
416
- - `updatePresence(presence: PondPresence, userId?: string): EventResponse`: Updates a user's presence in the channel.
417
-
418
- - `unTrackPresence(userId?: string): EventResponse`: Removes a user's presence from the channel.
419
-
420
- - `evictUser(reason: string, userId?: string): void`: Evicts a user from the channel.
421
-
422
- - `closeChannel(reason: string): void`: Closes the channel from the server-side for all clients.
423
-
424
- ### Channel
425
-
426
- The `Channel` class represents a single Channel created by the PondSocket server. Note that a PondChannel can have multiple channels associated with it.
427
-
428
- **Methods:**
429
-
430
- - `name: string`: The name of the channel.
431
-
432
- - `getAssigns: UserAssigns`: Gets the current assign data for the client.
433
-
434
- - `getUserData(userId: string): UserData`: Gets the assign data for a specific user identified by the provided `userId`.
435
-
436
- - `broadcastMessage(event: string, payload: PondMessage): void`: Broadcasts a message to every client in the channel with the specified event and payload.
437
-
438
- - `sendToUser(userId: string, event: string, payload: PondMessage): void`: Sends a message to a specific client in the channel identified by the provided `userId`, with the specified event and payload.
439
-
440
- - `sendToUsers(userIdS: string[], event: string, payload: PondMessage): void`: Sends a message to a specific set of clients identified by the provided `userIdS`, with the specified event and payload.
441
-
442
- - `evictUser(userId: string, reason?: string): void`: Bans a user from the channel identified by the provided `userId`. Optionally, you can provide a `reason` for the ban.
443
-
444
- - `trackPresence(userId: string, presence: PondPresence): void`: Tracks a user's presence in the channel identified by the provided `userId`.
445
-
446
- - `removePresence(userId: string): void`: Removes a user's presence from the channel identified by the provided `userId`.
447
-
448
- - `updatePresence(userId: string, presence: PondPresence): void`: Updates a user's presence in the channel identified by the provided `userId`.
449
-
450
- ### PondClient
451
-
452
- The `PondClient` class represents a client that connects to the PondSocket server.
453
-
454
- **Constructor:**
455
-
456
- - `constructor(endpoint: string, params?: Record<string, any>)`: Creates a new instance of the PondClient with the provided endpoint URL and optional parameters.
457
-
458
- **Methods:**
459
-
460
- - `connect(backoff?: number): void`: Connects to the server with an optional backoff time.
461
-
462
- - `getState(): boolean`: Returns the current state of the socket.
463
-
464
- - `disconnect(): void`: Disconnects the socket.
465
-
466
- - `createChannel(name: string, params?: JoinParams): ClientChannel`: Creates a channel with the given name and optional join parameters.
467
-
468
- - `onConnectionChange(callback: (state: boolean) => void): Unsubscribe`: Subscribes to the connection state changes and calls the provided callback when the state changes.
469
-
470
- ### ClientChannel
471
-
472
- The `ClientChannel` class represents a channel in the PondClient.
473
-
474
- **Methods:**
475
-
476
- - `join(): void`: Connects to the channel.
477
-
478
- - `leave(): void`: Disconnects from the channel.
479
-
480
- - `onMessage(callback: (event: string, message: PondMessage) => void): Unsubscribe`: Monitors the channel for messages and calls the provided callback when a message is received.
481
-
482
- - `onMessageEvent(event: string, callback: (message: PondMessage) => void): Unsubscribe`: Monitors the channel for messages with the specified event and calls the provided callback when a message is received.
483
-
484
- - `onChannelStateChange(callback: (connected: ChannelState) => void): Unsubscribe`: Monitors the channel state of the channel and calls the provided callback when the connection state changes.
485
-
486
- - `onJoin(callback: (presence: PondPresence) => void): Unsubscribe`: Detects when clients join the channel and calls the provided callback when a client joins the channel.
487
-
488
- - `onLeave(callback: (presence: PondPresence) => void): Unsubscribe`: Detects when clients leave the channel and calls the provided callback when a client leaves the channel.
489
-
490
- - `onPresenceChange(callback: (presence: PresencePayload) => void): Unsubscribe`: Detects when clients change their presence in the channel and calls the provided callback when a client changes their presence in the channel.
444
+ #### Presence Updates
445
+
446
+ ```typescript
447
+ @PondSocketChannel('/chat/:roomId')
448
+ export class ChatController {
449
+ @PondSocketEvent('status')
450
+ async handleStatus(ctx: Context) {
451
+ const { status } = ctx.event.payload;
452
+
453
+ return {
454
+ // Update user's presence
455
+ presence: {
456
+ status,
457
+ lastSeen: Date.now()
458
+ },
459
+
460
+ // Notify others about the status change
461
+ broadcastFrom: 'status_change',
462
+ username: ctx.user.assigns.username,
463
+ status,
464
+ timestamp: Date.now()
465
+ };
466
+ }
467
+ }
468
+ ```
491
469
 
492
- - `sendMessage(event: string, payload: PondMessage, recipient: string[]): void`: Sends a message to specific clients in the channel with the specified event, payload, and recipient.
470
+ ### Benefits
493
471
 
494
- - `sendForResponse(event: string, payload: PondMessage): Promise<PondMessage>`: Sends a message to the server with the specified event, payload, and returns a promise that resolves with the response.
472
+ 1. **Declarative Code**: Actions are clearly specified in the return object, making the code more readable and maintainable.
495
473
 
496
- - `broadcastFrom(event: string, payload: PondMessage): void`: Broadcasts a message to every other client in the channel except yourself with the specified event and payload.
474
+ 2. **Type Safety**: The return type is fully typed, providing excellent TypeScript support and IDE autocompletion.
497
475
 
498
- - `broadcast(event: string, payload: PondMessage): void`: Broadcasts a message to the channel, including yourself, with the specified event and payload.
476
+ 3. **Reduced Boilerplate**: No need to call multiple context methods; all actions are specified in a single return statement.
499
477
 
500
- - `getPresence(): PondPresence[]`: Gets the current presence of the channel.
478
+ 4. **Flexible Combinations**: You can combine multiple actions in a single return statement, making it easy to handle complex scenarios.
501
479
 
502
- - `onUsersChange(callback: (users: PondPresence[]) => void): Unsubscribe`: Monitors the presence of the channel and calls the provided callback when the presence changes.
480
+ 5. **Automatic Handling**: The framework automatically processes the returned object and executes the specified actions in the correct order.
503
481
 
504
- - `isConnected(): boolean`: Gets the current connection state of the channel.
482
+ ### Best Practices
505
483
 
506
- - `onConnectionChange(callback: (connected: boolean) => void): Unsubscribe`: Monitors the connection state of the channel and calls the provided callback when the connection state changes.
484
+ 1. **Type Your Returns**: Use TypeScript interfaces to define the shape of your return objects:
485
+ ```typescript
486
+ interface ChatMessage {
487
+ text: string;
488
+ username: string;
489
+ timestamp: number;
490
+ }
507
491
 
508
- ## License
492
+ @PondSocketEvent('message')
493
+ async handleMessage(ctx: Context): Promise<NestFuncType<'message', ChatMessage, UserPresence>> {
494
+ // Your implementation
495
+ }
496
+ ```
509
497
 
510
- PondSocket is released under the GPL-3.0 License. Please refer to the `LICENSE` file for detailed licensing information.
498
+ 2. **Keep It Simple**: While you can combine multiple actions, try to keep the return object focused on a single responsibility when possible.
511
499
 
512
- ## Conclusion
500
+ 3. **Use TypeScript**: Take advantage of TypeScript's type system to ensure your return objects are correctly structured.
513
501
 
514
- PondSocket is a powerful and versatile solution for building real-time applications that require efficient bidirectional communication between server and client components. Its minimalist design and comprehensive feature set make it an excellent choice for WebSocket-based projects, providing developers with a straightforward and reliable tool for building real-time communication systems. With the Node.js client, it also allows for easy communication between multiple server instances, expanding its capabilities even further.
502
+ 4. **Handle Errors**: Remember that you can still use `ctx.decline()` for error cases where returning an object isn't appropriate.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eleven-am/pondsocket-nest",
3
- "version": "0.0.129",
3
+ "version": "0.0.130",
4
4
  "description": "PondSocket is a fast simple socket server",
5
5
  "keywords": [
6
6
  "socket",
@@ -28,7 +28,7 @@
28
28
  "pipeline": "npm run build && npm run push"
29
29
  },
30
30
  "dependencies": {
31
- "@eleven-am/pondsocket": "^0.1.210",
31
+ "@eleven-am/pondsocket": "^0.1.212",
32
32
  "@golevelup/nestjs-discovery": "^5.0.0"
33
33
  },
34
34
  "devDependencies": {