@principal-ai/control-tower-core 0.1.25 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/abstractions/AuthAdapter.d.ts +1 -1
- package/dist/abstractions/AuthAdapter.d.ts.map +1 -1
- package/dist/abstractions/DefaultLockManager.d.ts +2 -2
- package/dist/abstractions/DefaultLockManager.d.ts.map +1 -1
- package/dist/abstractions/DefaultLockManager.js +7 -8
- package/dist/abstractions/DefaultPresenceManager.d.ts +4 -4
- package/dist/abstractions/DefaultPresenceManager.d.ts.map +1 -1
- package/dist/abstractions/DefaultPresenceManager.js +25 -25
- package/dist/abstractions/DefaultRoomManager.d.ts +3 -3
- package/dist/abstractions/DefaultRoomManager.d.ts.map +1 -1
- package/dist/abstractions/DefaultRoomManager.js +6 -4
- package/dist/abstractions/EventEmitter.d.ts.map +1 -1
- package/dist/abstractions/LockManager.d.ts +1 -1
- package/dist/abstractions/LockManager.d.ts.map +1 -1
- package/dist/abstractions/LockManager.js +1 -1
- package/dist/abstractions/PresenceExtension.d.ts +2 -2
- package/dist/abstractions/PresenceExtension.d.ts.map +1 -1
- package/dist/abstractions/PresenceManager.d.ts +1 -1
- package/dist/abstractions/PresenceManager.d.ts.map +1 -1
- package/dist/abstractions/PresenceManager.js +5 -5
- package/dist/abstractions/RoomManager.d.ts +2 -2
- package/dist/abstractions/RoomManager.d.ts.map +1 -1
- package/dist/abstractions/StorageAdapter.d.ts +4 -4
- package/dist/abstractions/StorageAdapter.d.ts.map +1 -1
- package/dist/abstractions/TransportAdapter.d.ts +4 -4
- package/dist/abstractions/TransportAdapter.d.ts.map +1 -1
- package/dist/abstractions/index.d.ts +11 -11
- package/dist/abstractions/index.d.ts.map +1 -1
- package/dist/abstractions/index.js +9 -9
- package/dist/adapters/mock/MockAuthAdapter.d.ts +2 -2
- package/dist/adapters/mock/MockAuthAdapter.d.ts.map +1 -1
- package/dist/adapters/mock/MockAuthAdapter.js +13 -11
- package/dist/adapters/mock/MockRTCDataChannel.d.ts +83 -0
- package/dist/adapters/mock/MockRTCDataChannel.d.ts.map +1 -0
- package/dist/adapters/mock/MockRTCDataChannel.js +146 -0
- package/dist/adapters/mock/MockRTCPeerConnection.d.ts +168 -0
- package/dist/adapters/mock/MockRTCPeerConnection.d.ts.map +1 -0
- package/dist/adapters/mock/MockRTCPeerConnection.js +449 -0
- package/dist/adapters/mock/MockStorageAdapter.d.ts +1 -1
- package/dist/adapters/mock/MockStorageAdapter.d.ts.map +1 -1
- package/dist/adapters/mock/MockStorageAdapter.js +18 -18
- package/dist/adapters/mock/MockTransportAdapter.d.ts +2 -2
- package/dist/adapters/mock/MockTransportAdapter.d.ts.map +1 -1
- package/dist/adapters/mock/MockTransportAdapter.js +38 -38
- package/dist/adapters/mock/index.d.ts +5 -3
- package/dist/adapters/mock/index.d.ts.map +1 -1
- package/dist/adapters/mock/index.js +10 -5
- package/dist/adapters/webrtc/WebRTCSignalingAdapter.d.ts +135 -0
- package/dist/adapters/webrtc/WebRTCSignalingAdapter.d.ts.map +1 -0
- package/dist/adapters/webrtc/WebRTCSignalingAdapter.js +368 -0
- package/dist/adapters/webrtc/index.d.ts +2 -0
- package/dist/adapters/webrtc/index.d.ts.map +1 -0
- package/dist/adapters/webrtc/index.js +5 -0
- package/dist/adapters/websocket/BrowserWebSocketTransportAdapter.d.ts +75 -0
- package/dist/adapters/websocket/BrowserWebSocketTransportAdapter.d.ts.map +1 -0
- package/dist/adapters/websocket/BrowserWebSocketTransportAdapter.js +231 -0
- package/dist/adapters/websocket/WebSocketClientTransportAdapter.d.ts +3 -3
- package/dist/adapters/websocket/WebSocketClientTransportAdapter.d.ts.map +1 -1
- package/dist/adapters/websocket/WebSocketClientTransportAdapter.js +38 -38
- package/dist/adapters/websocket/WebSocketServerTransportAdapter.d.ts +7 -7
- package/dist/adapters/websocket/WebSocketServerTransportAdapter.d.ts.map +1 -1
- package/dist/adapters/websocket/WebSocketServerTransportAdapter.js +94 -91
- package/dist/adapters/websocket/browser.d.ts +2 -0
- package/dist/adapters/websocket/browser.d.ts.map +1 -0
- package/dist/adapters/websocket/browser.js +6 -0
- package/dist/adapters/websocket/index.d.ts +3 -2
- package/dist/adapters/websocket/index.d.ts.map +1 -1
- package/dist/adapters/websocket/index.js +7 -3
- package/dist/adapters/websocket/node.d.ts +3 -0
- package/dist/adapters/websocket/node.d.ts.map +1 -0
- package/dist/adapters/websocket/node.js +8 -0
- package/dist/client/BaseClient.d.ts +6 -6
- package/dist/client/BaseClient.d.ts.map +1 -1
- package/dist/client/BaseClient.js +86 -72
- package/dist/client/ClientBuilder.d.ts +5 -5
- package/dist/client/ClientBuilder.d.ts.map +1 -1
- package/dist/client/ClientBuilder.js +3 -3
- package/dist/client/PresenceClient.d.ts +4 -4
- package/dist/client/PresenceClient.d.ts.map +1 -1
- package/dist/client/PresenceClient.js +30 -30
- package/dist/client/index.d.ts +3 -3
- package/dist/client/index.d.ts.map +1 -1
- package/dist/index.d.ts +7 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +27 -19
- package/dist/index.js.map +27 -23
- package/dist/index.mjs +1585 -558
- package/dist/index.mjs.map +27 -23
- package/dist/server/BaseServer.d.ts +13 -13
- package/dist/server/BaseServer.d.ts.map +1 -1
- package/dist/server/BaseServer.js +218 -143
- package/dist/server/ExperimentalAPI.d.ts +7 -7
- package/dist/server/ExperimentalAPI.d.ts.map +1 -1
- package/dist/server/ExperimentalAPI.js +22 -22
- package/dist/server/ServerBuilder.d.ts +11 -11
- package/dist/server/ServerBuilder.d.ts.map +1 -1
- package/dist/server/ServerBuilder.js +10 -10
- package/dist/server/index.d.ts +3 -3
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +3 -3
- package/dist/types/auth.d.ts.map +1 -1
- package/dist/types/events.d.ts +10 -10
- package/dist/types/events.d.ts.map +1 -1
- package/dist/types/experimental.d.ts +2 -2
- package/dist/types/experimental.d.ts.map +1 -1
- package/dist/types/experimental.js +1 -1
- package/dist/types/index.d.ts +7 -7
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +2 -2
- package/dist/types/lock.d.ts +2 -2
- package/dist/types/lock.d.ts.map +1 -1
- package/dist/types/presence.d.ts +3 -3
- package/dist/types/presence.d.ts.map +1 -1
- package/dist/types/room.d.ts +4 -4
- package/dist/types/room.d.ts.map +1 -1
- package/dist/types/room.js +2 -2
- package/package.json +15 -7
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.BaseServer = void 0;
|
|
4
|
-
const room_js_1 = require("../types/room.js");
|
|
5
4
|
const EventEmitter_js_1 = require("../abstractions/EventEmitter.js");
|
|
5
|
+
const room_js_1 = require("../types/room.js");
|
|
6
6
|
const ExperimentalAPI_js_1 = require("./ExperimentalAPI.js");
|
|
7
7
|
class BaseServer extends EventEmitter_js_1.TypedEventEmitter {
|
|
8
8
|
constructor(config) {
|
|
@@ -25,21 +25,24 @@ class BaseServer extends EventEmitter_js_1.TypedEventEmitter {
|
|
|
25
25
|
config: config.experimental || {
|
|
26
26
|
enableBroadcast: false,
|
|
27
27
|
warnOnExperimentalUse: true,
|
|
28
|
-
trackUsageMetrics: false
|
|
28
|
+
trackUsageMetrics: false,
|
|
29
29
|
},
|
|
30
30
|
logger: console,
|
|
31
31
|
emitMetric: (event) => {
|
|
32
|
-
void this.emit(
|
|
32
|
+
void this.emit("experimental_usage", event);
|
|
33
33
|
},
|
|
34
34
|
getClients: () => Array.from(this.clients.values()),
|
|
35
|
-
sendToClient: (clientId, message) => this.sendToClient(clientId, message)
|
|
35
|
+
sendToClient: (clientId, message) => this.sendToClient(clientId, message),
|
|
36
36
|
});
|
|
37
37
|
// Determine mode based on configuration
|
|
38
|
-
this.mode =
|
|
38
|
+
this.mode =
|
|
39
|
+
config.httpServer || config.webSocketServer
|
|
40
|
+
? "integration"
|
|
41
|
+
: "standalone";
|
|
39
42
|
// Connect auth adapter to transport if both exist and transport supports it
|
|
40
43
|
if (this.auth && this.transport) {
|
|
41
44
|
const transportWithAuth = this.transport;
|
|
42
|
-
if (typeof transportWithAuth.setAuthAdapter ===
|
|
45
|
+
if (typeof transportWithAuth.setAuthAdapter === "function") {
|
|
43
46
|
transportWithAuth.setAuthAdapter(this.auth);
|
|
44
47
|
}
|
|
45
48
|
}
|
|
@@ -66,29 +69,29 @@ class BaseServer extends EventEmitter_js_1.TypedEventEmitter {
|
|
|
66
69
|
const changes = await this.presenceManager.processHeartbeats();
|
|
67
70
|
// Emit presence change events
|
|
68
71
|
for (const change of changes) {
|
|
69
|
-
await this.emit(
|
|
72
|
+
await this.emit("presence_changed", {
|
|
70
73
|
userId: change.userId,
|
|
71
74
|
status: change.status,
|
|
72
|
-
timestamp: change.timestamp
|
|
75
|
+
timestamp: change.timestamp,
|
|
73
76
|
});
|
|
74
77
|
// Broadcast presence updates if enabled
|
|
75
78
|
if (config.broadcastPresenceUpdates) {
|
|
76
79
|
await this.experimental.broadcastAuthenticated({
|
|
77
|
-
type:
|
|
80
|
+
type: "presence:status_changed",
|
|
78
81
|
payload: {
|
|
79
82
|
userId: change.userId,
|
|
80
83
|
status: change.status,
|
|
81
84
|
previousStatus: change.previousStatus,
|
|
82
|
-
reason: change.reason
|
|
83
|
-
}
|
|
85
|
+
reason: change.reason,
|
|
86
|
+
},
|
|
84
87
|
});
|
|
85
88
|
}
|
|
86
89
|
}
|
|
87
90
|
}
|
|
88
91
|
catch (error) {
|
|
89
|
-
await this.emit(
|
|
92
|
+
await this.emit("error", {
|
|
90
93
|
error: error,
|
|
91
|
-
context:
|
|
94
|
+
context: "heartbeat_processor",
|
|
92
95
|
});
|
|
93
96
|
}
|
|
94
97
|
}, interval);
|
|
@@ -104,62 +107,70 @@ class BaseServer extends EventEmitter_js_1.TypedEventEmitter {
|
|
|
104
107
|
}
|
|
105
108
|
// Server Lifecycle
|
|
106
109
|
async start(port) {
|
|
107
|
-
if (this.mode ===
|
|
108
|
-
throw new Error(
|
|
110
|
+
if (this.mode === "integration") {
|
|
111
|
+
throw new Error("Cannot use start() in integration mode. Use initialize() instead.");
|
|
109
112
|
}
|
|
110
113
|
if (!port) {
|
|
111
|
-
throw new Error(
|
|
114
|
+
throw new Error("Port is required for standalone mode");
|
|
112
115
|
}
|
|
113
116
|
if (this.running) {
|
|
114
|
-
throw new Error(
|
|
117
|
+
throw new Error("Server is already running");
|
|
115
118
|
}
|
|
116
119
|
try {
|
|
117
|
-
await this.transport.connect(`ws://localhost:${port}`, {
|
|
120
|
+
await this.transport.connect(`ws://localhost:${port}`, {
|
|
121
|
+
url: `ws://localhost:${port}`,
|
|
122
|
+
});
|
|
118
123
|
this.running = true;
|
|
119
124
|
this.initialized = true;
|
|
120
|
-
await this.emit(
|
|
125
|
+
await this.emit("started", { port });
|
|
121
126
|
}
|
|
122
127
|
catch (error) {
|
|
123
|
-
await this.emit(
|
|
128
|
+
await this.emit("error", {
|
|
129
|
+
error: error,
|
|
130
|
+
context: "server_start",
|
|
131
|
+
});
|
|
124
132
|
throw error;
|
|
125
133
|
}
|
|
126
134
|
}
|
|
127
135
|
async initialize() {
|
|
128
|
-
if (this.mode ===
|
|
129
|
-
throw new Error(
|
|
136
|
+
if (this.mode === "standalone") {
|
|
137
|
+
throw new Error("Cannot use initialize() in standalone mode. Use start() instead.");
|
|
130
138
|
}
|
|
131
139
|
if (this.initialized) {
|
|
132
|
-
throw new Error(
|
|
140
|
+
throw new Error("Server is already initialized");
|
|
133
141
|
}
|
|
134
142
|
try {
|
|
135
143
|
const transport = this.transport;
|
|
136
144
|
if (this.config.webSocketServer) {
|
|
137
145
|
// Attach to existing WebSocket server
|
|
138
|
-
if (typeof transport.attachToWebSocketServer ===
|
|
146
|
+
if (typeof transport.attachToWebSocketServer === "function") {
|
|
139
147
|
await transport.attachToWebSocketServer(this.config.webSocketServer);
|
|
140
148
|
}
|
|
141
149
|
else {
|
|
142
|
-
throw new Error(
|
|
150
|
+
throw new Error("Transport adapter does not support attachToWebSocketServer");
|
|
143
151
|
}
|
|
144
152
|
}
|
|
145
153
|
else if (this.config.httpServer) {
|
|
146
154
|
// Attach to existing HTTP server
|
|
147
|
-
if (typeof transport.attach ===
|
|
148
|
-
await transport.attach(this.config.httpServer, this.config.webSocketPath ||
|
|
155
|
+
if (typeof transport.attach === "function") {
|
|
156
|
+
await transport.attach(this.config.httpServer, this.config.webSocketPath || "/ws");
|
|
149
157
|
}
|
|
150
158
|
else {
|
|
151
|
-
throw new Error(
|
|
159
|
+
throw new Error("Transport adapter does not support attach");
|
|
152
160
|
}
|
|
153
161
|
}
|
|
154
162
|
else {
|
|
155
|
-
throw new Error(
|
|
163
|
+
throw new Error("Either httpServer or webSocketServer must be provided in integration mode");
|
|
156
164
|
}
|
|
157
165
|
this.running = true;
|
|
158
166
|
this.initialized = true;
|
|
159
|
-
await this.emit(
|
|
167
|
+
await this.emit("started", { port: 0 }); // Port 0 indicates integration mode
|
|
160
168
|
}
|
|
161
169
|
catch (error) {
|
|
162
|
-
await this.emit(
|
|
170
|
+
await this.emit("error", {
|
|
171
|
+
error: error,
|
|
172
|
+
context: "server_initialize",
|
|
173
|
+
});
|
|
163
174
|
throw error;
|
|
164
175
|
}
|
|
165
176
|
}
|
|
@@ -172,13 +183,16 @@ class BaseServer extends EventEmitter_js_1.TypedEventEmitter {
|
|
|
172
183
|
this.stopHeartbeatProcessor();
|
|
173
184
|
// Disconnect all clients
|
|
174
185
|
for (const [clientId] of Array.from(this.clients)) {
|
|
175
|
-
await this.disconnectClient(clientId,
|
|
186
|
+
await this.disconnectClient(clientId, "Server shutting down");
|
|
176
187
|
}
|
|
177
188
|
try {
|
|
178
189
|
await this.transport.disconnect();
|
|
179
190
|
}
|
|
180
191
|
catch (error) {
|
|
181
|
-
await this.emit(
|
|
192
|
+
await this.emit("error", {
|
|
193
|
+
error: error,
|
|
194
|
+
context: "server_stop",
|
|
195
|
+
});
|
|
182
196
|
}
|
|
183
197
|
this.clients.clear();
|
|
184
198
|
this.clientMessageHandlers.clear();
|
|
@@ -188,22 +202,22 @@ class BaseServer extends EventEmitter_js_1.TypedEventEmitter {
|
|
|
188
202
|
if (this.presenceManager) {
|
|
189
203
|
await this.presenceManager.clear();
|
|
190
204
|
}
|
|
191
|
-
await this.emit(
|
|
205
|
+
await this.emit("stopped", {});
|
|
192
206
|
}
|
|
193
207
|
// Client Management
|
|
194
208
|
async handleTransportMessage(message) {
|
|
195
209
|
// Handle special transport messages
|
|
196
|
-
if (message.type ===
|
|
210
|
+
if (message.type === "connection") {
|
|
197
211
|
await this.handleConnectionMessage(message);
|
|
198
212
|
return;
|
|
199
213
|
}
|
|
200
|
-
if (message.type ===
|
|
214
|
+
if (message.type === "client_authenticated") {
|
|
201
215
|
await this.handleClientAuthenticatedMessage(message);
|
|
202
216
|
return;
|
|
203
217
|
}
|
|
204
|
-
if (message.type ===
|
|
218
|
+
if (message.type === "disconnect") {
|
|
205
219
|
const payload = message.payload;
|
|
206
|
-
await this.disconnectClient(payload.clientId, payload.reason ||
|
|
220
|
+
await this.disconnectClient(payload.clientId, payload.reason || "Client disconnected");
|
|
207
221
|
return;
|
|
208
222
|
}
|
|
209
223
|
// Transport messages contain clientId in the payload for routing
|
|
@@ -213,12 +227,12 @@ class BaseServer extends EventEmitter_js_1.TypedEventEmitter {
|
|
|
213
227
|
id: message.id,
|
|
214
228
|
type: type || message.type, // Use inner type if present, otherwise outer type
|
|
215
229
|
payload: clientMessage,
|
|
216
|
-
timestamp: message.timestamp
|
|
230
|
+
timestamp: message.timestamp,
|
|
217
231
|
};
|
|
218
232
|
if (!clientId) {
|
|
219
|
-
await this.emit(
|
|
220
|
-
error: new Error(
|
|
221
|
-
context:
|
|
233
|
+
await this.emit("error", {
|
|
234
|
+
error: new Error("Message missing clientId"),
|
|
235
|
+
context: "transport_message",
|
|
222
236
|
});
|
|
223
237
|
return;
|
|
224
238
|
}
|
|
@@ -227,9 +241,9 @@ class BaseServer extends EventEmitter_js_1.TypedEventEmitter {
|
|
|
227
241
|
await handler(clientMsg);
|
|
228
242
|
}
|
|
229
243
|
else {
|
|
230
|
-
await this.emit(
|
|
244
|
+
await this.emit("error", {
|
|
231
245
|
error: new Error(`No handler for client ${clientId}`),
|
|
232
|
-
context:
|
|
246
|
+
context: "transport_message",
|
|
233
247
|
});
|
|
234
248
|
}
|
|
235
249
|
}
|
|
@@ -239,55 +253,57 @@ class BaseServer extends EventEmitter_js_1.TypedEventEmitter {
|
|
|
239
253
|
const roomIds = new Set();
|
|
240
254
|
const client = {
|
|
241
255
|
id: payload.clientId,
|
|
242
|
-
userId: payload.userId ||
|
|
256
|
+
userId: payload.userId || "",
|
|
243
257
|
roomIds,
|
|
244
258
|
get roomId() {
|
|
245
|
-
return roomIds.size > 0 ?
|
|
259
|
+
return roomIds.size > 0 ? roomIds.values().next().value || null : null;
|
|
246
260
|
},
|
|
247
261
|
authenticated: payload.authenticated,
|
|
248
|
-
connectedAt: Date.now()
|
|
262
|
+
connectedAt: Date.now(),
|
|
249
263
|
};
|
|
250
264
|
this.clients.set(payload.clientId, client);
|
|
251
265
|
this.clientMessageHandlers.set(payload.clientId, this.createClientMessageHandler(payload.clientId));
|
|
252
|
-
await this.emit(
|
|
266
|
+
await this.emit("client_connected", { client });
|
|
253
267
|
// Register with presence manager if authenticated
|
|
254
|
-
if (payload.authenticated &&
|
|
268
|
+
if (payload.authenticated &&
|
|
269
|
+
this.presenceManager?.isEnabled() &&
|
|
270
|
+
payload.userId) {
|
|
255
271
|
try {
|
|
256
272
|
await this.presenceManager.connectDevice(payload.userId, payload.clientId, {
|
|
257
273
|
connectedAt: Date.now(),
|
|
258
274
|
lastActivity: Date.now(),
|
|
259
|
-
metadata: payload.metadata
|
|
275
|
+
metadata: payload.metadata,
|
|
260
276
|
});
|
|
261
|
-
await this.emit(
|
|
277
|
+
await this.emit("presence_device_connected", {
|
|
262
278
|
userId: payload.userId,
|
|
263
279
|
deviceId: payload.clientId,
|
|
264
|
-
timestamp: Date.now()
|
|
280
|
+
timestamp: Date.now(),
|
|
265
281
|
});
|
|
266
282
|
// Broadcast presence update
|
|
267
283
|
const presenceConfig = this.presenceManager.getConfig();
|
|
268
284
|
if (presenceConfig.broadcastPresenceUpdates) {
|
|
269
285
|
await this.experimental.broadcastAuthenticated({
|
|
270
|
-
type:
|
|
286
|
+
type: "presence:user_online",
|
|
271
287
|
payload: {
|
|
272
288
|
userId: payload.userId,
|
|
273
289
|
deviceId: payload.clientId,
|
|
274
|
-
status:
|
|
275
|
-
}
|
|
290
|
+
status: "online",
|
|
291
|
+
},
|
|
276
292
|
});
|
|
277
293
|
}
|
|
278
294
|
}
|
|
279
295
|
catch (error) {
|
|
280
|
-
await this.emit(
|
|
296
|
+
await this.emit("error", {
|
|
281
297
|
error: error,
|
|
282
|
-
context:
|
|
298
|
+
context: "presence_connect_device",
|
|
283
299
|
});
|
|
284
300
|
}
|
|
285
301
|
}
|
|
286
302
|
if (payload.authenticated) {
|
|
287
|
-
await this.emit(
|
|
303
|
+
await this.emit("client_authenticated", {
|
|
288
304
|
clientId: payload.clientId,
|
|
289
|
-
userId: payload.userId ||
|
|
290
|
-
metadata: payload.metadata
|
|
305
|
+
userId: payload.userId || "",
|
|
306
|
+
metadata: payload.metadata,
|
|
291
307
|
});
|
|
292
308
|
}
|
|
293
309
|
}
|
|
@@ -303,15 +319,15 @@ class BaseServer extends EventEmitter_js_1.TypedEventEmitter {
|
|
|
303
319
|
this.userClientMap.set(payload.userId, new Set());
|
|
304
320
|
}
|
|
305
321
|
this.userClientMap.get(payload.userId).add(payload.clientId);
|
|
306
|
-
await this.emit(
|
|
322
|
+
await this.emit("client_authenticated", {
|
|
307
323
|
clientId: payload.clientId,
|
|
308
324
|
userId: payload.userId,
|
|
309
|
-
metadata: payload.metadata
|
|
325
|
+
metadata: payload.metadata,
|
|
310
326
|
});
|
|
311
327
|
}
|
|
312
328
|
}
|
|
313
329
|
async handleTransportError(error) {
|
|
314
|
-
await this.emit(
|
|
330
|
+
await this.emit("error", { error, context: "transport" });
|
|
315
331
|
}
|
|
316
332
|
async handleTransportClose(_code, _reason) {
|
|
317
333
|
// Transport close means server is shutting down
|
|
@@ -323,18 +339,18 @@ class BaseServer extends EventEmitter_js_1.TypedEventEmitter {
|
|
|
323
339
|
const roomIds = new Set();
|
|
324
340
|
const client = {
|
|
325
341
|
id: clientId,
|
|
326
|
-
userId:
|
|
342
|
+
userId: "",
|
|
327
343
|
roomIds,
|
|
328
344
|
get roomId() {
|
|
329
|
-
return roomIds.size > 0 ?
|
|
345
|
+
return roomIds.size > 0 ? roomIds.values().next().value || null : null;
|
|
330
346
|
},
|
|
331
347
|
authenticated: false,
|
|
332
|
-
connectedAt: Date.now()
|
|
348
|
+
connectedAt: Date.now(),
|
|
333
349
|
};
|
|
334
350
|
this.clients.set(clientId, client);
|
|
335
351
|
// Set up message handler for this client
|
|
336
352
|
this.clientMessageHandlers.set(clientId, this.createClientMessageHandler(clientId));
|
|
337
|
-
await this.emit(
|
|
353
|
+
await this.emit("client_connected", { client });
|
|
338
354
|
}
|
|
339
355
|
async disconnectClient(clientId, reason) {
|
|
340
356
|
const client = this.clients.get(clientId);
|
|
@@ -356,43 +372,44 @@ class BaseServer extends EventEmitter_js_1.TypedEventEmitter {
|
|
|
356
372
|
if (this.presenceManager?.isEnabled() && client.userId) {
|
|
357
373
|
try {
|
|
358
374
|
const presence = await this.presenceManager.disconnectDevice(client.userId, clientId);
|
|
359
|
-
await this.emit(
|
|
375
|
+
await this.emit("presence_device_disconnected", {
|
|
360
376
|
userId: client.userId,
|
|
361
377
|
deviceId: clientId,
|
|
362
|
-
timestamp: Date.now()
|
|
378
|
+
timestamp: Date.now(),
|
|
363
379
|
});
|
|
364
380
|
// Broadcast presence update if user went offline or into grace period
|
|
365
381
|
const presenceConfig = this.presenceManager.getConfig();
|
|
366
|
-
if (presenceConfig.broadcastPresenceUpdates &&
|
|
382
|
+
if (presenceConfig.broadcastPresenceUpdates &&
|
|
383
|
+
(!presence || presence.status === "offline")) {
|
|
367
384
|
await this.experimental.broadcastAuthenticated({
|
|
368
|
-
type:
|
|
385
|
+
type: "presence:user_offline",
|
|
369
386
|
payload: {
|
|
370
387
|
userId: client.userId,
|
|
371
388
|
deviceId: clientId,
|
|
372
|
-
status: presence?.status ||
|
|
373
|
-
gracePeriod: presenceConfig.gracePeriod
|
|
374
|
-
}
|
|
389
|
+
status: presence?.status || "offline",
|
|
390
|
+
gracePeriod: presenceConfig.gracePeriod,
|
|
391
|
+
},
|
|
375
392
|
});
|
|
376
393
|
}
|
|
377
394
|
}
|
|
378
395
|
catch (error) {
|
|
379
|
-
await this.emit(
|
|
396
|
+
await this.emit("error", {
|
|
380
397
|
error: error,
|
|
381
|
-
context:
|
|
398
|
+
context: "presence_disconnect_device",
|
|
382
399
|
});
|
|
383
400
|
}
|
|
384
401
|
}
|
|
385
402
|
// Leave all rooms the client is in
|
|
386
403
|
for (const roomId of client.roomIds) {
|
|
387
404
|
await this.roomManager.leaveRoom(roomId, client.userId);
|
|
388
|
-
await this.emit(
|
|
405
|
+
await this.emit("client_left_room", { clientId, roomId });
|
|
389
406
|
}
|
|
390
407
|
// Release all locks held by this client
|
|
391
408
|
await this.lockManager.releaseUserLocks(client.userId);
|
|
392
409
|
// Clean up
|
|
393
410
|
this.clients.delete(clientId);
|
|
394
411
|
this.clientMessageHandlers.delete(clientId);
|
|
395
|
-
await this.emit(
|
|
412
|
+
await this.emit("client_disconnected", { clientId, reason });
|
|
396
413
|
}
|
|
397
414
|
createClientMessageHandler(clientId) {
|
|
398
415
|
return async (message) => {
|
|
@@ -402,57 +419,72 @@ class BaseServer extends EventEmitter_js_1.TypedEventEmitter {
|
|
|
402
419
|
return;
|
|
403
420
|
}
|
|
404
421
|
// Auto-update presence activity on ANY message
|
|
405
|
-
if (this.presenceManager?.isEnabled() &&
|
|
406
|
-
|
|
422
|
+
if (this.presenceManager?.isEnabled() &&
|
|
423
|
+
client.userId &&
|
|
424
|
+
client.authenticated) {
|
|
425
|
+
await this.updatePresenceActivity(client.userId, clientId, "message");
|
|
407
426
|
}
|
|
408
427
|
switch (message.type) {
|
|
409
|
-
case
|
|
428
|
+
case "authenticate":
|
|
410
429
|
await this.handleAuthenticate(clientId, message.payload);
|
|
411
430
|
break;
|
|
412
|
-
case
|
|
431
|
+
case "join_room":
|
|
413
432
|
await this.handleJoinRoom(clientId, message.payload);
|
|
414
433
|
break;
|
|
415
|
-
case
|
|
434
|
+
case "leave_room":
|
|
416
435
|
await this.handleLeaveRoom(clientId, message.payload);
|
|
417
436
|
break;
|
|
418
|
-
case
|
|
437
|
+
case "broadcast_event":
|
|
419
438
|
await this.handleBroadcastEvent(clientId, message.payload);
|
|
420
439
|
break;
|
|
421
|
-
case
|
|
440
|
+
case "request_lock":
|
|
422
441
|
await this.handleLockRequest(clientId, message.payload);
|
|
423
442
|
break;
|
|
424
|
-
case
|
|
443
|
+
case "release_lock":
|
|
425
444
|
await this.handleLockRelease(clientId, message.payload);
|
|
426
445
|
break;
|
|
427
|
-
case
|
|
428
|
-
await this.sendToClient(clientId, {
|
|
446
|
+
case "ping":
|
|
447
|
+
await this.sendToClient(clientId, {
|
|
448
|
+
type: "pong",
|
|
449
|
+
timestamp: Date.now(),
|
|
450
|
+
});
|
|
429
451
|
// Presence activity already updated automatically above
|
|
430
452
|
break;
|
|
431
|
-
case
|
|
453
|
+
case "heartbeat":
|
|
432
454
|
// Heartbeat messages are used to keep presence activity updated
|
|
433
455
|
// The activity is already tracked above (line ~570), so we just acknowledge
|
|
434
|
-
await this.sendToClient(clientId, {
|
|
456
|
+
await this.sendToClient(clientId, {
|
|
457
|
+
type: "heartbeat_ack",
|
|
458
|
+
timestamp: Date.now(),
|
|
459
|
+
});
|
|
435
460
|
break;
|
|
436
461
|
default:
|
|
437
462
|
await this.sendToClient(clientId, {
|
|
438
|
-
type:
|
|
439
|
-
error: `Unknown message type: ${message.type}
|
|
463
|
+
type: "error",
|
|
464
|
+
error: `Unknown message type: ${message.type}`,
|
|
440
465
|
});
|
|
441
466
|
}
|
|
442
467
|
}
|
|
443
468
|
catch (error) {
|
|
444
469
|
await this.sendToClient(clientId, {
|
|
445
|
-
type:
|
|
446
|
-
error: error.message
|
|
470
|
+
type: "error",
|
|
471
|
+
error: error.message,
|
|
472
|
+
});
|
|
473
|
+
await this.emit("error", {
|
|
474
|
+
error: error,
|
|
475
|
+
context: `client_${clientId}`,
|
|
447
476
|
});
|
|
448
|
-
await this.emit('error', { error: error, context: `client_${clientId}` });
|
|
449
477
|
}
|
|
450
478
|
};
|
|
451
479
|
}
|
|
452
480
|
// Message Handlers
|
|
453
481
|
async handleAuthenticate(clientId, payload) {
|
|
454
482
|
if (!this.auth) {
|
|
455
|
-
await this.sendToClient(clientId, {
|
|
483
|
+
await this.sendToClient(clientId, {
|
|
484
|
+
type: "auth_result",
|
|
485
|
+
success: false,
|
|
486
|
+
error: "Authentication not configured",
|
|
487
|
+
});
|
|
456
488
|
return;
|
|
457
489
|
}
|
|
458
490
|
try {
|
|
@@ -467,16 +499,24 @@ class BaseServer extends EventEmitter_js_1.TypedEventEmitter {
|
|
|
467
499
|
this.userClientMap.set(tokenPayload.userId, new Set());
|
|
468
500
|
}
|
|
469
501
|
this.userClientMap.get(tokenPayload.userId).add(clientId);
|
|
470
|
-
await this.emit(
|
|
502
|
+
await this.emit("client_authenticated", {
|
|
471
503
|
clientId,
|
|
472
504
|
userId: tokenPayload.userId,
|
|
473
|
-
metadata: tokenPayload.metadata
|
|
505
|
+
metadata: tokenPayload.metadata,
|
|
474
506
|
});
|
|
475
507
|
}
|
|
476
|
-
await this.sendToClient(clientId, {
|
|
508
|
+
await this.sendToClient(clientId, {
|
|
509
|
+
type: "auth_result",
|
|
510
|
+
success: true,
|
|
511
|
+
userId: tokenPayload.userId,
|
|
512
|
+
});
|
|
477
513
|
}
|
|
478
514
|
catch (error) {
|
|
479
|
-
await this.sendToClient(clientId, {
|
|
515
|
+
await this.sendToClient(clientId, {
|
|
516
|
+
type: "auth_result",
|
|
517
|
+
success: false,
|
|
518
|
+
error: error.message,
|
|
519
|
+
});
|
|
480
520
|
}
|
|
481
521
|
}
|
|
482
522
|
async handleJoinRoom(clientId, payload) {
|
|
@@ -489,62 +529,77 @@ class BaseServer extends EventEmitter_js_1.TypedEventEmitter {
|
|
|
489
529
|
await this.handleAuthenticate(clientId, { token: payload.token });
|
|
490
530
|
}
|
|
491
531
|
if (!client.authenticated) {
|
|
492
|
-
await this.sendToClient(clientId, {
|
|
532
|
+
await this.sendToClient(clientId, {
|
|
533
|
+
type: "error",
|
|
534
|
+
error: "Authentication required to join room",
|
|
535
|
+
});
|
|
493
536
|
return;
|
|
494
537
|
}
|
|
495
538
|
try {
|
|
496
539
|
// Check if already in the room
|
|
497
540
|
if (client.roomIds.has(payload.roomId)) {
|
|
498
|
-
await this.sendToClient(clientId, {
|
|
541
|
+
await this.sendToClient(clientId, {
|
|
542
|
+
type: "error",
|
|
543
|
+
error: "Already in this room",
|
|
544
|
+
});
|
|
499
545
|
return;
|
|
500
546
|
}
|
|
501
547
|
// Get or create room
|
|
502
548
|
let roomState = await this.roomManager.getRoomState(payload.roomId);
|
|
503
549
|
if (!roomState) {
|
|
504
|
-
const roomConfig = {
|
|
550
|
+
const roomConfig = {
|
|
551
|
+
...this.config.defaultRoomConfig,
|
|
552
|
+
id: payload.roomId,
|
|
553
|
+
};
|
|
505
554
|
const room = await this.roomManager.createRoom(payload.roomId, roomConfig);
|
|
506
555
|
roomState = await this.roomManager.getRoomState(payload.roomId);
|
|
507
|
-
await this.emit(
|
|
556
|
+
await this.emit("room_created", { room });
|
|
508
557
|
}
|
|
509
558
|
if (!roomState) {
|
|
510
|
-
throw new Error(
|
|
559
|
+
throw new Error("Failed to create or get room state");
|
|
511
560
|
}
|
|
512
561
|
// Join room
|
|
513
562
|
const user = {
|
|
514
563
|
id: client.userId,
|
|
515
564
|
username: client.userId, // TODO: Get from auth token
|
|
516
|
-
status:
|
|
565
|
+
status: "online",
|
|
517
566
|
joinedAt: Date.now(),
|
|
518
567
|
lastActivity: Date.now(),
|
|
519
|
-
permissions: roomState.room.permissions || [
|
|
568
|
+
permissions: roomState.room.permissions || ["read", "write"],
|
|
520
569
|
};
|
|
521
570
|
await this.roomManager.joinRoom(payload.roomId, user);
|
|
522
571
|
client.roomIds.add(payload.roomId);
|
|
523
|
-
await this.emit(
|
|
572
|
+
await this.emit("client_joined_room", {
|
|
573
|
+
clientId,
|
|
574
|
+
roomId: payload.roomId,
|
|
575
|
+
});
|
|
524
576
|
// Get updated room state after joining (so it includes the current user)
|
|
525
577
|
const updatedRoomState = await this.roomManager.getRoomState(payload.roomId);
|
|
526
578
|
if (!updatedRoomState) {
|
|
527
|
-
throw new Error(
|
|
579
|
+
throw new Error("Failed to get updated room state after joining");
|
|
528
580
|
}
|
|
529
581
|
// Convert Maps to plain objects for JSON serialization
|
|
530
582
|
const serializableState = (0, room_js_1.serializeRoomState)(updatedRoomState);
|
|
531
583
|
// Send room state to client
|
|
532
584
|
await this.sendToClient(clientId, {
|
|
533
|
-
type:
|
|
585
|
+
type: "room_joined",
|
|
534
586
|
roomId: payload.roomId,
|
|
535
|
-
state: serializableState
|
|
587
|
+
state: serializableState,
|
|
536
588
|
});
|
|
537
589
|
// Send event history
|
|
538
590
|
const history = await this.roomManager.getEventHistory(payload.roomId, 50);
|
|
539
591
|
if (history.length > 0) {
|
|
540
592
|
await this.sendToClient(clientId, {
|
|
541
|
-
type:
|
|
542
|
-
events: history
|
|
593
|
+
type: "event_history",
|
|
594
|
+
events: history,
|
|
543
595
|
});
|
|
544
596
|
}
|
|
545
597
|
}
|
|
546
598
|
catch (error) {
|
|
547
|
-
await this.sendToClient(clientId, {
|
|
599
|
+
await this.sendToClient(clientId, {
|
|
600
|
+
type: "error",
|
|
601
|
+
error: error.message,
|
|
602
|
+
});
|
|
548
603
|
}
|
|
549
604
|
}
|
|
550
605
|
async handleLeaveRoom(clientId, payload) {
|
|
@@ -566,14 +621,17 @@ class BaseServer extends EventEmitter_js_1.TypedEventEmitter {
|
|
|
566
621
|
}
|
|
567
622
|
await this.roomManager.leaveRoom(roomId, client.userId);
|
|
568
623
|
client.roomIds.delete(roomId);
|
|
569
|
-
await this.emit(
|
|
570
|
-
await this.sendToClient(clientId, { type:
|
|
624
|
+
await this.emit("client_left_room", { clientId, roomId });
|
|
625
|
+
await this.sendToClient(clientId, { type: "room_left", roomId });
|
|
571
626
|
}
|
|
572
627
|
}
|
|
573
628
|
async handleBroadcastEvent(clientId, payload) {
|
|
574
629
|
const client = this.clients.get(clientId);
|
|
575
630
|
if (!client || !client.roomIds.has(payload.roomId)) {
|
|
576
|
-
await this.sendToClient(clientId, {
|
|
631
|
+
await this.sendToClient(clientId, {
|
|
632
|
+
type: "error",
|
|
633
|
+
error: "Not in specified room",
|
|
634
|
+
});
|
|
577
635
|
return;
|
|
578
636
|
}
|
|
579
637
|
// Add metadata to event
|
|
@@ -583,42 +641,53 @@ class BaseServer extends EventEmitter_js_1.TypedEventEmitter {
|
|
|
583
641
|
...payload.event.metadata,
|
|
584
642
|
userId: client.userId,
|
|
585
643
|
timestamp: Date.now(),
|
|
586
|
-
roomId: payload.roomId
|
|
587
|
-
}
|
|
644
|
+
roomId: payload.roomId,
|
|
645
|
+
},
|
|
588
646
|
};
|
|
589
647
|
// Add to history
|
|
590
648
|
await this.roomManager.addEventToHistory(payload.roomId, enrichedEvent);
|
|
591
649
|
// Broadcast to room
|
|
592
650
|
await this.roomManager.broadcastToRoom(payload.roomId, enrichedEvent, client.userId);
|
|
593
|
-
await this.emit(
|
|
651
|
+
await this.emit("event_broadcast", {
|
|
594
652
|
roomId: payload.roomId,
|
|
595
653
|
event: enrichedEvent,
|
|
596
|
-
fromClientId: clientId
|
|
654
|
+
fromClientId: clientId,
|
|
597
655
|
});
|
|
598
656
|
}
|
|
599
657
|
async handleLockRequest(clientId, payload) {
|
|
600
658
|
const client = this.clients.get(clientId);
|
|
601
659
|
if (!client || !client.roomIds.has(payload.roomId)) {
|
|
602
|
-
await this.sendToClient(clientId, {
|
|
660
|
+
await this.sendToClient(clientId, {
|
|
661
|
+
type: "error",
|
|
662
|
+
error: "Not in specified room",
|
|
663
|
+
});
|
|
603
664
|
return;
|
|
604
665
|
}
|
|
605
666
|
try {
|
|
606
667
|
const lock = await this.lockManager.acquireLock(client.userId, client.userId, payload.request);
|
|
607
|
-
await this.emit(
|
|
608
|
-
await this.sendToClient(clientId, { type:
|
|
668
|
+
await this.emit("lock_acquired", { lock, clientId });
|
|
669
|
+
await this.sendToClient(clientId, { type: "lock_acquired", lock });
|
|
609
670
|
// Broadcast lock status to room
|
|
610
671
|
await this.roomManager.broadcastToRoom(payload.roomId, {
|
|
611
672
|
id: this.generateId(),
|
|
612
|
-
type:
|
|
673
|
+
type: "lock_status",
|
|
613
674
|
timestamp: Date.now(),
|
|
614
675
|
userId: client.userId,
|
|
615
676
|
roomId: payload.roomId,
|
|
616
|
-
data: { lock, action:
|
|
617
|
-
metadata: {
|
|
677
|
+
data: { lock, action: "acquired" },
|
|
678
|
+
metadata: {
|
|
679
|
+
userId: client.userId,
|
|
680
|
+
timestamp: Date.now(),
|
|
681
|
+
roomId: payload.roomId,
|
|
682
|
+
},
|
|
618
683
|
});
|
|
619
684
|
}
|
|
620
685
|
catch (error) {
|
|
621
|
-
await this.sendToClient(clientId, {
|
|
686
|
+
await this.sendToClient(clientId, {
|
|
687
|
+
type: "lock_denied",
|
|
688
|
+
request: payload.request,
|
|
689
|
+
reason: error.message,
|
|
690
|
+
});
|
|
622
691
|
}
|
|
623
692
|
}
|
|
624
693
|
async handleLockRelease(clientId, payload) {
|
|
@@ -628,32 +697,38 @@ class BaseServer extends EventEmitter_js_1.TypedEventEmitter {
|
|
|
628
697
|
}
|
|
629
698
|
try {
|
|
630
699
|
await this.lockManager.releaseLock(payload.lockId);
|
|
631
|
-
await this.emit(
|
|
632
|
-
await this.sendToClient(clientId, {
|
|
700
|
+
await this.emit("lock_released", { lockId: payload.lockId, clientId });
|
|
701
|
+
await this.sendToClient(clientId, {
|
|
702
|
+
type: "lock_released",
|
|
703
|
+
lockId: payload.lockId,
|
|
704
|
+
});
|
|
633
705
|
// Broadcast lock status to all rooms the client is in
|
|
634
706
|
for (const roomId of client.roomIds) {
|
|
635
707
|
await this.roomManager.broadcastToRoom(roomId, {
|
|
636
708
|
id: this.generateId(),
|
|
637
|
-
type:
|
|
709
|
+
type: "lock_status",
|
|
638
710
|
timestamp: Date.now(),
|
|
639
711
|
userId: client.userId,
|
|
640
712
|
roomId,
|
|
641
|
-
data: { lockId: payload.lockId, action:
|
|
642
|
-
metadata: { userId: client.userId, timestamp: Date.now(), roomId }
|
|
713
|
+
data: { lockId: payload.lockId, action: "released" },
|
|
714
|
+
metadata: { userId: client.userId, timestamp: Date.now(), roomId },
|
|
643
715
|
});
|
|
644
716
|
}
|
|
645
717
|
}
|
|
646
718
|
catch (error) {
|
|
647
|
-
await this.sendToClient(clientId, {
|
|
719
|
+
await this.sendToClient(clientId, {
|
|
720
|
+
type: "error",
|
|
721
|
+
error: error.message,
|
|
722
|
+
});
|
|
648
723
|
}
|
|
649
724
|
}
|
|
650
725
|
// Utility Methods
|
|
651
726
|
async sendToClient(clientId, message) {
|
|
652
727
|
const transportMessage = {
|
|
653
728
|
id: this.generateId(),
|
|
654
|
-
type:
|
|
729
|
+
type: "server_message",
|
|
655
730
|
payload: { clientId, ...message },
|
|
656
|
-
timestamp: Date.now()
|
|
731
|
+
timestamp: Date.now(),
|
|
657
732
|
};
|
|
658
733
|
await this.transport.send(transportMessage);
|
|
659
734
|
}
|
|
@@ -663,12 +738,12 @@ class BaseServer extends EventEmitter_js_1.TypedEventEmitter {
|
|
|
663
738
|
// Public API for external management
|
|
664
739
|
async createRoom(roomId, config) {
|
|
665
740
|
const room = await this.roomManager.createRoom(roomId, config);
|
|
666
|
-
await this.emit(
|
|
741
|
+
await this.emit("room_created", { room });
|
|
667
742
|
return room;
|
|
668
743
|
}
|
|
669
744
|
async deleteRoom(roomId) {
|
|
670
745
|
await this.roomManager.deleteRoom(roomId);
|
|
671
|
-
await this.emit(
|
|
746
|
+
await this.emit("room_deleted", { roomId });
|
|
672
747
|
}
|
|
673
748
|
getConnectedClients() {
|
|
674
749
|
return Array.from(this.clients.values());
|
|
@@ -701,19 +776,19 @@ class BaseServer extends EventEmitter_js_1.TypedEventEmitter {
|
|
|
701
776
|
userId,
|
|
702
777
|
deviceId,
|
|
703
778
|
timestamp: Date.now(),
|
|
704
|
-
activityType
|
|
779
|
+
activityType,
|
|
705
780
|
});
|
|
706
|
-
await this.emit(
|
|
781
|
+
await this.emit("presence_activity", {
|
|
707
782
|
userId,
|
|
708
783
|
deviceId,
|
|
709
|
-
timestamp: Date.now()
|
|
784
|
+
timestamp: Date.now(),
|
|
710
785
|
});
|
|
711
786
|
}
|
|
712
787
|
catch (error) {
|
|
713
788
|
// Don't throw, just log the error
|
|
714
|
-
await this.emit(
|
|
789
|
+
await this.emit("error", {
|
|
715
790
|
error: error,
|
|
716
|
-
context:
|
|
791
|
+
context: "presence_update_activity",
|
|
717
792
|
});
|
|
718
793
|
}
|
|
719
794
|
}
|