@fluidframework/presence 2.13.0 → 2.20.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.
@@ -1 +1 @@
1
- {"version":3,"file":"latestValueManager.js","sourceRoot":"","sources":["../src/latestValueManager.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 { Listenable } from \"@fluidframework/core-interfaces\";\n\nimport type { BroadcastControls, BroadcastControlSettings } from \"./broadcastControls.js\";\nimport { OptionalBroadcastControl } from \"./broadcastControls.js\";\nimport type { ValueManager } from \"./internalTypes.js\";\nimport { objectEntries } from \"./internalUtils.js\";\nimport type { LatestValueClientData, LatestValueData } from \"./latestValueTypes.js\";\nimport type { ISessionClient } from \"./presence.js\";\nimport { datastoreFromHandle, type StateDatastore } from \"./stateDatastore.js\";\nimport { brandIVM } from \"./valueManager.js\";\n\nimport type {\n\tJsonDeserialized,\n\tJsonSerializable,\n} from \"@fluidframework/presence/internal/core-interfaces\";\nimport type { InternalTypes } from \"@fluidframework/presence/internal/exposedInternalTypes\";\nimport type { InternalUtilityTypes } from \"@fluidframework/presence/internal/exposedUtilityTypes\";\n\n/**\n * @sealed\n * @alpha\n */\nexport interface LatestValueManagerEvents<T> {\n\t/**\n\t * Raised when remote client's value is updated, which may be the same value.\n\t *\n\t * @eventProperty\n\t */\n\tupdated: (update: LatestValueClientData<T>) => void;\n}\n\n/**\n * Value manager that provides the latest known value from this client to others and read access to their values.\n * All participant clients must provide a value.\n *\n * @remarks Create using {@link Latest} registered to {@link PresenceStates}.\n *\n * @sealed\n * @alpha\n */\nexport interface LatestValueManager<T> {\n\t/**\n\t * Events for Latest value manager.\n\t */\n\treadonly events: Listenable<LatestValueManagerEvents<T>>;\n\n\t/**\n\t * Controls for management of sending updates.\n\t */\n\treadonly controls: BroadcastControls;\n\n\t/**\n\t * Current state for this client.\n\t * State for this client that will be transmitted to all other connected clients.\n\t * @remarks Manager assumes ownership of the value and its references. Make a deep clone before\n\t * setting, if needed. No comparison is done to detect changes; all sets are transmitted.\n\t */\n\tget local(): InternalUtilityTypes.FullyReadonly<JsonDeserialized<T>>;\n\tset local(value: JsonSerializable<T> & JsonDeserialized<T>);\n\n\t/**\n\t * Iterable access to remote clients' values.\n\t */\n\tclientValues(): IterableIterator<LatestValueClientData<T>>;\n\t/**\n\t * Array of known clients.\n\t */\n\tclients(): ISessionClient[];\n\t/**\n\t * Access to a specific client's value.\n\t */\n\tclientValue(client: ISessionClient): LatestValueData<T>;\n}\n\nclass LatestValueManagerImpl<T, Key extends string>\n\timplements\n\t\tLatestValueManager<T>,\n\t\tRequired<ValueManager<T, InternalTypes.ValueRequiredState<T>>>\n{\n\tpublic readonly events = createEmitter<LatestValueManagerEvents<T>>();\n\tpublic readonly controls: OptionalBroadcastControl;\n\n\tpublic constructor(\n\t\tprivate readonly key: Key,\n\t\tprivate readonly datastore: StateDatastore<Key, InternalTypes.ValueRequiredState<T>>,\n\t\tpublic readonly value: InternalTypes.ValueRequiredState<T>,\n\t\tcontrolSettings: BroadcastControlSettings | undefined,\n\t) {\n\t\tthis.controls = new OptionalBroadcastControl(controlSettings);\n\t}\n\n\tpublic get local(): InternalUtilityTypes.FullyReadonly<JsonDeserialized<T>> {\n\t\treturn this.value.value;\n\t}\n\n\tpublic set local(value: JsonSerializable<T> & JsonDeserialized<T>) {\n\t\tthis.value.rev += 1;\n\t\tthis.value.timestamp = Date.now();\n\t\tthis.value.value = value;\n\t\tthis.datastore.localUpdate(this.key, this.value, {\n\t\t\tallowableUpdateLatencyMs: this.controls.allowableUpdateLatencyMs,\n\t\t});\n\t}\n\n\tpublic *clientValues(): IterableIterator<LatestValueClientData<T>> {\n\t\tconst allKnownStates = this.datastore.knownValues(this.key);\n\t\tfor (const [clientSessionId, value] of objectEntries(allKnownStates.states)) {\n\t\t\tif (clientSessionId !== allKnownStates.self) {\n\t\t\t\tyield {\n\t\t\t\t\tclient: this.datastore.lookupClient(clientSessionId),\n\t\t\t\t\tvalue: value.value,\n\t\t\t\t\tmetadata: { revision: value.rev, timestamp: value.timestamp },\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic clients(): ISessionClient[] {\n\t\tconst allKnownStates = this.datastore.knownValues(this.key);\n\t\treturn Object.keys(allKnownStates.states)\n\t\t\t.filter((clientSessionId) => clientSessionId !== allKnownStates.self)\n\t\t\t.map((clientSessionId) => this.datastore.lookupClient(clientSessionId));\n\t}\n\n\tpublic clientValue(client: ISessionClient): LatestValueData<T> {\n\t\tconst allKnownStates = this.datastore.knownValues(this.key);\n\t\tconst clientSessionId = client.sessionId;\n\t\tif (clientSessionId in allKnownStates.states) {\n\t\t\tconst { value, rev: revision } = allKnownStates.states[clientSessionId];\n\t\t\treturn { value, metadata: { revision, timestamp: Date.now() } };\n\t\t}\n\t\tthrow new Error(\"No entry for clientId\");\n\t}\n\n\tpublic update(\n\t\tclient: ISessionClient,\n\t\t_received: number,\n\t\tvalue: InternalTypes.ValueRequiredState<T>,\n\t): void {\n\t\tconst allKnownStates = this.datastore.knownValues(this.key);\n\t\tconst clientSessionId = client.sessionId;\n\t\tif (clientSessionId in allKnownStates.states) {\n\t\t\tconst currentState = allKnownStates.states[clientSessionId];\n\t\t\tif (currentState.rev >= value.rev) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\tthis.datastore.update(this.key, clientSessionId, value);\n\t\tthis.events.emit(\"updated\", {\n\t\t\tclient,\n\t\t\tvalue: value.value,\n\t\t\tmetadata: { revision: value.rev, timestamp: value.timestamp },\n\t\t});\n\t}\n}\n\n/**\n * Factory for creating a {@link LatestValueManager}.\n *\n * @alpha\n */\nexport function Latest<T extends object, Key extends string = string>(\n\tinitialValue: JsonSerializable<T> & JsonDeserialized<T> & object,\n\tcontrols?: BroadcastControlSettings,\n): InternalTypes.ManagerFactory<\n\tKey,\n\tInternalTypes.ValueRequiredState<T>,\n\tLatestValueManager<T>\n> {\n\t// LatestValueManager takes ownership of initialValue but makes a shallow\n\t// copy for basic protection.\n\tconst value: InternalTypes.ValueRequiredState<T> = {\n\t\trev: 0,\n\t\ttimestamp: Date.now(),\n\t\tvalue: { ...initialValue },\n\t};\n\tconst factory = (\n\t\tkey: Key,\n\t\tdatastoreHandle: InternalTypes.StateDatastoreHandle<\n\t\t\tKey,\n\t\t\tInternalTypes.ValueRequiredState<T>\n\t\t>,\n\t): {\n\t\tinitialData: { value: typeof value; allowableUpdateLatencyMs: number | undefined };\n\t\tmanager: InternalTypes.StateValue<LatestValueManager<T>>;\n\t} => ({\n\t\tinitialData: { value, allowableUpdateLatencyMs: controls?.allowableUpdateLatencyMs },\n\t\tmanager: brandIVM<LatestValueManagerImpl<T, Key>, T, InternalTypes.ValueRequiredState<T>>(\n\t\t\tnew LatestValueManagerImpl(key, datastoreFromHandle(datastoreHandle), value, controls),\n\t\t),\n\t});\n\treturn Object.assign(factory, { instanceBase: LatestValueManagerImpl });\n}\n"]}
1
+ {"version":3,"file":"latestValueManager.js","sourceRoot":"","sources":["../src/latestValueManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAI7D,OAAO,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AAElE,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAGnD,OAAO,EAAE,mBAAmB,EAAuB,MAAM,qBAAqB,CAAC;AAC/E,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAiE7C,MAAM,sBAAsB;IAQ3B,YACkB,GAAQ,EACR,SAAmE,EACpE,KAA0C,EAC1D,eAAqD;QAHpC,QAAG,GAAH,GAAG,CAAK;QACR,cAAS,GAAT,SAAS,CAA0D;QACpE,UAAK,GAAL,KAAK,CAAqC;QAN3C,WAAM,GAAG,aAAa,EAA+B,CAAC;QASrE,IAAI,CAAC,QAAQ,GAAG,IAAI,wBAAwB,CAAC,eAAe,CAAC,CAAC;IAC/D,CAAC;IAED,IAAW,KAAK;QACf,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;IACzB,CAAC;IAED,IAAW,KAAK,CAAC,KAAgD;QAChE,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;QACpB,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAClC,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC;QACzB,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,EAAE;YAChD,wBAAwB,EAAE,IAAI,CAAC,QAAQ,CAAC,wBAAwB;SAChE,CAAC,CAAC;IACJ,CAAC;IAEM,CAAC,YAAY;QACnB,MAAM,cAAc,GAAG,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC5D,KAAK,MAAM,CAAC,eAAe,EAAE,KAAK,CAAC,IAAI,aAAa,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC;YAC7E,IAAI,eAAe,KAAK,cAAc,CAAC,IAAI,EAAE,CAAC;gBAC7C,MAAM;oBACL,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,eAAe,CAAC;oBACpD,KAAK,EAAE,KAAK,CAAC,KAAK;oBAClB,QAAQ,EAAE,EAAE,QAAQ,EAAE,KAAK,CAAC,GAAG,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE;iBAC7D,CAAC;YACH,CAAC;QACF,CAAC;IACF,CAAC;IAEM,OAAO;QACb,MAAM,cAAc,GAAG,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC5D,OAAO,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC;aACvC,MAAM,CAAC,CAAC,eAAe,EAAE,EAAE,CAAC,eAAe,KAAK,cAAc,CAAC,IAAI,CAAC;aACpE,GAAG,CAAC,CAAC,eAAe,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,eAAe,CAAC,CAAC,CAAC;IAC1E,CAAC;IAEM,WAAW,CAAC,MAAsB;QACxC,MAAM,cAAc,GAAG,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC5D,MAAM,WAAW,GAAG,cAAc,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC5D,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;YAC/B,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;QAC1C,CAAC;QACD,OAAO;YACN,KAAK,EAAE,WAAW,CAAC,KAAK;YACxB,QAAQ,EAAE,EAAE,QAAQ,EAAE,WAAW,CAAC,GAAG,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE;SAC9D,CAAC;IACH,CAAC;IAEM,MAAM,CACZ,MAAsB,EACtB,SAAiB,EACjB,KAA0C;QAE1C,MAAM,cAAc,GAAG,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC5D,MAAM,eAAe,GAAG,MAAM,CAAC,SAAS,CAAC;QACzC,MAAM,YAAY,GAAG,cAAc,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;QAC5D,IAAI,YAAY,KAAK,SAAS,IAAI,YAAY,CAAC,GAAG,IAAI,KAAK,CAAC,GAAG,EAAE,CAAC;YACjE,OAAO;QACR,CAAC;QACD,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,eAAe,EAAE,KAAK,CAAC,CAAC;QACxD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE;YAC3B,MAAM;YACN,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,QAAQ,EAAE,EAAE,QAAQ,EAAE,KAAK,CAAC,GAAG,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE;SAC7D,CAAC,CAAC;IACJ,CAAC;CACD;AAED;;;;GAIG;AACH,MAAM,UAAU,MAAM,CACrB,YAAgE,EAChE,QAAmC;IAMnC,yEAAyE;IACzE,6BAA6B;IAC7B,MAAM,KAAK,GAAwC;QAClD,GAAG,EAAE,CAAC;QACN,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;QACrB,KAAK,EAAE,EAAE,GAAG,YAAY,EAAE;KAC1B,CAAC;IACF,MAAM,OAAO,GAAG,CACf,GAAQ,EACR,eAGC,EAIA,EAAE,CAAC,CAAC;QACL,WAAW,EAAE,EAAE,KAAK,EAAE,wBAAwB,EAAE,QAAQ,EAAE,wBAAwB,EAAE;QACpF,OAAO,EAAE,QAAQ,CAChB,IAAI,sBAAsB,CAAC,GAAG,EAAE,mBAAmB,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,CACtF;KACD,CAAC,CAAC;IACH,OAAO,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,YAAY,EAAE,sBAAsB,EAAE,CAAC,CAAC;AACzE,CAAC","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 { Listenable } from \"@fluidframework/core-interfaces\";\n\nimport type { BroadcastControls, BroadcastControlSettings } from \"./broadcastControls.js\";\nimport { OptionalBroadcastControl } from \"./broadcastControls.js\";\nimport type { ValueManager } from \"./internalTypes.js\";\nimport { objectEntries } from \"./internalUtils.js\";\nimport type { LatestValueClientData, LatestValueData } from \"./latestValueTypes.js\";\nimport type { ISessionClient } from \"./presence.js\";\nimport { datastoreFromHandle, type StateDatastore } from \"./stateDatastore.js\";\nimport { brandIVM } from \"./valueManager.js\";\n\nimport type {\n\tJsonDeserialized,\n\tJsonSerializable,\n} from \"@fluidframework/presence/internal/core-interfaces\";\nimport type { InternalTypes } from \"@fluidframework/presence/internal/exposedInternalTypes\";\nimport type { InternalUtilityTypes } from \"@fluidframework/presence/internal/exposedUtilityTypes\";\n\n/**\n * @sealed\n * @alpha\n */\nexport interface LatestValueManagerEvents<T> {\n\t/**\n\t * Raised when remote client's value is updated, which may be the same value.\n\t *\n\t * @eventProperty\n\t */\n\tupdated: (update: LatestValueClientData<T>) => void;\n}\n\n/**\n * Value manager that provides the latest known value from this client to others and read access to their values.\n * All participant clients must provide a value.\n *\n * @remarks Create using {@link Latest} registered to {@link PresenceStates}.\n *\n * @sealed\n * @alpha\n */\nexport interface LatestValueManager<T> {\n\t/**\n\t * Events for Latest value manager.\n\t */\n\treadonly events: Listenable<LatestValueManagerEvents<T>>;\n\n\t/**\n\t * Controls for management of sending updates.\n\t */\n\treadonly controls: BroadcastControls;\n\n\t/**\n\t * Current state for this client.\n\t * State for this client that will be transmitted to all other connected clients.\n\t * @remarks Manager assumes ownership of the value and its references. Make a deep clone before\n\t * setting, if needed. No comparison is done to detect changes; all sets are transmitted.\n\t */\n\tget local(): InternalUtilityTypes.FullyReadonly<JsonDeserialized<T>>;\n\tset local(value: JsonSerializable<T> & JsonDeserialized<T>);\n\n\t/**\n\t * Iterable access to remote clients' values.\n\t */\n\tclientValues(): IterableIterator<LatestValueClientData<T>>;\n\t/**\n\t * Array of known clients.\n\t */\n\tclients(): ISessionClient[];\n\t/**\n\t * Access to a specific client's value.\n\t */\n\tclientValue(client: ISessionClient): LatestValueData<T>;\n}\n\nclass LatestValueManagerImpl<T, Key extends string>\n\timplements\n\t\tLatestValueManager<T>,\n\t\tRequired<ValueManager<T, InternalTypes.ValueRequiredState<T>>>\n{\n\tpublic readonly events = createEmitter<LatestValueManagerEvents<T>>();\n\tpublic readonly controls: OptionalBroadcastControl;\n\n\tpublic constructor(\n\t\tprivate readonly key: Key,\n\t\tprivate readonly datastore: StateDatastore<Key, InternalTypes.ValueRequiredState<T>>,\n\t\tpublic readonly value: InternalTypes.ValueRequiredState<T>,\n\t\tcontrolSettings: BroadcastControlSettings | undefined,\n\t) {\n\t\tthis.controls = new OptionalBroadcastControl(controlSettings);\n\t}\n\n\tpublic get local(): InternalUtilityTypes.FullyReadonly<JsonDeserialized<T>> {\n\t\treturn this.value.value;\n\t}\n\n\tpublic set local(value: JsonSerializable<T> & JsonDeserialized<T>) {\n\t\tthis.value.rev += 1;\n\t\tthis.value.timestamp = Date.now();\n\t\tthis.value.value = value;\n\t\tthis.datastore.localUpdate(this.key, this.value, {\n\t\t\tallowableUpdateLatencyMs: this.controls.allowableUpdateLatencyMs,\n\t\t});\n\t}\n\n\tpublic *clientValues(): IterableIterator<LatestValueClientData<T>> {\n\t\tconst allKnownStates = this.datastore.knownValues(this.key);\n\t\tfor (const [clientSessionId, value] of objectEntries(allKnownStates.states)) {\n\t\t\tif (clientSessionId !== allKnownStates.self) {\n\t\t\t\tyield {\n\t\t\t\t\tclient: this.datastore.lookupClient(clientSessionId),\n\t\t\t\t\tvalue: value.value,\n\t\t\t\t\tmetadata: { revision: value.rev, timestamp: value.timestamp },\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic clients(): ISessionClient[] {\n\t\tconst allKnownStates = this.datastore.knownValues(this.key);\n\t\treturn Object.keys(allKnownStates.states)\n\t\t\t.filter((clientSessionId) => clientSessionId !== allKnownStates.self)\n\t\t\t.map((clientSessionId) => this.datastore.lookupClient(clientSessionId));\n\t}\n\n\tpublic clientValue(client: ISessionClient): LatestValueData<T> {\n\t\tconst allKnownStates = this.datastore.knownValues(this.key);\n\t\tconst clientState = allKnownStates.states[client.sessionId];\n\t\tif (clientState === undefined) {\n\t\t\tthrow new Error(\"No entry for clientId\");\n\t\t}\n\t\treturn {\n\t\t\tvalue: clientState.value,\n\t\t\tmetadata: { revision: clientState.rev, timestamp: Date.now() },\n\t\t};\n\t}\n\n\tpublic update(\n\t\tclient: ISessionClient,\n\t\t_received: number,\n\t\tvalue: InternalTypes.ValueRequiredState<T>,\n\t): void {\n\t\tconst allKnownStates = this.datastore.knownValues(this.key);\n\t\tconst clientSessionId = client.sessionId;\n\t\tconst currentState = allKnownStates.states[clientSessionId];\n\t\tif (currentState !== undefined && currentState.rev >= value.rev) {\n\t\t\treturn;\n\t\t}\n\t\tthis.datastore.update(this.key, clientSessionId, value);\n\t\tthis.events.emit(\"updated\", {\n\t\t\tclient,\n\t\t\tvalue: value.value,\n\t\t\tmetadata: { revision: value.rev, timestamp: value.timestamp },\n\t\t});\n\t}\n}\n\n/**\n * Factory for creating a {@link LatestValueManager}.\n *\n * @alpha\n */\nexport function Latest<T extends object, Key extends string = string>(\n\tinitialValue: JsonSerializable<T> & JsonDeserialized<T> & object,\n\tcontrols?: BroadcastControlSettings,\n): InternalTypes.ManagerFactory<\n\tKey,\n\tInternalTypes.ValueRequiredState<T>,\n\tLatestValueManager<T>\n> {\n\t// LatestValueManager takes ownership of initialValue but makes a shallow\n\t// copy for basic protection.\n\tconst value: InternalTypes.ValueRequiredState<T> = {\n\t\trev: 0,\n\t\ttimestamp: Date.now(),\n\t\tvalue: { ...initialValue },\n\t};\n\tconst factory = (\n\t\tkey: Key,\n\t\tdatastoreHandle: InternalTypes.StateDatastoreHandle<\n\t\t\tKey,\n\t\t\tInternalTypes.ValueRequiredState<T>\n\t\t>,\n\t): {\n\t\tinitialData: { value: typeof value; allowableUpdateLatencyMs: number | undefined };\n\t\tmanager: InternalTypes.StateValue<LatestValueManager<T>>;\n\t} => ({\n\t\tinitialData: { value, allowableUpdateLatencyMs: controls?.allowableUpdateLatencyMs },\n\t\tmanager: brandIVM<LatestValueManagerImpl<T, Key>, T, InternalTypes.ValueRequiredState<T>>(\n\t\t\tnew LatestValueManagerImpl(key, datastoreFromHandle(datastoreHandle), value, controls),\n\t\t),\n\t});\n\treturn Object.assign(factory, { instanceBase: LatestValueManagerImpl });\n}\n"]}
@@ -22,11 +22,11 @@ function mergeGeneralDatastoreMessageContent(base, newData) {
22
22
  // be set explicitly after the loop.
23
23
  const mergedData = queueDatastore[workspaceName] ?? {};
24
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 objectEntries(workspaceData[valueManagerKey])) {
27
- mergedData[valueManagerKey] ?? (mergedData[valueManagerKey] = {});
28
- const oldData = mergedData[valueManagerKey][clientSessionId];
29
- mergedData[valueManagerKey][clientSessionId] = mergeValueDirectory(oldData, value, 0);
25
+ for (const [valueManagerKey, valueManagerValue] of objectEntries(workspaceData)) {
26
+ for (const [clientSessionId, value] of objectEntries(valueManagerValue)) {
27
+ const mergeObject = (mergedData[valueManagerKey] ?? (mergedData[valueManagerKey] = {}));
28
+ const oldData = mergeObject[clientSessionId];
29
+ mergeObject[clientSessionId] = mergeValueDirectory(oldData, value, 0);
30
30
  }
31
31
  }
32
32
  // Store the merged data in the function-local queue workspace. The whole contents of this
@@ -1 +1 @@
1
- {"version":3,"file":"presenceDatastoreManager.js","sourceRoot":"","sources":["../src/presenceDatastoreManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,qCAAqC,CAAC;AAO7D,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAQnD,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,aAAa,CAAC,aAAa,CAAC,eAAe,CAAC,CAAC,EAAE,CAAC;gBACtF,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,GACrB,IAAI,CAAC,SAAS,CAAC,wBAAwB,CAAC,CAAC;QAC1C,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;QACrC,iFAAiF;QACjF,oEAAoE;QACpE,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC,iBAAiB,CAAC,kBAAkB,CAAE,CAAC;QAE1E,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 type { IEphemeralRuntime } from \"./internalTypes.js\";\nimport { objectEntries } from \"./internalUtils.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 objectEntries(workspaceData[valueManagerKey])) {\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: ValueElementMap<PresenceStatesSchema> | undefined =\n\t\t\tthis.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\t// When connected, `clientToSessionId` must always have current connection entry.\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\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
+ {"version":3,"file":"presenceDatastoreManager.js","sourceRoot":"","sources":["../src/presenceDatastoreManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,qCAAqC,CAAC;AAO7D,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAQnD,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,CAAC,eAAe,EAAE,iBAAiB,CAAC,IAAI,aAAa,CAAC,aAAa,CAAC,EAAE,CAAC;YACjF,KAAK,MAAM,CAAC,eAAe,EAAE,KAAK,CAAC,IAAI,aAAa,CAAC,iBAAiB,CAAC,EAAE,CAAC;gBACzE,MAAM,WAAW,GAAG,CAAC,UAAU,CAAC,eAAe,MAA1B,UAAU,CAAC,eAAe,IAAM,EAAE,EAAC,CAAC;gBACzD,MAAM,OAAO,GAAG,WAAW,CAAC,eAAe,CAAC,CAAC;gBAC7C,WAAW,CAAC,eAAe,CAAC,GAAG,mBAAmB,CACjD,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,GACrB,IAAI,CAAC,SAAS,CAAC,wBAAwB,CAAC,CAAC;QAC1C,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;QACrC,iFAAiF;QACjF,oEAAoE;QACpE,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC,iBAAiB,CAAC,kBAAkB,CAAE,CAAC;QAE1E,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 type { IEphemeralRuntime } from \"./internalTypes.js\";\nimport { objectEntries } from \"./internalUtils.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, valueManagerValue] of objectEntries(workspaceData)) {\n\t\t\tfor (const [clientSessionId, value] of objectEntries(valueManagerValue)) {\n\t\t\t\tconst mergeObject = (mergedData[valueManagerKey] ??= {});\n\t\t\t\tconst oldData = mergeObject[clientSessionId];\n\t\t\t\tmergeObject[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: ValueElementMap<PresenceStatesSchema> | undefined =\n\t\t\tthis.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\t// When connected, `clientToSessionId` must always have current connection entry.\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\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":"presenceStates.d.ts","sourceRoot":"","sources":["../src/presenceStates.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"presenceStates.d.ts","sourceRoot":"","sources":["../src/presenceStates.ts"],"names":[],"mappings":"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"}
@@ -94,8 +94,7 @@ class PresenceStatesImpl {
94
94
  nodes[key] = newNodeData.manager;
95
95
  if ("initialData" in newNodeData) {
96
96
  const { value, allowableUpdateLatencyMs } = newNodeData.initialData;
97
- datastore[key] ?? (datastore[key] = {});
98
- datastore[key][clientSessionId] = value;
97
+ (datastore[key] ?? (datastore[key] = {}))[clientSessionId] = value;
99
98
  newValues[key] = value;
100
99
  if (allowableUpdateLatencyMs !== undefined) {
101
100
  cumulativeAllowableUpdateLatencyMs =
@@ -121,6 +120,8 @@ class PresenceStatesImpl {
121
120
  knownValues(key) {
122
121
  return {
123
122
  self: this.runtime.clientSessionId,
123
+ // Caller must only use `key`s that are part of `this.datastore`.
124
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
124
125
  states: this.datastore[key],
125
126
  };
126
127
  }
@@ -145,14 +146,15 @@ class PresenceStatesImpl {
145
146
  this.nodes[key] = nodeData.manager;
146
147
  if ("initialData" in nodeData) {
147
148
  const { value, allowableUpdateLatencyMs } = nodeData.initialData;
148
- if (key in this.datastore) {
149
- // Already have received state from other clients. Kept in `all`.
150
- // TODO: Send current `all` state to state manager.
149
+ let datastoreValue = this.datastore[key];
150
+ if (datastoreValue === undefined) {
151
+ datastoreValue = this.datastore[key] = {};
151
152
  }
152
153
  else {
153
- this.datastore[key] = {};
154
+ // Already have received state from other clients. Kept in `all`.
155
+ // TODO: Send current `all` state to state manager.
154
156
  }
155
- this.datastore[key][this.runtime.clientSessionId] = value;
157
+ datastoreValue[this.runtime.clientSessionId] = value;
156
158
  this.runtime.localUpdate({ [key]: value }, {
157
159
  allowableUpdateLatencyMs: allowableUpdateLatencyMs ?? this.controls.allowableUpdateLatencyMs,
158
160
  });
@@ -163,31 +165,33 @@ class PresenceStatesImpl {
163
165
  this.controls.allowableUpdateLatencyMs = controls.allowableUpdateLatencyMs;
164
166
  }
165
167
  for (const [key, nodeFactory] of Object.entries(content)) {
166
- if (key in this.nodes) {
167
- const node = unbrandIVM(this.nodes[key]);
168
+ const brandedIVM = this.nodes[key];
169
+ if (brandedIVM === undefined) {
170
+ this.add(key, nodeFactory);
171
+ }
172
+ else {
173
+ const node = unbrandIVM(brandedIVM);
168
174
  if (!(node instanceof nodeFactory.instanceBase)) {
169
175
  throw new TypeError(`State "${key}" previously created by different value manager.`);
170
176
  }
171
177
  }
172
- else {
173
- this.add(key, nodeFactory);
174
- }
175
178
  }
176
179
  return this;
177
180
  }
178
181
  processUpdate(received, timeModifier, remoteDatastore) {
179
182
  for (const [key, remoteAllKnownState] of Object.entries(remoteDatastore)) {
180
- if (key in this.nodes) {
181
- const node = unbrandIVM(this.nodes[key]);
183
+ const brandedIVM = this.nodes[key];
184
+ if (brandedIVM === undefined) {
185
+ // Assume all broadcast state is meant to be kept even if not currently registered.
186
+ mergeUntrackedDatastore(key, remoteAllKnownState, this.datastore, timeModifier);
187
+ }
188
+ else {
189
+ const node = unbrandIVM(brandedIVM);
182
190
  for (const [clientSessionId, value] of objectEntries(remoteAllKnownState)) {
183
191
  const client = this.runtime.lookupClient(clientSessionId);
184
192
  node.update(client, received, value);
185
193
  }
186
194
  }
187
- else {
188
- // Assume all broadcast state is meant to be kept even if not currently registered.
189
- mergeUntrackedDatastore(key, remoteAllKnownState, this.datastore, timeModifier);
190
- }
191
195
  }
192
196
  }
193
197
  }
@@ -1 +1 @@
1
- {"version":3,"file":"presenceStates.js","sourceRoot":"","sources":["../src/presenceStates.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\";\n\nimport type { ClientConnectionId } from \"./baseTypes.js\";\nimport type { BroadcastControlSettings } from \"./broadcastControls.js\";\nimport { RequiredBroadcastControl } from \"./broadcastControls.js\";\nimport type { InternalTypes } from \"./exposedInternalTypes.js\";\nimport type { ClientRecord } from \"./internalTypes.js\";\nimport type { RecordEntryTypes } from \"./internalUtils.js\";\nimport { getOrCreateRecord, objectEntries } from \"./internalUtils.js\";\nimport type { ClientSessionId, ISessionClient } from \"./presence.js\";\nimport type { LocalStateUpdateOptions, StateDatastore } from \"./stateDatastore.js\";\nimport { handleFromDatastore } from \"./stateDatastore.js\";\nimport type { PresenceStates, PresenceStatesSchema } from \"./types.js\";\nimport { unbrandIVM } from \"./valueManager.js\";\n\n/**\n * Extracts `Part` from {@link InternalTypes.ManagerFactory} return type\n * matching the {@link PresenceStatesSchema} `Keys` given.\n *\n * @remarks\n * If the `Part` is an optional property, undefined will be included in the\n * result. Applying `Required` to the return type prior to extracting `Part`\n * does not work as expected. Use Exclude\\<, undefined\\> can be used as needed.\n *\n * @internal\n */\nexport type MapSchemaElement<\n\tTSchema extends PresenceStatesSchema,\n\tPart extends keyof ReturnType<TSchema[keyof TSchema]>,\n\tKeys extends keyof TSchema = keyof TSchema,\n> = ReturnType<TSchema[Keys]>[Part];\n\n/**\n * @internal\n */\nexport interface RuntimeLocalUpdateOptions {\n\tallowableUpdateLatencyMs: number;\n\n\t/**\n\t * Special option allowed for unicast notifications.\n\t */\n\ttargetClientId?: ClientConnectionId;\n}\n\n/**\n * @internal\n */\nexport interface PresenceRuntime {\n\treadonly clientSessionId: ClientSessionId;\n\tlookupClient(clientId: ClientConnectionId): ISessionClient;\n\tlocalUpdate(\n\t\tstates: { [key: string]: ClientUpdateEntry },\n\t\toptions: RuntimeLocalUpdateOptions,\n\t): void;\n}\n\ntype PresenceSubSchemaFromWorkspaceSchema<\n\tTSchema extends PresenceStatesSchema,\n\tPart extends keyof ReturnType<TSchema[keyof TSchema]>,\n> = {\n\t[Key in keyof TSchema]: MapSchemaElement<TSchema, Part, Key>;\n};\n\ntype MapEntries<TSchema extends PresenceStatesSchema> = PresenceSubSchemaFromWorkspaceSchema<\n\tTSchema,\n\t\"manager\"\n>;\n\n/**\n * ValueElementMap is a map of key to a map of clientId to ValueState.\n * It is not restricted to the schema of the map as it may receive updates from other clients\n * with managers that have not been registered locally. Each map node is responsible for keeping\n * all session's state to be able to pick arbitrary client to rebroadcast to others.\n *\n * This generic aspect makes some typing difficult. The loose typing is not broadcast to the\n * consumers that are expected to maintain their schema over multiple versions of clients.\n *\n * @internal\n */\nexport interface ValueElementMap<_TSchema extends PresenceStatesSchema> {\n\t[key: string]: ClientRecord<InternalTypes.ValueDirectoryOrState<unknown>>;\n}\n\n// An attempt to make the type more precise, but it is not working.\n// If the casting in support code is too much we could keep two references to the same\n// complete datastore, but with the respective types desired.\n// type ValueElementMap<TSchema extends PresenceStatesNodeSchema> =\n// \t| {\n// \t\t\t[Key in keyof TSchema & string]?: {\n// \t\t\t\t[ClientSessionId: ClientSessionId]: InternalTypes.ValueDirectoryOrState<MapSchemaElement<TSchema,\"value\",Key>>;\n// \t\t\t};\n// \t }\n// \t| {\n// \t\t\t[key: string]: ClientRecord<InternalTypes.ValueDirectoryOrState<unknown>>;\n// \t };\n// interface ValueElementMap<TValue> {\n// \t[Id: string]: ClientRecord<InternalTypes.ValueDirectoryOrState<TValue>>;\n// \t// Version with local packed in is convenient for map, but not for join broadcast to serialize simply.\n// \t// [Id: string]: {\n// \t// \tlocal: InternalTypes.ValueDirectoryOrState<TValue>;\n// \t// \tall: ClientRecord<InternalTypes.ValueDirectoryOrState<TValue>>;\n// \t// };\n// }\n\n/**\n * @internal\n */\nexport type ClientUpdateEntry = InternalTypes.ValueDirectoryOrState<unknown> & {\n\tignoreUnmonitored?: true;\n};\n\ntype ClientUpdateRecord = ClientRecord<ClientUpdateEntry>;\n\ninterface ValueUpdateRecord {\n\t[valueKey: string]: ClientUpdateRecord;\n}\n\n/**\n * @internal\n */\nexport interface PresenceStatesInternal {\n\tensureContent<TSchemaAdditional extends PresenceStatesSchema>(\n\t\tcontent: TSchemaAdditional,\n\t\tcontrols: BroadcastControlSettings | undefined,\n\t): PresenceStates<TSchemaAdditional>;\n\tprocessUpdate(\n\t\treceived: number,\n\t\ttimeModifier: number,\n\t\tremoteDatastore: ValueUpdateRecord,\n\t\tsenderConnectionId: ClientConnectionId,\n\t): void;\n}\n\nfunction isValueDirectory<\n\tT,\n\tTValueState extends\n\t\t| InternalTypes.ValueRequiredState<T>\n\t\t| InternalTypes.ValueOptionalState<T>,\n>(\n\tvalue: InternalTypes.ValueDirectory<T> | TValueState,\n): value is InternalTypes.ValueDirectory<T> {\n\treturn \"items\" in value;\n}\n\n/**\n * Merge a value directory.\n *\n * @internal\n */\nexport function mergeValueDirectory<\n\tT,\n\tTValueState extends\n\t\t| InternalTypes.ValueRequiredState<T>\n\t\t| InternalTypes.ValueOptionalState<T>,\n>(\n\tbase: TValueState | InternalTypes.ValueDirectory<T> | undefined,\n\tupdate: TValueState | InternalTypes.ValueDirectory<T>,\n\ttimeDelta: number,\n): TValueState | InternalTypes.ValueDirectory<T> {\n\tif (!isValueDirectory(update)) {\n\t\tif (base === undefined || update.rev > base.rev) {\n\t\t\treturn { ...update, timestamp: update.timestamp + timeDelta };\n\t\t}\n\t\treturn base;\n\t}\n\n\tlet mergeBase: InternalTypes.ValueDirectory<T>;\n\tif (base === undefined) {\n\t\tmergeBase = { rev: update.rev, items: {} };\n\t} else {\n\t\tconst baseIsDirectory = isValueDirectory(base);\n\t\tif (base.rev >= update.rev) {\n\t\t\tif (!baseIsDirectory) {\n\t\t\t\t// base is leaf value that is more recent - nothing to do\n\t\t\t\treturn base;\n\t\t\t}\n\t\t\t// While base has more advanced revision, assume mis-ordering or\n\t\t\t// missed and catchup update needs merged in.\n\t\t\tmergeBase = base;\n\t\t} else {\n\t\t\tmergeBase = { rev: update.rev, items: baseIsDirectory ? base.items : {} };\n\t\t}\n\t}\n\tfor (const [key, value] of Object.entries(update.items)) {\n\t\tconst baseElement = mergeBase.items[key];\n\t\tmergeBase.items[key] = mergeValueDirectory(baseElement, value, timeDelta);\n\t}\n\treturn mergeBase;\n}\n\n/**\n * Updates remote state into the local [untracked] datastore.\n *\n * @param key - The key of the datastore to merge the untracked data into.\n * @param remoteAllKnownState - The remote state to merge into the datastore.\n * @param datastore - The datastore to merge the untracked data into.\n *\n * @remarks\n * In the case of ignored unmonitored data, the client entries are not stored,\n * though the value keys will be populated and often remain empty.\n *\n * @internal\n */\nexport function mergeUntrackedDatastore(\n\tkey: string,\n\tremoteAllKnownState: ClientUpdateRecord,\n\tdatastore: ValueElementMap<PresenceStatesSchema>,\n\ttimeModifier: number,\n): void {\n\tconst localAllKnownState = getOrCreateRecord(\n\t\tdatastore,\n\t\tkey,\n\t\t(): RecordEntryTypes<typeof datastore> => ({}),\n\t);\n\tfor (const [clientSessionId, value] of objectEntries(remoteAllKnownState)) {\n\t\tif (!(\"ignoreUnmonitored\" in value)) {\n\t\t\tlocalAllKnownState[clientSessionId] = mergeValueDirectory(\n\t\t\t\tlocalAllKnownState[clientSessionId],\n\t\t\t\tvalue,\n\t\t\t\ttimeModifier,\n\t\t\t);\n\t\t}\n\t}\n}\n\n/**\n * The default allowable update latency for PresenceStates workspaces in milliseconds.\n */\nconst defaultAllowableUpdateLatencyMs = 60;\n\n/**\n * Produces the value type of a schema element or set of elements.\n */\ntype SchemaElementValueType<\n\tTSchema extends PresenceStatesSchema,\n\tKeys extends keyof TSchema & string,\n> = Exclude<MapSchemaElement<TSchema, \"initialData\", Keys>, undefined>[\"value\"];\n\nclass PresenceStatesImpl<TSchema extends PresenceStatesSchema>\n\timplements\n\t\tPresenceStatesInternal,\n\t\tPresenceStates<TSchema>,\n\t\tStateDatastore<\n\t\t\tkeyof TSchema & string,\n\t\t\tSchemaElementValueType<TSchema, keyof TSchema & string>\n\t\t>\n{\n\tprivate readonly nodes: MapEntries<TSchema>;\n\tpublic readonly props: PresenceStates<TSchema>[\"props\"];\n\n\tpublic readonly controls: RequiredBroadcastControl;\n\n\tpublic constructor(\n\t\tprivate readonly runtime: PresenceRuntime,\n\t\tprivate readonly datastore: ValueElementMap<TSchema>,\n\t\tinitialContent: TSchema,\n\t\tcontrolsSettings: BroadcastControlSettings | undefined,\n\t) {\n\t\tthis.controls = new RequiredBroadcastControl(defaultAllowableUpdateLatencyMs);\n\t\tif (controlsSettings?.allowableUpdateLatencyMs !== undefined) {\n\t\t\tthis.controls.allowableUpdateLatencyMs = controlsSettings.allowableUpdateLatencyMs;\n\t\t}\n\n\t\t// Prepare initial map content from initial state\n\t\t{\n\t\t\tconst clientSessionId = this.runtime.clientSessionId;\n\t\t\t// Empty record does not satisfy the type, but nodes will post loop.\n\t\t\t// eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n\t\t\tconst nodes = {} as MapEntries<TSchema>;\n\t\t\tlet anyInitialValues = false;\n\t\t\tconst newValues: { [key: string]: InternalTypes.ValueDirectoryOrState<unknown> } = {};\n\t\t\tlet cumulativeAllowableUpdateLatencyMs: number | undefined;\n\t\t\tfor (const [key, nodeFactory] of Object.entries(initialContent)) {\n\t\t\t\tconst newNodeData = nodeFactory(key, handleFromDatastore(this));\n\t\t\t\tnodes[key as keyof TSchema] = newNodeData.manager;\n\t\t\t\tif (\"initialData\" in newNodeData) {\n\t\t\t\t\tconst { value, allowableUpdateLatencyMs } = newNodeData.initialData;\n\t\t\t\t\tdatastore[key] ??= {};\n\t\t\t\t\tdatastore[key][clientSessionId] = value;\n\t\t\t\t\tnewValues[key] = value;\n\t\t\t\t\tif (allowableUpdateLatencyMs !== undefined) {\n\t\t\t\t\t\tcumulativeAllowableUpdateLatencyMs =\n\t\t\t\t\t\t\tcumulativeAllowableUpdateLatencyMs === undefined\n\t\t\t\t\t\t\t\t? allowableUpdateLatencyMs\n\t\t\t\t\t\t\t\t: Math.min(cumulativeAllowableUpdateLatencyMs, allowableUpdateLatencyMs);\n\t\t\t\t\t}\n\t\t\t\t\tanyInitialValues = true;\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.nodes = nodes;\n\t\t\t// props is the public view of nodes that limits the entries types to\n\t\t\t// the public interface of the value manager with an additional type\n\t\t\t// filter that beguiles the type system. So just reinterpret cast.\n\t\t\tthis.props = this.nodes as unknown as PresenceStates<TSchema>[\"props\"];\n\n\t\t\tif (anyInitialValues) {\n\t\t\t\tthis.runtime.localUpdate(newValues, {\n\t\t\t\t\tallowableUpdateLatencyMs:\n\t\t\t\t\t\tcumulativeAllowableUpdateLatencyMs ?? this.controls.allowableUpdateLatencyMs,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic knownValues<Key extends keyof TSchema & string>(\n\t\tkey: Key,\n\t): {\n\t\tself: ClientSessionId | undefined;\n\t\tstates: ClientRecord<SchemaElementValueType<TSchema, Key>>;\n\t} {\n\t\treturn {\n\t\t\tself: this.runtime.clientSessionId,\n\t\t\tstates: this.datastore[key],\n\t\t};\n\t}\n\n\tpublic localUpdate<Key extends keyof TSchema & string>(\n\t\tkey: Key,\n\t\tvalue: SchemaElementValueType<TSchema, Key> & ClientUpdateEntry,\n\t\toptions: LocalStateUpdateOptions,\n\t): void {\n\t\tthis.runtime.localUpdate(\n\t\t\t{ [key]: value },\n\t\t\t{\n\t\t\t\t...options,\n\t\t\t\tallowableUpdateLatencyMs:\n\t\t\t\t\toptions.allowableUpdateLatencyMs ?? this.controls.allowableUpdateLatencyMs,\n\t\t\t},\n\t\t);\n\t}\n\n\tpublic update<Key extends keyof TSchema & string>(\n\t\tkey: Key,\n\t\tclientId: ClientSessionId,\n\t\tvalue: Exclude<MapSchemaElement<TSchema, \"initialData\", Key>, undefined>[\"value\"],\n\t): void {\n\t\t// Callers my only use `key`s that are part of `this.datastore`.\n\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\tconst allKnownState = this.datastore[key]!;\n\t\tallKnownState[clientId] = mergeValueDirectory(allKnownState[clientId], value, 0);\n\t}\n\n\tpublic lookupClient(clientId: ClientConnectionId): ISessionClient {\n\t\treturn this.runtime.lookupClient(clientId);\n\t}\n\n\tpublic add<\n\t\tTKey extends string,\n\t\tTValue extends InternalTypes.ValueDirectoryOrState<unknown>,\n\t\tTValueManager,\n\t>(\n\t\tkey: TKey,\n\t\tnodeFactory: InternalTypes.ManagerFactory<TKey, TValue, TValueManager>,\n\t): asserts this is PresenceStates<\n\t\tTSchema & Record<TKey, InternalTypes.ManagerFactory<TKey, TValue, TValueManager>>\n\t> {\n\t\tassert(!(key in this.nodes), 0xa3c /* Already have entry for key in map */);\n\t\tconst nodeData = nodeFactory(key, handleFromDatastore(this));\n\t\tthis.nodes[key] = nodeData.manager;\n\t\tif (\"initialData\" in nodeData) {\n\t\t\tconst { value, allowableUpdateLatencyMs } = nodeData.initialData;\n\t\t\tif (key in this.datastore) {\n\t\t\t\t// Already have received state from other clients. Kept in `all`.\n\t\t\t\t// TODO: Send current `all` state to state manager.\n\t\t\t} else {\n\t\t\t\tthis.datastore[key] = {};\n\t\t\t}\n\t\t\tthis.datastore[key][this.runtime.clientSessionId] = value;\n\t\t\tthis.runtime.localUpdate(\n\t\t\t\t{ [key]: value },\n\t\t\t\t{\n\t\t\t\t\tallowableUpdateLatencyMs:\n\t\t\t\t\t\tallowableUpdateLatencyMs ?? this.controls.allowableUpdateLatencyMs,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t}\n\n\tpublic ensureContent<TSchemaAdditional extends PresenceStatesSchema>(\n\t\tcontent: TSchemaAdditional,\n\t\tcontrols: BroadcastControlSettings | undefined,\n\t): PresenceStates<TSchema & TSchemaAdditional> {\n\t\tif (controls?.allowableUpdateLatencyMs !== undefined) {\n\t\t\tthis.controls.allowableUpdateLatencyMs = controls.allowableUpdateLatencyMs;\n\t\t}\n\t\tfor (const [key, nodeFactory] of Object.entries(content)) {\n\t\t\tif (key in this.nodes) {\n\t\t\t\tconst node = unbrandIVM(this.nodes[key]);\n\t\t\t\tif (!(node instanceof nodeFactory.instanceBase)) {\n\t\t\t\t\tthrow new TypeError(`State \"${key}\" previously created by different value manager.`);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tthis.add(key, nodeFactory);\n\t\t\t}\n\t\t}\n\t\treturn this as PresenceStates<TSchema & TSchemaAdditional>;\n\t}\n\n\tpublic processUpdate(\n\t\treceived: number,\n\t\ttimeModifier: number,\n\t\tremoteDatastore: ValueUpdateRecord,\n\t): void {\n\t\tfor (const [key, remoteAllKnownState] of Object.entries(remoteDatastore)) {\n\t\t\tif (key in this.nodes) {\n\t\t\t\tconst node = unbrandIVM(this.nodes[key]);\n\t\t\t\tfor (const [clientSessionId, value] of objectEntries(remoteAllKnownState)) {\n\t\t\t\t\tconst client = this.runtime.lookupClient(clientSessionId);\n\t\t\t\t\tnode.update(client, received, value);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Assume all broadcast state is meant to be kept even if not currently registered.\n\t\t\t\tmergeUntrackedDatastore(key, remoteAllKnownState, this.datastore, timeModifier);\n\t\t\t}\n\t\t}\n\t}\n}\n\n/**\n * Create a new PresenceStates using the DataStoreRuntime provided.\n * @param initialContent - The initial value managers to register.\n */\nexport function createPresenceStates<TSchema extends PresenceStatesSchema>(\n\truntime: PresenceRuntime,\n\tdatastore: ValueElementMap<PresenceStatesSchema>,\n\tinitialContent: TSchema,\n\tcontrols: BroadcastControlSettings | undefined,\n): { public: PresenceStates<TSchema>; internal: PresenceStatesInternal } {\n\tconst impl = new PresenceStatesImpl<TSchema>(runtime, datastore, initialContent, controls);\n\n\treturn {\n\t\tpublic: impl,\n\t\tinternal: impl,\n\t};\n}\n"]}
1
+ {"version":3,"file":"presenceStates.js","sourceRoot":"","sources":["../src/presenceStates.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,qCAAqC,CAAC;AAI7D,OAAO,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AAIlE,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAGtE,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAwH/C,SAAS,gBAAgB,CAMxB,KAAoD;IAEpD,OAAO,OAAO,IAAI,KAAK,CAAC;AACzB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAMlC,IAA+D,EAC/D,MAAqD,EACrD,SAAiB;IAEjB,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,EAAE,CAAC;QAC/B,IAAI,IAAI,KAAK,SAAS,IAAI,MAAM,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACjD,OAAO,EAAE,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,GAAG,SAAS,EAAE,CAAC;QAC/D,CAAC;QACD,OAAO,IAAI,CAAC;IACb,CAAC;IAED,IAAI,SAA0C,CAAC;IAC/C,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACxB,SAAS,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;IAC5C,CAAC;SAAM,CAAC;QACP,MAAM,eAAe,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;QAC/C,IAAI,IAAI,CAAC,GAAG,IAAI,MAAM,CAAC,GAAG,EAAE,CAAC;YAC5B,IAAI,CAAC,eAAe,EAAE,CAAC;gBACtB,yDAAyD;gBACzD,OAAO,IAAI,CAAC;YACb,CAAC;YACD,gEAAgE;YAChE,6CAA6C;YAC7C,SAAS,GAAG,IAAI,CAAC;QAClB,CAAC;aAAM,CAAC;YACP,SAAS,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,KAAK,EAAE,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;QAC3E,CAAC;IACF,CAAC;IACD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QACzD,MAAM,WAAW,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACzC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,mBAAmB,CAAC,WAAW,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;IAC3E,CAAC;IACD,OAAO,SAAS,CAAC;AAClB,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,uBAAuB,CACtC,GAAW,EACX,mBAAuC,EACvC,SAAgD,EAChD,YAAoB;IAEpB,MAAM,kBAAkB,GAAG,iBAAiB,CAC3C,SAAS,EACT,GAAG,EACH,GAAuC,EAAE,CAAC,CAAC,EAAE,CAAC,CAC9C,CAAC;IACF,KAAK,MAAM,CAAC,eAAe,EAAE,KAAK,CAAC,IAAI,aAAa,CAAC,mBAAmB,CAAC,EAAE,CAAC;QAC3E,IAAI,CAAC,CAAC,mBAAmB,IAAI,KAAK,CAAC,EAAE,CAAC;YACrC,kBAAkB,CAAC,eAAe,CAAC,GAAG,mBAAmB,CACxD,kBAAkB,CAAC,eAAe,CAAC,EACnC,KAAK,EACL,YAAY,CACZ,CAAC;QACH,CAAC;IACF,CAAC;AACF,CAAC;AAED;;GAEG;AACH,MAAM,+BAA+B,GAAG,EAAE,CAAC;AAU3C,MAAM,kBAAkB;IAcvB,YACkB,OAAwB,EACxB,SAAmC,EACpD,cAAuB,EACvB,gBAAsD;QAHrC,YAAO,GAAP,OAAO,CAAiB;QACxB,cAAS,GAAT,SAAS,CAA0B;QAIpD,IAAI,CAAC,QAAQ,GAAG,IAAI,wBAAwB,CAAC,+BAA+B,CAAC,CAAC;QAC9E,IAAI,gBAAgB,EAAE,wBAAwB,KAAK,SAAS,EAAE,CAAC;YAC9D,IAAI,CAAC,QAAQ,CAAC,wBAAwB,GAAG,gBAAgB,CAAC,wBAAwB,CAAC;QACpF,CAAC;QAED,iDAAiD;QACjD,CAAC;YACA,MAAM,eAAe,GAAG,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC;YACrD,oEAAoE;YACpE,yEAAyE;YACzE,MAAM,KAAK,GAAG,EAAyB,CAAC;YACxC,IAAI,gBAAgB,GAAG,KAAK,CAAC;YAC7B,MAAM,SAAS,GAAoE,EAAE,CAAC;YACtF,IAAI,kCAAsD,CAAC;YAC3D,KAAK,MAAM,CAAC,GAAG,EAAE,WAAW,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE,CAAC;gBACjE,MAAM,WAAW,GAAG,WAAW,CAAC,GAAG,EAAE,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAC;gBAChE,KAAK,CAAC,GAAoB,CAAC,GAAG,WAAW,CAAC,OAAO,CAAC;gBAClD,IAAI,aAAa,IAAI,WAAW,EAAE,CAAC;oBAClC,MAAM,EAAE,KAAK,EAAE,wBAAwB,EAAE,GAAG,WAAW,CAAC,WAAW,CAAC;oBACpE,CAAC,SAAS,CAAC,GAAG,MAAb,SAAS,CAAC,GAAG,IAAM,EAAE,EAAC,CAAC,eAAe,CAAC,GAAG,KAAK,CAAC;oBACjD,SAAS,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;oBACvB,IAAI,wBAAwB,KAAK,SAAS,EAAE,CAAC;wBAC5C,kCAAkC;4BACjC,kCAAkC,KAAK,SAAS;gCAC/C,CAAC,CAAC,wBAAwB;gCAC1B,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,kCAAkC,EAAE,wBAAwB,CAAC,CAAC;oBAC5E,CAAC;oBACD,gBAAgB,GAAG,IAAI,CAAC;gBACzB,CAAC;YACF,CAAC;YACD,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;YACnB,qEAAqE;YACrE,oEAAoE;YACpE,kEAAkE;YAClE,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAoD,CAAC;YAEvE,IAAI,gBAAgB,EAAE,CAAC;gBACtB,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,SAAS,EAAE;oBACnC,wBAAwB,EACvB,kCAAkC,IAAI,IAAI,CAAC,QAAQ,CAAC,wBAAwB;iBAC7E,CAAC,CAAC;YACJ,CAAC;QACF,CAAC;IACF,CAAC;IAEM,WAAW,CACjB,GAAQ;QAKR,OAAO;YACN,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,eAAe;YAClC,iEAAiE;YACjE,oEAAoE;YACpE,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAE;SAC5B,CAAC;IACH,CAAC;IAEM,WAAW,CACjB,GAAQ,EACR,KAA+D,EAC/D,OAAgC;QAEhC,IAAI,CAAC,OAAO,CAAC,WAAW,CACvB,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,EAChB;YACC,GAAG,OAAO;YACV,wBAAwB,EACvB,OAAO,CAAC,wBAAwB,IAAI,IAAI,CAAC,QAAQ,CAAC,wBAAwB;SAC3E,CACD,CAAC;IACH,CAAC;IAEM,MAAM,CACZ,GAAQ,EACR,QAAyB,EACzB,KAAiF;QAEjF,gEAAgE;QAChE,oEAAoE;QACpE,MAAM,aAAa,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAE,CAAC;QAC3C,aAAa,CAAC,QAAQ,CAAC,GAAG,mBAAmB,CAAC,aAAa,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;IAClF,CAAC;IAEM,YAAY,CAAC,QAA4B;QAC/C,OAAO,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;IAC5C,CAAC;IAEM,GAAG,CAKT,GAAS,EACT,WAAsE;QAItE,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,uCAAuC,CAAC,CAAC;QAC5E,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,EAAE,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAC;QAC7D,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC;QACnC,IAAI,aAAa,IAAI,QAAQ,EAAE,CAAC;YAC/B,MAAM,EAAE,KAAK,EAAE,wBAAwB,EAAE,GAAG,QAAQ,CAAC,WAAW,CAAC;YACjE,IAAI,cAAc,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;YACzC,IAAI,cAAc,KAAK,SAAS,EAAE,CAAC;gBAClC,cAAc,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC;YAC3C,CAAC;iBAAM,CAAC;gBACP,iEAAiE;gBACjE,mDAAmD;YACpD,CAAC;YACD,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,GAAG,KAAK,CAAC;YACrD,IAAI,CAAC,OAAO,CAAC,WAAW,CACvB,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,EAChB;gBACC,wBAAwB,EACvB,wBAAwB,IAAI,IAAI,CAAC,QAAQ,CAAC,wBAAwB;aACnE,CACD,CAAC;QACH,CAAC;IACF,CAAC;IAEM,aAAa,CACnB,OAA0B,EAC1B,QAA8C;QAE9C,IAAI,QAAQ,EAAE,wBAAwB,KAAK,SAAS,EAAE,CAAC;YACtD,IAAI,CAAC,QAAQ,CAAC,wBAAwB,GAAG,QAAQ,CAAC,wBAAwB,CAAC;QAC5E,CAAC;QACD,KAAK,MAAM,CAAC,GAAG,EAAE,WAAW,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;YAC1D,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YACnC,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;gBAC9B,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;YAC5B,CAAC;iBAAM,CAAC;gBACP,MAAM,IAAI,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC;gBACpC,IAAI,CAAC,CAAC,IAAI,YAAY,WAAW,CAAC,YAAY,CAAC,EAAE,CAAC;oBACjD,MAAM,IAAI,SAAS,CAAC,UAAU,GAAG,kDAAkD,CAAC,CAAC;gBACtF,CAAC;YACF,CAAC;QACF,CAAC;QACD,OAAO,IAAmD,CAAC;IAC5D,CAAC;IAEM,aAAa,CACnB,QAAgB,EAChB,YAAoB,EACpB,eAAkC;QAElC,KAAK,MAAM,CAAC,GAAG,EAAE,mBAAmB,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,EAAE,CAAC;YAC1E,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YACnC,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;gBAC9B,mFAAmF;gBACnF,uBAAuB,CAAC,GAAG,EAAE,mBAAmB,EAAE,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;YACjF,CAAC;iBAAM,CAAC;gBACP,MAAM,IAAI,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC;gBACpC,KAAK,MAAM,CAAC,eAAe,EAAE,KAAK,CAAC,IAAI,aAAa,CAAC,mBAAmB,CAAC,EAAE,CAAC;oBAC3E,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,eAAe,CAAC,CAAC;oBAC1D,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;gBACtC,CAAC;YACF,CAAC;QACF,CAAC;IACF,CAAC;CACD;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CACnC,OAAwB,EACxB,SAAgD,EAChD,cAAuB,EACvB,QAA8C;IAE9C,MAAM,IAAI,GAAG,IAAI,kBAAkB,CAAU,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,QAAQ,CAAC,CAAC;IAE3F,OAAO;QACN,MAAM,EAAE,IAAI;QACZ,QAAQ,EAAE,IAAI;KACd,CAAC;AACH,CAAC","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\";\n\nimport type { ClientConnectionId } from \"./baseTypes.js\";\nimport type { BroadcastControlSettings } from \"./broadcastControls.js\";\nimport { RequiredBroadcastControl } from \"./broadcastControls.js\";\nimport type { InternalTypes } from \"./exposedInternalTypes.js\";\nimport type { ClientRecord } from \"./internalTypes.js\";\nimport type { RecordEntryTypes } from \"./internalUtils.js\";\nimport { getOrCreateRecord, objectEntries } from \"./internalUtils.js\";\nimport type { ClientSessionId, ISessionClient } from \"./presence.js\";\nimport type { LocalStateUpdateOptions, StateDatastore } from \"./stateDatastore.js\";\nimport { handleFromDatastore } from \"./stateDatastore.js\";\nimport type { PresenceStates, PresenceStatesSchema } from \"./types.js\";\nimport { unbrandIVM } from \"./valueManager.js\";\n\n/**\n * Extracts `Part` from {@link InternalTypes.ManagerFactory} return type\n * matching the {@link PresenceStatesSchema} `Keys` given.\n *\n * @remarks\n * If the `Part` is an optional property, undefined will be included in the\n * result. Applying `Required` to the return type prior to extracting `Part`\n * does not work as expected. Use Exclude\\<, undefined\\> can be used as needed.\n *\n * @internal\n */\nexport type MapSchemaElement<\n\tTSchema extends PresenceStatesSchema,\n\tPart extends keyof ReturnType<TSchema[keyof TSchema]>,\n\tKeys extends keyof TSchema = keyof TSchema,\n> = ReturnType<TSchema[Keys]>[Part];\n\n/**\n * @internal\n */\nexport interface RuntimeLocalUpdateOptions {\n\tallowableUpdateLatencyMs: number;\n\n\t/**\n\t * Special option allowed for unicast notifications.\n\t */\n\ttargetClientId?: ClientConnectionId;\n}\n\n/**\n * @internal\n */\nexport interface PresenceRuntime {\n\treadonly clientSessionId: ClientSessionId;\n\tlookupClient(clientId: ClientConnectionId): ISessionClient;\n\tlocalUpdate(\n\t\tstates: { [key: string]: ClientUpdateEntry },\n\t\toptions: RuntimeLocalUpdateOptions,\n\t): void;\n}\n\ntype PresenceSubSchemaFromWorkspaceSchema<\n\tTSchema extends PresenceStatesSchema,\n\tPart extends keyof ReturnType<TSchema[keyof TSchema]>,\n> = {\n\t[Key in keyof TSchema]: MapSchemaElement<TSchema, Part, Key>;\n};\n\ntype MapEntries<TSchema extends PresenceStatesSchema> = PresenceSubSchemaFromWorkspaceSchema<\n\tTSchema,\n\t\"manager\"\n>;\n\n/**\n * ValueElementMap is a map of key to a map of clientId to ValueState.\n * It is not restricted to the schema of the map as it may receive updates from other clients\n * with managers that have not been registered locally. Each map node is responsible for keeping\n * all session's state to be able to pick arbitrary client to rebroadcast to others.\n *\n * This generic aspect makes some typing difficult. The loose typing is not broadcast to the\n * consumers that are expected to maintain their schema over multiple versions of clients.\n *\n * @internal\n */\nexport interface ValueElementMap<_TSchema extends PresenceStatesSchema> {\n\t[key: string]: ClientRecord<InternalTypes.ValueDirectoryOrState<unknown>>;\n}\n\n// An attempt to make the type more precise, but it is not working.\n// If the casting in support code is too much we could keep two references to the same\n// complete datastore, but with the respective types desired.\n// type ValueElementMap<TSchema extends PresenceStatesNodeSchema> =\n// \t| {\n// \t\t\t[Key in keyof TSchema & string]?: {\n// \t\t\t\t[ClientSessionId: ClientSessionId]: InternalTypes.ValueDirectoryOrState<MapSchemaElement<TSchema,\"value\",Key>>;\n// \t\t\t};\n// \t }\n// \t| {\n// \t\t\t[key: string]: ClientRecord<InternalTypes.ValueDirectoryOrState<unknown>>;\n// \t };\n// interface ValueElementMap<TValue> {\n// \t[Id: string]: ClientRecord<InternalTypes.ValueDirectoryOrState<TValue>>;\n// \t// Version with local packed in is convenient for map, but not for join broadcast to serialize simply.\n// \t// [Id: string]: {\n// \t// \tlocal: InternalTypes.ValueDirectoryOrState<TValue>;\n// \t// \tall: ClientRecord<InternalTypes.ValueDirectoryOrState<TValue>>;\n// \t// };\n// }\n\n/**\n * @internal\n */\nexport type ClientUpdateEntry = InternalTypes.ValueDirectoryOrState<unknown> & {\n\tignoreUnmonitored?: true;\n};\n\ntype ClientUpdateRecord = ClientRecord<ClientUpdateEntry>;\n\ninterface ValueUpdateRecord {\n\t[valueKey: string]: ClientUpdateRecord;\n}\n\n/**\n * @internal\n */\nexport interface PresenceStatesInternal {\n\tensureContent<TSchemaAdditional extends PresenceStatesSchema>(\n\t\tcontent: TSchemaAdditional,\n\t\tcontrols: BroadcastControlSettings | undefined,\n\t): PresenceStates<TSchemaAdditional>;\n\tprocessUpdate(\n\t\treceived: number,\n\t\ttimeModifier: number,\n\t\tremoteDatastore: ValueUpdateRecord,\n\t\tsenderConnectionId: ClientConnectionId,\n\t): void;\n}\n\nfunction isValueDirectory<\n\tT,\n\tTValueState extends\n\t\t| InternalTypes.ValueRequiredState<T>\n\t\t| InternalTypes.ValueOptionalState<T>,\n>(\n\tvalue: InternalTypes.ValueDirectory<T> | TValueState,\n): value is InternalTypes.ValueDirectory<T> {\n\treturn \"items\" in value;\n}\n\n/**\n * Merge a value directory.\n *\n * @internal\n */\nexport function mergeValueDirectory<\n\tT,\n\tTValueState extends\n\t\t| InternalTypes.ValueRequiredState<T>\n\t\t| InternalTypes.ValueOptionalState<T>,\n>(\n\tbase: TValueState | InternalTypes.ValueDirectory<T> | undefined,\n\tupdate: TValueState | InternalTypes.ValueDirectory<T>,\n\ttimeDelta: number,\n): TValueState | InternalTypes.ValueDirectory<T> {\n\tif (!isValueDirectory(update)) {\n\t\tif (base === undefined || update.rev > base.rev) {\n\t\t\treturn { ...update, timestamp: update.timestamp + timeDelta };\n\t\t}\n\t\treturn base;\n\t}\n\n\tlet mergeBase: InternalTypes.ValueDirectory<T>;\n\tif (base === undefined) {\n\t\tmergeBase = { rev: update.rev, items: {} };\n\t} else {\n\t\tconst baseIsDirectory = isValueDirectory(base);\n\t\tif (base.rev >= update.rev) {\n\t\t\tif (!baseIsDirectory) {\n\t\t\t\t// base is leaf value that is more recent - nothing to do\n\t\t\t\treturn base;\n\t\t\t}\n\t\t\t// While base has more advanced revision, assume mis-ordering or\n\t\t\t// missed and catchup update needs merged in.\n\t\t\tmergeBase = base;\n\t\t} else {\n\t\t\tmergeBase = { rev: update.rev, items: baseIsDirectory ? base.items : {} };\n\t\t}\n\t}\n\tfor (const [key, value] of Object.entries(update.items)) {\n\t\tconst baseElement = mergeBase.items[key];\n\t\tmergeBase.items[key] = mergeValueDirectory(baseElement, value, timeDelta);\n\t}\n\treturn mergeBase;\n}\n\n/**\n * Updates remote state into the local [untracked] datastore.\n *\n * @param key - The key of the datastore to merge the untracked data into.\n * @param remoteAllKnownState - The remote state to merge into the datastore.\n * @param datastore - The datastore to merge the untracked data into.\n *\n * @remarks\n * In the case of ignored unmonitored data, the client entries are not stored,\n * though the value keys will be populated and often remain empty.\n *\n * @internal\n */\nexport function mergeUntrackedDatastore(\n\tkey: string,\n\tremoteAllKnownState: ClientUpdateRecord,\n\tdatastore: ValueElementMap<PresenceStatesSchema>,\n\ttimeModifier: number,\n): void {\n\tconst localAllKnownState = getOrCreateRecord(\n\t\tdatastore,\n\t\tkey,\n\t\t(): RecordEntryTypes<typeof datastore> => ({}),\n\t);\n\tfor (const [clientSessionId, value] of objectEntries(remoteAllKnownState)) {\n\t\tif (!(\"ignoreUnmonitored\" in value)) {\n\t\t\tlocalAllKnownState[clientSessionId] = mergeValueDirectory(\n\t\t\t\tlocalAllKnownState[clientSessionId],\n\t\t\t\tvalue,\n\t\t\t\ttimeModifier,\n\t\t\t);\n\t\t}\n\t}\n}\n\n/**\n * The default allowable update latency for PresenceStates workspaces in milliseconds.\n */\nconst defaultAllowableUpdateLatencyMs = 60;\n\n/**\n * Produces the value type of a schema element or set of elements.\n */\ntype SchemaElementValueType<\n\tTSchema extends PresenceStatesSchema,\n\tKeys extends keyof TSchema & string,\n> = Exclude<MapSchemaElement<TSchema, \"initialData\", Keys>, undefined>[\"value\"];\n\nclass PresenceStatesImpl<TSchema extends PresenceStatesSchema>\n\timplements\n\t\tPresenceStatesInternal,\n\t\tPresenceStates<TSchema>,\n\t\tStateDatastore<\n\t\t\tkeyof TSchema & string,\n\t\t\tSchemaElementValueType<TSchema, keyof TSchema & string>\n\t\t>\n{\n\tprivate readonly nodes: MapEntries<TSchema>;\n\tpublic readonly props: PresenceStates<TSchema>[\"props\"];\n\n\tpublic readonly controls: RequiredBroadcastControl;\n\n\tpublic constructor(\n\t\tprivate readonly runtime: PresenceRuntime,\n\t\tprivate readonly datastore: ValueElementMap<TSchema>,\n\t\tinitialContent: TSchema,\n\t\tcontrolsSettings: BroadcastControlSettings | undefined,\n\t) {\n\t\tthis.controls = new RequiredBroadcastControl(defaultAllowableUpdateLatencyMs);\n\t\tif (controlsSettings?.allowableUpdateLatencyMs !== undefined) {\n\t\t\tthis.controls.allowableUpdateLatencyMs = controlsSettings.allowableUpdateLatencyMs;\n\t\t}\n\n\t\t// Prepare initial map content from initial state\n\t\t{\n\t\t\tconst clientSessionId = this.runtime.clientSessionId;\n\t\t\t// Empty record does not satisfy the type, but nodes will post loop.\n\t\t\t// eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n\t\t\tconst nodes = {} as MapEntries<TSchema>;\n\t\t\tlet anyInitialValues = false;\n\t\t\tconst newValues: { [key: string]: InternalTypes.ValueDirectoryOrState<unknown> } = {};\n\t\t\tlet cumulativeAllowableUpdateLatencyMs: number | undefined;\n\t\t\tfor (const [key, nodeFactory] of Object.entries(initialContent)) {\n\t\t\t\tconst newNodeData = nodeFactory(key, handleFromDatastore(this));\n\t\t\t\tnodes[key as keyof TSchema] = newNodeData.manager;\n\t\t\t\tif (\"initialData\" in newNodeData) {\n\t\t\t\t\tconst { value, allowableUpdateLatencyMs } = newNodeData.initialData;\n\t\t\t\t\t(datastore[key] ??= {})[clientSessionId] = value;\n\t\t\t\t\tnewValues[key] = value;\n\t\t\t\t\tif (allowableUpdateLatencyMs !== undefined) {\n\t\t\t\t\t\tcumulativeAllowableUpdateLatencyMs =\n\t\t\t\t\t\t\tcumulativeAllowableUpdateLatencyMs === undefined\n\t\t\t\t\t\t\t\t? allowableUpdateLatencyMs\n\t\t\t\t\t\t\t\t: Math.min(cumulativeAllowableUpdateLatencyMs, allowableUpdateLatencyMs);\n\t\t\t\t\t}\n\t\t\t\t\tanyInitialValues = true;\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.nodes = nodes;\n\t\t\t// props is the public view of nodes that limits the entries types to\n\t\t\t// the public interface of the value manager with an additional type\n\t\t\t// filter that beguiles the type system. So just reinterpret cast.\n\t\t\tthis.props = this.nodes as unknown as PresenceStates<TSchema>[\"props\"];\n\n\t\t\tif (anyInitialValues) {\n\t\t\t\tthis.runtime.localUpdate(newValues, {\n\t\t\t\t\tallowableUpdateLatencyMs:\n\t\t\t\t\t\tcumulativeAllowableUpdateLatencyMs ?? this.controls.allowableUpdateLatencyMs,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic knownValues<Key extends keyof TSchema & string>(\n\t\tkey: Key,\n\t): {\n\t\tself: ClientSessionId | undefined;\n\t\tstates: ClientRecord<SchemaElementValueType<TSchema, Key>>;\n\t} {\n\t\treturn {\n\t\t\tself: this.runtime.clientSessionId,\n\t\t\t// Caller must only use `key`s that are part of `this.datastore`.\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\tstates: this.datastore[key]!,\n\t\t};\n\t}\n\n\tpublic localUpdate<Key extends keyof TSchema & string>(\n\t\tkey: Key,\n\t\tvalue: SchemaElementValueType<TSchema, Key> & ClientUpdateEntry,\n\t\toptions: LocalStateUpdateOptions,\n\t): void {\n\t\tthis.runtime.localUpdate(\n\t\t\t{ [key]: value },\n\t\t\t{\n\t\t\t\t...options,\n\t\t\t\tallowableUpdateLatencyMs:\n\t\t\t\t\toptions.allowableUpdateLatencyMs ?? this.controls.allowableUpdateLatencyMs,\n\t\t\t},\n\t\t);\n\t}\n\n\tpublic update<Key extends keyof TSchema & string>(\n\t\tkey: Key,\n\t\tclientId: ClientSessionId,\n\t\tvalue: Exclude<MapSchemaElement<TSchema, \"initialData\", Key>, undefined>[\"value\"],\n\t): void {\n\t\t// Callers my only use `key`s that are part of `this.datastore`.\n\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\tconst allKnownState = this.datastore[key]!;\n\t\tallKnownState[clientId] = mergeValueDirectory(allKnownState[clientId], value, 0);\n\t}\n\n\tpublic lookupClient(clientId: ClientConnectionId): ISessionClient {\n\t\treturn this.runtime.lookupClient(clientId);\n\t}\n\n\tpublic add<\n\t\tTKey extends string,\n\t\tTValue extends InternalTypes.ValueDirectoryOrState<unknown>,\n\t\tTValueManager,\n\t>(\n\t\tkey: TKey,\n\t\tnodeFactory: InternalTypes.ManagerFactory<TKey, TValue, TValueManager>,\n\t): asserts this is PresenceStates<\n\t\tTSchema & Record<TKey, InternalTypes.ManagerFactory<TKey, TValue, TValueManager>>\n\t> {\n\t\tassert(!(key in this.nodes), 0xa3c /* Already have entry for key in map */);\n\t\tconst nodeData = nodeFactory(key, handleFromDatastore(this));\n\t\tthis.nodes[key] = nodeData.manager;\n\t\tif (\"initialData\" in nodeData) {\n\t\t\tconst { value, allowableUpdateLatencyMs } = nodeData.initialData;\n\t\t\tlet datastoreValue = this.datastore[key];\n\t\t\tif (datastoreValue === undefined) {\n\t\t\t\tdatastoreValue = this.datastore[key] = {};\n\t\t\t} else {\n\t\t\t\t// Already have received state from other clients. Kept in `all`.\n\t\t\t\t// TODO: Send current `all` state to state manager.\n\t\t\t}\n\t\t\tdatastoreValue[this.runtime.clientSessionId] = value;\n\t\t\tthis.runtime.localUpdate(\n\t\t\t\t{ [key]: value },\n\t\t\t\t{\n\t\t\t\t\tallowableUpdateLatencyMs:\n\t\t\t\t\t\tallowableUpdateLatencyMs ?? this.controls.allowableUpdateLatencyMs,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t}\n\n\tpublic ensureContent<TSchemaAdditional extends PresenceStatesSchema>(\n\t\tcontent: TSchemaAdditional,\n\t\tcontrols: BroadcastControlSettings | undefined,\n\t): PresenceStates<TSchema & TSchemaAdditional> {\n\t\tif (controls?.allowableUpdateLatencyMs !== undefined) {\n\t\t\tthis.controls.allowableUpdateLatencyMs = controls.allowableUpdateLatencyMs;\n\t\t}\n\t\tfor (const [key, nodeFactory] of Object.entries(content)) {\n\t\t\tconst brandedIVM = this.nodes[key];\n\t\t\tif (brandedIVM === undefined) {\n\t\t\t\tthis.add(key, nodeFactory);\n\t\t\t} else {\n\t\t\t\tconst node = unbrandIVM(brandedIVM);\n\t\t\t\tif (!(node instanceof nodeFactory.instanceBase)) {\n\t\t\t\t\tthrow new TypeError(`State \"${key}\" previously created by different value manager.`);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn this as PresenceStates<TSchema & TSchemaAdditional>;\n\t}\n\n\tpublic processUpdate(\n\t\treceived: number,\n\t\ttimeModifier: number,\n\t\tremoteDatastore: ValueUpdateRecord,\n\t): void {\n\t\tfor (const [key, remoteAllKnownState] of Object.entries(remoteDatastore)) {\n\t\t\tconst brandedIVM = this.nodes[key];\n\t\t\tif (brandedIVM === undefined) {\n\t\t\t\t// Assume all broadcast state is meant to be kept even if not currently registered.\n\t\t\t\tmergeUntrackedDatastore(key, remoteAllKnownState, this.datastore, timeModifier);\n\t\t\t} else {\n\t\t\t\tconst node = unbrandIVM(brandedIVM);\n\t\t\t\tfor (const [clientSessionId, value] of objectEntries(remoteAllKnownState)) {\n\t\t\t\t\tconst client = this.runtime.lookupClient(clientSessionId);\n\t\t\t\t\tnode.update(client, received, value);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\n/**\n * Create a new PresenceStates using the DataStoreRuntime provided.\n * @param initialContent - The initial value managers to register.\n */\nexport function createPresenceStates<TSchema extends PresenceStatesSchema>(\n\truntime: PresenceRuntime,\n\tdatastore: ValueElementMap<PresenceStatesSchema>,\n\tinitialContent: TSchema,\n\tcontrols: BroadcastControlSettings | undefined,\n): { public: PresenceStates<TSchema>; internal: PresenceStatesInternal } {\n\tconst impl = new PresenceStatesImpl<TSchema>(runtime, datastore, initialContent, controls);\n\n\treturn {\n\t\tpublic: impl,\n\t\tinternal: impl,\n\t};\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"systemWorkspace.d.ts","sourceRoot":"","sources":["../src/systemWorkspace.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uCAAuC,CAAC;AACvE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0CAA0C,CAAC;AAGzE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,KAAK,EACX,eAAe,EACf,SAAS,EAET,cAAc,EACd,MAAM,eAAe,CAAC;AAEvB,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAClE,OAAO,KAAK,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEvE;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACxC,iBAAiB,EAAE;QAClB,CAAC,YAAY,EAAE,kBAAkB,GAAG,aAAa,CAAC,kBAAkB,CAAC,eAAe,CAAC,CAAC;KACtF,CAAC;CACF;AA0CD;;GAEG;AACH,MAAM,WAAW,eAGhB,SAAQ,IAAI,CAAC,SAAS,EAAE,cAAc,GAAG,aAAa,GAAG,WAAW,CAAC;IACrE;;;;OAIG;IACH,iBAAiB,CAAC,kBAAkB,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAEhE;;;;OAIG;IACH,wBAAwB,CAAC,kBAAkB,EAAE,kBAAkB,GAAG,IAAI,CAAC;CACvE;AA6KD;;;;GAIG;AACH,wBAAgB,qBAAqB,CACpC,eAAe,EAAE,eAAe,EAChC,SAAS,EAAE,wBAAwB,EACnC,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,cAAc,EAAE,gBAAgB,CAAC,CAAC,EACxD,QAAQ,EAAE,SAAS,GACjB;IACF,SAAS,EAAE,eAAe,CAAC;IAC3B,WAAW,EAAE;QACZ,QAAQ,EAAE,sBAAsB,CAAC;QACjC,MAAM,EAAE,cAAc,CAAC,oBAAoB,CAAC,CAAC;KAC7C,CAAC;CACF,CASA"}
1
+ {"version":3,"file":"systemWorkspace.d.ts","sourceRoot":"","sources":["../src/systemWorkspace.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uCAAuC,CAAC;AACvE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0CAA0C,CAAC;AAGzE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,KAAK,EACX,eAAe,EACf,SAAS,EAET,cAAc,EACd,MAAM,eAAe,CAAC;AAEvB,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAElE,OAAO,KAAK,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEvE;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACxC,iBAAiB,EAAE;QAClB,CAAC,YAAY,EAAE,kBAAkB,GAAG,aAAa,CAAC,kBAAkB,CAAC,eAAe,CAAC,CAAC;KACtF,CAAC;CACF;AAoCD;;GAEG;AACH,MAAM,WAAW,eAGhB,SAAQ,IAAI,CAAC,SAAS,EAAE,cAAc,GAAG,aAAa,GAAG,WAAW,CAAC;IACrE;;;;OAIG;IACH,iBAAiB,CAAC,kBAAkB,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAEhE;;;;OAIG;IACH,wBAAwB,CAAC,kBAAkB,EAAE,kBAAkB,GAAG,IAAI,CAAC;CACvE;AAmND;;;;GAIG;AACH,wBAAgB,qBAAqB,CACpC,eAAe,EAAE,eAAe,EAChC,SAAS,EAAE,wBAAwB,EACnC,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,cAAc,EAAE,gBAAgB,CAAC,CAAC,EACxD,QAAQ,EAAE,SAAS,GACjB;IACF,SAAS,EAAE,eAAe,CAAC;IAC3B,WAAW,EAAE;QACZ,QAAQ,EAAE,sBAAsB,CAAC;QACjC,MAAM,EAAE,cAAc,CAAC,oBAAoB,CAAC,CAAC;KAC7C,CAAC;CACF,CASA"}
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { assert } from "@fluidframework/core-utils/internal";
6
6
  import { SessionClientStatus } from "./presence.js";
7
+ import { TimerManager } from "./timerManager.js";
7
8
  class SessionClient {
8
9
  constructor(sessionId, connectionId = undefined) {
9
10
  this.sessionId = sessionId;
@@ -13,10 +14,7 @@ class SessionClient {
13
14
  * during a session.
14
15
  */
15
16
  this.order = 0;
16
- this.connectionStatus =
17
- connectionId === undefined
18
- ? SessionClientStatus.Disconnected
19
- : SessionClientStatus.Connected;
17
+ this.connectionStatus = SessionClientStatus.Disconnected;
20
18
  }
21
19
  getConnectionId() {
22
20
  if (this.connectionId === undefined) {
@@ -27,8 +25,7 @@ class SessionClient {
27
25
  getConnectionStatus() {
28
26
  return this.connectionStatus;
29
27
  }
30
- setConnectionId(connectionId) {
31
- this.connectionId = connectionId;
28
+ setConnected() {
32
29
  this.connectionStatus = SessionClientStatus.Connected;
33
30
  }
34
31
  setDisconnected() {
@@ -48,6 +45,10 @@ class SystemWorkspaceImpl {
48
45
  * An entry is for session ID if the value's `sessionId` matches the key.
49
46
  */
50
47
  this.attendees = new Map();
48
+ // When local client disconnects, we lose the connectivity status updates for remote attendees in the session.
49
+ // Upon reconnect, we mark all other attendees connections as stale and update their status to disconnected after 30 seconds of inactivity.
50
+ this.staleConnectionClients = new Set();
51
+ this.staleConnectionTimer = new TimerManager();
51
52
  this.selfAttendee = new SessionClient(clientSessionId);
52
53
  this.attendees.set(clientSessionId, this.selfAttendee);
53
54
  }
@@ -55,28 +56,19 @@ class SystemWorkspaceImpl {
55
56
  throw new Error("Method not implemented.");
56
57
  }
57
58
  processUpdate(_received, _timeModifier, remoteDatastore, senderConnectionId) {
58
- const postUpdateActions = [];
59
59
  const audienceMembers = this.audience.getMembers();
60
- const connectedAttendees = new Set();
60
+ const joiningAttendees = new Set();
61
61
  for (const [clientConnectionId, value] of Object.entries(remoteDatastore.clientToSessionId)) {
62
62
  const clientSessionId = value.value;
63
- const { attendee, isNew } = this.ensureAttendee(clientSessionId, clientConnectionId,
64
- /* order */ value.rev);
65
- // Check new attendee against audience to see if they're currently connected
66
- const isAttendeeConnected = audienceMembers.has(clientConnectionId);
67
- if (isAttendeeConnected) {
68
- connectedAttendees.add(attendee);
69
- if (attendee.getConnectionStatus() === SessionClientStatus.Disconnected) {
70
- attendee.setConnectionId(clientConnectionId);
71
- }
72
- if (isNew) {
73
- // If the attendee is both new and in audience (i.e. currently connected), emit an attendeeJoined event.
74
- postUpdateActions.push(() => this.events.emit("attendeeJoined", attendee));
75
- }
76
- }
77
- // If the attendee is not in the audience, they are considered disconnected.
78
- if (!connectedAttendees.has(attendee)) {
79
- attendee.setDisconnected();
63
+ const { attendee, isJoining } = this.ensureAttendee(clientSessionId, clientConnectionId,
64
+ /* order */ value.rev,
65
+ // If the attendee is present in audience OR if the attendee update is from the sending remote client itself,
66
+ // then the attendee is considered connected.
67
+ /* isConnected */ senderConnectionId === clientConnectionId ||
68
+ audienceMembers.has(clientConnectionId));
69
+ // If the attendee is joining the session, add them to the list of joining attendees to be announced later.
70
+ if (isJoining) {
71
+ joiningAttendees.add(attendee);
80
72
  }
81
73
  const knownSessionId = this.datastore.clientToSessionId[clientConnectionId];
82
74
  if (knownSessionId === undefined) {
@@ -87,24 +79,46 @@ class SystemWorkspaceImpl {
87
79
  }
88
80
  }
89
81
  // TODO: reorganize processUpdate and caller to process actions after all updates are processed.
90
- for (const action of postUpdateActions) {
91
- action();
82
+ for (const announcedAttendee of joiningAttendees) {
83
+ this.events.emit("attendeeJoined", announcedAttendee);
92
84
  }
93
85
  }
94
86
  onConnectionAdded(clientConnectionId) {
87
+ assert(this.selfAttendee.getConnectionStatus() === SessionClientStatus.Disconnected, 0xaad /* Local client should be 'Disconnected' before adding new connection. */);
95
88
  this.datastore.clientToSessionId[clientConnectionId] = {
96
89
  rev: this.selfAttendee.order++,
97
90
  timestamp: Date.now(),
98
91
  value: this.selfAttendee.sessionId,
99
92
  };
100
- this.selfAttendee.setConnectionId(clientConnectionId);
93
+ // Mark 'Connected' remote attendees connections as stale
94
+ for (const staleConnectionClient of this.attendees.values()) {
95
+ if (staleConnectionClient.getConnectionStatus() === SessionClientStatus.Connected) {
96
+ this.staleConnectionClients.add(staleConnectionClient);
97
+ }
98
+ }
99
+ // Update the self attendee
100
+ this.selfAttendee.connectionId = clientConnectionId;
101
+ this.selfAttendee.setConnected();
101
102
  this.attendees.set(clientConnectionId, this.selfAttendee);
103
+ this.staleConnectionTimer.setTimeout(() => {
104
+ for (const client of this.staleConnectionClients) {
105
+ client.setDisconnected();
106
+ }
107
+ for (const client of this.staleConnectionClients) {
108
+ this.events.emit("attendeeDisconnected", client);
109
+ }
110
+ this.staleConnectionClients.clear();
111
+ }, 30000);
102
112
  }
103
113
  removeClientConnectionId(clientConnectionId) {
104
114
  const attendee = this.attendees.get(clientConnectionId);
105
115
  if (!attendee) {
106
116
  return;
107
117
  }
118
+ // If the local connection is being removed, clear the stale connection timer
119
+ if (attendee === this.selfAttendee) {
120
+ this.staleConnectionTimer.clearTimeout();
121
+ }
108
122
  // If the last known connectionID is different from the connection ID being removed, the attendee has reconnected,
109
123
  // therefore we should not change the attendee connection status or emit a disconnect event.
110
124
  const attendeeReconnected = attendee.getConnectionId() !== clientConnectionId;
@@ -112,6 +126,7 @@ class SystemWorkspaceImpl {
112
126
  if (!attendeeReconnected && connected) {
113
127
  attendee.setDisconnected();
114
128
  this.events.emit("attendeeDisconnected", attendee);
129
+ this.staleConnectionClients.delete(attendee);
115
130
  }
116
131
  }
117
132
  getAttendees() {
@@ -136,26 +151,37 @@ class SystemWorkspaceImpl {
136
151
  * in the attendee map. If not present, SessionClient is created and added
137
152
  * to map. If present, make sure the current connection ID is updated.
138
153
  */
139
- ensureAttendee(clientSessionId, clientConnectionId, order) {
154
+ ensureAttendee(clientSessionId, clientConnectionId, order, isConnected) {
140
155
  let attendee = this.attendees.get(clientSessionId);
141
- let isNew = false;
156
+ let isJoining = false;
142
157
  if (attendee === undefined) {
143
158
  // New attendee. Create SessionClient and add session ID based
144
159
  // entry to map.
145
160
  attendee = new SessionClient(clientSessionId, clientConnectionId);
146
161
  this.attendees.set(clientSessionId, attendee);
147
- isNew = true;
162
+ if (isConnected) {
163
+ attendee.setConnected();
164
+ isJoining = true;
165
+ }
148
166
  }
149
167
  else if (order > attendee.order) {
150
168
  // The given association is newer than the one we have.
151
169
  // Update the order and current connection ID.
152
170
  attendee.order = order;
153
- attendee.setConnectionId(clientConnectionId);
154
- isNew = true;
171
+ // Known attendee is joining the session if they are currently disconnected
172
+ if (attendee.getConnectionStatus() === SessionClientStatus.Disconnected && isConnected) {
173
+ attendee.setConnected();
174
+ isJoining = true;
175
+ }
176
+ attendee.connectionId = clientConnectionId;
177
+ }
178
+ if (isConnected) {
179
+ // If the attendee is connected, remove them from the stale connection set
180
+ this.staleConnectionClients.delete(attendee);
155
181
  }
156
182
  // Always update entry for the connection ID. (Okay if already set.)
157
183
  this.attendees.set(clientConnectionId, attendee);
158
- return { attendee, isNew };
184
+ return { attendee, isJoining };
159
185
  }
160
186
  }
161
187
  /**