@fluidframework/presence 2.74.0 → 2.81.0-374083

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.
Files changed (73) hide show
  1. package/dist/alpha.d.ts +1 -0
  2. package/dist/beta.d.ts +1 -0
  3. package/dist/exposedInternalTypes.d.ts +1 -1
  4. package/dist/exposedInternalTypes.d.ts.map +1 -1
  5. package/dist/exposedInternalTypes.js.map +1 -1
  6. package/dist/getPresence.js +2 -2
  7. package/dist/getPresence.js.map +1 -1
  8. package/dist/index.d.ts +1 -1
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js.map +1 -1
  11. package/dist/internalTypes.d.ts +6 -0
  12. package/dist/internalTypes.d.ts.map +1 -1
  13. package/dist/internalTypes.js.map +1 -1
  14. package/dist/latestMapValueManager.d.ts +33 -14
  15. package/dist/latestMapValueManager.d.ts.map +1 -1
  16. package/dist/latestMapValueManager.js +33 -16
  17. package/dist/latestMapValueManager.js.map +1 -1
  18. package/dist/legacy.alpha.d.ts +1 -0
  19. package/dist/packageVersion.d.ts +1 -1
  20. package/dist/packageVersion.d.ts.map +1 -1
  21. package/dist/packageVersion.js +1 -1
  22. package/dist/packageVersion.js.map +1 -1
  23. package/dist/presenceDatastoreManager.d.ts +7 -11
  24. package/dist/presenceDatastoreManager.d.ts.map +1 -1
  25. package/dist/presenceDatastoreManager.js +15 -65
  26. package/dist/presenceDatastoreManager.js.map +1 -1
  27. package/dist/presenceManager.d.ts.map +1 -1
  28. package/dist/presenceManager.js +68 -23
  29. package/dist/presenceManager.js.map +1 -1
  30. package/dist/stateDatastore.d.ts +2 -2
  31. package/dist/stateDatastore.d.ts.map +1 -1
  32. package/dist/stateDatastore.js.map +1 -1
  33. package/dist/systemWorkspace.d.ts +2 -1
  34. package/dist/systemWorkspace.d.ts.map +1 -1
  35. package/dist/systemWorkspace.js +95 -42
  36. package/dist/systemWorkspace.js.map +1 -1
  37. package/lib/alpha.d.ts +1 -0
  38. package/lib/beta.d.ts +1 -0
  39. package/lib/exposedInternalTypes.d.ts +1 -1
  40. package/lib/exposedInternalTypes.d.ts.map +1 -1
  41. package/lib/exposedInternalTypes.js.map +1 -1
  42. package/lib/getPresence.js +2 -2
  43. package/lib/getPresence.js.map +1 -1
  44. package/lib/index.d.ts +1 -1
  45. package/lib/index.d.ts.map +1 -1
  46. package/lib/index.js.map +1 -1
  47. package/lib/internalTypes.d.ts +6 -0
  48. package/lib/internalTypes.d.ts.map +1 -1
  49. package/lib/internalTypes.js.map +1 -1
  50. package/lib/latestMapValueManager.d.ts +33 -14
  51. package/lib/latestMapValueManager.d.ts.map +1 -1
  52. package/lib/latestMapValueManager.js +33 -16
  53. package/lib/latestMapValueManager.js.map +1 -1
  54. package/lib/legacy.alpha.d.ts +1 -0
  55. package/lib/packageVersion.d.ts +1 -1
  56. package/lib/packageVersion.d.ts.map +1 -1
  57. package/lib/packageVersion.js +1 -1
  58. package/lib/packageVersion.js.map +1 -1
  59. package/lib/presenceDatastoreManager.d.ts +7 -11
  60. package/lib/presenceDatastoreManager.d.ts.map +1 -1
  61. package/lib/presenceDatastoreManager.js +15 -65
  62. package/lib/presenceDatastoreManager.js.map +1 -1
  63. package/lib/presenceManager.d.ts.map +1 -1
  64. package/lib/presenceManager.js +64 -19
  65. package/lib/presenceManager.js.map +1 -1
  66. package/lib/stateDatastore.d.ts +2 -2
  67. package/lib/stateDatastore.d.ts.map +1 -1
  68. package/lib/stateDatastore.js.map +1 -1
  69. package/lib/systemWorkspace.d.ts +2 -1
  70. package/lib/systemWorkspace.d.ts.map +1 -1
  71. package/lib/systemWorkspace.js +95 -42
  72. package/lib/systemWorkspace.js.map +1 -1
  73. package/package.json +26 -26
