@eleven-am/pondsocket-nest 0.0.136 → 0.0.138
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 +141 -541
- package/dist/context/context.d.ts +41 -0
- package/dist/context/context.js +238 -0
- package/{decorators → dist/decorators}/onConnectionRequest.js +2 -2
- package/{decorators → dist/decorators}/onEvent.js +2 -2
- package/{decorators → dist/decorators}/onJoinRequest.js +2 -2
- package/{decorators → dist/decorators}/onLeave.js +3 -4
- package/{decorators → dist/decorators}/onOutgoingEvent.js +10 -2
- package/{helpers → dist/helpers}/createParamDecorator.js +4 -3
- package/{helpers → dist/helpers}/misc.d.ts +1 -1
- package/dist/helpers/misc.js +27 -0
- package/dist/index.d.ts +6 -0
- package/{index.js → dist/index.js} +1 -0
- package/dist/managers/outgoingEvent.d.ts +5 -0
- package/{modules → dist/modules}/pondSocket.js +4 -5
- package/dist/performers/action.d.ts +8 -0
- package/dist/performers/action.js +121 -0
- package/dist/performers/errors.d.ts +4 -0
- package/dist/performers/errors.js +43 -0
- package/dist/performers/response.d.ts +5 -0
- package/dist/performers/response.js +65 -0
- package/{services → dist/services}/pondSocket.d.ts +12 -4
- package/dist/services/pondSocket.js +288 -0
- package/dist/types.d.ts +113 -0
- package/package.json +57 -46
- package/context/context.d.ts +0 -27
- package/context/context.js +0 -103
- package/helpers/misc.js +0 -13
- package/index.d.ts +0 -5
- package/managers/outgoingEvent.d.ts +0 -5
- package/performers/action.d.ts +0 -5
- package/performers/action.js +0 -89
- package/performers/errors.d.ts +0 -2
- package/performers/errors.js +0 -37
- package/performers/narrow.d.ts +0 -7
- package/performers/narrow.js +0 -21
- package/performers/response.d.ts +0 -3
- package/performers/response.js +0 -62
- package/services/pondSocket.js +0 -190
- package/types.d.ts +0 -64
- /package/{constants.d.ts → dist/constants.d.ts} +0 -0
- /package/{constants.js → dist/constants.js} +0 -0
- /package/{decorators → dist/decorators}/channel.d.ts +0 -0
- /package/{decorators → dist/decorators}/channel.js +0 -0
- /package/{decorators → dist/decorators}/channelInstance.d.ts +0 -0
- /package/{decorators → dist/decorators}/channelInstance.js +0 -0
- /package/{decorators → dist/decorators}/endpoint.d.ts +0 -0
- /package/{decorators → dist/decorators}/endpoint.js +0 -0
- /package/{decorators → dist/decorators}/endpointInstance.d.ts +0 -0
- /package/{decorators → dist/decorators}/endpointInstance.js +0 -0
- /package/{decorators → dist/decorators}/getChannel.d.ts +0 -0
- /package/{decorators → dist/decorators}/getChannel.js +0 -0
- /package/{decorators → dist/decorators}/getConnectionContext.d.ts +0 -0
- /package/{decorators → dist/decorators}/getConnectionContext.js +0 -0
- /package/{decorators → dist/decorators}/getConnectionHeaders.d.ts +0 -0
- /package/{decorators → dist/decorators}/getConnectionHeaders.js +0 -0
- /package/{decorators → dist/decorators}/getContext.d.ts +0 -0
- /package/{decorators → dist/decorators}/getContext.js +0 -0
- /package/{decorators → dist/decorators}/getEventContext.d.ts +0 -0
- /package/{decorators → dist/decorators}/getEventContext.js +0 -0
- /package/{decorators → dist/decorators}/getEventParams.d.ts +0 -0
- /package/{decorators → dist/decorators}/getEventParams.js +0 -0
- /package/{decorators → dist/decorators}/getEventPayload.d.ts +0 -0
- /package/{decorators → dist/decorators}/getEventPayload.js +0 -0
- /package/{decorators → dist/decorators}/getEventQuery.d.ts +0 -0
- /package/{decorators → dist/decorators}/getEventQuery.js +0 -0
- /package/{decorators → dist/decorators}/getJoinContext.d.ts +0 -0
- /package/{decorators → dist/decorators}/getJoinContext.js +0 -0
- /package/{decorators → dist/decorators}/getJoinParams.d.ts +0 -0
- /package/{decorators → dist/decorators}/getJoinParams.js +0 -0
- /package/{decorators → dist/decorators}/getLeaveEvent.d.ts +0 -0
- /package/{decorators → dist/decorators}/getLeaveEvent.js +0 -0
- /package/{decorators → dist/decorators}/getUserAssigns.d.ts +0 -0
- /package/{decorators → dist/decorators}/getUserAssigns.js +0 -0
- /package/{decorators → dist/decorators}/getUserData.d.ts +0 -0
- /package/{decorators → dist/decorators}/getUserData.js +0 -0
- /package/{decorators → dist/decorators}/getUserPresence.d.ts +0 -0
- /package/{decorators → dist/decorators}/getUserPresence.js +0 -0
- /package/{decorators → dist/decorators}/index.d.ts +0 -0
- /package/{decorators → dist/decorators}/index.js +0 -0
- /package/{decorators → dist/decorators}/onConnectionRequest.d.ts +0 -0
- /package/{decorators → dist/decorators}/onEvent.d.ts +0 -0
- /package/{decorators → dist/decorators}/onJoinRequest.d.ts +0 -0
- /package/{decorators → dist/decorators}/onLeave.d.ts +0 -0
- /package/{decorators → dist/decorators}/onOutgoingEvent.d.ts +0 -0
- /package/{decorators → dist/decorators}/useGuards.d.ts +0 -0
- /package/{decorators → dist/decorators}/useGuards.js +0 -0
- /package/{decorators → dist/decorators}/usePipes.d.ts +0 -0
- /package/{decorators → dist/decorators}/usePipes.js +0 -0
- /package/{helpers → dist/helpers}/createContextParamDecorator.d.ts +0 -0
- /package/{helpers → dist/helpers}/createContextParamDecorator.js +0 -0
- /package/{helpers → dist/helpers}/createParamDecorator.d.ts +0 -0
- /package/{managers → dist/managers}/channel.d.ts +0 -0
- /package/{managers → dist/managers}/channel.js +0 -0
- /package/{managers → dist/managers}/channelInstance.d.ts +0 -0
- /package/{managers → dist/managers}/channelInstance.js +0 -0
- /package/{managers → dist/managers}/class.d.ts +0 -0
- /package/{managers → dist/managers}/class.js +0 -0
- /package/{managers → dist/managers}/connection.d.ts +0 -0
- /package/{managers → dist/managers}/connection.js +0 -0
- /package/{managers → dist/managers}/createMetadataManager.d.ts +0 -0
- /package/{managers → dist/managers}/createMetadataManager.js +0 -0
- /package/{managers → dist/managers}/endpoint.d.ts +0 -0
- /package/{managers → dist/managers}/endpoint.js +0 -0
- /package/{managers → dist/managers}/endpointInstance.d.ts +0 -0
- /package/{managers → dist/managers}/endpointInstance.js +0 -0
- /package/{managers → dist/managers}/event.d.ts +0 -0
- /package/{managers → dist/managers}/event.js +0 -0
- /package/{managers → dist/managers}/guards.d.ts +0 -0
- /package/{managers → dist/managers}/guards.js +0 -0
- /package/{managers → dist/managers}/handlers.d.ts +0 -0
- /package/{managers → dist/managers}/handlers.js +0 -0
- /package/{managers → dist/managers}/join.d.ts +0 -0
- /package/{managers → dist/managers}/join.js +0 -0
- /package/{managers → dist/managers}/leave.d.ts +0 -0
- /package/{managers → dist/managers}/leave.js +0 -0
- /package/{managers → dist/managers}/method.d.ts +0 -0
- /package/{managers → dist/managers}/method.js +0 -0
- /package/{managers → dist/managers}/outgoingEvent.js +0 -0
- /package/{managers → dist/managers}/parametres.d.ts +0 -0
- /package/{managers → dist/managers}/parametres.js +0 -0
- /package/{managers → dist/managers}/pipes.d.ts +0 -0
- /package/{managers → dist/managers}/pipes.js +0 -0
- /package/{managers → dist/managers}/property.d.ts +0 -0
- /package/{managers → dist/managers}/property.js +0 -0
- /package/{modules → dist/modules}/pondSocket.d.ts +0 -0
- /package/{types.js → dist/types.js} +0 -0
package/README.md
CHANGED
|
@@ -1,590 +1,190 @@
|
|
|
1
|
-
# PondSocket NestJS
|
|
1
|
+
# PondSocket NestJS
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
functionality within NestJS applications.
|
|
3
|
+
NestJS integration for PondSocket endpoints and channels.
|
|
5
4
|
|
|
6
|
-
##
|
|
5
|
+
## Install
|
|
7
6
|
|
|
8
7
|
```bash
|
|
9
|
-
npm install @eleven-am/pondsocket-nest
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
guards: [AuthGuard], // Optional: Global guards
|
|
40
|
-
pipes: [ValidationPipe], // Optional: Global pipes
|
|
41
|
-
isGlobal: true, // Optional: Make the module global
|
|
42
|
-
})
|
|
43
|
-
]
|
|
44
|
-
})
|
|
45
|
-
export class AppModule {}
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
### Creating WebSocket Endpoints
|
|
49
|
-
|
|
50
|
-
```typescript
|
|
51
|
-
import {Controller} from '@nestjs/common';
|
|
52
|
-
import {PondSocketEndpoint, PondSocketConnection, Context} from '@eleven-am/pondsocket-nest';
|
|
53
|
-
|
|
54
|
-
@Endpoint('/api/socket')
|
|
55
|
-
export class SocketController {
|
|
56
|
-
@OnConnection()
|
|
57
|
-
async handleConnection (ctx: Context) {
|
|
58
|
-
const token = ctx.request.query.token;
|
|
59
|
-
|
|
60
|
-
if (isValidToken (token)) {
|
|
61
|
-
const role = getRoleFromToken (token);
|
|
62
|
-
ctx.accept ({role});
|
|
63
|
-
} else {
|
|
64
|
-
ctx.reject ('Invalid token', 401);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
8
|
+
npm install @eleven-am/pondsocket @eleven-am/pondsocket-common @eleven-am/pondsocket-nest
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
`@nestjs/common`, `@nestjs/core`, and `rxjs` are peer dependencies.
|
|
12
|
+
|
|
13
|
+
## Shared schema
|
|
14
|
+
|
|
15
|
+
Define a channel once in code shared by the server and client. Schema use is optional; the legacy string decorators and channel APIs remain supported.
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import {
|
|
19
|
+
definePondChannel,
|
|
20
|
+
definePondEndpoint,
|
|
21
|
+
definePondSchema,
|
|
22
|
+
} from '@eleven-am/pondsocket-common';
|
|
23
|
+
|
|
24
|
+
interface ChatSchema {
|
|
25
|
+
events: {
|
|
26
|
+
'message/:messageId': { text: string };
|
|
27
|
+
ping: [{ sentAt: number }, { receivedAt: number }];
|
|
28
|
+
};
|
|
29
|
+
presence: {
|
|
30
|
+
online: boolean;
|
|
31
|
+
};
|
|
32
|
+
assigns: {
|
|
33
|
+
role: 'admin' | 'member';
|
|
34
|
+
};
|
|
35
|
+
joinParams: {
|
|
36
|
+
token: string;
|
|
37
|
+
};
|
|
67
38
|
}
|
|
68
|
-
```
|
|
69
39
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
@Channel('/channel/:id')
|
|
77
|
-
export class ChannelController {
|
|
78
|
-
@OnJoin()
|
|
79
|
-
async handleJoin (ctx: Context) {
|
|
80
|
-
const {role} = ctx.user.assigns;
|
|
81
|
-
const {username} = ctx.joinParams;
|
|
82
|
-
const {id} = ctx.event.params;
|
|
83
|
-
|
|
84
|
-
if (role === 'admin') {
|
|
85
|
-
ctx.accept({username})
|
|
86
|
-
.trackPresence({
|
|
87
|
-
username,
|
|
88
|
-
role,
|
|
89
|
-
status: 'online',
|
|
90
|
-
onlineSince: Date.now (),
|
|
91
|
-
});
|
|
92
|
-
} else {
|
|
93
|
-
ctx.decline('Insufficient permissions', 403);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
40
|
+
export const Socket = definePondEndpoint('/v1/socket');
|
|
41
|
+
export const Chat = definePondChannel(
|
|
42
|
+
definePondSchema<ChatSchema>(),
|
|
43
|
+
'/chat/:roomId',
|
|
44
|
+
);
|
|
97
45
|
```
|
|
98
46
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
```typescript
|
|
102
|
-
import {Controller} from '@nestjs/common';
|
|
103
|
-
import {PondSocketEvent, Context} from '@eleven-am/pondsocket-nest';
|
|
104
|
-
|
|
105
|
-
@Channel('/channel/:id')
|
|
106
|
-
export class ChannelController {
|
|
107
|
-
@OnEvent('message')
|
|
108
|
-
async handleMessage (ctx: Context) {
|
|
109
|
-
const {text} = ctx.event.payload;
|
|
110
|
-
|
|
111
|
-
// Broadcast to all users in the channel
|
|
112
|
-
ctx.broadcast('message', {text});
|
|
113
|
-
|
|
114
|
-
// Broadcast to specific users
|
|
115
|
-
ctx.broadcastTo(['user1', 'user2'], 'message', {text});
|
|
116
|
-
|
|
117
|
-
// Broadcast to all except sender
|
|
118
|
-
ctx.broadcastFrom('message', {text});
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
```
|
|
47
|
+
The optional second argument to `definePondChannel` is the runtime channel pattern. It is inferred as a literal type, so it is never repeated as a generic.
|
|
122
48
|
|
|
123
|
-
##
|
|
49
|
+
## Nest handlers
|
|
124
50
|
|
|
125
|
-
|
|
51
|
+
Endpoint association uses the endpoint class. If an application has exactly one endpoint, `{ endpoint: SocketEndpoint }` may be omitted.
|
|
126
52
|
|
|
127
|
-
```
|
|
53
|
+
```ts
|
|
54
|
+
import type {
|
|
55
|
+
PondConnectionContext,
|
|
56
|
+
PondEventContext,
|
|
57
|
+
PondJoinContext,
|
|
58
|
+
PondOutgoingContext,
|
|
59
|
+
} from '@eleven-am/pondsocket-nest';
|
|
60
|
+
import { Chat, Socket } from './pond-definitions';
|
|
128
61
|
|
|
129
|
-
@
|
|
130
|
-
export class
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
status: 'online',
|
|
136
|
-
lastSeen: Date.now ()
|
|
137
|
-
});
|
|
138
|
-
}
|
|
62
|
+
@Socket.Endpoint()
|
|
63
|
+
export class SocketEndpoint {
|
|
64
|
+
@Socket.OnConnection()
|
|
65
|
+
connect(@Socket.GetContext() ctx: PondConnectionContext<typeof Socket>) {
|
|
66
|
+
ctx.accept();
|
|
67
|
+
}
|
|
139
68
|
}
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
### User Assigns
|
|
143
|
-
|
|
144
|
-
```typescript
|
|
145
69
|
|
|
146
|
-
@Channel
|
|
147
|
-
export class
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
70
|
+
@Chat.Channel({ endpoint: SocketEndpoint })
|
|
71
|
+
export class ChatController {
|
|
72
|
+
@Chat.OnJoin()
|
|
73
|
+
join(@Chat.GetContext() ctx: PondJoinContext<typeof Chat>) {
|
|
74
|
+
ctx.joinParams.token.toUpperCase();
|
|
75
|
+
ctx.params.roomId.toUpperCase();
|
|
76
|
+
ctx.accept({ role: 'member' }).trackPresence({ online: true });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@Chat.OnEvent('message/:messageId')
|
|
80
|
+
message(
|
|
81
|
+
@Chat.GetContext()
|
|
82
|
+
ctx: PondEventContext<typeof Chat, 'message/:messageId'>,
|
|
83
|
+
) {
|
|
84
|
+
ctx.params.messageId.toUpperCase();
|
|
85
|
+
ctx.payload.text.toUpperCase();
|
|
86
|
+
ctx.broadcast(ctx.event.event, ctx.payload);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@Chat.OnEvent('ping')
|
|
90
|
+
ping(
|
|
91
|
+
@Chat.GetContext()
|
|
92
|
+
ctx: PondEventContext<typeof Chat, 'ping'>,
|
|
93
|
+
) {
|
|
94
|
+
ctx.reply({ receivedAt: Date.now() });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
@Chat.OnOutgoingEvent('message/:messageId')
|
|
98
|
+
outgoing(
|
|
99
|
+
@Chat.GetContext()
|
|
100
|
+
ctx: PondOutgoingContext<typeof Chat, 'message/:messageId'>,
|
|
101
|
+
) {
|
|
102
|
+
ctx.transform({ text: ctx.payload.text.trim() });
|
|
103
|
+
}
|
|
155
104
|
}
|
|
156
105
|
```
|
|
157
106
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
```typescript
|
|
161
|
-
import {PondChannel} from "@eleven-am/pondsocket/types";
|
|
162
|
-
import {ChannelInstance} from "@eleven-am/pondsocke-nest";
|
|
163
|
-
|
|
164
|
-
@Channel ('/channel/:id')
|
|
165
|
-
export class ChannelController {
|
|
166
|
-
@ChannelInstance()
|
|
167
|
-
instancd: PondChannel // Instance of the channel
|
|
168
|
-
|
|
169
|
-
@OnEvent ('message')
|
|
170
|
-
async handleMessage (ctx: Context) {
|
|
171
|
-
try {
|
|
172
|
-
// Your logic here
|
|
173
|
-
ctx.accept();
|
|
174
|
-
} catch (error) {
|
|
175
|
-
ctx.decline (error.message, 400);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
```
|
|
107
|
+
Every handler parameter must use a PondSocket parameter decorator. `@Chat.GetContext()` and `@Socket.GetContext()` explicitly inject the definition-bound context; the TypeScript annotation narrows it to the handler lifecycle and event. Extraction decorators such as `@GetEventPayload()`, `@GetEventParams()`, and custom decorators remain available for individual values.
|
|
180
108
|
|
|
181
|
-
|
|
109
|
+
Outgoing handlers run once per recipient. They must call `ctx.transform(payload)` or `ctx.block()` explicitly; return values are ignored. For request/response tuple events, `ctx.action` discriminates the payload: `BROADCAST` carries the request payload and `SYSTEM` carries the response payload.
|
|
182
110
|
|
|
183
|
-
|
|
111
|
+
## Module
|
|
184
112
|
|
|
185
|
-
```
|
|
186
|
-
import {Module} from '@nestjs/common';
|
|
187
|
-
import {PondSocketModule} from '@eleven-am/pondsocket-nest';
|
|
188
|
-
import {RedisBackend} from '@eleven-am/pondsocket';
|
|
113
|
+
```ts
|
|
114
|
+
import { Module } from '@nestjs/common';
|
|
115
|
+
import { PondSocketModule } from '@eleven-am/pondsocket-nest';
|
|
189
116
|
|
|
190
117
|
@Module({
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
})
|
|
198
|
-
]
|
|
118
|
+
imports: [
|
|
119
|
+
PondSocketModule.forRoot({
|
|
120
|
+
providers: [SocketEndpoint, ChatController],
|
|
121
|
+
isExclusiveSocketServer: false,
|
|
122
|
+
}),
|
|
123
|
+
],
|
|
199
124
|
})
|
|
200
|
-
export class AppModule {
|
|
201
|
-
}
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
### Distributed Mode Features
|
|
205
|
-
|
|
206
|
-
The distributed mode enables you to scale your WebSocket application across multiple server instances while maintaining
|
|
207
|
-
state synchronization. Here are the key features:
|
|
208
|
-
|
|
209
|
-
1. **State Synchronization**
|
|
210
|
-
- Channel presence is synchronized across all instances
|
|
211
|
-
- User assigns are shared between instances
|
|
212
|
-
- Channel events are broadcasted to all instances
|
|
213
|
-
|
|
214
|
-
2. **Load Balancing**
|
|
215
|
-
- Multiple server instances can handle WebSocket connections
|
|
216
|
-
- Connections are distributed across available instances
|
|
217
|
-
- Automatic failover if an instance goes down
|
|
218
|
-
|
|
219
|
-
3. **Backend Options**
|
|
220
|
-
```typescript
|
|
221
|
-
// Redis Backend (Recommended for production)
|
|
222
|
-
import { RedisBackend } from '@eleven-am/pondsocket';
|
|
223
|
-
|
|
224
|
-
PondSocketModule.forRoot({
|
|
225
|
-
backend: new RedisBackend({
|
|
226
|
-
host: 'localhost',
|
|
227
|
-
port: 6379,
|
|
228
|
-
password: 'optional-password',
|
|
229
|
-
db: 0,
|
|
230
|
-
keyPrefix: 'pondsocket:', // Optional prefix for Redis keys
|
|
231
|
-
})
|
|
232
|
-
})
|
|
233
|
-
|
|
234
|
-
// Memory Backend (For development/testing)
|
|
235
|
-
import { MemoryBackend } from '@eleven-am/pondsocket';
|
|
236
|
-
|
|
237
|
-
PondSocketModule.forRoot({
|
|
238
|
-
backend: new MemoryBackend()
|
|
239
|
-
})
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
4. **Configuration Options**
|
|
243
|
-
```typescript
|
|
244
|
-
interface DistributedBackendOptions {
|
|
245
|
-
// Redis specific options
|
|
246
|
-
host?: string;
|
|
247
|
-
port?: number;
|
|
248
|
-
password?: string;
|
|
249
|
-
db?: number;
|
|
250
|
-
keyPrefix?: string;
|
|
251
|
-
|
|
252
|
-
// General options
|
|
253
|
-
reconnectInterval?: number; // Time between reconnection attempts
|
|
254
|
-
maxRetries?: number; // Maximum number of reconnection attempts
|
|
255
|
-
timeout?: number; // Operation timeout in milliseconds
|
|
256
|
-
}
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
5. **Error Handling**
|
|
260
|
-
```typescript
|
|
261
|
-
@Channel('/channel/:id')
|
|
262
|
-
export class ChannelController {
|
|
263
|
-
@OnEvent('message')
|
|
264
|
-
async handleMessage(ctx: Context) {
|
|
265
|
-
try {
|
|
266
|
-
// Your logic here
|
|
267
|
-
ctx.accept();
|
|
268
|
-
} catch (error) {
|
|
269
|
-
// Handle distributed backend errors
|
|
270
|
-
if (error instanceof DistributedBackendError) {
|
|
271
|
-
// Handle specific distributed backend errors
|
|
272
|
-
ctx.decline('Backend error occurred', 500);
|
|
273
|
-
} else {
|
|
274
|
-
ctx.decline(error.message, 400);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
6. **Health Checks**
|
|
282
|
-
```typescript
|
|
283
|
-
import { PondSocketService } from '@eleven-am/pondsocket-nest';
|
|
284
|
-
|
|
285
|
-
@Controller('health')
|
|
286
|
-
export class HealthController {
|
|
287
|
-
constructor(private readonly pondSocketService: PondSocketService) {}
|
|
288
|
-
|
|
289
|
-
@Get('websocket')
|
|
290
|
-
async checkWebSocketHealth() {
|
|
291
|
-
const isHealthy = await this.pondSocketService.isHealthy();
|
|
292
|
-
return {
|
|
293
|
-
status: isHealthy ? 'healthy' : 'unhealthy',
|
|
294
|
-
timestamp: new Date().toISOString()
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
7. **Best Practices**
|
|
301
|
-
- Use Redis backend in production environments
|
|
302
|
-
- Implement proper error handling for distributed operations
|
|
303
|
-
- Monitor backend connection health
|
|
304
|
-
- Use appropriate Redis configuration for your scale
|
|
305
|
-
- Consider using Redis Cluster for high availability
|
|
306
|
-
- Implement proper logging for distributed operations
|
|
307
|
-
|
|
308
|
-
8. **Scaling Considerations**
|
|
309
|
-
- Monitor Redis memory usage
|
|
310
|
-
- Implement proper cleanup of stale data
|
|
311
|
-
- Consider using Redis Cluster for larger deployments
|
|
312
|
-
- Implement proper error handling and retry mechanisms
|
|
313
|
-
- Monitor network latency between instances
|
|
314
|
-
- Implement proper logging and monitoring
|
|
315
|
-
|
|
316
|
-
## Configuration Options
|
|
317
|
-
|
|
318
|
-
The `PondSocketModule.forRoot()` method accepts the following options:
|
|
319
|
-
|
|
320
|
-
```typescript
|
|
321
|
-
interface PondSocketOptions {
|
|
322
|
-
guards?: any[]; // Global guards
|
|
323
|
-
pipes?: any[]; // Global pipes
|
|
324
|
-
providers?: any[]; // Additional providers
|
|
325
|
-
imports?: any[]; // Additional imports
|
|
326
|
-
exports?: any[]; // Additional exports
|
|
327
|
-
isGlobal?: boolean; // Make the module global
|
|
328
|
-
isExclusiveSocketServer?: boolean; // Use exclusive socket server
|
|
329
|
-
backend?: IDistributedBackend; // Distributed backend
|
|
330
|
-
}
|
|
125
|
+
export class AppModule {}
|
|
331
126
|
```
|
|
332
127
|
|
|
333
|
-
|
|
128
|
+
`forRootAsync` accepts the same Nest module metadata plus `inject` and `useFactory`. Its factory can return `backend`, `maxMessageSize`, and `heartbeatInterval`.
|
|
334
129
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
```typescript
|
|
338
|
-
import PondClient from "@eleven-am/pondsocket-client";
|
|
339
|
-
|
|
340
|
-
const socket = new PondClient('ws://your-server/api/socket', {
|
|
341
|
-
token: 'your-auth-token'
|
|
342
|
-
});
|
|
130
|
+
## Client
|
|
343
131
|
|
|
344
|
-
|
|
132
|
+
```ts
|
|
133
|
+
import { PondClient } from '@eleven-am/pondsocket-client';
|
|
134
|
+
import { Chat } from './pond-definitions';
|
|
345
135
|
|
|
346
|
-
const
|
|
347
|
-
|
|
136
|
+
const client = new PondClient('wss://example.com/v1/socket');
|
|
137
|
+
const channel = client.createChannel(Chat, {
|
|
138
|
+
params: { roomId: 'general' },
|
|
139
|
+
joinParams: { token: 'secret' },
|
|
348
140
|
});
|
|
349
141
|
|
|
350
|
-
channel.
|
|
351
|
-
|
|
352
|
-
channel.onMessage((event, message) => {
|
|
353
|
-
console.log (`Received message: ${message.text}`);
|
|
142
|
+
channel.onMessageEvent('message/:messageId', (payload) => {
|
|
143
|
+
payload.text.toUpperCase();
|
|
354
144
|
});
|
|
355
145
|
|
|
356
|
-
channel.
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
## Contributing
|
|
360
|
-
|
|
361
|
-
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
362
|
-
|
|
363
|
-
## License
|
|
364
|
-
|
|
365
|
-
This project is licensed under the GPL-3.0 License - see the LICENSE file for details.
|
|
366
|
-
|
|
367
|
-
## Return Type Functionality
|
|
368
|
-
|
|
369
|
-
The NestJS integration provides a powerful return type system that allows you to declaratively specify actions to be
|
|
370
|
-
taken when handling WebSocket events. Instead of using the context object directly, you can return an object with
|
|
371
|
-
specific properties to trigger various actions.
|
|
372
|
-
|
|
373
|
-
### Return Type Interface
|
|
374
|
-
|
|
375
|
-
```typescript
|
|
376
|
-
type NestFuncType<Event extends string, Payload extends PondMessage, Presence extends PondPresence, Assigns extends PondAssigns = PondAssigns> =
|
|
377
|
-
{
|
|
378
|
-
// Send an event to the user
|
|
379
|
-
event?: Event;
|
|
380
|
-
|
|
381
|
-
// Broadcast to all users in the channel
|
|
382
|
-
broadcast?: Event;
|
|
383
|
-
|
|
384
|
-
// Broadcast to all users except the sender
|
|
385
|
-
broadcastFrom?: Event;
|
|
386
|
-
|
|
387
|
-
// Update user assigns
|
|
388
|
-
assigns?: Partial<Assigns>;
|
|
389
|
-
|
|
390
|
-
// Update user presence
|
|
391
|
-
presence?: Presence;
|
|
392
|
-
}
|
|
393
|
-
& Payload;
|
|
146
|
+
channel.onError((error) => console.error(error.code, error.status, error.message));
|
|
147
|
+
channel.join();
|
|
394
148
|
```
|
|
395
149
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
#### Channel Join with Multiple Actions
|
|
399
|
-
|
|
400
|
-
```typescript
|
|
401
|
-
|
|
402
|
-
@PondSocketChannel('/chat/:roomId')
|
|
403
|
-
export class ChatController {
|
|
404
|
-
@PondSocketJoin()
|
|
405
|
-
async handleJoin (ctx: Context) {
|
|
406
|
-
const {username} = ctx.joinParams;
|
|
407
|
-
|
|
408
|
-
return {
|
|
409
|
-
// Send welcome message to the joining user
|
|
410
|
-
event: 'welcome',
|
|
411
|
-
message: 'Welcome to the chat!',
|
|
412
|
-
|
|
413
|
-
// Broadcast join notification to all users
|
|
414
|
-
broadcast: 'user_joined',
|
|
415
|
-
username,
|
|
416
|
-
timestamp: Date.now(),
|
|
417
|
-
|
|
418
|
-
// Update user's assigns
|
|
419
|
-
assigns: {
|
|
420
|
-
username,
|
|
421
|
-
joinedAt: Date.now(),
|
|
422
|
-
role: 'member'
|
|
423
|
-
},
|
|
424
|
-
|
|
425
|
-
// Update user's presence
|
|
426
|
-
presence: {
|
|
427
|
-
username,
|
|
428
|
-
status: 'online',
|
|
429
|
-
lastSeen: Date.now()
|
|
430
|
-
}
|
|
431
|
-
};
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
```
|
|
150
|
+
Required route and join parameters are derived from the definition. Presence, assigns, event names, event payloads, and request/response tuples are derived from the same schema.
|
|
435
151
|
|
|
436
|
-
|
|
152
|
+
## Responses and errors
|
|
437
153
|
|
|
438
|
-
|
|
154
|
+
A handler can use the context directly or return a declarative response. The nested `payload` form avoids collisions with control fields:
|
|
439
155
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
// Broadcast the message to all users
|
|
449
|
-
broadcast: 'message',
|
|
450
|
-
text,
|
|
451
|
-
username,
|
|
452
|
-
timestamp: Date.now(),
|
|
453
|
-
|
|
454
|
-
// Update user's last message timestamp
|
|
455
|
-
assigns: {
|
|
456
|
-
lastMessageAt: Date.now()
|
|
457
|
-
}
|
|
458
|
-
};
|
|
459
|
-
}
|
|
460
|
-
}
|
|
156
|
+
```ts
|
|
157
|
+
return {
|
|
158
|
+
event: 'message/:messageId',
|
|
159
|
+
eventParams: { messageId: 'message-1' },
|
|
160
|
+
payload: { text: 'saved' },
|
|
161
|
+
assigns: { role: 'member' },
|
|
162
|
+
presence: { online: true },
|
|
163
|
+
};
|
|
461
164
|
```
|
|
462
165
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
```typescript
|
|
166
|
+
Connection and join handlers can decline declaratively:
|
|
466
167
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
@PondSocketEvent('status')
|
|
470
|
-
async handleStatus(ctx: Context) {
|
|
471
|
-
const {status} = ctx.event.payload;
|
|
472
|
-
|
|
473
|
-
return {
|
|
474
|
-
// Update user's presence
|
|
475
|
-
presence: {
|
|
476
|
-
status,
|
|
477
|
-
lastSeen: Date.now()
|
|
478
|
-
},
|
|
479
|
-
|
|
480
|
-
// Notify others about the status change
|
|
481
|
-
broadcastFrom: 'status_change',
|
|
482
|
-
username: ctx.user.assigns.username,
|
|
483
|
-
status,
|
|
484
|
-
timestamp: Date.now()
|
|
485
|
-
};
|
|
486
|
-
}
|
|
487
|
-
}
|
|
168
|
+
```ts
|
|
169
|
+
return { decline: { message: 'Invalid token', status: 401 } };
|
|
488
170
|
```
|
|
489
171
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
1. **Declarative Code**: Actions are clearly specified in the return object, making the code more readable and
|
|
493
|
-
maintainable.
|
|
494
|
-
|
|
495
|
-
2. **Type Safety**: The return type is fully typed, providing excellent TypeScript support and IDE autocompletion.
|
|
172
|
+
Unhandled exceptions are logged through Nest's `Logger`. Event failures reply with `INTERNAL_SERVER_ERROR`; rejected event guards reply with `UNAUTHORIZED_BROADCAST`.
|
|
496
173
|
|
|
497
|
-
|
|
498
|
-
statement.
|
|
174
|
+
## Operations
|
|
499
175
|
|
|
500
|
-
|
|
501
|
-
complex scenarios.
|
|
176
|
+
`PondSocketService` is exported and injectable. `pondSocket` exposes the underlying server and `isHealthy()` reports backend/server health. Module initialization waits for the distributed backend, and module shutdown awaits PondSocket cleanup without closing Nest's shared HTTP server.
|
|
502
177
|
|
|
503
|
-
|
|
504
|
-
|
|
178
|
+
```ts
|
|
179
|
+
constructor(private readonly sockets: PondSocketService) {}
|
|
505
180
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
### Class Decorators
|
|
511
|
-
|
|
512
|
-
| Decorator | Description |
|
|
513
|
-
|-----------|-------------|
|
|
514
|
-
| `@Channel(path?: string)` | Marks a class as a WebSocket channel handler for the specified path |
|
|
515
|
-
| `@Endpoint(path?: string)` | Registers a class as a WebSocket endpoint at the specified path |
|
|
516
|
-
|
|
517
|
-
### Property Decorators
|
|
518
|
-
|
|
519
|
-
| Decorator | Description |
|
|
520
|
-
|-----------|-------------|
|
|
521
|
-
| `@ChannelInstance(channel?: string)` | Injects the underlying PondSocket channel instance into a property |
|
|
522
|
-
| `@EndpointInstance()` | Injects the endpoint instance into a property |
|
|
523
|
-
|
|
524
|
-
### Method Decorators
|
|
525
|
-
|
|
526
|
-
| Decorator | Description |
|
|
527
|
-
|-----------|-------------|
|
|
528
|
-
| `@OnConnectionRequest()` | Marks a method to handle new socket connections (authentication, setup, etc.) |
|
|
529
|
-
| `@OnEvent(event?: string)` | Marks a method to handle a specific event in a channel |
|
|
530
|
-
| `@OnJoinRequest()` | Marks a method to handle when a user joins a channel |
|
|
531
|
-
| `@OnLeave()` | Marks a method to handle when a user leaves a channel |
|
|
532
|
-
| `@UseGuards(...guards)` | Attaches guard(s) to a class or method for authorization logic |
|
|
533
|
-
| `@UsePipes(...pipes)` | Attaches pipe(s) (validators/transformers) to a class or method |
|
|
534
|
-
|
|
535
|
-
### Parameter Decorators
|
|
536
|
-
|
|
537
|
-
These decorators inject request-specific data into handler method parameters:
|
|
538
|
-
|
|
539
|
-
| Decorator | Description |
|
|
540
|
-
|-----------|-------------|
|
|
541
|
-
| `@GetChannel()` | Injects the current channel instance |
|
|
542
|
-
| `@GetConnectionHeaders()` | Injects the current connection headers |
|
|
543
|
-
| `@GetConnectionContext()` | Injects the current connection context |
|
|
544
|
-
| `@GetContext()` | Injects the current request context |
|
|
545
|
-
| `@GetEventParams()` | Injects the current event parameters |
|
|
546
|
-
| `@GetEventPayload()` | Injects the current event payload |
|
|
547
|
-
| `@GetEventQuery()` | Injects the current event query |
|
|
548
|
-
| `@GetEventContext()` | Injects the current event context |
|
|
549
|
-
| `@GetJoinParams()` | Injects the current join parameters |
|
|
550
|
-
| `@GetJoinContext()` | Injects the current join context |
|
|
551
|
-
| `@GetLeaveEvent()` | Injects the current leave event |
|
|
552
|
-
| `@GetUserData()` | Injects the current user data |
|
|
553
|
-
| `@GetUserAssigns()` | Injects the current user assigns |
|
|
554
|
-
| `@GetUserPresence()` | Injects the current user presence |
|
|
181
|
+
health() {
|
|
182
|
+
return this.sockets.isHealthy();
|
|
183
|
+
}
|
|
184
|
+
```
|
|
555
185
|
|
|
556
|
-
|
|
186
|
+
Channel patterns use path syntax: `/jobs/:id` and whole-segment `*` are supported. Patterns such as `job:*` are rejected at startup. A valid join that matches no registered channel receives a correlated `CHANNEL_NOT_FOUND` error instead of remaining in `JOINING`.
|
|
557
187
|
|
|
558
|
-
|
|
188
|
+
## Legacy API
|
|
559
189
|
|
|
560
|
-
|
|
561
|
-
@Channel('/chat/:roomId')
|
|
562
|
-
export class ChatController {
|
|
563
|
-
@ChannelInstance()
|
|
564
|
-
private channel: PondChannel;
|
|
565
|
-
|
|
566
|
-
@OnJoinRequest()
|
|
567
|
-
async handleJoin(
|
|
568
|
-
@GetContext() ctx: Context,
|
|
569
|
-
@GetJoinParams() params: any
|
|
570
|
-
) {
|
|
571
|
-
// Handle join request
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
@OnEvent('message')
|
|
575
|
-
async handleMessage(
|
|
576
|
-
@GetEventPayload() payload: any,
|
|
577
|
-
@GetUserData() user: any
|
|
578
|
-
) {
|
|
579
|
-
// Handle message event
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
@OnLeave()
|
|
583
|
-
async handleLeave(
|
|
584
|
-
@GetLeaveEvent() event: any,
|
|
585
|
-
@GetUserPresence() presence: any
|
|
586
|
-
) {
|
|
587
|
-
// Handle leave event
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
```
|
|
190
|
+
`@Endpoint(path)`, `@Channel(path)`, `@OnConnectionRequest()`, `@OnJoinRequest()`, `@OnEvent(event)`, `@OnOutgoingEvent(event)`, and `@OnLeave()` remain available. In applications with multiple endpoints, shared definitions are recommended because they provide explicit endpoint ownership and schema-derived types.
|