servcraft 0.1.0 → 0.1.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/.claude/settings.local.json +29 -0
- package/.github/CODEOWNERS +18 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +46 -0
- package/.github/dependabot.yml +59 -0
- package/.github/workflows/ci.yml +188 -0
- package/.github/workflows/release.yml +195 -0
- package/AUDIT.md +602 -0
- package/README.md +1070 -1
- package/dist/cli/index.cjs +2026 -2168
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +2026 -2168
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +595 -616
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +114 -52
- package/dist/index.d.ts +114 -52
- package/dist/index.js +595 -616
- package/dist/index.js.map +1 -1
- package/docs/CLI-001_MULTI_DB_PLAN.md +546 -0
- package/docs/DATABASE_MULTI_ORM.md +399 -0
- package/docs/PHASE1_BREAKDOWN.md +346 -0
- package/docs/PROGRESS.md +550 -0
- package/docs/modules/ANALYTICS.md +226 -0
- package/docs/modules/API-VERSIONING.md +252 -0
- package/docs/modules/AUDIT.md +192 -0
- package/docs/modules/AUTH.md +431 -0
- package/docs/modules/CACHE.md +346 -0
- package/docs/modules/EMAIL.md +254 -0
- package/docs/modules/FEATURE-FLAG.md +291 -0
- package/docs/modules/I18N.md +294 -0
- package/docs/modules/MEDIA-PROCESSING.md +281 -0
- package/docs/modules/MFA.md +266 -0
- package/docs/modules/NOTIFICATION.md +311 -0
- package/docs/modules/OAUTH.md +237 -0
- package/docs/modules/PAYMENT.md +804 -0
- package/docs/modules/QUEUE.md +540 -0
- package/docs/modules/RATE-LIMIT.md +339 -0
- package/docs/modules/SEARCH.md +288 -0
- package/docs/modules/SECURITY.md +327 -0
- package/docs/modules/SESSION.md +382 -0
- package/docs/modules/SWAGGER.md +305 -0
- package/docs/modules/UPLOAD.md +296 -0
- package/docs/modules/USER.md +505 -0
- package/docs/modules/VALIDATION.md +294 -0
- package/docs/modules/WEBHOOK.md +270 -0
- package/docs/modules/WEBSOCKET.md +691 -0
- package/package.json +53 -38
- package/prisma/schema.prisma +395 -1
- package/src/cli/commands/add-module.ts +520 -87
- package/src/cli/commands/db.ts +3 -4
- package/src/cli/commands/docs.ts +256 -6
- package/src/cli/commands/generate.ts +12 -19
- package/src/cli/commands/init.ts +384 -214
- package/src/cli/index.ts +0 -4
- package/src/cli/templates/repository.ts +6 -1
- package/src/cli/templates/routes.ts +6 -21
- package/src/cli/utils/docs-generator.ts +6 -7
- package/src/cli/utils/env-manager.ts +717 -0
- package/src/cli/utils/field-parser.ts +16 -7
- package/src/cli/utils/interactive-prompt.ts +223 -0
- package/src/cli/utils/template-manager.ts +346 -0
- package/src/config/database.config.ts +183 -0
- package/src/config/env.ts +0 -10
- package/src/config/index.ts +0 -14
- package/src/core/server.ts +1 -1
- package/src/database/adapters/mongoose.adapter.ts +132 -0
- package/src/database/adapters/prisma.adapter.ts +118 -0
- package/src/database/connection.ts +190 -0
- package/src/database/interfaces/database.interface.ts +85 -0
- package/src/database/interfaces/index.ts +7 -0
- package/src/database/interfaces/repository.interface.ts +129 -0
- package/src/database/models/mongoose/index.ts +7 -0
- package/src/database/models/mongoose/payment.schema.ts +347 -0
- package/src/database/models/mongoose/user.schema.ts +154 -0
- package/src/database/prisma.ts +1 -4
- package/src/database/redis.ts +101 -0
- package/src/database/repositories/mongoose/index.ts +7 -0
- package/src/database/repositories/mongoose/payment.repository.ts +380 -0
- package/src/database/repositories/mongoose/user.repository.ts +255 -0
- package/src/database/seed.ts +6 -1
- package/src/index.ts +9 -20
- package/src/middleware/security.ts +2 -6
- package/src/modules/analytics/analytics.routes.ts +80 -0
- package/src/modules/analytics/analytics.service.ts +364 -0
- package/src/modules/analytics/index.ts +18 -0
- package/src/modules/analytics/types.ts +180 -0
- package/src/modules/api-versioning/index.ts +15 -0
- package/src/modules/api-versioning/types.ts +86 -0
- package/src/modules/api-versioning/versioning.middleware.ts +120 -0
- package/src/modules/api-versioning/versioning.routes.ts +54 -0
- package/src/modules/api-versioning/versioning.service.ts +189 -0
- package/src/modules/audit/audit.repository.ts +206 -0
- package/src/modules/audit/audit.service.ts +27 -59
- package/src/modules/auth/auth.controller.ts +2 -2
- package/src/modules/auth/auth.middleware.ts +3 -9
- package/src/modules/auth/auth.routes.ts +10 -107
- package/src/modules/auth/auth.service.ts +126 -23
- package/src/modules/auth/index.ts +3 -4
- package/src/modules/cache/cache.service.ts +367 -0
- package/src/modules/cache/index.ts +10 -0
- package/src/modules/cache/types.ts +44 -0
- package/src/modules/email/email.service.ts +3 -10
- package/src/modules/email/templates.ts +2 -8
- package/src/modules/feature-flag/feature-flag.repository.ts +303 -0
- package/src/modules/feature-flag/feature-flag.routes.ts +247 -0
- package/src/modules/feature-flag/feature-flag.service.ts +566 -0
- package/src/modules/feature-flag/index.ts +20 -0
- package/src/modules/feature-flag/types.ts +192 -0
- package/src/modules/i18n/i18n.middleware.ts +186 -0
- package/src/modules/i18n/i18n.routes.ts +191 -0
- package/src/modules/i18n/i18n.service.ts +456 -0
- package/src/modules/i18n/index.ts +18 -0
- package/src/modules/i18n/types.ts +118 -0
- package/src/modules/media-processing/index.ts +17 -0
- package/src/modules/media-processing/media-processing.routes.ts +111 -0
- package/src/modules/media-processing/media-processing.service.ts +245 -0
- package/src/modules/media-processing/types.ts +156 -0
- package/src/modules/mfa/index.ts +20 -0
- package/src/modules/mfa/mfa.repository.ts +206 -0
- package/src/modules/mfa/mfa.routes.ts +595 -0
- package/src/modules/mfa/mfa.service.ts +572 -0
- package/src/modules/mfa/totp.ts +150 -0
- package/src/modules/mfa/types.ts +57 -0
- package/src/modules/notification/index.ts +20 -0
- package/src/modules/notification/notification.repository.ts +356 -0
- package/src/modules/notification/notification.service.ts +483 -0
- package/src/modules/notification/types.ts +119 -0
- package/src/modules/oauth/index.ts +20 -0
- package/src/modules/oauth/oauth.repository.ts +219 -0
- package/src/modules/oauth/oauth.routes.ts +446 -0
- package/src/modules/oauth/oauth.service.ts +293 -0
- package/src/modules/oauth/providers/apple.provider.ts +250 -0
- package/src/modules/oauth/providers/facebook.provider.ts +181 -0
- package/src/modules/oauth/providers/github.provider.ts +248 -0
- package/src/modules/oauth/providers/google.provider.ts +189 -0
- package/src/modules/oauth/providers/twitter.provider.ts +214 -0
- package/src/modules/oauth/types.ts +94 -0
- package/src/modules/payment/index.ts +19 -0
- package/src/modules/payment/payment.repository.ts +733 -0
- package/src/modules/payment/payment.routes.ts +390 -0
- package/src/modules/payment/payment.service.ts +354 -0
- package/src/modules/payment/providers/mobile-money.provider.ts +274 -0
- package/src/modules/payment/providers/paypal.provider.ts +190 -0
- package/src/modules/payment/providers/stripe.provider.ts +215 -0
- package/src/modules/payment/types.ts +140 -0
- package/src/modules/queue/cron.ts +438 -0
- package/src/modules/queue/index.ts +87 -0
- package/src/modules/queue/queue.routes.ts +600 -0
- package/src/modules/queue/queue.service.ts +842 -0
- package/src/modules/queue/types.ts +222 -0
- package/src/modules/queue/workers.ts +366 -0
- package/src/modules/rate-limit/index.ts +59 -0
- package/src/modules/rate-limit/rate-limit.middleware.ts +134 -0
- package/src/modules/rate-limit/rate-limit.routes.ts +269 -0
- package/src/modules/rate-limit/rate-limit.service.ts +348 -0
- package/src/modules/rate-limit/stores/memory.store.ts +165 -0
- package/src/modules/rate-limit/stores/redis.store.ts +322 -0
- package/src/modules/rate-limit/types.ts +153 -0
- package/src/modules/search/adapters/elasticsearch.adapter.ts +326 -0
- package/src/modules/search/adapters/meilisearch.adapter.ts +261 -0
- package/src/modules/search/adapters/memory.adapter.ts +278 -0
- package/src/modules/search/index.ts +21 -0
- package/src/modules/search/search.service.ts +234 -0
- package/src/modules/search/types.ts +214 -0
- package/src/modules/security/index.ts +40 -0
- package/src/modules/security/sanitize.ts +223 -0
- package/src/modules/security/security-audit.service.ts +388 -0
- package/src/modules/security/security.middleware.ts +398 -0
- package/src/modules/session/index.ts +3 -0
- package/src/modules/session/session.repository.ts +159 -0
- package/src/modules/session/session.service.ts +340 -0
- package/src/modules/session/types.ts +38 -0
- package/src/modules/swagger/index.ts +7 -1
- package/src/modules/swagger/schema-builder.ts +16 -4
- package/src/modules/swagger/swagger.service.ts +9 -10
- package/src/modules/swagger/types.ts +0 -2
- package/src/modules/upload/index.ts +14 -0
- package/src/modules/upload/types.ts +83 -0
- package/src/modules/upload/upload.repository.ts +199 -0
- package/src/modules/upload/upload.routes.ts +311 -0
- package/src/modules/upload/upload.service.ts +448 -0
- package/src/modules/user/index.ts +3 -3
- package/src/modules/user/user.controller.ts +15 -9
- package/src/modules/user/user.repository.ts +237 -113
- package/src/modules/user/user.routes.ts +39 -164
- package/src/modules/user/user.service.ts +4 -3
- package/src/modules/validation/validator.ts +12 -17
- package/src/modules/webhook/index.ts +91 -0
- package/src/modules/webhook/retry.ts +196 -0
- package/src/modules/webhook/signature.ts +135 -0
- package/src/modules/webhook/types.ts +181 -0
- package/src/modules/webhook/webhook.repository.ts +358 -0
- package/src/modules/webhook/webhook.routes.ts +442 -0
- package/src/modules/webhook/webhook.service.ts +457 -0
- package/src/modules/websocket/features.ts +504 -0
- package/src/modules/websocket/index.ts +106 -0
- package/src/modules/websocket/middlewares.ts +298 -0
- package/src/modules/websocket/types.ts +181 -0
- package/src/modules/websocket/websocket.service.ts +692 -0
- package/src/utils/errors.ts +7 -0
- package/src/utils/pagination.ts +4 -1
- package/tests/helpers/db-check.ts +79 -0
- package/tests/integration/auth-redis.test.ts +94 -0
- package/tests/integration/cache-redis.test.ts +387 -0
- package/tests/integration/mongoose-repositories.test.ts +410 -0
- package/tests/integration/payment-prisma.test.ts +637 -0
- package/tests/integration/queue-bullmq.test.ts +417 -0
- package/tests/integration/user-prisma.test.ts +441 -0
- package/tests/integration/websocket-socketio.test.ts +552 -0
- package/tests/setup.ts +11 -9
- package/vitest.config.ts +3 -8
- package/npm-cache/_cacache/content-v2/sha512/1c/d0/03440d500a0487621aad1d6402978340698976602046db8e24fa03c01ee6c022c69b0582f969042d9442ee876ac35c038e960dd427d1e622fa24b8eb7dba +0 -0
- package/npm-cache/_cacache/content-v2/sha512/42/55/28b493ca491833e5aab0e9c3108d29ab3f36c248ca88f45d4630674fce9130959e56ae308797ac2b6328fa7f09a610b9550ed09cb971d039876d293fc69d +0 -0
- package/npm-cache/_cacache/content-v2/sha512/e0/12/f360dc9315ee5f17844a0c8c233ee6bf7c30837c4a02ea0d56c61c7f7ab21c0e958e50ed2c57c59f983c762b93056778c9009b2398ffc26def0183999b13 +0 -0
- package/npm-cache/_cacache/content-v2/sha512/ed/b0/fae1161902898f4c913c67d7f6cdf6be0665aec3b389b9c4f4f0a101ca1da59badf1b59c4e0030f5223023b8d63cfe501c46a32c20c895d4fb3f11ca2232 +0 -0
- package/npm-cache/_cacache/index-v5/58/94/c2cba79e0f16b4c10e95a87e32255741149e8222cc314a476aab67c39cc0 +0 -5
|
@@ -0,0 +1,691 @@
|
|
|
1
|
+
# WebSocket Module Documentation
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The WebSocket module provides real-time bidirectional communication using Socket.io with Redis adapter support for multi-instance deployments. It manages connections, rooms, messages, and broadcasting capabilities.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- ✅ Real Socket.io integration (HTTP server required)
|
|
10
|
+
- ✅ Redis adapter for multi-instance support
|
|
11
|
+
- ✅ Connection lifecycle management
|
|
12
|
+
- ✅ Room-based communication
|
|
13
|
+
- ✅ Message history storage
|
|
14
|
+
- ✅ Broadcasting (to rooms, users, all)
|
|
15
|
+
- ✅ Typing indicators
|
|
16
|
+
- ✅ User presence tracking
|
|
17
|
+
- ✅ Connection statistics
|
|
18
|
+
- ✅ Graceful shutdown
|
|
19
|
+
|
|
20
|
+
## Configuration
|
|
21
|
+
|
|
22
|
+
### Basic Setup (Single Instance)
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { createServer } from 'http';
|
|
26
|
+
import { WebSocketService } from './modules/websocket/websocket.service.js';
|
|
27
|
+
|
|
28
|
+
const httpServer = createServer();
|
|
29
|
+
|
|
30
|
+
const wsService = new WebSocketService({
|
|
31
|
+
path: '/socket.io',
|
|
32
|
+
cors: {
|
|
33
|
+
origin: '*',
|
|
34
|
+
credentials: true,
|
|
35
|
+
},
|
|
36
|
+
pingTimeout: 60000,
|
|
37
|
+
pingInterval: 25000,
|
|
38
|
+
maxHttpBufferSize: 1e6, // 1MB
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Initialize Socket.io with HTTP server
|
|
42
|
+
wsService.initialize(httpServer);
|
|
43
|
+
|
|
44
|
+
httpServer.listen(3000);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Multi-Instance Setup (with Redis)
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
const wsService = new WebSocketService({
|
|
51
|
+
path: '/socket.io',
|
|
52
|
+
cors: {
|
|
53
|
+
origin: ['https://example.com', 'https://www.example.com'],
|
|
54
|
+
credentials: true,
|
|
55
|
+
},
|
|
56
|
+
redis: {
|
|
57
|
+
host: process.env.REDIS_HOST || 'localhost',
|
|
58
|
+
port: parseInt(process.env.REDIS_PORT || '6379'),
|
|
59
|
+
password: process.env.REDIS_PASSWORD,
|
|
60
|
+
},
|
|
61
|
+
pingTimeout: 60000,
|
|
62
|
+
pingInterval: 25000,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
wsService.initialize(httpServer);
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## API Reference
|
|
69
|
+
|
|
70
|
+
### Initialization
|
|
71
|
+
|
|
72
|
+
#### `initialize(httpServer: HTTPServer): void`
|
|
73
|
+
|
|
74
|
+
Initialize Socket.io server with an HTTP server instance.
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
wsService.initialize(httpServer);
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**Important**: Without an HTTP server, the service runs in mock mode (logs only).
|
|
81
|
+
|
|
82
|
+
### Connection Management
|
|
83
|
+
|
|
84
|
+
#### `handleConnection(socket: AuthenticatedSocket): Promise<void>`
|
|
85
|
+
|
|
86
|
+
Handle new client connection. Called automatically by Socket.io.
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
// Automatically handled on 'connection' event
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
#### `handleDisconnection(socketId: string, reason?: string): Promise<void>`
|
|
93
|
+
|
|
94
|
+
Handle client disconnection.
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
await wsService.handleDisconnection(socketId, 'client_disconnect');
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
#### `getUser(socketId: string): SocketUser | undefined`
|
|
101
|
+
|
|
102
|
+
Get connected user by socket ID.
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
const user = wsService.getUser(socketId);
|
|
106
|
+
if (user) {
|
|
107
|
+
console.log(user.username, user.email);
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
#### `getConnectedUsers(): SocketUser[]`
|
|
112
|
+
|
|
113
|
+
Get all connected users.
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
const users = wsService.getConnectedUsers();
|
|
117
|
+
console.log(`${users.length} users online`);
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
#### `isUserOnline(userId: string): boolean`
|
|
121
|
+
|
|
122
|
+
Check if user is online.
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
if (wsService.isUserOnline(userId)) {
|
|
126
|
+
console.log('User is online');
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
#### `getUserSockets(userId: string): string[]`
|
|
131
|
+
|
|
132
|
+
Get all socket IDs for a user (multiple tabs/devices).
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
const sockets = wsService.getUserSockets(userId);
|
|
136
|
+
console.log(`User has ${sockets.length} active connections`);
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Room Management
|
|
140
|
+
|
|
141
|
+
#### `createRoom(name: string, namespace?: string, createdBy?: string, metadata?: object): Promise<Room>`
|
|
142
|
+
|
|
143
|
+
Create a new room.
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
const room = await wsService.createRoom('general-chat', 'default', userId, {
|
|
147
|
+
topic: 'General Discussion',
|
|
148
|
+
public: true,
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
#### `getRoom(roomId: string): Room | undefined`
|
|
153
|
+
|
|
154
|
+
Get room by ID.
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
const room = wsService.getRoom(roomId);
|
|
158
|
+
if (room) {
|
|
159
|
+
console.log(room.name, room.members.size);
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
#### `listRooms(namespace?: string): Room[]`
|
|
164
|
+
|
|
165
|
+
List all rooms, optionally filtered by namespace.
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
const allRooms = wsService.listRooms();
|
|
169
|
+
const chatRooms = wsService.listRooms('chat');
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
#### `joinRoom(socketId: string, roomId: string): Promise<void>`
|
|
173
|
+
|
|
174
|
+
Add user to room.
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
await wsService.joinRoom(socketId, roomId);
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**Note**: Client should also call `socket.join(roomId)` on Socket.io side.
|
|
181
|
+
|
|
182
|
+
#### `leaveRoom(socketId: string, roomId: string): Promise<void>`
|
|
183
|
+
|
|
184
|
+
Remove user from room.
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
await wsService.leaveRoom(socketId, roomId);
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
#### `deleteRoom(roomId: string): Promise<void>`
|
|
191
|
+
|
|
192
|
+
Delete a room.
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
await wsService.deleteRoom(roomId);
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
#### `getRoomMembers(roomId: string): SocketUser[]`
|
|
199
|
+
|
|
200
|
+
Get all members in a room.
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
const members = wsService.getRoomMembers(roomId);
|
|
204
|
+
members.forEach((user) => console.log(user.username));
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Messaging
|
|
208
|
+
|
|
209
|
+
#### `sendMessage(roomId: string, userId: string, content: string, type?: MessageType, metadata?: object): Promise<Message>`
|
|
210
|
+
|
|
211
|
+
Send message to room and store in history.
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
const message = await wsService.sendMessage(
|
|
215
|
+
roomId,
|
|
216
|
+
userId,
|
|
217
|
+
'Hello everyone!',
|
|
218
|
+
'text',
|
|
219
|
+
{ edited: false }
|
|
220
|
+
);
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
**Message types**: `'text'`, `'image'`, `'file'`, `'system'`
|
|
224
|
+
|
|
225
|
+
#### `getRoomMessages(roomId: string, limit?: number, offset?: number): Message[]`
|
|
226
|
+
|
|
227
|
+
Get room message history.
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
const recentMessages = wsService.getRoomMessages(roomId, 50); // Last 50 messages
|
|
231
|
+
const older = wsService.getRoomMessages(roomId, 50, 50); // 50 messages, skip last 50
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Broadcasting
|
|
235
|
+
|
|
236
|
+
#### `broadcastToRoom(roomId: string, event: string, data: unknown, options?: BroadcastOptions): Promise<void>`
|
|
237
|
+
|
|
238
|
+
Broadcast event to all users in a room.
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
await wsService.broadcastToRoom('room-123', 'user:joined', {
|
|
242
|
+
username: 'Alice',
|
|
243
|
+
timestamp: new Date(),
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Exclude specific sockets
|
|
247
|
+
await wsService.broadcastToRoom('room-123', 'message', messageData, {
|
|
248
|
+
except: [senderSocketId],
|
|
249
|
+
});
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
#### `broadcastToUsers(userIds: string[], event: string, data: unknown): Promise<void>`
|
|
253
|
+
|
|
254
|
+
Broadcast to specific users (all their sockets).
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
await wsService.broadcastToUsers(
|
|
258
|
+
[userId1, userId2, userId3],
|
|
259
|
+
'notification',
|
|
260
|
+
{ type: 'friend_request', from: 'Alice' }
|
|
261
|
+
);
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
#### `broadcastToAll(event: string, data: unknown, options?: BroadcastOptions): Promise<void>`
|
|
265
|
+
|
|
266
|
+
Broadcast to all connected users.
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
await wsService.broadcastToAll('system:announcement', {
|
|
270
|
+
message: 'Server maintenance in 5 minutes',
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// Exclude certain sockets
|
|
274
|
+
await wsService.broadcastToAll('update', data, {
|
|
275
|
+
except: [adminSocketId],
|
|
276
|
+
});
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
#### `emitToSocket(socketId: string, event: string, data: unknown): Promise<void>`
|
|
280
|
+
|
|
281
|
+
Emit event to specific socket.
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
await wsService.emitToSocket(socketId, 'private:message', {
|
|
285
|
+
from: 'Admin',
|
|
286
|
+
content: 'Welcome!',
|
|
287
|
+
});
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Statistics
|
|
291
|
+
|
|
292
|
+
#### `getStats(): ConnectionStats`
|
|
293
|
+
|
|
294
|
+
Get connection statistics.
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
const stats = wsService.getStats();
|
|
298
|
+
console.log(`
|
|
299
|
+
Total connections: ${stats.totalConnections}
|
|
300
|
+
Active connections: ${stats.activeConnections}
|
|
301
|
+
Total rooms: ${stats.totalRooms}
|
|
302
|
+
`);
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
#### `getRoomStats(roomId: string): RoomStats | null`
|
|
306
|
+
|
|
307
|
+
Get room statistics.
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
const stats = wsService.getRoomStats(roomId);
|
|
311
|
+
if (stats) {
|
|
312
|
+
console.log(`
|
|
313
|
+
Members: ${stats.memberCount}
|
|
314
|
+
Messages: ${stats.messageCount}
|
|
315
|
+
Created: ${stats.createdAt}
|
|
316
|
+
Last activity: ${stats.lastActivity}
|
|
317
|
+
`);
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### Maintenance
|
|
322
|
+
|
|
323
|
+
#### `disconnectUser(userId: string, reason?: string): Promise<void>`
|
|
324
|
+
|
|
325
|
+
Forcefully disconnect all user's sockets.
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
await wsService.disconnectUser(userId, 'violation_of_terms');
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
#### `cleanup(inactiveMinutes?: number): Promise<number>`
|
|
332
|
+
|
|
333
|
+
Clean up inactive connections.
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
const cleaned = await wsService.cleanup(30); // Remove connections inactive for 30+ minutes
|
|
337
|
+
console.log(`Cleaned ${cleaned} inactive connections`);
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
#### `shutdown(): Promise<void>`
|
|
341
|
+
|
|
342
|
+
Gracefully shutdown WebSocket service.
|
|
343
|
+
|
|
344
|
+
```typescript
|
|
345
|
+
await wsService.shutdown();
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
## Client-Side Usage
|
|
349
|
+
|
|
350
|
+
### Connection
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
import { io } from 'socket.io-client';
|
|
354
|
+
|
|
355
|
+
const socket = io('http://localhost:3000', {
|
|
356
|
+
path: '/socket.io',
|
|
357
|
+
query: {
|
|
358
|
+
username: 'Alice',
|
|
359
|
+
email: 'alice@example.com',
|
|
360
|
+
},
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
socket.on('connect', () => {
|
|
364
|
+
console.log('Connected:', socket.id);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
socket.on('disconnect', () => {
|
|
368
|
+
console.log('Disconnected');
|
|
369
|
+
});
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### Joining Rooms
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
socket.emit('room:join', { roomId: 'room-123' });
|
|
376
|
+
|
|
377
|
+
socket.on('error', (error) => {
|
|
378
|
+
console.error('Error:', error.message);
|
|
379
|
+
});
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### Sending Messages
|
|
383
|
+
|
|
384
|
+
```typescript
|
|
385
|
+
socket.emit('message', {
|
|
386
|
+
roomId: 'room-123',
|
|
387
|
+
content: 'Hello everyone!',
|
|
388
|
+
type: 'text',
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
socket.on('message', (message) => {
|
|
392
|
+
console.log(`${message.username}: ${message.content}`);
|
|
393
|
+
});
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### Typing Indicators
|
|
397
|
+
|
|
398
|
+
```typescript
|
|
399
|
+
// Start typing
|
|
400
|
+
socket.emit('typing', { roomId: 'room-123', isTyping: true });
|
|
401
|
+
|
|
402
|
+
// Stop typing
|
|
403
|
+
socket.emit('typing', { roomId: 'room-123', isTyping: false });
|
|
404
|
+
|
|
405
|
+
// Listen for others typing
|
|
406
|
+
socket.on('typing', (data) => {
|
|
407
|
+
if (data.isTyping) {
|
|
408
|
+
console.log(`${data.username} is typing...`);
|
|
409
|
+
} else {
|
|
410
|
+
console.log(`${data.username} stopped typing`);
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### Listening to Events
|
|
416
|
+
|
|
417
|
+
```typescript
|
|
418
|
+
// Room events
|
|
419
|
+
socket.on('user:joined', (data) => {
|
|
420
|
+
console.log(`${data.username} joined the room`);
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
socket.on('user:left', (data) => {
|
|
424
|
+
console.log(`${data.username} left the room`);
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
// Notifications
|
|
428
|
+
socket.on('notification', (notification) => {
|
|
429
|
+
console.log('New notification:', notification);
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
// System announcements
|
|
433
|
+
socket.on('system:announcement', (data) => {
|
|
434
|
+
alert(data.message);
|
|
435
|
+
});
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
## Server-Side Events
|
|
439
|
+
|
|
440
|
+
The WebSocket service emits events via EventEmitter:
|
|
441
|
+
|
|
442
|
+
```typescript
|
|
443
|
+
wsService.on('connection', (user) => {
|
|
444
|
+
console.log(`User connected: ${user.username}`);
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
wsService.on('disconnect', (user, reason) => {
|
|
448
|
+
console.log(`User disconnected: ${user.username} (${reason})`);
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
wsService.on('message', (message) => {
|
|
452
|
+
console.log(`Message in ${message.roomId}: ${message.content}`);
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
wsService.on('room:join', ({ user, room }) => {
|
|
456
|
+
console.log(`${user.username} joined ${room.name}`);
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
wsService.on('room:leave', ({ user, room }) => {
|
|
460
|
+
console.log(`${user.username} left ${room.name}`);
|
|
461
|
+
});
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
## Architecture
|
|
465
|
+
|
|
466
|
+
### Single Instance
|
|
467
|
+
|
|
468
|
+
```
|
|
469
|
+
Client <--> Socket.io Server <--> WebSocketService
|
|
470
|
+
|
|
|
471
|
+
In-Memory Storage
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### Multi-Instance (with Redis)
|
|
475
|
+
|
|
476
|
+
```
|
|
477
|
+
Client 1 <--> Socket.io Server 1 <--\
|
|
478
|
+
|
|
|
479
|
+
Client 2 <--> Socket.io Server 2 <--+-> Redis Pub/Sub <-> WebSocketService
|
|
480
|
+
|
|
|
481
|
+
Client 3 <--> Socket.io Server 3 <--/
|
|
482
|
+
|
|
|
483
|
+
In-Memory Storage (per instance)
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
## Use Cases
|
|
487
|
+
|
|
488
|
+
### Chat Application
|
|
489
|
+
|
|
490
|
+
```typescript
|
|
491
|
+
// Server
|
|
492
|
+
const chatRoom = await wsService.createRoom('chat-general', 'chat');
|
|
493
|
+
|
|
494
|
+
// Client joins
|
|
495
|
+
socket.emit('room:join', { roomId: chatRoom.id });
|
|
496
|
+
|
|
497
|
+
// Send message
|
|
498
|
+
socket.emit('message', {
|
|
499
|
+
roomId: chatRoom.id,
|
|
500
|
+
content: 'Hello chat!',
|
|
501
|
+
type: 'text',
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
// Everyone in room receives
|
|
505
|
+
socket.on('message', (msg) => {
|
|
506
|
+
displayMessage(msg);
|
|
507
|
+
});
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
### Real-Time Notifications
|
|
511
|
+
|
|
512
|
+
```typescript
|
|
513
|
+
// Server: Send notification to specific user
|
|
514
|
+
await wsService.broadcastToUsers([userId], 'notification', {
|
|
515
|
+
type: 'friend_request',
|
|
516
|
+
from: 'Alice',
|
|
517
|
+
timestamp: new Date(),
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
// Client
|
|
521
|
+
socket.on('notification', (notification) => {
|
|
522
|
+
showNotification(notification);
|
|
523
|
+
});
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
### Live Presence
|
|
527
|
+
|
|
528
|
+
```typescript
|
|
529
|
+
// Track user status
|
|
530
|
+
socket.on('user:status', (data) => {
|
|
531
|
+
updateUserPresence(data.userId, data.status); // online, away, offline
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
// Server broadcast status changes
|
|
535
|
+
await wsService.broadcastToAll('user:status', {
|
|
536
|
+
userId: user.id,
|
|
537
|
+
status: 'online',
|
|
538
|
+
});
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
### Collaborative Editing
|
|
542
|
+
|
|
543
|
+
```typescript
|
|
544
|
+
// Broadcast changes to room
|
|
545
|
+
socket.emit('document:edit', {
|
|
546
|
+
roomId: documentId,
|
|
547
|
+
changes: delta,
|
|
548
|
+
cursor: cursorPosition,
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
// Receive others' changes
|
|
552
|
+
socket.on('document:edit', (data) => {
|
|
553
|
+
applyChanges(data.changes);
|
|
554
|
+
updateCursor(data.userId, data.cursor);
|
|
555
|
+
});
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
## Environment Variables
|
|
559
|
+
|
|
560
|
+
```bash
|
|
561
|
+
# Redis Configuration (for multi-instance)
|
|
562
|
+
REDIS_HOST=localhost
|
|
563
|
+
REDIS_PORT=6379
|
|
564
|
+
REDIS_PASSWORD=your_redis_password
|
|
565
|
+
|
|
566
|
+
# Socket.io Configuration
|
|
567
|
+
WEBSOCKET_PATH=/socket.io
|
|
568
|
+
WEBSOCKET_PING_TIMEOUT=60000
|
|
569
|
+
WEBSOCKET_PING_INTERVAL=25000
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
## Migration from Mock Implementation
|
|
573
|
+
|
|
574
|
+
**Before (v0.1.0):**
|
|
575
|
+
```typescript
|
|
576
|
+
// Placeholder Socket.io - not actually connected
|
|
577
|
+
wsService.initialize(); // No HTTP server needed
|
|
578
|
+
// Broadcasts only logged, not actually sent
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
**After (v0.2.0):**
|
|
582
|
+
```typescript
|
|
583
|
+
// Real Socket.io connection
|
|
584
|
+
wsService.initialize(httpServer); // HTTP server required
|
|
585
|
+
// Full Socket.io functionality with real-time communication
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
## Performance Considerations
|
|
589
|
+
|
|
590
|
+
### Memory Usage
|
|
591
|
+
|
|
592
|
+
- **In-memory storage**: Connection and room data stored in Map structures
|
|
593
|
+
- **Message history**: Limited by retention policy (default: unlimited, configure manually)
|
|
594
|
+
- **Redis adapter**: Adds pub/sub overhead but enables horizontal scaling
|
|
595
|
+
|
|
596
|
+
### Scaling
|
|
597
|
+
|
|
598
|
+
| Aspect | Single Instance | Multi-Instance (Redis) |
|
|
599
|
+
|--------|----------------|------------------------|
|
|
600
|
+
| Max connections | ~10,000 | Unlimited (horizontal) |
|
|
601
|
+
| Message latency | <1ms | ~5-10ms (Redis pub/sub) |
|
|
602
|
+
| Memory per connection | ~1KB | ~1KB + Redis overhead |
|
|
603
|
+
| Cross-instance events | No | Yes |
|
|
604
|
+
|
|
605
|
+
### Best Practices
|
|
606
|
+
|
|
607
|
+
1. **Limit message history**: Clean up old messages periodically
|
|
608
|
+
2. **Use rooms efficiently**: Group users logically, avoid too many small rooms
|
|
609
|
+
3. **Monitor connections**: Use `getStats()` to track active connections
|
|
610
|
+
4. **Set reasonable timeouts**: Adjust `pingTimeout` and `pingInterval` for your use case
|
|
611
|
+
5. **Enable Redis for production**: Always use Redis adapter for multi-instance deployments
|
|
612
|
+
6. **Clean up inactive connections**: Run `cleanup()` periodically (e.g., every hour)
|
|
613
|
+
|
|
614
|
+
## Troubleshooting
|
|
615
|
+
|
|
616
|
+
### Socket.io not working
|
|
617
|
+
|
|
618
|
+
**Problem**: Clients can't connect.
|
|
619
|
+
|
|
620
|
+
**Solution**:
|
|
621
|
+
1. Ensure HTTP server is passed to `initialize(httpServer)`
|
|
622
|
+
2. Check CORS configuration matches client origin
|
|
623
|
+
3. Verify path matches client configuration
|
|
624
|
+
4. Check firewall allows WebSocket connections
|
|
625
|
+
|
|
626
|
+
### Messages not reaching all instances
|
|
627
|
+
|
|
628
|
+
**Problem**: In multi-instance setup, messages only reach users on same server.
|
|
629
|
+
|
|
630
|
+
**Solution**:
|
|
631
|
+
1. Ensure Redis is configured in `WebSocketConfig`
|
|
632
|
+
2. Verify Redis pub/sub clients are connected (check logs)
|
|
633
|
+
3. Check Redis host/port/password are correct
|
|
634
|
+
4. Ensure all instances use same Redis server
|
|
635
|
+
|
|
636
|
+
### High memory usage
|
|
637
|
+
|
|
638
|
+
**Problem**: Memory grows over time.
|
|
639
|
+
|
|
640
|
+
**Solution**:
|
|
641
|
+
- Run `cleanup()` periodically to remove inactive connections
|
|
642
|
+
- Limit message history retention
|
|
643
|
+
- Monitor room count and clean up unused rooms
|
|
644
|
+
|
|
645
|
+
### Connection drops
|
|
646
|
+
|
|
647
|
+
**Problem**: Users frequently disconnected.
|
|
648
|
+
|
|
649
|
+
**Solution**:
|
|
650
|
+
- Increase `pingTimeout` (default 60s)
|
|
651
|
+
- Adjust `pingInterval` (default 25s)
|
|
652
|
+
- Check network stability
|
|
653
|
+
- Verify client reconnection logic
|
|
654
|
+
|
|
655
|
+
## Testing
|
|
656
|
+
|
|
657
|
+
Run WebSocket integration tests:
|
|
658
|
+
|
|
659
|
+
```bash
|
|
660
|
+
# Ensure HTTP server available
|
|
661
|
+
npm test tests/integration/websocket-socketio.test.ts
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
**Note**: Tests create HTTP server on port 3010 automatically.
|
|
665
|
+
|
|
666
|
+
## Related Modules
|
|
667
|
+
|
|
668
|
+
- **Auth Module**: Authenticate WebSocket connections
|
|
669
|
+
- **User Module**: Track user presence and status
|
|
670
|
+
- **Notification Module**: Send real-time notifications
|
|
671
|
+
|
|
672
|
+
## Changelog
|
|
673
|
+
|
|
674
|
+
### v0.2.0 (2025-12-19)
|
|
675
|
+
|
|
676
|
+
**WEBSOCKET-001 Completed:**
|
|
677
|
+
- ✅ Connected real Socket.io with HTTP server
|
|
678
|
+
- ✅ Implemented Redis adapter for multi-instance support
|
|
679
|
+
- ✅ Replaced all placeholder broadcasts with real Socket.io emits
|
|
680
|
+
- ✅ Added connection lifecycle handlers (connect, disconnect)
|
|
681
|
+
- ✅ Added event handlers (room:join, room:leave, message, typing)
|
|
682
|
+
- ✅ Implemented graceful shutdown with Redis cleanup
|
|
683
|
+
- ✅ Added 26 comprehensive integration tests
|
|
684
|
+
- ✅ Added this documentation
|
|
685
|
+
|
|
686
|
+
### v0.1.0 (Initial)
|
|
687
|
+
|
|
688
|
+
- Mock Socket.io implementation
|
|
689
|
+
- In-memory storage only
|
|
690
|
+
- No real WebSocket connections
|
|
691
|
+
- Placeholder broadcasts (logged only)
|