@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
package/dist/index.js
CHANGED
|
@@ -1,1205 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
entry = __defProp({}, "__esModule", { value: true });
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function")
|
|
12
|
-
__getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
|
|
13
|
-
get: () => from[key],
|
|
14
|
-
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
15
|
-
}));
|
|
16
|
-
__moduleCache.set(from, entry);
|
|
17
|
-
return entry;
|
|
18
|
-
};
|
|
19
|
-
var __export = (target, all) => {
|
|
20
|
-
for (var name in all)
|
|
21
|
-
__defProp(target, name, {
|
|
22
|
-
get: all[name],
|
|
23
|
-
enumerable: true,
|
|
24
|
-
configurable: true,
|
|
25
|
-
set: (newValue) => all[name] = () => newValue
|
|
26
|
-
});
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
// src/index.ts
|
|
30
|
-
var exports_src = {};
|
|
31
|
-
__export(exports_src, {
|
|
32
|
-
TypedEventEmitter: () => TypedEventEmitter,
|
|
33
|
-
ServerBuilder: () => ServerBuilder,
|
|
34
|
-
RoomManager: () => RoomManager,
|
|
35
|
-
MockTransportAdapter: () => MockTransportAdapter,
|
|
36
|
-
MockStorageAdapter: () => MockStorageAdapter,
|
|
37
|
-
MockAuthAdapter: () => MockAuthAdapter,
|
|
38
|
-
LockManager: () => LockManager,
|
|
39
|
-
ClientBuilder: () => ClientBuilder,
|
|
40
|
-
BaseServer: () => BaseServer,
|
|
41
|
-
BaseClient: () => BaseClient
|
|
42
|
-
});
|
|
43
|
-
module.exports = __toCommonJS(exports_src);
|
|
44
|
-
// src/abstractions/EventEmitter.ts
|
|
45
|
-
class TypedEventEmitter {
|
|
46
|
-
listeners = new Map;
|
|
47
|
-
onceListeners = new Map;
|
|
48
|
-
on(event, listener) {
|
|
49
|
-
if (!this.listeners.has(event)) {
|
|
50
|
-
this.listeners.set(event, new Set);
|
|
51
|
-
}
|
|
52
|
-
this.listeners.get(event).add(listener);
|
|
53
|
-
return () => this.off(event, listener);
|
|
54
|
-
}
|
|
55
|
-
once(event, listener) {
|
|
56
|
-
if (!this.onceListeners.has(event)) {
|
|
57
|
-
this.onceListeners.set(event, new Set);
|
|
58
|
-
}
|
|
59
|
-
this.onceListeners.get(event).add(listener);
|
|
60
|
-
return () => this.off(event, listener);
|
|
61
|
-
}
|
|
62
|
-
off(event, listener) {
|
|
63
|
-
this.listeners.get(event)?.delete(listener);
|
|
64
|
-
this.onceListeners.get(event)?.delete(listener);
|
|
65
|
-
}
|
|
66
|
-
async emit(event, data) {
|
|
67
|
-
const listeners = this.listeners.get(event);
|
|
68
|
-
const onceListeners = this.onceListeners.get(event);
|
|
69
|
-
if (onceListeners) {
|
|
70
|
-
const listenersToCall = Array.from(onceListeners);
|
|
71
|
-
this.onceListeners.delete(event);
|
|
72
|
-
for (const listener of listenersToCall) {
|
|
73
|
-
await listener(data);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
if (listeners) {
|
|
77
|
-
for (const listener of Array.from(listeners)) {
|
|
78
|
-
await listener(data);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
removeAllListeners(event) {
|
|
83
|
-
if (event) {
|
|
84
|
-
this.listeners.delete(event);
|
|
85
|
-
this.onceListeners.delete(event);
|
|
86
|
-
} else {
|
|
87
|
-
this.listeners.clear();
|
|
88
|
-
this.onceListeners.clear();
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
listenerCount(event) {
|
|
92
|
-
const regular = this.listeners.get(event)?.size ?? 0;
|
|
93
|
-
const once = this.onceListeners.get(event)?.size ?? 0;
|
|
94
|
-
return regular + once;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
// src/abstractions/RoomManager.ts
|
|
98
|
-
class RoomManager {
|
|
99
|
-
rooms = new Map;
|
|
100
|
-
}
|
|
101
|
-
// src/abstractions/LockManager.ts
|
|
102
|
-
class LockManager {
|
|
103
|
-
lockState = {
|
|
104
|
-
locks: new Map,
|
|
105
|
-
queue: new Map,
|
|
106
|
-
userLocks: new Map
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
// src/adapters/mock/MockTransportAdapter.ts
|
|
110
|
-
class MockTransportAdapter {
|
|
111
|
-
state = "disconnected";
|
|
112
|
-
messageHandlers = new Set;
|
|
113
|
-
errorHandlers = new Set;
|
|
114
|
-
closeHandlers = new Set;
|
|
115
|
-
messageQueue = [];
|
|
116
|
-
simulateLatency = 0;
|
|
117
|
-
shouldFailConnection = false;
|
|
118
|
-
connectedUrl = null;
|
|
119
|
-
constructor(options) {
|
|
120
|
-
this.simulateLatency = options?.simulateLatency ?? 0;
|
|
121
|
-
this.shouldFailConnection = options?.shouldFailConnection ?? false;
|
|
122
|
-
}
|
|
123
|
-
async connect(url, _options) {
|
|
124
|
-
if (this.shouldFailConnection) {
|
|
125
|
-
this.state = "disconnected";
|
|
126
|
-
const error = new Error("Mock connection failed");
|
|
127
|
-
this.errorHandlers.forEach((handler) => handler(error));
|
|
128
|
-
throw error;
|
|
129
|
-
}
|
|
130
|
-
this.state = "connecting";
|
|
131
|
-
this.connectedUrl = url;
|
|
132
|
-
if (this.simulateLatency > 0) {
|
|
133
|
-
await new Promise((resolve) => setTimeout(resolve, this.simulateLatency));
|
|
134
|
-
}
|
|
135
|
-
this.state = "connected";
|
|
136
|
-
}
|
|
137
|
-
async disconnect() {
|
|
138
|
-
if (this.state === "disconnected")
|
|
139
|
-
return;
|
|
140
|
-
this.state = "disconnecting";
|
|
141
|
-
if (this.simulateLatency > 0) {
|
|
142
|
-
await new Promise((resolve) => setTimeout(resolve, this.simulateLatency / 2));
|
|
143
|
-
}
|
|
144
|
-
this.state = "disconnected";
|
|
145
|
-
this.connectedUrl = null;
|
|
146
|
-
this.closeHandlers.forEach((handler) => handler(1000, "Normal closure"));
|
|
147
|
-
this.messageQueue = [];
|
|
148
|
-
}
|
|
149
|
-
async send(message) {
|
|
150
|
-
if (this.state !== "connected") {
|
|
151
|
-
throw new Error("Not connected");
|
|
152
|
-
}
|
|
153
|
-
if (this.simulateLatency > 0) {
|
|
154
|
-
await new Promise((resolve) => setTimeout(resolve, this.simulateLatency));
|
|
155
|
-
}
|
|
156
|
-
this.messageQueue.push(message);
|
|
157
|
-
}
|
|
158
|
-
onMessage(handler) {
|
|
159
|
-
this.messageHandlers.add(handler);
|
|
160
|
-
}
|
|
161
|
-
onError(handler) {
|
|
162
|
-
this.errorHandlers.add(handler);
|
|
163
|
-
}
|
|
164
|
-
onClose(handler) {
|
|
165
|
-
this.closeHandlers.add(handler);
|
|
166
|
-
}
|
|
167
|
-
getState() {
|
|
168
|
-
return this.state;
|
|
169
|
-
}
|
|
170
|
-
isConnected() {
|
|
171
|
-
return this.state === "connected";
|
|
172
|
-
}
|
|
173
|
-
simulateMessage(message) {
|
|
174
|
-
if (this.state !== "connected") {
|
|
175
|
-
throw new Error("Cannot simulate message when not connected");
|
|
176
|
-
}
|
|
177
|
-
this.messageHandlers.forEach((handler) => handler(message));
|
|
178
|
-
}
|
|
179
|
-
simulateError(error) {
|
|
180
|
-
this.errorHandlers.forEach((handler) => handler(error));
|
|
181
|
-
}
|
|
182
|
-
simulateClose(code, reason) {
|
|
183
|
-
this.state = "disconnected";
|
|
184
|
-
this.closeHandlers.forEach((handler) => handler(code, reason));
|
|
185
|
-
}
|
|
186
|
-
getMessageQueue() {
|
|
187
|
-
return [...this.messageQueue];
|
|
188
|
-
}
|
|
189
|
-
clearMessageQueue() {
|
|
190
|
-
this.messageQueue = [];
|
|
191
|
-
}
|
|
192
|
-
getConnectedUrl() {
|
|
193
|
-
return this.connectedUrl;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
// src/adapters/mock/MockStorageAdapter.ts
|
|
197
|
-
class MockStorageAdapter {
|
|
198
|
-
storage = new Map;
|
|
199
|
-
simulateLatency = 0;
|
|
200
|
-
shouldFailOperations = false;
|
|
201
|
-
constructor(options) {
|
|
202
|
-
this.simulateLatency = options?.simulateLatency ?? 0;
|
|
203
|
-
this.shouldFailOperations = options?.shouldFailOperations ?? false;
|
|
204
|
-
}
|
|
205
|
-
async get(key) {
|
|
206
|
-
await this.simulateDelay();
|
|
207
|
-
if (this.shouldFailOperations) {
|
|
208
|
-
throw new Error("Mock storage operation failed");
|
|
209
|
-
}
|
|
210
|
-
const item = this.storage.get(key);
|
|
211
|
-
if (!item)
|
|
212
|
-
return null;
|
|
213
|
-
if (item.expiresAt && item.expiresAt < Date.now()) {
|
|
214
|
-
this.storage.delete(key);
|
|
215
|
-
return null;
|
|
216
|
-
}
|
|
217
|
-
return item.value;
|
|
218
|
-
}
|
|
219
|
-
async set(key, value, ttl) {
|
|
220
|
-
await this.simulateDelay();
|
|
221
|
-
if (this.shouldFailOperations) {
|
|
222
|
-
throw new Error("Mock storage operation failed");
|
|
223
|
-
}
|
|
224
|
-
const item = {
|
|
225
|
-
value,
|
|
226
|
-
expiresAt: ttl ? Date.now() + ttl : undefined
|
|
227
|
-
};
|
|
228
|
-
this.storage.set(key, item);
|
|
229
|
-
}
|
|
230
|
-
async delete(key) {
|
|
231
|
-
await this.simulateDelay();
|
|
232
|
-
if (this.shouldFailOperations) {
|
|
233
|
-
throw new Error("Mock storage operation failed");
|
|
234
|
-
}
|
|
235
|
-
this.storage.delete(key);
|
|
236
|
-
}
|
|
237
|
-
async exists(key) {
|
|
238
|
-
await this.simulateDelay();
|
|
239
|
-
if (this.shouldFailOperations) {
|
|
240
|
-
throw new Error("Mock storage operation failed");
|
|
241
|
-
}
|
|
242
|
-
const item = this.storage.get(key);
|
|
243
|
-
if (!item)
|
|
244
|
-
return false;
|
|
245
|
-
if (item.expiresAt && item.expiresAt < Date.now()) {
|
|
246
|
-
this.storage.delete(key);
|
|
247
|
-
return false;
|
|
248
|
-
}
|
|
249
|
-
return true;
|
|
250
|
-
}
|
|
251
|
-
async scan(pattern) {
|
|
252
|
-
await this.simulateDelay();
|
|
253
|
-
if (this.shouldFailOperations) {
|
|
254
|
-
throw new Error("Mock storage operation failed");
|
|
255
|
-
}
|
|
256
|
-
const regexPattern = pattern.replace(/\*/g, ".*").replace(/\?/g, ".").replace(/\[!/g, "[^").replace(/\[/g, "[").replace(/\]/g, "]");
|
|
257
|
-
const regex = new RegExp(`^${regexPattern}$`);
|
|
258
|
-
const keys = [];
|
|
259
|
-
for (const [key, item] of this.storage.entries()) {
|
|
260
|
-
if (item.expiresAt && item.expiresAt < Date.now()) {
|
|
261
|
-
this.storage.delete(key);
|
|
262
|
-
continue;
|
|
263
|
-
}
|
|
264
|
-
if (regex.test(key)) {
|
|
265
|
-
keys.push(key);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
return keys;
|
|
269
|
-
}
|
|
270
|
-
async clear() {
|
|
271
|
-
await this.simulateDelay();
|
|
272
|
-
if (this.shouldFailOperations) {
|
|
273
|
-
throw new Error("Mock storage operation failed");
|
|
274
|
-
}
|
|
275
|
-
this.storage.clear();
|
|
276
|
-
}
|
|
277
|
-
async transaction(operations) {
|
|
278
|
-
await this.simulateDelay();
|
|
279
|
-
if (this.shouldFailOperations) {
|
|
280
|
-
throw new Error("Mock storage operation failed");
|
|
281
|
-
}
|
|
282
|
-
const results = [];
|
|
283
|
-
const rollback = [];
|
|
284
|
-
try {
|
|
285
|
-
for (const op of operations) {
|
|
286
|
-
switch (op.type) {
|
|
287
|
-
case "get": {
|
|
288
|
-
const value = await this.get(op.key);
|
|
289
|
-
results.push(value);
|
|
290
|
-
break;
|
|
291
|
-
}
|
|
292
|
-
case "set": {
|
|
293
|
-
const oldValue = await this.get(op.key);
|
|
294
|
-
await this.set(op.key, op.value, op.ttl);
|
|
295
|
-
rollback.push(() => {
|
|
296
|
-
if (oldValue !== null) {
|
|
297
|
-
this.storage.set(op.key, { value: oldValue });
|
|
298
|
-
} else {
|
|
299
|
-
this.storage.delete(op.key);
|
|
300
|
-
}
|
|
301
|
-
});
|
|
302
|
-
results.push(true);
|
|
303
|
-
break;
|
|
304
|
-
}
|
|
305
|
-
case "delete": {
|
|
306
|
-
const oldValue = await this.get(op.key);
|
|
307
|
-
await this.delete(op.key);
|
|
308
|
-
rollback.push(() => {
|
|
309
|
-
if (oldValue !== null) {
|
|
310
|
-
this.storage.set(op.key, { value: oldValue });
|
|
311
|
-
}
|
|
312
|
-
});
|
|
313
|
-
results.push(true);
|
|
314
|
-
break;
|
|
315
|
-
}
|
|
316
|
-
case "exists": {
|
|
317
|
-
const exists = await this.exists(op.key);
|
|
318
|
-
results.push(exists);
|
|
319
|
-
break;
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
return results;
|
|
324
|
-
} catch (error) {
|
|
325
|
-
for (const rollbackFn of rollback.reverse()) {
|
|
326
|
-
rollbackFn();
|
|
327
|
-
}
|
|
328
|
-
throw error;
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
async simulateDelay() {
|
|
332
|
-
if (this.simulateLatency > 0) {
|
|
333
|
-
await new Promise((resolve) => setTimeout(resolve, this.simulateLatency));
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
getSize() {
|
|
337
|
-
return this.storage.size;
|
|
338
|
-
}
|
|
339
|
-
getRawStorage() {
|
|
340
|
-
return new Map(this.storage);
|
|
341
|
-
}
|
|
342
|
-
setFailOperations(fail) {
|
|
343
|
-
this.shouldFailOperations = fail;
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
// src/adapters/mock/MockAuthAdapter.ts
|
|
347
|
-
class MockAuthAdapter {
|
|
348
|
-
tokens = new Map;
|
|
349
|
-
revokedTokens = new Set;
|
|
350
|
-
users = new Map;
|
|
351
|
-
simulateLatency = 0;
|
|
352
|
-
shouldFailAuth = false;
|
|
353
|
-
tokenCounter = 0;
|
|
354
|
-
constructor(options) {
|
|
355
|
-
this.simulateLatency = options?.simulateLatency ?? 0;
|
|
356
|
-
this.shouldFailAuth = options?.shouldFailAuth ?? false;
|
|
357
|
-
this.addUser("testuser", "password123", {
|
|
358
|
-
userId: "user-1",
|
|
359
|
-
email: "test@example.com",
|
|
360
|
-
username: "testuser",
|
|
361
|
-
permissions: ["read", "write"]
|
|
362
|
-
});
|
|
363
|
-
this.addUser("admin", "admin123", {
|
|
364
|
-
userId: "user-admin",
|
|
365
|
-
email: "admin@example.com",
|
|
366
|
-
username: "admin",
|
|
367
|
-
permissions: ["read", "write", "admin"]
|
|
368
|
-
});
|
|
369
|
-
}
|
|
370
|
-
async validateToken(token) {
|
|
371
|
-
await this.simulateDelay();
|
|
372
|
-
if (this.shouldFailAuth) {
|
|
373
|
-
throw new Error("Mock auth validation failed");
|
|
374
|
-
}
|
|
375
|
-
if (this.revokedTokens.has(token)) {
|
|
376
|
-
throw new Error("Token has been revoked");
|
|
377
|
-
}
|
|
378
|
-
const payload = this.tokens.get(token);
|
|
379
|
-
if (!payload) {
|
|
380
|
-
throw new Error("Invalid token");
|
|
381
|
-
}
|
|
382
|
-
if (payload.expiresAt && payload.expiresAt < Date.now()) {
|
|
383
|
-
this.tokens.delete(token);
|
|
384
|
-
throw new Error("Token expired");
|
|
385
|
-
}
|
|
386
|
-
return payload;
|
|
387
|
-
}
|
|
388
|
-
async generateToken(payload) {
|
|
389
|
-
await this.simulateDelay();
|
|
390
|
-
if (this.shouldFailAuth) {
|
|
391
|
-
throw new Error("Mock token generation failed");
|
|
392
|
-
}
|
|
393
|
-
const token = `mock-token-${++this.tokenCounter}`;
|
|
394
|
-
const expiresAt = payload.expiresAt ?? Date.now() + 3600000;
|
|
395
|
-
this.tokens.set(token, { ...payload, expiresAt });
|
|
396
|
-
return token;
|
|
397
|
-
}
|
|
398
|
-
async refreshToken(token) {
|
|
399
|
-
await this.simulateDelay();
|
|
400
|
-
if (this.shouldFailAuth) {
|
|
401
|
-
throw new Error("Mock token refresh failed");
|
|
402
|
-
}
|
|
403
|
-
const payload = await this.validateToken(token);
|
|
404
|
-
await this.revokeToken(token);
|
|
405
|
-
const newPayload = {
|
|
406
|
-
...payload,
|
|
407
|
-
expiresAt: Date.now() + 3600000
|
|
408
|
-
};
|
|
409
|
-
return this.generateToken(newPayload);
|
|
410
|
-
}
|
|
411
|
-
async revokeToken(token) {
|
|
412
|
-
await this.simulateDelay();
|
|
413
|
-
if (this.shouldFailAuth) {
|
|
414
|
-
throw new Error("Mock token revocation failed");
|
|
415
|
-
}
|
|
416
|
-
this.tokens.delete(token);
|
|
417
|
-
this.revokedTokens.add(token);
|
|
418
|
-
}
|
|
419
|
-
async authenticate(credentials) {
|
|
420
|
-
await this.simulateDelay();
|
|
421
|
-
if (this.shouldFailAuth) {
|
|
422
|
-
throw new Error("Mock authentication failed");
|
|
423
|
-
}
|
|
424
|
-
switch (credentials.type) {
|
|
425
|
-
case "password": {
|
|
426
|
-
if (!credentials.username || !credentials.password) {
|
|
427
|
-
throw new Error("Username and password required");
|
|
428
|
-
}
|
|
429
|
-
const user = this.users.get(credentials.username);
|
|
430
|
-
if (!user || user.password !== credentials.password) {
|
|
431
|
-
throw new Error("Invalid credentials");
|
|
432
|
-
}
|
|
433
|
-
const token = await this.generateToken(user.payload);
|
|
434
|
-
const refreshToken = `refresh-${token}`;
|
|
435
|
-
this.tokens.set(refreshToken, user.payload);
|
|
436
|
-
return {
|
|
437
|
-
token,
|
|
438
|
-
refreshToken,
|
|
439
|
-
user: user.payload,
|
|
440
|
-
expiresIn: 3600
|
|
441
|
-
};
|
|
442
|
-
}
|
|
443
|
-
case "token": {
|
|
444
|
-
if (!credentials.token) {
|
|
445
|
-
throw new Error("Token required");
|
|
446
|
-
}
|
|
447
|
-
const payload = await this.validateToken(credentials.token);
|
|
448
|
-
return {
|
|
449
|
-
token: credentials.token,
|
|
450
|
-
user: payload,
|
|
451
|
-
expiresIn: 3600
|
|
452
|
-
};
|
|
453
|
-
}
|
|
454
|
-
case "oauth": {
|
|
455
|
-
if (!credentials.code) {
|
|
456
|
-
throw new Error("OAuth code required");
|
|
457
|
-
}
|
|
458
|
-
const payload = {
|
|
459
|
-
userId: `oauth-user-${Date.now()}`,
|
|
460
|
-
email: "oauth@example.com",
|
|
461
|
-
username: "oauthuser",
|
|
462
|
-
permissions: ["read", "write"]
|
|
463
|
-
};
|
|
464
|
-
const token = await this.generateToken(payload);
|
|
465
|
-
return {
|
|
466
|
-
token,
|
|
467
|
-
user: payload,
|
|
468
|
-
expiresIn: 3600
|
|
469
|
-
};
|
|
470
|
-
}
|
|
471
|
-
default:
|
|
472
|
-
throw new Error("Unsupported credential type");
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
async simulateDelay() {
|
|
476
|
-
if (this.simulateLatency > 0) {
|
|
477
|
-
await new Promise((resolve) => setTimeout(resolve, this.simulateLatency));
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
addUser(username, password, payload) {
|
|
481
|
-
this.users.set(username, { password, payload });
|
|
482
|
-
}
|
|
483
|
-
removeUser(username) {
|
|
484
|
-
this.users.delete(username);
|
|
485
|
-
}
|
|
486
|
-
getTokenCount() {
|
|
487
|
-
return this.tokens.size;
|
|
488
|
-
}
|
|
489
|
-
getRevokedTokenCount() {
|
|
490
|
-
return this.revokedTokens.size;
|
|
491
|
-
}
|
|
492
|
-
clearTokens() {
|
|
493
|
-
this.tokens.clear();
|
|
494
|
-
this.revokedTokens.clear();
|
|
495
|
-
}
|
|
496
|
-
setFailAuth(fail) {
|
|
497
|
-
this.shouldFailAuth = fail;
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
// src/client/BaseClient.ts
|
|
501
|
-
class BaseClient extends TypedEventEmitter {
|
|
502
|
-
transport;
|
|
503
|
-
auth;
|
|
504
|
-
storage;
|
|
505
|
-
config;
|
|
506
|
-
connectionState = "disconnected";
|
|
507
|
-
currentRoomId = null;
|
|
508
|
-
currentRoomState = null;
|
|
509
|
-
authToken = null;
|
|
510
|
-
userId = null;
|
|
511
|
-
reconnectAttempts = 0;
|
|
512
|
-
reconnectTimer = null;
|
|
513
|
-
lastConnectionUrl = null;
|
|
514
|
-
lastCredentials = null;
|
|
515
|
-
constructor(config) {
|
|
516
|
-
super();
|
|
517
|
-
this.config = config;
|
|
518
|
-
this.transport = config.transport;
|
|
519
|
-
this.auth = config.auth;
|
|
520
|
-
this.storage = config.storage;
|
|
521
|
-
this.transport.onMessage(this.handleMessage.bind(this));
|
|
522
|
-
this.transport.onError(this.handleError.bind(this));
|
|
523
|
-
this.transport.onClose(this.handleClose.bind(this));
|
|
524
|
-
}
|
|
525
|
-
async connect(url, credentials) {
|
|
526
|
-
if (this.connectionState !== "disconnected") {
|
|
527
|
-
throw new Error("Client is already connected or connecting");
|
|
528
|
-
}
|
|
529
|
-
this.connectionState = "connecting";
|
|
530
|
-
this.lastConnectionUrl = url;
|
|
531
|
-
this.lastCredentials = credentials || null;
|
|
532
|
-
try {
|
|
533
|
-
if (credentials && this.auth) {
|
|
534
|
-
const authResult = await this.auth.authenticate(credentials);
|
|
535
|
-
this.authToken = authResult.token;
|
|
536
|
-
this.userId = authResult.user.userId;
|
|
537
|
-
}
|
|
538
|
-
const options = {
|
|
539
|
-
url,
|
|
540
|
-
reconnect: false,
|
|
541
|
-
...this.config.reconnection
|
|
542
|
-
};
|
|
543
|
-
await this.transport.connect(url, options);
|
|
544
|
-
this.connectionState = "connected";
|
|
545
|
-
await this.emit("connected", { url });
|
|
546
|
-
} catch (error) {
|
|
547
|
-
this.connectionState = "disconnected";
|
|
548
|
-
await this.emit("error", { error });
|
|
549
|
-
throw error;
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
async disconnect() {
|
|
553
|
-
if (this.connectionState === "disconnected") {
|
|
554
|
-
return;
|
|
555
|
-
}
|
|
556
|
-
this.connectionState = "disconnecting";
|
|
557
|
-
if (this.reconnectTimer) {
|
|
558
|
-
clearTimeout(this.reconnectTimer);
|
|
559
|
-
this.reconnectTimer = null;
|
|
560
|
-
}
|
|
561
|
-
if (this.currentRoomId) {
|
|
562
|
-
await this.leaveRoom();
|
|
563
|
-
}
|
|
564
|
-
try {
|
|
565
|
-
await this.transport.disconnect();
|
|
566
|
-
} finally {
|
|
567
|
-
this.connectionState = "disconnected";
|
|
568
|
-
this.currentRoomId = null;
|
|
569
|
-
this.currentRoomState = null;
|
|
570
|
-
this.authToken = null;
|
|
571
|
-
this.userId = null;
|
|
572
|
-
this.reconnectAttempts = 0;
|
|
573
|
-
await this.emit("disconnected", { code: 1000, reason: "Client disconnected" });
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
async reconnect() {
|
|
577
|
-
if (!this.lastConnectionUrl) {
|
|
578
|
-
throw new Error("No previous connection to reconnect to");
|
|
579
|
-
}
|
|
580
|
-
if (this.connectionState !== "disconnected") {
|
|
581
|
-
await this.disconnect();
|
|
582
|
-
}
|
|
583
|
-
await this.connect(this.lastConnectionUrl, this.lastCredentials || undefined);
|
|
584
|
-
}
|
|
585
|
-
async joinRoom(roomId) {
|
|
586
|
-
if (this.connectionState !== "connected") {
|
|
587
|
-
throw new Error("Client must be connected to join a room");
|
|
588
|
-
}
|
|
589
|
-
if (this.currentRoomId) {
|
|
590
|
-
await this.leaveRoom();
|
|
591
|
-
}
|
|
592
|
-
const message = {
|
|
593
|
-
id: this.generateId(),
|
|
594
|
-
type: "join_room",
|
|
595
|
-
payload: { roomId, token: this.authToken },
|
|
596
|
-
timestamp: Date.now()
|
|
597
|
-
};
|
|
598
|
-
await this.transport.send(message);
|
|
599
|
-
}
|
|
600
|
-
async leaveRoom() {
|
|
601
|
-
if (!this.currentRoomId) {
|
|
602
|
-
return;
|
|
603
|
-
}
|
|
604
|
-
const message = {
|
|
605
|
-
id: this.generateId(),
|
|
606
|
-
type: "leave_room",
|
|
607
|
-
payload: { roomId: this.currentRoomId },
|
|
608
|
-
timestamp: Date.now()
|
|
609
|
-
};
|
|
610
|
-
await this.transport.send(message);
|
|
611
|
-
this.currentRoomId = null;
|
|
612
|
-
this.currentRoomState = null;
|
|
613
|
-
}
|
|
614
|
-
async broadcast(event) {
|
|
615
|
-
if (this.connectionState !== "connected" || !this.currentRoomId) {
|
|
616
|
-
throw new Error("Client must be connected and in a room to broadcast events");
|
|
617
|
-
}
|
|
618
|
-
const message = {
|
|
619
|
-
id: this.generateId(),
|
|
620
|
-
type: "broadcast_event",
|
|
621
|
-
payload: { event, roomId: this.currentRoomId },
|
|
622
|
-
timestamp: Date.now()
|
|
623
|
-
};
|
|
624
|
-
await this.transport.send(message);
|
|
625
|
-
}
|
|
626
|
-
async requestLock(request) {
|
|
627
|
-
if (this.connectionState !== "connected" || !this.currentRoomId) {
|
|
628
|
-
throw new Error("Client must be connected and in a room to request locks");
|
|
629
|
-
}
|
|
630
|
-
const message = {
|
|
631
|
-
id: this.generateId(),
|
|
632
|
-
type: "request_lock",
|
|
633
|
-
payload: { request, roomId: this.currentRoomId },
|
|
634
|
-
timestamp: Date.now()
|
|
635
|
-
};
|
|
636
|
-
await this.transport.send(message);
|
|
637
|
-
}
|
|
638
|
-
async releaseLock(lockId) {
|
|
639
|
-
if (this.connectionState !== "connected" || !this.currentRoomId) {
|
|
640
|
-
throw new Error("Client must be connected and in a room to release locks");
|
|
641
|
-
}
|
|
642
|
-
const message = {
|
|
643
|
-
id: this.generateId(),
|
|
644
|
-
type: "release_lock",
|
|
645
|
-
payload: { lockId, roomId: this.currentRoomId },
|
|
646
|
-
timestamp: Date.now()
|
|
647
|
-
};
|
|
648
|
-
await this.transport.send(message);
|
|
649
|
-
}
|
|
650
|
-
getConnectionState() {
|
|
651
|
-
return this.connectionState;
|
|
652
|
-
}
|
|
653
|
-
getRoomState() {
|
|
654
|
-
return this.currentRoomState;
|
|
655
|
-
}
|
|
656
|
-
getPresence() {
|
|
657
|
-
return this.currentRoomState ? Array.from(this.currentRoomState.users.values()) : [];
|
|
658
|
-
}
|
|
659
|
-
getCurrentRoomId() {
|
|
660
|
-
return this.currentRoomId;
|
|
661
|
-
}
|
|
662
|
-
getUserId() {
|
|
663
|
-
return this.userId;
|
|
664
|
-
}
|
|
665
|
-
async handleMessage(message) {
|
|
666
|
-
try {
|
|
667
|
-
switch (message.type) {
|
|
668
|
-
case "room_joined":
|
|
669
|
-
await this.handleRoomJoined(message.payload);
|
|
670
|
-
break;
|
|
671
|
-
case "room_left":
|
|
672
|
-
await this.handleRoomLeft(message.payload);
|
|
673
|
-
break;
|
|
674
|
-
case "event_broadcast":
|
|
675
|
-
await this.handleEventBroadcast(message.payload);
|
|
676
|
-
break;
|
|
677
|
-
case "lock_acquired":
|
|
678
|
-
await this.handleLockAcquired(message.payload);
|
|
679
|
-
break;
|
|
680
|
-
case "lock_released":
|
|
681
|
-
await this.handleLockReleased(message.payload);
|
|
682
|
-
break;
|
|
683
|
-
case "lock_denied":
|
|
684
|
-
await this.handleLockDenied(message.payload);
|
|
685
|
-
break;
|
|
686
|
-
case "presence_updated":
|
|
687
|
-
await this.handlePresenceUpdated(message.payload);
|
|
688
|
-
break;
|
|
689
|
-
case "error":
|
|
690
|
-
await this.handleServerError(message.payload);
|
|
691
|
-
break;
|
|
692
|
-
}
|
|
693
|
-
} catch (error) {
|
|
694
|
-
await this.emit("error", { error });
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
async handleError(error) {
|
|
698
|
-
await this.emit("error", { error });
|
|
699
|
-
}
|
|
700
|
-
async handleClose(code, reason) {
|
|
701
|
-
const wasConnected = this.connectionState === "connected";
|
|
702
|
-
this.connectionState = "disconnected";
|
|
703
|
-
if (wasConnected) {
|
|
704
|
-
await this.emit("disconnected", { code, reason });
|
|
705
|
-
if (this.config.reconnection?.enabled && this.lastConnectionUrl) {
|
|
706
|
-
this.scheduleReconnect();
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
async handleRoomJoined(payload) {
|
|
711
|
-
this.currentRoomId = payload.roomId;
|
|
712
|
-
this.currentRoomState = payload.state;
|
|
713
|
-
await this.emit("room_joined", payload);
|
|
714
|
-
}
|
|
715
|
-
async handleRoomLeft(payload) {
|
|
716
|
-
if (this.currentRoomId === payload.roomId) {
|
|
717
|
-
this.currentRoomId = null;
|
|
718
|
-
this.currentRoomState = null;
|
|
719
|
-
}
|
|
720
|
-
await this.emit("room_left", payload);
|
|
721
|
-
}
|
|
722
|
-
async handleEventBroadcast(payload) {
|
|
723
|
-
await this.emit("event_received", payload);
|
|
724
|
-
}
|
|
725
|
-
async handleLockAcquired(payload) {
|
|
726
|
-
await this.emit("lock_acquired", payload);
|
|
727
|
-
}
|
|
728
|
-
async handleLockReleased(payload) {
|
|
729
|
-
await this.emit("lock_released", payload);
|
|
730
|
-
}
|
|
731
|
-
async handleLockDenied(payload) {
|
|
732
|
-
await this.emit("lock_denied", payload);
|
|
733
|
-
}
|
|
734
|
-
async handlePresenceUpdated(payload) {
|
|
735
|
-
if (this.currentRoomState) {
|
|
736
|
-
this.currentRoomState.users = new Map(payload.users.map((user) => [user.id, user]));
|
|
737
|
-
}
|
|
738
|
-
await this.emit("presence_updated", payload);
|
|
739
|
-
}
|
|
740
|
-
async handleServerError(payload) {
|
|
741
|
-
const error = new Error(payload.error);
|
|
742
|
-
await this.emit("error", { error });
|
|
743
|
-
}
|
|
744
|
-
async scheduleReconnect() {
|
|
745
|
-
if (!this.config.reconnection || this.reconnectAttempts >= this.config.reconnection.maxAttempts) {
|
|
746
|
-
return;
|
|
747
|
-
}
|
|
748
|
-
const delay = Math.min(this.config.reconnection.initialDelay * Math.pow(this.config.reconnection.backoffFactor, this.reconnectAttempts), this.config.reconnection.maxDelay);
|
|
749
|
-
this.reconnectAttempts++;
|
|
750
|
-
await this.emit("reconnecting", { attempt: this.reconnectAttempts, delay });
|
|
751
|
-
this.reconnectTimer = setTimeout(async () => {
|
|
752
|
-
try {
|
|
753
|
-
await this.reconnect();
|
|
754
|
-
this.reconnectAttempts = 0;
|
|
755
|
-
await this.emit("reconnected", { url: this.lastConnectionUrl });
|
|
756
|
-
} catch {
|
|
757
|
-
this.scheduleReconnect();
|
|
758
|
-
}
|
|
759
|
-
}, delay);
|
|
760
|
-
}
|
|
761
|
-
generateId() {
|
|
762
|
-
return Math.random().toString(36).substring(2) + Date.now().toString(36);
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
// src/client/ClientBuilder.ts
|
|
766
|
-
class ClientBuilder {
|
|
767
|
-
transport;
|
|
768
|
-
auth;
|
|
769
|
-
storage;
|
|
770
|
-
reconnection;
|
|
771
|
-
withTransport(transport) {
|
|
772
|
-
this.transport = transport;
|
|
773
|
-
return this;
|
|
774
|
-
}
|
|
775
|
-
withAuth(auth) {
|
|
776
|
-
this.auth = auth;
|
|
777
|
-
return this;
|
|
778
|
-
}
|
|
779
|
-
withStorage(storage) {
|
|
780
|
-
this.storage = storage;
|
|
781
|
-
return this;
|
|
782
|
-
}
|
|
783
|
-
withReconnection(config) {
|
|
784
|
-
this.reconnection = config;
|
|
785
|
-
return this;
|
|
786
|
-
}
|
|
787
|
-
build() {
|
|
788
|
-
if (!this.transport) {
|
|
789
|
-
throw new Error("Transport adapter is required");
|
|
790
|
-
}
|
|
791
|
-
const config = {
|
|
792
|
-
transport: this.transport,
|
|
793
|
-
auth: this.auth,
|
|
794
|
-
storage: this.storage,
|
|
795
|
-
reconnection: this.reconnection || {
|
|
796
|
-
enabled: true,
|
|
797
|
-
maxAttempts: 5,
|
|
798
|
-
initialDelay: 1000,
|
|
799
|
-
maxDelay: 30000,
|
|
800
|
-
backoffFactor: 2
|
|
801
|
-
}
|
|
802
|
-
};
|
|
803
|
-
return new BaseClient(config);
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
// src/server/BaseServer.ts
|
|
807
|
-
class BaseServer extends TypedEventEmitter {
|
|
808
|
-
transport;
|
|
809
|
-
auth;
|
|
810
|
-
storage;
|
|
811
|
-
roomManager;
|
|
812
|
-
lockManager;
|
|
813
|
-
config;
|
|
814
|
-
clients = new Map;
|
|
815
|
-
clientMessageHandlers = new Map;
|
|
816
|
-
running = false;
|
|
817
|
-
constructor(config) {
|
|
818
|
-
super();
|
|
819
|
-
this.config = config;
|
|
820
|
-
this.transport = config.transport;
|
|
821
|
-
this.auth = config.auth;
|
|
822
|
-
this.storage = config.storage;
|
|
823
|
-
this.roomManager = config.roomManager;
|
|
824
|
-
this.lockManager = config.lockManager;
|
|
825
|
-
this.transport.onMessage(this.handleTransportMessage.bind(this));
|
|
826
|
-
this.transport.onError(this.handleTransportError.bind(this));
|
|
827
|
-
this.transport.onClose(this.handleTransportClose.bind(this));
|
|
828
|
-
}
|
|
829
|
-
async start(port) {
|
|
830
|
-
if (this.running) {
|
|
831
|
-
throw new Error("Server is already running");
|
|
832
|
-
}
|
|
833
|
-
try {
|
|
834
|
-
await this.transport.connect(`ws://localhost:${port}`, { url: `ws://localhost:${port}` });
|
|
835
|
-
this.running = true;
|
|
836
|
-
await this.emit("started", { port });
|
|
837
|
-
} catch (error) {
|
|
838
|
-
await this.emit("error", { error, context: "server_start" });
|
|
839
|
-
throw error;
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
async stop() {
|
|
843
|
-
if (!this.running) {
|
|
844
|
-
return;
|
|
845
|
-
}
|
|
846
|
-
this.running = false;
|
|
847
|
-
for (const [clientId] of Array.from(this.clients)) {
|
|
848
|
-
await this.disconnectClient(clientId, "Server shutting down");
|
|
849
|
-
}
|
|
850
|
-
try {
|
|
851
|
-
await this.transport.disconnect();
|
|
852
|
-
} catch (error) {
|
|
853
|
-
await this.emit("error", { error, context: "server_stop" });
|
|
854
|
-
}
|
|
855
|
-
this.clients.clear();
|
|
856
|
-
this.clientMessageHandlers.clear();
|
|
857
|
-
await this.emit("stopped", {});
|
|
858
|
-
}
|
|
859
|
-
async handleTransportMessage(message) {
|
|
860
|
-
const payload = message.payload;
|
|
861
|
-
const { clientId, ...clientMessage } = payload;
|
|
862
|
-
const clientMsg = {
|
|
863
|
-
id: message.id,
|
|
864
|
-
type: message.type,
|
|
865
|
-
payload: clientMessage,
|
|
866
|
-
timestamp: message.timestamp
|
|
867
|
-
};
|
|
868
|
-
if (!clientId) {
|
|
869
|
-
await this.emit("error", {
|
|
870
|
-
error: new Error("Message missing clientId"),
|
|
871
|
-
context: "transport_message"
|
|
872
|
-
});
|
|
873
|
-
return;
|
|
874
|
-
}
|
|
875
|
-
const handler = this.clientMessageHandlers.get(clientId);
|
|
876
|
-
if (handler) {
|
|
877
|
-
await handler(clientMsg);
|
|
878
|
-
} else {
|
|
879
|
-
await this.emit("error", {
|
|
880
|
-
error: new Error(`No handler for client ${clientId}`),
|
|
881
|
-
context: "transport_message"
|
|
882
|
-
});
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
async handleTransportError(error) {
|
|
886
|
-
await this.emit("error", { error, context: "transport" });
|
|
887
|
-
}
|
|
888
|
-
async handleTransportClose(_code, _reason) {
|
|
889
|
-
if (this.running) {
|
|
890
|
-
await this.stop();
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
async addClient(clientId) {
|
|
894
|
-
const client = {
|
|
895
|
-
id: clientId,
|
|
896
|
-
userId: "",
|
|
897
|
-
roomId: null,
|
|
898
|
-
authenticated: false,
|
|
899
|
-
connectedAt: Date.now()
|
|
900
|
-
};
|
|
901
|
-
this.clients.set(clientId, client);
|
|
902
|
-
this.clientMessageHandlers.set(clientId, this.createClientMessageHandler(clientId));
|
|
903
|
-
await this.emit("client_connected", { client });
|
|
904
|
-
}
|
|
905
|
-
async disconnectClient(clientId, reason) {
|
|
906
|
-
const client = this.clients.get(clientId);
|
|
907
|
-
if (!client) {
|
|
908
|
-
return;
|
|
909
|
-
}
|
|
910
|
-
if (client.roomId) {
|
|
911
|
-
await this.roomManager.leaveRoom(client.roomId, client.userId);
|
|
912
|
-
await this.emit("client_left_room", { clientId, roomId: client.roomId });
|
|
913
|
-
}
|
|
914
|
-
await this.lockManager.releaseUserLocks(client.userId);
|
|
915
|
-
this.clients.delete(clientId);
|
|
916
|
-
this.clientMessageHandlers.delete(clientId);
|
|
917
|
-
await this.emit("client_disconnected", { clientId, reason });
|
|
918
|
-
}
|
|
919
|
-
createClientMessageHandler(clientId) {
|
|
920
|
-
return async (message) => {
|
|
921
|
-
try {
|
|
922
|
-
const client = this.clients.get(clientId);
|
|
923
|
-
if (!client) {
|
|
924
|
-
return;
|
|
925
|
-
}
|
|
926
|
-
switch (message.type) {
|
|
927
|
-
case "authenticate":
|
|
928
|
-
await this.handleAuthenticate(clientId, message.payload);
|
|
929
|
-
break;
|
|
930
|
-
case "join_room":
|
|
931
|
-
await this.handleJoinRoom(clientId, message.payload);
|
|
932
|
-
break;
|
|
933
|
-
case "leave_room":
|
|
934
|
-
await this.handleLeaveRoom(clientId);
|
|
935
|
-
break;
|
|
936
|
-
case "broadcast_event":
|
|
937
|
-
await this.handleBroadcastEvent(clientId, message.payload);
|
|
938
|
-
break;
|
|
939
|
-
case "request_lock":
|
|
940
|
-
await this.handleLockRequest(clientId, message.payload);
|
|
941
|
-
break;
|
|
942
|
-
case "release_lock":
|
|
943
|
-
await this.handleLockRelease(clientId, message.payload);
|
|
944
|
-
break;
|
|
945
|
-
case "ping":
|
|
946
|
-
await this.sendToClient(clientId, { type: "pong", timestamp: Date.now() });
|
|
947
|
-
break;
|
|
948
|
-
default:
|
|
949
|
-
await this.sendToClient(clientId, {
|
|
950
|
-
type: "error",
|
|
951
|
-
error: `Unknown message type: ${message.type}`
|
|
952
|
-
});
|
|
953
|
-
}
|
|
954
|
-
} catch (error) {
|
|
955
|
-
await this.sendToClient(clientId, {
|
|
956
|
-
type: "error",
|
|
957
|
-
error: error.message
|
|
958
|
-
});
|
|
959
|
-
await this.emit("error", { error, context: `client_${clientId}` });
|
|
960
|
-
}
|
|
961
|
-
};
|
|
962
|
-
}
|
|
963
|
-
async handleAuthenticate(clientId, payload) {
|
|
964
|
-
if (!this.auth) {
|
|
965
|
-
await this.sendToClient(clientId, { type: "auth_result", success: false, error: "Authentication not configured" });
|
|
966
|
-
return;
|
|
967
|
-
}
|
|
968
|
-
try {
|
|
969
|
-
const tokenPayload = await this.auth.validateToken(payload.token);
|
|
970
|
-
const client = this.clients.get(clientId);
|
|
971
|
-
if (client) {
|
|
972
|
-
client.userId = tokenPayload.userId;
|
|
973
|
-
client.authenticated = true;
|
|
974
|
-
await this.emit("client_authenticated", { clientId, userId: tokenPayload.userId });
|
|
975
|
-
}
|
|
976
|
-
await this.sendToClient(clientId, { type: "auth_result", success: true });
|
|
977
|
-
} catch (error) {
|
|
978
|
-
await this.sendToClient(clientId, { type: "auth_result", success: false, error: error.message });
|
|
979
|
-
}
|
|
980
|
-
}
|
|
981
|
-
async handleJoinRoom(clientId, payload) {
|
|
982
|
-
const client = this.clients.get(clientId);
|
|
983
|
-
if (!client) {
|
|
984
|
-
return;
|
|
985
|
-
}
|
|
986
|
-
if (payload.token && !client.authenticated) {
|
|
987
|
-
await this.handleAuthenticate(clientId, { token: payload.token });
|
|
988
|
-
}
|
|
989
|
-
if (!client.authenticated) {
|
|
990
|
-
await this.sendToClient(clientId, { type: "error", error: "Authentication required to join room" });
|
|
991
|
-
return;
|
|
992
|
-
}
|
|
993
|
-
try {
|
|
994
|
-
if (client.roomId) {
|
|
995
|
-
await this.roomManager.leaveRoom(client.roomId, client.userId);
|
|
996
|
-
await this.emit("client_left_room", { clientId, roomId: client.roomId });
|
|
997
|
-
}
|
|
998
|
-
let roomState = await this.roomManager.getRoomState(payload.roomId);
|
|
999
|
-
if (!roomState) {
|
|
1000
|
-
const roomConfig = { ...this.config.defaultRoomConfig, id: payload.roomId };
|
|
1001
|
-
const room = await this.roomManager.createRoom(payload.roomId, roomConfig);
|
|
1002
|
-
roomState = await this.roomManager.getRoomState(payload.roomId);
|
|
1003
|
-
await this.emit("room_created", { room });
|
|
1004
|
-
}
|
|
1005
|
-
if (!roomState) {
|
|
1006
|
-
throw new Error("Failed to create or get room state");
|
|
1007
|
-
}
|
|
1008
|
-
const user = {
|
|
1009
|
-
id: client.userId,
|
|
1010
|
-
username: client.userId,
|
|
1011
|
-
status: "online",
|
|
1012
|
-
joinedAt: Date.now(),
|
|
1013
|
-
lastActivity: Date.now(),
|
|
1014
|
-
permissions: roomState.room.permissions || ["read", "write"]
|
|
1015
|
-
};
|
|
1016
|
-
await this.roomManager.joinRoom(payload.roomId, user);
|
|
1017
|
-
client.roomId = payload.roomId;
|
|
1018
|
-
await this.emit("client_joined_room", { clientId, roomId: payload.roomId });
|
|
1019
|
-
await this.sendToClient(clientId, {
|
|
1020
|
-
type: "room_joined",
|
|
1021
|
-
roomId: payload.roomId,
|
|
1022
|
-
state: roomState
|
|
1023
|
-
});
|
|
1024
|
-
const history = await this.roomManager.getEventHistory(payload.roomId, 50);
|
|
1025
|
-
if (history.length > 0) {
|
|
1026
|
-
await this.sendToClient(clientId, {
|
|
1027
|
-
type: "event_history",
|
|
1028
|
-
events: history
|
|
1029
|
-
});
|
|
1030
|
-
}
|
|
1031
|
-
} catch (error) {
|
|
1032
|
-
await this.sendToClient(clientId, { type: "error", error: error.message });
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
async handleLeaveRoom(clientId) {
|
|
1036
|
-
const client = this.clients.get(clientId);
|
|
1037
|
-
if (!client || !client.roomId) {
|
|
1038
|
-
return;
|
|
1039
|
-
}
|
|
1040
|
-
const roomId = client.roomId;
|
|
1041
|
-
await this.roomManager.leaveRoom(roomId, client.userId);
|
|
1042
|
-
client.roomId = null;
|
|
1043
|
-
await this.emit("client_left_room", { clientId, roomId });
|
|
1044
|
-
await this.sendToClient(clientId, { type: "room_left", roomId });
|
|
1045
|
-
}
|
|
1046
|
-
async handleBroadcastEvent(clientId, payload) {
|
|
1047
|
-
const client = this.clients.get(clientId);
|
|
1048
|
-
if (!client || client.roomId !== payload.roomId) {
|
|
1049
|
-
await this.sendToClient(clientId, { type: "error", error: "Not in specified room" });
|
|
1050
|
-
return;
|
|
1051
|
-
}
|
|
1052
|
-
const enrichedEvent = {
|
|
1053
|
-
...payload.event,
|
|
1054
|
-
metadata: {
|
|
1055
|
-
...payload.event.metadata,
|
|
1056
|
-
userId: client.userId,
|
|
1057
|
-
timestamp: Date.now(),
|
|
1058
|
-
roomId: payload.roomId
|
|
1059
|
-
}
|
|
1060
|
-
};
|
|
1061
|
-
await this.roomManager.addEventToHistory(payload.roomId, enrichedEvent);
|
|
1062
|
-
await this.roomManager.broadcastToRoom(payload.roomId, enrichedEvent, client.userId);
|
|
1063
|
-
await this.emit("event_broadcast", {
|
|
1064
|
-
roomId: payload.roomId,
|
|
1065
|
-
event: enrichedEvent,
|
|
1066
|
-
fromClientId: clientId
|
|
1067
|
-
});
|
|
1068
|
-
}
|
|
1069
|
-
async handleLockRequest(clientId, payload) {
|
|
1070
|
-
const client = this.clients.get(clientId);
|
|
1071
|
-
if (!client || client.roomId !== payload.roomId) {
|
|
1072
|
-
await this.sendToClient(clientId, { type: "error", error: "Not in specified room" });
|
|
1073
|
-
return;
|
|
1074
|
-
}
|
|
1075
|
-
try {
|
|
1076
|
-
const lock = await this.lockManager.acquireLock(client.userId, client.userId, payload.request);
|
|
1077
|
-
await this.emit("lock_acquired", { lock, clientId });
|
|
1078
|
-
await this.sendToClient(clientId, { type: "lock_acquired", lock });
|
|
1079
|
-
await this.roomManager.broadcastToRoom(payload.roomId, {
|
|
1080
|
-
id: this.generateId(),
|
|
1081
|
-
type: "lock_status",
|
|
1082
|
-
timestamp: Date.now(),
|
|
1083
|
-
userId: client.userId,
|
|
1084
|
-
roomId: payload.roomId,
|
|
1085
|
-
data: { lock, action: "acquired" },
|
|
1086
|
-
metadata: { userId: client.userId, timestamp: Date.now(), roomId: payload.roomId }
|
|
1087
|
-
});
|
|
1088
|
-
} catch (error) {
|
|
1089
|
-
await this.sendToClient(clientId, { type: "lock_denied", request: payload.request, reason: error.message });
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1092
|
-
async handleLockRelease(clientId, payload) {
|
|
1093
|
-
const client = this.clients.get(clientId);
|
|
1094
|
-
if (!client) {
|
|
1095
|
-
return;
|
|
1096
|
-
}
|
|
1097
|
-
try {
|
|
1098
|
-
await this.lockManager.releaseLock(payload.lockId);
|
|
1099
|
-
await this.emit("lock_released", { lockId: payload.lockId, clientId });
|
|
1100
|
-
await this.sendToClient(clientId, { type: "lock_released", lockId: payload.lockId });
|
|
1101
|
-
if (client.roomId) {
|
|
1102
|
-
await this.roomManager.broadcastToRoom(client.roomId, {
|
|
1103
|
-
id: this.generateId(),
|
|
1104
|
-
type: "lock_status",
|
|
1105
|
-
timestamp: Date.now(),
|
|
1106
|
-
userId: client.userId,
|
|
1107
|
-
roomId: client.roomId,
|
|
1108
|
-
data: { lockId: payload.lockId, action: "released" },
|
|
1109
|
-
metadata: { userId: client.userId, timestamp: Date.now(), roomId: client.roomId }
|
|
1110
|
-
});
|
|
1111
|
-
}
|
|
1112
|
-
} catch (error) {
|
|
1113
|
-
await this.sendToClient(clientId, { type: "error", error: error.message });
|
|
1114
|
-
}
|
|
1115
|
-
}
|
|
1116
|
-
async sendToClient(clientId, message) {
|
|
1117
|
-
const transportMessage = {
|
|
1118
|
-
id: this.generateId(),
|
|
1119
|
-
type: "server_message",
|
|
1120
|
-
payload: { clientId, ...message },
|
|
1121
|
-
timestamp: Date.now()
|
|
1122
|
-
};
|
|
1123
|
-
await this.transport.send(transportMessage);
|
|
1124
|
-
}
|
|
1125
|
-
generateId() {
|
|
1126
|
-
return Math.random().toString(36).substring(2) + Date.now().toString(36);
|
|
1127
|
-
}
|
|
1128
|
-
async createRoom(roomId, config) {
|
|
1129
|
-
const room = await this.roomManager.createRoom(roomId, config);
|
|
1130
|
-
await this.emit("room_created", { room });
|
|
1131
|
-
return room;
|
|
1132
|
-
}
|
|
1133
|
-
async deleteRoom(roomId) {
|
|
1134
|
-
await this.roomManager.deleteRoom(roomId);
|
|
1135
|
-
await this.emit("room_deleted", { roomId });
|
|
1136
|
-
}
|
|
1137
|
-
getConnectedClients() {
|
|
1138
|
-
return Array.from(this.clients.values());
|
|
1139
|
-
}
|
|
1140
|
-
getClient(clientId) {
|
|
1141
|
-
return this.clients.get(clientId) || null;
|
|
1142
|
-
}
|
|
1143
|
-
isRunning() {
|
|
1144
|
-
return this.running;
|
|
1145
|
-
}
|
|
1146
|
-
}
|
|
1147
|
-
// src/server/ServerBuilder.ts
|
|
1148
|
-
class ServerBuilder {
|
|
1149
|
-
transport;
|
|
1150
|
-
auth;
|
|
1151
|
-
storage;
|
|
1152
|
-
roomManager;
|
|
1153
|
-
lockManager;
|
|
1154
|
-
defaultRoomConfig;
|
|
1155
|
-
withTransport(transport) {
|
|
1156
|
-
this.transport = transport;
|
|
1157
|
-
return this;
|
|
1158
|
-
}
|
|
1159
|
-
withAuth(auth) {
|
|
1160
|
-
this.auth = auth;
|
|
1161
|
-
return this;
|
|
1162
|
-
}
|
|
1163
|
-
withStorage(storage) {
|
|
1164
|
-
this.storage = storage;
|
|
1165
|
-
return this;
|
|
1166
|
-
}
|
|
1167
|
-
withRoomManager(roomManager) {
|
|
1168
|
-
this.roomManager = roomManager;
|
|
1169
|
-
return this;
|
|
1170
|
-
}
|
|
1171
|
-
withLockManager(lockManager) {
|
|
1172
|
-
this.lockManager = lockManager;
|
|
1173
|
-
return this;
|
|
1174
|
-
}
|
|
1175
|
-
withDefaultRoomConfig(config) {
|
|
1176
|
-
this.defaultRoomConfig = config;
|
|
1177
|
-
return this;
|
|
1178
|
-
}
|
|
1179
|
-
build() {
|
|
1180
|
-
if (!this.transport) {
|
|
1181
|
-
throw new Error("Transport adapter is required");
|
|
1182
|
-
}
|
|
1183
|
-
if (!this.roomManager) {
|
|
1184
|
-
throw new Error("Room manager is required");
|
|
1185
|
-
}
|
|
1186
|
-
if (!this.lockManager) {
|
|
1187
|
-
throw new Error("Lock manager is required");
|
|
1188
|
-
}
|
|
1189
|
-
const config = {
|
|
1190
|
-
transport: this.transport,
|
|
1191
|
-
auth: this.auth,
|
|
1192
|
-
storage: this.storage,
|
|
1193
|
-
roomManager: this.roomManager,
|
|
1194
|
-
lockManager: this.lockManager,
|
|
1195
|
-
defaultRoomConfig: this.defaultRoomConfig || {
|
|
1196
|
-
maxUsers: 50,
|
|
1197
|
-
maxHistory: 100,
|
|
1198
|
-
permissions: ["read", "write"]
|
|
1199
|
-
}
|
|
1200
|
-
};
|
|
1201
|
-
return new BaseServer(config);
|
|
1202
|
-
}
|
|
1203
|
-
}
|
|
1204
|
-
|
|
1205
|
-
//# debugId=3AB78025BA51886B64756E2164756E21
|
|
1
|
+
// Abstractions
|
|
2
|
+
export { TypedEventEmitter, RoomManager, LockManager, DefaultRoomManager, DefaultLockManager } from './abstractions';
|
|
3
|
+
// Adapters
|
|
4
|
+
export { MockTransportAdapter, MockStorageAdapter, MockAuthAdapter } from './adapters/mock';
|
|
5
|
+
export { WebSocketTransportAdapter } from './adapters/websocket';
|
|
6
|
+
// Client
|
|
7
|
+
export { BaseClient, ClientBuilder } from './client';
|
|
8
|
+
// Server
|
|
9
|
+
export { BaseServer, ServerBuilder } from './server';
|