@roj-ai/transport 0.0.2
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/dist/client/client-connection.d.ts +48 -0
- package/dist/client/client-connection.d.ts.map +1 -0
- package/dist/client/client-connection.js +160 -0
- package/dist/client/index.d.ts +10 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +7 -0
- package/dist/core/connection.d.ts +59 -0
- package/dist/core/connection.d.ts.map +1 -0
- package/dist/core/connection.js +165 -0
- package/dist/core/index.d.ts +16 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +13 -0
- package/dist/core/protocol.d.ts +206 -0
- package/dist/core/protocol.d.ts.map +1 -0
- package/dist/core/protocol.js +93 -0
- package/dist/core/result.d.ts +26 -0
- package/dist/core/result.d.ts.map +1 -0
- package/dist/core/result.js +20 -0
- package/dist/core/router.d.ts +79 -0
- package/dist/core/router.d.ts.map +1 -0
- package/dist/core/router.js +119 -0
- package/dist/core/types.d.ts +44 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +14 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/dist/platform/browser.d.ts +14 -0
- package/dist/platform/browser.d.ts.map +1 -0
- package/dist/platform/browser.js +65 -0
- package/dist/platform/bun.d.ts +69 -0
- package/dist/platform/bun.d.ts.map +1 -0
- package/dist/platform/bun.js +152 -0
- package/dist/platform/types.d.ts +83 -0
- package/dist/platform/types.d.ts.map +1 -0
- package/dist/platform/types.js +14 -0
- package/dist/server/connection-manager.d.ts +106 -0
- package/dist/server/connection-manager.d.ts.map +1 -0
- package/dist/server/connection-manager.js +234 -0
- package/dist/server/index.d.ts +12 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +8 -0
- package/dist/server/server-connection.d.ts +50 -0
- package/dist/server/server-connection.d.ts.map +1 -0
- package/dist/server/server-connection.js +84 -0
- package/package.json +52 -0
- package/src/__tests__/integration.test.ts +443 -0
- package/src/client/client-connection.ts +207 -0
- package/src/client/index.ts +24 -0
- package/src/core/connection.ts +203 -0
- package/src/core/index.ts +52 -0
- package/src/core/protocol.test.ts +235 -0
- package/src/core/protocol.ts +348 -0
- package/src/core/result.ts +35 -0
- package/src/core/router.test.ts +248 -0
- package/src/core/router.ts +186 -0
- package/src/core/types.ts +85 -0
- package/src/index.ts +80 -0
- package/src/platform/browser.ts +79 -0
- package/src/platform/bun.ts +220 -0
- package/src/platform/types.ts +102 -0
- package/src/server/connection-manager.ts +298 -0
- package/src/server/index.ts +26 -0
- package/src/server/server-connection.ts +122 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform Abstraction Types
|
|
3
|
+
*
|
|
4
|
+
* Common interfaces for WebSocket implementations across platforms (browser + Bun).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* WebSocket ready state values.
|
|
9
|
+
*/
|
|
10
|
+
export const WebSocketReadyState = {
|
|
11
|
+
CONNECTING: 0,
|
|
12
|
+
OPEN: 1,
|
|
13
|
+
CLOSING: 2,
|
|
14
|
+
CLOSED: 3,
|
|
15
|
+
} as const
|
|
16
|
+
|
|
17
|
+
export type WebSocketReadyState = (typeof WebSocketReadyState)[keyof typeof WebSocketReadyState]
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Close event structure.
|
|
21
|
+
*/
|
|
22
|
+
export interface ICloseEvent {
|
|
23
|
+
code: number
|
|
24
|
+
reason: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Platform-agnostic WebSocket interface.
|
|
29
|
+
*/
|
|
30
|
+
export interface IWebSocket {
|
|
31
|
+
/** Current ready state */
|
|
32
|
+
readonly readyState: WebSocketReadyState
|
|
33
|
+
|
|
34
|
+
/** Send a text message */
|
|
35
|
+
send(data: string): void
|
|
36
|
+
|
|
37
|
+
/** Close the connection */
|
|
38
|
+
close(code?: number, reason?: string): void
|
|
39
|
+
|
|
40
|
+
/** Called when connection opens */
|
|
41
|
+
onopen: (() => void) | null
|
|
42
|
+
|
|
43
|
+
/** Called when connection closes */
|
|
44
|
+
onclose: ((event: ICloseEvent) => void) | null
|
|
45
|
+
|
|
46
|
+
/** Called on error */
|
|
47
|
+
onerror: ((error: Error) => void) | null
|
|
48
|
+
|
|
49
|
+
/** Called when a message is received */
|
|
50
|
+
onmessage: ((data: string) => void) | null
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Factory for creating WebSocket client connections.
|
|
55
|
+
*/
|
|
56
|
+
export interface IWebSocketFactory {
|
|
57
|
+
/** Create a new WebSocket connection to the given URL */
|
|
58
|
+
create(url: string): IWebSocket
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Server-side WebSocket interface (for handling incoming connections).
|
|
63
|
+
* Extended from IWebSocket with server-specific capabilities.
|
|
64
|
+
*/
|
|
65
|
+
export interface IServerWebSocket<T = unknown> extends IWebSocket {
|
|
66
|
+
/** Attached data for this connection */
|
|
67
|
+
data: T
|
|
68
|
+
|
|
69
|
+
/** Subscribe to a topic for pub/sub messaging */
|
|
70
|
+
subscribe(topic: string): void
|
|
71
|
+
|
|
72
|
+
/** Unsubscribe from a topic */
|
|
73
|
+
unsubscribe(topic: string): void
|
|
74
|
+
|
|
75
|
+
/** Publish to a topic (message sent to all subscribers except sender) */
|
|
76
|
+
publish(topic: string, message: string): void
|
|
77
|
+
|
|
78
|
+
/** Check if subscribed to a topic */
|
|
79
|
+
isSubscribed(topic: string): boolean
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* WebSocket server upgrade request data.
|
|
84
|
+
*/
|
|
85
|
+
export interface IUpgradeData<T = unknown> {
|
|
86
|
+
/** Data to attach to the WebSocket connection */
|
|
87
|
+
data: T
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* WebSocket server interface for handling multiple connections.
|
|
92
|
+
*/
|
|
93
|
+
export interface IWebSocketServer<T = unknown> {
|
|
94
|
+
/** Handle WebSocket upgrade from HTTP request */
|
|
95
|
+
upgrade(request: Request, data: T): boolean
|
|
96
|
+
|
|
97
|
+
/** Publish to all connections subscribed to a topic */
|
|
98
|
+
publish(topic: string, message: string): void
|
|
99
|
+
|
|
100
|
+
/** Get all active connections */
|
|
101
|
+
getConnections(): Iterable<IServerWebSocket<T>>
|
|
102
|
+
}
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Connection Manager
|
|
3
|
+
*
|
|
4
|
+
* Manages multiple WebSocket connections and session subscriptions.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ProtocolDef } from '../core/protocol.js'
|
|
8
|
+
import type { ServerConnection } from './server-connection.js'
|
|
9
|
+
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// Types
|
|
12
|
+
// ============================================================================
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Connection statistics.
|
|
16
|
+
*/
|
|
17
|
+
export interface ConnectionStats {
|
|
18
|
+
/** Total number of connections */
|
|
19
|
+
totalConnections: number
|
|
20
|
+
/** Number of active (connected) connections */
|
|
21
|
+
activeConnections: number
|
|
22
|
+
/** Number of unique sessions with subscribers */
|
|
23
|
+
activeSessions: number
|
|
24
|
+
/** Connections by client type */
|
|
25
|
+
byClientType: Record<string, number>
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ============================================================================
|
|
29
|
+
// Connection Manager
|
|
30
|
+
// ============================================================================
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Manages multiple server connections and session subscriptions.
|
|
34
|
+
*
|
|
35
|
+
* Features:
|
|
36
|
+
* - Track all active connections
|
|
37
|
+
* - Manage session subscriptions
|
|
38
|
+
* - Broadcast to subscribed connections
|
|
39
|
+
* - Connection statistics
|
|
40
|
+
*/
|
|
41
|
+
export class ConnectionManager<
|
|
42
|
+
TReceive extends ProtocolDef = ProtocolDef,
|
|
43
|
+
TSend extends ProtocolDef = ProtocolDef,
|
|
44
|
+
> {
|
|
45
|
+
private connections = new Map<string, ServerConnection<TReceive, TSend>>()
|
|
46
|
+
private sessionSubscriptions = new Map<string, Set<string>>() // sessionId -> connectionIds
|
|
47
|
+
|
|
48
|
+
// ============================================================================
|
|
49
|
+
// Connection Management
|
|
50
|
+
// ============================================================================
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Add a connection.
|
|
54
|
+
*/
|
|
55
|
+
add(connection: ServerConnection<TReceive, TSend>): void {
|
|
56
|
+
this.connections.set(connection.getConnectionId(), connection)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Remove a connection.
|
|
61
|
+
*/
|
|
62
|
+
remove(connectionId: string): void {
|
|
63
|
+
const connection = this.connections.get(connectionId)
|
|
64
|
+
if (!connection) return
|
|
65
|
+
|
|
66
|
+
// Clean up subscriptions
|
|
67
|
+
for (const sessionId of connection.getSubscribedSessions()) {
|
|
68
|
+
this.unsubscribeInternal(connectionId, sessionId)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
this.connections.delete(connectionId)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Get a connection by ID.
|
|
76
|
+
*/
|
|
77
|
+
get(connectionId: string): ServerConnection<TReceive, TSend> | undefined {
|
|
78
|
+
return this.connections.get(connectionId)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Check if a connection exists.
|
|
83
|
+
*/
|
|
84
|
+
has(connectionId: string): boolean {
|
|
85
|
+
return this.connections.has(connectionId)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Get all connections.
|
|
90
|
+
*/
|
|
91
|
+
getAll(): Iterable<ServerConnection<TReceive, TSend>> {
|
|
92
|
+
return this.connections.values()
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get all connection IDs.
|
|
97
|
+
*/
|
|
98
|
+
getAllIds(): Iterable<string> {
|
|
99
|
+
return this.connections.keys()
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Get connection count.
|
|
104
|
+
*/
|
|
105
|
+
get size(): number {
|
|
106
|
+
return this.connections.size
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ============================================================================
|
|
110
|
+
// Session Subscriptions
|
|
111
|
+
// ============================================================================
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Subscribe a connection to a session.
|
|
115
|
+
*/
|
|
116
|
+
subscribe(connectionId: string, sessionId: string): boolean {
|
|
117
|
+
const connection = this.connections.get(connectionId)
|
|
118
|
+
if (!connection) return false
|
|
119
|
+
|
|
120
|
+
connection.subscribe(sessionId)
|
|
121
|
+
|
|
122
|
+
let subscribers = this.sessionSubscriptions.get(sessionId)
|
|
123
|
+
if (!subscribers) {
|
|
124
|
+
subscribers = new Set()
|
|
125
|
+
this.sessionSubscriptions.set(sessionId, subscribers)
|
|
126
|
+
}
|
|
127
|
+
subscribers.add(connectionId)
|
|
128
|
+
|
|
129
|
+
return true
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Unsubscribe a connection from a session.
|
|
134
|
+
*/
|
|
135
|
+
unsubscribe(connectionId: string, sessionId: string): boolean {
|
|
136
|
+
const connection = this.connections.get(connectionId)
|
|
137
|
+
if (!connection) return false
|
|
138
|
+
|
|
139
|
+
connection.unsubscribe(sessionId)
|
|
140
|
+
this.unsubscribeInternal(connectionId, sessionId)
|
|
141
|
+
|
|
142
|
+
return true
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Get all subscribers for a session.
|
|
147
|
+
*/
|
|
148
|
+
getSubscribers(sessionId: string): ServerConnection<TReceive, TSend>[] {
|
|
149
|
+
const subscriberIds = this.sessionSubscriptions.get(sessionId)
|
|
150
|
+
if (!subscriberIds) return []
|
|
151
|
+
|
|
152
|
+
const subscribers: ServerConnection<TReceive, TSend>[] = []
|
|
153
|
+
const deadIds: string[] = []
|
|
154
|
+
for (const connectionId of subscriberIds) {
|
|
155
|
+
const connection = this.connections.get(connectionId)
|
|
156
|
+
if (connection?.isConnected()) {
|
|
157
|
+
subscribers.push(connection)
|
|
158
|
+
} else {
|
|
159
|
+
deadIds.push(connectionId)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Clean up dead subscriptions
|
|
164
|
+
for (const id of deadIds) {
|
|
165
|
+
subscriberIds.delete(id)
|
|
166
|
+
}
|
|
167
|
+
if (subscriberIds.size === 0) {
|
|
168
|
+
this.sessionSubscriptions.delete(sessionId)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return subscribers
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Get subscriber count for a session.
|
|
176
|
+
*/
|
|
177
|
+
getSubscriberCount(sessionId: string): number {
|
|
178
|
+
return this.sessionSubscriptions.get(sessionId)?.size ?? 0
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Check if a session has any subscribers.
|
|
183
|
+
*/
|
|
184
|
+
hasSubscribers(sessionId: string): boolean {
|
|
185
|
+
return this.getSubscriberCount(sessionId) > 0
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ============================================================================
|
|
189
|
+
// Broadcasting
|
|
190
|
+
// ============================================================================
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Broadcast a raw message to all subscribers of a session.
|
|
194
|
+
*/
|
|
195
|
+
broadcast(sessionId: string, message: string): number {
|
|
196
|
+
const subscribers = this.getSubscribers(sessionId)
|
|
197
|
+
let sent = 0
|
|
198
|
+
|
|
199
|
+
for (const connection of subscribers) {
|
|
200
|
+
if (connection.send(message)) {
|
|
201
|
+
sent++
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return sent
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Broadcast to all connections.
|
|
210
|
+
*/
|
|
211
|
+
broadcastToAll(message: string): number {
|
|
212
|
+
let sent = 0
|
|
213
|
+
|
|
214
|
+
for (const connection of this.connections.values()) {
|
|
215
|
+
if (connection.isConnected() && connection.send(message)) {
|
|
216
|
+
sent++
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return sent
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// ============================================================================
|
|
224
|
+
// Statistics
|
|
225
|
+
// ============================================================================
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Get connection statistics.
|
|
229
|
+
*/
|
|
230
|
+
getStats(): ConnectionStats {
|
|
231
|
+
const byClientType: Record<string, number> = {}
|
|
232
|
+
let activeConnections = 0
|
|
233
|
+
|
|
234
|
+
for (const connection of this.connections.values()) {
|
|
235
|
+
const clientType = connection.getClientType()
|
|
236
|
+
byClientType[clientType] = (byClientType[clientType] ?? 0) + 1
|
|
237
|
+
|
|
238
|
+
if (connection.isConnected()) {
|
|
239
|
+
activeConnections++
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
totalConnections: this.connections.size,
|
|
245
|
+
activeConnections,
|
|
246
|
+
activeSessions: this.sessionSubscriptions.size,
|
|
247
|
+
byClientType,
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ============================================================================
|
|
252
|
+
// Cleanup
|
|
253
|
+
// ============================================================================
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Clear all connections.
|
|
257
|
+
*/
|
|
258
|
+
clear(): void {
|
|
259
|
+
for (const connection of this.connections.values()) {
|
|
260
|
+
connection.disconnect()
|
|
261
|
+
}
|
|
262
|
+
this.connections.clear()
|
|
263
|
+
this.sessionSubscriptions.clear()
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Remove disconnected connections.
|
|
268
|
+
*/
|
|
269
|
+
cleanup(): number {
|
|
270
|
+
let removed = 0
|
|
271
|
+
|
|
272
|
+
for (const [connectionId, connection] of this.connections) {
|
|
273
|
+
if (!connection.isConnected()) {
|
|
274
|
+
this.remove(connectionId)
|
|
275
|
+
removed++
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return removed
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// ============================================================================
|
|
283
|
+
// Private Methods
|
|
284
|
+
// ============================================================================
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Internal unsubscribe (updates subscription map only).
|
|
288
|
+
*/
|
|
289
|
+
private unsubscribeInternal(connectionId: string, sessionId: string): void {
|
|
290
|
+
const subscribers = this.sessionSubscriptions.get(sessionId)
|
|
291
|
+
if (subscribers) {
|
|
292
|
+
subscribers.delete(connectionId)
|
|
293
|
+
if (subscribers.size === 0) {
|
|
294
|
+
this.sessionSubscriptions.delete(sessionId)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server Connection Module
|
|
3
|
+
*
|
|
4
|
+
* WebSocket server for handling incoming client connections.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { createServerConnection, ServerConnection } from './server-connection.js'
|
|
8
|
+
export type { ServerConnectionConfig, ServerHandlerContext } from './server-connection.js'
|
|
9
|
+
|
|
10
|
+
export { ConnectionManager } from './connection-manager.js'
|
|
11
|
+
export type { ConnectionStats } from './connection-manager.js'
|
|
12
|
+
|
|
13
|
+
// Re-export core types commonly used with server
|
|
14
|
+
export type {
|
|
15
|
+
ConnectionState,
|
|
16
|
+
HandlerContext,
|
|
17
|
+
Protocol,
|
|
18
|
+
ProtocolCaller,
|
|
19
|
+
ProtocolDef,
|
|
20
|
+
ProtocolHandlers,
|
|
21
|
+
ProtocolNotifier,
|
|
22
|
+
TransportEvent,
|
|
23
|
+
TransportEventListener,
|
|
24
|
+
} from '../core/index.js'
|
|
25
|
+
|
|
26
|
+
export { defineProtocol, method, notification } from '../core/index.js'
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server Connection
|
|
3
|
+
*
|
|
4
|
+
* Represents a single client connection on the server side.
|
|
5
|
+
* Simplified: no handshake - connection is ready immediately when attached.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Connection } from '../core/connection.js'
|
|
9
|
+
import type { ConnectionConfig } from '../core/connection.js'
|
|
10
|
+
import type { HandlerContext, ProtocolDef } from '../core/protocol.js'
|
|
11
|
+
import type { IServerWebSocket } from '../platform/types.js'
|
|
12
|
+
import { WebSocketReadyState } from '../platform/types.js'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Server connection configuration.
|
|
16
|
+
*/
|
|
17
|
+
export interface ServerConnectionConfig<TReceive extends ProtocolDef, TSend extends ProtocolDef> extends ConnectionConfig<TReceive, TSend> {
|
|
18
|
+
connectionId?: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Extended handler context for server connections.
|
|
23
|
+
*/
|
|
24
|
+
export interface ServerHandlerContext extends HandlerContext {
|
|
25
|
+
clientType?: string
|
|
26
|
+
subscribedSessions: Set<string>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Server-side WebSocket connection representing a single client.
|
|
31
|
+
*/
|
|
32
|
+
export class ServerConnection<TReceive extends ProtocolDef, TSend extends ProtocolDef> extends Connection<TReceive, TSend> {
|
|
33
|
+
private serverWs: IServerWebSocket | null = null
|
|
34
|
+
private clientType: string = 'unknown'
|
|
35
|
+
private subscribedSessions = new Set<string>()
|
|
36
|
+
|
|
37
|
+
constructor(config: ServerConnectionConfig<TReceive, TSend>) {
|
|
38
|
+
super(config)
|
|
39
|
+
if (config.connectionId) {
|
|
40
|
+
this.connectionId = config.connectionId
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Attach an already-accepted WebSocket connection.
|
|
46
|
+
* Connection is immediately ready - no handshake needed.
|
|
47
|
+
*/
|
|
48
|
+
attach(ws: IServerWebSocket): void {
|
|
49
|
+
this.serverWs = ws
|
|
50
|
+
this.setupServerWebSocket(ws)
|
|
51
|
+
this.setState('connected')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async connect(): Promise<void> {
|
|
55
|
+
throw new Error('Server connections use attach() instead of connect()')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async disconnect(): Promise<void> {
|
|
59
|
+
this.cleanup()
|
|
60
|
+
|
|
61
|
+
if (this.serverWs && this.serverWs.readyState !== WebSocketReadyState.CLOSED) {
|
|
62
|
+
this.serverWs.close(1000, 'Server disconnect')
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
this.serverWs = null
|
|
66
|
+
this.ws = null
|
|
67
|
+
this.setState('disconnected')
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
getClientType(): string {
|
|
71
|
+
return this.clientType
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
setClientType(clientType: string): void {
|
|
75
|
+
this.clientType = clientType
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
getSubscribedSessions(): ReadonlySet<string> {
|
|
79
|
+
return this.subscribedSessions
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
subscribe(sessionId: string): void {
|
|
83
|
+
this.subscribedSessions.add(sessionId)
|
|
84
|
+
this.serverWs?.subscribe(`session:${sessionId}`)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
unsubscribe(sessionId: string): void {
|
|
88
|
+
this.subscribedSessions.delete(sessionId)
|
|
89
|
+
this.serverWs?.unsubscribe(`session:${sessionId}`)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
isSubscribedTo(sessionId: string): boolean {
|
|
93
|
+
return this.subscribedSessions.has(sessionId)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
publishToSession(sessionId: string, message: string): void {
|
|
97
|
+
this.serverWs?.publish(`session:${sessionId}`, message)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
getHandlerContext(): ServerHandlerContext {
|
|
101
|
+
return {
|
|
102
|
+
connectionId: this.connectionId,
|
|
103
|
+
clientType: this.clientType,
|
|
104
|
+
subscribedSessions: this.subscribedSessions,
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Use server context for handlers
|
|
109
|
+
private setupServerWebSocket(ws: IServerWebSocket): void {
|
|
110
|
+
this.ws = ws
|
|
111
|
+
this.router.setSender((message) => this.send(message))
|
|
112
|
+
ws.onmessage = (data) => this.router.handleMessage(data, this.getHandlerContext())
|
|
113
|
+
ws.onerror = (error) => this.emit('error', error)
|
|
114
|
+
ws.onclose = (event) => this.handleClose(event.code, event.reason)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function createServerConnection<TReceive extends ProtocolDef, TSend extends ProtocolDef>(
|
|
119
|
+
config: ServerConnectionConfig<TReceive, TSend>,
|
|
120
|
+
): ServerConnection<TReceive, TSend> {
|
|
121
|
+
return new ServerConnection(config)
|
|
122
|
+
}
|