@principal-ai/control-tower-core 0.1.12 → 0.1.13
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/DefaultPresenceManager.d.ts +27 -4
- package/dist/abstractions/DefaultPresenceManager.d.ts.map +1 -1
- package/dist/abstractions/DefaultPresenceManager.js +145 -4
- package/dist/abstractions/PresenceExtension.d.ts +121 -0
- package/dist/abstractions/PresenceExtension.d.ts.map +1 -0
- package/dist/abstractions/PresenceExtension.js +14 -0
- package/dist/abstractions/index.d.ts +1 -0
- package/dist/abstractions/index.d.ts.map +1 -1
- package/dist/client/PresenceClient.d.ts +152 -0
- package/dist/client/PresenceClient.d.ts.map +1 -0
- package/dist/client/PresenceClient.js +193 -0
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +3 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +7 -6
- package/dist/index.mjs +290 -9
- package/dist/index.mjs.map +7 -6
- package/dist/server/BaseServer.d.ts +27 -0
- package/dist/server/BaseServer.d.ts.map +1 -1
- package/dist/server/BaseServer.js +57 -4
- package/dist/server/ServerBuilder.d.ts +5 -0
- package/dist/server/ServerBuilder.d.ts.map +1 -1
- package/dist/server/ServerBuilder.js +50 -1
- package/package.json +1 -1
|
@@ -9,17 +9,40 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { PresenceManager } from './PresenceManager.js';
|
|
11
11
|
import type { UserPresence, DeviceInfo, PresenceStatus, PresenceConfig, PresenceChangeEvent, ActivityUpdate } from '../types/presence.js';
|
|
12
|
-
|
|
12
|
+
import type { PresenceExtension, ExtendedUserPresence } from './PresenceExtension.js';
|
|
13
|
+
import type { BaseServer } from '../server/BaseServer.js';
|
|
14
|
+
export declare class DefaultPresenceManager<TExtendedData = Record<string, unknown>> extends PresenceManager {
|
|
13
15
|
private userPresences;
|
|
14
16
|
private deviceToUser;
|
|
15
17
|
private gracePeriodEntries;
|
|
16
|
-
|
|
18
|
+
private extensions;
|
|
19
|
+
private server?;
|
|
20
|
+
constructor(config?: PresenceConfig & {
|
|
21
|
+
extensions?: PresenceExtension<TExtendedData>[];
|
|
22
|
+
});
|
|
23
|
+
/**
|
|
24
|
+
* Set server reference for auto-broadcasting
|
|
25
|
+
*
|
|
26
|
+
* This is called automatically by ServerBuilder.
|
|
27
|
+
* @internal
|
|
28
|
+
*/
|
|
29
|
+
setServer(server: BaseServer): void;
|
|
30
|
+
/**
|
|
31
|
+
* Initialize all extensions
|
|
32
|
+
* @internal
|
|
33
|
+
*/
|
|
34
|
+
initializeExtensions(): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Destroy all extensions
|
|
37
|
+
* @internal
|
|
38
|
+
*/
|
|
39
|
+
destroyExtensions(): Promise<void>;
|
|
17
40
|
connectDevice(userId: string, deviceId: string, deviceInfo?: Partial<DeviceInfo>): Promise<UserPresence>;
|
|
18
41
|
disconnectDevice(userId: string, deviceId: string): Promise<UserPresence | null>;
|
|
19
42
|
updateActivity(update: ActivityUpdate): Promise<UserPresence>;
|
|
20
|
-
getUserPresence(userId: string): Promise<
|
|
43
|
+
getUserPresence(userId: string): Promise<ExtendedUserPresence<TExtendedData> | null>;
|
|
21
44
|
getUsersPresence(userIds: string[]): Promise<Map<string, UserPresence>>;
|
|
22
|
-
getOnlineUsers(): Promise<
|
|
45
|
+
getOnlineUsers(): Promise<ExtendedUserPresence<TExtendedData>[]>;
|
|
23
46
|
getUserDevices(userId: string): Promise<DeviceInfo[]>;
|
|
24
47
|
setUserStatus(userId: string, status: PresenceStatus): Promise<UserPresence>;
|
|
25
48
|
processHeartbeats(): Promise<PresenceChangeEvent[]>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DefaultPresenceManager.d.ts","sourceRoot":"","sources":["../../src/abstractions/DefaultPresenceManager.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,KAAK,EACV,YAAY,EACZ,UAAU,EACV,cAAc,EACd,cAAc,EACd,mBAAmB,EACnB,cAAc,EACf,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"DefaultPresenceManager.d.ts","sourceRoot":"","sources":["../../src/abstractions/DefaultPresenceManager.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,KAAK,EACV,YAAY,EACZ,UAAU,EACV,cAAc,EACd,cAAc,EACd,mBAAmB,EACnB,cAAc,EACf,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AACtF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAW1D,qBAAa,sBAAsB,CAAC,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAE,SAAQ,eAAe;IAClG,OAAO,CAAC,aAAa,CAAmC;IACxD,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,kBAAkB,CAAuC;IACjE,OAAO,CAAC,UAAU,CAA0C;IAC5D,OAAO,CAAC,MAAM,CAAC,CAAa;gBAEhB,MAAM,CAAC,EAAE,cAAc,GAAG;QACpC,UAAU,CAAC,EAAE,iBAAiB,CAAC,aAAa,CAAC,EAAE,CAAC;KACjD;IAKD;;;;;OAKG;IACH,SAAS,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAInC;;;OAGG;IACG,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC;IAM3C;;;OAGG;IACG,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC;IAMlC,aAAa,CACjB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,UAAU,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,GAC/B,OAAO,CAAC,YAAY,CAAC;IAmHlB,gBAAgB,CACpB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAuEzB,cAAc,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,YAAY,CAAC;IAiC7D,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC,aAAa,CAAC,GAAG,IAAI,CAAC;IAyBpF,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAavE,cAAc,IAAI,OAAO,CAAC,oBAAoB,CAAC,aAAa,CAAC,EAAE,CAAC;IA4BhE,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IASrD,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,YAAY,CAAC;IAU5E,iBAAiB,IAAI,OAAO,CAAC,mBAAmB,EAAE,CAAC;IA4EnD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAS5B;;OAEG;IACH,mBAAmB,IAAI,MAAM;IAI7B;;OAEG;IACH,uBAAuB,IAAI,MAAM;IAIjC;;OAEG;IACH,kBAAkB,IAAI,MAAM;CAG7B"}
|
|
@@ -17,9 +17,39 @@ class DefaultPresenceManager extends PresenceManager_js_1.PresenceManager {
|
|
|
17
17
|
this.userPresences = new Map();
|
|
18
18
|
this.deviceToUser = new Map();
|
|
19
19
|
this.gracePeriodEntries = new Map();
|
|
20
|
+
this.extensions = [];
|
|
21
|
+
this.extensions = config?.extensions || [];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Set server reference for auto-broadcasting
|
|
25
|
+
*
|
|
26
|
+
* This is called automatically by ServerBuilder.
|
|
27
|
+
* @internal
|
|
28
|
+
*/
|
|
29
|
+
setServer(server) {
|
|
30
|
+
this.server = server;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Initialize all extensions
|
|
34
|
+
* @internal
|
|
35
|
+
*/
|
|
36
|
+
async initializeExtensions() {
|
|
37
|
+
for (const ext of this.extensions) {
|
|
38
|
+
await ext.initialize?.();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Destroy all extensions
|
|
43
|
+
* @internal
|
|
44
|
+
*/
|
|
45
|
+
async destroyExtensions() {
|
|
46
|
+
for (const ext of this.extensions) {
|
|
47
|
+
await ext.destroy?.();
|
|
48
|
+
}
|
|
20
49
|
}
|
|
21
50
|
async connectDevice(userId, deviceId, deviceInfo) {
|
|
22
51
|
const now = Date.now();
|
|
52
|
+
const isFirstDevice = !this.userPresences.has(userId) && !this.gracePeriodEntries.has(userId);
|
|
23
53
|
// Check if user is in grace period (reconnecting)
|
|
24
54
|
const gracePeriodEntry = this.gracePeriodEntries.get(userId);
|
|
25
55
|
if (gracePeriodEntry) {
|
|
@@ -39,10 +69,35 @@ class DefaultPresenceManager extends PresenceManager_js_1.PresenceManager {
|
|
|
39
69
|
presence.lastActivity = now;
|
|
40
70
|
this.userPresences.set(userId, presence);
|
|
41
71
|
this.deviceToUser.set(deviceId, userId);
|
|
72
|
+
// Call extension hooks
|
|
73
|
+
if (isFirstDevice) {
|
|
74
|
+
for (const ext of this.extensions) {
|
|
75
|
+
await ext.onUserOnline?.(userId, deviceId, deviceInfo?.metadata || {});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
for (const ext of this.extensions) {
|
|
80
|
+
await ext.onDeviceConnected?.(userId, deviceId, deviceInfo?.metadata || {});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// Auto-broadcast
|
|
84
|
+
if (this.config.broadcastPresenceUpdates && this.server) {
|
|
85
|
+
const experimental = this.server.experimental;
|
|
86
|
+
await experimental?.broadcastAuthenticated({
|
|
87
|
+
type: 'presence:user_online',
|
|
88
|
+
payload: {
|
|
89
|
+
userId,
|
|
90
|
+
deviceId,
|
|
91
|
+
status: 'online',
|
|
92
|
+
timestamp: now
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
42
96
|
return presence;
|
|
43
97
|
}
|
|
44
98
|
// Get or create user presence
|
|
45
99
|
let presence = this.userPresences.get(userId);
|
|
100
|
+
const wasOnline = presence !== undefined;
|
|
46
101
|
if (!presence) {
|
|
47
102
|
// New user
|
|
48
103
|
presence = {
|
|
@@ -67,6 +122,30 @@ class DefaultPresenceManager extends PresenceManager_js_1.PresenceManager {
|
|
|
67
122
|
presence.status = this.calculatePresenceStatus(presence.devices, now);
|
|
68
123
|
presence.lastActivity = now;
|
|
69
124
|
this.deviceToUser.set(deviceId, userId);
|
|
125
|
+
// Call extension hooks
|
|
126
|
+
if (!wasOnline) {
|
|
127
|
+
for (const ext of this.extensions) {
|
|
128
|
+
await ext.onUserOnline?.(userId, deviceId, deviceInfo?.metadata || {});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
for (const ext of this.extensions) {
|
|
133
|
+
await ext.onDeviceConnected?.(userId, deviceId, deviceInfo?.metadata || {});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// Auto-broadcast if user came online
|
|
137
|
+
if (!wasOnline && this.config.broadcastPresenceUpdates && this.server) {
|
|
138
|
+
const experimental = this.server.experimental;
|
|
139
|
+
await experimental?.broadcastAuthenticated({
|
|
140
|
+
type: 'presence:user_online',
|
|
141
|
+
payload: {
|
|
142
|
+
userId,
|
|
143
|
+
deviceId,
|
|
144
|
+
status: 'online',
|
|
145
|
+
timestamp: now
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
}
|
|
70
149
|
return presence;
|
|
71
150
|
}
|
|
72
151
|
async disconnectDevice(userId, deviceId) {
|
|
@@ -78,8 +157,27 @@ class DefaultPresenceManager extends PresenceManager_js_1.PresenceManager {
|
|
|
78
157
|
presence.devices.delete(deviceId);
|
|
79
158
|
this.deviceToUser.delete(deviceId);
|
|
80
159
|
const now = Date.now();
|
|
160
|
+
const isLastDevice = presence.devices.size === 0;
|
|
81
161
|
// If no devices left, start grace period
|
|
82
|
-
if (
|
|
162
|
+
if (isLastDevice) {
|
|
163
|
+
// Call extension hooks for user going offline
|
|
164
|
+
for (const ext of this.extensions) {
|
|
165
|
+
await ext.onUserOffline?.(userId, deviceId);
|
|
166
|
+
}
|
|
167
|
+
// Auto-broadcast user offline
|
|
168
|
+
if (this.config.broadcastPresenceUpdates && this.server) {
|
|
169
|
+
const experimental = this.server.experimental;
|
|
170
|
+
await experimental?.broadcastAuthenticated({
|
|
171
|
+
type: 'presence:user_offline',
|
|
172
|
+
payload: {
|
|
173
|
+
userId,
|
|
174
|
+
deviceId,
|
|
175
|
+
status: 'offline',
|
|
176
|
+
timestamp: now,
|
|
177
|
+
gracePeriod: this.config.gracePeriod
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
}
|
|
83
181
|
if (this.config.gracePeriod > 0) {
|
|
84
182
|
// Move to grace period
|
|
85
183
|
this.gracePeriodEntries.set(userId, {
|
|
@@ -96,6 +194,10 @@ class DefaultPresenceManager extends PresenceManager_js_1.PresenceManager {
|
|
|
96
194
|
return null;
|
|
97
195
|
}
|
|
98
196
|
}
|
|
197
|
+
// Still has devices - call device disconnected hook
|
|
198
|
+
for (const ext of this.extensions) {
|
|
199
|
+
await ext.onDeviceDisconnected?.(userId, deviceId);
|
|
200
|
+
}
|
|
99
201
|
// Still has devices, update status
|
|
100
202
|
presence.status = this.calculatePresenceStatus(presence.devices, now);
|
|
101
203
|
// Update lastActivity to most recent device activity
|
|
@@ -109,7 +211,7 @@ class DefaultPresenceManager extends PresenceManager_js_1.PresenceManager {
|
|
|
109
211
|
return presence;
|
|
110
212
|
}
|
|
111
213
|
async updateActivity(update) {
|
|
112
|
-
const { userId, deviceId, timestamp } = update;
|
|
214
|
+
const { userId, deviceId, timestamp, activityType } = update;
|
|
113
215
|
const presence = this.userPresences.get(userId);
|
|
114
216
|
if (!presence) {
|
|
115
217
|
throw new Error(`User ${userId} not found in presence system`);
|
|
@@ -127,10 +229,32 @@ class DefaultPresenceManager extends PresenceManager_js_1.PresenceManager {
|
|
|
127
229
|
// Recalculate status
|
|
128
230
|
const previousStatus = presence.status;
|
|
129
231
|
presence.status = this.calculatePresenceStatus(presence.devices, timestamp);
|
|
232
|
+
// Call extension hooks
|
|
233
|
+
for (const ext of this.extensions) {
|
|
234
|
+
await ext.onActivity?.(userId, deviceId, activityType || 'heartbeat');
|
|
235
|
+
}
|
|
130
236
|
return presence;
|
|
131
237
|
}
|
|
132
238
|
async getUserPresence(userId) {
|
|
133
|
-
|
|
239
|
+
const basePresence = this.userPresences.get(userId);
|
|
240
|
+
if (!basePresence) {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
// Merge extended data from all extensions
|
|
244
|
+
if (this.extensions.length > 0) {
|
|
245
|
+
const extendedData = {};
|
|
246
|
+
for (const ext of this.extensions) {
|
|
247
|
+
const data = await ext.getExtendedData?.(userId);
|
|
248
|
+
if (data) {
|
|
249
|
+
Object.assign(extendedData, data);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return {
|
|
253
|
+
...basePresence,
|
|
254
|
+
extended: extendedData
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
return basePresence;
|
|
134
258
|
}
|
|
135
259
|
async getUsersPresence(userIds) {
|
|
136
260
|
const result = new Map();
|
|
@@ -146,7 +270,22 @@ class DefaultPresenceManager extends PresenceManager_js_1.PresenceManager {
|
|
|
146
270
|
const online = [];
|
|
147
271
|
for (const presence of this.userPresences.values()) {
|
|
148
272
|
if (presence.status === 'online') {
|
|
149
|
-
|
|
273
|
+
// Check visibility with extensions
|
|
274
|
+
let visible = true;
|
|
275
|
+
for (const ext of this.extensions) {
|
|
276
|
+
const shouldShow = await ext.shouldBeVisible?.(presence.userId);
|
|
277
|
+
if (shouldShow === false) {
|
|
278
|
+
visible = false;
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
if (visible) {
|
|
283
|
+
// Get full presence with extended data
|
|
284
|
+
const fullPresence = await this.getUserPresence(presence.userId);
|
|
285
|
+
if (fullPresence) {
|
|
286
|
+
online.push(fullPresence);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
150
289
|
}
|
|
151
290
|
}
|
|
152
291
|
return online;
|
|
@@ -230,6 +369,8 @@ class DefaultPresenceManager extends PresenceManager_js_1.PresenceManager {
|
|
|
230
369
|
return changes;
|
|
231
370
|
}
|
|
232
371
|
async clear() {
|
|
372
|
+
// Destroy extensions first
|
|
373
|
+
await this.destroyExtensions();
|
|
233
374
|
this.userPresences.clear();
|
|
234
375
|
this.deviceToUser.clear();
|
|
235
376
|
this.gracePeriodEntries.clear();
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Presence Extension Interface
|
|
3
|
+
*
|
|
4
|
+
* Allows applications to extend presence tracking with custom data and behavior
|
|
5
|
+
* without needing to create a separate presence manager.
|
|
6
|
+
*
|
|
7
|
+
* Extensions are called at key lifecycle points and can:
|
|
8
|
+
* - Add custom data to user presence
|
|
9
|
+
* - Control visibility of users
|
|
10
|
+
* - React to room joins/leaves
|
|
11
|
+
* - Track application-specific presence state
|
|
12
|
+
*/
|
|
13
|
+
import type { UserPresence } from '../types/presence.js';
|
|
14
|
+
/**
|
|
15
|
+
* Extension for adding custom presence data and behavior
|
|
16
|
+
*
|
|
17
|
+
* @template TExtendedData - Type of extended presence data
|
|
18
|
+
*/
|
|
19
|
+
export interface PresenceExtension<TExtendedData = Record<string, unknown>> {
|
|
20
|
+
/**
|
|
21
|
+
* Called when a user comes online (first device connects)
|
|
22
|
+
*
|
|
23
|
+
* @param userId - User identifier
|
|
24
|
+
* @param deviceId - Device/client identifier
|
|
25
|
+
* @param metadata - Connection metadata
|
|
26
|
+
*/
|
|
27
|
+
onUserOnline?(userId: string, deviceId: string, metadata: Record<string, unknown>): Promise<void> | void;
|
|
28
|
+
/**
|
|
29
|
+
* Called when a user goes offline (last device disconnects)
|
|
30
|
+
*
|
|
31
|
+
* Note: This is called when the user enters grace period or fully disconnects.
|
|
32
|
+
*
|
|
33
|
+
* @param userId - User identifier
|
|
34
|
+
* @param deviceId - Last device that disconnected
|
|
35
|
+
*/
|
|
36
|
+
onUserOffline?(userId: string, deviceId: string): Promise<void> | void;
|
|
37
|
+
/**
|
|
38
|
+
* Called when a device connects for an already-online user
|
|
39
|
+
*
|
|
40
|
+
* @param userId - User identifier
|
|
41
|
+
* @param deviceId - New device identifier
|
|
42
|
+
* @param metadata - Connection metadata
|
|
43
|
+
*/
|
|
44
|
+
onDeviceConnected?(userId: string, deviceId: string, metadata: Record<string, unknown>): Promise<void> | void;
|
|
45
|
+
/**
|
|
46
|
+
* Called when a device disconnects but user remains online
|
|
47
|
+
*
|
|
48
|
+
* @param userId - User identifier
|
|
49
|
+
* @param deviceId - Device that disconnected
|
|
50
|
+
*/
|
|
51
|
+
onDeviceDisconnected?(userId: string, deviceId: string): Promise<void> | void;
|
|
52
|
+
/**
|
|
53
|
+
* Called when a client joins a room
|
|
54
|
+
*
|
|
55
|
+
* This is useful for tracking room-specific presence data.
|
|
56
|
+
*
|
|
57
|
+
* @param userId - User identifier
|
|
58
|
+
* @param roomId - Room identifier
|
|
59
|
+
* @param deviceId - Device/client identifier
|
|
60
|
+
*/
|
|
61
|
+
onRoomJoined?(userId: string, roomId: string, deviceId: string): Promise<void> | void;
|
|
62
|
+
/**
|
|
63
|
+
* Called when a client leaves a room
|
|
64
|
+
*
|
|
65
|
+
* @param userId - User identifier
|
|
66
|
+
* @param roomId - Room identifier
|
|
67
|
+
* @param deviceId - Device/client identifier
|
|
68
|
+
*/
|
|
69
|
+
onRoomLeft?(userId: string, roomId: string, deviceId: string): Promise<void> | void;
|
|
70
|
+
/**
|
|
71
|
+
* Get extended presence data for a user
|
|
72
|
+
*
|
|
73
|
+
* This data will be merged into the UserPresence object.
|
|
74
|
+
*
|
|
75
|
+
* @param userId - User identifier
|
|
76
|
+
* @returns Extended presence data, or null if none
|
|
77
|
+
*/
|
|
78
|
+
getExtendedData?(userId: string): Promise<TExtendedData | null> | TExtendedData | null;
|
|
79
|
+
/**
|
|
80
|
+
* Determine if a user should be visible to others
|
|
81
|
+
*
|
|
82
|
+
* This allows filtering users from presence lists.
|
|
83
|
+
* If multiple extensions return false, the user is hidden.
|
|
84
|
+
*
|
|
85
|
+
* @param userId - User identifier
|
|
86
|
+
* @returns true if user should be visible, false to hide
|
|
87
|
+
*/
|
|
88
|
+
shouldBeVisible?(userId: string): Promise<boolean> | boolean;
|
|
89
|
+
/**
|
|
90
|
+
* Called when user activity is detected
|
|
91
|
+
*
|
|
92
|
+
* @param userId - User identifier
|
|
93
|
+
* @param deviceId - Device/client identifier
|
|
94
|
+
* @param activityType - Type of activity
|
|
95
|
+
*/
|
|
96
|
+
onActivity?(userId: string, deviceId: string, activityType: 'heartbeat' | 'message' | 'user_action'): Promise<void> | void;
|
|
97
|
+
/**
|
|
98
|
+
* Lifecycle: Called when extension is initialized
|
|
99
|
+
*
|
|
100
|
+
* Use this for setup tasks like loading initial state.
|
|
101
|
+
*/
|
|
102
|
+
initialize?(): Promise<void> | void;
|
|
103
|
+
/**
|
|
104
|
+
* Lifecycle: Called when extension is destroyed
|
|
105
|
+
*
|
|
106
|
+
* Use this for cleanup tasks like persisting state.
|
|
107
|
+
*/
|
|
108
|
+
destroy?(): Promise<void> | void;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Extended user presence with custom data
|
|
112
|
+
*
|
|
113
|
+
* @template TExtendedData - Type of extended presence data
|
|
114
|
+
*/
|
|
115
|
+
export interface ExtendedUserPresence<TExtendedData = Record<string, unknown>> extends UserPresence {
|
|
116
|
+
/**
|
|
117
|
+
* Extended data from presence extensions
|
|
118
|
+
*/
|
|
119
|
+
extended?: TExtendedData;
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=PresenceExtension.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PresenceExtension.d.ts","sourceRoot":"","sources":["../../src/abstractions/PresenceExtension.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD;;;;GAIG;AACH,MAAM,WAAW,iBAAiB,CAAC,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACxE;;;;;;OAMG;IACH,YAAY,CAAC,CACX,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAExB;;;;;;;OAOG;IACH,aAAa,CAAC,CACZ,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAExB;;;;;;OAMG;IACH,iBAAiB,CAAC,CAChB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAExB;;;;;OAKG;IACH,oBAAoB,CAAC,CACnB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAExB;;;;;;;;OAQG;IACH,YAAY,CAAC,CACX,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAExB;;;;;;OAMG;IACH,UAAU,CAAC,CACT,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAExB;;;;;;;OAOG;IACH,eAAe,CAAC,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,GAAG,aAAa,GAAG,IAAI,CAAC;IAEvF;;;;;;;;OAQG;IACH,eAAe,CAAC,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IAE7D;;;;;;OAMG;IACH,UAAU,CAAC,CACT,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,WAAW,GAAG,SAAS,GAAG,aAAa,GACpD,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAExB;;;;OAIG;IACH,UAAU,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAEpC;;;;OAIG;IACH,OAAO,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAClC;AAED;;;;GAIG;AACH,MAAM,WAAW,oBAAoB,CAAC,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAC3E,SAAQ,YAAY;IACpB;;OAEG;IACH,QAAQ,CAAC,EAAE,aAAa,CAAC;CAC1B"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Presence Extension Interface
|
|
4
|
+
*
|
|
5
|
+
* Allows applications to extend presence tracking with custom data and behavior
|
|
6
|
+
* without needing to create a separate presence manager.
|
|
7
|
+
*
|
|
8
|
+
* Extensions are called at key lifecycle points and can:
|
|
9
|
+
* - Add custom data to user presence
|
|
10
|
+
* - Control visibility of users
|
|
11
|
+
* - React to room joins/leaves
|
|
12
|
+
* - Track application-specific presence state
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -8,4 +8,5 @@ export { DefaultRoomManager } from './DefaultRoomManager.js';
|
|
|
8
8
|
export { DefaultLockManager } from './DefaultLockManager.js';
|
|
9
9
|
export { PresenceManager } from './PresenceManager.js';
|
|
10
10
|
export { DefaultPresenceManager } from './DefaultPresenceManager.js';
|
|
11
|
+
export { type PresenceExtension, type ExtendedUserPresence } from './PresenceExtension.js';
|
|
11
12
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/abstractions/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACxE,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACpF,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/abstractions/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACxE,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACpF,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EAC1B,MAAM,wBAAwB,CAAC"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Presence Client Helper
|
|
3
|
+
*
|
|
4
|
+
* Simplifies client-side presence integration for web/Electron applications.
|
|
5
|
+
* Automatically handles connection, authentication, and presence event forwarding.
|
|
6
|
+
*/
|
|
7
|
+
import { BaseClient, type ClientConfig } from './BaseClient.js';
|
|
8
|
+
import { TypedEventEmitter } from '../abstractions/EventEmitter.js';
|
|
9
|
+
import type { UserPresence } from '../types/presence.js';
|
|
10
|
+
export interface PresenceClientConfig {
|
|
11
|
+
/**
|
|
12
|
+
* Base client configuration
|
|
13
|
+
* Provide the full client config including transport and auth adapters
|
|
14
|
+
*/
|
|
15
|
+
clientConfig: ClientConfig;
|
|
16
|
+
/**
|
|
17
|
+
* WebSocket URL to connect to
|
|
18
|
+
*/
|
|
19
|
+
wsUrl: string;
|
|
20
|
+
/**
|
|
21
|
+
* Automatically connect on construction
|
|
22
|
+
* @default false
|
|
23
|
+
*/
|
|
24
|
+
autoConnect?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Automatically reconnect on disconnect
|
|
27
|
+
* @default true
|
|
28
|
+
*/
|
|
29
|
+
autoReconnect?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Global presence room ID
|
|
32
|
+
* @default '__global_presence__'
|
|
33
|
+
*/
|
|
34
|
+
globalRoomId?: string;
|
|
35
|
+
}
|
|
36
|
+
export interface PresenceClientEvents {
|
|
37
|
+
/**
|
|
38
|
+
* User came online
|
|
39
|
+
*/
|
|
40
|
+
userOnline: {
|
|
41
|
+
userId: string;
|
|
42
|
+
devices: unknown[];
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* User went offline
|
|
46
|
+
*/
|
|
47
|
+
userOffline: {
|
|
48
|
+
userId: string;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* User's presence status changed
|
|
52
|
+
*/
|
|
53
|
+
statusChanged: {
|
|
54
|
+
userId: string;
|
|
55
|
+
status: string;
|
|
56
|
+
statusMessage?: string;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Connected to presence system
|
|
60
|
+
*/
|
|
61
|
+
connected: {};
|
|
62
|
+
/**
|
|
63
|
+
* Disconnected from presence system
|
|
64
|
+
*/
|
|
65
|
+
disconnected: {};
|
|
66
|
+
/**
|
|
67
|
+
* Error occurred
|
|
68
|
+
*/
|
|
69
|
+
error: {
|
|
70
|
+
error: Error;
|
|
71
|
+
};
|
|
72
|
+
[key: string]: unknown;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* High-level presence client for easy integration
|
|
76
|
+
*
|
|
77
|
+
* Example:
|
|
78
|
+
* ```typescript
|
|
79
|
+
* import { PresenceClient } from '@principal-ai/control-tower-core';
|
|
80
|
+
* import { WebSocketClientTransportAdapter } from '@principal-ai/control-tower-core';
|
|
81
|
+
* import { ClientBuilder } from '@principal-ai/control-tower-core';
|
|
82
|
+
*
|
|
83
|
+
* const transport = new WebSocketClientTransportAdapter();
|
|
84
|
+
*
|
|
85
|
+
* const client = new ClientBuilder()
|
|
86
|
+
* .withTransport(transport)
|
|
87
|
+
* .build();
|
|
88
|
+
*
|
|
89
|
+
* const presence = new PresenceClient({
|
|
90
|
+
* clientConfig: client,
|
|
91
|
+
* wsUrl: 'ws://localhost:3000/ws',
|
|
92
|
+
* autoConnect: true,
|
|
93
|
+
* autoReconnect: true
|
|
94
|
+
* });
|
|
95
|
+
*
|
|
96
|
+
* presence.on('userOnline', (data) => {
|
|
97
|
+
* console.log(`${data.userId} came online`);
|
|
98
|
+
* });
|
|
99
|
+
*
|
|
100
|
+
* presence.on('userOffline', (data) => {
|
|
101
|
+
* console.log(`${data.userId} went offline`);
|
|
102
|
+
* });
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
export declare class PresenceClient extends TypedEventEmitter<PresenceClientEvents> {
|
|
106
|
+
private client;
|
|
107
|
+
private wsUrl;
|
|
108
|
+
private autoReconnectEnabled;
|
|
109
|
+
private globalRoomId;
|
|
110
|
+
private reconnectTimeout?;
|
|
111
|
+
constructor(config: PresenceClientConfig);
|
|
112
|
+
/**
|
|
113
|
+
* Connect to the presence system
|
|
114
|
+
*/
|
|
115
|
+
connect(): Promise<void>;
|
|
116
|
+
/**
|
|
117
|
+
* Disconnect from the presence system
|
|
118
|
+
*/
|
|
119
|
+
disconnect(): Promise<void>;
|
|
120
|
+
/**
|
|
121
|
+
* Get all online users
|
|
122
|
+
*
|
|
123
|
+
* Note: This requires a corresponding API endpoint on your server.
|
|
124
|
+
*/
|
|
125
|
+
getOnlineUsers(): Promise<UserPresence[]>;
|
|
126
|
+
/**
|
|
127
|
+
* Set user status
|
|
128
|
+
*
|
|
129
|
+
* Note: This requires a corresponding API endpoint on your server.
|
|
130
|
+
*/
|
|
131
|
+
setStatus(status: 'online' | 'away', message?: string): Promise<void>;
|
|
132
|
+
/**
|
|
133
|
+
* Set visibility
|
|
134
|
+
*
|
|
135
|
+
* Note: This requires a corresponding API endpoint on your server.
|
|
136
|
+
*/
|
|
137
|
+
setVisibility(visible: boolean): Promise<void>;
|
|
138
|
+
/**
|
|
139
|
+
* Get the underlying BaseClient
|
|
140
|
+
*/
|
|
141
|
+
getClient(): BaseClient;
|
|
142
|
+
/**
|
|
143
|
+
* Check if connected
|
|
144
|
+
*/
|
|
145
|
+
isConnected(): boolean;
|
|
146
|
+
/**
|
|
147
|
+
* Set up event forwarding from BaseClient
|
|
148
|
+
* @private
|
|
149
|
+
*/
|
|
150
|
+
private setupEventForwarding;
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=PresenceClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PresenceClient.d.ts","sourceRoot":"","sources":["../../src/client/PresenceClient.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAChE,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,YAAY,EAAE,YAAY,CAAC;IAE3B;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,oBAAoB;IACnC;;OAEG;IACH,UAAU,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,EAAE,CAAA;KAAE,CAAC;IAEnD;;OAEG;IACH,WAAW,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAEhC;;OAEG;IACH,aAAa,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAE1E;;OAEG;IACH,SAAS,EAAE,EAAE,CAAC;IAEd;;OAEG;IACH,YAAY,EAAE,EAAE,CAAC;IAEjB;;OAEG;IACH,KAAK,EAAE;QAAE,KAAK,EAAE,KAAK,CAAA;KAAE,CAAC;IAExB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,qBAAa,cAAe,SAAQ,iBAAiB,CAAC,oBAAoB,CAAC;IACzE,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,oBAAoB,CAAU;IACtC,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,gBAAgB,CAAC,CAAiB;gBAE9B,MAAM,EAAE,oBAAoB;IAiBxC;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAe9B;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAuBjC;;;;OAIG;IACG,cAAc,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;IAS/C;;;;OAIG;IACG,SAAS,CAAC,MAAM,EAAE,QAAQ,GAAG,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAS3E;;;;OAIG;IACG,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IASpD;;OAEG;IACH,SAAS,IAAI,UAAU;IAIvB;;OAEG;IACH,WAAW,IAAI,OAAO;IAMtB;;;OAGG;IACH,OAAO,CAAC,oBAAoB;CAoD7B"}
|