@principal-ai/control-tower-core 0.1.0 → 0.1.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/README.md +84 -0
- package/dist/abstractions/AuthAdapter.d.ts +9 -0
- package/dist/abstractions/AuthAdapter.d.ts.map +1 -0
- package/dist/abstractions/AuthAdapter.js +1 -0
- package/dist/abstractions/DefaultLockManager.d.ts +18 -0
- package/dist/abstractions/DefaultLockManager.d.ts.map +1 -0
- package/dist/abstractions/DefaultLockManager.js +152 -0
- package/dist/abstractions/DefaultRoomManager.d.ts +15 -0
- package/dist/abstractions/DefaultRoomManager.d.ts.map +1 -0
- package/dist/abstractions/DefaultRoomManager.js +91 -0
- package/dist/abstractions/EventEmitter.d.ts +14 -0
- package/dist/abstractions/EventEmitter.d.ts.map +1 -0
- package/dist/abstractions/EventEmitter.js +56 -0
- package/dist/abstractions/LockManager.d.ts +16 -0
- package/dist/abstractions/LockManager.d.ts.map +1 -0
- package/dist/abstractions/LockManager.js +9 -0
- package/dist/abstractions/RoomManager.d.ts +15 -0
- package/dist/abstractions/RoomManager.d.ts.map +1 -0
- package/dist/abstractions/RoomManager.js +5 -0
- package/dist/abstractions/StorageAdapter.d.ts +25 -0
- package/dist/abstractions/StorageAdapter.d.ts.map +1 -0
- package/dist/abstractions/StorageAdapter.js +1 -0
- package/dist/abstractions/TransportAdapter.d.ts +17 -0
- package/dist/abstractions/TransportAdapter.d.ts.map +1 -0
- package/dist/abstractions/TransportAdapter.js +1 -0
- package/dist/abstractions/index.d.ts +9 -0
- package/dist/abstractions/index.d.ts.map +1 -0
- package/dist/abstractions/index.js +5 -0
- package/dist/adapters/mock/MockAuthAdapter.d.ts +27 -0
- package/dist/adapters/mock/MockAuthAdapter.d.ts.map +1 -0
- package/dist/adapters/mock/MockAuthAdapter.js +161 -0
- package/dist/adapters/mock/MockStorageAdapter.d.ts +27 -0
- package/dist/adapters/mock/MockStorageAdapter.d.ts.map +1 -0
- package/dist/adapters/mock/MockStorageAdapter.js +162 -0
- package/dist/adapters/mock/MockTransportAdapter.d.ts +31 -0
- package/dist/adapters/mock/MockTransportAdapter.d.ts.map +1 -0
- package/dist/adapters/mock/MockTransportAdapter.js +90 -0
- package/dist/adapters/mock/index.d.ts +4 -0
- package/dist/adapters/mock/index.d.ts.map +1 -0
- package/dist/adapters/mock/index.js +3 -0
- package/dist/adapters/websocket/WebSocketTransportAdapter.d.ts +40 -0
- package/dist/adapters/websocket/WebSocketTransportAdapter.d.ts.map +1 -0
- package/dist/adapters/websocket/WebSocketTransportAdapter.js +204 -0
- package/dist/adapters/websocket/index.d.ts +2 -0
- package/dist/adapters/websocket/index.d.ts.map +1 -0
- package/dist/adapters/websocket/index.js +1 -0
- package/dist/client/BaseClient.d.ts +103 -0
- package/dist/client/BaseClient.d.ts.map +1 -0
- package/dist/client/BaseClient.js +282 -0
- package/dist/client/ClientBuilder.d.ts +16 -0
- package/dist/client/ClientBuilder.d.ts.map +1 -0
- package/dist/client/ClientBuilder.js +37 -0
- package/dist/client/index.d.ts +3 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +3 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -1205
- package/dist/index.js.map +23 -6
- package/dist/server/BaseServer.d.ts +116 -0
- package/dist/server/BaseServer.d.ts.map +1 -0
- package/dist/server/BaseServer.js +422 -0
- package/dist/server/ServerBuilder.d.ts +32 -0
- package/dist/server/ServerBuilder.d.ts.map +1 -0
- package/dist/server/ServerBuilder.js +66 -0
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +3 -0
- package/dist/types/auth.d.ts +40 -0
- package/dist/types/auth.d.ts.map +1 -0
- package/dist/types/auth.js +1 -0
- package/dist/types/events.d.ts +66 -0
- package/dist/types/events.d.ts.map +1 -0
- package/dist/types/events.js +1 -0
- package/dist/types/index.d.ts +23 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/lock.d.ts +35 -0
- package/dist/types/lock.d.ts.map +1 -0
- package/dist/types/lock.js +1 -0
- package/dist/types/room.d.ts +40 -0
- package/dist/types/room.d.ts.map +1 -0
- package/dist/types/room.js +1 -0
- package/package.json +7 -3
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { Event, Room, RoomConfig, Lock } from '../types';
|
|
2
|
+
import type { ITransportAdapter } from '../abstractions/TransportAdapter';
|
|
3
|
+
import type { IAuthAdapter } from '../abstractions/AuthAdapter';
|
|
4
|
+
import type { IStorageAdapter } from '../abstractions/StorageAdapter';
|
|
5
|
+
import type { RoomManager } from '../abstractions/RoomManager';
|
|
6
|
+
import type { LockManager } from '../abstractions/LockManager';
|
|
7
|
+
import { TypedEventEmitter } from '../abstractions/EventEmitter';
|
|
8
|
+
import type { Server as HttpServer } from 'http';
|
|
9
|
+
import type { Server as HttpsServer } from 'https';
|
|
10
|
+
import type { WebSocketServer } from 'ws';
|
|
11
|
+
export interface ServerConfig {
|
|
12
|
+
transport: ITransportAdapter;
|
|
13
|
+
auth?: IAuthAdapter;
|
|
14
|
+
storage?: IStorageAdapter;
|
|
15
|
+
roomManager: RoomManager;
|
|
16
|
+
lockManager: LockManager;
|
|
17
|
+
defaultRoomConfig?: Partial<RoomConfig>;
|
|
18
|
+
httpServer?: HttpServer | HttpsServer;
|
|
19
|
+
webSocketPath?: string;
|
|
20
|
+
webSocketServer?: WebSocketServer;
|
|
21
|
+
}
|
|
22
|
+
export interface ConnectedClient {
|
|
23
|
+
id: string;
|
|
24
|
+
userId: string;
|
|
25
|
+
roomId: string | null;
|
|
26
|
+
authenticated: boolean;
|
|
27
|
+
connectedAt: number;
|
|
28
|
+
}
|
|
29
|
+
export interface ServerEvents {
|
|
30
|
+
started: {
|
|
31
|
+
port: number;
|
|
32
|
+
};
|
|
33
|
+
stopped: {};
|
|
34
|
+
client_connected: {
|
|
35
|
+
client: ConnectedClient;
|
|
36
|
+
};
|
|
37
|
+
client_disconnected: {
|
|
38
|
+
clientId: string;
|
|
39
|
+
reason: string;
|
|
40
|
+
};
|
|
41
|
+
client_authenticated: {
|
|
42
|
+
clientId: string;
|
|
43
|
+
userId: string;
|
|
44
|
+
};
|
|
45
|
+
room_created: {
|
|
46
|
+
room: Room;
|
|
47
|
+
};
|
|
48
|
+
room_deleted: {
|
|
49
|
+
roomId: string;
|
|
50
|
+
};
|
|
51
|
+
client_joined_room: {
|
|
52
|
+
clientId: string;
|
|
53
|
+
roomId: string;
|
|
54
|
+
};
|
|
55
|
+
client_left_room: {
|
|
56
|
+
clientId: string;
|
|
57
|
+
roomId: string;
|
|
58
|
+
};
|
|
59
|
+
event_broadcast: {
|
|
60
|
+
roomId: string;
|
|
61
|
+
event: Event;
|
|
62
|
+
fromClientId: string;
|
|
63
|
+
};
|
|
64
|
+
lock_acquired: {
|
|
65
|
+
lock: Lock;
|
|
66
|
+
clientId: string;
|
|
67
|
+
};
|
|
68
|
+
lock_released: {
|
|
69
|
+
lockId: string;
|
|
70
|
+
clientId: string;
|
|
71
|
+
};
|
|
72
|
+
error: {
|
|
73
|
+
error: Error;
|
|
74
|
+
context?: string;
|
|
75
|
+
};
|
|
76
|
+
[key: string]: unknown;
|
|
77
|
+
}
|
|
78
|
+
export declare class BaseServer extends TypedEventEmitter<ServerEvents> {
|
|
79
|
+
private transport;
|
|
80
|
+
private auth?;
|
|
81
|
+
private storage?;
|
|
82
|
+
private roomManager;
|
|
83
|
+
private lockManager;
|
|
84
|
+
private config;
|
|
85
|
+
private clients;
|
|
86
|
+
private clientMessageHandlers;
|
|
87
|
+
private running;
|
|
88
|
+
private initialized;
|
|
89
|
+
private mode;
|
|
90
|
+
constructor(config: ServerConfig);
|
|
91
|
+
start(port?: number): Promise<void>;
|
|
92
|
+
initialize(): Promise<void>;
|
|
93
|
+
stop(): Promise<void>;
|
|
94
|
+
private handleTransportMessage;
|
|
95
|
+
private handleTransportError;
|
|
96
|
+
private handleTransportClose;
|
|
97
|
+
private addClient;
|
|
98
|
+
private disconnectClient;
|
|
99
|
+
private createClientMessageHandler;
|
|
100
|
+
private handleAuthenticate;
|
|
101
|
+
private handleJoinRoom;
|
|
102
|
+
private handleLeaveRoom;
|
|
103
|
+
private handleBroadcastEvent;
|
|
104
|
+
private handleLockRequest;
|
|
105
|
+
private handleLockRelease;
|
|
106
|
+
private sendToClient;
|
|
107
|
+
private generateId;
|
|
108
|
+
createRoom(roomId: string, config: RoomConfig): Promise<Room>;
|
|
109
|
+
deleteRoom(roomId: string): Promise<void>;
|
|
110
|
+
getConnectedClients(): ConnectedClient[];
|
|
111
|
+
getClient(clientId: string): ConnectedClient | null;
|
|
112
|
+
isRunning(): boolean;
|
|
113
|
+
getMode(): 'standalone' | 'integration';
|
|
114
|
+
isInitialized(): boolean;
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=BaseServer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BaseServer.d.ts","sourceRoot":"","sources":["../../src/server/BaseServer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
import { TypedEventEmitter } from '../abstractions/EventEmitter';
|
|
2
|
+
export class BaseServer extends TypedEventEmitter {
|
|
3
|
+
constructor(config) {
|
|
4
|
+
super();
|
|
5
|
+
this.clients = new Map();
|
|
6
|
+
this.clientMessageHandlers = new Map();
|
|
7
|
+
this.running = false;
|
|
8
|
+
this.initialized = false;
|
|
9
|
+
this.config = config;
|
|
10
|
+
this.transport = config.transport;
|
|
11
|
+
this.auth = config.auth;
|
|
12
|
+
this.storage = config.storage;
|
|
13
|
+
this.roomManager = config.roomManager;
|
|
14
|
+
this.lockManager = config.lockManager;
|
|
15
|
+
// Determine mode based on configuration
|
|
16
|
+
this.mode = (config.httpServer || config.webSocketServer) ? 'integration' : 'standalone';
|
|
17
|
+
// Set up transport event handlers
|
|
18
|
+
this.transport.onMessage(this.handleTransportMessage.bind(this));
|
|
19
|
+
this.transport.onError(this.handleTransportError.bind(this));
|
|
20
|
+
this.transport.onClose(this.handleTransportClose.bind(this));
|
|
21
|
+
}
|
|
22
|
+
// Server Lifecycle
|
|
23
|
+
async start(port) {
|
|
24
|
+
if (this.mode === 'integration') {
|
|
25
|
+
throw new Error('Cannot use start() in integration mode. Use initialize() instead.');
|
|
26
|
+
}
|
|
27
|
+
if (!port) {
|
|
28
|
+
throw new Error('Port is required for standalone mode');
|
|
29
|
+
}
|
|
30
|
+
if (this.running) {
|
|
31
|
+
throw new Error('Server is already running');
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
await this.transport.connect(`ws://localhost:${port}`, { url: `ws://localhost:${port}` });
|
|
35
|
+
this.running = true;
|
|
36
|
+
this.initialized = true;
|
|
37
|
+
await this.emit('started', { port });
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
await this.emit('error', { error: error, context: 'server_start' });
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async initialize() {
|
|
45
|
+
if (this.mode === 'standalone') {
|
|
46
|
+
throw new Error('Cannot use initialize() in standalone mode. Use start() instead.');
|
|
47
|
+
}
|
|
48
|
+
if (this.initialized) {
|
|
49
|
+
throw new Error('Server is already initialized');
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
const transport = this.transport;
|
|
53
|
+
if (this.config.webSocketServer) {
|
|
54
|
+
// Attach to existing WebSocket server
|
|
55
|
+
if (typeof transport.attachToWebSocketServer === 'function') {
|
|
56
|
+
await transport.attachToWebSocketServer(this.config.webSocketServer);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
throw new Error('Transport adapter does not support attachToWebSocketServer');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
else if (this.config.httpServer) {
|
|
63
|
+
// Attach to existing HTTP server
|
|
64
|
+
if (typeof transport.attach === 'function') {
|
|
65
|
+
await transport.attach(this.config.httpServer, this.config.webSocketPath || '/ws');
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
throw new Error('Transport adapter does not support attach');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
throw new Error('Either httpServer or webSocketServer must be provided in integration mode');
|
|
73
|
+
}
|
|
74
|
+
this.running = true;
|
|
75
|
+
this.initialized = true;
|
|
76
|
+
await this.emit('started', { port: 0 }); // Port 0 indicates integration mode
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
await this.emit('error', { error: error, context: 'server_initialize' });
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async stop() {
|
|
84
|
+
if (!this.running) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
this.running = false;
|
|
88
|
+
// Disconnect all clients
|
|
89
|
+
for (const [clientId] of Array.from(this.clients)) {
|
|
90
|
+
await this.disconnectClient(clientId, 'Server shutting down');
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
await this.transport.disconnect();
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
await this.emit('error', { error: error, context: 'server_stop' });
|
|
97
|
+
}
|
|
98
|
+
this.clients.clear();
|
|
99
|
+
this.clientMessageHandlers.clear();
|
|
100
|
+
await this.emit('stopped', {});
|
|
101
|
+
}
|
|
102
|
+
// Client Management
|
|
103
|
+
async handleTransportMessage(message) {
|
|
104
|
+
// Transport messages contain clientId in the payload for routing
|
|
105
|
+
const payload = message.payload;
|
|
106
|
+
const { clientId, ...clientMessage } = payload;
|
|
107
|
+
const clientMsg = {
|
|
108
|
+
id: message.id,
|
|
109
|
+
type: message.type,
|
|
110
|
+
payload: clientMessage,
|
|
111
|
+
timestamp: message.timestamp
|
|
112
|
+
};
|
|
113
|
+
if (!clientId) {
|
|
114
|
+
await this.emit('error', {
|
|
115
|
+
error: new Error('Message missing clientId'),
|
|
116
|
+
context: 'transport_message'
|
|
117
|
+
});
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const handler = this.clientMessageHandlers.get(clientId);
|
|
121
|
+
if (handler) {
|
|
122
|
+
await handler(clientMsg);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
await this.emit('error', {
|
|
126
|
+
error: new Error(`No handler for client ${clientId}`),
|
|
127
|
+
context: 'transport_message'
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async handleTransportError(error) {
|
|
132
|
+
await this.emit('error', { error, context: 'transport' });
|
|
133
|
+
}
|
|
134
|
+
async handleTransportClose(_code, _reason) {
|
|
135
|
+
// Transport close means server is shutting down
|
|
136
|
+
if (this.running) {
|
|
137
|
+
await this.stop();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async addClient(clientId) {
|
|
141
|
+
const client = {
|
|
142
|
+
id: clientId,
|
|
143
|
+
userId: '',
|
|
144
|
+
roomId: null,
|
|
145
|
+
authenticated: false,
|
|
146
|
+
connectedAt: Date.now()
|
|
147
|
+
};
|
|
148
|
+
this.clients.set(clientId, client);
|
|
149
|
+
// Set up message handler for this client
|
|
150
|
+
this.clientMessageHandlers.set(clientId, this.createClientMessageHandler(clientId));
|
|
151
|
+
await this.emit('client_connected', { client });
|
|
152
|
+
}
|
|
153
|
+
async disconnectClient(clientId, reason) {
|
|
154
|
+
const client = this.clients.get(clientId);
|
|
155
|
+
if (!client) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
// Leave room if in one
|
|
159
|
+
if (client.roomId) {
|
|
160
|
+
await this.roomManager.leaveRoom(client.roomId, client.userId);
|
|
161
|
+
await this.emit('client_left_room', { clientId, roomId: client.roomId });
|
|
162
|
+
}
|
|
163
|
+
// Release all locks held by this client
|
|
164
|
+
await this.lockManager.releaseUserLocks(client.userId);
|
|
165
|
+
// Clean up
|
|
166
|
+
this.clients.delete(clientId);
|
|
167
|
+
this.clientMessageHandlers.delete(clientId);
|
|
168
|
+
await this.emit('client_disconnected', { clientId, reason });
|
|
169
|
+
}
|
|
170
|
+
createClientMessageHandler(clientId) {
|
|
171
|
+
return async (message) => {
|
|
172
|
+
try {
|
|
173
|
+
const client = this.clients.get(clientId);
|
|
174
|
+
if (!client) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
switch (message.type) {
|
|
178
|
+
case 'authenticate':
|
|
179
|
+
await this.handleAuthenticate(clientId, message.payload);
|
|
180
|
+
break;
|
|
181
|
+
case 'join_room':
|
|
182
|
+
await this.handleJoinRoom(clientId, message.payload);
|
|
183
|
+
break;
|
|
184
|
+
case 'leave_room':
|
|
185
|
+
await this.handleLeaveRoom(clientId);
|
|
186
|
+
break;
|
|
187
|
+
case 'broadcast_event':
|
|
188
|
+
await this.handleBroadcastEvent(clientId, message.payload);
|
|
189
|
+
break;
|
|
190
|
+
case 'request_lock':
|
|
191
|
+
await this.handleLockRequest(clientId, message.payload);
|
|
192
|
+
break;
|
|
193
|
+
case 'release_lock':
|
|
194
|
+
await this.handleLockRelease(clientId, message.payload);
|
|
195
|
+
break;
|
|
196
|
+
case 'ping':
|
|
197
|
+
await this.sendToClient(clientId, { type: 'pong', timestamp: Date.now() });
|
|
198
|
+
break;
|
|
199
|
+
default:
|
|
200
|
+
await this.sendToClient(clientId, {
|
|
201
|
+
type: 'error',
|
|
202
|
+
error: `Unknown message type: ${message.type}`
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
await this.sendToClient(clientId, {
|
|
208
|
+
type: 'error',
|
|
209
|
+
error: error.message
|
|
210
|
+
});
|
|
211
|
+
await this.emit('error', { error: error, context: `client_${clientId}` });
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
// Message Handlers
|
|
216
|
+
async handleAuthenticate(clientId, payload) {
|
|
217
|
+
if (!this.auth) {
|
|
218
|
+
await this.sendToClient(clientId, { type: 'auth_result', success: false, error: 'Authentication not configured' });
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
try {
|
|
222
|
+
const tokenPayload = await this.auth.validateToken(payload.token);
|
|
223
|
+
const client = this.clients.get(clientId);
|
|
224
|
+
if (client) {
|
|
225
|
+
client.userId = tokenPayload.userId;
|
|
226
|
+
client.authenticated = true;
|
|
227
|
+
await this.emit('client_authenticated', { clientId, userId: tokenPayload.userId });
|
|
228
|
+
}
|
|
229
|
+
await this.sendToClient(clientId, { type: 'auth_result', success: true });
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
await this.sendToClient(clientId, { type: 'auth_result', success: false, error: error.message });
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
async handleJoinRoom(clientId, payload) {
|
|
236
|
+
const client = this.clients.get(clientId);
|
|
237
|
+
if (!client) {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
// Authenticate if token provided
|
|
241
|
+
if (payload.token && !client.authenticated) {
|
|
242
|
+
await this.handleAuthenticate(clientId, { token: payload.token });
|
|
243
|
+
}
|
|
244
|
+
if (!client.authenticated) {
|
|
245
|
+
await this.sendToClient(clientId, { type: 'error', error: 'Authentication required to join room' });
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
try {
|
|
249
|
+
// Leave current room if any
|
|
250
|
+
if (client.roomId) {
|
|
251
|
+
await this.roomManager.leaveRoom(client.roomId, client.userId);
|
|
252
|
+
await this.emit('client_left_room', { clientId, roomId: client.roomId });
|
|
253
|
+
}
|
|
254
|
+
// Get or create room
|
|
255
|
+
let roomState = await this.roomManager.getRoomState(payload.roomId);
|
|
256
|
+
if (!roomState) {
|
|
257
|
+
const roomConfig = { ...this.config.defaultRoomConfig, id: payload.roomId };
|
|
258
|
+
const room = await this.roomManager.createRoom(payload.roomId, roomConfig);
|
|
259
|
+
roomState = await this.roomManager.getRoomState(payload.roomId);
|
|
260
|
+
await this.emit('room_created', { room });
|
|
261
|
+
}
|
|
262
|
+
if (!roomState) {
|
|
263
|
+
throw new Error('Failed to create or get room state');
|
|
264
|
+
}
|
|
265
|
+
// Join room
|
|
266
|
+
const user = {
|
|
267
|
+
id: client.userId,
|
|
268
|
+
username: client.userId, // TODO: Get from auth token
|
|
269
|
+
status: 'online',
|
|
270
|
+
joinedAt: Date.now(),
|
|
271
|
+
lastActivity: Date.now(),
|
|
272
|
+
permissions: roomState.room.permissions || ['read', 'write']
|
|
273
|
+
};
|
|
274
|
+
await this.roomManager.joinRoom(payload.roomId, user);
|
|
275
|
+
client.roomId = payload.roomId;
|
|
276
|
+
await this.emit('client_joined_room', { clientId, roomId: payload.roomId });
|
|
277
|
+
// Send room state to client
|
|
278
|
+
await this.sendToClient(clientId, {
|
|
279
|
+
type: 'room_joined',
|
|
280
|
+
roomId: payload.roomId,
|
|
281
|
+
state: roomState
|
|
282
|
+
});
|
|
283
|
+
// Send event history
|
|
284
|
+
const history = await this.roomManager.getEventHistory(payload.roomId, 50);
|
|
285
|
+
if (history.length > 0) {
|
|
286
|
+
await this.sendToClient(clientId, {
|
|
287
|
+
type: 'event_history',
|
|
288
|
+
events: history
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
catch (error) {
|
|
293
|
+
await this.sendToClient(clientId, { type: 'error', error: error.message });
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
async handleLeaveRoom(clientId) {
|
|
297
|
+
const client = this.clients.get(clientId);
|
|
298
|
+
if (!client || !client.roomId) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const roomId = client.roomId;
|
|
302
|
+
await this.roomManager.leaveRoom(roomId, client.userId);
|
|
303
|
+
client.roomId = null;
|
|
304
|
+
await this.emit('client_left_room', { clientId, roomId });
|
|
305
|
+
await this.sendToClient(clientId, { type: 'room_left', roomId });
|
|
306
|
+
}
|
|
307
|
+
async handleBroadcastEvent(clientId, payload) {
|
|
308
|
+
const client = this.clients.get(clientId);
|
|
309
|
+
if (!client || client.roomId !== payload.roomId) {
|
|
310
|
+
await this.sendToClient(clientId, { type: 'error', error: 'Not in specified room' });
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
// Add metadata to event
|
|
314
|
+
const enrichedEvent = {
|
|
315
|
+
...payload.event,
|
|
316
|
+
metadata: {
|
|
317
|
+
...payload.event.metadata,
|
|
318
|
+
userId: client.userId,
|
|
319
|
+
timestamp: Date.now(),
|
|
320
|
+
roomId: payload.roomId
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
// Add to history
|
|
324
|
+
await this.roomManager.addEventToHistory(payload.roomId, enrichedEvent);
|
|
325
|
+
// Broadcast to room
|
|
326
|
+
await this.roomManager.broadcastToRoom(payload.roomId, enrichedEvent, client.userId);
|
|
327
|
+
await this.emit('event_broadcast', {
|
|
328
|
+
roomId: payload.roomId,
|
|
329
|
+
event: enrichedEvent,
|
|
330
|
+
fromClientId: clientId
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
async handleLockRequest(clientId, payload) {
|
|
334
|
+
const client = this.clients.get(clientId);
|
|
335
|
+
if (!client || client.roomId !== payload.roomId) {
|
|
336
|
+
await this.sendToClient(clientId, { type: 'error', error: 'Not in specified room' });
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
try {
|
|
340
|
+
const lock = await this.lockManager.acquireLock(client.userId, client.userId, payload.request);
|
|
341
|
+
await this.emit('lock_acquired', { lock, clientId });
|
|
342
|
+
await this.sendToClient(clientId, { type: 'lock_acquired', lock });
|
|
343
|
+
// Broadcast lock status to room
|
|
344
|
+
await this.roomManager.broadcastToRoom(payload.roomId, {
|
|
345
|
+
id: this.generateId(),
|
|
346
|
+
type: 'lock_status',
|
|
347
|
+
timestamp: Date.now(),
|
|
348
|
+
userId: client.userId,
|
|
349
|
+
roomId: payload.roomId,
|
|
350
|
+
data: { lock, action: 'acquired' },
|
|
351
|
+
metadata: { userId: client.userId, timestamp: Date.now(), roomId: payload.roomId }
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
catch (error) {
|
|
355
|
+
await this.sendToClient(clientId, { type: 'lock_denied', request: payload.request, reason: error.message });
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
async handleLockRelease(clientId, payload) {
|
|
359
|
+
const client = this.clients.get(clientId);
|
|
360
|
+
if (!client) {
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
try {
|
|
364
|
+
await this.lockManager.releaseLock(payload.lockId);
|
|
365
|
+
await this.emit('lock_released', { lockId: payload.lockId, clientId });
|
|
366
|
+
await this.sendToClient(clientId, { type: 'lock_released', lockId: payload.lockId });
|
|
367
|
+
// Broadcast lock status to room if client is in one
|
|
368
|
+
if (client.roomId) {
|
|
369
|
+
await this.roomManager.broadcastToRoom(client.roomId, {
|
|
370
|
+
id: this.generateId(),
|
|
371
|
+
type: 'lock_status',
|
|
372
|
+
timestamp: Date.now(),
|
|
373
|
+
userId: client.userId,
|
|
374
|
+
roomId: client.roomId,
|
|
375
|
+
data: { lockId: payload.lockId, action: 'released' },
|
|
376
|
+
metadata: { userId: client.userId, timestamp: Date.now(), roomId: client.roomId }
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
catch (error) {
|
|
381
|
+
await this.sendToClient(clientId, { type: 'error', error: error.message });
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
// Utility Methods
|
|
385
|
+
async sendToClient(clientId, message) {
|
|
386
|
+
const transportMessage = {
|
|
387
|
+
id: this.generateId(),
|
|
388
|
+
type: 'server_message',
|
|
389
|
+
payload: { clientId, ...message },
|
|
390
|
+
timestamp: Date.now()
|
|
391
|
+
};
|
|
392
|
+
await this.transport.send(transportMessage);
|
|
393
|
+
}
|
|
394
|
+
generateId() {
|
|
395
|
+
return Math.random().toString(36).substring(2) + Date.now().toString(36);
|
|
396
|
+
}
|
|
397
|
+
// Public API for external management
|
|
398
|
+
async createRoom(roomId, config) {
|
|
399
|
+
const room = await this.roomManager.createRoom(roomId, config);
|
|
400
|
+
await this.emit('room_created', { room });
|
|
401
|
+
return room;
|
|
402
|
+
}
|
|
403
|
+
async deleteRoom(roomId) {
|
|
404
|
+
await this.roomManager.deleteRoom(roomId);
|
|
405
|
+
await this.emit('room_deleted', { roomId });
|
|
406
|
+
}
|
|
407
|
+
getConnectedClients() {
|
|
408
|
+
return Array.from(this.clients.values());
|
|
409
|
+
}
|
|
410
|
+
getClient(clientId) {
|
|
411
|
+
return this.clients.get(clientId) || null;
|
|
412
|
+
}
|
|
413
|
+
isRunning() {
|
|
414
|
+
return this.running;
|
|
415
|
+
}
|
|
416
|
+
getMode() {
|
|
417
|
+
return this.mode;
|
|
418
|
+
}
|
|
419
|
+
isInitialized() {
|
|
420
|
+
return this.initialized;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ITransportAdapter } from '../abstractions/TransportAdapter';
|
|
2
|
+
import type { IAuthAdapter } from '../abstractions/AuthAdapter';
|
|
3
|
+
import type { IStorageAdapter } from '../abstractions/StorageAdapter';
|
|
4
|
+
import type { RoomManager } from '../abstractions/RoomManager';
|
|
5
|
+
import type { LockManager } from '../abstractions/LockManager';
|
|
6
|
+
import type { RoomConfig } from '../types';
|
|
7
|
+
import { BaseServer } from './BaseServer';
|
|
8
|
+
import type { Server as HttpServer } from 'http';
|
|
9
|
+
import type { Server as HttpsServer } from 'https';
|
|
10
|
+
import type { WebSocketServer } from 'ws';
|
|
11
|
+
export declare class ServerBuilder {
|
|
12
|
+
private transport?;
|
|
13
|
+
private auth?;
|
|
14
|
+
private storage?;
|
|
15
|
+
private roomManager?;
|
|
16
|
+
private lockManager?;
|
|
17
|
+
private defaultRoomConfig?;
|
|
18
|
+
private httpServer?;
|
|
19
|
+
private webSocketPath?;
|
|
20
|
+
private webSocketServer?;
|
|
21
|
+
withTransport(transport: ITransportAdapter): this;
|
|
22
|
+
withAuth(auth: IAuthAdapter): this;
|
|
23
|
+
withStorage(storage: IStorageAdapter): this;
|
|
24
|
+
withRoomManager(roomManager: RoomManager): this;
|
|
25
|
+
withLockManager(lockManager: LockManager): this;
|
|
26
|
+
withDefaultRoomConfig(config: Partial<RoomConfig>): this;
|
|
27
|
+
withHttpServer(server: HttpServer | HttpsServer): this;
|
|
28
|
+
withWebSocketPath(path: string): this;
|
|
29
|
+
withWebSocketServer(wss: WebSocketServer): this;
|
|
30
|
+
build(): BaseServer;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=ServerBuilder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ServerBuilder.d.ts","sourceRoot":"","sources":["../../src/server/ServerBuilder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AAC1E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACtE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC/D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAqB,MAAM,cAAc,CAAC;AAC7D,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,MAAM,CAAC;AACjD,OAAO,KAAK,EAAE,MAAM,IAAI,WAAW,EAAE,MAAM,OAAO,CAAC;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,IAAI,CAAC;AAE1C,qBAAa,aAAa;IACxB,OAAO,CAAC,SAAS,CAAC,CAAoB;IACtC,OAAO,CAAC,IAAI,CAAC,CAAe;IAC5B,OAAO,CAAC,OAAO,CAAC,CAAkB;IAClC,OAAO,CAAC,WAAW,CAAC,CAAc;IAClC,OAAO,CAAC,WAAW,CAAC,CAAc;IAClC,OAAO,CAAC,iBAAiB,CAAC,CAAsB;IAChD,OAAO,CAAC,UAAU,CAAC,CAA2B;IAC9C,OAAO,CAAC,aAAa,CAAC,CAAS;IAC/B,OAAO,CAAC,eAAe,CAAC,CAAkB;IAE1C,aAAa,CAAC,SAAS,EAAE,iBAAiB,GAAG,IAAI;IAKjD,QAAQ,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI;IAKlC,WAAW,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI;IAK3C,eAAe,CAAC,WAAW,EAAE,WAAW,GAAG,IAAI;IAK/C,eAAe,CAAC,WAAW,EAAE,WAAW,GAAG,IAAI;IAK/C,qBAAqB,CAAC,MAAM,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,IAAI;IAKxD,cAAc,CAAC,MAAM,EAAE,UAAU,GAAG,WAAW,GAAG,IAAI;IAKtD,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAKrC,mBAAmB,CAAC,GAAG,EAAE,eAAe,GAAG,IAAI;IAK/C,KAAK,IAAI,UAAU;CA+BpB"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { BaseServer } from './BaseServer';
|
|
2
|
+
export class ServerBuilder {
|
|
3
|
+
withTransport(transport) {
|
|
4
|
+
this.transport = transport;
|
|
5
|
+
return this;
|
|
6
|
+
}
|
|
7
|
+
withAuth(auth) {
|
|
8
|
+
this.auth = auth;
|
|
9
|
+
return this;
|
|
10
|
+
}
|
|
11
|
+
withStorage(storage) {
|
|
12
|
+
this.storage = storage;
|
|
13
|
+
return this;
|
|
14
|
+
}
|
|
15
|
+
withRoomManager(roomManager) {
|
|
16
|
+
this.roomManager = roomManager;
|
|
17
|
+
return this;
|
|
18
|
+
}
|
|
19
|
+
withLockManager(lockManager) {
|
|
20
|
+
this.lockManager = lockManager;
|
|
21
|
+
return this;
|
|
22
|
+
}
|
|
23
|
+
withDefaultRoomConfig(config) {
|
|
24
|
+
this.defaultRoomConfig = config;
|
|
25
|
+
return this;
|
|
26
|
+
}
|
|
27
|
+
withHttpServer(server) {
|
|
28
|
+
this.httpServer = server;
|
|
29
|
+
return this;
|
|
30
|
+
}
|
|
31
|
+
withWebSocketPath(path) {
|
|
32
|
+
this.webSocketPath = path;
|
|
33
|
+
return this;
|
|
34
|
+
}
|
|
35
|
+
withWebSocketServer(wss) {
|
|
36
|
+
this.webSocketServer = wss;
|
|
37
|
+
return this;
|
|
38
|
+
}
|
|
39
|
+
build() {
|
|
40
|
+
if (!this.transport) {
|
|
41
|
+
throw new Error('Transport adapter is required');
|
|
42
|
+
}
|
|
43
|
+
if (!this.roomManager) {
|
|
44
|
+
throw new Error('Room manager is required');
|
|
45
|
+
}
|
|
46
|
+
if (!this.lockManager) {
|
|
47
|
+
throw new Error('Lock manager is required');
|
|
48
|
+
}
|
|
49
|
+
const config = {
|
|
50
|
+
transport: this.transport,
|
|
51
|
+
auth: this.auth,
|
|
52
|
+
storage: this.storage,
|
|
53
|
+
roomManager: this.roomManager,
|
|
54
|
+
lockManager: this.lockManager,
|
|
55
|
+
defaultRoomConfig: this.defaultRoomConfig || {
|
|
56
|
+
maxUsers: 50,
|
|
57
|
+
maxHistory: 100,
|
|
58
|
+
permissions: ['read', 'write']
|
|
59
|
+
},
|
|
60
|
+
httpServer: this.httpServer,
|
|
61
|
+
webSocketPath: this.webSocketPath,
|
|
62
|
+
webSocketServer: this.webSocketServer
|
|
63
|
+
};
|
|
64
|
+
return new BaseServer(config);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AACtG,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC"}
|