@principal-ai/control-tower-core 0.2.0 → 0.2.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.
Files changed (42) hide show
  1. package/dist/abstractions/ConnectedRoomManager.d.ts +22 -0
  2. package/dist/abstractions/ConnectedRoomManager.d.ts.map +1 -0
  3. package/dist/abstractions/ConnectedRoomManager.js +56 -0
  4. package/dist/abstractions/index.d.ts +1 -0
  5. package/dist/abstractions/index.d.ts.map +1 -1
  6. package/dist/abstractions/index.js +3 -1
  7. package/dist/generated/client-connection-auth.types.d.ts +312 -0
  8. package/dist/generated/client-connection-auth.types.d.ts.map +1 -0
  9. package/dist/generated/client-connection-auth.types.js +11 -0
  10. package/dist/generated/control-tower-execution.types.d.ts +445 -0
  11. package/dist/generated/control-tower-execution.types.d.ts.map +1 -0
  12. package/dist/generated/control-tower-execution.types.js +11 -0
  13. package/dist/index.d.ts +1 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +2 -1
  16. package/dist/index.js.map +8 -7
  17. package/dist/index.mjs +180 -99
  18. package/dist/index.mjs.map +8 -7
  19. package/dist/server/BaseServer.d.ts +30 -2
  20. package/dist/server/BaseServer.d.ts.map +1 -1
  21. package/dist/server/BaseServer.js +77 -8
  22. package/dist/server/ServerBuilder.d.ts.map +1 -1
  23. package/dist/server/ServerBuilder.js +11 -0
  24. package/dist/telemetry/EventValidationIntegration.d.ts +135 -0
  25. package/dist/telemetry/EventValidationIntegration.d.ts.map +1 -0
  26. package/dist/telemetry/EventValidationIntegration.js +253 -0
  27. package/dist/telemetry/EventValidationIntegration.test.d.ts +7 -0
  28. package/dist/telemetry/EventValidationIntegration.test.d.ts.map +1 -0
  29. package/dist/telemetry/EventValidationIntegration.test.js +322 -0
  30. package/dist/telemetry/TelemetryCapture.d.ts +268 -0
  31. package/dist/telemetry/TelemetryCapture.d.ts.map +1 -0
  32. package/dist/telemetry/TelemetryCapture.js +263 -0
  33. package/dist/telemetry/TelemetryCapture.test.d.ts +7 -0
  34. package/dist/telemetry/TelemetryCapture.test.d.ts.map +1 -0
  35. package/dist/telemetry/TelemetryCapture.test.js +396 -0
  36. package/dist/telemetry-example.d.ts +33 -0
  37. package/dist/telemetry-example.d.ts.map +1 -0
  38. package/dist/telemetry-example.js +124 -0
  39. package/package.json +1 -1
  40. package/dist/adapters/websocket/WebSocketTransportAdapter.d.ts +0 -60
  41. package/dist/adapters/websocket/WebSocketTransportAdapter.d.ts.map +0 -1
  42. package/dist/adapters/websocket/WebSocketTransportAdapter.js +0 -386
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Example: Using Generated Event Types
3
+ *
4
+ * This demonstrates how to use the generated types for type-safe telemetry
5
+ * in the Control Tower server.
6
+ */
7
+ /**
8
+ * Example usage in message handler
9
+ */
10
+ export declare class MessageHandlerWithTelemetry {
11
+ private emit;
12
+ handleMessage(clientId: string, messageType: string, messageSize: number): void;
13
+ private validateMessage;
14
+ }
15
+ /**
16
+ * Example usage in client lifecycle manager
17
+ */
18
+ export declare class ClientLifecycleWithTelemetry {
19
+ private emit;
20
+ handleConnect(clientId: string, transportType: string): void;
21
+ handleAuthentication(clientId: string, userId: string, authMethod: string): void;
22
+ handleDisconnect(clientId: string, reason?: string, duration?: number): void;
23
+ }
24
+ /**
25
+ * Example with multiple event type handlers
26
+ */
27
+ export declare class ControlTowerWithTelemetry {
28
+ private messageEmit;
29
+ private clientEmit;
30
+ handleNewClient(clientId: string, transportType: string): void;
31
+ handleMessage(clientId: string, messageType: string): void;
32
+ }
33
+ //# sourceMappingURL=telemetry-example.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry-example.d.ts","sourceRoot":"","sources":["../src/telemetry-example.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AA+BH;;GAEG;AACH,qBAAa,2BAA2B;IACtC,OAAO,CAAC,IAAI,CAAiC;IAE7C,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM;IA0BxE,OAAO,CAAC,eAAe;CAIxB;AAED;;GAEG;AACH,qBAAa,4BAA4B;IACvC,OAAO,CAAC,IAAI,CAAkC;IAE9C,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM;IASrD,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM;IASzE,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM;CAQtE;AAED;;GAEG;AACH,qBAAa,yBAAyB;IACpC,OAAO,CAAC,WAAW,CAAiC;IACpD,OAAO,CAAC,UAAU,CAAkC;IAEpD,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM;IASvD,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM;CAWpD"}
@@ -0,0 +1,124 @@
1
+ "use strict";
2
+ /**
3
+ * Example: Using Generated Event Types
4
+ *
5
+ * This demonstrates how to use the generated types for type-safe telemetry
6
+ * in the Control Tower server.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.ControlTowerWithTelemetry = exports.ClientLifecycleWithTelemetry = exports.MessageHandlerWithTelemetry = void 0;
10
+ /**
11
+ * Type-safe emitter for message handler events
12
+ */
13
+ function createMessageHandlerEmitter() {
14
+ return (eventName, attributes) => {
15
+ // In production, this would emit to OpenTelemetry or your observability platform
16
+ console.log(`[MessageHandler] ${eventName}`, attributes);
17
+ };
18
+ }
19
+ /**
20
+ * Type-safe emitter for client lifecycle events
21
+ */
22
+ function createClientLifecycleEmitter() {
23
+ return (eventName, attributes) => {
24
+ console.log(`[ClientLifecycle] ${eventName}`, attributes);
25
+ };
26
+ }
27
+ /**
28
+ * Example usage in message handler
29
+ */
30
+ class MessageHandlerWithTelemetry {
31
+ constructor() {
32
+ this.emit = createMessageHandlerEmitter();
33
+ }
34
+ handleMessage(clientId, messageType, messageSize) {
35
+ // ✅ Type-safe: TypeScript knows the exact attributes for this event
36
+ this.emit('message.received', {
37
+ 'client.id': clientId,
38
+ 'message.type': messageType,
39
+ 'message.size': messageSize, // Optional field
40
+ });
41
+ // Validate message...
42
+ const isValid = this.validateMessage(messageType);
43
+ if (isValid) {
44
+ this.emit('message.validated', {
45
+ 'client.id': clientId,
46
+ 'message.type': messageType,
47
+ });
48
+ }
49
+ else {
50
+ this.emit('message.validation_failed', {
51
+ 'client.id': clientId,
52
+ 'message.type': messageType,
53
+ 'error.message': 'Invalid message schema',
54
+ 'error.field': 'payload', // Optional field
55
+ });
56
+ }
57
+ }
58
+ validateMessage(messageType) {
59
+ // Mock validation
60
+ return messageType !== 'invalid';
61
+ }
62
+ }
63
+ exports.MessageHandlerWithTelemetry = MessageHandlerWithTelemetry;
64
+ /**
65
+ * Example usage in client lifecycle manager
66
+ */
67
+ class ClientLifecycleWithTelemetry {
68
+ constructor() {
69
+ this.emit = createClientLifecycleEmitter();
70
+ }
71
+ handleConnect(clientId, transportType) {
72
+ // ✅ Type-safe connection event
73
+ this.emit('client.connected', {
74
+ 'client.id': clientId,
75
+ 'transport.type': transportType,
76
+ 'connection.time': Date.now(),
77
+ });
78
+ }
79
+ handleAuthentication(clientId, userId, authMethod) {
80
+ // ✅ Type-safe authentication event
81
+ this.emit('client.authenticated', {
82
+ 'client.id': clientId,
83
+ 'user.id': userId,
84
+ 'auth.method': authMethod, // Optional field
85
+ });
86
+ }
87
+ handleDisconnect(clientId, reason, duration) {
88
+ // ✅ Type-safe disconnection event
89
+ this.emit('client.disconnected', {
90
+ 'client.id': clientId,
91
+ 'disconnect.reason': reason, // Optional field
92
+ 'session.duration': duration, // Optional field
93
+ });
94
+ }
95
+ }
96
+ exports.ClientLifecycleWithTelemetry = ClientLifecycleWithTelemetry;
97
+ /**
98
+ * Example with multiple event type handlers
99
+ */
100
+ class ControlTowerWithTelemetry {
101
+ constructor() {
102
+ this.messageEmit = createMessageHandlerEmitter();
103
+ this.clientEmit = createClientLifecycleEmitter();
104
+ // ❌ TypeScript error: can't use client events with message emitter
105
+ // handleWrongEvent() {
106
+ // this.messageEmit('client.connected', { ... }); // Error!
107
+ // }
108
+ }
109
+ handleNewClient(clientId, transportType) {
110
+ // Each emitter is type-safe to its specific events
111
+ this.clientEmit('client.connected', {
112
+ 'client.id': clientId,
113
+ 'transport.type': transportType,
114
+ 'connection.time': Date.now(),
115
+ });
116
+ }
117
+ handleMessage(clientId, messageType) {
118
+ this.messageEmit('message.received', {
119
+ 'client.id': clientId,
120
+ 'message.type': messageType,
121
+ });
122
+ }
123
+ }
124
+ exports.ControlTowerWithTelemetry = ControlTowerWithTelemetry;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@principal-ai/control-tower-core",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Centralized, runtime-agnostic library for real-time collaboration and synchronization",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -1,60 +0,0 @@
1
- import type { ITransportAdapter } from '../../abstractions/TransportAdapter.js';
2
- import type { IAuthAdapter } from '../../abstractions/AuthAdapter.js';
3
- import type { ConnectionState, ConnectionOptions, Message, MessageHandler, ErrorHandler, CloseHandler } from '../../types/index.js';
4
- import type { Server as HttpServer } from 'http';
5
- import type { Server as HttpsServer } from 'https';
6
- import { WebSocketServer, WebSocket } from 'ws';
7
- interface ClientConnection {
8
- id: string;
9
- ws: WebSocket;
10
- userId?: string;
11
- authenticated: boolean;
12
- metadata?: Record<string, unknown>;
13
- authTimeout?: NodeJS.Timeout;
14
- connectedAt: number;
15
- }
16
- export interface WebSocketTransportConfig {
17
- authTimeout?: number;
18
- closeOnAuthFailure?: boolean;
19
- requireAuth?: boolean;
20
- }
21
- export declare class WebSocketTransportAdapter implements ITransportAdapter {
22
- private state;
23
- private messageHandlers;
24
- private errorHandlers;
25
- private closeHandlers;
26
- private wss?;
27
- private serverUrl?;
28
- private attachedServer?;
29
- private attachedWss?;
30
- private webSocketPath?;
31
- private clients;
32
- private mode;
33
- private authAdapter?;
34
- private config;
35
- constructor(config?: WebSocketTransportConfig);
36
- setAuthAdapter(auth: IAuthAdapter): void;
37
- connect(url: string, _options?: ConnectionOptions): Promise<void>;
38
- attach(server: HttpServer | HttpsServer, path?: string): Promise<void>;
39
- attachToWebSocketServer(wss: WebSocketServer): Promise<void>;
40
- private handleConnection;
41
- private handleClientMessage;
42
- private handleAuthMessage;
43
- private extractBearerToken;
44
- private sendToClient;
45
- disconnect(): Promise<void>;
46
- send(message: Message): Promise<void>;
47
- onMessage(handler: MessageHandler): void;
48
- onError(handler: ErrorHandler): void;
49
- onClose(handler: CloseHandler): void;
50
- getState(): ConnectionState;
51
- isConnected(): boolean;
52
- getConnectedClients(): ClientConnection[];
53
- getAuthenticatedClients(): ClientConnection[];
54
- getClientCount(): number;
55
- getMode(): 'standalone' | 'integration';
56
- isAuthRequired(): boolean;
57
- private generateId;
58
- }
59
- export {};
60
- //# sourceMappingURL=WebSocketTransportAdapter.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"WebSocketTransportAdapter.d.ts","sourceRoot":"","sources":["../../../src/adapters/websocket/WebSocketTransportAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAChF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACtE,OAAO,KAAK,EACV,eAAe,EACf,iBAAiB,EACjB,OAAO,EACP,cAAc,EACd,YAAY,EACZ,YAAY,EAGb,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,MAAM,CAAC;AACjD,OAAO,KAAK,EAAE,MAAM,IAAI,WAAW,EAAE,MAAM,OAAO,CAAC;AAEnD,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAEhD,UAAU,gBAAgB;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,SAAS,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC;IAC7B,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,wBAAwB;IACvC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,qBAAa,yBAA0B,YAAW,iBAAiB;IACjE,OAAO,CAAC,KAAK,CAAmC;IAChD,OAAO,CAAC,eAAe,CAAkC;IACzD,OAAO,CAAC,aAAa,CAAgC;IACrD,OAAO,CAAC,aAAa,CAAgC;IAGrD,OAAO,CAAC,GAAG,CAAC,CAAkB;IAC9B,OAAO,CAAC,SAAS,CAAC,CAAS;IAG3B,OAAO,CAAC,cAAc,CAAC,CAA2B;IAClD,OAAO,CAAC,WAAW,CAAC,CAAkB;IACtC,OAAO,CAAC,aAAa,CAAC,CAAS;IAG/B,OAAO,CAAC,OAAO,CAAuC;IACtD,OAAO,CAAC,IAAI,CAA8C;IAG1D,OAAO,CAAC,WAAW,CAAC,CAAe;IACnC,OAAO,CAAC,MAAM,CAA2B;gBAE7B,MAAM,CAAC,EAAE,wBAAwB;IAU7C,cAAc,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI;IAclC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAsCjE,MAAM,CAAC,MAAM,EAAE,UAAU,GAAG,WAAW,EAAE,IAAI,GAAE,MAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAiC7E,uBAAuB,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;YA2BpD,gBAAgB;YA6EhB,mBAAmB;YAuCnB,iBAAiB;IA4E/B,OAAO,CAAC,kBAAkB;IAO1B,OAAO,CAAC,YAAY;IAMd,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAiC3B,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IA8B3C,SAAS,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI;IAIxC,OAAO,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI;IAIpC,OAAO,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI;IAIpC,QAAQ,IAAI,eAAe;IAI3B,WAAW,IAAI,OAAO;IAKtB,mBAAmB,IAAI,gBAAgB,EAAE;IAIzC,uBAAuB,IAAI,gBAAgB,EAAE;IAI7C,cAAc,IAAI,MAAM;IAIxB,OAAO,IAAI,YAAY,GAAG,aAAa;IAIvC,cAAc,IAAI,OAAO;IAIzB,OAAO,CAAC,UAAU;CAGnB"}
@@ -1,386 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.WebSocketTransportAdapter = void 0;
4
- const ws_1 = require("ws");
5
- class WebSocketTransportAdapter {
6
- constructor(config) {
7
- this.state = 'disconnected';
8
- this.messageHandlers = new Set();
9
- this.errorHandlers = new Set();
10
- this.closeHandlers = new Set();
11
- // Client management
12
- this.clients = new Map();
13
- this.mode = 'standalone';
14
- this.config = {
15
- authTimeout: config?.authTimeout ?? 5000,
16
- closeOnAuthFailure: config?.closeOnAuthFailure ?? false,
17
- requireAuth: config?.requireAuth ?? false,
18
- ...config
19
- };
20
- }
21
- // Set the auth adapter
22
- setAuthAdapter(auth) {
23
- this.authAdapter = auth;
24
- // Use auth adapter's preference only if requireAuth is not already true
25
- // This allows explicit requireAuth: true in config to take precedence
26
- if (auth.isAuthRequired && !this.config.requireAuth) {
27
- this.config.requireAuth = auth.isAuthRequired();
28
- }
29
- // For authTimeout, use adapter's value if not explicitly set (not matching default)
30
- if (auth.getAuthTimeout && this.config.authTimeout === 5000) {
31
- this.config.authTimeout = auth.getAuthTimeout();
32
- }
33
- }
34
- // Standalone mode implementation
35
- async connect(url, _options) {
36
- if (this.state === 'connected' || this.state === 'connecting') {
37
- throw new Error('Already connected or connecting');
38
- }
39
- this.state = 'connecting';
40
- this.serverUrl = url;
41
- this.mode = 'standalone';
42
- try {
43
- // Extract port from URL
44
- const urlObj = new URL(url);
45
- const port = parseInt(urlObj.port) || (urlObj.protocol === 'wss:' ? 443 : 80);
46
- // Create WebSocket server
47
- this.wss = new ws_1.WebSocketServer({ port });
48
- this.wss.on('connection', (ws, req) => {
49
- this.handleConnection(ws, req);
50
- });
51
- this.wss.on('error', (error) => {
52
- this.errorHandlers.forEach(handler => handler(error));
53
- });
54
- this.wss.on('close', () => {
55
- this.state = 'disconnected';
56
- this.closeHandlers.forEach(handler => handler(1000, 'Server closed'));
57
- });
58
- this.state = 'connected';
59
- }
60
- catch (error) {
61
- this.state = 'disconnected';
62
- throw error;
63
- }
64
- }
65
- // Integration mode implementation
66
- async attach(server, path = '/ws') {
67
- if (this.state === 'connected' || this.state === 'connecting') {
68
- throw new Error('Already connected or connecting');
69
- }
70
- this.state = 'connecting';
71
- this.attachedServer = server;
72
- this.webSocketPath = path;
73
- this.mode = 'integration';
74
- try {
75
- // Create WebSocket server attached to the existing HTTP server
76
- this.wss = new ws_1.WebSocketServer({
77
- server,
78
- path,
79
- perMessageDeflate: false
80
- });
81
- this.wss.on('connection', (ws, req) => {
82
- this.handleConnection(ws, req);
83
- });
84
- this.wss.on('error', (error) => {
85
- this.errorHandlers.forEach(handler => handler(error));
86
- });
87
- this.state = 'connected';
88
- }
89
- catch (error) {
90
- this.state = 'disconnected';
91
- throw error;
92
- }
93
- }
94
- async attachToWebSocketServer(wss) {
95
- if (this.state === 'connected' || this.state === 'connecting') {
96
- throw new Error('Already connected or connecting');
97
- }
98
- this.state = 'connecting';
99
- this.attachedWss = wss;
100
- this.mode = 'integration';
101
- try {
102
- this.wss = wss;
103
- this.wss.on('connection', (ws, req) => {
104
- this.handleConnection(ws, req);
105
- });
106
- this.wss.on('error', (error) => {
107
- this.errorHandlers.forEach(handler => handler(error));
108
- });
109
- this.state = 'connected';
110
- }
111
- catch (error) {
112
- this.state = 'disconnected';
113
- throw error;
114
- }
115
- }
116
- async handleConnection(ws, req) {
117
- const clientId = this.generateId();
118
- // Step 1: Try header authentication if auth adapter is available
119
- let authenticated = false;
120
- let authResult = null;
121
- if (this.authAdapter && req.headers.authorization) {
122
- const token = this.extractBearerToken(req.headers.authorization);
123
- if (token) {
124
- try {
125
- authResult = await this.authAdapter.authenticate({
126
- type: 'bearer',
127
- token: token
128
- });
129
- authenticated = authResult.success;
130
- }
131
- catch {
132
- // Header auth failed but don't reject connection
133
- }
134
- }
135
- }
136
- // Step 2: Create client with auth state
137
- const client = {
138
- id: clientId,
139
- ws,
140
- authenticated,
141
- userId: authResult?.userId || (authResult?.user?.userId),
142
- metadata: authResult?.metadata || authResult?.user?.metadata,
143
- connectedAt: Date.now()
144
- };
145
- this.clients.set(clientId, client);
146
- // Step 3: Set up message handling
147
- ws.on('message', (data) => {
148
- this.handleClientMessage(clientId, data);
149
- });
150
- ws.on('error', (error) => {
151
- this.errorHandlers.forEach(handler => handler(error));
152
- });
153
- ws.on('close', (_code, _reason) => {
154
- // Clear auth timeout if exists
155
- if (client.authTimeout) {
156
- clearTimeout(client.authTimeout);
157
- }
158
- this.clients.delete(clientId);
159
- });
160
- // Step 4: Set auth timeout if not authenticated and auth is required
161
- if (!authenticated && this.config.requireAuth && this.authAdapter) {
162
- client.authTimeout = setTimeout(() => {
163
- if (!client.authenticated) {
164
- ws.close(1008, 'Authentication timeout');
165
- this.clients.delete(clientId);
166
- }
167
- }, this.config.authTimeout);
168
- }
169
- // Step 5: Notify about connection (with auth status)
170
- const connectionMessage = {
171
- id: this.generateId(),
172
- type: 'connection',
173
- payload: {
174
- clientId,
175
- authenticated,
176
- userId: client.userId,
177
- metadata: client.metadata
178
- },
179
- timestamp: Date.now()
180
- };
181
- this.messageHandlers.forEach(handler => handler(connectionMessage));
182
- }
183
- async handleClientMessage(clientId, data) {
184
- const client = this.clients.get(clientId);
185
- if (!client)
186
- return;
187
- try {
188
- const message = JSON.parse(data.toString());
189
- // Special handling for authentication messages
190
- if (message.type === 'authenticate' && !client.authenticated && this.authAdapter) {
191
- await this.handleAuthMessage(client, message);
192
- return;
193
- }
194
- // Reject messages from unauthenticated clients if auth is required
195
- if (this.config.requireAuth && !client.authenticated) {
196
- this.sendToClient(client, {
197
- id: this.generateId(),
198
- type: 'error',
199
- payload: { error: 'Authentication required' },
200
- timestamp: Date.now()
201
- });
202
- return;
203
- }
204
- // Add clientId to message payload for routing
205
- const enrichedMessage = {
206
- ...message,
207
- payload: {
208
- ...message.payload,
209
- clientId
210
- }
211
- };
212
- this.messageHandlers.forEach(handler => handler(enrichedMessage));
213
- }
214
- catch (error) {
215
- this.errorHandlers.forEach(handler => handler(error));
216
- }
217
- }
218
- async handleAuthMessage(client, message) {
219
- if (!this.authAdapter) {
220
- this.sendToClient(client, {
221
- id: this.generateId(),
222
- type: 'auth_error',
223
- payload: { error: 'No auth adapter configured' },
224
- timestamp: Date.now()
225
- });
226
- return;
227
- }
228
- try {
229
- const credentials = message.payload;
230
- const result = await this.authAdapter.authenticate(credentials);
231
- if (result.success) {
232
- // Clear auth timeout
233
- if (client.authTimeout) {
234
- clearTimeout(client.authTimeout);
235
- client.authTimeout = undefined;
236
- }
237
- // Update client state
238
- client.authenticated = true;
239
- client.userId = result.userId || result.user?.userId;
240
- client.metadata = result.metadata || result.user?.metadata;
241
- // Send success response
242
- this.sendToClient(client, {
243
- id: this.generateId(),
244
- type: 'auth_success',
245
- payload: {
246
- userId: client.userId,
247
- metadata: client.metadata
248
- },
249
- timestamp: Date.now()
250
- });
251
- // Notify handlers about authentication
252
- const authMessage = {
253
- id: this.generateId(),
254
- type: 'client_authenticated',
255
- payload: {
256
- clientId: client.id,
257
- userId: client.userId,
258
- metadata: client.metadata
259
- },
260
- timestamp: Date.now()
261
- };
262
- this.messageHandlers.forEach(handler => handler(authMessage));
263
- }
264
- else {
265
- // Send error response
266
- this.sendToClient(client, {
267
- id: this.generateId(),
268
- type: 'auth_error',
269
- payload: { error: result.error || 'Authentication failed' },
270
- timestamp: Date.now()
271
- });
272
- // Optionally close connection
273
- if (this.config.closeOnAuthFailure) {
274
- client.ws.close(1008, 'Authentication failed');
275
- this.clients.delete(client.id);
276
- }
277
- }
278
- }
279
- catch (error) {
280
- this.sendToClient(client, {
281
- id: this.generateId(),
282
- type: 'auth_error',
283
- payload: { error: error.message },
284
- timestamp: Date.now()
285
- });
286
- }
287
- }
288
- extractBearerToken(authHeader) {
289
- if (authHeader.startsWith('Bearer ')) {
290
- return authHeader.substring(7);
291
- }
292
- return null;
293
- }
294
- sendToClient(client, message) {
295
- if (client.ws.readyState === ws_1.WebSocket.OPEN) {
296
- client.ws.send(JSON.stringify(message));
297
- }
298
- }
299
- async disconnect() {
300
- if (this.state === 'disconnected') {
301
- return;
302
- }
303
- this.state = 'disconnecting';
304
- // Close all client connections
305
- for (const [_clientId, client] of this.clients) {
306
- try {
307
- if (client.authTimeout) {
308
- clearTimeout(client.authTimeout);
309
- }
310
- client.ws.close(1000, 'Server shutting down');
311
- }
312
- catch {
313
- // Ignore errors when closing individual connections
314
- }
315
- }
316
- this.clients.clear();
317
- // Close the WebSocket server (only if we created it)
318
- if (this.wss && this.mode === 'standalone') {
319
- await new Promise((resolve) => {
320
- this.wss.close(() => resolve());
321
- });
322
- }
323
- this.state = 'disconnected';
324
- this.wss = undefined;
325
- this.attachedServer = undefined;
326
- this.attachedWss = undefined;
327
- }
328
- async send(message) {
329
- if (this.state !== 'connected') {
330
- throw new Error('Not connected');
331
- }
332
- // Extract clientId from message payload for routing
333
- const payload = message.payload;
334
- const { clientId, ...clientMessage } = payload;
335
- if (!clientId) {
336
- throw new Error('Message must contain clientId in payload for routing');
337
- }
338
- const client = this.clients.get(clientId);
339
- if (!client) {
340
- throw new Error(`Client ${clientId} not found`);
341
- }
342
- if (client.ws.readyState !== ws_1.WebSocket.OPEN) {
343
- throw new Error(`Client ${clientId} connection is not open`);
344
- }
345
- const messageToSend = {
346
- ...message,
347
- payload: clientMessage
348
- };
349
- client.ws.send(JSON.stringify(messageToSend));
350
- }
351
- onMessage(handler) {
352
- this.messageHandlers.add(handler);
353
- }
354
- onError(handler) {
355
- this.errorHandlers.add(handler);
356
- }
357
- onClose(handler) {
358
- this.closeHandlers.add(handler);
359
- }
360
- getState() {
361
- return this.state;
362
- }
363
- isConnected() {
364
- return this.state === 'connected';
365
- }
366
- // Utility methods
367
- getConnectedClients() {
368
- return Array.from(this.clients.values());
369
- }
370
- getAuthenticatedClients() {
371
- return Array.from(this.clients.values()).filter(c => c.authenticated);
372
- }
373
- getClientCount() {
374
- return this.clients.size;
375
- }
376
- getMode() {
377
- return this.mode;
378
- }
379
- isAuthRequired() {
380
- return this.config.requireAuth ?? false;
381
- }
382
- generateId() {
383
- return Math.random().toString(36).substring(2) + Date.now().toString(36);
384
- }
385
- }
386
- exports.WebSocketTransportAdapter = WebSocketTransportAdapter;