@agentick/gateway 0.0.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/LICENSE +21 -0
- package/README.md +477 -0
- package/dist/agent-registry.d.ts +51 -0
- package/dist/agent-registry.d.ts.map +1 -0
- package/dist/agent-registry.js +78 -0
- package/dist/agent-registry.js.map +1 -0
- package/dist/app-registry.d.ts +51 -0
- package/dist/app-registry.d.ts.map +1 -0
- package/dist/app-registry.js +78 -0
- package/dist/app-registry.js.map +1 -0
- package/dist/bin.d.ts +8 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +37 -0
- package/dist/bin.js.map +1 -0
- package/dist/gateway.d.ts +165 -0
- package/dist/gateway.d.ts.map +1 -0
- package/dist/gateway.js +1339 -0
- package/dist/gateway.js.map +1 -0
- package/dist/http-transport.d.ts +65 -0
- package/dist/http-transport.d.ts.map +1 -0
- package/dist/http-transport.js +517 -0
- package/dist/http-transport.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol.d.ts +162 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +16 -0
- package/dist/protocol.js.map +1 -0
- package/dist/session-manager.d.ts +101 -0
- package/dist/session-manager.d.ts.map +1 -0
- package/dist/session-manager.js +208 -0
- package/dist/session-manager.js.map +1 -0
- package/dist/testing.d.ts +92 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +129 -0
- package/dist/testing.js.map +1 -0
- package/dist/transport-protocol.d.ts +162 -0
- package/dist/transport-protocol.d.ts.map +1 -0
- package/dist/transport-protocol.js +16 -0
- package/dist/transport-protocol.js.map +1 -0
- package/dist/transport.d.ts +115 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +56 -0
- package/dist/transport.js.map +1 -0
- package/dist/types.d.ts +314 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +37 -0
- package/dist/types.js.map +1 -0
- package/dist/websocket-server.d.ts +87 -0
- package/dist/websocket-server.d.ts.map +1 -0
- package/dist/websocket-server.js +245 -0
- package/dist/websocket-server.js.map +1 -0
- package/dist/ws-transport.d.ts +17 -0
- package/dist/ws-transport.d.ts.map +1 -0
- package/dist/ws-transport.js +174 -0
- package/dist/ws-transport.js.map +1 -0
- package/package.json +51 -0
- package/src/__tests__/custom-methods.spec.ts +220 -0
- package/src/__tests__/gateway-methods.spec.ts +262 -0
- package/src/__tests__/gateway.spec.ts +404 -0
- package/src/__tests__/guards.spec.ts +235 -0
- package/src/__tests__/protocol.spec.ts +58 -0
- package/src/__tests__/session-manager.spec.ts +220 -0
- package/src/__tests__/ws-transport.spec.ts +246 -0
- package/src/app-registry.ts +103 -0
- package/src/bin.ts +38 -0
- package/src/gateway.ts +1712 -0
- package/src/http-transport.ts +623 -0
- package/src/index.ts +94 -0
- package/src/session-manager.ts +272 -0
- package/src/testing.ts +236 -0
- package/src/transport-protocol.ts +249 -0
- package/src/transport.ts +191 -0
- package/src/types.ts +392 -0
- package/src/websocket-server.ts +303 -0
- package/src/ws-transport.ts +205 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gateway Types
|
|
3
|
+
*/
|
|
4
|
+
import type { App } from "@agentick/core";
|
|
5
|
+
import type { KernelContext, UserContext } from "@agentick/kernel";
|
|
6
|
+
import type { AuthConfig } from "@agentick/server";
|
|
7
|
+
export type { AuthConfig, AuthResult } from "@agentick/server";
|
|
8
|
+
/**
|
|
9
|
+
* Schema type that works with both Zod 3 and Zod 4.
|
|
10
|
+
* We only need parse() and type inference (_output).
|
|
11
|
+
*/
|
|
12
|
+
export interface ZodLikeSchema<T = unknown> {
|
|
13
|
+
parse(data: unknown): T;
|
|
14
|
+
_output: T;
|
|
15
|
+
}
|
|
16
|
+
export type { UserContext } from "@agentick/kernel";
|
|
17
|
+
export interface GatewayConfig {
|
|
18
|
+
/**
|
|
19
|
+
* Port to listen on (ignored in embedded mode)
|
|
20
|
+
* @default 18789
|
|
21
|
+
*/
|
|
22
|
+
port?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Host to bind to (ignored in embedded mode)
|
|
25
|
+
* @default "127.0.0.1"
|
|
26
|
+
*/
|
|
27
|
+
host?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Gateway ID (auto-generated if not provided)
|
|
30
|
+
*/
|
|
31
|
+
id?: string;
|
|
32
|
+
/**
|
|
33
|
+
* App definitions
|
|
34
|
+
*/
|
|
35
|
+
apps: Record<string, App>;
|
|
36
|
+
/**
|
|
37
|
+
* Default app to use when session key doesn't specify one
|
|
38
|
+
*/
|
|
39
|
+
defaultApp: string;
|
|
40
|
+
/**
|
|
41
|
+
* Authentication configuration
|
|
42
|
+
*/
|
|
43
|
+
auth?: AuthConfig;
|
|
44
|
+
/**
|
|
45
|
+
* Run in embedded mode (no standalone server).
|
|
46
|
+
* Use handleRequest() to process requests from your framework.
|
|
47
|
+
* @default false
|
|
48
|
+
*/
|
|
49
|
+
embedded?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Persistence configuration
|
|
52
|
+
*/
|
|
53
|
+
storage?: StorageConfig;
|
|
54
|
+
/**
|
|
55
|
+
* Channel adapters (WhatsApp, Slack, etc.)
|
|
56
|
+
*/
|
|
57
|
+
channels?: ChannelAdapter[];
|
|
58
|
+
/**
|
|
59
|
+
* Message routing configuration
|
|
60
|
+
*/
|
|
61
|
+
routing?: RoutingConfig;
|
|
62
|
+
/**
|
|
63
|
+
* Transport mode (ignored in embedded mode)
|
|
64
|
+
* - "websocket": WebSocket only (default, good for CLI/native clients)
|
|
65
|
+
* - "http": HTTP/SSE only (good for web browsers)
|
|
66
|
+
* - "both": Both transports on different ports
|
|
67
|
+
* @default "websocket"
|
|
68
|
+
*/
|
|
69
|
+
transport?: "websocket" | "http" | "both";
|
|
70
|
+
/**
|
|
71
|
+
* HTTP path prefix (e.g., "/api")
|
|
72
|
+
* @default ""
|
|
73
|
+
*/
|
|
74
|
+
httpPathPrefix?: string;
|
|
75
|
+
/**
|
|
76
|
+
* CORS origin for HTTP transport
|
|
77
|
+
* @default "*"
|
|
78
|
+
*/
|
|
79
|
+
httpCorsOrigin?: string;
|
|
80
|
+
/**
|
|
81
|
+
* HTTP port when using "both" mode
|
|
82
|
+
* @default port + 1
|
|
83
|
+
*/
|
|
84
|
+
httpPort?: number;
|
|
85
|
+
/**
|
|
86
|
+
* Custom methods - runs within Agentick ALS context.
|
|
87
|
+
*
|
|
88
|
+
* Supports:
|
|
89
|
+
* - Simple handlers: `async (params) => result`
|
|
90
|
+
* - Streaming: `async function* (params) { yield value }`
|
|
91
|
+
* - With config: `method({ schema, handler, roles, guard })`
|
|
92
|
+
* - Namespaces: `{ tasks: { list, create, admin: { ... } } }` (recursive)
|
|
93
|
+
*
|
|
94
|
+
* Use method() wrapper for schema validation, roles, guards, etc.
|
|
95
|
+
* ctx param is optional - use Context.get() for idiomatic access.
|
|
96
|
+
*/
|
|
97
|
+
methods?: MethodsConfig;
|
|
98
|
+
}
|
|
99
|
+
export interface StorageConfig {
|
|
100
|
+
/**
|
|
101
|
+
* Base directory for storage
|
|
102
|
+
* @default "~/.agentick"
|
|
103
|
+
*/
|
|
104
|
+
directory?: string;
|
|
105
|
+
/**
|
|
106
|
+
* Enable session persistence
|
|
107
|
+
* @default true
|
|
108
|
+
*/
|
|
109
|
+
sessions?: boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Enable memory persistence
|
|
112
|
+
* @default true
|
|
113
|
+
*/
|
|
114
|
+
memory?: boolean;
|
|
115
|
+
}
|
|
116
|
+
export interface ChannelAdapter {
|
|
117
|
+
/**
|
|
118
|
+
* Channel identifier
|
|
119
|
+
*/
|
|
120
|
+
id: string;
|
|
121
|
+
/**
|
|
122
|
+
* Human-readable name
|
|
123
|
+
*/
|
|
124
|
+
name: string;
|
|
125
|
+
/**
|
|
126
|
+
* Initialize the channel
|
|
127
|
+
*/
|
|
128
|
+
initialize(gateway: GatewayContext): Promise<void>;
|
|
129
|
+
/**
|
|
130
|
+
* Clean up resources
|
|
131
|
+
*/
|
|
132
|
+
destroy(): Promise<void>;
|
|
133
|
+
}
|
|
134
|
+
export interface GatewayContext {
|
|
135
|
+
/**
|
|
136
|
+
* Send a message to a session
|
|
137
|
+
*/
|
|
138
|
+
sendToSession(sessionId: string, message: string): Promise<void>;
|
|
139
|
+
/**
|
|
140
|
+
* Get available apps
|
|
141
|
+
*/
|
|
142
|
+
getApps(): string[];
|
|
143
|
+
/**
|
|
144
|
+
* Get or create a session
|
|
145
|
+
*/
|
|
146
|
+
getSession(sessionId: string): SessionContext;
|
|
147
|
+
}
|
|
148
|
+
export interface SessionContext {
|
|
149
|
+
id: string;
|
|
150
|
+
appId: string;
|
|
151
|
+
send(message: string): AsyncGenerator<SessionEvent>;
|
|
152
|
+
}
|
|
153
|
+
export interface SessionEvent {
|
|
154
|
+
type: string;
|
|
155
|
+
data: unknown;
|
|
156
|
+
}
|
|
157
|
+
export interface RoutingConfig {
|
|
158
|
+
/**
|
|
159
|
+
* Map channels to agents
|
|
160
|
+
*/
|
|
161
|
+
channels?: Record<string, string>;
|
|
162
|
+
/**
|
|
163
|
+
* Custom routing function
|
|
164
|
+
*/
|
|
165
|
+
custom?: (message: IncomingMessage, context: RoutingContext) => string | null;
|
|
166
|
+
}
|
|
167
|
+
export interface IncomingMessage {
|
|
168
|
+
text: string;
|
|
169
|
+
channel?: string;
|
|
170
|
+
from?: string;
|
|
171
|
+
metadata?: Record<string, unknown>;
|
|
172
|
+
}
|
|
173
|
+
export interface RoutingContext {
|
|
174
|
+
availableApps: string[];
|
|
175
|
+
defaultApp: string;
|
|
176
|
+
sessionHistory?: Array<{
|
|
177
|
+
role: string;
|
|
178
|
+
content: string;
|
|
179
|
+
}>;
|
|
180
|
+
}
|
|
181
|
+
export interface ClientState {
|
|
182
|
+
id: string;
|
|
183
|
+
connectedAt: Date;
|
|
184
|
+
authenticated: boolean;
|
|
185
|
+
/** Full user context from auth */
|
|
186
|
+
user?: UserContext;
|
|
187
|
+
subscriptions: Set<string>;
|
|
188
|
+
metadata?: Record<string, unknown>;
|
|
189
|
+
}
|
|
190
|
+
export interface SessionState {
|
|
191
|
+
id: string;
|
|
192
|
+
appId: string;
|
|
193
|
+
createdAt: Date;
|
|
194
|
+
lastActivityAt: Date;
|
|
195
|
+
messageCount: number;
|
|
196
|
+
isActive: boolean;
|
|
197
|
+
subscribers: Set<string>;
|
|
198
|
+
}
|
|
199
|
+
export interface GatewayEvents {
|
|
200
|
+
started: {
|
|
201
|
+
port: number;
|
|
202
|
+
host: string;
|
|
203
|
+
};
|
|
204
|
+
stopped: Record<string, never>;
|
|
205
|
+
"client:connected": {
|
|
206
|
+
clientId: string;
|
|
207
|
+
ip?: string;
|
|
208
|
+
};
|
|
209
|
+
"client:disconnected": {
|
|
210
|
+
clientId: string;
|
|
211
|
+
reason?: string;
|
|
212
|
+
};
|
|
213
|
+
"client:authenticated": {
|
|
214
|
+
clientId: string;
|
|
215
|
+
user?: UserContext;
|
|
216
|
+
};
|
|
217
|
+
"session:created": {
|
|
218
|
+
sessionId: string;
|
|
219
|
+
appId: string;
|
|
220
|
+
};
|
|
221
|
+
"session:closed": {
|
|
222
|
+
sessionId: string;
|
|
223
|
+
};
|
|
224
|
+
"session:message": {
|
|
225
|
+
sessionId: string;
|
|
226
|
+
role: "user" | "assistant";
|
|
227
|
+
content: string;
|
|
228
|
+
};
|
|
229
|
+
"app:message": {
|
|
230
|
+
appId: string;
|
|
231
|
+
sessionId: string;
|
|
232
|
+
message: string;
|
|
233
|
+
};
|
|
234
|
+
"channel:message": {
|
|
235
|
+
channel: string;
|
|
236
|
+
from: string;
|
|
237
|
+
message: string;
|
|
238
|
+
};
|
|
239
|
+
"channel:error": {
|
|
240
|
+
channel: string;
|
|
241
|
+
error: Error;
|
|
242
|
+
};
|
|
243
|
+
error: Error;
|
|
244
|
+
}
|
|
245
|
+
/** Symbol for detecting method definitions vs namespaces */
|
|
246
|
+
export declare const METHOD_DEFINITION: unique symbol;
|
|
247
|
+
/**
|
|
248
|
+
* Simple method handler - ctx param is optional since Context.get() works
|
|
249
|
+
*/
|
|
250
|
+
export type SimpleMethodHandler<TParams = Record<string, unknown>, TResult = unknown> = (params: TParams, ctx?: KernelContext) => Promise<TResult> | TResult;
|
|
251
|
+
/**
|
|
252
|
+
* Streaming method handler - yields values to client
|
|
253
|
+
*/
|
|
254
|
+
export type StreamingMethodHandler<TParams = Record<string, unknown>, TYield = unknown> = (params: TParams, ctx?: KernelContext) => AsyncGenerator<TYield>;
|
|
255
|
+
/**
|
|
256
|
+
* Method definition input (what you pass to method())
|
|
257
|
+
*/
|
|
258
|
+
export interface MethodDefinitionInput<TSchema extends ZodLikeSchema = ZodLikeSchema> {
|
|
259
|
+
/** Zod schema for params validation + TypeScript inference */
|
|
260
|
+
schema?: TSchema;
|
|
261
|
+
/** Handler function - receives validated & typed params */
|
|
262
|
+
handler: SimpleMethodHandler<TSchema["_output"]> | StreamingMethodHandler<TSchema["_output"]>;
|
|
263
|
+
/** Required roles - checked before handler */
|
|
264
|
+
roles?: string[];
|
|
265
|
+
/** Custom guard function */
|
|
266
|
+
guard?: (ctx: KernelContext) => boolean | Promise<boolean>;
|
|
267
|
+
/** Method description for discovery */
|
|
268
|
+
description?: string;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Method definition with symbol marker (returned by method())
|
|
272
|
+
*/
|
|
273
|
+
export interface MethodDefinition<TSchema extends ZodLikeSchema = ZodLikeSchema> extends MethodDefinitionInput<TSchema> {
|
|
274
|
+
[METHOD_DEFINITION]: true;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Factory function to create a method definition.
|
|
278
|
+
* Stores config (schema, roles, guards) - the gateway creates the
|
|
279
|
+
* actual procedure during initialization with the full inferred path name.
|
|
280
|
+
*
|
|
281
|
+
* @example
|
|
282
|
+
* methods: {
|
|
283
|
+
* tasks: {
|
|
284
|
+
* list: async (params) => { ... }, // Simple - auto-wrapped
|
|
285
|
+
* create: method({ // With config
|
|
286
|
+
* schema: z.object({ title: z.string() }),
|
|
287
|
+
* handler: async (params) => { ... }
|
|
288
|
+
* }),
|
|
289
|
+
* }
|
|
290
|
+
* }
|
|
291
|
+
*/
|
|
292
|
+
export declare function method<TSchema extends ZodLikeSchema>(definition: MethodDefinitionInput<TSchema>): MethodDefinition<TSchema>;
|
|
293
|
+
/**
|
|
294
|
+
* Check if a value is a method definition (vs a namespace)
|
|
295
|
+
*/
|
|
296
|
+
export declare function isMethodDefinition(value: unknown): value is MethodDefinition;
|
|
297
|
+
/**
|
|
298
|
+
* Method can be:
|
|
299
|
+
* - Simple function: async (params) => result
|
|
300
|
+
* - Streaming function: async function* (params) { yield }
|
|
301
|
+
* - Method definition: method({ schema, handler, roles, ... })
|
|
302
|
+
*/
|
|
303
|
+
export type Method = SimpleMethodHandler | StreamingMethodHandler | MethodDefinition<any>;
|
|
304
|
+
/**
|
|
305
|
+
* Method namespace - recursively nested, arbitrary depth
|
|
306
|
+
*/
|
|
307
|
+
export type MethodNamespace = {
|
|
308
|
+
[key: string]: Method | MethodNamespace;
|
|
309
|
+
};
|
|
310
|
+
/**
|
|
311
|
+
* Methods config - supports flat or nested namespaces
|
|
312
|
+
*/
|
|
313
|
+
export type MethodsConfig = MethodNamespace;
|
|
314
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gateway Types
|
|
3
|
+
*/
|
|
4
|
+
// ============================================================================
|
|
5
|
+
// Custom Methods
|
|
6
|
+
// ============================================================================
|
|
7
|
+
/** Symbol for detecting method definitions vs namespaces */
|
|
8
|
+
export const METHOD_DEFINITION = Symbol.for("agentick:method-definition");
|
|
9
|
+
/**
|
|
10
|
+
* Factory function to create a method definition.
|
|
11
|
+
* Stores config (schema, roles, guards) - the gateway creates the
|
|
12
|
+
* actual procedure during initialization with the full inferred path name.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* methods: {
|
|
16
|
+
* tasks: {
|
|
17
|
+
* list: async (params) => { ... }, // Simple - auto-wrapped
|
|
18
|
+
* create: method({ // With config
|
|
19
|
+
* schema: z.object({ title: z.string() }),
|
|
20
|
+
* handler: async (params) => { ... }
|
|
21
|
+
* }),
|
|
22
|
+
* }
|
|
23
|
+
* }
|
|
24
|
+
*/
|
|
25
|
+
export function method(definition) {
|
|
26
|
+
return {
|
|
27
|
+
[METHOD_DEFINITION]: true,
|
|
28
|
+
...definition,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Check if a value is a method definition (vs a namespace)
|
|
33
|
+
*/
|
|
34
|
+
export function isMethodDefinition(value) {
|
|
35
|
+
return typeof value === "object" && value !== null && METHOD_DEFINITION in value;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAkSH,+EAA+E;AAC/E,iBAAiB;AACjB,+EAA+E;AAE/E,4DAA4D;AAC5D,MAAM,CAAC,MAAM,iBAAiB,GAAG,MAAM,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;AA2C1E;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,MAAM,CACpB,UAA0C;IAE1C,OAAO;QACL,CAAC,iBAAiB,CAAC,EAAE,IAAI;QACzB,GAAG,UAAU;KACd,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAc;IAC/C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,iBAAiB,IAAI,KAAK,CAAC;AACnF,CAAC"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket Server
|
|
3
|
+
*
|
|
4
|
+
* Handles WebSocket connections and message routing.
|
|
5
|
+
*/
|
|
6
|
+
import { WebSocket } from "ws";
|
|
7
|
+
import type { ClientMessage, GatewayMessage } from "./transport-protocol.js";
|
|
8
|
+
import type { ClientState, AuthConfig } from "./types.js";
|
|
9
|
+
export interface WSServerConfig {
|
|
10
|
+
port: number;
|
|
11
|
+
host: string;
|
|
12
|
+
auth?: AuthConfig;
|
|
13
|
+
}
|
|
14
|
+
export interface WSServerEvents {
|
|
15
|
+
connection: (client: WSClient) => void;
|
|
16
|
+
disconnect: (clientId: string, reason?: string) => void;
|
|
17
|
+
message: (clientId: string, message: ClientMessage) => void;
|
|
18
|
+
error: (error: Error) => void;
|
|
19
|
+
}
|
|
20
|
+
export declare class WSClient {
|
|
21
|
+
readonly id: string;
|
|
22
|
+
readonly socket: WebSocket;
|
|
23
|
+
readonly state: ClientState;
|
|
24
|
+
private server;
|
|
25
|
+
constructor(id: string, socket: WebSocket, server: WSServer);
|
|
26
|
+
/**
|
|
27
|
+
* Send a message to this client
|
|
28
|
+
*/
|
|
29
|
+
send(message: GatewayMessage): void;
|
|
30
|
+
/**
|
|
31
|
+
* Close the connection
|
|
32
|
+
*/
|
|
33
|
+
close(code?: number, reason?: string): void;
|
|
34
|
+
/**
|
|
35
|
+
* Check if connected
|
|
36
|
+
*/
|
|
37
|
+
get isConnected(): boolean;
|
|
38
|
+
}
|
|
39
|
+
export declare class WSServer {
|
|
40
|
+
private wss;
|
|
41
|
+
private clients;
|
|
42
|
+
private config;
|
|
43
|
+
private handlers;
|
|
44
|
+
private clientIdCounter;
|
|
45
|
+
constructor(config: WSServerConfig);
|
|
46
|
+
/**
|
|
47
|
+
* Start the WebSocket server
|
|
48
|
+
*/
|
|
49
|
+
start(): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Stop the server
|
|
52
|
+
*/
|
|
53
|
+
stop(): Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* Register event handlers
|
|
56
|
+
*/
|
|
57
|
+
on<K extends keyof WSServerEvents>(event: K, handler: WSServerEvents[K]): void;
|
|
58
|
+
/**
|
|
59
|
+
* Get a client by ID
|
|
60
|
+
*/
|
|
61
|
+
getClient(id: string): WSClient | undefined;
|
|
62
|
+
/**
|
|
63
|
+
* Get all clients
|
|
64
|
+
*/
|
|
65
|
+
getClients(): WSClient[];
|
|
66
|
+
/**
|
|
67
|
+
* Get authenticated clients
|
|
68
|
+
*/
|
|
69
|
+
getAuthenticatedClients(): WSClient[];
|
|
70
|
+
/**
|
|
71
|
+
* Broadcast a message to all authenticated clients
|
|
72
|
+
*/
|
|
73
|
+
broadcast(message: GatewayMessage): void;
|
|
74
|
+
/**
|
|
75
|
+
* Send a message to clients subscribed to a session
|
|
76
|
+
*/
|
|
77
|
+
sendToSubscribers(sessionId: string, message: GatewayMessage): void;
|
|
78
|
+
/**
|
|
79
|
+
* Get connected client count
|
|
80
|
+
*/
|
|
81
|
+
get clientCount(): number;
|
|
82
|
+
private handleConnection;
|
|
83
|
+
private handleMessage;
|
|
84
|
+
private handleConnect;
|
|
85
|
+
private validateAuth;
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=websocket-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"websocket-server.d.ts","sourceRoot":"","sources":["../src/websocket-server.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAmB,SAAS,EAAE,MAAM,IAAI,CAAC;AAEhD,OAAO,KAAK,EACV,aAAa,EACb,cAAc,EAGf,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAc,MAAM,YAAY,CAAC;AAEtE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,UAAU,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,CAAC,MAAM,EAAE,QAAQ,KAAK,IAAI,CAAC;IACvC,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACxD,OAAO,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,KAAK,IAAI,CAAC;IAC5D,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAC/B;AAED,qBAAa,QAAQ;IACnB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,OAAO,CAAC,MAAM,CAAW;gBAEb,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ;IAY3D;;OAEG;IACH,IAAI,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI;IAMnC;;OAEG;IACH,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;IAI3C;;OAEG;IACH,IAAI,WAAW,IAAI,OAAO,CAEzB;CACF;AAED,qBAAa,QAAQ;IACnB,OAAO,CAAC,GAAG,CAAgC;IAC3C,OAAO,CAAC,OAAO,CAA+B;IAC9C,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,QAAQ,CAA+B;IAC/C,OAAO,CAAC,eAAe,CAAK;gBAEhB,MAAM,EAAE,cAAc;IAIlC;;OAEG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAuBtB;;OAEG;IACH,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAqBrB;;OAEG;IACH,EAAE,CAAC,CAAC,SAAS,MAAM,cAAc,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,IAAI;IAI9E;;OAEG;IACH,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS;IAI3C;;OAEG;IACH,UAAU,IAAI,QAAQ,EAAE;IAIxB;;OAEG;IACH,uBAAuB,IAAI,QAAQ,EAAE;IAIrC;;OAEG;IACH,SAAS,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI;IAMxC;;OAEG;IACH,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,IAAI;IAQnE;;OAEG;IACH,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,OAAO,CAAC,gBAAgB;YA+BV,aAAa;YA2Bb,aAAa;YAgCb,YAAY;CAyB3B"}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket Server
|
|
3
|
+
*
|
|
4
|
+
* Handles WebSocket connections and message routing.
|
|
5
|
+
*/
|
|
6
|
+
import { WebSocketServer, WebSocket } from "ws";
|
|
7
|
+
export class WSClient {
|
|
8
|
+
id;
|
|
9
|
+
socket;
|
|
10
|
+
state;
|
|
11
|
+
server;
|
|
12
|
+
constructor(id, socket, server) {
|
|
13
|
+
this.id = id;
|
|
14
|
+
this.socket = socket;
|
|
15
|
+
this.server = server;
|
|
16
|
+
this.state = {
|
|
17
|
+
id,
|
|
18
|
+
connectedAt: new Date(),
|
|
19
|
+
authenticated: false,
|
|
20
|
+
subscriptions: new Set(),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Send a message to this client
|
|
25
|
+
*/
|
|
26
|
+
send(message) {
|
|
27
|
+
if (this.socket.readyState === WebSocket.OPEN) {
|
|
28
|
+
this.socket.send(JSON.stringify(message));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Close the connection
|
|
33
|
+
*/
|
|
34
|
+
close(code, reason) {
|
|
35
|
+
this.socket.close(code, reason);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Check if connected
|
|
39
|
+
*/
|
|
40
|
+
get isConnected() {
|
|
41
|
+
return this.socket.readyState === WebSocket.OPEN;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export class WSServer {
|
|
45
|
+
wss = null;
|
|
46
|
+
clients = new Map();
|
|
47
|
+
config;
|
|
48
|
+
handlers = {};
|
|
49
|
+
clientIdCounter = 0;
|
|
50
|
+
constructor(config) {
|
|
51
|
+
this.config = config;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Start the WebSocket server
|
|
55
|
+
*/
|
|
56
|
+
start() {
|
|
57
|
+
return new Promise((resolve, reject) => {
|
|
58
|
+
try {
|
|
59
|
+
this.wss = new WebSocketServer({
|
|
60
|
+
port: this.config.port,
|
|
61
|
+
host: this.config.host,
|
|
62
|
+
});
|
|
63
|
+
this.wss.on("connection", this.handleConnection.bind(this));
|
|
64
|
+
this.wss.on("error", (error) => {
|
|
65
|
+
this.handlers.error?.(error);
|
|
66
|
+
reject(error);
|
|
67
|
+
});
|
|
68
|
+
this.wss.on("listening", () => {
|
|
69
|
+
resolve();
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
reject(error);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Stop the server
|
|
79
|
+
*/
|
|
80
|
+
stop() {
|
|
81
|
+
return new Promise((resolve) => {
|
|
82
|
+
if (!this.wss) {
|
|
83
|
+
resolve();
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
// Close all client connections
|
|
87
|
+
for (const client of this.clients.values()) {
|
|
88
|
+
client.close(1001, "Server shutting down");
|
|
89
|
+
}
|
|
90
|
+
this.clients.clear();
|
|
91
|
+
// Close the server
|
|
92
|
+
this.wss.close(() => {
|
|
93
|
+
this.wss = null;
|
|
94
|
+
resolve();
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Register event handlers
|
|
100
|
+
*/
|
|
101
|
+
on(event, handler) {
|
|
102
|
+
this.handlers[event] = handler;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Get a client by ID
|
|
106
|
+
*/
|
|
107
|
+
getClient(id) {
|
|
108
|
+
return this.clients.get(id);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Get all clients
|
|
112
|
+
*/
|
|
113
|
+
getClients() {
|
|
114
|
+
return Array.from(this.clients.values());
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Get authenticated clients
|
|
118
|
+
*/
|
|
119
|
+
getAuthenticatedClients() {
|
|
120
|
+
return this.getClients().filter((c) => c.state.authenticated);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Broadcast a message to all authenticated clients
|
|
124
|
+
*/
|
|
125
|
+
broadcast(message) {
|
|
126
|
+
for (const client of this.getAuthenticatedClients()) {
|
|
127
|
+
client.send(message);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Send a message to clients subscribed to a session
|
|
132
|
+
*/
|
|
133
|
+
sendToSubscribers(sessionId, message) {
|
|
134
|
+
for (const client of this.getAuthenticatedClients()) {
|
|
135
|
+
if (client.state.subscriptions.has(sessionId)) {
|
|
136
|
+
client.send(message);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Get connected client count
|
|
142
|
+
*/
|
|
143
|
+
get clientCount() {
|
|
144
|
+
return this.clients.size;
|
|
145
|
+
}
|
|
146
|
+
handleConnection(socket, _request) {
|
|
147
|
+
const clientId = `client-${++this.clientIdCounter}`;
|
|
148
|
+
const client = new WSClient(clientId, socket, this);
|
|
149
|
+
this.clients.set(clientId, client);
|
|
150
|
+
socket.on("message", (data) => {
|
|
151
|
+
try {
|
|
152
|
+
const message = JSON.parse(data.toString());
|
|
153
|
+
this.handleMessage(client, message);
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
client.send({
|
|
157
|
+
type: "error",
|
|
158
|
+
code: "INVALID_MESSAGE",
|
|
159
|
+
message: "Failed to parse message",
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
socket.on("close", () => {
|
|
164
|
+
this.clients.delete(clientId);
|
|
165
|
+
this.handlers.disconnect?.(clientId);
|
|
166
|
+
});
|
|
167
|
+
socket.on("error", (error) => {
|
|
168
|
+
this.handlers.error?.(error);
|
|
169
|
+
});
|
|
170
|
+
// Notify handler of new connection (before auth)
|
|
171
|
+
this.handlers.connection?.(client);
|
|
172
|
+
}
|
|
173
|
+
async handleMessage(client, message) {
|
|
174
|
+
// Handle connect message (authentication)
|
|
175
|
+
if (message.type === "connect") {
|
|
176
|
+
await this.handleConnect(client, message);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
// Handle ping
|
|
180
|
+
if (message.type === "ping") {
|
|
181
|
+
client.send({ type: "pong", timestamp: message.timestamp });
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
// All other messages require authentication
|
|
185
|
+
if (!client.state.authenticated) {
|
|
186
|
+
client.send({
|
|
187
|
+
type: "error",
|
|
188
|
+
code: "UNAUTHORIZED",
|
|
189
|
+
message: "Authentication required. Send connect message first.",
|
|
190
|
+
});
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
// Forward to message handler
|
|
194
|
+
this.handlers.message?.(client.id, message);
|
|
195
|
+
}
|
|
196
|
+
async handleConnect(client, message) {
|
|
197
|
+
// Validate authentication
|
|
198
|
+
const authResult = await this.validateAuth(message.token);
|
|
199
|
+
if (!authResult.valid) {
|
|
200
|
+
client.send({
|
|
201
|
+
type: "error",
|
|
202
|
+
code: "AUTH_FAILED",
|
|
203
|
+
message: "Authentication failed",
|
|
204
|
+
});
|
|
205
|
+
client.close(4001, "Authentication failed");
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
// Update client state
|
|
209
|
+
client.state.authenticated = true;
|
|
210
|
+
client.state.user = authResult.user;
|
|
211
|
+
client.state.metadata = {
|
|
212
|
+
...client.state.metadata,
|
|
213
|
+
...authResult.metadata,
|
|
214
|
+
...message.metadata,
|
|
215
|
+
};
|
|
216
|
+
// Client ID from message takes precedence
|
|
217
|
+
if (message.clientId) {
|
|
218
|
+
// Update internal tracking if client provides their own ID
|
|
219
|
+
this.clients.delete(client.id);
|
|
220
|
+
client.id = message.clientId;
|
|
221
|
+
this.clients.set(message.clientId, client);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
async validateAuth(token) {
|
|
225
|
+
const auth = this.config.auth;
|
|
226
|
+
// No auth configured
|
|
227
|
+
if (!auth || auth.type === "none") {
|
|
228
|
+
return { valid: true };
|
|
229
|
+
}
|
|
230
|
+
// Token auth
|
|
231
|
+
if (auth.type === "token") {
|
|
232
|
+
return { valid: token === auth.token };
|
|
233
|
+
}
|
|
234
|
+
// JWT auth
|
|
235
|
+
if (auth.type === "jwt") {
|
|
236
|
+
return { valid: false };
|
|
237
|
+
}
|
|
238
|
+
// Custom auth
|
|
239
|
+
if (auth.type === "custom") {
|
|
240
|
+
return await auth.validate(token ?? "");
|
|
241
|
+
}
|
|
242
|
+
return { valid: false };
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
//# sourceMappingURL=websocket-server.js.map
|