@fluidframework/presence 2.10.0-307399 → 2.11.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/README.md +41 -2
- package/dist/alpha.d.ts +5 -4
- package/dist/broadcastControls.d.ts +70 -0
- package/dist/broadcastControls.d.ts.map +1 -0
- package/dist/broadcastControls.js +61 -0
- package/dist/broadcastControls.js.map +1 -0
- package/dist/exposedInternalTypes.d.ts +4 -1
- package/dist/exposedInternalTypes.d.ts.map +1 -1
- package/dist/exposedInternalTypes.js.map +1 -1
- package/dist/exposedUtilityTypes.d.ts +5 -5
- package/dist/exposedUtilityTypes.d.ts.map +1 -1
- package/dist/exposedUtilityTypes.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/internalTypes.d.ts +2 -2
- package/dist/internalTypes.d.ts.map +1 -1
- package/dist/internalTypes.js.map +1 -1
- package/dist/latestMapValueManager.d.ts +5 -5
- package/dist/latestMapValueManager.d.ts.map +1 -1
- package/dist/latestMapValueManager.js +11 -15
- package/dist/latestMapValueManager.js.map +1 -1
- package/dist/latestValueManager.d.ts +5 -5
- package/dist/latestValueManager.d.ts.map +1 -1
- package/dist/latestValueManager.js +9 -13
- package/dist/latestValueManager.js.map +1 -1
- package/dist/notificationsManager.d.ts +28 -15
- package/dist/notificationsManager.d.ts.map +1 -1
- package/dist/notificationsManager.js +19 -11
- package/dist/notificationsManager.js.map +1 -1
- package/dist/package.json +0 -1
- package/dist/presence.d.ts +5 -3
- package/dist/presence.d.ts.map +1 -1
- package/dist/presence.js.map +1 -1
- package/dist/presenceDatastoreManager.d.ts +19 -5
- package/dist/presenceDatastoreManager.d.ts.map +1 -1
- package/dist/presenceDatastoreManager.js +100 -22
- package/dist/presenceDatastoreManager.js.map +1 -1
- package/dist/presenceManager.d.ts.map +1 -1
- package/dist/presenceManager.js +4 -4
- package/dist/presenceManager.js.map +1 -1
- package/dist/presenceStates.d.ts +29 -4
- package/dist/presenceStates.d.ts.map +1 -1
- package/dist/presenceStates.js +48 -14
- package/dist/presenceStates.js.map +1 -1
- package/dist/stateDatastore.d.ts +13 -3
- package/dist/stateDatastore.d.ts.map +1 -1
- package/dist/stateDatastore.js.map +1 -1
- package/dist/systemWorkspace.d.ts +1 -1
- package/dist/systemWorkspace.d.ts.map +1 -1
- package/dist/systemWorkspace.js.map +1 -1
- package/dist/timerManager.d.ts +37 -0
- package/dist/timerManager.d.ts.map +1 -0
- package/dist/timerManager.js +65 -0
- package/dist/timerManager.js.map +1 -0
- package/dist/types.d.ts +27 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/lib/alpha.d.ts +5 -4
- package/lib/broadcastControls.d.ts +70 -0
- package/lib/broadcastControls.d.ts.map +1 -0
- package/lib/broadcastControls.js +56 -0
- package/lib/broadcastControls.js.map +1 -0
- package/lib/exposedInternalTypes.d.ts +4 -1
- package/lib/exposedInternalTypes.d.ts.map +1 -1
- package/lib/exposedInternalTypes.js.map +1 -1
- package/lib/exposedUtilityTypes.d.ts +5 -5
- package/lib/exposedUtilityTypes.d.ts.map +1 -1
- package/lib/exposedUtilityTypes.js.map +1 -1
- package/lib/index.d.ts +4 -4
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/internalTypes.d.ts +2 -2
- package/lib/internalTypes.d.ts.map +1 -1
- package/lib/internalTypes.js.map +1 -1
- package/lib/latestMapValueManager.d.ts +5 -5
- package/lib/latestMapValueManager.d.ts.map +1 -1
- package/lib/latestMapValueManager.js +10 -14
- package/lib/latestMapValueManager.js.map +1 -1
- package/lib/latestValueManager.d.ts +5 -5
- package/lib/latestValueManager.d.ts.map +1 -1
- package/lib/latestValueManager.js +8 -12
- package/lib/latestValueManager.js.map +1 -1
- package/lib/notificationsManager.d.ts +28 -15
- package/lib/notificationsManager.d.ts.map +1 -1
- package/lib/notificationsManager.js +18 -10
- package/lib/notificationsManager.js.map +1 -1
- package/lib/presence.d.ts +5 -3
- package/lib/presence.d.ts.map +1 -1
- package/lib/presence.js.map +1 -1
- package/lib/presenceDatastoreManager.d.ts +19 -5
- package/lib/presenceDatastoreManager.d.ts.map +1 -1
- package/lib/presenceDatastoreManager.js +101 -23
- package/lib/presenceDatastoreManager.js.map +1 -1
- package/lib/presenceManager.d.ts.map +1 -1
- package/lib/presenceManager.js +3 -3
- package/lib/presenceManager.js.map +1 -1
- package/lib/presenceStates.d.ts +29 -4
- package/lib/presenceStates.d.ts.map +1 -1
- package/lib/presenceStates.js +47 -14
- package/lib/presenceStates.js.map +1 -1
- package/lib/stateDatastore.d.ts +13 -3
- package/lib/stateDatastore.d.ts.map +1 -1
- package/lib/stateDatastore.js.map +1 -1
- package/lib/systemWorkspace.d.ts +1 -1
- package/lib/systemWorkspace.d.ts.map +1 -1
- package/lib/systemWorkspace.js.map +1 -1
- package/lib/timerManager.d.ts +37 -0
- package/lib/timerManager.d.ts.map +1 -0
- package/lib/timerManager.js +61 -0
- package/lib/timerManager.js.map +1 -0
- package/lib/types.d.ts +27 -5
- package/lib/types.d.ts.map +1 -1
- package/lib/types.js.map +1 -1
- package/package.json +19 -23
- package/dist/events/events.d.ts +0 -198
- package/dist/events/events.d.ts.map +0 -1
- package/dist/events/events.js +0 -157
- package/dist/events/events.js.map +0 -1
- package/dist/latestValueControls.d.ts +0 -44
- package/dist/latestValueControls.d.ts.map +0 -1
- package/dist/latestValueControls.js +0 -28
- package/dist/latestValueControls.js.map +0 -1
- package/lib/events/events.d.ts +0 -198
- package/lib/events/events.d.ts.map +0 -1
- package/lib/events/events.js +0 -152
- package/lib/events/events.js.map +0 -1
- package/lib/latestValueControls.d.ts +0 -44
- package/lib/latestValueControls.d.ts.map +0 -1
- package/lib/latestValueControls.js +0 -24
- package/lib/latestValueControls.js.map +0 -1
|
@@ -5,13 +5,14 @@
|
|
|
5
5
|
import type { IInboundSignalMessage } from "@fluidframework/runtime-definitions/internal";
|
|
6
6
|
import type { ITelemetryLoggerExt } from "@fluidframework/telemetry-utils/internal";
|
|
7
7
|
import type { ClientConnectionId } from "./baseTypes.js";
|
|
8
|
+
import type { BroadcastControlSettings } from "./broadcastControls.js";
|
|
8
9
|
import type { IEphemeralRuntime } from "./internalTypes.js";
|
|
9
10
|
import type { ClientSessionId, ISessionClient } from "./presence.js";
|
|
10
11
|
import type { ClientUpdateEntry, PresenceStatesInternal } from "./presenceStates.js";
|
|
11
12
|
import type { SystemWorkspaceDatastore } from "./systemWorkspace.js";
|
|
12
13
|
import type { PresenceStates, PresenceStatesSchema, PresenceWorkspaceAddress } from "./types.js";
|
|
13
14
|
import type { IExtensionMessage } from "@fluidframework/presence/internal/container-definitions/internal";
|
|
14
|
-
interface
|
|
15
|
+
interface PresenceWorkspaceEntry<TSchema extends PresenceStatesSchema> {
|
|
15
16
|
public: PresenceStates<TSchema>;
|
|
16
17
|
internal: PresenceStatesInternal;
|
|
17
18
|
}
|
|
@@ -52,7 +53,7 @@ interface ClientJoinMessage extends IInboundSignalMessage {
|
|
|
52
53
|
*/
|
|
53
54
|
export interface PresenceDatastoreManager {
|
|
54
55
|
joinSession(clientId: ClientConnectionId): void;
|
|
55
|
-
getWorkspace<TSchema extends PresenceStatesSchema>(internalWorkspaceAddress: InternalWorkspaceAddress, requestedContent: TSchema): PresenceStates<TSchema>;
|
|
56
|
+
getWorkspace<TSchema extends PresenceStatesSchema>(internalWorkspaceAddress: InternalWorkspaceAddress, requestedContent: TSchema, controls?: BroadcastControlSettings): PresenceStates<TSchema>;
|
|
56
57
|
processSignal(message: IExtensionMessage, local: boolean): void;
|
|
57
58
|
}
|
|
58
59
|
/**
|
|
@@ -67,11 +68,24 @@ export declare class PresenceDatastoreManagerImpl implements PresenceDatastoreMa
|
|
|
67
68
|
private averageLatency;
|
|
68
69
|
private returnedMessages;
|
|
69
70
|
private refreshBroadcastRequested;
|
|
71
|
+
private readonly timer;
|
|
70
72
|
private readonly workspaces;
|
|
71
|
-
constructor(clientSessionId: ClientSessionId, runtime: IEphemeralRuntime, lookupClient: (clientId: ClientSessionId) => ISessionClient, logger: ITelemetryLoggerExt | undefined, systemWorkspaceDatastore: SystemWorkspaceDatastore, systemWorkspace:
|
|
73
|
+
constructor(clientSessionId: ClientSessionId, runtime: IEphemeralRuntime, lookupClient: (clientId: ClientSessionId) => ISessionClient, logger: ITelemetryLoggerExt | undefined, systemWorkspaceDatastore: SystemWorkspaceDatastore, systemWorkspace: PresenceWorkspaceEntry<PresenceStatesSchema>);
|
|
72
74
|
joinSession(clientId: ClientConnectionId): void;
|
|
73
|
-
getWorkspace<TSchema extends PresenceStatesSchema>(internalWorkspaceAddress: InternalWorkspaceAddress, requestedContent: TSchema): PresenceStates<TSchema>;
|
|
74
|
-
|
|
75
|
+
getWorkspace<TSchema extends PresenceStatesSchema>(internalWorkspaceAddress: InternalWorkspaceAddress, requestedContent: TSchema, controls?: BroadcastControlSettings): PresenceStates<TSchema>;
|
|
76
|
+
/**
|
|
77
|
+
* The combined contents of all queued updates. Will be undefined when no messages are queued.
|
|
78
|
+
*/
|
|
79
|
+
private queuedData;
|
|
80
|
+
/**
|
|
81
|
+
* Enqueues a new message to be sent. The message may be queued or may be sent immediately depending on the state of
|
|
82
|
+
* the send timer, other messages in the queue, the configured allowed latency, etc.
|
|
83
|
+
*/
|
|
84
|
+
private enqueueMessage;
|
|
85
|
+
/**
|
|
86
|
+
* Send any queued signal immediately. Does nothing if no message is queued.
|
|
87
|
+
*/
|
|
88
|
+
private sendQueuedMessage;
|
|
75
89
|
private broadcastAllKnownState;
|
|
76
90
|
processSignal(message: IInboundSignalMessage | DatastoreUpdateMessage | ClientJoinMessage, local: boolean): void;
|
|
77
91
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"presenceDatastoreManager.d.ts","sourceRoot":"","sources":["../src/presenceDatastoreManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,8CAA8C,CAAC;AAC1F,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0CAA0C,CAAC;AAEpF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACrE,OAAO,KAAK,EACX,iBAAiB,
|
|
1
|
+
{"version":3,"file":"presenceDatastoreManager.d.ts","sourceRoot":"","sources":["../src/presenceDatastoreManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,8CAA8C,CAAC;AAC1F,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0CAA0C,CAAC;AAEpF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AAEvE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACrE,OAAO,KAAK,EACX,iBAAiB,EAEjB,sBAAsB,EAEtB,MAAM,qBAAqB,CAAC;AAM7B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAErE,OAAO,KAAK,EACX,cAAc,EACd,oBAAoB,EACpB,wBAAwB,EACxB,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kEAAkE,CAAC;AAE1G,UAAU,sBAAsB,CAAC,OAAO,SAAS,oBAAoB;IACpE,MAAM,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC;IAChC,QAAQ,EAAE,sBAAsB,CAAC;CACjC;AAED,UAAU,eAAe;IACxB,iBAAiB,EAAE,wBAAwB,CAAC;CAC5C;AAED,KAAK,wBAAwB,GAAG,GAAG,GAAG,GAAG,GAAG,IAAI,wBAAwB,EAAE,CAAC;AAM3E,UAAU,8BAA8B;IACvC,CAAC,gBAAgB,EAAE,MAAM,GAAG;QAC3B,CAAC,oBAAoB,EAAE,MAAM,GAAG;YAC/B,CAAC,eAAe,EAAE,eAAe,GAAG,iBAAiB,CAAC;SACtD,CAAC;KACF,CAAC;CACF;AAED,KAAK,uBAAuB,GAAG,eAAe,GAAG,8BAA8B,CAAC;AAEhF,QAAA,MAAM,0BAA0B,yBAAyB,CAAC;AAC1D,UAAU,sBAAuB,SAAQ,qBAAqB;IAC7D,IAAI,EAAE,OAAO,0BAA0B,CAAC;IACxC,OAAO,EAAE;QACR,aAAa,EAAE,MAAM,CAAC;QACtB,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,CAAC,EAAE,IAAI,CAAC;QAClB,IAAI,EAAE,uBAAuB,CAAC;KAC9B,CAAC;CACF;AAED,QAAA,MAAM,eAAe,oBAAoB,CAAC;AAC1C,UAAU,iBAAkB,SAAQ,qBAAqB;IACxD,IAAI,EAAE,OAAO,eAAe,CAAC;IAC7B,OAAO,EAAE;QACR,eAAe,EAAE,kBAAkB,EAAE,CAAC;QACtC,aAAa,EAAE,MAAM,CAAC;QACtB,UAAU,EAAE,MAAM,CAAC;QACnB,IAAI,EAAE,uBAAuB,CAAC;KAC9B,CAAC;CACF;AAQD;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACxC,WAAW,CAAC,QAAQ,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAChD,YAAY,CAAC,OAAO,SAAS,oBAAoB,EAChD,wBAAwB,EAAE,wBAAwB,EAClD,gBAAgB,EAAE,OAAO,EACzB,QAAQ,CAAC,EAAE,wBAAwB,GACjC,cAAc,CAAC,OAAO,CAAC,CAAC;IAC3B,aAAa,CAAC,OAAO,EAAE,iBAAiB,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;CAChE;AAuCD;;GAEG;AACH,qBAAa,4BAA6B,YAAW,wBAAwB;IAY3E,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,MAAM;IAdxB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAoB;IAC9C,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,yBAAyB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAsB;IAC5C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAGvB;gBAGc,eAAe,EAAE,eAAe,EAChC,OAAO,EAAE,iBAAiB,EAC1B,YAAY,EAAE,CAAC,QAAQ,EAAE,eAAe,KAAK,cAAc,EAC3D,MAAM,EAAE,mBAAmB,GAAG,SAAS,EACxD,wBAAwB,EAAE,wBAAwB,EAClD,eAAe,EAAE,sBAAsB,CAAC,oBAAoB,CAAC;IAOvD,WAAW,CAAC,QAAQ,EAAE,kBAAkB,GAAG,IAAI;IAkB/C,YAAY,CAAC,OAAO,SAAS,oBAAoB,EACvD,wBAAwB,EAAE,wBAAwB,EAClD,gBAAgB,EAAE,OAAO,EACzB,QAAQ,CAAC,EAAE,wBAAwB,GACjC,cAAc,CAAC,OAAO,CAAC;IAgD1B;;OAEG;IACH,OAAO,CAAC,UAAU,CAA6C;IAE/D;;;OAGG;IACH,OAAO,CAAC,cAAc;IAuCtB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAyCzB,OAAO,CAAC,sBAAsB;IAUvB,aAAa,CAOnB,OAAO,EAAE,qBAAqB,GAAG,sBAAsB,GAAG,iBAAiB,EAC3E,KAAK,EAAE,OAAO,GACZ,IAAI;IAkEP;;;;;;;;;;OAUG;IACH,OAAO,CAAC,mBAAmB;CAgE3B"}
|
|
@@ -3,12 +3,38 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
import { assert } from "@fluidframework/core-utils/internal";
|
|
6
|
-
import {
|
|
6
|
+
import { brandedObjectEntries } from "./internalTypes.js";
|
|
7
|
+
import { createPresenceStates, mergeUntrackedDatastore, mergeValueDirectory, } from "./presenceStates.js";
|
|
8
|
+
import { TimerManager } from "./timerManager.js";
|
|
7
9
|
const datastoreUpdateMessageType = "Pres:DatastoreUpdate";
|
|
8
10
|
const joinMessageType = "Pres:ClientJoin";
|
|
9
11
|
function isPresenceMessage(message) {
|
|
10
12
|
return message.type.startsWith("Pres:");
|
|
11
13
|
}
|
|
14
|
+
function mergeGeneralDatastoreMessageContent(base, newData) {
|
|
15
|
+
// This function-local "datastore" will hold the merged message data.
|
|
16
|
+
const queueDatastore = base ?? {};
|
|
17
|
+
// Merge the current data with the existing data, if any exists.
|
|
18
|
+
// Iterate over the current message data; individual items are workspaces.
|
|
19
|
+
for (const [workspaceName, workspaceData] of Object.entries(newData)) {
|
|
20
|
+
// Initialize the merged data as the queued datastore entry for the workspace.
|
|
21
|
+
// Since the key might not exist, create an empty object in that case. It will
|
|
22
|
+
// be set explicitly after the loop.
|
|
23
|
+
const mergedData = queueDatastore[workspaceName] ?? {};
|
|
24
|
+
// Iterate over each value manager and its data, merging it as needed.
|
|
25
|
+
for (const valueManagerKey of Object.keys(workspaceData)) {
|
|
26
|
+
for (const [clientSessionId, value] of brandedObjectEntries(workspaceData[valueManagerKey])) {
|
|
27
|
+
mergedData[valueManagerKey] ?? (mergedData[valueManagerKey] = {});
|
|
28
|
+
const oldData = mergedData[valueManagerKey][clientSessionId];
|
|
29
|
+
mergedData[valueManagerKey][clientSessionId] = mergeValueDirectory(oldData, value, 0);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// Store the merged data in the function-local queue workspace. The whole contents of this
|
|
33
|
+
// datastore will be sent as the message data.
|
|
34
|
+
queueDatastore[workspaceName] = mergedData;
|
|
35
|
+
}
|
|
36
|
+
return queueDatastore;
|
|
37
|
+
}
|
|
12
38
|
/**
|
|
13
39
|
* Manages singleton datastore for all Presence.
|
|
14
40
|
*/
|
|
@@ -21,6 +47,7 @@ export class PresenceDatastoreManagerImpl {
|
|
|
21
47
|
this.averageLatency = 0;
|
|
22
48
|
this.returnedMessages = 0;
|
|
23
49
|
this.refreshBroadcastRequested = false;
|
|
50
|
+
this.timer = new TimerManager();
|
|
24
51
|
this.workspaces = new Map();
|
|
25
52
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
26
53
|
this.datastore = { "system:presence": systemWorkspaceDatastore };
|
|
@@ -41,56 +68,107 @@ export class PresenceDatastoreManagerImpl {
|
|
|
41
68
|
updateProviders,
|
|
42
69
|
});
|
|
43
70
|
}
|
|
44
|
-
getWorkspace(internalWorkspaceAddress, requestedContent) {
|
|
71
|
+
getWorkspace(internalWorkspaceAddress, requestedContent, controls) {
|
|
45
72
|
const existing = this.workspaces.get(internalWorkspaceAddress);
|
|
46
73
|
if (existing) {
|
|
47
|
-
return existing.internal.ensureContent(requestedContent);
|
|
74
|
+
return existing.internal.ensureContent(requestedContent, controls);
|
|
48
75
|
}
|
|
49
76
|
let workspaceDatastore = this.datastore[internalWorkspaceAddress];
|
|
50
77
|
if (workspaceDatastore === undefined) {
|
|
51
78
|
workspaceDatastore = this.datastore[internalWorkspaceAddress] = {};
|
|
52
79
|
}
|
|
53
|
-
const localUpdate = (states,
|
|
80
|
+
const localUpdate = (states, options) => {
|
|
54
81
|
// Check for connectivity before sending updates.
|
|
55
82
|
if (!this.runtime.connected) {
|
|
56
83
|
return;
|
|
57
84
|
}
|
|
58
|
-
const clientConnectionId = this.runtime.clientId;
|
|
59
|
-
assert(clientConnectionId !== undefined, 0xa59 /* Client connected without clientId */);
|
|
60
|
-
const currentClientToSessionValueState = this.datastore["system:presence"].clientToSessionId[clientConnectionId];
|
|
61
85
|
const updates = {};
|
|
62
86
|
for (const [key, value] of Object.entries(states)) {
|
|
63
87
|
updates[key] = { [this.clientSessionId]: value };
|
|
64
88
|
}
|
|
65
|
-
this.
|
|
66
|
-
// Always send current connection mapping for some resiliency against
|
|
67
|
-
// lost signals. This ensures that client session id found in `updates`
|
|
68
|
-
// (which is this client's client session id) is always represented in
|
|
69
|
-
// system workspace of recipient clients.
|
|
70
|
-
"system:presence": {
|
|
71
|
-
clientToSessionId: {
|
|
72
|
-
[clientConnectionId]: { ...currentClientToSessionValueState },
|
|
73
|
-
},
|
|
74
|
-
},
|
|
89
|
+
this.enqueueMessage({
|
|
75
90
|
[internalWorkspaceAddress]: updates,
|
|
76
|
-
},
|
|
91
|
+
}, options);
|
|
77
92
|
};
|
|
78
93
|
const entry = createPresenceStates({
|
|
79
94
|
clientSessionId: this.clientSessionId,
|
|
80
95
|
lookupClient: this.lookupClient,
|
|
81
96
|
localUpdate,
|
|
82
|
-
}, workspaceDatastore, requestedContent);
|
|
97
|
+
}, workspaceDatastore, requestedContent, controls);
|
|
83
98
|
this.workspaces.set(internalWorkspaceAddress, entry);
|
|
84
99
|
return entry.public;
|
|
85
100
|
}
|
|
86
|
-
|
|
87
|
-
|
|
101
|
+
/**
|
|
102
|
+
* Enqueues a new message to be sent. The message may be queued or may be sent immediately depending on the state of
|
|
103
|
+
* the send timer, other messages in the queue, the configured allowed latency, etc.
|
|
104
|
+
*/
|
|
105
|
+
enqueueMessage(data, options) {
|
|
106
|
+
// Merging the message with any queued messages effectively queues the message.
|
|
107
|
+
// It is OK to queue all incoming messages as long as when we send, we send the queued data.
|
|
108
|
+
this.queuedData = mergeGeneralDatastoreMessageContent(this.queuedData, data);
|
|
109
|
+
const { allowableUpdateLatencyMs } = options;
|
|
110
|
+
const now = Date.now();
|
|
111
|
+
const thisMessageDeadline = now + allowableUpdateLatencyMs;
|
|
112
|
+
if (
|
|
113
|
+
// If the timer has not expired, we can short-circuit because the timer will fire
|
|
114
|
+
// and cover this update. In other words, queuing this will be fast enough to
|
|
115
|
+
// meet its deadline, because a timer is already scheduled to fire before its deadline.
|
|
116
|
+
!this.timer.hasExpired() &&
|
|
117
|
+
// If the deadline for this message is later than the overall send deadline, then
|
|
118
|
+
// we can exit early since a timer will take care of sending it.
|
|
119
|
+
thisMessageDeadline >= this.timer.expireTime) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
// Either we need to send this message immediately, or we need to schedule a timer
|
|
123
|
+
// to fire at the send deadline that will take care of it.
|
|
124
|
+
// Note that timeoutInMs === allowableUpdateLatency, but the calculation is done this way for clarity.
|
|
125
|
+
const timeoutInMs = thisMessageDeadline - now;
|
|
126
|
+
const scheduleForLater = timeoutInMs > 0;
|
|
127
|
+
if (scheduleForLater) {
|
|
128
|
+
// Schedule the queued messages to be sent at the updateDeadline
|
|
129
|
+
this.timer.setTimeout(this.sendQueuedMessage.bind(this), timeoutInMs);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
this.sendQueuedMessage();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Send any queued signal immediately. Does nothing if no message is queued.
|
|
137
|
+
*/
|
|
138
|
+
sendQueuedMessage() {
|
|
139
|
+
this.timer.clearTimeout();
|
|
140
|
+
if (this.queuedData === undefined) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
// Check for connectivity before sending updates.
|
|
144
|
+
if (!this.runtime.connected) {
|
|
145
|
+
// Clear the queued data since we're disconnected. We don't want messages
|
|
146
|
+
// to queue infinitely while disconnected.
|
|
147
|
+
this.queuedData = undefined;
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const clientConnectionId = this.runtime.clientId;
|
|
151
|
+
assert(clientConnectionId !== undefined, 0xa59 /* Client connected without clientId */);
|
|
152
|
+
const currentClientToSessionValueState = this.datastore["system:presence"].clientToSessionId[clientConnectionId];
|
|
153
|
+
const newMessage = {
|
|
88
154
|
sendTimestamp: Date.now(),
|
|
89
155
|
avgLatency: this.averageLatency,
|
|
90
156
|
// isComplete: false,
|
|
91
|
-
data
|
|
157
|
+
data: {
|
|
158
|
+
// Always send current connection mapping for some resiliency against
|
|
159
|
+
// lost signals. This ensures that client session id found in `updates`
|
|
160
|
+
// (which is this client's client session id) is always represented in
|
|
161
|
+
// system workspace of recipient clients.
|
|
162
|
+
"system:presence": {
|
|
163
|
+
clientToSessionId: {
|
|
164
|
+
[clientConnectionId]: { ...currentClientToSessionValueState },
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
...this.queuedData,
|
|
168
|
+
},
|
|
92
169
|
};
|
|
93
|
-
this.
|
|
170
|
+
this.queuedData = undefined;
|
|
171
|
+
this.runtime.submitSignal(datastoreUpdateMessageType, newMessage);
|
|
94
172
|
}
|
|
95
173
|
broadcastAllKnownState() {
|
|
96
174
|
this.runtime.submitSignal(datastoreUpdateMessageType, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"presenceDatastoreManager.js","sourceRoot":"","sources":["../src/presenceDatastoreManager.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { assert } from \"@fluidframework/core-utils/internal\";\nimport type { IInboundSignalMessage } from \"@fluidframework/runtime-definitions/internal\";\nimport type { ITelemetryLoggerExt } from \"@fluidframework/telemetry-utils/internal\";\n\nimport type { ClientConnectionId } from \"./baseTypes.js\";\nimport type { IEphemeralRuntime } from \"./internalTypes.js\";\nimport type { ClientSessionId, ISessionClient } from \"./presence.js\";\nimport type {\n\tClientUpdateEntry,\n\tPresenceStatesInternal,\n\tValueElementMap,\n} from \"./presenceStates.js\";\nimport { createPresenceStates, mergeUntrackedDatastore } from \"./presenceStates.js\";\nimport type { SystemWorkspaceDatastore } from \"./systemWorkspace.js\";\nimport type {\n\tPresenceStates,\n\tPresenceStatesSchema,\n\tPresenceWorkspaceAddress,\n} from \"./types.js\";\n\nimport type { IExtensionMessage } from \"@fluidframework/presence/internal/container-definitions/internal\";\n\ninterface PresenceStatesEntry<TSchema extends PresenceStatesSchema> {\n\tpublic: PresenceStates<TSchema>;\n\tinternal: PresenceStatesInternal;\n}\n\ninterface SystemDatastore {\n\t\"system:presence\": SystemWorkspaceDatastore;\n}\n\ntype InternalWorkspaceAddress = `${\"s\" | \"n\"}:${PresenceWorkspaceAddress}`;\n\ntype PresenceDatastore = SystemDatastore & {\n\t[WorkspaceAddress: string]: ValueElementMap<PresenceStatesSchema>;\n};\n\ninterface GeneralDatastoreMessageContent {\n\t[WorkspaceAddress: string]: {\n\t\t[StateValueManagerKey: string]: {\n\t\t\t[ClientSessionId: ClientSessionId]: ClientUpdateEntry;\n\t\t};\n\t};\n}\n\ntype DatastoreMessageContent = SystemDatastore & GeneralDatastoreMessageContent;\n\nconst datastoreUpdateMessageType = \"Pres:DatastoreUpdate\";\ninterface DatastoreUpdateMessage extends IInboundSignalMessage {\n\ttype: typeof datastoreUpdateMessageType;\n\tcontent: {\n\t\tsendTimestamp: number;\n\t\tavgLatency: number;\n\t\tisComplete?: true;\n\t\tdata: DatastoreMessageContent;\n\t};\n}\n\nconst joinMessageType = \"Pres:ClientJoin\";\ninterface ClientJoinMessage extends IInboundSignalMessage {\n\ttype: typeof joinMessageType;\n\tcontent: {\n\t\tupdateProviders: ClientConnectionId[];\n\t\tsendTimestamp: number;\n\t\tavgLatency: number;\n\t\tdata: DatastoreMessageContent;\n\t};\n}\n\nfunction isPresenceMessage(\n\tmessage: IInboundSignalMessage,\n): message is DatastoreUpdateMessage | ClientJoinMessage {\n\treturn message.type.startsWith(\"Pres:\");\n}\n\n/**\n * @internal\n */\nexport interface PresenceDatastoreManager {\n\tjoinSession(clientId: ClientConnectionId): void;\n\tgetWorkspace<TSchema extends PresenceStatesSchema>(\n\t\tinternalWorkspaceAddress: InternalWorkspaceAddress,\n\t\trequestedContent: TSchema,\n\t): PresenceStates<TSchema>;\n\tprocessSignal(message: IExtensionMessage, local: boolean): void;\n}\n\n/**\n * Manages singleton datastore for all Presence.\n */\nexport class PresenceDatastoreManagerImpl implements PresenceDatastoreManager {\n\tprivate readonly datastore: PresenceDatastore;\n\tprivate averageLatency = 0;\n\tprivate returnedMessages = 0;\n\tprivate refreshBroadcastRequested = false;\n\n\tprivate readonly workspaces = new Map<string, PresenceStatesEntry<PresenceStatesSchema>>();\n\n\tpublic constructor(\n\t\tprivate readonly clientSessionId: ClientSessionId,\n\t\tprivate readonly runtime: IEphemeralRuntime,\n\t\tprivate readonly lookupClient: (clientId: ClientSessionId) => ISessionClient,\n\t\tprivate readonly logger: ITelemetryLoggerExt | undefined,\n\t\tsystemWorkspaceDatastore: SystemWorkspaceDatastore,\n\t\tsystemWorkspace: PresenceStatesEntry<PresenceStatesSchema>,\n\t) {\n\t\t// eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n\t\tthis.datastore = { \"system:presence\": systemWorkspaceDatastore } as PresenceDatastore;\n\t\tthis.workspaces.set(\"system:presence\", systemWorkspace);\n\t}\n\n\tpublic joinSession(clientId: ClientConnectionId): void {\n\t\t// Broadcast join message to all clients\n\t\tconst updateProviders = [...this.runtime.getQuorum().getMembers().keys()].filter(\n\t\t\t(quorumClientId) => quorumClientId !== clientId,\n\t\t);\n\t\t// Limit to three providers to prevent flooding the network.\n\t\t// If none respond, others present will (should) after a delay.\n\t\tif (updateProviders.length > 3) {\n\t\t\tupdateProviders.length = 3;\n\t\t}\n\t\tthis.runtime.submitSignal(joinMessageType, {\n\t\t\tsendTimestamp: Date.now(),\n\t\t\tavgLatency: this.averageLatency,\n\t\t\tdata: this.datastore,\n\t\t\tupdateProviders,\n\t\t} satisfies ClientJoinMessage[\"content\"]);\n\t}\n\n\tpublic getWorkspace<TSchema extends PresenceStatesSchema>(\n\t\tinternalWorkspaceAddress: InternalWorkspaceAddress,\n\t\trequestedContent: TSchema,\n\t): PresenceStates<TSchema> {\n\t\tconst existing = this.workspaces.get(internalWorkspaceAddress);\n\t\tif (existing) {\n\t\t\treturn existing.internal.ensureContent(requestedContent);\n\t\t}\n\n\t\tlet workspaceDatastore = this.datastore[internalWorkspaceAddress];\n\t\tif (workspaceDatastore === undefined) {\n\t\t\tworkspaceDatastore = this.datastore[internalWorkspaceAddress] = {};\n\t\t}\n\n\t\tconst localUpdate = (\n\t\t\tstates: { [key: string]: ClientUpdateEntry },\n\t\t\tforceBroadcast: boolean,\n\t\t): void => {\n\t\t\t// Check for connectivity before sending updates.\n\t\t\tif (!this.runtime.connected) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst clientConnectionId = this.runtime.clientId;\n\t\t\tassert(clientConnectionId !== undefined, 0xa59 /* Client connected without clientId */);\n\t\t\tconst currentClientToSessionValueState =\n\t\t\t\tthis.datastore[\"system:presence\"].clientToSessionId[clientConnectionId];\n\n\t\t\tconst updates: GeneralDatastoreMessageContent[InternalWorkspaceAddress] = {};\n\t\t\tfor (const [key, value] of Object.entries(states)) {\n\t\t\t\tupdates[key] = { [this.clientSessionId]: value };\n\t\t\t}\n\t\t\tthis.localUpdate(\n\t\t\t\t{\n\t\t\t\t\t// Always send current connection mapping for some resiliency against\n\t\t\t\t\t// lost signals. This ensures that client session id found in `updates`\n\t\t\t\t\t// (which is this client's client session id) is always represented in\n\t\t\t\t\t// system workspace of recipient clients.\n\t\t\t\t\t\"system:presence\": {\n\t\t\t\t\t\tclientToSessionId: {\n\t\t\t\t\t\t\t[clientConnectionId]: { ...currentClientToSessionValueState },\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t[internalWorkspaceAddress]: updates,\n\t\t\t\t},\n\t\t\t\tforceBroadcast,\n\t\t\t);\n\t\t};\n\n\t\tconst entry = createPresenceStates(\n\t\t\t{\n\t\t\t\tclientSessionId: this.clientSessionId,\n\t\t\t\tlookupClient: this.lookupClient,\n\t\t\t\tlocalUpdate,\n\t\t\t},\n\t\t\tworkspaceDatastore,\n\t\t\trequestedContent,\n\t\t);\n\n\t\tthis.workspaces.set(internalWorkspaceAddress, entry);\n\t\treturn entry.public;\n\t}\n\n\tprivate localUpdate(data: DatastoreMessageContent, _forceBroadcast: boolean): void {\n\t\tconst content = {\n\t\t\tsendTimestamp: Date.now(),\n\t\t\tavgLatency: this.averageLatency,\n\t\t\t// isComplete: false,\n\t\t\tdata,\n\t\t} satisfies DatastoreUpdateMessage[\"content\"];\n\t\tthis.runtime.submitSignal(datastoreUpdateMessageType, content);\n\t}\n\n\tprivate broadcastAllKnownState(): void {\n\t\tthis.runtime.submitSignal(datastoreUpdateMessageType, {\n\t\t\tsendTimestamp: Date.now(),\n\t\t\tavgLatency: this.averageLatency,\n\t\t\tisComplete: true,\n\t\t\tdata: this.datastore,\n\t\t} satisfies DatastoreUpdateMessage[\"content\"]);\n\t\tthis.refreshBroadcastRequested = false;\n\t}\n\n\tpublic processSignal(\n\t\t// Note: IInboundSignalMessage is used here in place of IExtensionMessage\n\t\t// as IExtensionMessage's strictly JSON `content` creates type compatibility\n\t\t// issues with `ClientSessionId` keys and really unknown value content.\n\t\t// IExtensionMessage is a subset of IInboundSignalMessage so this is safe.\n\t\t// Change types of DatastoreUpdateMessage | ClientJoinMessage to\n\t\t// IExtensionMessage<> derivatives to see the issues.\n\t\tmessage: IInboundSignalMessage | DatastoreUpdateMessage | ClientJoinMessage,\n\t\tlocal: boolean,\n\t): void {\n\t\tconst received = Date.now();\n\t\tassert(message.clientId !== null, 0xa3a /* Map received signal without clientId */);\n\t\tif (!isPresenceMessage(message)) {\n\t\t\treturn;\n\t\t}\n\t\tif (local) {\n\t\t\tconst deliveryDelta = received - message.content.sendTimestamp;\n\t\t\t// Limit returnedMessages count to 256 such that newest message\n\t\t\t// always contributes at least 1/256th to the average. Older\n\t\t\t// messages have more weight, but that diminishes as new messages\n\t\t\t// contribute.\n\t\t\tthis.returnedMessages = Math.min(this.returnedMessages + 1, 256);\n\t\t\tthis.averageLatency =\n\t\t\t\t(this.averageLatency * (this.returnedMessages - 1) + deliveryDelta) /\n\t\t\t\tthis.returnedMessages;\n\t\t\treturn;\n\t\t}\n\n\t\tconst timeModifier =\n\t\t\treceived -\n\t\t\t(this.averageLatency + message.content.avgLatency + message.content.sendTimestamp);\n\n\t\tif (message.type === joinMessageType) {\n\t\t\t// It is possible for some signals to come in while client is not connected due\n\t\t\t// to how work is scheduled. If we are not connected, we can't respond to the\n\t\t\t// join request. We will make our own Join request once we are connected.\n\t\t\tif (this.runtime.connected) {\n\t\t\t\tthis.prepareJoinResponse(message.content.updateProviders, message.clientId);\n\t\t\t}\n\t\t\t// It is okay to continue processing the contained updates even if we are not\n\t\t\t// connected.\n\t\t} else {\n\t\t\tassert(message.type === datastoreUpdateMessageType, 0xa3b /* Unexpected message type */);\n\t\t\tif (message.content.isComplete) {\n\t\t\t\tthis.refreshBroadcastRequested = false;\n\t\t\t}\n\t\t}\n\n\t\tfor (const [workspaceAddress, remoteDatastore] of Object.entries(message.content.data)) {\n\t\t\t// Direct to the appropriate Presence Workspace, if present.\n\t\t\tconst workspace = this.workspaces.get(workspaceAddress);\n\t\t\tif (workspace) {\n\t\t\t\tworkspace.internal.processUpdate(\n\t\t\t\t\treceived,\n\t\t\t\t\ttimeModifier,\n\t\t\t\t\tremoteDatastore,\n\t\t\t\t\tmessage.clientId,\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\t// All broadcast state is kept even if not currently registered, unless a value\n\t\t\t\t// notes itself to be ignored.\n\t\t\t\tlet workspaceDatastore = this.datastore[workspaceAddress];\n\t\t\t\tif (workspaceDatastore === undefined) {\n\t\t\t\t\tworkspaceDatastore = this.datastore[workspaceAddress] = {};\n\t\t\t\t\tif (!workspaceAddress.startsWith(\"system:\")) {\n\t\t\t\t\t\t// TODO: Emit workspaceActivated event for PresenceEvents\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor (const [key, remoteAllKnownState] of Object.entries(remoteDatastore)) {\n\t\t\t\t\tmergeUntrackedDatastore(key, remoteAllKnownState, workspaceDatastore, timeModifier);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Handles responding to another client joining the session.\n\t *\n\t * @param updateProviders - list of client connection id's that requestor selected\n\t * to provide response\n\t * @param requestor - `requestor` is only used in telemetry. While it is the requestor's\n\t * client connection id, that is not most important. It is important that this is a\n\t * unique shared id across all clients that might respond as we want to monitor the\n\t * response patterns. The convenience of being client connection id will allow\n\t * correlation with other telemetry where it is often called just `clientId`.\n\t */\n\tprivate prepareJoinResponse(\n\t\tupdateProviders: ClientConnectionId[],\n\t\trequestor: ClientConnectionId,\n\t): void {\n\t\tthis.refreshBroadcastRequested = true;\n\t\t// We must be connected to receive this message, so clientId should be defined.\n\t\t// If it isn't then, not really a problem; just won't be in provider or quorum list.\n\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\tconst clientId = this.runtime.clientId!;\n\t\t// const requestor = message.clientId;\n\t\tif (updateProviders.includes(clientId)) {\n\t\t\t// Send all current state to the new client\n\t\t\tthis.broadcastAllKnownState();\n\t\t\tthis.logger?.sendTelemetryEvent({\n\t\t\t\teventName: \"JoinResponse\",\n\t\t\t\tdetails: {\n\t\t\t\t\ttype: \"broadcastAll\",\n\t\t\t\t\trequestor,\n\t\t\t\t\trole: \"primary\",\n\t\t\t\t},\n\t\t\t});\n\t\t} else {\n\t\t\t// Schedule a broadcast to the new client after a delay only to send if\n\t\t\t// another broadcast hasn't been seen in the meantime. The delay is based\n\t\t\t// on the position in the quorum list. It doesn't have to be a stable\n\t\t\t// list across all clients. We need something to provide suggested order\n\t\t\t// to prevent a flood of broadcasts.\n\t\t\tlet relativeResponseOrder;\n\t\t\tconst quorumMembers = this.runtime.getQuorum().getMembers();\n\t\t\tconst self = quorumMembers.get(clientId);\n\t\t\tif (self) {\n\t\t\t\t// Compute order quorum join order (indicated by sequenceNumber)\n\t\t\t\trelativeResponseOrder = 0;\n\t\t\t\tfor (const { sequenceNumber } of quorumMembers.values()) {\n\t\t\t\t\tif (sequenceNumber < self.sequenceNumber) {\n\t\t\t\t\t\trelativeResponseOrder++;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Order past quorum members + arbitrary additional offset up to 10\n\t\t\t\trelativeResponseOrder = quorumMembers.size + Math.random() * 10;\n\t\t\t}\n\t\t\t// These numbers have been chosen arbitrarily to start with.\n\t\t\t// 20 is minimum wait time, 20 is the additional wait time per provider\n\t\t\t// given an chance before us with named providers given more time.\n\t\t\tconst waitTime = 20 + 20 * (3 * updateProviders.length + relativeResponseOrder);\n\t\t\tsetTimeout(() => {\n\t\t\t\t// Make sure a broadcast is still needed and we are currently connected.\n\t\t\t\t// If not connected, nothing we can do.\n\t\t\t\tif (this.refreshBroadcastRequested && this.runtime.connected) {\n\t\t\t\t\tthis.broadcastAllKnownState();\n\t\t\t\t\tthis.logger?.sendTelemetryEvent({\n\t\t\t\t\t\teventName: \"JoinResponse\",\n\t\t\t\t\t\tdetails: {\n\t\t\t\t\t\t\ttype: \"broadcastAll\",\n\t\t\t\t\t\t\trequestor,\n\t\t\t\t\t\t\trole: \"secondary\",\n\t\t\t\t\t\t\torder: relativeResponseOrder,\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}, waitTime);\n\t\t}\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"presenceDatastoreManager.js","sourceRoot":"","sources":["../src/presenceDatastoreManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,qCAAqC,CAAC;AAM7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAS1D,OAAO,EACN,oBAAoB,EACpB,uBAAuB,EACvB,mBAAmB,GACnB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAkCjD,MAAM,0BAA0B,GAAG,sBAAsB,CAAC;AAW1D,MAAM,eAAe,GAAG,iBAAiB,CAAC;AAW1C,SAAS,iBAAiB,CACzB,OAA8B;IAE9B,OAAO,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;AACzC,CAAC;AAeD,SAAS,mCAAmC,CAC3C,IAAgD,EAChD,OAAuC;IAEvC,qEAAqE;IACrE,MAAM,cAAc,GAAG,IAAI,IAAI,EAAE,CAAC;IAElC,gEAAgE;IAChE,0EAA0E;IAC1E,KAAK,MAAM,CAAC,aAAa,EAAE,aAAa,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACtE,8EAA8E;QAC9E,8EAA8E;QAC9E,oCAAoC;QACpC,MAAM,UAAU,GAAG,cAAc,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC;QAEvD,sEAAsE;QACtE,KAAK,MAAM,eAAe,IAAI,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC;YAC1D,KAAK,MAAM,CAAC,eAAe,EAAE,KAAK,CAAC,IAAI,oBAAoB,CAC1D,aAAa,CAAC,eAAe,CAAC,CAC9B,EAAE,CAAC;gBACH,UAAU,CAAC,eAAe,MAA1B,UAAU,CAAC,eAAe,IAAM,EAAE,EAAC;gBACnC,MAAM,OAAO,GAAG,UAAU,CAAC,eAAe,CAAC,CAAC,eAAe,CAAC,CAAC;gBAC7D,UAAU,CAAC,eAAe,CAAC,CAAC,eAAe,CAAC,GAAG,mBAAmB,CACjE,OAAO,EACP,KAAK,EACL,CAAC,CACD,CAAC;YACH,CAAC;QACF,CAAC;QAED,0FAA0F;QAC1F,8CAA8C;QAC9C,cAAc,CAAC,aAAa,CAAC,GAAG,UAAU,CAAC;IAC5C,CAAC;IACD,OAAO,cAAc,CAAC;AACvB,CAAC;AAED;;GAEG;AACH,MAAM,OAAO,4BAA4B;IAWxC,YACkB,eAAgC,EAChC,OAA0B,EAC1B,YAA2D,EAC3D,MAAuC,EACxD,wBAAkD,EAClD,eAA6D;QAL5C,oBAAe,GAAf,eAAe,CAAiB;QAChC,YAAO,GAAP,OAAO,CAAmB;QAC1B,iBAAY,GAAZ,YAAY,CAA+C;QAC3D,WAAM,GAAN,MAAM,CAAiC;QAbjD,mBAAc,GAAG,CAAC,CAAC;QACnB,qBAAgB,GAAG,CAAC,CAAC;QACrB,8BAAyB,GAAG,KAAK,CAAC;QACzB,UAAK,GAAG,IAAI,YAAY,EAAE,CAAC;QAC3B,eAAU,GAAG,IAAI,GAAG,EAGlC,CAAC;QAUH,yEAAyE;QACzE,IAAI,CAAC,SAAS,GAAG,EAAE,iBAAiB,EAAE,wBAAwB,EAAuB,CAAC;QACtF,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,iBAAiB,EAAE,eAAe,CAAC,CAAC;IACzD,CAAC;IAEM,WAAW,CAAC,QAA4B;QAC9C,wCAAwC;QACxC,MAAM,eAAe,GAAG,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAC/E,CAAC,cAAc,EAAE,EAAE,CAAC,cAAc,KAAK,QAAQ,CAC/C,CAAC;QACF,4DAA4D;QAC5D,+DAA+D;QAC/D,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAChC,eAAe,CAAC,MAAM,GAAG,CAAC,CAAC;QAC5B,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,eAAe,EAAE;YAC1C,aAAa,EAAE,IAAI,CAAC,GAAG,EAAE;YACzB,UAAU,EAAE,IAAI,CAAC,cAAc;YAC/B,IAAI,EAAE,IAAI,CAAC,SAAS;YACpB,eAAe;SACwB,CAAC,CAAC;IAC3C,CAAC;IAEM,YAAY,CAClB,wBAAkD,EAClD,gBAAyB,EACzB,QAAmC;QAEnC,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;QAC/D,IAAI,QAAQ,EAAE,CAAC;YACd,OAAO,QAAQ,CAAC,QAAQ,CAAC,aAAa,CAAC,gBAAgB,EAAE,QAAQ,CAAC,CAAC;QACpE,CAAC;QAED,IAAI,kBAAkB,GAAG,IAAI,CAAC,SAAS,CAAC,wBAAwB,CAAC,CAAC;QAClE,IAAI,kBAAkB,KAAK,SAAS,EAAE,CAAC;YACtC,kBAAkB,GAAG,IAAI,CAAC,SAAS,CAAC,wBAAwB,CAAC,GAAG,EAAE,CAAC;QACpE,CAAC;QAED,MAAM,WAAW,GAAG,CACnB,MAA4C,EAC5C,OAAkC,EAC3B,EAAE;YACT,iDAAiD;YACjD,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC;gBAC7B,OAAO;YACR,CAAC;YAED,MAAM,OAAO,GAA6D,EAAE,CAAC;YAC7E,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;gBACnD,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,CAAC;YAClD,CAAC;YAED,IAAI,CAAC,cAAc,CAClB;gBACC,CAAC,wBAAwB,CAAC,EAAE,OAAO;aACnC,EACD,OAAO,CACP,CAAC;QACH,CAAC,CAAC;QAEF,MAAM,KAAK,GAAG,oBAAoB,CACjC;YACC,eAAe,EAAE,IAAI,CAAC,eAAe;YACrC,YAAY,EAAE,IAAI,CAAC,YAAY;YAC/B,WAAW;SACX,EACD,kBAAkB,EAClB,gBAAgB,EAChB,QAAQ,CACR,CAAC;QAEF,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,wBAAwB,EAAE,KAAK,CAAC,CAAC;QACrD,OAAO,KAAK,CAAC,MAAM,CAAC;IACrB,CAAC;IAOD;;;OAGG;IACK,cAAc,CACrB,IAAoC,EACpC,OAAkC;QAElC,+EAA+E;QAC/E,4FAA4F;QAC5F,IAAI,CAAC,UAAU,GAAG,mCAAmC,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;QAE7E,MAAM,EAAE,wBAAwB,EAAE,GAAG,OAAO,CAAC;QAC7C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,mBAAmB,GAAG,GAAG,GAAG,wBAAwB,CAAC;QAE3D;QACC,iFAAiF;QACjF,6EAA6E;QAC7E,uFAAuF;QACvF,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE;YACxB,iFAAiF;YACjF,gEAAgE;YAChE,mBAAmB,IAAI,IAAI,CAAC,KAAK,CAAC,UAAU,EAC3C,CAAC;YACF,OAAO;QACR,CAAC;QAED,kFAAkF;QAClF,0DAA0D;QAE1D,sGAAsG;QACtG,MAAM,WAAW,GAAG,mBAAmB,GAAG,GAAG,CAAC;QAC9C,MAAM,gBAAgB,GAAG,WAAW,GAAG,CAAC,CAAC;QAEzC,IAAI,gBAAgB,EAAE,CAAC;YACtB,gEAAgE;YAChE,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,CAAC;QACvE,CAAC;aAAM,CAAC;YACP,IAAI,CAAC,iBAAiB,EAAE,CAAC;QAC1B,CAAC;IACF,CAAC;IAED;;OAEG;IACK,iBAAiB;QACxB,IAAI,CAAC,KAAK,CAAC,YAAY,EAAE,CAAC;QAE1B,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;YACnC,OAAO;QACR,CAAC;QAED,iDAAiD;QACjD,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC;YAC7B,yEAAyE;YACzE,0CAA0C;YAC1C,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;YAC5B,OAAO;QACR,CAAC;QAED,MAAM,kBAAkB,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC;QACjD,MAAM,CAAC,kBAAkB,KAAK,SAAS,EAAE,KAAK,CAAC,uCAAuC,CAAC,CAAC;QACxF,MAAM,gCAAgC,GACrC,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC,iBAAiB,CAAC,kBAAkB,CAAC,CAAC;QAEzE,MAAM,UAAU,GAAG;YAClB,aAAa,EAAE,IAAI,CAAC,GAAG,EAAE;YACzB,UAAU,EAAE,IAAI,CAAC,cAAc;YAC/B,qBAAqB;YACrB,IAAI,EAAE;gBACL,qEAAqE;gBACrE,uEAAuE;gBACvE,sEAAsE;gBACtE,yCAAyC;gBACzC,iBAAiB,EAAE;oBAClB,iBAAiB,EAAE;wBAClB,CAAC,kBAAkB,CAAC,EAAE,EAAE,GAAG,gCAAgC,EAAE;qBAC7D;iBACD;gBACD,GAAG,IAAI,CAAC,UAAU;aAClB;SAC2C,CAAC;QAC9C,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;QAC5B,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,0BAA0B,EAAE,UAAU,CAAC,CAAC;IACnE,CAAC;IAEO,sBAAsB;QAC7B,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,0BAA0B,EAAE;YACrD,aAAa,EAAE,IAAI,CAAC,GAAG,EAAE;YACzB,UAAU,EAAE,IAAI,CAAC,cAAc;YAC/B,UAAU,EAAE,IAAI;YAChB,IAAI,EAAE,IAAI,CAAC,SAAS;SACwB,CAAC,CAAC;QAC/C,IAAI,CAAC,yBAAyB,GAAG,KAAK,CAAC;IACxC,CAAC;IAEM,aAAa;IACnB,yEAAyE;IACzE,4EAA4E;IAC5E,uEAAuE;IACvE,0EAA0E;IAC1E,gEAAgE;IAChE,qDAAqD;IACrD,OAA2E,EAC3E,KAAc;QAEd,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC5B,MAAM,CAAC,OAAO,CAAC,QAAQ,KAAK,IAAI,EAAE,KAAK,CAAC,0CAA0C,CAAC,CAAC;QACpF,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,EAAE,CAAC;YACjC,OAAO;QACR,CAAC;QACD,IAAI,KAAK,EAAE,CAAC;YACX,MAAM,aAAa,GAAG,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC;YAC/D,+DAA+D;YAC/D,4DAA4D;YAC5D,iEAAiE;YACjE,cAAc;YACd,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,gBAAgB,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC;YACjE,IAAI,CAAC,cAAc;gBAClB,CAAC,IAAI,CAAC,cAAc,GAAG,CAAC,IAAI,CAAC,gBAAgB,GAAG,CAAC,CAAC,GAAG,aAAa,CAAC;oBACnE,IAAI,CAAC,gBAAgB,CAAC;YACvB,OAAO;QACR,CAAC;QAED,MAAM,YAAY,GACjB,QAAQ;YACR,CAAC,IAAI,CAAC,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;QAEpF,IAAI,OAAO,CAAC,IAAI,KAAK,eAAe,EAAE,CAAC;YACtC,+EAA+E;YAC/E,6EAA6E;YAC7E,yEAAyE;YACzE,IAAI,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC;gBAC5B,IAAI,CAAC,mBAAmB,CAAC,OAAO,CAAC,OAAO,CAAC,eAAe,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;YAC7E,CAAC;YACD,6EAA6E;YAC7E,aAAa;QACd,CAAC;aAAM,CAAC;YACP,MAAM,CAAC,OAAO,CAAC,IAAI,KAAK,0BAA0B,EAAE,KAAK,CAAC,6BAA6B,CAAC,CAAC;YACzF,IAAI,OAAO,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC;gBAChC,IAAI,CAAC,yBAAyB,GAAG,KAAK,CAAC;YACxC,CAAC;QACF,CAAC;QAED,KAAK,MAAM,CAAC,gBAAgB,EAAE,eAAe,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YACxF,4DAA4D;YAC5D,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;YACxD,IAAI,SAAS,EAAE,CAAC;gBACf,SAAS,CAAC,QAAQ,CAAC,aAAa,CAC/B,QAAQ,EACR,YAAY,EACZ,eAAe,EACf,OAAO,CAAC,QAAQ,CAChB,CAAC;YACH,CAAC;iBAAM,CAAC;gBACP,+EAA+E;gBAC/E,8BAA8B;gBAC9B,IAAI,kBAAkB,GAAG,IAAI,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAC;gBAC1D,IAAI,kBAAkB,KAAK,SAAS,EAAE,CAAC;oBACtC,kBAAkB,GAAG,IAAI,CAAC,SAAS,CAAC,gBAAgB,CAAC,GAAG,EAAE,CAAC;oBAC3D,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;wBAC7C,yDAAyD;oBAC1D,CAAC;gBACF,CAAC;gBACD,KAAK,MAAM,CAAC,GAAG,EAAE,mBAAmB,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,EAAE,CAAC;oBAC1E,uBAAuB,CAAC,GAAG,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,YAAY,CAAC,CAAC;gBACrF,CAAC;YACF,CAAC;QACF,CAAC;IACF,CAAC;IAED;;;;;;;;;;OAUG;IACK,mBAAmB,CAC1B,eAAqC,EACrC,SAA6B;QAE7B,IAAI,CAAC,yBAAyB,GAAG,IAAI,CAAC;QACtC,+EAA+E;QAC/E,oFAAoF;QACpF,oEAAoE;QACpE,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,QAAS,CAAC;QACxC,sCAAsC;QACtC,IAAI,eAAe,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YACxC,2CAA2C;YAC3C,IAAI,CAAC,sBAAsB,EAAE,CAAC;YAC9B,IAAI,CAAC,MAAM,EAAE,kBAAkB,CAAC;gBAC/B,SAAS,EAAE,cAAc;gBACzB,OAAO,EAAE;oBACR,IAAI,EAAE,cAAc;oBACpB,SAAS;oBACT,IAAI,EAAE,SAAS;iBACf;aACD,CAAC,CAAC;QACJ,CAAC;aAAM,CAAC;YACP,uEAAuE;YACvE,yEAAyE;YACzE,qEAAqE;YACrE,wEAAwE;YACxE,oCAAoC;YACpC,IAAI,qBAAqB,CAAC;YAC1B,MAAM,aAAa,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,UAAU,EAAE,CAAC;YAC5D,MAAM,IAAI,GAAG,aAAa,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YACzC,IAAI,IAAI,EAAE,CAAC;gBACV,gEAAgE;gBAChE,qBAAqB,GAAG,CAAC,CAAC;gBAC1B,KAAK,MAAM,EAAE,cAAc,EAAE,IAAI,aAAa,CAAC,MAAM,EAAE,EAAE,CAAC;oBACzD,IAAI,cAAc,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;wBAC1C,qBAAqB,EAAE,CAAC;oBACzB,CAAC;gBACF,CAAC;YACF,CAAC;iBAAM,CAAC;gBACP,mEAAmE;gBACnE,qBAAqB,GAAG,aAAa,CAAC,IAAI,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC;YACjE,CAAC;YACD,4DAA4D;YAC5D,uEAAuE;YACvE,kEAAkE;YAClE,MAAM,QAAQ,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,GAAG,eAAe,CAAC,MAAM,GAAG,qBAAqB,CAAC,CAAC;YAChF,UAAU,CAAC,GAAG,EAAE;gBACf,wEAAwE;gBACxE,uCAAuC;gBACvC,IAAI,IAAI,CAAC,yBAAyB,IAAI,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC;oBAC9D,IAAI,CAAC,sBAAsB,EAAE,CAAC;oBAC9B,IAAI,CAAC,MAAM,EAAE,kBAAkB,CAAC;wBAC/B,SAAS,EAAE,cAAc;wBACzB,OAAO,EAAE;4BACR,IAAI,EAAE,cAAc;4BACpB,SAAS;4BACT,IAAI,EAAE,WAAW;4BACjB,KAAK,EAAE,qBAAqB;yBAC5B;qBACD,CAAC,CAAC;gBACJ,CAAC;YACF,CAAC,EAAE,QAAQ,CAAC,CAAC;QACd,CAAC;IACF,CAAC;CACD","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { assert } from \"@fluidframework/core-utils/internal\";\nimport type { IInboundSignalMessage } from \"@fluidframework/runtime-definitions/internal\";\nimport type { ITelemetryLoggerExt } from \"@fluidframework/telemetry-utils/internal\";\n\nimport type { ClientConnectionId } from \"./baseTypes.js\";\nimport type { BroadcastControlSettings } from \"./broadcastControls.js\";\nimport { brandedObjectEntries } from \"./internalTypes.js\";\nimport type { IEphemeralRuntime } from \"./internalTypes.js\";\nimport type { ClientSessionId, ISessionClient } from \"./presence.js\";\nimport type {\n\tClientUpdateEntry,\n\tRuntimeLocalUpdateOptions,\n\tPresenceStatesInternal,\n\tValueElementMap,\n} from \"./presenceStates.js\";\nimport {\n\tcreatePresenceStates,\n\tmergeUntrackedDatastore,\n\tmergeValueDirectory,\n} from \"./presenceStates.js\";\nimport type { SystemWorkspaceDatastore } from \"./systemWorkspace.js\";\nimport { TimerManager } from \"./timerManager.js\";\nimport type {\n\tPresenceStates,\n\tPresenceStatesSchema,\n\tPresenceWorkspaceAddress,\n} from \"./types.js\";\n\nimport type { IExtensionMessage } from \"@fluidframework/presence/internal/container-definitions/internal\";\n\ninterface PresenceWorkspaceEntry<TSchema extends PresenceStatesSchema> {\n\tpublic: PresenceStates<TSchema>;\n\tinternal: PresenceStatesInternal;\n}\n\ninterface SystemDatastore {\n\t\"system:presence\": SystemWorkspaceDatastore;\n}\n\ntype InternalWorkspaceAddress = `${\"s\" | \"n\"}:${PresenceWorkspaceAddress}`;\n\ntype PresenceDatastore = SystemDatastore & {\n\t[WorkspaceAddress: string]: ValueElementMap<PresenceStatesSchema>;\n};\n\ninterface GeneralDatastoreMessageContent {\n\t[WorkspaceAddress: string]: {\n\t\t[StateValueManagerKey: string]: {\n\t\t\t[ClientSessionId: ClientSessionId]: ClientUpdateEntry;\n\t\t};\n\t};\n}\n\ntype DatastoreMessageContent = SystemDatastore & GeneralDatastoreMessageContent;\n\nconst datastoreUpdateMessageType = \"Pres:DatastoreUpdate\";\ninterface DatastoreUpdateMessage extends IInboundSignalMessage {\n\ttype: typeof datastoreUpdateMessageType;\n\tcontent: {\n\t\tsendTimestamp: number;\n\t\tavgLatency: number;\n\t\tisComplete?: true;\n\t\tdata: DatastoreMessageContent;\n\t};\n}\n\nconst joinMessageType = \"Pres:ClientJoin\";\ninterface ClientJoinMessage extends IInboundSignalMessage {\n\ttype: typeof joinMessageType;\n\tcontent: {\n\t\tupdateProviders: ClientConnectionId[];\n\t\tsendTimestamp: number;\n\t\tavgLatency: number;\n\t\tdata: DatastoreMessageContent;\n\t};\n}\n\nfunction isPresenceMessage(\n\tmessage: IInboundSignalMessage,\n): message is DatastoreUpdateMessage | ClientJoinMessage {\n\treturn message.type.startsWith(\"Pres:\");\n}\n\n/**\n * @internal\n */\nexport interface PresenceDatastoreManager {\n\tjoinSession(clientId: ClientConnectionId): void;\n\tgetWorkspace<TSchema extends PresenceStatesSchema>(\n\t\tinternalWorkspaceAddress: InternalWorkspaceAddress,\n\t\trequestedContent: TSchema,\n\t\tcontrols?: BroadcastControlSettings,\n\t): PresenceStates<TSchema>;\n\tprocessSignal(message: IExtensionMessage, local: boolean): void;\n}\n\nfunction mergeGeneralDatastoreMessageContent(\n\tbase: GeneralDatastoreMessageContent | undefined,\n\tnewData: GeneralDatastoreMessageContent,\n): GeneralDatastoreMessageContent {\n\t// This function-local \"datastore\" will hold the merged message data.\n\tconst queueDatastore = base ?? {};\n\n\t// Merge the current data with the existing data, if any exists.\n\t// Iterate over the current message data; individual items are workspaces.\n\tfor (const [workspaceName, workspaceData] of Object.entries(newData)) {\n\t\t// Initialize the merged data as the queued datastore entry for the workspace.\n\t\t// Since the key might not exist, create an empty object in that case. It will\n\t\t// be set explicitly after the loop.\n\t\tconst mergedData = queueDatastore[workspaceName] ?? {};\n\n\t\t// Iterate over each value manager and its data, merging it as needed.\n\t\tfor (const valueManagerKey of Object.keys(workspaceData)) {\n\t\t\tfor (const [clientSessionId, value] of brandedObjectEntries(\n\t\t\t\tworkspaceData[valueManagerKey],\n\t\t\t)) {\n\t\t\t\tmergedData[valueManagerKey] ??= {};\n\t\t\t\tconst oldData = mergedData[valueManagerKey][clientSessionId];\n\t\t\t\tmergedData[valueManagerKey][clientSessionId] = mergeValueDirectory(\n\t\t\t\t\toldData,\n\t\t\t\t\tvalue,\n\t\t\t\t\t0, // local values do not need a time shift\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// Store the merged data in the function-local queue workspace. The whole contents of this\n\t\t// datastore will be sent as the message data.\n\t\tqueueDatastore[workspaceName] = mergedData;\n\t}\n\treturn queueDatastore;\n}\n\n/**\n * Manages singleton datastore for all Presence.\n */\nexport class PresenceDatastoreManagerImpl implements PresenceDatastoreManager {\n\tprivate readonly datastore: PresenceDatastore;\n\tprivate averageLatency = 0;\n\tprivate returnedMessages = 0;\n\tprivate refreshBroadcastRequested = false;\n\tprivate readonly timer = new TimerManager();\n\tprivate readonly workspaces = new Map<\n\t\tstring,\n\t\tPresenceWorkspaceEntry<PresenceStatesSchema>\n\t>();\n\n\tpublic constructor(\n\t\tprivate readonly clientSessionId: ClientSessionId,\n\t\tprivate readonly runtime: IEphemeralRuntime,\n\t\tprivate readonly lookupClient: (clientId: ClientSessionId) => ISessionClient,\n\t\tprivate readonly logger: ITelemetryLoggerExt | undefined,\n\t\tsystemWorkspaceDatastore: SystemWorkspaceDatastore,\n\t\tsystemWorkspace: PresenceWorkspaceEntry<PresenceStatesSchema>,\n\t) {\n\t\t// eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n\t\tthis.datastore = { \"system:presence\": systemWorkspaceDatastore } as PresenceDatastore;\n\t\tthis.workspaces.set(\"system:presence\", systemWorkspace);\n\t}\n\n\tpublic joinSession(clientId: ClientConnectionId): void {\n\t\t// Broadcast join message to all clients\n\t\tconst updateProviders = [...this.runtime.getQuorum().getMembers().keys()].filter(\n\t\t\t(quorumClientId) => quorumClientId !== clientId,\n\t\t);\n\t\t// Limit to three providers to prevent flooding the network.\n\t\t// If none respond, others present will (should) after a delay.\n\t\tif (updateProviders.length > 3) {\n\t\t\tupdateProviders.length = 3;\n\t\t}\n\t\tthis.runtime.submitSignal(joinMessageType, {\n\t\t\tsendTimestamp: Date.now(),\n\t\t\tavgLatency: this.averageLatency,\n\t\t\tdata: this.datastore,\n\t\t\tupdateProviders,\n\t\t} satisfies ClientJoinMessage[\"content\"]);\n\t}\n\n\tpublic getWorkspace<TSchema extends PresenceStatesSchema>(\n\t\tinternalWorkspaceAddress: InternalWorkspaceAddress,\n\t\trequestedContent: TSchema,\n\t\tcontrols?: BroadcastControlSettings,\n\t): PresenceStates<TSchema> {\n\t\tconst existing = this.workspaces.get(internalWorkspaceAddress);\n\t\tif (existing) {\n\t\t\treturn existing.internal.ensureContent(requestedContent, controls);\n\t\t}\n\n\t\tlet workspaceDatastore = this.datastore[internalWorkspaceAddress];\n\t\tif (workspaceDatastore === undefined) {\n\t\t\tworkspaceDatastore = this.datastore[internalWorkspaceAddress] = {};\n\t\t}\n\n\t\tconst localUpdate = (\n\t\t\tstates: { [key: string]: ClientUpdateEntry },\n\t\t\toptions: RuntimeLocalUpdateOptions,\n\t\t): void => {\n\t\t\t// Check for connectivity before sending updates.\n\t\t\tif (!this.runtime.connected) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst updates: GeneralDatastoreMessageContent[InternalWorkspaceAddress] = {};\n\t\t\tfor (const [key, value] of Object.entries(states)) {\n\t\t\t\tupdates[key] = { [this.clientSessionId]: value };\n\t\t\t}\n\n\t\t\tthis.enqueueMessage(\n\t\t\t\t{\n\t\t\t\t\t[internalWorkspaceAddress]: updates,\n\t\t\t\t},\n\t\t\t\toptions,\n\t\t\t);\n\t\t};\n\n\t\tconst entry = createPresenceStates(\n\t\t\t{\n\t\t\t\tclientSessionId: this.clientSessionId,\n\t\t\t\tlookupClient: this.lookupClient,\n\t\t\t\tlocalUpdate,\n\t\t\t},\n\t\t\tworkspaceDatastore,\n\t\t\trequestedContent,\n\t\t\tcontrols,\n\t\t);\n\n\t\tthis.workspaces.set(internalWorkspaceAddress, entry);\n\t\treturn entry.public;\n\t}\n\n\t/**\n\t * The combined contents of all queued updates. Will be undefined when no messages are queued.\n\t */\n\tprivate queuedData: GeneralDatastoreMessageContent | undefined;\n\n\t/**\n\t * Enqueues a new message to be sent. The message may be queued or may be sent immediately depending on the state of\n\t * the send timer, other messages in the queue, the configured allowed latency, etc.\n\t */\n\tprivate enqueueMessage(\n\t\tdata: GeneralDatastoreMessageContent,\n\t\toptions: RuntimeLocalUpdateOptions,\n\t): void {\n\t\t// Merging the message with any queued messages effectively queues the message.\n\t\t// It is OK to queue all incoming messages as long as when we send, we send the queued data.\n\t\tthis.queuedData = mergeGeneralDatastoreMessageContent(this.queuedData, data);\n\n\t\tconst { allowableUpdateLatencyMs } = options;\n\t\tconst now = Date.now();\n\t\tconst thisMessageDeadline = now + allowableUpdateLatencyMs;\n\n\t\tif (\n\t\t\t// If the timer has not expired, we can short-circuit because the timer will fire\n\t\t\t// and cover this update. In other words, queuing this will be fast enough to\n\t\t\t// meet its deadline, because a timer is already scheduled to fire before its deadline.\n\t\t\t!this.timer.hasExpired() &&\n\t\t\t// If the deadline for this message is later than the overall send deadline, then\n\t\t\t// we can exit early since a timer will take care of sending it.\n\t\t\tthisMessageDeadline >= this.timer.expireTime\n\t\t) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Either we need to send this message immediately, or we need to schedule a timer\n\t\t// to fire at the send deadline that will take care of it.\n\n\t\t// Note that timeoutInMs === allowableUpdateLatency, but the calculation is done this way for clarity.\n\t\tconst timeoutInMs = thisMessageDeadline - now;\n\t\tconst scheduleForLater = timeoutInMs > 0;\n\n\t\tif (scheduleForLater) {\n\t\t\t// Schedule the queued messages to be sent at the updateDeadline\n\t\t\tthis.timer.setTimeout(this.sendQueuedMessage.bind(this), timeoutInMs);\n\t\t} else {\n\t\t\tthis.sendQueuedMessage();\n\t\t}\n\t}\n\n\t/**\n\t * Send any queued signal immediately. Does nothing if no message is queued.\n\t */\n\tprivate sendQueuedMessage(): void {\n\t\tthis.timer.clearTimeout();\n\n\t\tif (this.queuedData === undefined) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Check for connectivity before sending updates.\n\t\tif (!this.runtime.connected) {\n\t\t\t// Clear the queued data since we're disconnected. We don't want messages\n\t\t\t// to queue infinitely while disconnected.\n\t\t\tthis.queuedData = undefined;\n\t\t\treturn;\n\t\t}\n\n\t\tconst clientConnectionId = this.runtime.clientId;\n\t\tassert(clientConnectionId !== undefined, 0xa59 /* Client connected without clientId */);\n\t\tconst currentClientToSessionValueState =\n\t\t\tthis.datastore[\"system:presence\"].clientToSessionId[clientConnectionId];\n\n\t\tconst newMessage = {\n\t\t\tsendTimestamp: Date.now(),\n\t\t\tavgLatency: this.averageLatency,\n\t\t\t// isComplete: false,\n\t\t\tdata: {\n\t\t\t\t// Always send current connection mapping for some resiliency against\n\t\t\t\t// lost signals. This ensures that client session id found in `updates`\n\t\t\t\t// (which is this client's client session id) is always represented in\n\t\t\t\t// system workspace of recipient clients.\n\t\t\t\t\"system:presence\": {\n\t\t\t\t\tclientToSessionId: {\n\t\t\t\t\t\t[clientConnectionId]: { ...currentClientToSessionValueState },\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t...this.queuedData,\n\t\t\t},\n\t\t} satisfies DatastoreUpdateMessage[\"content\"];\n\t\tthis.queuedData = undefined;\n\t\tthis.runtime.submitSignal(datastoreUpdateMessageType, newMessage);\n\t}\n\n\tprivate broadcastAllKnownState(): void {\n\t\tthis.runtime.submitSignal(datastoreUpdateMessageType, {\n\t\t\tsendTimestamp: Date.now(),\n\t\t\tavgLatency: this.averageLatency,\n\t\t\tisComplete: true,\n\t\t\tdata: this.datastore,\n\t\t} satisfies DatastoreUpdateMessage[\"content\"]);\n\t\tthis.refreshBroadcastRequested = false;\n\t}\n\n\tpublic processSignal(\n\t\t// Note: IInboundSignalMessage is used here in place of IExtensionMessage\n\t\t// as IExtensionMessage's strictly JSON `content` creates type compatibility\n\t\t// issues with `ClientSessionId` keys and really unknown value content.\n\t\t// IExtensionMessage is a subset of IInboundSignalMessage so this is safe.\n\t\t// Change types of DatastoreUpdateMessage | ClientJoinMessage to\n\t\t// IExtensionMessage<> derivatives to see the issues.\n\t\tmessage: IInboundSignalMessage | DatastoreUpdateMessage | ClientJoinMessage,\n\t\tlocal: boolean,\n\t): void {\n\t\tconst received = Date.now();\n\t\tassert(message.clientId !== null, 0xa3a /* Map received signal without clientId */);\n\t\tif (!isPresenceMessage(message)) {\n\t\t\treturn;\n\t\t}\n\t\tif (local) {\n\t\t\tconst deliveryDelta = received - message.content.sendTimestamp;\n\t\t\t// Limit returnedMessages count to 256 such that newest message\n\t\t\t// always contributes at least 1/256th to the average. Older\n\t\t\t// messages have more weight, but that diminishes as new messages\n\t\t\t// contribute.\n\t\t\tthis.returnedMessages = Math.min(this.returnedMessages + 1, 256);\n\t\t\tthis.averageLatency =\n\t\t\t\t(this.averageLatency * (this.returnedMessages - 1) + deliveryDelta) /\n\t\t\t\tthis.returnedMessages;\n\t\t\treturn;\n\t\t}\n\n\t\tconst timeModifier =\n\t\t\treceived -\n\t\t\t(this.averageLatency + message.content.avgLatency + message.content.sendTimestamp);\n\n\t\tif (message.type === joinMessageType) {\n\t\t\t// It is possible for some signals to come in while client is not connected due\n\t\t\t// to how work is scheduled. If we are not connected, we can't respond to the\n\t\t\t// join request. We will make our own Join request once we are connected.\n\t\t\tif (this.runtime.connected) {\n\t\t\t\tthis.prepareJoinResponse(message.content.updateProviders, message.clientId);\n\t\t\t}\n\t\t\t// It is okay to continue processing the contained updates even if we are not\n\t\t\t// connected.\n\t\t} else {\n\t\t\tassert(message.type === datastoreUpdateMessageType, 0xa3b /* Unexpected message type */);\n\t\t\tif (message.content.isComplete) {\n\t\t\t\tthis.refreshBroadcastRequested = false;\n\t\t\t}\n\t\t}\n\n\t\tfor (const [workspaceAddress, remoteDatastore] of Object.entries(message.content.data)) {\n\t\t\t// Direct to the appropriate Presence Workspace, if present.\n\t\t\tconst workspace = this.workspaces.get(workspaceAddress);\n\t\t\tif (workspace) {\n\t\t\t\tworkspace.internal.processUpdate(\n\t\t\t\t\treceived,\n\t\t\t\t\ttimeModifier,\n\t\t\t\t\tremoteDatastore,\n\t\t\t\t\tmessage.clientId,\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\t// All broadcast state is kept even if not currently registered, unless a value\n\t\t\t\t// notes itself to be ignored.\n\t\t\t\tlet workspaceDatastore = this.datastore[workspaceAddress];\n\t\t\t\tif (workspaceDatastore === undefined) {\n\t\t\t\t\tworkspaceDatastore = this.datastore[workspaceAddress] = {};\n\t\t\t\t\tif (!workspaceAddress.startsWith(\"system:\")) {\n\t\t\t\t\t\t// TODO: Emit workspaceActivated event for PresenceEvents\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor (const [key, remoteAllKnownState] of Object.entries(remoteDatastore)) {\n\t\t\t\t\tmergeUntrackedDatastore(key, remoteAllKnownState, workspaceDatastore, timeModifier);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Handles responding to another client joining the session.\n\t *\n\t * @param updateProviders - list of client connection id's that requestor selected\n\t * to provide response\n\t * @param requestor - `requestor` is only used in telemetry. While it is the requestor's\n\t * client connection id, that is not most important. It is important that this is a\n\t * unique shared id across all clients that might respond as we want to monitor the\n\t * response patterns. The convenience of being client connection id will allow\n\t * correlation with other telemetry where it is often called just `clientId`.\n\t */\n\tprivate prepareJoinResponse(\n\t\tupdateProviders: ClientConnectionId[],\n\t\trequestor: ClientConnectionId,\n\t): void {\n\t\tthis.refreshBroadcastRequested = true;\n\t\t// We must be connected to receive this message, so clientId should be defined.\n\t\t// If it isn't then, not really a problem; just won't be in provider or quorum list.\n\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\tconst clientId = this.runtime.clientId!;\n\t\t// const requestor = message.clientId;\n\t\tif (updateProviders.includes(clientId)) {\n\t\t\t// Send all current state to the new client\n\t\t\tthis.broadcastAllKnownState();\n\t\t\tthis.logger?.sendTelemetryEvent({\n\t\t\t\teventName: \"JoinResponse\",\n\t\t\t\tdetails: {\n\t\t\t\t\ttype: \"broadcastAll\",\n\t\t\t\t\trequestor,\n\t\t\t\t\trole: \"primary\",\n\t\t\t\t},\n\t\t\t});\n\t\t} else {\n\t\t\t// Schedule a broadcast to the new client after a delay only to send if\n\t\t\t// another broadcast hasn't been seen in the meantime. The delay is based\n\t\t\t// on the position in the quorum list. It doesn't have to be a stable\n\t\t\t// list across all clients. We need something to provide suggested order\n\t\t\t// to prevent a flood of broadcasts.\n\t\t\tlet relativeResponseOrder;\n\t\t\tconst quorumMembers = this.runtime.getQuorum().getMembers();\n\t\t\tconst self = quorumMembers.get(clientId);\n\t\t\tif (self) {\n\t\t\t\t// Compute order quorum join order (indicated by sequenceNumber)\n\t\t\t\trelativeResponseOrder = 0;\n\t\t\t\tfor (const { sequenceNumber } of quorumMembers.values()) {\n\t\t\t\t\tif (sequenceNumber < self.sequenceNumber) {\n\t\t\t\t\t\trelativeResponseOrder++;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Order past quorum members + arbitrary additional offset up to 10\n\t\t\t\trelativeResponseOrder = quorumMembers.size + Math.random() * 10;\n\t\t\t}\n\t\t\t// These numbers have been chosen arbitrarily to start with.\n\t\t\t// 20 is minimum wait time, 20 is the additional wait time per provider\n\t\t\t// given an chance before us with named providers given more time.\n\t\t\tconst waitTime = 20 + 20 * (3 * updateProviders.length + relativeResponseOrder);\n\t\t\tsetTimeout(() => {\n\t\t\t\t// Make sure a broadcast is still needed and we are currently connected.\n\t\t\t\t// If not connected, nothing we can do.\n\t\t\t\tif (this.refreshBroadcastRequested && this.runtime.connected) {\n\t\t\t\t\tthis.broadcastAllKnownState();\n\t\t\t\t\tthis.logger?.sendTelemetryEvent({\n\t\t\t\t\t\teventName: \"JoinResponse\",\n\t\t\t\t\t\tdetails: {\n\t\t\t\t\t\t\ttype: \"broadcastAll\",\n\t\t\t\t\t\t\trequestor,\n\t\t\t\t\t\t\trole: \"secondary\",\n\t\t\t\t\t\t\torder: relativeResponseOrder,\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}, waitTime);\n\t\t}\n\t}\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"presenceManager.d.ts","sourceRoot":"","sources":["../src/presenceManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"presenceManager.d.ts","sourceRoot":"","sources":["../src/presenceManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAaH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,KAAK,EACX,eAAe,EACf,SAAS,EAGT,MAAM,eAAe,CAAC;AAWvB,OAAO,KAAK,EACX,mBAAmB,EAEnB,MAAM,kEAAkE,CAAC;AAE1E;;;;GAIG;AACH,MAAM,MAAM,0BAA0B,GAAG,QAAQ,CAChD,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC,EAAE,eAAe,CAAC,CACjD,CAAC;AAyIF;;;;GAIG;AACH,wBAAgB,qBAAqB,CACpC,OAAO,EAAE,iBAAiB,EAC1B,eAAe,GAAE,eAAsD,GACrE,SAAS,GAAG,0BAA0B,CAExC"}
|
package/lib/presenceManager.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
|
+
import { createEmitter } from "@fluid-internal/client-utils";
|
|
5
6
|
import { createSessionId } from "@fluidframework/id-compressor/internal";
|
|
6
7
|
import { createChildMonitoringContext } from "@fluidframework/telemetry-utils/internal";
|
|
7
8
|
import { PresenceDatastoreManagerImpl } from "./presenceDatastoreManager.js";
|
|
8
9
|
import { createSystemWorkspace } from "./systemWorkspace.js";
|
|
9
|
-
import { createEmitter } from "@fluidframework/presence/internal/events";
|
|
10
10
|
/**
|
|
11
11
|
* The Presence manager
|
|
12
12
|
*/
|
|
@@ -54,8 +54,8 @@ class PresenceManager {
|
|
|
54
54
|
getMyself() {
|
|
55
55
|
return this.systemWorkspace.getMyself();
|
|
56
56
|
}
|
|
57
|
-
getStates(workspaceAddress, requestedContent) {
|
|
58
|
-
return this.datastoreManager.getWorkspace(`s:${workspaceAddress}`, requestedContent);
|
|
57
|
+
getStates(workspaceAddress, requestedContent, controls) {
|
|
58
|
+
return this.datastoreManager.getWorkspace(`s:${workspaceAddress}`, requestedContent, controls);
|
|
59
59
|
}
|
|
60
60
|
getNotifications(workspaceAddress, requestedContent) {
|
|
61
61
|
return this.datastoreManager.getWorkspace(`n:${workspaceAddress}`, requestedContent);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"presenceManager.js","sourceRoot":"","sources":["../src/presenceManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,wCAAwC,CAAC;AAKzE,OAAO,EAAE,4BAA4B,EAAE,MAAM,0CAA0C,CAAC;
|
|
1
|
+
{"version":3,"file":"presenceManager.js","sourceRoot":"","sources":["../src/presenceManager.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { createEmitter } from \"@fluid-internal/client-utils\";\nimport type { IEmitter } from \"@fluidframework/core-interfaces/internal\";\nimport { createSessionId } from \"@fluidframework/id-compressor/internal\";\nimport type {\n\tITelemetryLoggerExt,\n\tMonitoringContext,\n} from \"@fluidframework/telemetry-utils/internal\";\nimport { createChildMonitoringContext } from \"@fluidframework/telemetry-utils/internal\";\n\nimport type { ClientConnectionId } from \"./baseTypes.js\";\nimport type { BroadcastControlSettings } from \"./broadcastControls.js\";\nimport type { IEphemeralRuntime } from \"./internalTypes.js\";\nimport type {\n\tClientSessionId,\n\tIPresence,\n\tISessionClient,\n\tPresenceEvents,\n} from \"./presence.js\";\nimport type { PresenceDatastoreManager } from \"./presenceDatastoreManager.js\";\nimport { PresenceDatastoreManagerImpl } from \"./presenceDatastoreManager.js\";\nimport type { SystemWorkspace, SystemWorkspaceDatastore } from \"./systemWorkspace.js\";\nimport { createSystemWorkspace } from \"./systemWorkspace.js\";\nimport type {\n\tPresenceStates,\n\tPresenceWorkspaceAddress,\n\tPresenceStatesSchema,\n} from \"./types.js\";\n\nimport type {\n\tIContainerExtension,\n\tIExtensionMessage,\n} from \"@fluidframework/presence/internal/container-definitions/internal\";\n\n/**\n * Portion of the container extension requirements ({@link IContainerExtension}) that are delegated to presence manager.\n *\n * @internal\n */\nexport type PresenceExtensionInterface = Required<\n\tPick<IContainerExtension<never>, \"processSignal\">\n>;\n\n/**\n * The Presence manager\n */\nclass PresenceManager implements IPresence, PresenceExtensionInterface {\n\tprivate readonly datastoreManager: PresenceDatastoreManager;\n\tprivate readonly systemWorkspace: SystemWorkspace;\n\n\tpublic readonly events = createEmitter<PresenceEvents>();\n\n\tprivate readonly mc: MonitoringContext | undefined = undefined;\n\n\tpublic constructor(runtime: IEphemeralRuntime, clientSessionId: ClientSessionId) {\n\t\tconst logger = runtime.logger;\n\t\tif (logger) {\n\t\t\tthis.mc = createChildMonitoringContext({ logger, namespace: \"Presence\" });\n\t\t\tthis.mc.logger.sendTelemetryEvent({ eventName: \"PresenceInstantiated\" });\n\t\t}\n\n\t\t[this.datastoreManager, this.systemWorkspace] = setupSubComponents(\n\t\t\tclientSessionId,\n\t\t\truntime,\n\t\t\tthis.events,\n\t\t\tthis.mc?.logger,\n\t\t);\n\n\t\truntime.on(\"connected\", this.onConnect.bind(this));\n\n\t\truntime.on(\"disconnected\", () => {\n\t\t\tif (runtime.clientId !== undefined) {\n\t\t\t\tthis.removeClientConnectionId(runtime.clientId);\n\t\t\t}\n\t\t});\n\n\t\truntime.getAudience().on(\"removeMember\", this.removeClientConnectionId.bind(this));\n\n\t\t// Check if already connected at the time of construction.\n\t\t// If constructed during data store load, the runtime may already be connected\n\t\t// and the \"connected\" event will be raised during completion. With construction\n\t\t// delayed we expect that \"connected\" event has passed.\n\t\t// Note: In some manual testing, this does not appear to be enough to\n\t\t// always trigger an initial connect.\n\t\tconst clientId = runtime.clientId;\n\t\tif (clientId !== undefined && runtime.connected) {\n\t\t\tthis.onConnect(clientId);\n\t\t}\n\t}\n\n\tprivate onConnect(clientConnectionId: ClientConnectionId): void {\n\t\tthis.systemWorkspace.onConnectionAdded(clientConnectionId);\n\t\tthis.datastoreManager.joinSession(clientConnectionId);\n\t}\n\n\tprivate removeClientConnectionId(clientConnectionId: ClientConnectionId): void {\n\t\tthis.systemWorkspace.removeClientConnectionId(clientConnectionId);\n\t}\n\n\tpublic getAttendees(): ReadonlySet<ISessionClient> {\n\t\treturn this.systemWorkspace.getAttendees();\n\t}\n\n\tpublic getAttendee(clientId: ClientConnectionId | ClientSessionId): ISessionClient {\n\t\treturn this.systemWorkspace.getAttendee(clientId);\n\t}\n\n\tpublic getMyself(): ISessionClient {\n\t\treturn this.systemWorkspace.getMyself();\n\t}\n\n\tpublic getStates<TSchema extends PresenceStatesSchema>(\n\t\tworkspaceAddress: PresenceWorkspaceAddress,\n\t\trequestedContent: TSchema,\n\t\tcontrols?: BroadcastControlSettings,\n\t): PresenceStates<TSchema> {\n\t\treturn this.datastoreManager.getWorkspace(\n\t\t\t`s:${workspaceAddress}`,\n\t\t\trequestedContent,\n\t\t\tcontrols,\n\t\t);\n\t}\n\n\tpublic getNotifications<TSchema extends PresenceStatesSchema>(\n\t\tworkspaceAddress: PresenceWorkspaceAddress,\n\t\trequestedContent: TSchema,\n\t): PresenceStates<TSchema> {\n\t\treturn this.datastoreManager.getWorkspace(`n:${workspaceAddress}`, requestedContent);\n\t}\n\n\t/**\n\t * Check for Presence message and process it.\n\t *\n\t * @param address - Address of the message\n\t * @param message - Message to be processed\n\t * @param local - Whether the message originated locally (`true`) or remotely (`false`)\n\t */\n\tpublic processSignal(address: string, message: IExtensionMessage, local: boolean): void {\n\t\tthis.datastoreManager.processSignal(message, local);\n\t}\n}\n\n/**\n * Helper for Presence Manager setup\n *\n * Presence Manager is outermost layer of the presence system and has two main\n * sub-components:\n * 1. PresenceDatastoreManager: Manages the unified general data for states and\n * registry for workspaces.\n * 2. SystemWorkspace: Custom internal workspace for system states including\n * attendee management. It is registered with the PresenceDatastoreManager.\n */\nfunction setupSubComponents(\n\tclientSessionId: ClientSessionId,\n\truntime: IEphemeralRuntime,\n\tevents: IEmitter<PresenceEvents>,\n\tlogger: ITelemetryLoggerExt | undefined,\n): [PresenceDatastoreManager, SystemWorkspace] {\n\tconst systemWorkspaceDatastore: SystemWorkspaceDatastore = {\n\t\tclientToSessionId: {},\n\t};\n\tconst systemWorkspaceConfig = createSystemWorkspace(\n\t\tclientSessionId,\n\t\tsystemWorkspaceDatastore,\n\t\tevents,\n\t\truntime.getAudience(),\n\t);\n\tconst datastoreManager = new PresenceDatastoreManagerImpl(\n\t\tclientSessionId,\n\t\truntime,\n\t\tsystemWorkspaceConfig.workspace.getAttendee.bind(systemWorkspaceConfig.workspace),\n\t\tlogger,\n\t\tsystemWorkspaceDatastore,\n\t\tsystemWorkspaceConfig.statesEntry,\n\t);\n\treturn [datastoreManager, systemWorkspaceConfig.workspace];\n}\n\n/**\n * Instantiates Presence Manager\n *\n * @internal\n */\nexport function createPresenceManager(\n\truntime: IEphemeralRuntime,\n\tclientSessionId: ClientSessionId = createSessionId() as ClientSessionId,\n): IPresence & PresenceExtensionInterface {\n\treturn new PresenceManager(runtime, clientSessionId);\n}\n"]}
|
package/lib/presenceStates.d.ts
CHANGED
|
@@ -3,14 +3,33 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
import type { ClientConnectionId } from "./baseTypes.js";
|
|
6
|
+
import type { BroadcastControlSettings } from "./broadcastControls.js";
|
|
6
7
|
import type { InternalTypes } from "./exposedInternalTypes.js";
|
|
7
8
|
import type { ClientRecord } from "./internalTypes.js";
|
|
8
9
|
import type { ClientSessionId, ISessionClient } from "./presence.js";
|
|
9
10
|
import type { PresenceStates, PresenceStatesSchema } from "./types.js";
|
|
10
11
|
/**
|
|
12
|
+
* Extracts `Part` from {@link InternalTypes.ManagerFactory} return type
|
|
13
|
+
* matching the {@link PresenceStatesSchema} `Keys` given.
|
|
14
|
+
*
|
|
15
|
+
* @remarks
|
|
16
|
+
* If the `Part` is an optional property, undefined will be included in the
|
|
17
|
+
* result. Applying `Required` to the return type prior to extracting `Part`
|
|
18
|
+
* does not work as expected. Use Exclude\<, undefined\> can be used as needed.
|
|
19
|
+
*
|
|
11
20
|
* @internal
|
|
12
21
|
*/
|
|
13
22
|
export type MapSchemaElement<TSchema extends PresenceStatesSchema, Part extends keyof ReturnType<TSchema[keyof TSchema]>, Keys extends keyof TSchema = keyof TSchema> = ReturnType<TSchema[Keys]>[Part];
|
|
23
|
+
/**
|
|
24
|
+
* @internal
|
|
25
|
+
*/
|
|
26
|
+
export interface RuntimeLocalUpdateOptions {
|
|
27
|
+
allowableUpdateLatencyMs: number;
|
|
28
|
+
/**
|
|
29
|
+
* Special option allowed for unicast notifications.
|
|
30
|
+
*/
|
|
31
|
+
targetClientId?: ClientConnectionId;
|
|
32
|
+
}
|
|
14
33
|
/**
|
|
15
34
|
* @internal
|
|
16
35
|
*/
|
|
@@ -19,13 +38,13 @@ export interface PresenceRuntime {
|
|
|
19
38
|
lookupClient(clientId: ClientConnectionId): ISessionClient;
|
|
20
39
|
localUpdate(states: {
|
|
21
40
|
[key: string]: ClientUpdateEntry;
|
|
22
|
-
},
|
|
41
|
+
}, options: RuntimeLocalUpdateOptions): void;
|
|
23
42
|
}
|
|
24
43
|
/**
|
|
25
44
|
* ValueElementMap is a map of key to a map of clientId to ValueState.
|
|
26
45
|
* It is not restricted to the schema of the map as it may receive updates from other clients
|
|
27
46
|
* with managers that have not been registered locally. Each map node is responsible for keeping
|
|
28
|
-
* all
|
|
47
|
+
* all session's state to be able to pick arbitrary client to rebroadcast to others.
|
|
29
48
|
*
|
|
30
49
|
* This generic aspect makes some typing difficult. The loose typing is not broadcast to the
|
|
31
50
|
* consumers that are expected to maintain their schema over multiple versions of clients.
|
|
@@ -49,9 +68,15 @@ interface ValueUpdateRecord {
|
|
|
49
68
|
* @internal
|
|
50
69
|
*/
|
|
51
70
|
export interface PresenceStatesInternal {
|
|
52
|
-
ensureContent<TSchemaAdditional extends PresenceStatesSchema>(content: TSchemaAdditional): PresenceStates<TSchemaAdditional>;
|
|
71
|
+
ensureContent<TSchemaAdditional extends PresenceStatesSchema>(content: TSchemaAdditional, controls: BroadcastControlSettings | undefined): PresenceStates<TSchemaAdditional>;
|
|
53
72
|
processUpdate(received: number, timeModifier: number, remoteDatastore: ValueUpdateRecord, senderConnectionId: ClientConnectionId): void;
|
|
54
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Merge a value directory.
|
|
76
|
+
*
|
|
77
|
+
* @internal
|
|
78
|
+
*/
|
|
79
|
+
export declare function mergeValueDirectory<T, TValueState extends InternalTypes.ValueRequiredState<T> | InternalTypes.ValueOptionalState<T>>(base: TValueState | InternalTypes.ValueDirectory<T> | undefined, update: TValueState | InternalTypes.ValueDirectory<T>, timeDelta: number): TValueState | InternalTypes.ValueDirectory<T>;
|
|
55
80
|
/**
|
|
56
81
|
* Updates remote state into the local [untracked] datastore.
|
|
57
82
|
*
|
|
@@ -70,7 +95,7 @@ export declare function mergeUntrackedDatastore(key: string, remoteAllKnownState
|
|
|
70
95
|
* Create a new PresenceStates using the DataStoreRuntime provided.
|
|
71
96
|
* @param initialContent - The initial value managers to register.
|
|
72
97
|
*/
|
|
73
|
-
export declare function createPresenceStates<TSchema extends PresenceStatesSchema>(runtime: PresenceRuntime, datastore: ValueElementMap<PresenceStatesSchema>, initialContent: TSchema): {
|
|
98
|
+
export declare function createPresenceStates<TSchema extends PresenceStatesSchema>(runtime: PresenceRuntime, datastore: ValueElementMap<PresenceStatesSchema>, initialContent: TSchema, controls: BroadcastControlSettings | undefined): {
|
|
74
99
|
public: PresenceStates<TSchema>;
|
|
75
100
|
internal: PresenceStatesInternal;
|
|
76
101
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"presenceStates.d.ts","sourceRoot":"","sources":["../src/presenceStates.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"presenceStates.d.ts","sourceRoot":"","sources":["../src/presenceStates.ts"],"names":[],"mappings":"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"}
|