@@ -1 +1 @@
1
- {"version":3,"file":"presenceManager.js","sourceRoot":"","sources":["../src/presenceManager.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { createEmitter } from \"@fluid-internal/client-utils\";\nimport type {\n\tContainerExtension,\n\tInboundExtensionMessage,\n} from \"@fluidframework/container-runtime-definitions/internal\";\nimport type { IEmitter, Listenable } from \"@fluidframework/core-interfaces/internal\";\nimport { createSessionId } from \"@fluidframework/id-compressor/internal\";\nimport type {\n\tITelemetryLoggerExt,\n\tMonitoringContext,\n} from \"@fluidframework/telemetry-utils/internal\";\nimport { createChildMonitoringContext } from \"@fluidframework/telemetry-utils/internal\";\n\nimport type { ClientConnectionId } from \"./baseTypes.js\";\nimport type { BroadcastControlSettings } from \"./broadcastControls.js\";\nimport type { ExtensionRuntimeProperties, IEphemeralRuntime } from \"./internalTypes.js\";\nimport type {\n\tAttendeesEvents,\n\tAttendeeId,\n\tPresenceWithNotifications as Presence,\n\tPresenceEvents,\n} from \"./presence.js\";\nimport type { PresenceDatastoreManager } from \"./presenceDatastoreManager.js\";\nimport { PresenceDatastoreManagerImpl } from \"./presenceDatastoreManager.js\";\nimport type { SignalMessages } from \"./protocol.js\";\nimport type { SystemWorkspace, SystemWorkspaceDatastore } from \"./systemWorkspace.js\";\nimport { createSystemWorkspace } from \"./systemWorkspace.js\";\nimport type {\n\tNotificationsWorkspace,\n\tNotificationsWorkspaceSchema,\n\tStatesWorkspace,\n\tStatesWorkspaceSchema,\n\tWorkspaceAddress,\n} from \"./types.js\";\n\n/**\n * Portion of the container extension requirements ({@link ContainerExtension}) that are delegated to presence manager.\n */\nexport type PresenceExtensionInterface = Required<\n\tPick<ContainerExtension<ExtensionRuntimeProperties>, \"processSignal\">\n>;\n\n/**\n * The Presence manager\n */\nclass PresenceManager implements Presence, PresenceExtensionInterface {\n\tprivate readonly datastoreManager: PresenceDatastoreManager;\n\tprivate readonly systemWorkspace: SystemWorkspace;\n\n\tpublic readonly events = createEmitter<PresenceEvents & AttendeesEvents>();\n\n\tpublic readonly attendees: Presence[\"attendees\"];\n\n\tpublic readonly states = {\n\t\tgetWorkspace: <TSchema extends StatesWorkspaceSchema>(\n\t\t\tworkspaceAddress: WorkspaceAddress,\n\t\t\trequestedContent: TSchema,\n\t\t\tsettings?: BroadcastControlSettings,\n\t\t): StatesWorkspace<TSchema> =>\n\t\t\tthis.datastoreManager.getWorkspace(`s:${workspaceAddress}`, requestedContent, settings),\n\t};\n\tpublic readonly notifications = {\n\t\tgetWorkspace: <TSchema extends NotificationsWorkspaceSchema>(\n\t\t\tworkspaceAddress: WorkspaceAddress,\n\t\t\trequestedContent: TSchema,\n\t\t): NotificationsWorkspace<TSchema> =>\n\t\t\tthis.datastoreManager.getWorkspace(`n:${workspaceAddress}`, requestedContent),\n\t};\n\n\tprivate readonly mc: MonitoringContext | undefined = undefined;\n\n\tpublic constructor(runtime: IEphemeralRuntime, attendeeId: AttendeeId) {\n\t\tconst logger = runtime.logger;\n\t\tif (logger) {\n\t\t\tthis.mc = createChildMonitoringContext({ logger, namespace: \"Presence\" });\n\t\t\tthis.mc.logger.sendTelemetryEvent({ eventName: \"PresenceInstantiated\" });\n\t\t}\n\n\t\t[this.datastoreManager, this.systemWorkspace] = setupSubComponents(\n\t\t\tattendeeId,\n\t\t\truntime,\n\t\t\tthis.events,\n\t\t\tthis.mc?.logger,\n\t\t\tthis,\n\t\t);\n\t\tthis.attendees = this.systemWorkspace;\n\n\t\truntime.events.on(\"joined\", ({ clientId: joinedClientId }: { clientId: string }) => {\n\t\t\tthis.onJoin(joinedClientId);\n\t\t});\n\n\t\truntime.events.on(\"disconnected\", () => {\n\t\t\tconst currentClientId = runtime.getClientId();\n\t\t\tif (currentClientId !== undefined) {\n\t\t\t\tthis.removeClientConnectionId(currentClientId);\n\t\t\t}\n\t\t\tthis.datastoreManager.onDisconnected();\n\t\t});\n\n\t\truntime.getAudience().on(\"removeMember\", this.removeClientConnectionId.bind(this));\n\n\t\t// Check if already connected (can send signals) at the time of construction.\n\t\t// If constructed during data store load, the runtime may already be connected\n\t\t// and the \"joined\" event will be raised during completion. With construction\n\t\t// delayed we expect that \"joined\" event has passed.\n\t\t// Note: In some manual testing, this does not appear to be enough to\n\t\t// always trigger an initial connect.\n\t\tconst clientId = runtime.getClientId();\n\t\tif (clientId !== undefined && runtime.getJoinedStatus() !== \"disconnected\") {\n\t\t\tthis.onJoin(clientId);\n\t\t}\n\t}\n\n\tprivate onJoin(clientConnectionId: ClientConnectionId): void {\n\t\tthis.systemWorkspace.onConnectionAdded(clientConnectionId);\n\t\tthis.datastoreManager.joinSession(clientConnectionId);\n\t}\n\n\tprivate removeClientConnectionId(clientConnectionId: ClientConnectionId): void {\n\t\tthis.systemWorkspace.removeClientConnectionId(clientConnectionId);\n\t}\n\t/**\n\t * Check for Presence message and process it.\n\t *\n\t * @param addressChain - Address chain of the message\n\t * @param message - Unverified message to be processed\n\t * @param local - Whether the message originated locally (`true`) or remotely (`false`)\n\t */\n\tpublic processSignal(\n\t\taddressChain: string[],\n\t\tmessage: InboundExtensionMessage<SignalMessages>,\n\t\tlocal: boolean,\n\t): void {\n\t\tthis.datastoreManager.processSignal(\n\t\t\tmessage,\n\t\t\tlocal,\n\t\t\t/* optional */ addressChain[0] === \"?\",\n\t\t);\n\t}\n}\n\n/**\n * Helper for Presence Manager setup\n *\n * Presence Manager is outermost layer of the presence system and has two main\n * sub-components:\n * 1. PresenceDatastoreManager: Manages the unified general data for states and\n * registry for workspaces.\n * 2. SystemWorkspace: Custom internal workspace for system states including\n * attendee management. It is registered with the PresenceDatastoreManager.\n */\nfunction setupSubComponents(\n\tattendeeId: AttendeeId,\n\truntime: IEphemeralRuntime,\n\tevents: Listenable<PresenceEvents & AttendeesEvents> &\n\t\tIEmitter<PresenceEvents & AttendeesEvents>,\n\tlogger: ITelemetryLoggerExt | undefined,\n\tpresence: Presence,\n): [PresenceDatastoreManager, SystemWorkspace] {\n\tconst systemWorkspaceDatastore: SystemWorkspaceDatastore = {\n\t\tclientToSessionId: {},\n\t};\n\tconst systemWorkspaceConfig = createSystemWorkspace(\n\t\tattendeeId,\n\t\tsystemWorkspaceDatastore,\n\t\tevents,\n\t\truntime.getAudience(),\n\t);\n\tconst datastoreManager = new PresenceDatastoreManagerImpl(\n\t\tattendeeId,\n\t\truntime,\n\t\tlogger,\n\t\tevents,\n\t\tpresence,\n\t\tsystemWorkspaceDatastore,\n\t\tsystemWorkspaceConfig.statesEntry,\n\t);\n\treturn [datastoreManager, systemWorkspaceConfig.workspace];\n}\n\n/**\n * Instantiates Presence Manager\n */\nexport function createPresenceManager(\n\truntime: IEphemeralRuntime,\n\tattendeeId: AttendeeId = createSessionId() as AttendeeId,\n): Presence & PresenceExtensionInterface {\n\treturn new PresenceManager(runtime, attendeeId);\n}\n"]}
1
+ {"version":3,"file":"presenceManager.js","sourceRoot":"","sources":["../src/presenceManager.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { createEmitter } from \"@fluid-internal/client-utils\";\nimport type {\n\tContainerExtension,\n\tInboundExtensionMessage,\n} from \"@fluidframework/container-runtime-definitions/internal\";\nimport type { IEmitter, Listenable } from \"@fluidframework/core-interfaces/internal\";\nimport { assert } from \"@fluidframework/core-utils/internal\";\nimport { createSessionId } from \"@fluidframework/id-compressor/internal\";\nimport type {\n\tITelemetryLoggerExt,\n\tMonitoringContext,\n} from \"@fluidframework/telemetry-utils/internal\";\nimport { createChildMonitoringContext } from \"@fluidframework/telemetry-utils/internal\";\n\nimport type { ClientConnectionId } from \"./baseTypes.js\";\nimport type { BroadcastControlSettings } from \"./broadcastControls.js\";\nimport type { ExtensionRuntimeProperties, IEphemeralRuntime } from \"./internalTypes.js\";\nimport type {\n\tAttendeesEvents,\n\tAttendeeId,\n\tPresenceWithNotifications as Presence,\n\tPresenceEvents,\n} from \"./presence.js\";\nimport type { PresenceDatastoreManager } from \"./presenceDatastoreManager.js\";\nimport { PresenceDatastoreManagerImpl } from \"./presenceDatastoreManager.js\";\nimport type { SignalMessages } from \"./protocol.js\";\nimport type { SystemWorkspace, SystemWorkspaceDatastore } from \"./systemWorkspace.js\";\nimport { createSystemWorkspace } from \"./systemWorkspace.js\";\nimport type {\n\tNotificationsWorkspace,\n\tNotificationsWorkspaceSchema,\n\tStatesWorkspace,\n\tStatesWorkspaceSchema,\n\tWorkspaceAddress,\n} from \"./types.js\";\n\n/**\n * Portion of the container extension requirements ({@link ContainerExtension}) that are delegated to presence manager.\n */\nexport type PresenceExtensionInterface = Required<\n\tPick<ContainerExtension<ExtensionRuntimeProperties>, \"processSignal\">\n>;\n\n/**\n * Confirms that the message is from a client and has a clientId\n * versus from the system which specifies `null` for clientId.\n */\nfunction assertMessageIsFromAClient(\n\tmessage: InboundExtensionMessage<SignalMessages>,\n): asserts message is InboundExtensionMessage<SignalMessages> & {\n\tclientId: ClientConnectionId;\n} {\n\tassert(message.clientId !== null, 0xa3a /* Presence received signal without clientId */);\n}\n\n/**\n * The Presence manager\n */\nclass PresenceManager implements Presence, PresenceExtensionInterface {\n\tprivate joined = false;\n\tprivate readonly datastoreManager: PresenceDatastoreManager;\n\tprivate readonly systemWorkspace: SystemWorkspace;\n\n\tpublic readonly events = createEmitter<PresenceEvents & AttendeesEvents>();\n\n\tpublic readonly attendees: Presence[\"attendees\"];\n\n\tpublic readonly states = {\n\t\tgetWorkspace: <TSchema extends StatesWorkspaceSchema>(\n\t\t\tworkspaceAddress: WorkspaceAddress,\n\t\t\trequestedContent: TSchema,\n\t\t\tsettings?: BroadcastControlSettings,\n\t\t): StatesWorkspace<TSchema> =>\n\t\t\tthis.datastoreManager.getWorkspace(`s:${workspaceAddress}`, requestedContent, settings),\n\t};\n\tpublic readonly notifications = {\n\t\tgetWorkspace: <TSchema extends NotificationsWorkspaceSchema>(\n\t\t\tworkspaceAddress: WorkspaceAddress,\n\t\t\trequestedContent: TSchema,\n\t\t): NotificationsWorkspace<TSchema> =>\n\t\t\tthis.datastoreManager.getWorkspace(`n:${workspaceAddress}`, requestedContent),\n\t};\n\n\tprivate readonly mc: MonitoringContext | undefined = undefined;\n\n\tpublic constructor(\n\t\tprivate readonly runtime: IEphemeralRuntime,\n\t\tattendeeId: AttendeeId,\n\t) {\n\t\tconst logger = runtime.logger;\n\t\tif (logger) {\n\t\t\tthis.mc = createChildMonitoringContext({ logger, namespace: \"Presence\" });\n\t\t\tthis.mc.logger.sendTelemetryEvent({ eventName: \"PresenceInstantiated\" });\n\t\t}\n\n\t\t[this.datastoreManager, this.systemWorkspace] = setupSubComponents(\n\t\t\tattendeeId,\n\t\t\truntime,\n\t\t\tthis.events,\n\t\t\tthis.mc?.logger,\n\t\t\tthis,\n\t\t);\n\t\tthis.attendees = this.systemWorkspace;\n\n\t\truntime.events.on(\"disconnected\", this.onDisconnected.bind(this));\n\n\t\tconst audience = runtime.getAudience();\n\t\t// Listen for self add to Audience to indicate join (with a stable\n\t\t// audience population).\n\t\taudience.on(\"addMember\", this.addClientConnectionId.bind(this));\n\t\taudience.on(\"removeMember\", this.removeClientConnectionId.bind(this));\n\n\t\t// Check if already connected (can send signals and complete audience)\n\t\t// at the time of construction.\n\t\t// If constructed during data store load, the runtime may already be connected\n\t\t// and the self-\"addMember\" event will be raised during completion. With\n\t\t// construction delayed we expect that self-\"addMember\" event has passed.\n\t\t// Note: In some manual testing, this does not appear to be enough to\n\t\t// always trigger an initial connect.\n\t\tconst clientId = runtime.getClientId();\n\t\tif (\n\t\t\tclientId !== undefined &&\n\t\t\truntime.getJoinedStatus() !== \"disconnected\" &&\n\t\t\taudience.getMember(clientId) !== undefined\n\t\t) {\n\t\t\tthis.onJoin(clientId, /* alternateUpdateProvider */ undefined);\n\t\t}\n\t}\n\n\tprivate addClientConnectionId(clientConnectionId: ClientConnectionId): void {\n\t\t// Check specifically for self join that indicates stable audience\n\t\t// and is preferred trigger for presence join.\n\t\tif (clientConnectionId === this.runtime.getClientId()) {\n\t\t\tthis.onJoin(clientConnectionId, /* alternateUpdateProvider */ undefined);\n\t\t}\n\t}\n\n\tprivate removeClientConnectionId(clientConnectionId: ClientConnectionId): void {\n\t\tthis.systemWorkspace.removeClientConnectionId(clientConnectionId);\n\t}\n\n\tprivate onJoin(\n\t\tclientConnectionId: ClientConnectionId,\n\t\talternateUpdateProvider: ClientConnectionId | undefined,\n\t): void {\n\t\t// System workspace is notified even if already \"joined\", to handle the\n\t\t// audience -> attendee status updates.\n\t\tthis.systemWorkspace.onConnectionAdded(\n\t\t\tclientConnectionId,\n\t\t\t// audienceOutOfDate - out of date when onJoin is forced by receiving\n\t\t\t// a signal (which calls with alternateUpdateProvider defined).\n\t\t\talternateUpdateProvider !== undefined,\n\t\t);\n\t\tif (!this.joined) {\n\t\t\tthis.datastoreManager.joinSession(clientConnectionId, alternateUpdateProvider);\n\t\t\tthis.joined = true;\n\t\t}\n\t}\n\n\tprivate onDisconnected(): void {\n\t\tthis.joined = false;\n\t\tconst currentClientId = this.runtime.getClientId();\n\t\tif (currentClientId !== undefined) {\n\t\t\tthis.removeClientConnectionId(currentClientId);\n\t\t}\n\t\tthis.datastoreManager.onDisconnected();\n\t}\n\n\t/**\n\t * Check for Presence message and process it.\n\t *\n\t * @param addressChain - Address chain of the message\n\t * @param message - Unverified message to be processed\n\t * @param local - Whether the message originated locally (`true`) or remotely (`false`)\n\t */\n\tpublic processSignal(\n\t\taddressChain: string[],\n\t\tmessage: InboundExtensionMessage<SignalMessages>,\n\t\tlocal: boolean,\n\t): void {\n\t\tassertMessageIsFromAClient(message);\n\n\t\t// Check for undesired case of receiving a remote presence signal\n\t\t// without having been alerted to self audience join that is preferred\n\t\t// trigger for join. (Perhaps join signal was dropped.)\n\t\t// In practice it is commonly observed that local signals can be\n\t\t// returned ahead of audience join notification. So, it is reasonable\n\t\t// to expect that audience join notification may be delayed until after\n\t\t// other presence signals are received. One is enough to get things\n\t\t// rolling.\n\t\tif (!local && !this.joined) {\n\t\t\tconst selfClientId = this.runtime.getClientId();\n\t\t\tassert(selfClientId !== undefined, 0xcbf /* Received signal without clientId */);\n\t\t\tthis.onJoin(selfClientId, /* alternateUpdateProvider */ message.clientId);\n\t\t}\n\n\t\tthis.datastoreManager.processSignal(\n\t\t\tmessage,\n\t\t\tlocal,\n\t\t\t/* optional */ addressChain[0] === \"?\",\n\t\t);\n\t}\n}\n\n/**\n * Helper for Presence Manager setup\n *\n * Presence Manager is outermost layer of the presence system and has two main\n * sub-components:\n * 1. PresenceDatastoreManager: Manages the unified general data for states and\n * registry for workspaces.\n * 2. SystemWorkspace: Custom internal workspace for system states including\n * attendee management. It is registered with the PresenceDatastoreManager.\n */\nfunction setupSubComponents(\n\tattendeeId: AttendeeId,\n\truntime: IEphemeralRuntime,\n\tevents: Listenable<PresenceEvents & AttendeesEvents> &\n\t\tIEmitter<PresenceEvents & AttendeesEvents>,\n\tlogger: ITelemetryLoggerExt | undefined,\n\tpresence: Presence,\n): [PresenceDatastoreManager, SystemWorkspace] {\n\tconst systemWorkspaceDatastore: SystemWorkspaceDatastore = {\n\t\tclientToSessionId: {},\n\t};\n\tconst systemWorkspaceConfig = createSystemWorkspace(\n\t\tattendeeId,\n\t\tsystemWorkspaceDatastore,\n\t\tevents,\n\t\truntime.getAudience(),\n\t);\n\tconst datastoreManager = new PresenceDatastoreManagerImpl(\n\t\tattendeeId,\n\t\truntime,\n\t\tlogger,\n\t\tevents,\n\t\tpresence,\n\t\tsystemWorkspaceDatastore,\n\t\tsystemWorkspaceConfig.statesEntry,\n\t);\n\treturn [datastoreManager, systemWorkspaceConfig.workspace];\n}\n\n/**\n * Instantiates Presence Manager\n */\nexport function createPresenceManager(\n\truntime: IEphemeralRuntime,\n\tattendeeId: AttendeeId = createSessionId() as AttendeeId,\n): Presence & PresenceExtensionInterface {\n\treturn new PresenceManager(runtime, attendeeId);\n}\n"]}
@@ -25,9 +25,9 @@ export interface LocalStateUpdateOptions {
25
25
  * Contract for States Workspace to support State Manager access to
26
26
  * datastore and general internal presence knowledge.
27
27
  */
28
- export interface StateDatastore<TKey extends string, TUpdateValue extends InternalTypes.ValueDirectoryOrState<unknown>, TStoredValue extends ValidatableValueDirectoryOrState<unknown> = ValidatableValueStructure<TUpdateValue>> {
28
+ export interface StateDatastore<TKey extends string, TLocalUpdateValue extends InternalTypes.ValueDirectoryOrState<unknown>, TStoredValue extends ValidatableValueDirectoryOrState<unknown> = ValidatableValueStructure<TLocalUpdateValue>> {
29
29
  readonly presence: Presence;
30
- localUpdate(key: TKey, value: TUpdateValue & {
30
+ localUpdate(key: TKey, value: TLocalUpdateValue & {
31
31
  ignoreUnmonitored?: true;
32
32
  }, options: LocalStateUpdateOptions): void;
33
33
  update(key: TKey, attendeeId: AttendeeId, value: TStoredValue): void;
@@ -1 +1 @@
1
- {"version":3,"file":"stateDatastore.d.ts","sourceRoot":"","sources":["../src/stateDatastore.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,KAAK,EACX,YAAY,EACZ,gCAAgC,EAChC,yBAAyB,EACzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,UAAU,EAAE,yBAAyB,IAAI,QAAQ,EAAE,MAAM,eAAe,CAAC;AAcvF;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACvC;;;;OAIG;IACH,wBAAwB,EAAE,MAAM,GAAG,SAAS,CAAC;IAE7C;;OAEG;IACH,cAAc,CAAC,EAAE,kBAAkB,CAAC;CACpC;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc,CAC9B,IAAI,SAAS,MAAM,EACnB,YAAY,SAAS,aAAa,CAAC,qBAAqB,CAAC,OAAO,CAAC,EACjE,YAAY,SACX,gCAAgC,CAAC,OAAO,CAAC,GAAG,yBAAyB,CAAC,YAAY,CAAC;IAEpF,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,WAAW,CACV,GAAG,EAAE,IAAI,EACT,KAAK,EAAE,YAAY,GAAG;QACrB,iBAAiB,CAAC,EAAE,IAAI,CAAC;KACzB,EACD,OAAO,EAAE,uBAAuB,GAC9B,IAAI,CAAC;IACR,MAAM,CAAC,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE,YAAY,GAAG,IAAI,CAAC;IACrE,WAAW,CAAC,GAAG,EAAE,IAAI,GAAG;QACvB,IAAI,EAAE,UAAU,GAAG,SAAS,CAAC;QAC7B,MAAM,EAAE,YAAY,CAAC,YAAY,CAAC,CAAC;KACnC,CAAC;CACF;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAIlC,IAAI,SAAS,MAAM,EACnB,MAAM,SAAS,aAAa,CAAC,qBAAqB,CAAC,OAAO,CAAC,EAE3D,SAAS,EAAE,cAAc,CAAC,IAAI,EAAE,MAAM,CAAC,GACrC,aAAa,CAAC,oBAAoB,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAKtE;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAClC,IAAI,SAAS,MAAM,EACnB,MAAM,SAAS,aAAa,CAAC,qBAAqB,CAAC,GAAG,CAAC,EACtD,MAAM,EAAE,aAAa,CAAC,oBAAoB,CAAC,IAAI,EAAE,MAAM,CAAC,GAAG,cAAc,CAAC,IAAI,EAAE,MAAM,CAAC,CAExF"}
1
+ {"version":3,"file":"stateDatastore.d.ts","sourceRoot":"","sources":["../src/stateDatastore.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,KAAK,EACX,YAAY,EACZ,gCAAgC,EAChC,yBAAyB,EACzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,UAAU,EAAE,yBAAyB,IAAI,QAAQ,EAAE,MAAM,eAAe,CAAC;AAcvF;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACvC;;;;OAIG;IACH,wBAAwB,EAAE,MAAM,GAAG,SAAS,CAAC;IAE7C;;OAEG;IACH,cAAc,CAAC,EAAE,kBAAkB,CAAC;CACpC;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc,CAC9B,IAAI,SAAS,MAAM,EACnB,iBAAiB,SAAS,aAAa,CAAC,qBAAqB,CAAC,OAAO,CAAC,EACtE,YAAY,SACX,gCAAgC,CAAC,OAAO,CAAC,GAAG,yBAAyB,CAAC,iBAAiB,CAAC;IAEzF,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,WAAW,CACV,GAAG,EAAE,IAAI,EACT,KAAK,EAAE,iBAAiB,GAAG;QAC1B,iBAAiB,CAAC,EAAE,IAAI,CAAC;KACzB,EACD,OAAO,EAAE,uBAAuB,GAC9B,IAAI,CAAC;IACR,MAAM,CAAC,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE,YAAY,GAAG,IAAI,CAAC;IACrE,WAAW,CAAC,GAAG,EAAE,IAAI,GAAG;QACvB,IAAI,EAAE,UAAU,GAAG,SAAS,CAAC;QAC7B,MAAM,EAAE,YAAY,CAAC,YAAY,CAAC,CAAC;KACnC,CAAC;CACF;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAIlC,IAAI,SAAS,MAAM,EACnB,MAAM,SAAS,aAAa,CAAC,qBAAqB,CAAC,OAAO,CAAC,EAE3D,SAAS,EAAE,cAAc,CAAC,IAAI,EAAE,MAAM,CAAC,GACrC,aAAa,CAAC,oBAAoB,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAKtE;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAClC,IAAI,SAAS,MAAM,EACnB,MAAM,SAAS,aAAa,CAAC,qBAAqB,CAAC,GAAG,CAAC,EACtD,MAAM,EAAE,aAAa,CAAC,oBAAoB,CAAC,IAAI,EAAE,MAAM,CAAC,GAAG,cAAc,CAAC,IAAI,EAAE,MAAM,CAAC,CAExF"}
@@ -1 +1 @@
1
- {"version":3,"file":"stateDatastore.js","sourceRoot":"","sources":["../src/stateDatastore.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAiEH;;GAEG;AACH,MAAM,UAAU,mBAAmB,CAOlC,SAAuC;IAEvC,OAAO,SAGN,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,mBAAmB,CAGjC,MAAwD;IACzD,OAAO,MAAiD,CAAC;AAC1D,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport type { ClientConnectionId } from \"./baseTypes.js\";\nimport type { InternalTypes } from \"./exposedInternalTypes.js\";\nimport type {\n\tClientRecord,\n\tValidatableValueDirectoryOrState,\n\tValidatableValueStructure,\n} from \"./internalTypes.js\";\nimport type { AttendeeId, PresenceWithNotifications as Presence } from \"./presence.js\";\n\n// type StateDatastoreSchemaNode<\n// \tTValue extends InternalTypes.ValueDirectoryOrState<any> = InternalTypes.ValueDirectoryOrState<unknown>,\n// > = TValue extends InternalTypes.ValueDirectoryOrState<infer T> ? InternalTypes.ValueDirectoryOrState<T> : never;\n\n// export interface StateDatastoreSchema {\n// \t// This type is not precise. It may\n// \t// need to be replaced with StatesWorkspace schema pattern\n// \t// similar to what is commented out.\n// \t[key: string]: InternalTypes.ValueDirectoryOrState<unknown>;\n// \t// [key: string]: StateDatastoreSchemaNode;\n// }\n\n/**\n * Miscellaneous options for local state updates\n */\nexport interface LocalStateUpdateOptions {\n\t/**\n\t * When defined, this is the maximum time in milliseconds that this\n\t * update is allowed to be delayed before it must be sent to service.\n\t * When `undefined`, the callee may determine maximum delay.\n\t */\n\tallowableUpdateLatencyMs: number | undefined;\n\n\t/**\n\t * Special option allowed for unicast notifications.\n\t */\n\ttargetClientId?: ClientConnectionId;\n}\n\n/**\n * Contract for States Workspace to support State Manager access to\n * datastore and general internal presence knowledge.\n */\nexport interface StateDatastore<\n\tTKey extends string,\n\tTUpdateValue extends InternalTypes.ValueDirectoryOrState<unknown>,\n\tTStoredValue extends\n\t\tValidatableValueDirectoryOrState<unknown> = ValidatableValueStructure<TUpdateValue>,\n> {\n\treadonly presence: Presence;\n\tlocalUpdate(\n\t\tkey: TKey,\n\t\tvalue: TUpdateValue & {\n\t\t\tignoreUnmonitored?: true;\n\t\t},\n\t\toptions: LocalStateUpdateOptions,\n\t): void;\n\tupdate(key: TKey, attendeeId: AttendeeId, value: TStoredValue): void;\n\tknownValues(key: TKey): {\n\t\tself: AttendeeId | undefined;\n\t\tstates: ClientRecord<TStoredValue>;\n\t};\n}\n\n/**\n * Helper to get a handle from a datastore.\n */\nexport function handleFromDatastore<\n\t// Constraining TSchema would be great, but it seems nested types (at least with undefined) cause trouble.\n\t// TSchema as `unknown` still provides some type safety.\n\t// TSchema extends StateDatastoreSchema,\n\tTKey extends string /* & keyof TSchema */,\n\tTValue extends InternalTypes.ValueDirectoryOrState<unknown>,\n>(\n\tdatastore: StateDatastore<TKey, TValue>,\n): InternalTypes.StateDatastoreHandle<TKey, Exclude<TValue, undefined>> {\n\treturn datastore as unknown as InternalTypes.StateDatastoreHandle<\n\t\tTKey,\n\t\tExclude<TValue, undefined>\n\t>;\n}\n\n/**\n * Helper to get the datastore back from its handle.\n */\nexport function datastoreFromHandle<\n\tTKey extends string,\n\tTValue extends InternalTypes.ValueDirectoryOrState<any>,\n>(handle: InternalTypes.StateDatastoreHandle<TKey, TValue>): StateDatastore<TKey, TValue> {\n\treturn handle as unknown as StateDatastore<TKey, TValue>;\n}\n"]}
1
+ {"version":3,"file":"stateDatastore.js","sourceRoot":"","sources":["../src/stateDatastore.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAiEH;;GAEG;AACH,MAAM,UAAU,mBAAmB,CAOlC,SAAuC;IAEvC,OAAO,SAGN,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,mBAAmB,CAGjC,MAAwD;IACzD,OAAO,MAAiD,CAAC;AAC1D,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport type { ClientConnectionId } from \"./baseTypes.js\";\nimport type { InternalTypes } from \"./exposedInternalTypes.js\";\nimport type {\n\tClientRecord,\n\tValidatableValueDirectoryOrState,\n\tValidatableValueStructure,\n} from \"./internalTypes.js\";\nimport type { AttendeeId, PresenceWithNotifications as Presence } from \"./presence.js\";\n\n// type StateDatastoreSchemaNode<\n// \tTValue extends InternalTypes.ValueDirectoryOrState<any> = InternalTypes.ValueDirectoryOrState<unknown>,\n// > = TValue extends InternalTypes.ValueDirectoryOrState<infer T> ? InternalTypes.ValueDirectoryOrState<T> : never;\n\n// export interface StateDatastoreSchema {\n// \t// This type is not precise. It may\n// \t// need to be replaced with StatesWorkspace schema pattern\n// \t// similar to what is commented out.\n// \t[key: string]: InternalTypes.ValueDirectoryOrState<unknown>;\n// \t// [key: string]: StateDatastoreSchemaNode;\n// }\n\n/**\n * Miscellaneous options for local state updates\n */\nexport interface LocalStateUpdateOptions {\n\t/**\n\t * When defined, this is the maximum time in milliseconds that this\n\t * update is allowed to be delayed before it must be sent to service.\n\t * When `undefined`, the callee may determine maximum delay.\n\t */\n\tallowableUpdateLatencyMs: number | undefined;\n\n\t/**\n\t * Special option allowed for unicast notifications.\n\t */\n\ttargetClientId?: ClientConnectionId;\n}\n\n/**\n * Contract for States Workspace to support State Manager access to\n * datastore and general internal presence knowledge.\n */\nexport interface StateDatastore<\n\tTKey extends string,\n\tTLocalUpdateValue extends InternalTypes.ValueDirectoryOrState<unknown>,\n\tTStoredValue extends\n\t\tValidatableValueDirectoryOrState<unknown> = ValidatableValueStructure<TLocalUpdateValue>,\n> {\n\treadonly presence: Presence;\n\tlocalUpdate(\n\t\tkey: TKey,\n\t\tvalue: TLocalUpdateValue & {\n\t\t\tignoreUnmonitored?: true;\n\t\t},\n\t\toptions: LocalStateUpdateOptions,\n\t): void;\n\tupdate(key: TKey, attendeeId: AttendeeId, value: TStoredValue): void;\n\tknownValues(key: TKey): {\n\t\tself: AttendeeId | undefined;\n\t\tstates: ClientRecord<TStoredValue>;\n\t};\n}\n\n/**\n * Helper to get a handle from a datastore.\n */\nexport function handleFromDatastore<\n\t// Constraining TSchema would be great, but it seems nested types (at least with undefined) cause trouble.\n\t// TSchema as `unknown` still provides some type safety.\n\t// TSchema extends StateDatastoreSchema,\n\tTKey extends string /* & keyof TSchema */,\n\tTValue extends InternalTypes.ValueDirectoryOrState<unknown>,\n>(\n\tdatastore: StateDatastore<TKey, TValue>,\n): InternalTypes.StateDatastoreHandle<TKey, Exclude<TValue, undefined>> {\n\treturn datastore as unknown as InternalTypes.StateDatastoreHandle<\n\t\tTKey,\n\t\tExclude<TValue, undefined>\n\t>;\n}\n\n/**\n * Helper to get the datastore back from its handle.\n */\nexport function datastoreFromHandle<\n\tTKey extends string,\n\tTValue extends InternalTypes.ValueDirectoryOrState<any>,\n>(handle: InternalTypes.StateDatastoreHandle<TKey, TValue>): StateDatastore<TKey, TValue> {\n\treturn handle as unknown as StateDatastore<TKey, TValue>;\n}\n"]}
@@ -34,8 +34,9 @@ export interface SystemWorkspace extends Exclude<Presence["attendees"], never> {
34
34
  * Must be called when the current client acquires a new connection.
35
35
  *
36
36
  * @param clientConnectionId - The new client connection ID.
37
+ * @param audienceOutOfDate - When true, audience cannot be used as authoritative.
37
38
  */
38
- onConnectionAdded(clientConnectionId: ClientConnectionId): void;
39
+ onConnectionAdded(clientConnectionId: ClientConnectionId, audienceOutOfDate: boolean): void;
39
40
  /**
40
41
  * Removes the client connection ID from the system workspace.
41
42
  *
@@ -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,UAAU,EAAE,MAAM,0CAA0C,CAAC;AAGrF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAG/D,OAAO,KAAK,EAAY,eAAe,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAErF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAElE,OAAO,KAAK,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAEtE;;;;;GAKG;AACH,UAAU,oBAAqB,SAAQ,aAAa,CAAC,kBAAkB;IACtE,KAAK,EAAE,UAAU,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACxC,iBAAiB,EAAE;QAClB,CAAC,YAAY,EAAE,kBAAkB,GAAG,oBAAoB,CAAC;KACzD,CAAC;CACF;AAoCD;;GAEG;AACH,MAAM,WAAW,eAGhB,SAAQ,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,KAAK,CAAC;IAC7C;;;;OAIG;IACH,iBAAiB,CAAC,kBAAkB,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAEhE;;;;OAIG;IACH,wBAAwB,CAAC,kBAAkB,EAAE,kBAAkB,GAAG,IAAI,CAAC;CACvE;AAqND;;GAEG;AACH,wBAAgB,qBAAqB,CACpC,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,wBAAwB,EACnC,MAAM,EAAE,UAAU,CAAC,eAAe,CAAC,GAAG,QAAQ,CAAC,eAAe,CAAC,EAC/D,QAAQ,EAAE,SAAS,GACjB;IACF,SAAS,EAAE,eAAe,CAAC;IAC3B,WAAW,EAAE;QACZ,QAAQ,EAAE,sBAAsB,CAAC;QACjC,MAAM,EAAE,YAAY,CAAC,qBAAqB,CAAC,CAAC;KAC5C,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,UAAU,EAAE,MAAM,0CAA0C,CAAC;AAGrF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAG/D,OAAO,KAAK,EAAY,eAAe,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAErF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAElE,OAAO,KAAK,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAEtE;;;;;GAKG;AACH,UAAU,oBAAqB,SAAQ,aAAa,CAAC,kBAAkB;IACtE,KAAK,EAAE,UAAU,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACxC,iBAAiB,EAAE;QAClB,CAAC,YAAY,EAAE,kBAAkB,GAAG,oBAAoB,CAAC;KACzD,CAAC;CACF;AAmCD;;GAEG;AACH,MAAM,WAAW,eAGhB,SAAQ,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,KAAK,CAAC;IAC7C;;;;;OAKG;IACH,iBAAiB,CAAC,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,OAAO,GAAG,IAAI,CAAC;IAE5F;;;;OAIG;IACH,wBAAwB,CAAC,kBAAkB,EAAE,kBAAkB,GAAG,IAAI,CAAC;CACvE;AAuRD;;GAEG;AACH,wBAAgB,qBAAqB,CACpC,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,wBAAwB,EACnC,MAAM,EAAE,UAAU,CAAC,eAAe,CAAC,GAAG,QAAQ,CAAC,eAAe,CAAC,EAC/D,QAAQ,EAAE,SAAS,GACjB;IACF,SAAS,EAAE,eAAe,CAAC;IAC3B,WAAW,EAAE;QACZ,QAAQ,EAAE,sBAAsB,CAAC;QACjC,MAAM,EAAE,YAAY,CAAC,qBAAqB,CAAC,CAAC;KAC5C,CAAC;CACF,CASA"}
@@ -7,14 +7,15 @@ import { revealOpaqueJson } from "./internalUtils.js";
7
7
  import { AttendeeStatus } from "./presence.js";
8
8
  import { TimerManager } from "./timerManager.js";
9
9
  class SessionClient {
10
- constructor(attendeeId, connectionId = undefined) {
10
+ constructor(attendeeId,
11
+ /**
12
+ * Order is used to track the most recent client connection
13
+ * during a session.
14
+ */
15
+ order = 0, connectionId = undefined) {
11
16
  this.attendeeId = attendeeId;
17
+ this.order = order;
12
18
  this.connectionId = connectionId;
13
- /**
14
- * Order is used to track the most recent client connection
15
- * during a session.
16
- */
17
- this.order = 0;
18
19
  this.connectionStatus = AttendeeStatus.Disconnected;
19
20
  }
20
21
  getConnectionId() {
@@ -72,12 +73,13 @@ class SystemWorkspaceImpl {
72
73
  const postUpdateActions = [];
73
74
  for (const [clientConnectionId, value] of Object.entries(revealOpaqueJson(remoteDatastore.clientToSessionId))) {
74
75
  const attendeeId = value.value;
75
- const { attendee, isJoining } = this.ensureAttendee(attendeeId, clientConnectionId,
76
- /* order */ value.rev,
77
- // If the attendee is present in audience OR if the attendee update is from the sending remote client itself,
78
- // then the attendee is considered connected.
79
- /* isConnected */ senderConnectionId === clientConnectionId ||
80
- audienceMembers.has(clientConnectionId));
76
+ const { attendee, isJoining } = this.ensureAttendee({
77
+ attendeeId,
78
+ clientConnectionId,
79
+ order: value.rev,
80
+ isSender: senderConnectionId === clientConnectionId,
81
+ isInAudience: audienceMembers.has(clientConnectionId),
82
+ });
81
83
  // If the attendee is joining the session, add them to the list of joining attendees to be announced later.
82
84
  if (isJoining) {
83
85
  postUpdateActions.push(() => this.events.emit("attendeeConnected", attendee));
@@ -92,32 +94,57 @@ class SystemWorkspaceImpl {
92
94
  }
93
95
  return postUpdateActions;
94
96
  }
95
- onConnectionAdded(clientConnectionId) {
97
+ onConnectionAdded(clientConnectionId, audienceOutOfDate) {
96
98
  assert(this.selfAttendee.getConnectionStatus() === AttendeeStatus.Disconnected, 0xaad /* Local client should be 'Disconnected' before adding new connection. */);
97
- this.datastore.clientToSessionId[clientConnectionId] = {
98
- rev: this.selfAttendee.order++,
99
- timestamp: Date.now(),
100
- value: this.selfAttendee.attendeeId,
101
- };
102
- // Mark 'Connected' remote attendees connections as stale
103
- for (const staleConnectionClient of this.attendees.values()) {
104
- if (staleConnectionClient.getConnectionStatus() === AttendeeStatus.Connected) {
105
- this.staleConnectionClients.add(staleConnectionClient);
106
- }
99
+ const selfInAudience = this.audience.getMember(clientConnectionId) !== undefined;
100
+ assert(selfInAudience || audienceOutOfDate, 0xcc0 /* Local client must be in audience for presence to handle added connection. */);
101
+ if (!(clientConnectionId in this.datastore.clientToSessionId)) {
102
+ this.datastore.clientToSessionId[clientConnectionId] = {
103
+ rev: this.selfAttendee.order++,
104
+ timestamp: Date.now(),
105
+ value: this.selfAttendee.attendeeId,
106
+ };
107
107
  }
108
- // Update the self attendee
108
+ // Update the self attendee connection information, but not connection
109
+ // status yet. Connection status is updated once self is in audience -
110
+ // see later. It is only once our connection is known to audience that
111
+ // audience can be used to track other attendees' connection statuses
112
+ // and we seek to present a consistent view locally.
109
113
  this.selfAttendee.connectionId = clientConnectionId;
110
- this.selfAttendee.setConnected();
111
114
  this.attendees.set(clientConnectionId, this.selfAttendee);
112
- this.staleConnectionTimer.setTimeout(() => {
113
- for (const client of this.staleConnectionClients) {
114
- client.setDisconnected();
115
+ if (selfInAudience) {
116
+ // Mark 'Connected' remote attendees connections as stale
117
+ // Performance note: This will visit attendees multiple times as the
118
+ // attendee map has attendeeIds and connectionIds entries that point to
119
+ // the same attendee. But the getConnectionStatus check is cheap and
120
+ // staleConnectionClients.add will handle duplicates.
121
+ this.staleConnectionClients.clear();
122
+ for (const staleConnectionClient of this.attendees.values()) {
123
+ if (staleConnectionClient.getConnectionStatus() === AttendeeStatus.Connected) {
124
+ this.staleConnectionClients.add(staleConnectionClient);
125
+ }
115
126
  }
116
- for (const client of this.staleConnectionClients) {
117
- this.events.emit("attendeeDisconnected", client);
127
+ this.staleConnectionTimer.setTimeout(this.resolveStaleConnections.bind(this), 30_000);
128
+ this.selfAttendee.setConnected();
129
+ // TODO: AB#56686: self-Attendee never announced as Connected - Emit this event once there are tests in place
130
+ // this.events.emit("attendeeConnected", this.selfAttendee);
131
+ }
132
+ }
133
+ resolveStaleConnections() {
134
+ const consideredDisconnected = [];
135
+ for (const client of this.staleConnectionClients) {
136
+ // Confirm that audience no longer has connection. It is possible
137
+ // but unlikely that no one mentioned the attendee in this period
138
+ // and that they were never disconnected.
139
+ if (this.audience.getMember(client.getConnectionId()) === undefined) {
140
+ consideredDisconnected.push(client);
141
+ client.setDisconnected();
118
142
  }
119
- this.staleConnectionClients.clear();
120
- }, 30_000);
143
+ }
144
+ for (const client of consideredDisconnected) {
145
+ this.events.emit("attendeeDisconnected", client);
146
+ }
147
+ this.staleConnectionClients.clear();
121
148
  }
122
149
  removeClientConnectionId(clientConnectionId) {
123
150
  const attendee = this.attendees.get(clientConnectionId);
@@ -128,6 +155,14 @@ class SystemWorkspaceImpl {
128
155
  if (attendee === this.selfAttendee) {
129
156
  this.staleConnectionTimer.clearTimeout();
130
157
  }
158
+ else {
159
+ // When self is not connected, audience may go through a refresh that
160
+ // removes members and adds them back. Defer any removals until self
161
+ // is connected implying audience is stable.
162
+ if (this.selfAttendee.getConnectionStatus() !== AttendeeStatus.Connected) {
163
+ return;
164
+ }
165
+ }
131
166
  // If the last known connectionID is different from the connection ID being removed, the attendee has reconnected,
132
167
  // therefore we should not change the attendee connection status or emit a disconnect event.
133
168
  const attendeeReconnected = attendee.getConnectionId() !== clientConnectionId;
@@ -160,29 +195,47 @@ class SystemWorkspaceImpl {
160
195
  * in the attendee map. If not present, SessionClient is created and added
161
196
  * to map. If present, make sure the current connection ID is updated.
162
197
  */
163
- ensureAttendee(attendeeId, clientConnectionId, order, isConnected) {
198
+ ensureAttendee({ attendeeId, clientConnectionId, order, isSender, isInAudience, }) {
164
199
  let attendee = this.attendees.get(attendeeId);
200
+ let isConnected = false;
165
201
  let isJoining = false;
166
202
  if (attendee === undefined) {
167
203
  // New attendee. Create SessionClient and add session ID based
168
204
  // entry to map.
169
- attendee = new SessionClient(attendeeId, clientConnectionId);
205
+ attendee = new SessionClient(attendeeId, order, clientConnectionId);
170
206
  this.attendees.set(attendeeId, attendee);
171
- if (isConnected) {
207
+ // If the attendee update is from the sending remote client itself
208
+ // OR if the attendee is present in audience,
209
+ // then the attendee is considered connected. (Otherwise, leave
210
+ // state as disconnected - default.)
211
+ if (isSender || isInAudience) {
212
+ isConnected = true;
172
213
  attendee.setConnected();
173
214
  isJoining = true;
174
215
  }
175
216
  }
176
- else if (order > attendee.order) {
177
- // The given association is newer than the one we have.
178
- // Update the order and current connection ID.
179
- attendee.order = order;
180
- // Known attendee is joining the session if they are currently disconnected
181
- if (attendee.getConnectionStatus() === AttendeeStatus.Disconnected && isConnected) {
217
+ else {
218
+ // Known attendee is considered connected if
219
+ isConnected =
220
+ // this information is at least up to date with current knowledge
221
+ order >= attendee.order &&
222
+ // AND in the audience OR
223
+ (isInAudience ||
224
+ // not in audience, but client is the sender and has newer
225
+ // info. (Assume that audience is out of date and attendee
226
+ // is joining.)
227
+ (isSender && order > attendee.order));
228
+ if (order > attendee.order) {
229
+ // The given association is newer than the one we have.
230
+ // Update the order and current connection ID.
231
+ attendee.order = order;
232
+ attendee.connectionId = clientConnectionId;
233
+ }
234
+ // Known attendee is joining the session if they are currently disconnected.
235
+ if (isConnected && attendee.getConnectionStatus() === AttendeeStatus.Disconnected) {
182
236
  attendee.setConnected();
183
237
  isJoining = true;
184
238
  }
185
- attendee.connectionId = clientConnectionId;
186
239
  }
187
240
  if (isConnected) {
188
241
  // If the attendee is connected, remove them from the stale connection set
@@ -1 +1 @@
1
- {"version":3,"file":"systemWorkspace.js","sourceRoot":"","sources":["../src/systemWorkspace.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 type { IAudience } from \"@fluidframework/container-definitions\";\nimport type { IEmitter, Listenable } from \"@fluidframework/core-interfaces/internal\";\nimport { assert } from \"@fluidframework/core-utils/internal\";\n\nimport type { ClientConnectionId } from \"./baseTypes.js\";\nimport type { InternalTypes } from \"./exposedInternalTypes.js\";\nimport type { PostUpdateAction } from \"./internalTypes.js\";\nimport { revealOpaqueJson } from \"./internalUtils.js\";\nimport type { Attendee, AttendeesEvents, AttendeeId, Presence } from \"./presence.js\";\nimport { AttendeeStatus } from \"./presence.js\";\nimport type { PresenceStatesInternal } from \"./presenceStates.js\";\nimport { TimerManager } from \"./timerManager.js\";\nimport type { AnyWorkspace, StatesWorkspaceSchema } from \"./types.js\";\n\n/**\n * `ConnectionValueState` is known value state for `clientToSessionId` data.\n *\n * @remarks\n * It is {@link InternalTypes.ValueRequiredState} with a known value type.\n */\ninterface ConnectionValueState extends InternalTypes.ValueStateMetadata {\n\tvalue: AttendeeId;\n}\n\n/**\n * The system workspace's datastore structure.\n */\nexport interface SystemWorkspaceDatastore {\n\tclientToSessionId: {\n\t\t[ConnectionId: ClientConnectionId]: ConnectionValueState;\n\t};\n}\n\nclass SessionClient implements Attendee {\n\t/**\n\t * Order is used to track the most recent client connection\n\t * during a session.\n\t */\n\tpublic order: number = 0;\n\n\tprivate connectionStatus: AttendeeStatus = AttendeeStatus.Disconnected;\n\n\tpublic constructor(\n\t\tpublic readonly attendeeId: AttendeeId,\n\t\tpublic connectionId: ClientConnectionId | undefined = undefined,\n\t) {}\n\n\tpublic getConnectionId(): ClientConnectionId {\n\t\tif (this.connectionId === undefined) {\n\t\t\tthrow new Error(\"Client has never been connected\");\n\t\t}\n\t\treturn this.connectionId;\n\t}\n\n\tpublic getConnectionStatus(): AttendeeStatus {\n\t\treturn this.connectionStatus;\n\t}\n\n\tpublic setConnected(): void {\n\t\tthis.connectionStatus = AttendeeStatus.Connected;\n\t}\n\n\tpublic setDisconnected(): void {\n\t\tthis.connectionStatus = AttendeeStatus.Disconnected;\n\t}\n}\n\n/**\n * Internal workspace that manages metadata for session attendees.\n */\nexport interface SystemWorkspace\n\t// Portion of Presence that is handled by SystemWorkspace along with\n\t// responsibility for emitting \"attendeeConnected\" events.\n\textends Exclude<Presence[\"attendees\"], never> {\n\t/**\n\t * Must be called when the current client acquires a new connection.\n\t *\n\t * @param clientConnectionId - The new client connection ID.\n\t */\n\tonConnectionAdded(clientConnectionId: ClientConnectionId): void;\n\n\t/**\n\t * Removes the client connection ID from the system workspace.\n\t *\n\t * @param clientConnectionId - The client connection ID to remove.\n\t */\n\tremoveClientConnectionId(clientConnectionId: ClientConnectionId): void;\n}\n\nclass SystemWorkspaceImpl implements PresenceStatesInternal, SystemWorkspace {\n\tprivate readonly selfAttendee: SessionClient;\n\t/**\n\t * `attendees` is this client's understanding of the attendees in the\n\t * session. The map covers entries for both session ids and connection\n\t * ids, which are never expected to collide, but if they did for same\n\t * client that would be fine.\n\t * An entry is for session ID if the value's `attendeeId` matches the key.\n\t */\n\tprivate readonly attendees = new Map<ClientConnectionId | AttendeeId, SessionClient>();\n\n\t// When local client disconnects, we lose the connectivity status updates for remote attendees in the session.\n\t// Upon reconnect, we mark all other attendees connections as stale and update their status to disconnected after 30 seconds of inactivity.\n\tprivate readonly staleConnectionClients = new Set<SessionClient>();\n\n\tprivate readonly staleConnectionTimer = new TimerManager();\n\n\tpublic constructor(\n\t\tattendeeId: AttendeeId,\n\t\tprivate readonly datastore: SystemWorkspaceDatastore,\n\t\tpublic readonly events: Listenable<AttendeesEvents> & IEmitter<AttendeesEvents>,\n\t\tprivate readonly audience: IAudience,\n\t) {\n\t\tthis.selfAttendee = new SessionClient(attendeeId);\n\t\tthis.attendees.set(attendeeId, this.selfAttendee);\n\t}\n\n\tpublic ensureContent<TSchemaAdditional extends StatesWorkspaceSchema>(\n\t\t_content: TSchemaAdditional,\n\t): never {\n\t\tthrow new Error(\"Method not implemented.\");\n\t}\n\n\tpublic processUpdate(\n\t\t_received: number,\n\t\t_timeModifier: number,\n\t\t/**\n\t\t * Remote datastore typed to match {@link PresenceStatesInternal.processUpdate}'s\n\t\t * `ValueUpdateRecord` type that uses {@link InternalTypes.ValueRequiredState}\n\t\t * and expects an Opaque JSON type. (We get away with a non-`unknown` value type\n\t\t * per TypeScript's method parameter bivariance.) Proper type would be\n\t\t * {@link ConnectionValueState} directly.\n\t\t * {@link ClientConnectionId} use for index is also a deviation, but conveniently\n\t\t * the accurate {@link AttendeeId} type is just a branded string, and\n\t\t * {@link ClientConnectionId} is just `string`.\n\t\t */\n\t\tremoteDatastore: {\n\t\t\tclientToSessionId: {\n\t\t\t\t[ConnectionId: ClientConnectionId]: InternalTypes.ValueRequiredState<\n\t\t\t\t\tConnectionValueState[\"value\"]\n\t\t\t\t>;\n\t\t\t};\n\t\t},\n\t\tsenderConnectionId: ClientConnectionId,\n\t): PostUpdateAction[] {\n\t\tconst audienceMembers = this.audience.getMembers();\n\t\tconst postUpdateActions: PostUpdateAction[] = [];\n\t\tfor (const [clientConnectionId, value] of Object.entries(\n\t\t\trevealOpaqueJson(remoteDatastore.clientToSessionId),\n\t\t)) {\n\t\t\tconst attendeeId = value.value;\n\t\t\tconst { attendee, isJoining } = this.ensureAttendee(\n\t\t\t\tattendeeId,\n\t\t\t\tclientConnectionId,\n\t\t\t\t/* order */ value.rev,\n\t\t\t\t// If the attendee is present in audience OR if the attendee update is from the sending remote client itself,\n\t\t\t\t// then the attendee is considered connected.\n\t\t\t\t/* isConnected */ senderConnectionId === clientConnectionId ||\n\t\t\t\t\taudienceMembers.has(clientConnectionId),\n\t\t\t);\n\t\t\t// If the attendee is joining the session, add them to the list of joining attendees to be announced later.\n\t\t\tif (isJoining) {\n\t\t\t\tpostUpdateActions.push(() => this.events.emit(\"attendeeConnected\", attendee));\n\t\t\t}\n\n\t\t\tconst knownSessionId = this.datastore.clientToSessionId[clientConnectionId];\n\t\t\tif (knownSessionId === undefined) {\n\t\t\t\tthis.datastore.clientToSessionId[clientConnectionId] = value;\n\t\t\t} else {\n\t\t\t\tassert(knownSessionId.value === value.value, 0xa5a /* Mismatched SessionId */);\n\t\t\t}\n\t\t}\n\n\t\treturn postUpdateActions;\n\t}\n\n\tpublic onConnectionAdded(clientConnectionId: ClientConnectionId): void {\n\t\tassert(\n\t\t\tthis.selfAttendee.getConnectionStatus() === AttendeeStatus.Disconnected,\n\t\t\t0xaad /* Local client should be 'Disconnected' before adding new connection. */,\n\t\t);\n\n\t\tthis.datastore.clientToSessionId[clientConnectionId] = {\n\t\t\trev: this.selfAttendee.order++,\n\t\t\ttimestamp: Date.now(),\n\t\t\tvalue: this.selfAttendee.attendeeId,\n\t\t};\n\n\t\t// Mark 'Connected' remote attendees connections as stale\n\t\tfor (const staleConnectionClient of this.attendees.values()) {\n\t\t\tif (staleConnectionClient.getConnectionStatus() === AttendeeStatus.Connected) {\n\t\t\t\tthis.staleConnectionClients.add(staleConnectionClient);\n\t\t\t}\n\t\t}\n\n\t\t// Update the self attendee\n\t\tthis.selfAttendee.connectionId = clientConnectionId;\n\t\tthis.selfAttendee.setConnected();\n\t\tthis.attendees.set(clientConnectionId, this.selfAttendee);\n\n\t\tthis.staleConnectionTimer.setTimeout(() => {\n\t\t\tfor (const client of this.staleConnectionClients) {\n\t\t\t\tclient.setDisconnected();\n\t\t\t}\n\t\t\tfor (const client of this.staleConnectionClients) {\n\t\t\t\tthis.events.emit(\"attendeeDisconnected\", client);\n\t\t\t}\n\t\t\tthis.staleConnectionClients.clear();\n\t\t}, 30_000);\n\t}\n\n\tpublic removeClientConnectionId(clientConnectionId: ClientConnectionId): void {\n\t\tconst attendee = this.attendees.get(clientConnectionId);\n\t\tif (!attendee) {\n\t\t\treturn;\n\t\t}\n\n\t\t// If the local connection is being removed, clear the stale connection timer\n\t\tif (attendee === this.selfAttendee) {\n\t\t\tthis.staleConnectionTimer.clearTimeout();\n\t\t}\n\n\t\t// If the last known connectionID is different from the connection ID being removed, the attendee has reconnected,\n\t\t// therefore we should not change the attendee connection status or emit a disconnect event.\n\t\tconst attendeeReconnected = attendee.getConnectionId() !== clientConnectionId;\n\t\tconst connected = attendee.getConnectionStatus() === AttendeeStatus.Connected;\n\t\tif (!attendeeReconnected && connected) {\n\t\t\tattendee.setDisconnected();\n\t\t\tthis.events.emit(\"attendeeDisconnected\", attendee);\n\t\t\tthis.staleConnectionClients.delete(attendee);\n\t\t}\n\t}\n\n\tpublic getAttendees(): ReadonlySet<Attendee> {\n\t\treturn new Set(this.attendees.values());\n\t}\n\n\tpublic getAttendee(clientId: ClientConnectionId | AttendeeId): Attendee {\n\t\tconst attendee = this.attendees.get(clientId);\n\t\tif (attendee) {\n\t\t\treturn attendee;\n\t\t}\n\n\t\t// TODO: Restore option to add attendee on demand to handle internal\n\t\t// lookup cases that must come from internal data.\n\t\t// There aren't any resiliency mechanisms in place to handle a missed\n\t\t// ClientJoin right now.\n\t\tthrow new Error(\"Attendee not found\");\n\t}\n\n\tpublic getMyself(): Attendee {\n\t\treturn this.selfAttendee;\n\t}\n\n\t/**\n\t * Make sure the given client session and connection ID pair are represented\n\t * in the attendee map. If not present, SessionClient is created and added\n\t * to map. If present, make sure the current connection ID is updated.\n\t */\n\tprivate ensureAttendee(\n\t\tattendeeId: AttendeeId,\n\t\tclientConnectionId: ClientConnectionId,\n\t\torder: number,\n\t\tisConnected: boolean,\n\t): { attendee: SessionClient; isJoining: boolean } {\n\t\tlet attendee = this.attendees.get(attendeeId);\n\t\tlet isJoining = false;\n\n\t\tif (attendee === undefined) {\n\t\t\t// New attendee. Create SessionClient and add session ID based\n\t\t\t// entry to map.\n\t\t\tattendee = new SessionClient(attendeeId, clientConnectionId);\n\t\t\tthis.attendees.set(attendeeId, attendee);\n\t\t\tif (isConnected) {\n\t\t\t\tattendee.setConnected();\n\t\t\t\tisJoining = true;\n\t\t\t}\n\t\t} else if (order > attendee.order) {\n\t\t\t// The given association is newer than the one we have.\n\t\t\t// Update the order and current connection ID.\n\t\t\tattendee.order = order;\n\t\t\t// Known attendee is joining the session if they are currently disconnected\n\t\t\tif (attendee.getConnectionStatus() === AttendeeStatus.Disconnected && isConnected) {\n\t\t\t\tattendee.setConnected();\n\t\t\t\tisJoining = true;\n\t\t\t}\n\t\t\tattendee.connectionId = clientConnectionId;\n\t\t}\n\n\t\tif (isConnected) {\n\t\t\t// If the attendee is connected, remove them from the stale connection set\n\t\t\tthis.staleConnectionClients.delete(attendee);\n\t\t}\n\n\t\t// Always update entry for the connection ID. (Okay if already set.)\n\t\tthis.attendees.set(clientConnectionId, attendee);\n\n\t\treturn { attendee, isJoining };\n\t}\n}\n\n/**\n * Instantiates the system workspace.\n */\nexport function createSystemWorkspace(\n\tattendeeId: AttendeeId,\n\tdatastore: SystemWorkspaceDatastore,\n\tevents: Listenable<AttendeesEvents> & IEmitter<AttendeesEvents>,\n\taudience: IAudience,\n): {\n\tworkspace: SystemWorkspace;\n\tstatesEntry: {\n\t\tinternal: PresenceStatesInternal;\n\t\tpublic: AnyWorkspace<StatesWorkspaceSchema>;\n\t};\n} {\n\tconst workspace = new SystemWorkspaceImpl(attendeeId, datastore, events, audience);\n\treturn {\n\t\tworkspace,\n\t\tstatesEntry: {\n\t\t\tinternal: workspace,\n\t\t\tpublic: undefined as unknown as AnyWorkspace<StatesWorkspaceSchema>,\n\t\t},\n\t};\n}\n"]}
1
+ {"version":3,"file":"systemWorkspace.js","sourceRoot":"","sources":["../src/systemWorkspace.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 type { IAudience } from \"@fluidframework/container-definitions\";\nimport type { IEmitter, Listenable } from \"@fluidframework/core-interfaces/internal\";\nimport { assert } from \"@fluidframework/core-utils/internal\";\n\nimport type { ClientConnectionId } from \"./baseTypes.js\";\nimport type { InternalTypes } from \"./exposedInternalTypes.js\";\nimport type { PostUpdateAction } from \"./internalTypes.js\";\nimport { revealOpaqueJson } from \"./internalUtils.js\";\nimport type { Attendee, AttendeesEvents, AttendeeId, Presence } from \"./presence.js\";\nimport { AttendeeStatus } from \"./presence.js\";\nimport type { PresenceStatesInternal } from \"./presenceStates.js\";\nimport { TimerManager } from \"./timerManager.js\";\nimport type { AnyWorkspace, StatesWorkspaceSchema } from \"./types.js\";\n\n/**\n * `ConnectionValueState` is known value state for `clientToSessionId` data.\n *\n * @remarks\n * It is {@link InternalTypes.ValueRequiredState} with a known value type.\n */\ninterface ConnectionValueState extends InternalTypes.ValueStateMetadata {\n\tvalue: AttendeeId;\n}\n\n/**\n * The system workspace's datastore structure.\n */\nexport interface SystemWorkspaceDatastore {\n\tclientToSessionId: {\n\t\t[ConnectionId: ClientConnectionId]: ConnectionValueState;\n\t};\n}\n\nclass SessionClient implements Attendee {\n\tprivate connectionStatus: AttendeeStatus = AttendeeStatus.Disconnected;\n\n\tpublic constructor(\n\t\tpublic readonly attendeeId: AttendeeId,\n\t\t/**\n\t\t * Order is used to track the most recent client connection\n\t\t * during a session.\n\t\t */\n\t\tpublic order: number = 0,\n\t\tpublic connectionId: ClientConnectionId | undefined = undefined,\n\t) {}\n\n\tpublic getConnectionId(): ClientConnectionId {\n\t\tif (this.connectionId === undefined) {\n\t\t\tthrow new Error(\"Client has never been connected\");\n\t\t}\n\t\treturn this.connectionId;\n\t}\n\n\tpublic getConnectionStatus(): AttendeeStatus {\n\t\treturn this.connectionStatus;\n\t}\n\n\tpublic setConnected(): void {\n\t\tthis.connectionStatus = AttendeeStatus.Connected;\n\t}\n\n\tpublic setDisconnected(): void {\n\t\tthis.connectionStatus = AttendeeStatus.Disconnected;\n\t}\n}\n\n/**\n * Internal workspace that manages metadata for session attendees.\n */\nexport interface SystemWorkspace\n\t// Portion of Presence that is handled by SystemWorkspace along with\n\t// responsibility for emitting \"attendeeConnected\" events.\n\textends Exclude<Presence[\"attendees\"], never> {\n\t/**\n\t * Must be called when the current client acquires a new connection.\n\t *\n\t * @param clientConnectionId - The new client connection ID.\n\t * @param audienceOutOfDate - When true, audience cannot be used as authoritative.\n\t */\n\tonConnectionAdded(clientConnectionId: ClientConnectionId, audienceOutOfDate: boolean): void;\n\n\t/**\n\t * Removes the client connection ID from the system workspace.\n\t *\n\t * @param clientConnectionId - The client connection ID to remove.\n\t */\n\tremoveClientConnectionId(clientConnectionId: ClientConnectionId): void;\n}\n\nclass SystemWorkspaceImpl implements PresenceStatesInternal, SystemWorkspace {\n\tprivate readonly selfAttendee: SessionClient;\n\t/**\n\t * `attendees` is this client's understanding of the attendees in the\n\t * session. The map covers entries for both session ids and connection\n\t * ids, which are never expected to collide, but if they did for same\n\t * client that would be fine.\n\t * An entry is for session ID if the value's `attendeeId` matches the key.\n\t */\n\tprivate readonly attendees = new Map<ClientConnectionId | AttendeeId, SessionClient>();\n\n\t// When local client disconnects, we lose the connectivity status updates for remote attendees in the session.\n\t// Upon reconnect, we mark all other attendees connections as stale and update their status to disconnected after 30 seconds of inactivity.\n\tprivate readonly staleConnectionClients = new Set<SessionClient>();\n\n\tprivate readonly staleConnectionTimer = new TimerManager();\n\n\tpublic constructor(\n\t\tattendeeId: AttendeeId,\n\t\tprivate readonly datastore: SystemWorkspaceDatastore,\n\t\tpublic readonly events: Listenable<AttendeesEvents> & IEmitter<AttendeesEvents>,\n\t\tprivate readonly audience: IAudience,\n\t) {\n\t\tthis.selfAttendee = new SessionClient(attendeeId);\n\t\tthis.attendees.set(attendeeId, this.selfAttendee);\n\t}\n\n\tpublic ensureContent<TSchemaAdditional extends StatesWorkspaceSchema>(\n\t\t_content: TSchemaAdditional,\n\t): never {\n\t\tthrow new Error(\"Method not implemented.\");\n\t}\n\n\tpublic processUpdate(\n\t\t_received: number,\n\t\t_timeModifier: number,\n\t\t/**\n\t\t * Remote datastore typed to match {@link PresenceStatesInternal.processUpdate}'s\n\t\t * `ValueUpdateRecord` type that uses {@link InternalTypes.ValueRequiredState}\n\t\t * and expects an Opaque JSON type. (We get away with a non-`unknown` value type\n\t\t * per TypeScript's method parameter bivariance.) Proper type would be\n\t\t * {@link ConnectionValueState} directly.\n\t\t * {@link ClientConnectionId} use for index is also a deviation, but conveniently\n\t\t * the accurate {@link AttendeeId} type is just a branded string, and\n\t\t * {@link ClientConnectionId} is just `string`.\n\t\t */\n\t\tremoteDatastore: {\n\t\t\tclientToSessionId: {\n\t\t\t\t[ConnectionId: ClientConnectionId]: InternalTypes.ValueRequiredState<\n\t\t\t\t\tConnectionValueState[\"value\"]\n\t\t\t\t>;\n\t\t\t};\n\t\t},\n\t\tsenderConnectionId: ClientConnectionId,\n\t): PostUpdateAction[] {\n\t\tconst audienceMembers = this.audience.getMembers();\n\t\tconst postUpdateActions: PostUpdateAction[] = [];\n\t\tfor (const [clientConnectionId, value] of Object.entries(\n\t\t\trevealOpaqueJson(remoteDatastore.clientToSessionId),\n\t\t)) {\n\t\t\tconst attendeeId = value.value;\n\t\t\tconst { attendee, isJoining } = this.ensureAttendee({\n\t\t\t\tattendeeId,\n\t\t\t\tclientConnectionId,\n\t\t\t\torder: value.rev,\n\t\t\t\tisSender: senderConnectionId === clientConnectionId,\n\t\t\t\tisInAudience: audienceMembers.has(clientConnectionId),\n\t\t\t});\n\t\t\t// If the attendee is joining the session, add them to the list of joining attendees to be announced later.\n\t\t\tif (isJoining) {\n\t\t\t\tpostUpdateActions.push(() => this.events.emit(\"attendeeConnected\", attendee));\n\t\t\t}\n\n\t\t\tconst knownSessionId = this.datastore.clientToSessionId[clientConnectionId];\n\t\t\tif (knownSessionId === undefined) {\n\t\t\t\tthis.datastore.clientToSessionId[clientConnectionId] = value;\n\t\t\t} else {\n\t\t\t\tassert(knownSessionId.value === value.value, 0xa5a /* Mismatched SessionId */);\n\t\t\t}\n\t\t}\n\n\t\treturn postUpdateActions;\n\t}\n\n\tpublic onConnectionAdded(\n\t\tclientConnectionId: ClientConnectionId,\n\t\taudienceOutOfDate: boolean,\n\t): void {\n\t\tassert(\n\t\t\tthis.selfAttendee.getConnectionStatus() === AttendeeStatus.Disconnected,\n\t\t\t0xaad /* Local client should be 'Disconnected' before adding new connection. */,\n\t\t);\n\n\t\tconst selfInAudience = this.audience.getMember(clientConnectionId) !== undefined;\n\t\tassert(\n\t\t\tselfInAudience || audienceOutOfDate,\n\t\t\t0xcc0 /* Local client must be in audience for presence to handle added connection. */,\n\t\t);\n\n\t\tif (!(clientConnectionId in this.datastore.clientToSessionId)) {\n\t\t\tthis.datastore.clientToSessionId[clientConnectionId] = {\n\t\t\t\trev: this.selfAttendee.order++,\n\t\t\t\ttimestamp: Date.now(),\n\t\t\t\tvalue: this.selfAttendee.attendeeId,\n\t\t\t};\n\t\t}\n\n\t\t// Update the self attendee connection information, but not connection\n\t\t// status yet. Connection status is updated once self is in audience -\n\t\t// see later. It is only once our connection is known to audience that\n\t\t// audience can be used to track other attendees' connection statuses\n\t\t// and we seek to present a consistent view locally.\n\t\tthis.selfAttendee.connectionId = clientConnectionId;\n\t\tthis.attendees.set(clientConnectionId, this.selfAttendee);\n\n\t\tif (selfInAudience) {\n\t\t\t// Mark 'Connected' remote attendees connections as stale\n\t\t\t// Performance note: This will visit attendees multiple times as the\n\t\t\t// attendee map has attendeeIds and connectionIds entries that point to\n\t\t\t// the same attendee. But the getConnectionStatus check is cheap and\n\t\t\t// staleConnectionClients.add will handle duplicates.\n\t\t\tthis.staleConnectionClients.clear();\n\t\t\tfor (const staleConnectionClient of this.attendees.values()) {\n\t\t\t\tif (staleConnectionClient.getConnectionStatus() === AttendeeStatus.Connected) {\n\t\t\t\t\tthis.staleConnectionClients.add(staleConnectionClient);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.staleConnectionTimer.setTimeout(this.resolveStaleConnections.bind(this), 30_000);\n\n\t\t\tthis.selfAttendee.setConnected();\n\t\t\t// TODO: AB#56686: self-Attendee never announced as Connected - Emit this event once there are tests in place\n\t\t\t// this.events.emit(\"attendeeConnected\", this.selfAttendee);\n\t\t}\n\t}\n\n\tprivate resolveStaleConnections(): void {\n\t\tconst consideredDisconnected = [];\n\t\tfor (const client of this.staleConnectionClients) {\n\t\t\t// Confirm that audience no longer has connection. It is possible\n\t\t\t// but unlikely that no one mentioned the attendee in this period\n\t\t\t// and that they were never disconnected.\n\t\t\tif (this.audience.getMember(client.getConnectionId()) === undefined) {\n\t\t\t\tconsideredDisconnected.push(client);\n\t\t\t\tclient.setDisconnected();\n\t\t\t}\n\t\t}\n\t\tfor (const client of consideredDisconnected) {\n\t\t\tthis.events.emit(\"attendeeDisconnected\", client);\n\t\t}\n\t\tthis.staleConnectionClients.clear();\n\t}\n\n\tpublic removeClientConnectionId(clientConnectionId: ClientConnectionId): void {\n\t\tconst attendee = this.attendees.get(clientConnectionId);\n\t\tif (!attendee) {\n\t\t\treturn;\n\t\t}\n\n\t\t// If the local connection is being removed, clear the stale connection timer\n\t\tif (attendee === this.selfAttendee) {\n\t\t\tthis.staleConnectionTimer.clearTimeout();\n\t\t} else {\n\t\t\t// When self is not connected, audience may go through a refresh that\n\t\t\t// removes members and adds them back. Defer any removals until self\n\t\t\t// is connected implying audience is stable.\n\t\t\tif (this.selfAttendee.getConnectionStatus() !== AttendeeStatus.Connected) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// If the last known connectionID is different from the connection ID being removed, the attendee has reconnected,\n\t\t// therefore we should not change the attendee connection status or emit a disconnect event.\n\t\tconst attendeeReconnected = attendee.getConnectionId() !== clientConnectionId;\n\t\tconst connected = attendee.getConnectionStatus() === AttendeeStatus.Connected;\n\t\tif (!attendeeReconnected && connected) {\n\t\t\tattendee.setDisconnected();\n\t\t\tthis.events.emit(\"attendeeDisconnected\", attendee);\n\t\t\tthis.staleConnectionClients.delete(attendee);\n\t\t}\n\t}\n\n\tpublic getAttendees(): ReadonlySet<Attendee> {\n\t\treturn new Set(this.attendees.values());\n\t}\n\n\tpublic getAttendee(clientId: ClientConnectionId | AttendeeId): Attendee {\n\t\tconst attendee = this.attendees.get(clientId);\n\t\tif (attendee) {\n\t\t\treturn attendee;\n\t\t}\n\n\t\t// TODO: Restore option to add attendee on demand to handle internal\n\t\t// lookup cases that must come from internal data.\n\t\t// There aren't any resiliency mechanisms in place to handle a missed\n\t\t// ClientJoin right now.\n\t\tthrow new Error(\"Attendee not found\");\n\t}\n\n\tpublic getMyself(): Attendee {\n\t\treturn this.selfAttendee;\n\t}\n\n\t/**\n\t * Make sure the given client session and connection ID pair are represented\n\t * in the attendee map. If not present, SessionClient is created and added\n\t * to map. If present, make sure the current connection ID is updated.\n\t */\n\tprivate ensureAttendee({\n\t\tattendeeId,\n\t\tclientConnectionId,\n\t\torder,\n\t\tisSender,\n\t\tisInAudience,\n\t}: {\n\t\tattendeeId: AttendeeId;\n\t\tclientConnectionId: ClientConnectionId;\n\t\torder: number;\n\t\tisSender: boolean;\n\t\tisInAudience: boolean;\n\t}): { attendee: SessionClient; isJoining: boolean } {\n\t\tlet attendee = this.attendees.get(attendeeId);\n\t\tlet isConnected = false;\n\t\tlet isJoining = false;\n\n\t\tif (attendee === undefined) {\n\t\t\t// New attendee. Create SessionClient and add session ID based\n\t\t\t// entry to map.\n\t\t\tattendee = new SessionClient(attendeeId, order, clientConnectionId);\n\t\t\tthis.attendees.set(attendeeId, attendee);\n\t\t\t// If the attendee update is from the sending remote client itself\n\t\t\t// OR if the attendee is present in audience,\n\t\t\t// then the attendee is considered connected. (Otherwise, leave\n\t\t\t// state as disconnected - default.)\n\t\t\tif (isSender || isInAudience) {\n\t\t\t\tisConnected = true;\n\t\t\t\tattendee.setConnected();\n\t\t\t\tisJoining = true;\n\t\t\t}\n\t\t} else {\n\t\t\t// Known attendee is considered connected if\n\t\t\tisConnected =\n\t\t\t\t// this information is at least up to date with current knowledge\n\t\t\t\torder >= attendee.order &&\n\t\t\t\t// AND in the audience OR\n\t\t\t\t(isInAudience ||\n\t\t\t\t\t// not in audience, but client is the sender and has newer\n\t\t\t\t\t// info. (Assume that audience is out of date and attendee\n\t\t\t\t\t// is joining.)\n\t\t\t\t\t(isSender && order > attendee.order));\n\n\t\t\tif (order > attendee.order) {\n\t\t\t\t// The given association is newer than the one we have.\n\t\t\t\t// Update the order and current connection ID.\n\t\t\t\tattendee.order = order;\n\t\t\t\tattendee.connectionId = clientConnectionId;\n\t\t\t}\n\n\t\t\t// Known attendee is joining the session if they are currently disconnected.\n\t\t\tif (isConnected && attendee.getConnectionStatus() === AttendeeStatus.Disconnected) {\n\t\t\t\tattendee.setConnected();\n\t\t\t\tisJoining = true;\n\t\t\t}\n\t\t}\n\n\t\tif (isConnected) {\n\t\t\t// If the attendee is connected, remove them from the stale connection set\n\t\t\tthis.staleConnectionClients.delete(attendee);\n\t\t}\n\n\t\t// Always update entry for the connection ID. (Okay if already set.)\n\t\tthis.attendees.set(clientConnectionId, attendee);\n\n\t\treturn { attendee, isJoining };\n\t}\n}\n\n/**\n * Instantiates the system workspace.\n */\nexport function createSystemWorkspace(\n\tattendeeId: AttendeeId,\n\tdatastore: SystemWorkspaceDatastore,\n\tevents: Listenable<AttendeesEvents> & IEmitter<AttendeesEvents>,\n\taudience: IAudience,\n): {\n\tworkspace: SystemWorkspace;\n\tstatesEntry: {\n\t\tinternal: PresenceStatesInternal;\n\t\tpublic: AnyWorkspace<StatesWorkspaceSchema>;\n\t};\n} {\n\tconst workspace = new SystemWorkspaceImpl(attendeeId, datastore, events, audience);\n\treturn {\n\t\tworkspace,\n\t\tstatesEntry: {\n\t\t\tinternal: workspace,\n\t\t\tpublic: undefined as unknown as AnyWorkspace<StatesWorkspaceSchema>,\n\t\t},\n\t};\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluidframework/presence",
3
- "version": "2.74.0",
3
+ "version": "2.81.0-374083",
4
4
  "description": "A component for lightweight data sharing within a single session",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": {
@@ -73,42 +73,40 @@
73
73
  "temp-directory": "nyc/.nyc_output"
74
74
  },
75
75
  "dependencies": {
76
- "@fluid-internal/client-utils": "~2.74.0",
77
- "@fluidframework/container-definitions": "~2.74.0",
78
- "@fluidframework/container-runtime-definitions": "~2.74.0",
79
- "@fluidframework/core-interfaces": "~2.74.0",
80
- "@fluidframework/core-utils": "~2.74.0",
81
- "@fluidframework/datastore": "~2.74.0",
82
- "@fluidframework/datastore-definitions": "~2.74.0",
83
- "@fluidframework/fluid-static": "~2.74.0",
84
- "@fluidframework/id-compressor": "~2.74.0",
85
- "@fluidframework/runtime-definitions": "~2.74.0",
86
- "@fluidframework/telemetry-utils": "~2.74.0"
76
+ "@fluid-internal/client-utils": "2.81.0-374083",
77
+ "@fluidframework/container-definitions": "2.81.0-374083",
78
+ "@fluidframework/container-runtime-definitions": "2.81.0-374083",
79
+ "@fluidframework/core-interfaces": "2.81.0-374083",
80
+ "@fluidframework/core-utils": "2.81.0-374083",
81
+ "@fluidframework/fluid-static": "2.81.0-374083",
82
+ "@fluidframework/id-compressor": "2.81.0-374083",
83
+ "@fluidframework/runtime-definitions": "2.81.0-374083",
84
+ "@fluidframework/telemetry-utils": "2.81.0-374083"
87
85
  },
88
86
  "devDependencies": {
89
- "@arethetypeswrong/cli": "^0.17.1",
87
+ "@arethetypeswrong/cli": "^0.18.2",
90
88
  "@biomejs/biome": "~1.9.3",
91
- "@fluid-internal/mocha-test-setup": "~2.74.0",
92
- "@fluid-tools/build-cli": "^0.61.0",
89
+ "@fluid-internal/mocha-test-setup": "2.81.0-374083",
90
+ "@fluid-tools/build-cli": "^0.63.0",
93
91
  "@fluidframework/build-common": "^2.0.3",
94
- "@fluidframework/build-tools": "^0.61.0",
95
- "@fluidframework/driver-definitions": "~2.74.0",
96
- "@fluidframework/eslint-config-fluid": "~2.74.0",
97
- "@fluidframework/test-runtime-utils": "~2.74.0",
98
- "@fluidframework/test-utils": "~2.74.0",
92
+ "@fluidframework/build-tools": "^0.63.0",
93
+ "@fluidframework/driver-definitions": "2.81.0-374083",
94
+ "@fluidframework/eslint-config-fluid": "2.81.0-374083",
95
+ "@fluidframework/test-runtime-utils": "2.81.0-374083",
96
+ "@fluidframework/test-utils": "2.81.0-374083",
99
97
  "@microsoft/api-extractor": "7.52.11",
100
98
  "@types/mocha": "^10.0.10",
101
99
  "@types/node": "^18.19.0",
102
100
  "@types/sinon": "^17.0.3",
103
101
  "c8": "^10.1.3",
104
- "concurrently": "^8.2.1",
102
+ "concurrently": "^9.2.1",
105
103
  "copyfiles": "^2.4.1",
106
- "cross-env": "^7.0.3",
107
- "eslint": "~8.57.1",
104
+ "cross-env": "^10.1.0",
105
+ "eslint": "~9.39.1",
108
106
  "jiti": "^2.6.1",
109
107
  "mocha": "^10.8.2",
110
108
  "mocha-multi-reporters": "^1.5.1",
111
- "rimraf": "^4.4.0",
109
+ "rimraf": "^6.1.2",
112
110
  "sinon": "^18.0.1",
113
111
  "typescript": "~5.4.5"
114
112
  },
@@ -119,7 +117,8 @@
119
117
  ],
120
118
  "build:esnext:main": [
121
119
  "^api",
122
- "^build:esnext"
120
+ "^build:esnext",
121
+ "build:genver"
123
122
  ],
124
123
  "check:exports:bundle-release-tags": [
125
124
  "build:esnext"
@@ -133,7 +132,8 @@
133
132
  ],
134
133
  "tsc:main": [
135
134
  "^api",
136
- "^tsc"
135
+ "^tsc",
136
+ "build:genver"
137
137
  ]
138
138
  }
139
139
  },