@stacksjs/bun-router 0.0.12 → 0.0.14
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/CHANGELOG.md +770 -0
- package/README.md +584 -261
- package/package.json +43 -48
- package/bin/cli.ts +0 -9
- package/dist/auth.d.ts +0 -134
- package/dist/cache/lru-cache.d.ts +0 -139
- package/dist/cache/middleware-memoization.d.ts +0 -151
- package/dist/cache/route-cache-warmer.d.ts +0 -161
- package/dist/cache/sqlite-cache.d.ts +0 -209
- package/dist/cache/streaming-cache.d.ts +0 -132
- package/dist/chunk-1mvtv6jw.js +0 -13012
- package/dist/cli/colors.d.ts +0 -15
- package/dist/cli/index.d.ts +0 -10
- package/dist/cli/middleware.d.ts +0 -31
- package/dist/cli/openapi.d.ts +0 -17
- package/dist/cli/router.d.ts +0 -15
- package/dist/cli/routes.d.ts +0 -29
- package/dist/cli/utils.d.ts +0 -74
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js +0 -3195
- package/dist/config.d.ts +0 -7
- package/dist/container/container.d.ts +0 -273
- package/dist/container/contextual-binding.d.ts +0 -240
- package/dist/container/decorators.d.ts +0 -141
- package/dist/container/service-provider.d.ts +0 -285
- package/dist/development/hot-reload.d.ts +0 -187
- package/dist/development/index.d.ts +0 -167
- package/dist/development/performance-profiler.d.ts +0 -217
- package/dist/development/route-debugger.d.ts +0 -154
- package/dist/development/route-inspector.d.ts +0 -211
- package/dist/development/typescript-utilities.d.ts +0 -209
- package/dist/docs.d.ts +0 -11
- package/dist/errors/circuit-breaker.d.ts +0 -195
- package/dist/errors/error-handler.d.ts +0 -88
- package/dist/errors/error-reporting.d.ts +0 -144
- package/dist/errors/exceptions.d.ts +0 -184
- package/dist/errors/graceful-degradation.d.ts +0 -154
- package/dist/errors/index.d.ts +0 -9
- package/dist/errors/router-errors.d.ts +0 -110
- package/dist/file-serving/static-files.d.ts +0 -143
- package/dist/index.d.ts +0 -14
- package/dist/index.js +0 -13933
- package/dist/middleware/auth.d.ts +0 -63
- package/dist/middleware/content_security_policy.d.ts +0 -53
- package/dist/middleware/cors.d.ts +0 -5
- package/dist/middleware/csrf.d.ts +0 -7
- package/dist/middleware/ddos_protection.d.ts +0 -39
- package/dist/middleware/file_security.d.ts +0 -26
- package/dist/middleware/file_upload.d.ts +0 -38
- package/dist/middleware/helmet.d.ts +0 -54
- package/dist/middleware/index.d.ts +0 -44
- package/dist/middleware/input_validation.d.ts +0 -45
- package/dist/middleware/json_body.d.ts +0 -4
- package/dist/middleware/performance_alerting.d.ts +0 -87
- package/dist/middleware/performance_dashboard.d.ts +0 -87
- package/dist/middleware/performance_monitor.d.ts +0 -209
- package/dist/middleware/pipeline.d.ts +0 -131
- package/dist/middleware/rate_limit.d.ts +0 -36
- package/dist/middleware/request_id.d.ts +0 -4
- package/dist/middleware/request_signing.d.ts +0 -153
- package/dist/middleware/request_tracer.d.ts +0 -72
- package/dist/middleware/response_cache.d.ts +0 -97
- package/dist/middleware/security.d.ts +0 -69
- package/dist/middleware/security_suite.d.ts +0 -46
- package/dist/middleware/session.d.ts +0 -23
- package/dist/model-binding/index.d.ts +0 -2
- package/dist/model-binding/model-middleware.d.ts +0 -118
- package/dist/model-binding/model-registry.d.ts +0 -164
- package/dist/model-binding.d.ts +0 -185
- package/dist/model-resolver-factory.d.ts +0 -31
- package/dist/observability/correlation.d.ts +0 -187
- package/dist/observability/health-checks.d.ts +0 -186
- package/dist/observability/index.d.ts +0 -60
- package/dist/observability/integration.d.ts +0 -147
- package/dist/observability/metrics.d.ts +0 -184
- package/dist/observability/tracing.d.ts +0 -187
- package/dist/optimization/bun-utilities.d.ts +0 -224
- package/dist/query-builder-integration.d.ts +0 -36
- package/dist/request/context.d.ts +0 -34
- package/dist/request/enhanced-request.d.ts +0 -213
- package/dist/request/macros.d.ts +0 -300
- package/dist/response/macros.d.ts +0 -245
- package/dist/response/response-factory.d.ts +0 -133
- package/dist/router/api-routes.d.ts +0 -5
- package/dist/router/file-based-routing.d.ts +0 -118
- package/dist/router/file-streaming.d.ts +0 -5
- package/dist/router/fluent-router.d.ts +0 -315
- package/dist/router/fluent-routing.d.ts +0 -271
- package/dist/router/group-organization.d.ts +0 -5
- package/dist/router/handler-resolver.d.ts +0 -24
- package/dist/router/http-methods.d.ts +0 -10
- package/dist/router/index.d.ts +0 -114
- package/dist/router/middleware-groups.d.ts +0 -94
- package/dist/router/middleware-integration.d.ts +0 -112
- package/dist/router/middleware.d.ts +0 -5
- package/dist/router/model-binding.d.ts +0 -5
- package/dist/router/optimized-route-matching.d.ts +0 -6
- package/dist/router/route-building.d.ts +0 -5
- package/dist/router/route-compiler.d.ts +0 -153
- package/dist/router/route-matching.d.ts +0 -5
- package/dist/router/route-trie.d.ts +0 -114
- package/dist/router/router.d.ts +0 -294
- package/dist/router/server.d.ts +0 -5
- package/dist/router/validation-integration.d.ts +0 -170
- package/dist/router/view-rendering.d.ts +0 -5
- package/dist/router/websocket.d.ts +0 -5
- package/dist/routing/route-caching.d.ts +0 -129
- package/dist/routing/route-throttling.d.ts +0 -150
- package/dist/routing/subdomain-routing.d.ts +0 -206
- package/dist/session/database-store.d.ts +0 -51
- package/dist/session/file-store.d.ts +0 -22
- package/dist/session/index.d.ts +0 -106
- package/dist/session/memory-store.d.ts +0 -21
- package/dist/session/redis-store.d.ts +0 -31
- package/dist/streaming/index.d.ts +0 -3
- package/dist/streaming/sse-handler.d.ts +0 -138
- package/dist/streaming/stream-handler.d.ts +0 -114
- package/dist/testing/auth-testing.d.ts +0 -156
- package/dist/testing/file-upload-testing.d.ts +0 -190
- package/dist/testing/index.d.ts +0 -10
- package/dist/testing/middleware-testing.d.ts +0 -138
- package/dist/testing/model-binding-testing.d.ts +0 -186
- package/dist/testing/performance-testing.d.ts +0 -235
- package/dist/testing/test-client.d.ts +0 -117
- package/dist/testing/test-request.d.ts +0 -85
- package/dist/testing/test-response.d.ts +0 -90
- package/dist/testing/types.d.ts +0 -207
- package/dist/testing/websocket-testing.d.ts +0 -227
- package/dist/types/controller-types.d.ts +0 -208
- package/dist/types/core.d.ts +0 -543
- package/dist/types/middleware-types.d.ts +0 -227
- package/dist/types/request-response-augmentation.d.ts +0 -261
- package/dist/types/route-inference.d.ts +0 -168
- package/dist/types.d.ts +0 -1782
- package/dist/url.d.ts +0 -56
- package/dist/utils/index.d.ts +0 -1
- package/dist/utils/query-preservation.d.ts +0 -48
- package/dist/utils.d.ts +0 -69
- package/dist/validation/validator.d.ts +0 -140
- package/dist/websocket/clustering.d.ts +0 -185
- package/src/auth.ts +0 -469
- package/src/cache/lru-cache.ts +0 -457
- package/src/cache/middleware-memoization.ts +0 -531
- package/src/cache/route-cache-warmer.ts +0 -486
- package/src/cache/sqlite-cache.ts +0 -783
- package/src/cache/streaming-cache.ts +0 -572
- package/src/cli/colors.ts +0 -29
- package/src/cli/index.ts +0 -287
- package/src/cli/middleware.ts +0 -291
- package/src/cli/openapi.ts +0 -407
- package/src/cli/router.ts +0 -188
- package/src/cli/routes.ts +0 -265
- package/src/cli/utils.ts +0 -531
- package/src/cli.ts +0 -5
- package/src/config.ts +0 -322
- package/src/container/container.ts +0 -740
- package/src/container/contextual-binding.ts +0 -603
- package/src/container/decorators.ts +0 -359
- package/src/container/service-provider.ts +0 -596
- package/src/development/hot-reload.ts +0 -673
- package/src/development/index.ts +0 -499
- package/src/development/performance-profiler.ts +0 -717
- package/src/development/route-debugger.ts +0 -527
- package/src/development/route-inspector.ts +0 -749
- package/src/development/typescript-utilities.ts +0 -682
- package/src/docs.ts +0 -397
- package/src/errors/circuit-breaker.ts +0 -732
- package/src/errors/error-handler.ts +0 -569
- package/src/errors/error-reporting.ts +0 -672
- package/src/errors/exceptions.ts +0 -536
- package/src/errors/graceful-degradation.ts +0 -621
- package/src/errors/index.ts +0 -21
- package/src/errors/router-errors.ts +0 -632
- package/src/file-serving/static-files.ts +0 -581
- package/src/index.ts +0 -14
- package/src/middleware/auth.ts +0 -219
- package/src/middleware/content_security_policy.ts +0 -215
- package/src/middleware/cors.ts +0 -74
- package/src/middleware/csrf.ts +0 -108
- package/src/middleware/ddos_protection.ts +0 -255
- package/src/middleware/file_security.ts +0 -191
- package/src/middleware/file_upload.ts +0 -264
- package/src/middleware/helmet.ts +0 -268
- package/src/middleware/index.ts +0 -117
- package/src/middleware/input_validation.ts +0 -449
- package/src/middleware/json_body.ts +0 -37
- package/src/middleware/performance_alerting.ts +0 -538
- package/src/middleware/performance_dashboard.ts +0 -661
- package/src/middleware/performance_monitor.ts +0 -943
- package/src/middleware/pipeline.ts +0 -489
- package/src/middleware/rate_limit.ts +0 -245
- package/src/middleware/request_id.ts +0 -36
- package/src/middleware/request_signing.ts +0 -636
- package/src/middleware/request_tracer.ts +0 -636
- package/src/middleware/response_cache.ts +0 -743
- package/src/middleware/security.ts +0 -482
- package/src/middleware/security_suite.ts +0 -257
- package/src/middleware/session.ts +0 -91
- package/src/model-binding/index.ts +0 -18
- package/src/model-binding/model-middleware.ts +0 -425
- package/src/model-binding/model-registry.ts +0 -550
- package/src/model-binding.ts +0 -370
- package/src/model-resolver-factory.ts +0 -106
- package/src/observability/correlation.ts +0 -691
- package/src/observability/health-checks.ts +0 -729
- package/src/observability/index.ts +0 -184
- package/src/observability/integration.ts +0 -548
- package/src/observability/metrics.ts +0 -753
- package/src/observability/tracing.ts +0 -638
- package/src/optimization/bun-utilities.ts +0 -778
- package/src/query-builder-integration.ts +0 -137
- package/src/request/context.ts +0 -62
- package/src/request/enhanced-request.ts +0 -857
- package/src/request/macros.ts +0 -688
- package/src/response/macros.ts +0 -612
- package/src/response/response-factory.ts +0 -596
- package/src/router/api-routes.ts +0 -243
- package/src/router/file-based-routing.ts +0 -670
- package/src/router/file-streaming.ts +0 -372
- package/src/router/fluent-router.ts +0 -927
- package/src/router/fluent-routing.ts +0 -797
- package/src/router/group-organization.ts +0 -213
- package/src/router/handler-resolver.ts +0 -291
- package/src/router/http-methods.ts +0 -377
- package/src/router/index.ts +0 -187
- package/src/router/middleware-groups.ts +0 -222
- package/src/router/middleware-integration.ts +0 -399
- package/src/router/middleware.ts +0 -231
- package/src/router/model-binding.ts +0 -215
- package/src/router/optimized-route-matching.ts +0 -253
- package/src/router/route-building.ts +0 -212
- package/src/router/route-compiler.ts +0 -702
- package/src/router/route-matching.ts +0 -349
- package/src/router/route-trie.ts +0 -464
- package/src/router/router.ts +0 -1581
- package/src/router/server.ts +0 -462
- package/src/router/validation-integration.ts +0 -443
- package/src/router/view-rendering.ts +0 -233
- package/src/router/websocket.ts +0 -100
- package/src/routing/route-caching.ts +0 -402
- package/src/routing/route-throttling.ts +0 -469
- package/src/routing/subdomain-routing.ts +0 -492
- package/src/session/database-store.ts +0 -109
- package/src/session/file-store.ts +0 -148
- package/src/session/index.ts +0 -244
- package/src/session/memory-store.ts +0 -88
- package/src/session/redis-store.ts +0 -93
- package/src/streaming/index.ts +0 -17
- package/src/streaming/sse-handler.ts +0 -482
- package/src/streaming/stream-handler.ts +0 -552
- package/src/testing/auth-testing.ts +0 -446
- package/src/testing/file-upload-testing.ts +0 -543
- package/src/testing/index.ts +0 -10
- package/src/testing/middleware-testing.ts +0 -322
- package/src/testing/model-binding-testing.ts +0 -645
- package/src/testing/performance-testing.ts +0 -738
- package/src/testing/test-client.ts +0 -310
- package/src/testing/test-request.ts +0 -315
- package/src/testing/test-response.ts +0 -332
- package/src/testing/types.ts +0 -224
- package/src/testing/websocket-testing.ts +0 -590
- package/src/types/controller-types.ts +0 -385
- package/src/types/core.ts +0 -683
- package/src/types/middleware-types.ts +0 -419
- package/src/types/request-response-augmentation.ts +0 -489
- package/src/types/route-inference.ts +0 -357
- package/src/types.ts +0 -2055
- package/src/url.ts +0 -126
- package/src/utils/index.ts +0 -1
- package/src/utils/query-preservation.ts +0 -201
- package/src/utils.ts +0 -327
- package/src/validation/validator.ts +0 -685
- package/src/websocket/clustering.ts +0 -762
|
@@ -1,762 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* WebSocket Clustering
|
|
3
|
-
*
|
|
4
|
-
* Native WebSocket clustering across workers with high-performance optimizations
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { ServerWebSocket } from 'bun'
|
|
8
|
-
import type { Buffer } from 'node:buffer'
|
|
9
|
-
import process from 'node:process'
|
|
10
|
-
|
|
11
|
-
export interface WebSocketClusterConfig {
|
|
12
|
-
workers?: number
|
|
13
|
-
maxConnections?: number
|
|
14
|
-
heartbeatInterval?: number
|
|
15
|
-
compression?: boolean
|
|
16
|
-
backpressureLimit?: number
|
|
17
|
-
closeOnBackpressureLimit?: boolean
|
|
18
|
-
idleTimeout?: number
|
|
19
|
-
maxPayloadLength?: number
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface ClusterMessage {
|
|
23
|
-
type: 'broadcast' | 'direct' | 'join' | 'leave' | 'heartbeat' | 'stats'
|
|
24
|
-
workerId?: string
|
|
25
|
-
targetId?: string
|
|
26
|
-
roomId?: string
|
|
27
|
-
data?: any
|
|
28
|
-
timestamp: number
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface WebSocketConnection {
|
|
32
|
-
id: string
|
|
33
|
-
workerId: string
|
|
34
|
-
socket: ServerWebSocket<any>
|
|
35
|
-
rooms: Set<string>
|
|
36
|
-
metadata: Record<string, any>
|
|
37
|
-
lastHeartbeat: number
|
|
38
|
-
connected: boolean
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export interface WebSocketRoom {
|
|
42
|
-
id: string
|
|
43
|
-
connections: Set<string>
|
|
44
|
-
metadata: Record<string, any>
|
|
45
|
-
created: number
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export interface ClusterStats {
|
|
49
|
-
totalConnections: number
|
|
50
|
-
totalRooms: number
|
|
51
|
-
workerStats: Record<string, {
|
|
52
|
-
connections: number
|
|
53
|
-
rooms: number
|
|
54
|
-
memoryUsage: number
|
|
55
|
-
uptime: number
|
|
56
|
-
}>
|
|
57
|
-
messagesSent: number
|
|
58
|
-
messagesReceived: number
|
|
59
|
-
bytesTransferred: number
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* WebSocket cluster manager for distributing connections across workers
|
|
64
|
-
*/
|
|
65
|
-
export class WebSocketCluster {
|
|
66
|
-
private config: Required<WebSocketClusterConfig>
|
|
67
|
-
private workers = new Map<string, Worker>()
|
|
68
|
-
private connections = new Map<string, WebSocketConnection>()
|
|
69
|
-
private rooms = new Map<string, WebSocketRoom>()
|
|
70
|
-
private messageHandlers = new Map<string, (...args: any[]) => any>()
|
|
71
|
-
private stats: ClusterStats
|
|
72
|
-
private heartbeatTimer?: ReturnType<typeof setInterval>
|
|
73
|
-
private isMainWorker: boolean
|
|
74
|
-
private workerId: string
|
|
75
|
-
|
|
76
|
-
constructor(config: WebSocketClusterConfig = {}) {
|
|
77
|
-
this.config = {
|
|
78
|
-
workers: config.workers || Math.max(1, Math.floor(navigator.hardwareConcurrency / 2)),
|
|
79
|
-
maxConnections: config.maxConnections || 10000,
|
|
80
|
-
heartbeatInterval: config.heartbeatInterval || 30000,
|
|
81
|
-
compression: config.compression ?? true,
|
|
82
|
-
backpressureLimit: config.backpressureLimit || 64 * 1024,
|
|
83
|
-
closeOnBackpressureLimit: config.closeOnBackpressureLimit ?? false,
|
|
84
|
-
idleTimeout: config.idleTimeout || 120000,
|
|
85
|
-
maxPayloadLength: config.maxPayloadLength || 16 * 1024 * 1024,
|
|
86
|
-
...config,
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
this.workerId = this.generateWorkerId()
|
|
90
|
-
this.isMainWorker = !process.env.BUN_WORKER_ID
|
|
91
|
-
this.stats = this.initializeStats()
|
|
92
|
-
|
|
93
|
-
this.setupWorkerCommunication()
|
|
94
|
-
this.startHeartbeat()
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Create WebSocket server with clustering support
|
|
99
|
-
*/
|
|
100
|
-
createServer(options: {
|
|
101
|
-
port?: number
|
|
102
|
-
hostname?: string
|
|
103
|
-
onConnection?: (ws: ServerWebSocket<any>, request: Request) => void
|
|
104
|
-
onMessage?: (ws: ServerWebSocket<any>, message: string | Buffer) => void
|
|
105
|
-
onClose?: (ws: ServerWebSocket<any>, code?: number, reason?: string) => void
|
|
106
|
-
onError?: (ws: ServerWebSocket<any>, error: Error) => void
|
|
107
|
-
} = {}): any {
|
|
108
|
-
const server = Bun.serve({
|
|
109
|
-
port: options.port || 3000,
|
|
110
|
-
hostname: options.hostname || 'localhost',
|
|
111
|
-
|
|
112
|
-
fetch: (req, server) => {
|
|
113
|
-
const url = new URL(req.url)
|
|
114
|
-
|
|
115
|
-
if (url.pathname === '/ws') {
|
|
116
|
-
const success = server.upgrade(req, {
|
|
117
|
-
data: {
|
|
118
|
-
connectionId: this.generateConnectionId(),
|
|
119
|
-
workerId: this.workerId,
|
|
120
|
-
connectedAt: Date.now(),
|
|
121
|
-
rooms: new Set<string>(),
|
|
122
|
-
metadata: {},
|
|
123
|
-
} as any,
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
if (!success) {
|
|
127
|
-
return new Response('WebSocket upgrade failed', { status: 400 })
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return undefined
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (url.pathname === '/ws/stats') {
|
|
134
|
-
return Response.json(this.getStats())
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return new Response('Not Found', { status: 404 })
|
|
138
|
-
},
|
|
139
|
-
|
|
140
|
-
websocket: {
|
|
141
|
-
|
|
142
|
-
open: (ws) => {
|
|
143
|
-
const connectionId = (ws.data as any).connectionId
|
|
144
|
-
const connection: WebSocketConnection = {
|
|
145
|
-
id: connectionId,
|
|
146
|
-
workerId: this.workerId,
|
|
147
|
-
socket: ws,
|
|
148
|
-
rooms: new Set(),
|
|
149
|
-
metadata: (ws.data as any).metadata || {},
|
|
150
|
-
lastHeartbeat: Date.now(),
|
|
151
|
-
connected: true,
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
this.connections.set(connectionId, connection)
|
|
155
|
-
this.stats.totalConnections++
|
|
156
|
-
this.updateWorkerStats()
|
|
157
|
-
|
|
158
|
-
// Notify other workers about new connection
|
|
159
|
-
this.broadcastToWorkers({
|
|
160
|
-
type: 'join',
|
|
161
|
-
workerId: this.workerId,
|
|
162
|
-
data: { connectionId, metadata: connection.metadata },
|
|
163
|
-
timestamp: Date.now(),
|
|
164
|
-
})
|
|
165
|
-
|
|
166
|
-
options.onConnection?.(ws, (ws.data as any).request)
|
|
167
|
-
},
|
|
168
|
-
|
|
169
|
-
message: (ws, message) => {
|
|
170
|
-
const connectionId = (ws.data as any).connectionId
|
|
171
|
-
const connection = this.connections.get(connectionId)
|
|
172
|
-
|
|
173
|
-
if (!connection)
|
|
174
|
-
return
|
|
175
|
-
|
|
176
|
-
connection.lastHeartbeat = Date.now()
|
|
177
|
-
this.stats.messagesReceived++
|
|
178
|
-
this.stats.bytesTransferred += typeof message === 'string' ? message.length : message.byteLength
|
|
179
|
-
|
|
180
|
-
// Handle internal cluster messages
|
|
181
|
-
if (typeof message === 'string' && message.startsWith('__cluster__')) {
|
|
182
|
-
this.handleClusterMessage(connection, JSON.parse(message.slice(11)))
|
|
183
|
-
return
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
options.onMessage?.(ws, message)
|
|
187
|
-
},
|
|
188
|
-
|
|
189
|
-
close: (ws, code, reason) => {
|
|
190
|
-
const connectionId = (ws.data as any).connectionId
|
|
191
|
-
const connection = this.connections.get(connectionId)
|
|
192
|
-
|
|
193
|
-
if (connection) {
|
|
194
|
-
connection.connected = false
|
|
195
|
-
|
|
196
|
-
// Remove from all rooms
|
|
197
|
-
connection.rooms.forEach((roomId) => {
|
|
198
|
-
this.leaveRoom(connectionId, roomId)
|
|
199
|
-
})
|
|
200
|
-
|
|
201
|
-
this.connections.delete(connectionId)
|
|
202
|
-
this.stats.totalConnections--
|
|
203
|
-
this.updateWorkerStats()
|
|
204
|
-
|
|
205
|
-
// Notify other workers about disconnection
|
|
206
|
-
this.broadcastToWorkers({
|
|
207
|
-
type: 'leave',
|
|
208
|
-
workerId: this.workerId,
|
|
209
|
-
data: { connectionId },
|
|
210
|
-
timestamp: Date.now(),
|
|
211
|
-
})
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
options.onClose?.(ws, code, reason)
|
|
215
|
-
},
|
|
216
|
-
|
|
217
|
-
},
|
|
218
|
-
})
|
|
219
|
-
|
|
220
|
-
console.log(`WebSocket cluster worker ${this.workerId} listening on ${options.hostname || 'localhost'}:${options.port || 3000}`)
|
|
221
|
-
return server
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Join a connection to a room
|
|
226
|
-
*/
|
|
227
|
-
joinRoom(connectionId: string, roomId: string, metadata: Record<string, any> = {}): boolean {
|
|
228
|
-
const connection = this.connections.get(connectionId)
|
|
229
|
-
if (!connection || !connection.connected)
|
|
230
|
-
return false
|
|
231
|
-
|
|
232
|
-
connection.rooms.add(roomId)
|
|
233
|
-
|
|
234
|
-
if (!this.rooms.has(roomId)) {
|
|
235
|
-
this.rooms.set(roomId, {
|
|
236
|
-
id: roomId,
|
|
237
|
-
connections: new Set(),
|
|
238
|
-
metadata,
|
|
239
|
-
created: Date.now(),
|
|
240
|
-
})
|
|
241
|
-
this.stats.totalRooms++
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
const room = this.rooms.get(roomId)!
|
|
245
|
-
room.connections.add(connectionId)
|
|
246
|
-
|
|
247
|
-
// Notify other workers
|
|
248
|
-
this.broadcastToWorkers({
|
|
249
|
-
type: 'join',
|
|
250
|
-
workerId: this.workerId,
|
|
251
|
-
roomId,
|
|
252
|
-
data: { connectionId, metadata },
|
|
253
|
-
timestamp: Date.now(),
|
|
254
|
-
})
|
|
255
|
-
|
|
256
|
-
return true
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* Remove a connection from a room
|
|
261
|
-
*/
|
|
262
|
-
leaveRoom(connectionId: string, roomId: string): boolean {
|
|
263
|
-
const connection = this.connections.get(connectionId)
|
|
264
|
-
if (!connection)
|
|
265
|
-
return false
|
|
266
|
-
|
|
267
|
-
connection.rooms.delete(roomId)
|
|
268
|
-
|
|
269
|
-
const room = this.rooms.get(roomId)
|
|
270
|
-
if (room) {
|
|
271
|
-
room.connections.delete(connectionId)
|
|
272
|
-
|
|
273
|
-
if (room.connections.size === 0) {
|
|
274
|
-
this.rooms.delete(roomId)
|
|
275
|
-
this.stats.totalRooms--
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// Notify other workers
|
|
280
|
-
this.broadcastToWorkers({
|
|
281
|
-
type: 'leave',
|
|
282
|
-
workerId: this.workerId,
|
|
283
|
-
roomId,
|
|
284
|
-
data: { connectionId },
|
|
285
|
-
timestamp: Date.now(),
|
|
286
|
-
})
|
|
287
|
-
|
|
288
|
-
return true
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
/**
|
|
292
|
-
* Broadcast message to all connections in a room
|
|
293
|
-
*/
|
|
294
|
-
broadcastToRoom(roomId: string, message: string | Buffer, excludeConnectionId?: string): number {
|
|
295
|
-
const room = this.rooms.get(roomId)
|
|
296
|
-
if (!room)
|
|
297
|
-
return 0
|
|
298
|
-
|
|
299
|
-
let sentCount = 0
|
|
300
|
-
|
|
301
|
-
// Send to local connections
|
|
302
|
-
room.connections.forEach((connectionId) => {
|
|
303
|
-
if (connectionId === excludeConnectionId)
|
|
304
|
-
return
|
|
305
|
-
|
|
306
|
-
const connection = this.connections.get(connectionId)
|
|
307
|
-
if (connection && connection.connected) {
|
|
308
|
-
try {
|
|
309
|
-
connection.socket.send(message)
|
|
310
|
-
sentCount++
|
|
311
|
-
this.stats.messagesSent++
|
|
312
|
-
this.stats.bytesTransferred += typeof message === 'string' ? message.length : message.byteLength
|
|
313
|
-
}
|
|
314
|
-
catch (error) {
|
|
315
|
-
console.error('Failed to send message to connection:', error)
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
})
|
|
319
|
-
|
|
320
|
-
// Broadcast to other workers
|
|
321
|
-
this.broadcastToWorkers({
|
|
322
|
-
type: 'broadcast',
|
|
323
|
-
workerId: this.workerId,
|
|
324
|
-
roomId,
|
|
325
|
-
data: { message: message.toString(), excludeConnectionId },
|
|
326
|
-
timestamp: Date.now(),
|
|
327
|
-
})
|
|
328
|
-
|
|
329
|
-
return sentCount
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
/**
|
|
333
|
-
* Send message to specific connection
|
|
334
|
-
*/
|
|
335
|
-
sendToConnection(connectionId: string, message: string | Buffer): boolean {
|
|
336
|
-
const connection = this.connections.get(connectionId)
|
|
337
|
-
|
|
338
|
-
if (connection && connection.connected) {
|
|
339
|
-
try {
|
|
340
|
-
connection.socket.send(message)
|
|
341
|
-
this.stats.messagesSent++
|
|
342
|
-
this.stats.bytesTransferred += typeof message === 'string' ? message.length : message.byteLength
|
|
343
|
-
return true
|
|
344
|
-
}
|
|
345
|
-
catch (error) {
|
|
346
|
-
console.error('Failed to send message to connection:', error)
|
|
347
|
-
return false
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
// Try to send via other workers
|
|
352
|
-
this.broadcastToWorkers({
|
|
353
|
-
type: 'direct',
|
|
354
|
-
workerId: this.workerId,
|
|
355
|
-
targetId: connectionId,
|
|
356
|
-
data: { message: message.toString() },
|
|
357
|
-
timestamp: Date.now(),
|
|
358
|
-
})
|
|
359
|
-
|
|
360
|
-
return false
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
/**
|
|
364
|
-
* Get cluster statistics
|
|
365
|
-
*/
|
|
366
|
-
getStats(): ClusterStats {
|
|
367
|
-
this.updateWorkerStats()
|
|
368
|
-
return { ...this.stats }
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
/**
|
|
372
|
-
* Get connections in a room
|
|
373
|
-
*/
|
|
374
|
-
getRoomConnections(roomId: string): string[] {
|
|
375
|
-
const room = this.rooms.get(roomId)
|
|
376
|
-
return room ? Array.from(room.connections) : []
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
/**
|
|
380
|
-
* Get rooms for a connection
|
|
381
|
-
*/
|
|
382
|
-
getConnectionRooms(connectionId: string): string[] {
|
|
383
|
-
const connection = this.connections.get(connectionId)
|
|
384
|
-
return connection ? Array.from(connection.rooms) : []
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
/**
|
|
388
|
-
* Close connection
|
|
389
|
-
*/
|
|
390
|
-
closeConnection(connectionId: string, code?: number, reason?: string): boolean {
|
|
391
|
-
const connection = this.connections.get(connectionId)
|
|
392
|
-
if (!connection || !connection.connected)
|
|
393
|
-
return false
|
|
394
|
-
|
|
395
|
-
try {
|
|
396
|
-
connection.socket.close(code, reason)
|
|
397
|
-
return true
|
|
398
|
-
}
|
|
399
|
-
catch (error) {
|
|
400
|
-
console.error('Failed to close connection:', error)
|
|
401
|
-
return false
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
/**
|
|
406
|
-
* Shutdown cluster
|
|
407
|
-
*/
|
|
408
|
-
shutdown(): void {
|
|
409
|
-
if (this.heartbeatTimer) {
|
|
410
|
-
clearInterval(this.heartbeatTimer)
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
// Close all connections
|
|
414
|
-
this.connections.forEach((connection) => {
|
|
415
|
-
if (connection.connected) {
|
|
416
|
-
connection.socket.close(1001, 'Server shutdown')
|
|
417
|
-
}
|
|
418
|
-
})
|
|
419
|
-
|
|
420
|
-
// Terminate workers
|
|
421
|
-
this.workers.forEach((worker) => {
|
|
422
|
-
worker.terminate()
|
|
423
|
-
})
|
|
424
|
-
|
|
425
|
-
this.connections.clear()
|
|
426
|
-
this.rooms.clear()
|
|
427
|
-
this.workers.clear()
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
/**
|
|
431
|
-
* Generate unique connection ID
|
|
432
|
-
*/
|
|
433
|
-
private generateConnectionId(): string {
|
|
434
|
-
return `conn_${this.workerId}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
/**
|
|
438
|
-
* Generate unique worker ID
|
|
439
|
-
*/
|
|
440
|
-
private generateWorkerId(): string {
|
|
441
|
-
return process.env.BUN_WORKER_ID || `worker_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
/**
|
|
445
|
-
* Initialize statistics
|
|
446
|
-
*/
|
|
447
|
-
private initializeStats(): ClusterStats {
|
|
448
|
-
return {
|
|
449
|
-
totalConnections: 0,
|
|
450
|
-
totalRooms: 0,
|
|
451
|
-
workerStats: {},
|
|
452
|
-
messagesSent: 0,
|
|
453
|
-
messagesReceived: 0,
|
|
454
|
-
bytesTransferred: 0,
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
/**
|
|
459
|
-
* Setup worker communication
|
|
460
|
-
*/
|
|
461
|
-
private setupWorkerCommunication(): void {
|
|
462
|
-
if (this.isMainWorker) {
|
|
463
|
-
// Main worker spawns additional workers
|
|
464
|
-
for (let i = 1; i < this.config.workers; i++) {
|
|
465
|
-
const worker = new Worker(new URL(import.meta.url), {
|
|
466
|
-
env: { ...process.env, BUN_WORKER_ID: `worker_${i}` },
|
|
467
|
-
})
|
|
468
|
-
|
|
469
|
-
worker.onmessage = (event) => {
|
|
470
|
-
this.handleWorkerMessage(event.data)
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
this.workers.set(`worker_${i}`, worker)
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
else {
|
|
477
|
-
// Worker listens for messages from main worker
|
|
478
|
-
process.on('message', (message) => {
|
|
479
|
-
this.handleWorkerMessage(message as ClusterMessage)
|
|
480
|
-
})
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
/**
|
|
485
|
-
* Handle messages from other workers
|
|
486
|
-
*/
|
|
487
|
-
private handleWorkerMessage(message: ClusterMessage): void {
|
|
488
|
-
switch (message.type) {
|
|
489
|
-
case 'broadcast':
|
|
490
|
-
if (message.roomId && message.data?.message) {
|
|
491
|
-
const room = this.rooms.get(message.roomId)
|
|
492
|
-
if (room) {
|
|
493
|
-
room.connections.forEach((connectionId) => {
|
|
494
|
-
if (connectionId === message.data.excludeConnectionId)
|
|
495
|
-
return
|
|
496
|
-
|
|
497
|
-
const connection = this.connections.get(connectionId)
|
|
498
|
-
if (connection && connection.connected) {
|
|
499
|
-
connection.socket.send(message.data.message)
|
|
500
|
-
}
|
|
501
|
-
})
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
break
|
|
505
|
-
|
|
506
|
-
case 'direct':
|
|
507
|
-
if (message.targetId && message.data?.message) {
|
|
508
|
-
const connection = this.connections.get(message.targetId)
|
|
509
|
-
if (connection && connection.connected) {
|
|
510
|
-
connection.socket.send(message.data.message)
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
break
|
|
514
|
-
|
|
515
|
-
case 'join':
|
|
516
|
-
if (message.roomId && message.data?.connectionId) {
|
|
517
|
-
// Update room information from other workers
|
|
518
|
-
if (!this.rooms.has(message.roomId)) {
|
|
519
|
-
this.rooms.set(message.roomId, {
|
|
520
|
-
id: message.roomId,
|
|
521
|
-
connections: new Set(),
|
|
522
|
-
metadata: message.data.metadata || {},
|
|
523
|
-
created: Date.now(),
|
|
524
|
-
})
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
break
|
|
528
|
-
|
|
529
|
-
case 'leave':
|
|
530
|
-
if (message.roomId && message.data?.connectionId) {
|
|
531
|
-
const room = this.rooms.get(message.roomId)
|
|
532
|
-
if (room) {
|
|
533
|
-
room.connections.delete(message.data.connectionId)
|
|
534
|
-
if (room.connections.size === 0) {
|
|
535
|
-
this.rooms.delete(message.roomId)
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
break
|
|
540
|
-
|
|
541
|
-
case 'stats':
|
|
542
|
-
// Handle stats updates from other workers
|
|
543
|
-
if (message.data && message.workerId) {
|
|
544
|
-
this.stats.workerStats[message.workerId] = message.data
|
|
545
|
-
}
|
|
546
|
-
break
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
/**
|
|
551
|
-
* Handle cluster messages from connections
|
|
552
|
-
*/
|
|
553
|
-
private handleClusterMessage(connection: WebSocketConnection, message: ClusterMessage): void {
|
|
554
|
-
switch (message.type) {
|
|
555
|
-
case 'join':
|
|
556
|
-
if (message.roomId) {
|
|
557
|
-
this.joinRoom(connection.id, message.roomId, message.data || {})
|
|
558
|
-
}
|
|
559
|
-
break
|
|
560
|
-
|
|
561
|
-
case 'leave':
|
|
562
|
-
if (message.roomId) {
|
|
563
|
-
this.leaveRoom(connection.id, message.roomId)
|
|
564
|
-
}
|
|
565
|
-
break
|
|
566
|
-
|
|
567
|
-
case 'broadcast':
|
|
568
|
-
if (message.roomId && message.data?.message) {
|
|
569
|
-
this.broadcastToRoom(message.roomId, message.data.message, connection.id)
|
|
570
|
-
}
|
|
571
|
-
break
|
|
572
|
-
|
|
573
|
-
case 'heartbeat':
|
|
574
|
-
connection.lastHeartbeat = Date.now()
|
|
575
|
-
connection.socket.send(`__cluster__${JSON.stringify({
|
|
576
|
-
type: 'heartbeat',
|
|
577
|
-
timestamp: Date.now(),
|
|
578
|
-
})}`)
|
|
579
|
-
break
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
/**
|
|
584
|
-
* Broadcast message to all workers
|
|
585
|
-
*/
|
|
586
|
-
private broadcastToWorkers(message: ClusterMessage): void {
|
|
587
|
-
if (this.isMainWorker) {
|
|
588
|
-
this.workers.forEach((worker) => {
|
|
589
|
-
worker.postMessage(message)
|
|
590
|
-
})
|
|
591
|
-
}
|
|
592
|
-
else {
|
|
593
|
-
process.send?.(message)
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
/**
|
|
598
|
-
* Start heartbeat timer
|
|
599
|
-
*/
|
|
600
|
-
private startHeartbeat(): void {
|
|
601
|
-
this.heartbeatTimer = setInterval(() => {
|
|
602
|
-
const now = Date.now()
|
|
603
|
-
const timeout = this.config.heartbeatInterval * 2
|
|
604
|
-
|
|
605
|
-
// Check for stale connections
|
|
606
|
-
this.connections.forEach((connection, connectionId) => {
|
|
607
|
-
if (now - connection.lastHeartbeat > timeout) {
|
|
608
|
-
console.log(`Closing stale connection: ${connectionId}`)
|
|
609
|
-
this.closeConnection(connectionId, 1001, 'Connection timeout')
|
|
610
|
-
}
|
|
611
|
-
})
|
|
612
|
-
|
|
613
|
-
// Send stats to other workers
|
|
614
|
-
this.broadcastToWorkers({
|
|
615
|
-
type: 'stats',
|
|
616
|
-
workerId: this.workerId,
|
|
617
|
-
data: {
|
|
618
|
-
connections: this.connections.size,
|
|
619
|
-
rooms: this.rooms.size,
|
|
620
|
-
memoryUsage: process.memoryUsage().heapUsed,
|
|
621
|
-
uptime: process.uptime(),
|
|
622
|
-
},
|
|
623
|
-
timestamp: now,
|
|
624
|
-
})
|
|
625
|
-
}, this.config.heartbeatInterval)
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
/**
|
|
629
|
-
* Update worker statistics
|
|
630
|
-
*/
|
|
631
|
-
private updateWorkerStats(): void {
|
|
632
|
-
this.stats.workerStats[this.workerId] = {
|
|
633
|
-
connections: this.connections.size,
|
|
634
|
-
rooms: this.rooms.size,
|
|
635
|
-
memoryUsage: process.memoryUsage().heapUsed,
|
|
636
|
-
uptime: process.uptime(),
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
/**
|
|
642
|
-
* WebSocket cluster factory
|
|
643
|
-
*/
|
|
644
|
-
export class WebSocketClusterFactory {
|
|
645
|
-
/**
|
|
646
|
-
* Create WebSocket cluster with default configuration
|
|
647
|
-
*/
|
|
648
|
-
static create(config?: WebSocketClusterConfig): WebSocketCluster {
|
|
649
|
-
return new WebSocketCluster(config)
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
/**
|
|
653
|
-
* Create high-performance cluster for production
|
|
654
|
-
*/
|
|
655
|
-
static createHighPerformance(): WebSocketCluster {
|
|
656
|
-
return new WebSocketCluster({
|
|
657
|
-
workers: Math.max(2, navigator.hardwareConcurrency),
|
|
658
|
-
maxConnections: 50000,
|
|
659
|
-
heartbeatInterval: 60000,
|
|
660
|
-
compression: true,
|
|
661
|
-
backpressureLimit: 128 * 1024,
|
|
662
|
-
closeOnBackpressureLimit: true,
|
|
663
|
-
idleTimeout: 300000,
|
|
664
|
-
maxPayloadLength: 32 * 1024 * 1024,
|
|
665
|
-
})
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
/**
|
|
669
|
-
* Create development cluster with debugging
|
|
670
|
-
*/
|
|
671
|
-
static createDevelopment(): WebSocketCluster {
|
|
672
|
-
return new WebSocketCluster({
|
|
673
|
-
workers: 1,
|
|
674
|
-
maxConnections: 1000,
|
|
675
|
-
heartbeatInterval: 10000,
|
|
676
|
-
compression: false,
|
|
677
|
-
backpressureLimit: 16 * 1024,
|
|
678
|
-
closeOnBackpressureLimit: false,
|
|
679
|
-
idleTimeout: 60000,
|
|
680
|
-
maxPayloadLength: 1024 * 1024,
|
|
681
|
-
})
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
/**
|
|
686
|
-
* WebSocket cluster helpers
|
|
687
|
-
*/
|
|
688
|
-
export const WebSocketClusterHelpers = {
|
|
689
|
-
/**
|
|
690
|
-
* Create room-based chat system
|
|
691
|
-
*/
|
|
692
|
-
createChatSystem: (cluster: WebSocketCluster): any => ({
|
|
693
|
-
joinRoom: (connectionId: string, roomId: string, userInfo: any) => {
|
|
694
|
-
return cluster.joinRoom(connectionId, roomId, { userInfo, joinedAt: Date.now() })
|
|
695
|
-
},
|
|
696
|
-
|
|
697
|
-
leaveRoom: (connectionId: string, roomId: string) => {
|
|
698
|
-
return cluster.leaveRoom(connectionId, roomId)
|
|
699
|
-
},
|
|
700
|
-
|
|
701
|
-
sendMessage: (roomId: string, message: any, fromConnectionId?: string) => {
|
|
702
|
-
const chatMessage = JSON.stringify({
|
|
703
|
-
type: 'chat_message',
|
|
704
|
-
roomId,
|
|
705
|
-
message,
|
|
706
|
-
timestamp: Date.now(),
|
|
707
|
-
from: fromConnectionId,
|
|
708
|
-
})
|
|
709
|
-
return cluster.broadcastToRoom(roomId, chatMessage, fromConnectionId)
|
|
710
|
-
},
|
|
711
|
-
|
|
712
|
-
getRoomUsers: (roomId: string) => {
|
|
713
|
-
return cluster.getRoomConnections(roomId)
|
|
714
|
-
},
|
|
715
|
-
}),
|
|
716
|
-
|
|
717
|
-
/**
|
|
718
|
-
* Create real-time notifications system
|
|
719
|
-
*/
|
|
720
|
-
createNotificationSystem: (cluster: WebSocketCluster): any => ({
|
|
721
|
-
subscribe: (connectionId: string, topics: string[]) => {
|
|
722
|
-
topics.forEach((topic) => {
|
|
723
|
-
cluster.joinRoom(connectionId, `notification:${topic}`)
|
|
724
|
-
})
|
|
725
|
-
},
|
|
726
|
-
|
|
727
|
-
unsubscribe: (connectionId: string, topics: string[]) => {
|
|
728
|
-
topics.forEach((topic) => {
|
|
729
|
-
cluster.leaveRoom(connectionId, `notification:${topic}`)
|
|
730
|
-
})
|
|
731
|
-
},
|
|
732
|
-
|
|
733
|
-
notify: (topic: string, notification: any) => {
|
|
734
|
-
const message = JSON.stringify({
|
|
735
|
-
type: 'notification',
|
|
736
|
-
topic,
|
|
737
|
-
data: notification,
|
|
738
|
-
timestamp: Date.now(),
|
|
739
|
-
})
|
|
740
|
-
return cluster.broadcastToRoom(`notification:${topic}`, message)
|
|
741
|
-
},
|
|
742
|
-
}),
|
|
743
|
-
|
|
744
|
-
/**
|
|
745
|
-
* Create presence system
|
|
746
|
-
*/
|
|
747
|
-
createPresenceSystem: (cluster: WebSocketCluster): any => ({
|
|
748
|
-
setPresence: (connectionId: string, status: 'online' | 'away' | 'busy' | 'offline', metadata?: any) => {
|
|
749
|
-
cluster.broadcastToRoom('presence', JSON.stringify({
|
|
750
|
-
type: 'presence_update',
|
|
751
|
-
connectionId,
|
|
752
|
-
status,
|
|
753
|
-
metadata,
|
|
754
|
-
timestamp: Date.now(),
|
|
755
|
-
}), connectionId)
|
|
756
|
-
},
|
|
757
|
-
|
|
758
|
-
getOnlineUsers: () => {
|
|
759
|
-
return cluster.getRoomConnections('presence')
|
|
760
|
-
},
|
|
761
|
-
}),
|
|
762
|
-
}
|