@fluidframework/presence 2.10.0-307399

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 (255) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +216 -0
  3. package/dist/alpha.d.ts +58 -0
  4. package/dist/baseTypes.d.ts +24 -0
  5. package/dist/baseTypes.d.ts.map +1 -0
  6. package/dist/baseTypes.js +7 -0
  7. package/dist/baseTypes.js.map +1 -0
  8. package/dist/container-definitions/containerExtensions.d.ts +137 -0
  9. package/dist/container-definitions/containerExtensions.d.ts.map +1 -0
  10. package/dist/container-definitions/containerExtensions.js +7 -0
  11. package/dist/container-definitions/containerExtensions.js.map +1 -0
  12. package/dist/container-definitions/index.d.ts +7 -0
  13. package/dist/container-definitions/index.d.ts.map +1 -0
  14. package/dist/container-definitions/index.js +7 -0
  15. package/dist/container-definitions/index.js.map +1 -0
  16. package/dist/container-definitions/runtime.d.ts +12 -0
  17. package/dist/container-definitions/runtime.d.ts.map +1 -0
  18. package/dist/container-definitions/runtime.js +7 -0
  19. package/dist/container-definitions/runtime.js.map +1 -0
  20. package/dist/core-interfaces/exposedUtilityTypes.d.ts +446 -0
  21. package/dist/core-interfaces/exposedUtilityTypes.d.ts.map +1 -0
  22. package/dist/core-interfaces/exposedUtilityTypes.js +11 -0
  23. package/dist/core-interfaces/exposedUtilityTypes.js.map +1 -0
  24. package/dist/core-interfaces/index.d.ts +10 -0
  25. package/dist/core-interfaces/index.d.ts.map +1 -0
  26. package/dist/core-interfaces/index.js +7 -0
  27. package/dist/core-interfaces/index.js.map +1 -0
  28. package/dist/core-interfaces/jsonDeserialized.d.ts +109 -0
  29. package/dist/core-interfaces/jsonDeserialized.d.ts.map +1 -0
  30. package/dist/core-interfaces/jsonDeserialized.js +7 -0
  31. package/dist/core-interfaces/jsonDeserialized.js.map +1 -0
  32. package/dist/core-interfaces/jsonSerializable.d.ts +120 -0
  33. package/dist/core-interfaces/jsonSerializable.d.ts.map +1 -0
  34. package/dist/core-interfaces/jsonSerializable.js +7 -0
  35. package/dist/core-interfaces/jsonSerializable.js.map +1 -0
  36. package/dist/core-interfaces/jsonSerializationErrors.d.ts +31 -0
  37. package/dist/core-interfaces/jsonSerializationErrors.d.ts.map +1 -0
  38. package/dist/core-interfaces/jsonSerializationErrors.js +7 -0
  39. package/dist/core-interfaces/jsonSerializationErrors.js.map +1 -0
  40. package/dist/core-interfaces/jsonType.d.ts +29 -0
  41. package/dist/core-interfaces/jsonType.d.ts.map +1 -0
  42. package/dist/core-interfaces/jsonType.js +7 -0
  43. package/dist/core-interfaces/jsonType.js.map +1 -0
  44. package/dist/datastorePresenceManagerFactory.d.ts +48 -0
  45. package/dist/datastorePresenceManagerFactory.d.ts.map +1 -0
  46. package/dist/datastorePresenceManagerFactory.js +79 -0
  47. package/dist/datastorePresenceManagerFactory.js.map +1 -0
  48. package/dist/datastoreSupport.d.ts +31 -0
  49. package/dist/datastoreSupport.d.ts.map +1 -0
  50. package/dist/datastoreSupport.js +82 -0
  51. package/dist/datastoreSupport.js.map +1 -0
  52. package/dist/events/events.d.ts +198 -0
  53. package/dist/events/events.d.ts.map +1 -0
  54. package/dist/events/events.js +157 -0
  55. package/dist/events/events.js.map +1 -0
  56. package/dist/experimentalAccess.d.ts +15 -0
  57. package/dist/experimentalAccess.d.ts.map +1 -0
  58. package/dist/experimentalAccess.js +46 -0
  59. package/dist/experimentalAccess.js.map +1 -0
  60. package/dist/exposedInternalTypes.d.ts +100 -0
  61. package/dist/exposedInternalTypes.d.ts.map +1 -0
  62. package/dist/exposedInternalTypes.js +19 -0
  63. package/dist/exposedInternalTypes.js.map +1 -0
  64. package/dist/exposedUtilityTypes.d.ts +63 -0
  65. package/dist/exposedUtilityTypes.d.ts.map +1 -0
  66. package/dist/exposedUtilityTypes.js +7 -0
  67. package/dist/exposedUtilityTypes.js.map +1 -0
  68. package/dist/index.d.ts +22 -0
  69. package/dist/index.d.ts.map +1 -0
  70. package/dist/index.js +21 -0
  71. package/dist/index.js.map +1 -0
  72. package/dist/internalTypes.d.ts +39 -0
  73. package/dist/internalTypes.d.ts.map +1 -0
  74. package/dist/internalTypes.js +14 -0
  75. package/dist/internalTypes.js.map +1 -0
  76. package/dist/latestMapValueManager.d.ts +182 -0
  77. package/dist/latestMapValueManager.d.ts.map +1 -0
  78. package/dist/latestMapValueManager.js +206 -0
  79. package/dist/latestMapValueManager.js.map +1 -0
  80. package/dist/latestValueControls.d.ts +44 -0
  81. package/dist/latestValueControls.d.ts.map +1 -0
  82. package/dist/latestValueControls.js +28 -0
  83. package/dist/latestValueControls.js.map +1 -0
  84. package/dist/latestValueManager.d.ts +69 -0
  85. package/dist/latestValueManager.d.ts.map +1 -0
  86. package/dist/latestValueManager.js +100 -0
  87. package/dist/latestValueManager.js.map +1 -0
  88. package/dist/latestValueTypes.d.ts +44 -0
  89. package/dist/latestValueTypes.d.ts.map +1 -0
  90. package/dist/latestValueTypes.js +7 -0
  91. package/dist/latestValueTypes.js.map +1 -0
  92. package/dist/notificationsManager.d.ts +101 -0
  93. package/dist/notificationsManager.d.ts.map +1 -0
  94. package/dist/notificationsManager.js +82 -0
  95. package/dist/notificationsManager.js.map +1 -0
  96. package/dist/package.json +15 -0
  97. package/dist/presence.d.ts +180 -0
  98. package/dist/presence.d.ts.map +1 -0
  99. package/dist/presence.js +23 -0
  100. package/dist/presence.js.map +1 -0
  101. package/dist/presenceDatastoreManager.d.ts +91 -0
  102. package/dist/presenceDatastoreManager.d.ts.map +1 -0
  103. package/dist/presenceDatastoreManager.js +248 -0
  104. package/dist/presenceDatastoreManager.js.map +1 -0
  105. package/dist/presenceManager.d.ts +20 -0
  106. package/dist/presenceManager.d.ts.map +1 -0
  107. package/dist/presenceManager.js +104 -0
  108. package/dist/presenceManager.js.map +1 -0
  109. package/dist/presenceStates.d.ts +78 -0
  110. package/dist/presenceStates.d.ts.map +1 -0
  111. package/dist/presenceStates.js +182 -0
  112. package/dist/presenceStates.js.map +1 -0
  113. package/dist/stateDatastore.d.ts +35 -0
  114. package/dist/stateDatastore.d.ts.map +1 -0
  115. package/dist/stateDatastore.js +26 -0
  116. package/dist/stateDatastore.js.map +1 -0
  117. package/dist/systemWorkspace.d.ts +51 -0
  118. package/dist/systemWorkspace.d.ts.map +1 -0
  119. package/dist/systemWorkspace.js +180 -0
  120. package/dist/systemWorkspace.js.map +1 -0
  121. package/dist/types.d.ts +92 -0
  122. package/dist/types.d.ts.map +1 -0
  123. package/dist/types.js +8 -0
  124. package/dist/types.js.map +1 -0
  125. package/dist/valueManager.d.ts +19 -0
  126. package/dist/valueManager.d.ts.map +1 -0
  127. package/dist/valueManager.js +26 -0
  128. package/dist/valueManager.js.map +1 -0
  129. package/lib/alpha.d.ts +58 -0
  130. package/lib/baseTypes.d.ts +24 -0
  131. package/lib/baseTypes.d.ts.map +1 -0
  132. package/lib/baseTypes.js +6 -0
  133. package/lib/baseTypes.js.map +1 -0
  134. package/lib/container-definitions/containerExtensions.d.ts +137 -0
  135. package/lib/container-definitions/containerExtensions.d.ts.map +1 -0
  136. package/lib/container-definitions/containerExtensions.js +6 -0
  137. package/lib/container-definitions/containerExtensions.js.map +1 -0
  138. package/lib/container-definitions/index.d.ts +7 -0
  139. package/lib/container-definitions/index.d.ts.map +1 -0
  140. package/lib/container-definitions/index.js +6 -0
  141. package/lib/container-definitions/index.js.map +1 -0
  142. package/lib/container-definitions/runtime.d.ts +12 -0
  143. package/lib/container-definitions/runtime.d.ts.map +1 -0
  144. package/lib/container-definitions/runtime.js +6 -0
  145. package/lib/container-definitions/runtime.js.map +1 -0
  146. package/lib/core-interfaces/exposedUtilityTypes.d.ts +446 -0
  147. package/lib/core-interfaces/exposedUtilityTypes.d.ts.map +1 -0
  148. package/lib/core-interfaces/exposedUtilityTypes.js +10 -0
  149. package/lib/core-interfaces/exposedUtilityTypes.js.map +1 -0
  150. package/lib/core-interfaces/index.d.ts +10 -0
  151. package/lib/core-interfaces/index.d.ts.map +1 -0
  152. package/lib/core-interfaces/index.js +6 -0
  153. package/lib/core-interfaces/index.js.map +1 -0
  154. package/lib/core-interfaces/jsonDeserialized.d.ts +109 -0
  155. package/lib/core-interfaces/jsonDeserialized.d.ts.map +1 -0
  156. package/lib/core-interfaces/jsonDeserialized.js +6 -0
  157. package/lib/core-interfaces/jsonDeserialized.js.map +1 -0
  158. package/lib/core-interfaces/jsonSerializable.d.ts +120 -0
  159. package/lib/core-interfaces/jsonSerializable.d.ts.map +1 -0
  160. package/lib/core-interfaces/jsonSerializable.js +6 -0
  161. package/lib/core-interfaces/jsonSerializable.js.map +1 -0
  162. package/lib/core-interfaces/jsonSerializationErrors.d.ts +31 -0
  163. package/lib/core-interfaces/jsonSerializationErrors.d.ts.map +1 -0
  164. package/lib/core-interfaces/jsonSerializationErrors.js +6 -0
  165. package/lib/core-interfaces/jsonSerializationErrors.js.map +1 -0
  166. package/lib/core-interfaces/jsonType.d.ts +29 -0
  167. package/lib/core-interfaces/jsonType.d.ts.map +1 -0
  168. package/lib/core-interfaces/jsonType.js +6 -0
  169. package/lib/core-interfaces/jsonType.js.map +1 -0
  170. package/lib/datastorePresenceManagerFactory.d.ts +48 -0
  171. package/lib/datastorePresenceManagerFactory.d.ts.map +1 -0
  172. package/lib/datastorePresenceManagerFactory.js +75 -0
  173. package/lib/datastorePresenceManagerFactory.js.map +1 -0
  174. package/lib/datastoreSupport.d.ts +31 -0
  175. package/lib/datastoreSupport.d.ts.map +1 -0
  176. package/lib/datastoreSupport.js +77 -0
  177. package/lib/datastoreSupport.js.map +1 -0
  178. package/lib/events/events.d.ts +198 -0
  179. package/lib/events/events.d.ts.map +1 -0
  180. package/lib/events/events.js +152 -0
  181. package/lib/events/events.js.map +1 -0
  182. package/lib/experimentalAccess.d.ts +15 -0
  183. package/lib/experimentalAccess.d.ts.map +1 -0
  184. package/lib/experimentalAccess.js +42 -0
  185. package/lib/experimentalAccess.js.map +1 -0
  186. package/lib/exposedInternalTypes.d.ts +100 -0
  187. package/lib/exposedInternalTypes.d.ts.map +1 -0
  188. package/lib/exposedInternalTypes.js +16 -0
  189. package/lib/exposedInternalTypes.js.map +1 -0
  190. package/lib/exposedUtilityTypes.d.ts +63 -0
  191. package/lib/exposedUtilityTypes.d.ts.map +1 -0
  192. package/lib/exposedUtilityTypes.js +6 -0
  193. package/lib/exposedUtilityTypes.js.map +1 -0
  194. package/lib/index.d.ts +22 -0
  195. package/lib/index.d.ts.map +1 -0
  196. package/lib/index.js +11 -0
  197. package/lib/index.js.map +1 -0
  198. package/lib/internalTypes.d.ts +39 -0
  199. package/lib/internalTypes.d.ts.map +1 -0
  200. package/lib/internalTypes.js +11 -0
  201. package/lib/internalTypes.js.map +1 -0
  202. package/lib/latestMapValueManager.d.ts +182 -0
  203. package/lib/latestMapValueManager.d.ts.map +1 -0
  204. package/lib/latestMapValueManager.js +202 -0
  205. package/lib/latestMapValueManager.js.map +1 -0
  206. package/lib/latestValueControls.d.ts +44 -0
  207. package/lib/latestValueControls.d.ts.map +1 -0
  208. package/lib/latestValueControls.js +24 -0
  209. package/lib/latestValueControls.js.map +1 -0
  210. package/lib/latestValueManager.d.ts +69 -0
  211. package/lib/latestValueManager.d.ts.map +1 -0
  212. package/lib/latestValueManager.js +96 -0
  213. package/lib/latestValueManager.js.map +1 -0
  214. package/lib/latestValueTypes.d.ts +44 -0
  215. package/lib/latestValueTypes.d.ts.map +1 -0
  216. package/lib/latestValueTypes.js +6 -0
  217. package/lib/latestValueTypes.js.map +1 -0
  218. package/lib/notificationsManager.d.ts +101 -0
  219. package/lib/notificationsManager.d.ts.map +1 -0
  220. package/lib/notificationsManager.js +78 -0
  221. package/lib/notificationsManager.js.map +1 -0
  222. package/lib/presence.d.ts +180 -0
  223. package/lib/presence.d.ts.map +1 -0
  224. package/lib/presence.js +20 -0
  225. package/lib/presence.js.map +1 -0
  226. package/lib/presenceDatastoreManager.d.ts +91 -0
  227. package/lib/presenceDatastoreManager.d.ts.map +1 -0
  228. package/lib/presenceDatastoreManager.js +244 -0
  229. package/lib/presenceDatastoreManager.js.map +1 -0
  230. package/lib/presenceManager.d.ts +20 -0
  231. package/lib/presenceManager.d.ts.map +1 -0
  232. package/lib/presenceManager.js +100 -0
  233. package/lib/presenceManager.js.map +1 -0
  234. package/lib/presenceStates.d.ts +78 -0
  235. package/lib/presenceStates.d.ts.map +1 -0
  236. package/lib/presenceStates.js +177 -0
  237. package/lib/presenceStates.js.map +1 -0
  238. package/lib/stateDatastore.d.ts +35 -0
  239. package/lib/stateDatastore.d.ts.map +1 -0
  240. package/lib/stateDatastore.js +21 -0
  241. package/lib/stateDatastore.js.map +1 -0
  242. package/lib/systemWorkspace.d.ts +51 -0
  243. package/lib/systemWorkspace.d.ts.map +1 -0
  244. package/lib/systemWorkspace.js +176 -0
  245. package/lib/systemWorkspace.js.map +1 -0
  246. package/lib/tsdoc-metadata.json +11 -0
  247. package/lib/types.d.ts +92 -0
  248. package/lib/types.d.ts.map +1 -0
  249. package/lib/types.js +7 -0
  250. package/lib/types.js.map +1 -0
  251. package/lib/valueManager.d.ts +19 -0
  252. package/lib/valueManager.d.ts.map +1 -0
  253. package/lib/valueManager.js +21 -0
  254. package/lib/valueManager.js.map +1 -0
  255. package/package.json +175 -0
@@ -0,0 +1,180 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ import type { SessionId } from "@fluidframework/id-compressor";
6
+ import type { ClientConnectionId } from "./baseTypes.js";
7
+ import type { PresenceNotifications, PresenceNotificationsSchema, PresenceStates, PresenceStatesSchema, PresenceWorkspaceAddress } from "./types.js";
8
+ import type { ISubscribable } from "@fluidframework/presence/internal/events";
9
+ /**
10
+ * A Fluid client session identifier.
11
+ *
12
+ * @remarks
13
+ * Each client once connected to a session is given a unique identifier for the
14
+ * duration of the session. If a client disconnects and reconnects, it will
15
+ * retain its identifier. Prefer use of {@link ISessionClient} as a way to
16
+ * identify clients in a session. {@link ISessionClient.sessionId} will provide
17
+ * the session ID.
18
+ *
19
+ * @alpha
20
+ */
21
+ export type ClientSessionId = SessionId & {
22
+ readonly ClientSessionId: "ClientSessionId";
23
+ };
24
+ /**
25
+ * The connection status of the {@link ISessionClient}.
26
+ *
27
+ * @alpha
28
+ */
29
+ export declare const SessionClientStatus: {
30
+ /**
31
+ * The session client is connected to the Fluid service.
32
+ */
33
+ readonly Connected: "Connected";
34
+ /**
35
+ * The session client is not connected to the Fluid service.
36
+ */
37
+ readonly Disconnected: "Disconnected";
38
+ };
39
+ /**
40
+ * Represents the connection status of an {@link ISessionClient}.
41
+ *
42
+ * This type can be either `'Connected'` or `'Disconnected'`, indicating whether
43
+ * the session client is currently connected to the Fluid service.
44
+ *
45
+ * When `'Disconnected'`:
46
+ * - State changes are kept locally and communicated to others upon reconnect.
47
+ * - Notification requests are discarded (silently).
48
+ *
49
+ * @alpha
50
+ */
51
+ export type SessionClientStatus = (typeof SessionClientStatus)[keyof typeof SessionClientStatus];
52
+ /**
53
+ * A client within a Fluid session (period of container connectivity to service).
54
+ *
55
+ * @remarks
56
+ * Note: This is very preliminary session client representation.
57
+ *
58
+ * `ISessionClient` should be used as key to distinguish between different
59
+ * clients as they join, rejoin, and disconnect from a session. While a
60
+ * client's {@link ClientConnectionId} from {@link ISessionClient.getConnectionStatus}
61
+ * may change over time, `ISessionClient` will be fixed.
62
+ *
63
+ * @privateRemarks
64
+ * As this is evolved, pay attention to how this relates to Audience, Service
65
+ * Audience, and Quorum representations of clients and users.
66
+ *
67
+ * @sealed
68
+ * @alpha
69
+ */
70
+ export interface ISessionClient<SpecificSessionClientId extends ClientSessionId = ClientSessionId> {
71
+ /**
72
+ * The session ID of the client that is stable over all connections.
73
+ */
74
+ readonly sessionId: SpecificSessionClientId;
75
+ /**
76
+ * Get current client connection ID.
77
+ *
78
+ * @returns Current client connection ID.
79
+ *
80
+ * @remarks
81
+ * Connection ID will change on reconnect.
82
+ *
83
+ * If {@link ISessionClient.getConnectionStatus} is {@link (SessionClientStatus:variable).Disconnected}, this will represent the last known connection ID.
84
+ */
85
+ getConnectionId(): ClientConnectionId;
86
+ /**
87
+ * Get connection status of session client.
88
+ *
89
+ * @returns Connection status of session client.
90
+ *
91
+ */
92
+ getConnectionStatus(): SessionClientStatus;
93
+ }
94
+ /**
95
+ * Utility type limiting to a specific session client. (A session client with
96
+ * a specific session ID - not just any session ID.)
97
+ *
98
+ * @internal
99
+ */
100
+ export type SpecificSessionClient<SpecificSessionClientId extends ClientSessionId> = string extends SpecificSessionClientId ? never : ISessionClient<SpecificSessionClientId>;
101
+ /**
102
+ * @sealed
103
+ * @alpha
104
+ */
105
+ export interface PresenceEvents {
106
+ /**
107
+ * Raised when new client joins session.
108
+ *
109
+ * @eventProperty
110
+ */
111
+ attendeeJoined: (attendee: ISessionClient) => void;
112
+ /**
113
+ * Raised when client appears disconnected from session.
114
+ *
115
+ * @eventProperty
116
+ */
117
+ attendeeDisconnected: (attendee: ISessionClient) => void;
118
+ /**
119
+ * Raised when a workspace is activated within the session.
120
+ *
121
+ * "Activated" means that a workspace is being used by a client and this
122
+ * client is seeing information for the first time.
123
+ *
124
+ * @remarks
125
+ * Local workspaces may be passively acquired/registered when this event
126
+ * is raised. For a notifications workspace, that lazy registration must
127
+ * be done before the event handler returns to ensure no notifications
128
+ * are missed.
129
+ */
130
+ workspaceActivated: (workspaceAddress: PresenceWorkspaceAddress, type: "States" | "Notifications" | "Unknown") => void;
131
+ }
132
+ /**
133
+ * Presence represents known clients within a session and their custom states and notifications.
134
+ *
135
+ * @sealed
136
+ * @alpha
137
+ */
138
+ export interface IPresence {
139
+ /**
140
+ * Events for Notifications manager.
141
+ */
142
+ readonly events: ISubscribable<PresenceEvents>;
143
+ /**
144
+ * Get all attendees in the session.
145
+ *
146
+ * @remarks
147
+ * Attendee states are dynamic and will change as clients join and leave
148
+ * the session.
149
+ */
150
+ getAttendees(): ReadonlySet<ISessionClient>;
151
+ /**
152
+ * Lookup a specific attendee in the session.
153
+ *
154
+ * @param clientId - Client connection or session ID
155
+ */
156
+ getAttendee(clientId: ClientConnectionId | ClientSessionId): ISessionClient;
157
+ /**
158
+ * Get this client's session client.
159
+ *
160
+ * @returns This client's session client.
161
+ */
162
+ getMyself(): ISessionClient;
163
+ /**
164
+ * Acquires a PresenceStates workspace from store or adds new one.
165
+ *
166
+ * @param workspaceAddress - Address of the requested PresenceStates Workspace
167
+ * @param requestedContent - Requested states for the workspace
168
+ * @returns A PresenceStates workspace
169
+ */
170
+ getStates<StatesSchema extends PresenceStatesSchema>(workspaceAddress: PresenceWorkspaceAddress, requestedContent: StatesSchema): PresenceStates<StatesSchema>;
171
+ /**
172
+ * Acquires a Notifications workspace from store or adds new one.
173
+ *
174
+ * @param workspaceAddress - Address of the requested Notifications Workspace
175
+ * @param requestedContent - Requested notifications for the workspace
176
+ * @returns A Notifications workspace
177
+ */
178
+ getNotifications<NotificationsSchema extends PresenceNotificationsSchema>(notificationsId: PresenceWorkspaceAddress, requestedContent: NotificationsSchema): PresenceNotifications<NotificationsSchema>;
179
+ }
180
+ //# sourceMappingURL=presence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presence.d.ts","sourceRoot":"","sources":["../src/presence.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAE/D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,KAAK,EACX,qBAAqB,EACrB,2BAA2B,EAC3B,cAAc,EACd,oBAAoB,EACpB,wBAAwB,EACxB,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0CAA0C,CAAC;AAE9E;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG;IAAE,QAAQ,CAAC,eAAe,EAAE,iBAAiB,CAAA;CAAE,CAAC;AAE1F;;;;GAIG;AACH,eAAO,MAAM,mBAAmB;IAC/B;;OAEG;;IAGH;;OAEG;;CAEM,CAAC;AAEX;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,mBAAmB,GAC9B,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,OAAO,mBAAmB,CAAC,CAAC;AAEhE;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,cAAc,CAC9B,uBAAuB,SAAS,eAAe,GAAG,eAAe;IAEjE;;OAEG;IACH,QAAQ,CAAC,SAAS,EAAE,uBAAuB,CAAC;IAE5C;;;;;;;;;OASG;IACH,eAAe,IAAI,kBAAkB,CAAC;IAEtC;;;;;OAKG;IACH,mBAAmB,IAAI,mBAAmB,CAAC;CAC3C;AAED;;;;;GAKG;AACH,MAAM,MAAM,qBAAqB,CAAC,uBAAuB,SAAS,eAAe,IAChF,MAAM,SAAS,uBAAuB,GAAG,KAAK,GAAG,cAAc,CAAC,uBAAuB,CAAC,CAAC;AAE1F;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC9B;;;;OAIG;IACH,cAAc,EAAE,CAAC,QAAQ,EAAE,cAAc,KAAK,IAAI,CAAC;IAEnD;;;;OAIG;IACH,oBAAoB,EAAE,CAAC,QAAQ,EAAE,cAAc,KAAK,IAAI,CAAC;IAEzD;;;;;;;;;;;OAWG;IACH,kBAAkB,EAAE,CACnB,gBAAgB,EAAE,wBAAwB,EAC1C,IAAI,EAAE,QAAQ,GAAG,eAAe,GAAG,SAAS,KACxC,IAAI,CAAC;CACV;AAED;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACzB;;OAEG;IACH,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC;IAE/C;;;;;;OAMG;IACH,YAAY,IAAI,WAAW,CAAC,cAAc,CAAC,CAAC;IAE5C;;;;OAIG;IACH,WAAW,CAAC,QAAQ,EAAE,kBAAkB,GAAG,eAAe,GAAG,cAAc,CAAC;IAE5E;;;;OAIG;IACH,SAAS,IAAI,cAAc,CAAC;IAE5B;;;;;;OAMG;IACH,SAAS,CAAC,YAAY,SAAS,oBAAoB,EAClD,gBAAgB,EAAE,wBAAwB,EAC1C,gBAAgB,EAAE,YAAY,GAC5B,cAAc,CAAC,YAAY,CAAC,CAAC;IAEhC;;;;;;OAMG;IACH,gBAAgB,CAAC,mBAAmB,SAAS,2BAA2B,EACvE,eAAe,EAAE,wBAAwB,EACzC,gBAAgB,EAAE,mBAAmB,GACnC,qBAAqB,CAAC,mBAAmB,CAAC,CAAC;CAC9C"}
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ /*!
3
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
4
+ * Licensed under the MIT License.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.SessionClientStatus = void 0;
8
+ /**
9
+ * The connection status of the {@link ISessionClient}.
10
+ *
11
+ * @alpha
12
+ */
13
+ exports.SessionClientStatus = {
14
+ /**
15
+ * The session client is connected to the Fluid service.
16
+ */
17
+ Connected: "Connected",
18
+ /**
19
+ * The session client is not connected to the Fluid service.
20
+ */
21
+ Disconnected: "Disconnected",
22
+ };
23
+ //# sourceMappingURL=presence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presence.js","sourceRoot":"","sources":["../src/presence.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AA6BH;;;;GAIG;AACU,QAAA,mBAAmB,GAAG;IAClC;;OAEG;IACH,SAAS,EAAE,WAAW;IAEtB;;OAEG;IACH,YAAY,EAAE,cAAc;CACnB,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport type { SessionId } from \"@fluidframework/id-compressor\";\n\nimport type { ClientConnectionId } from \"./baseTypes.js\";\nimport type {\n\tPresenceNotifications,\n\tPresenceNotificationsSchema,\n\tPresenceStates,\n\tPresenceStatesSchema,\n\tPresenceWorkspaceAddress,\n} from \"./types.js\";\n\nimport type { ISubscribable } from \"@fluidframework/presence/internal/events\";\n\n/**\n * A Fluid client session identifier.\n *\n * @remarks\n * Each client once connected to a session is given a unique identifier for the\n * duration of the session. If a client disconnects and reconnects, it will\n * retain its identifier. Prefer use of {@link ISessionClient} as a way to\n * identify clients in a session. {@link ISessionClient.sessionId} will provide\n * the session ID.\n *\n * @alpha\n */\nexport type ClientSessionId = SessionId & { readonly ClientSessionId: \"ClientSessionId\" };\n\n/**\n * The connection status of the {@link ISessionClient}.\n *\n * @alpha\n */\nexport const SessionClientStatus = {\n\t/**\n\t * The session client is connected to the Fluid service.\n\t */\n\tConnected: \"Connected\",\n\n\t/**\n\t * The session client is not connected to the Fluid service.\n\t */\n\tDisconnected: \"Disconnected\",\n} as const;\n\n/**\n * Represents the connection status of an {@link ISessionClient}.\n *\n * This type can be either `'Connected'` or `'Disconnected'`, indicating whether\n * the session client is currently connected to the Fluid service.\n *\n * When `'Disconnected'`:\n * - State changes are kept locally and communicated to others upon reconnect.\n * - Notification requests are discarded (silently).\n *\n * @alpha\n */\nexport type SessionClientStatus =\n\t(typeof SessionClientStatus)[keyof typeof SessionClientStatus];\n\n/**\n * A client within a Fluid session (period of container connectivity to service).\n *\n * @remarks\n * Note: This is very preliminary session client representation.\n *\n * `ISessionClient` should be used as key to distinguish between different\n * clients as they join, rejoin, and disconnect from a session. While a\n * client's {@link ClientConnectionId} from {@link ISessionClient.getConnectionStatus}\n * may change over time, `ISessionClient` will be fixed.\n *\n * @privateRemarks\n * As this is evolved, pay attention to how this relates to Audience, Service\n * Audience, and Quorum representations of clients and users.\n *\n * @sealed\n * @alpha\n */\nexport interface ISessionClient<\n\tSpecificSessionClientId extends ClientSessionId = ClientSessionId,\n> {\n\t/**\n\t * The session ID of the client that is stable over all connections.\n\t */\n\treadonly sessionId: SpecificSessionClientId;\n\n\t/**\n\t * Get current client connection ID.\n\t *\n\t * @returns Current client connection ID.\n\t *\n\t * @remarks\n\t * Connection ID will change on reconnect.\n\t *\n\t * If {@link ISessionClient.getConnectionStatus} is {@link (SessionClientStatus:variable).Disconnected}, this will represent the last known connection ID.\n\t */\n\tgetConnectionId(): ClientConnectionId;\n\n\t/**\n\t * Get connection status of session client.\n\t *\n\t * @returns Connection status of session client.\n\t *\n\t */\n\tgetConnectionStatus(): SessionClientStatus;\n}\n\n/**\n * Utility type limiting to a specific session client. (A session client with\n * a specific session ID - not just any session ID.)\n *\n * @internal\n */\nexport type SpecificSessionClient<SpecificSessionClientId extends ClientSessionId> =\n\tstring extends SpecificSessionClientId ? never : ISessionClient<SpecificSessionClientId>;\n\n/**\n * @sealed\n * @alpha\n */\nexport interface PresenceEvents {\n\t/**\n\t * Raised when new client joins session.\n\t *\n\t * @eventProperty\n\t */\n\tattendeeJoined: (attendee: ISessionClient) => void;\n\n\t/**\n\t * Raised when client appears disconnected from session.\n\t *\n\t * @eventProperty\n\t */\n\tattendeeDisconnected: (attendee: ISessionClient) => void;\n\n\t/**\n\t * Raised when a workspace is activated within the session.\n\t *\n\t * \"Activated\" means that a workspace is being used by a client and this\n\t * client is seeing information for the first time.\n\t *\n\t * @remarks\n\t * Local workspaces may be passively acquired/registered when this event\n\t * is raised. For a notifications workspace, that lazy registration must\n\t * be done before the event handler returns to ensure no notifications\n\t * are missed.\n\t */\n\tworkspaceActivated: (\n\t\tworkspaceAddress: PresenceWorkspaceAddress,\n\t\ttype: \"States\" | \"Notifications\" | \"Unknown\",\n\t) => void;\n}\n\n/**\n * Presence represents known clients within a session and their custom states and notifications.\n *\n * @sealed\n * @alpha\n */\nexport interface IPresence {\n\t/**\n\t * Events for Notifications manager.\n\t */\n\treadonly events: ISubscribable<PresenceEvents>;\n\n\t/**\n\t * Get all attendees in the session.\n\t *\n\t * @remarks\n\t * Attendee states are dynamic and will change as clients join and leave\n\t * the session.\n\t */\n\tgetAttendees(): ReadonlySet<ISessionClient>;\n\n\t/**\n\t * Lookup a specific attendee in the session.\n\t *\n\t * @param clientId - Client connection or session ID\n\t */\n\tgetAttendee(clientId: ClientConnectionId | ClientSessionId): ISessionClient;\n\n\t/**\n\t * Get this client's session client.\n\t *\n\t * @returns This client's session client.\n\t */\n\tgetMyself(): ISessionClient;\n\n\t/**\n\t * Acquires a PresenceStates workspace from store or adds new one.\n\t *\n\t * @param workspaceAddress - Address of the requested PresenceStates Workspace\n\t * @param requestedContent - Requested states for the workspace\n\t * @returns A PresenceStates workspace\n\t */\n\tgetStates<StatesSchema extends PresenceStatesSchema>(\n\t\tworkspaceAddress: PresenceWorkspaceAddress,\n\t\trequestedContent: StatesSchema,\n\t): PresenceStates<StatesSchema>;\n\n\t/**\n\t * Acquires a Notifications workspace from store or adds new one.\n\t *\n\t * @param workspaceAddress - Address of the requested Notifications Workspace\n\t * @param requestedContent - Requested notifications for the workspace\n\t * @returns A Notifications workspace\n\t */\n\tgetNotifications<NotificationsSchema extends PresenceNotificationsSchema>(\n\t\tnotificationsId: PresenceWorkspaceAddress,\n\t\trequestedContent: NotificationsSchema,\n\t): PresenceNotifications<NotificationsSchema>;\n}\n"]}
@@ -0,0 +1,91 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ import type { IInboundSignalMessage } from "@fluidframework/runtime-definitions/internal";
6
+ import type { ITelemetryLoggerExt } from "@fluidframework/telemetry-utils/internal";
7
+ import type { ClientConnectionId } from "./baseTypes.js";
8
+ import type { IEphemeralRuntime } from "./internalTypes.js";
9
+ import type { ClientSessionId, ISessionClient } from "./presence.js";
10
+ import type { ClientUpdateEntry, PresenceStatesInternal } from "./presenceStates.js";
11
+ import type { SystemWorkspaceDatastore } from "./systemWorkspace.js";
12
+ import type { PresenceStates, PresenceStatesSchema, PresenceWorkspaceAddress } from "./types.js";
13
+ import type { IExtensionMessage } from "@fluidframework/presence/internal/container-definitions/internal";
14
+ interface PresenceStatesEntry<TSchema extends PresenceStatesSchema> {
15
+ public: PresenceStates<TSchema>;
16
+ internal: PresenceStatesInternal;
17
+ }
18
+ interface SystemDatastore {
19
+ "system:presence": SystemWorkspaceDatastore;
20
+ }
21
+ type InternalWorkspaceAddress = `${"s" | "n"}:${PresenceWorkspaceAddress}`;
22
+ interface GeneralDatastoreMessageContent {
23
+ [WorkspaceAddress: string]: {
24
+ [StateValueManagerKey: string]: {
25
+ [ClientSessionId: ClientSessionId]: ClientUpdateEntry;
26
+ };
27
+ };
28
+ }
29
+ type DatastoreMessageContent = SystemDatastore & GeneralDatastoreMessageContent;
30
+ declare const datastoreUpdateMessageType = "Pres:DatastoreUpdate";
31
+ interface DatastoreUpdateMessage extends IInboundSignalMessage {
32
+ type: typeof datastoreUpdateMessageType;
33
+ content: {
34
+ sendTimestamp: number;
35
+ avgLatency: number;
36
+ isComplete?: true;
37
+ data: DatastoreMessageContent;
38
+ };
39
+ }
40
+ declare const joinMessageType = "Pres:ClientJoin";
41
+ interface ClientJoinMessage extends IInboundSignalMessage {
42
+ type: typeof joinMessageType;
43
+ content: {
44
+ updateProviders: ClientConnectionId[];
45
+ sendTimestamp: number;
46
+ avgLatency: number;
47
+ data: DatastoreMessageContent;
48
+ };
49
+ }
50
+ /**
51
+ * @internal
52
+ */
53
+ export interface PresenceDatastoreManager {
54
+ joinSession(clientId: ClientConnectionId): void;
55
+ getWorkspace<TSchema extends PresenceStatesSchema>(internalWorkspaceAddress: InternalWorkspaceAddress, requestedContent: TSchema): PresenceStates<TSchema>;
56
+ processSignal(message: IExtensionMessage, local: boolean): void;
57
+ }
58
+ /**
59
+ * Manages singleton datastore for all Presence.
60
+ */
61
+ export declare class PresenceDatastoreManagerImpl implements PresenceDatastoreManager {
62
+ private readonly clientSessionId;
63
+ private readonly runtime;
64
+ private readonly lookupClient;
65
+ private readonly logger;
66
+ private readonly datastore;
67
+ private averageLatency;
68
+ private returnedMessages;
69
+ private refreshBroadcastRequested;
70
+ private readonly workspaces;
71
+ constructor(clientSessionId: ClientSessionId, runtime: IEphemeralRuntime, lookupClient: (clientId: ClientSessionId) => ISessionClient, logger: ITelemetryLoggerExt | undefined, systemWorkspaceDatastore: SystemWorkspaceDatastore, systemWorkspace: PresenceStatesEntry<PresenceStatesSchema>);
72
+ joinSession(clientId: ClientConnectionId): void;
73
+ getWorkspace<TSchema extends PresenceStatesSchema>(internalWorkspaceAddress: InternalWorkspaceAddress, requestedContent: TSchema): PresenceStates<TSchema>;
74
+ private localUpdate;
75
+ private broadcastAllKnownState;
76
+ processSignal(message: IInboundSignalMessage | DatastoreUpdateMessage | ClientJoinMessage, local: boolean): void;
77
+ /**
78
+ * Handles responding to another client joining the session.
79
+ *
80
+ * @param updateProviders - list of client connection id's that requestor selected
81
+ * to provide response
82
+ * @param requestor - `requestor` is only used in telemetry. While it is the requestor's
83
+ * client connection id, that is not most important. It is important that this is a
84
+ * unique shared id across all clients that might respond as we want to monitor the
85
+ * response patterns. The convenience of being client connection id will allow
86
+ * correlation with other telemetry where it is often called just `clientId`.
87
+ */
88
+ private prepareJoinResponse;
89
+ }
90
+ export {};
91
+ //# sourceMappingURL=presenceDatastoreManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presenceDatastoreManager.d.ts","sourceRoot":"","sources":["../src/presenceDatastoreManager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,248 @@
1
+ "use strict";
2
+ /*!
3
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
4
+ * Licensed under the MIT License.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.PresenceDatastoreManagerImpl = void 0;
8
+ const internal_1 = require("@fluidframework/core-utils/internal");
9
+ const presenceStates_js_1 = require("./presenceStates.js");
10
+ const datastoreUpdateMessageType = "Pres:DatastoreUpdate";
11
+ const joinMessageType = "Pres:ClientJoin";
12
+ function isPresenceMessage(message) {
13
+ return message.type.startsWith("Pres:");
14
+ }
15
+ /**
16
+ * Manages singleton datastore for all Presence.
17
+ */
18
+ class PresenceDatastoreManagerImpl {
19
+ constructor(clientSessionId, runtime, lookupClient, logger, systemWorkspaceDatastore, systemWorkspace) {
20
+ this.clientSessionId = clientSessionId;
21
+ this.runtime = runtime;
22
+ this.lookupClient = lookupClient;
23
+ this.logger = logger;
24
+ this.averageLatency = 0;
25
+ this.returnedMessages = 0;
26
+ this.refreshBroadcastRequested = false;
27
+ this.workspaces = new Map();
28
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
29
+ this.datastore = { "system:presence": systemWorkspaceDatastore };
30
+ this.workspaces.set("system:presence", systemWorkspace);
31
+ }
32
+ joinSession(clientId) {
33
+ // Broadcast join message to all clients
34
+ const updateProviders = [...this.runtime.getQuorum().getMembers().keys()].filter((quorumClientId) => quorumClientId !== clientId);
35
+ // Limit to three providers to prevent flooding the network.
36
+ // If none respond, others present will (should) after a delay.
37
+ if (updateProviders.length > 3) {
38
+ updateProviders.length = 3;
39
+ }
40
+ this.runtime.submitSignal(joinMessageType, {
41
+ sendTimestamp: Date.now(),
42
+ avgLatency: this.averageLatency,
43
+ data: this.datastore,
44
+ updateProviders,
45
+ });
46
+ }
47
+ getWorkspace(internalWorkspaceAddress, requestedContent) {
48
+ const existing = this.workspaces.get(internalWorkspaceAddress);
49
+ if (existing) {
50
+ return existing.internal.ensureContent(requestedContent);
51
+ }
52
+ let workspaceDatastore = this.datastore[internalWorkspaceAddress];
53
+ if (workspaceDatastore === undefined) {
54
+ workspaceDatastore = this.datastore[internalWorkspaceAddress] = {};
55
+ }
56
+ const localUpdate = (states, forceBroadcast) => {
57
+ // Check for connectivity before sending updates.
58
+ if (!this.runtime.connected) {
59
+ return;
60
+ }
61
+ const clientConnectionId = this.runtime.clientId;
62
+ (0, internal_1.assert)(clientConnectionId !== undefined, 0xa59 /* Client connected without clientId */);
63
+ const currentClientToSessionValueState = this.datastore["system:presence"].clientToSessionId[clientConnectionId];
64
+ const updates = {};
65
+ for (const [key, value] of Object.entries(states)) {
66
+ updates[key] = { [this.clientSessionId]: value };
67
+ }
68
+ this.localUpdate({
69
+ // Always send current connection mapping for some resiliency against
70
+ // lost signals. This ensures that client session id found in `updates`
71
+ // (which is this client's client session id) is always represented in
72
+ // system workspace of recipient clients.
73
+ "system:presence": {
74
+ clientToSessionId: {
75
+ [clientConnectionId]: { ...currentClientToSessionValueState },
76
+ },
77
+ },
78
+ [internalWorkspaceAddress]: updates,
79
+ }, forceBroadcast);
80
+ };
81
+ const entry = (0, presenceStates_js_1.createPresenceStates)({
82
+ clientSessionId: this.clientSessionId,
83
+ lookupClient: this.lookupClient,
84
+ localUpdate,
85
+ }, workspaceDatastore, requestedContent);
86
+ this.workspaces.set(internalWorkspaceAddress, entry);
87
+ return entry.public;
88
+ }
89
+ localUpdate(data, _forceBroadcast) {
90
+ const content = {
91
+ sendTimestamp: Date.now(),
92
+ avgLatency: this.averageLatency,
93
+ // isComplete: false,
94
+ data,
95
+ };
96
+ this.runtime.submitSignal(datastoreUpdateMessageType, content);
97
+ }
98
+ broadcastAllKnownState() {
99
+ this.runtime.submitSignal(datastoreUpdateMessageType, {
100
+ sendTimestamp: Date.now(),
101
+ avgLatency: this.averageLatency,
102
+ isComplete: true,
103
+ data: this.datastore,
104
+ });
105
+ this.refreshBroadcastRequested = false;
106
+ }
107
+ processSignal(
108
+ // Note: IInboundSignalMessage is used here in place of IExtensionMessage
109
+ // as IExtensionMessage's strictly JSON `content` creates type compatibility
110
+ // issues with `ClientSessionId` keys and really unknown value content.
111
+ // IExtensionMessage is a subset of IInboundSignalMessage so this is safe.
112
+ // Change types of DatastoreUpdateMessage | ClientJoinMessage to
113
+ // IExtensionMessage<> derivatives to see the issues.
114
+ message, local) {
115
+ const received = Date.now();
116
+ (0, internal_1.assert)(message.clientId !== null, 0xa3a /* Map received signal without clientId */);
117
+ if (!isPresenceMessage(message)) {
118
+ return;
119
+ }
120
+ if (local) {
121
+ const deliveryDelta = received - message.content.sendTimestamp;
122
+ // Limit returnedMessages count to 256 such that newest message
123
+ // always contributes at least 1/256th to the average. Older
124
+ // messages have more weight, but that diminishes as new messages
125
+ // contribute.
126
+ this.returnedMessages = Math.min(this.returnedMessages + 1, 256);
127
+ this.averageLatency =
128
+ (this.averageLatency * (this.returnedMessages - 1) + deliveryDelta) /
129
+ this.returnedMessages;
130
+ return;
131
+ }
132
+ const timeModifier = received -
133
+ (this.averageLatency + message.content.avgLatency + message.content.sendTimestamp);
134
+ if (message.type === joinMessageType) {
135
+ // It is possible for some signals to come in while client is not connected due
136
+ // to how work is scheduled. If we are not connected, we can't respond to the
137
+ // join request. We will make our own Join request once we are connected.
138
+ if (this.runtime.connected) {
139
+ this.prepareJoinResponse(message.content.updateProviders, message.clientId);
140
+ }
141
+ // It is okay to continue processing the contained updates even if we are not
142
+ // connected.
143
+ }
144
+ else {
145
+ (0, internal_1.assert)(message.type === datastoreUpdateMessageType, 0xa3b /* Unexpected message type */);
146
+ if (message.content.isComplete) {
147
+ this.refreshBroadcastRequested = false;
148
+ }
149
+ }
150
+ for (const [workspaceAddress, remoteDatastore] of Object.entries(message.content.data)) {
151
+ // Direct to the appropriate Presence Workspace, if present.
152
+ const workspace = this.workspaces.get(workspaceAddress);
153
+ if (workspace) {
154
+ workspace.internal.processUpdate(received, timeModifier, remoteDatastore, message.clientId);
155
+ }
156
+ else {
157
+ // All broadcast state is kept even if not currently registered, unless a value
158
+ // notes itself to be ignored.
159
+ let workspaceDatastore = this.datastore[workspaceAddress];
160
+ if (workspaceDatastore === undefined) {
161
+ workspaceDatastore = this.datastore[workspaceAddress] = {};
162
+ if (!workspaceAddress.startsWith("system:")) {
163
+ // TODO: Emit workspaceActivated event for PresenceEvents
164
+ }
165
+ }
166
+ for (const [key, remoteAllKnownState] of Object.entries(remoteDatastore)) {
167
+ (0, presenceStates_js_1.mergeUntrackedDatastore)(key, remoteAllKnownState, workspaceDatastore, timeModifier);
168
+ }
169
+ }
170
+ }
171
+ }
172
+ /**
173
+ * Handles responding to another client joining the session.
174
+ *
175
+ * @param updateProviders - list of client connection id's that requestor selected
176
+ * to provide response
177
+ * @param requestor - `requestor` is only used in telemetry. While it is the requestor's
178
+ * client connection id, that is not most important. It is important that this is a
179
+ * unique shared id across all clients that might respond as we want to monitor the
180
+ * response patterns. The convenience of being client connection id will allow
181
+ * correlation with other telemetry where it is often called just `clientId`.
182
+ */
183
+ prepareJoinResponse(updateProviders, requestor) {
184
+ this.refreshBroadcastRequested = true;
185
+ // We must be connected to receive this message, so clientId should be defined.
186
+ // If it isn't then, not really a problem; just won't be in provider or quorum list.
187
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
188
+ const clientId = this.runtime.clientId;
189
+ // const requestor = message.clientId;
190
+ if (updateProviders.includes(clientId)) {
191
+ // Send all current state to the new client
192
+ this.broadcastAllKnownState();
193
+ this.logger?.sendTelemetryEvent({
194
+ eventName: "JoinResponse",
195
+ details: {
196
+ type: "broadcastAll",
197
+ requestor,
198
+ role: "primary",
199
+ },
200
+ });
201
+ }
202
+ else {
203
+ // Schedule a broadcast to the new client after a delay only to send if
204
+ // another broadcast hasn't been seen in the meantime. The delay is based
205
+ // on the position in the quorum list. It doesn't have to be a stable
206
+ // list across all clients. We need something to provide suggested order
207
+ // to prevent a flood of broadcasts.
208
+ let relativeResponseOrder;
209
+ const quorumMembers = this.runtime.getQuorum().getMembers();
210
+ const self = quorumMembers.get(clientId);
211
+ if (self) {
212
+ // Compute order quorum join order (indicated by sequenceNumber)
213
+ relativeResponseOrder = 0;
214
+ for (const { sequenceNumber } of quorumMembers.values()) {
215
+ if (sequenceNumber < self.sequenceNumber) {
216
+ relativeResponseOrder++;
217
+ }
218
+ }
219
+ }
220
+ else {
221
+ // Order past quorum members + arbitrary additional offset up to 10
222
+ relativeResponseOrder = quorumMembers.size + Math.random() * 10;
223
+ }
224
+ // These numbers have been chosen arbitrarily to start with.
225
+ // 20 is minimum wait time, 20 is the additional wait time per provider
226
+ // given an chance before us with named providers given more time.
227
+ const waitTime = 20 + 20 * (3 * updateProviders.length + relativeResponseOrder);
228
+ setTimeout(() => {
229
+ // Make sure a broadcast is still needed and we are currently connected.
230
+ // If not connected, nothing we can do.
231
+ if (this.refreshBroadcastRequested && this.runtime.connected) {
232
+ this.broadcastAllKnownState();
233
+ this.logger?.sendTelemetryEvent({
234
+ eventName: "JoinResponse",
235
+ details: {
236
+ type: "broadcastAll",
237
+ requestor,
238
+ role: "secondary",
239
+ order: relativeResponseOrder,
240
+ },
241
+ });
242
+ }
243
+ }, waitTime);
244
+ }
245
+ }
246
+ }
247
+ exports.PresenceDatastoreManagerImpl = PresenceDatastoreManagerImpl;
248
+ //# sourceMappingURL=presenceDatastoreManager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presenceDatastoreManager.js","sourceRoot":"","sources":["../src/presenceDatastoreManager.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { assert } from \"@fluidframework/core-utils/internal\";\nimport type { IInboundSignalMessage } from \"@fluidframework/runtime-definitions/internal\";\nimport type { ITelemetryLoggerExt } from \"@fluidframework/telemetry-utils/internal\";\n\nimport type { ClientConnectionId } from \"./baseTypes.js\";\nimport type { IEphemeralRuntime } from \"./internalTypes.js\";\nimport type { ClientSessionId, ISessionClient } from \"./presence.js\";\nimport type {\n\tClientUpdateEntry,\n\tPresenceStatesInternal,\n\tValueElementMap,\n} from \"./presenceStates.js\";\nimport { createPresenceStates, mergeUntrackedDatastore } from \"./presenceStates.js\";\nimport type { SystemWorkspaceDatastore } from \"./systemWorkspace.js\";\nimport type {\n\tPresenceStates,\n\tPresenceStatesSchema,\n\tPresenceWorkspaceAddress,\n} from \"./types.js\";\n\nimport type { IExtensionMessage } from \"@fluidframework/presence/internal/container-definitions/internal\";\n\ninterface PresenceStatesEntry<TSchema extends PresenceStatesSchema> {\n\tpublic: PresenceStates<TSchema>;\n\tinternal: PresenceStatesInternal;\n}\n\ninterface SystemDatastore {\n\t\"system:presence\": SystemWorkspaceDatastore;\n}\n\ntype InternalWorkspaceAddress = `${\"s\" | \"n\"}:${PresenceWorkspaceAddress}`;\n\ntype PresenceDatastore = SystemDatastore & {\n\t[WorkspaceAddress: string]: ValueElementMap<PresenceStatesSchema>;\n};\n\ninterface GeneralDatastoreMessageContent {\n\t[WorkspaceAddress: string]: {\n\t\t[StateValueManagerKey: string]: {\n\t\t\t[ClientSessionId: ClientSessionId]: ClientUpdateEntry;\n\t\t};\n\t};\n}\n\ntype DatastoreMessageContent = SystemDatastore & GeneralDatastoreMessageContent;\n\nconst datastoreUpdateMessageType = \"Pres:DatastoreUpdate\";\ninterface DatastoreUpdateMessage extends IInboundSignalMessage {\n\ttype: typeof datastoreUpdateMessageType;\n\tcontent: {\n\t\tsendTimestamp: number;\n\t\tavgLatency: number;\n\t\tisComplete?: true;\n\t\tdata: DatastoreMessageContent;\n\t};\n}\n\nconst joinMessageType = \"Pres:ClientJoin\";\ninterface ClientJoinMessage extends IInboundSignalMessage {\n\ttype: typeof joinMessageType;\n\tcontent: {\n\t\tupdateProviders: ClientConnectionId[];\n\t\tsendTimestamp: number;\n\t\tavgLatency: number;\n\t\tdata: DatastoreMessageContent;\n\t};\n}\n\nfunction isPresenceMessage(\n\tmessage: IInboundSignalMessage,\n): message is DatastoreUpdateMessage | ClientJoinMessage {\n\treturn message.type.startsWith(\"Pres:\");\n}\n\n/**\n * @internal\n */\nexport interface PresenceDatastoreManager {\n\tjoinSession(clientId: ClientConnectionId): void;\n\tgetWorkspace<TSchema extends PresenceStatesSchema>(\n\t\tinternalWorkspaceAddress: InternalWorkspaceAddress,\n\t\trequestedContent: TSchema,\n\t): PresenceStates<TSchema>;\n\tprocessSignal(message: IExtensionMessage, local: boolean): void;\n}\n\n/**\n * Manages singleton datastore for all Presence.\n */\nexport class PresenceDatastoreManagerImpl implements PresenceDatastoreManager {\n\tprivate readonly datastore: PresenceDatastore;\n\tprivate averageLatency = 0;\n\tprivate returnedMessages = 0;\n\tprivate refreshBroadcastRequested = false;\n\n\tprivate readonly workspaces = new Map<string, PresenceStatesEntry<PresenceStatesSchema>>();\n\n\tpublic constructor(\n\t\tprivate readonly clientSessionId: ClientSessionId,\n\t\tprivate readonly runtime: IEphemeralRuntime,\n\t\tprivate readonly lookupClient: (clientId: ClientSessionId) => ISessionClient,\n\t\tprivate readonly logger: ITelemetryLoggerExt | undefined,\n\t\tsystemWorkspaceDatastore: SystemWorkspaceDatastore,\n\t\tsystemWorkspace: PresenceStatesEntry<PresenceStatesSchema>,\n\t) {\n\t\t// eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n\t\tthis.datastore = { \"system:presence\": systemWorkspaceDatastore } as PresenceDatastore;\n\t\tthis.workspaces.set(\"system:presence\", systemWorkspace);\n\t}\n\n\tpublic joinSession(clientId: ClientConnectionId): void {\n\t\t// Broadcast join message to all clients\n\t\tconst updateProviders = [...this.runtime.getQuorum().getMembers().keys()].filter(\n\t\t\t(quorumClientId) => quorumClientId !== clientId,\n\t\t);\n\t\t// Limit to three providers to prevent flooding the network.\n\t\t// If none respond, others present will (should) after a delay.\n\t\tif (updateProviders.length > 3) {\n\t\t\tupdateProviders.length = 3;\n\t\t}\n\t\tthis.runtime.submitSignal(joinMessageType, {\n\t\t\tsendTimestamp: Date.now(),\n\t\t\tavgLatency: this.averageLatency,\n\t\t\tdata: this.datastore,\n\t\t\tupdateProviders,\n\t\t} satisfies ClientJoinMessage[\"content\"]);\n\t}\n\n\tpublic getWorkspace<TSchema extends PresenceStatesSchema>(\n\t\tinternalWorkspaceAddress: InternalWorkspaceAddress,\n\t\trequestedContent: TSchema,\n\t): PresenceStates<TSchema> {\n\t\tconst existing = this.workspaces.get(internalWorkspaceAddress);\n\t\tif (existing) {\n\t\t\treturn existing.internal.ensureContent(requestedContent);\n\t\t}\n\n\t\tlet workspaceDatastore = this.datastore[internalWorkspaceAddress];\n\t\tif (workspaceDatastore === undefined) {\n\t\t\tworkspaceDatastore = this.datastore[internalWorkspaceAddress] = {};\n\t\t}\n\n\t\tconst localUpdate = (\n\t\t\tstates: { [key: string]: ClientUpdateEntry },\n\t\t\tforceBroadcast: boolean,\n\t\t): void => {\n\t\t\t// Check for connectivity before sending updates.\n\t\t\tif (!this.runtime.connected) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst clientConnectionId = this.runtime.clientId;\n\t\t\tassert(clientConnectionId !== undefined, 0xa59 /* Client connected without clientId */);\n\t\t\tconst currentClientToSessionValueState =\n\t\t\t\tthis.datastore[\"system:presence\"].clientToSessionId[clientConnectionId];\n\n\t\t\tconst updates: GeneralDatastoreMessageContent[InternalWorkspaceAddress] = {};\n\t\t\tfor (const [key, value] of Object.entries(states)) {\n\t\t\t\tupdates[key] = { [this.clientSessionId]: value };\n\t\t\t}\n\t\t\tthis.localUpdate(\n\t\t\t\t{\n\t\t\t\t\t// Always send current connection mapping for some resiliency against\n\t\t\t\t\t// lost signals. This ensures that client session id found in `updates`\n\t\t\t\t\t// (which is this client's client session id) is always represented in\n\t\t\t\t\t// system workspace of recipient clients.\n\t\t\t\t\t\"system:presence\": {\n\t\t\t\t\t\tclientToSessionId: {\n\t\t\t\t\t\t\t[clientConnectionId]: { ...currentClientToSessionValueState },\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t[internalWorkspaceAddress]: updates,\n\t\t\t\t},\n\t\t\t\tforceBroadcast,\n\t\t\t);\n\t\t};\n\n\t\tconst entry = createPresenceStates(\n\t\t\t{\n\t\t\t\tclientSessionId: this.clientSessionId,\n\t\t\t\tlookupClient: this.lookupClient,\n\t\t\t\tlocalUpdate,\n\t\t\t},\n\t\t\tworkspaceDatastore,\n\t\t\trequestedContent,\n\t\t);\n\n\t\tthis.workspaces.set(internalWorkspaceAddress, entry);\n\t\treturn entry.public;\n\t}\n\n\tprivate localUpdate(data: DatastoreMessageContent, _forceBroadcast: boolean): void {\n\t\tconst content = {\n\t\t\tsendTimestamp: Date.now(),\n\t\t\tavgLatency: this.averageLatency,\n\t\t\t// isComplete: false,\n\t\t\tdata,\n\t\t} satisfies DatastoreUpdateMessage[\"content\"];\n\t\tthis.runtime.submitSignal(datastoreUpdateMessageType, content);\n\t}\n\n\tprivate broadcastAllKnownState(): void {\n\t\tthis.runtime.submitSignal(datastoreUpdateMessageType, {\n\t\t\tsendTimestamp: Date.now(),\n\t\t\tavgLatency: this.averageLatency,\n\t\t\tisComplete: true,\n\t\t\tdata: this.datastore,\n\t\t} satisfies DatastoreUpdateMessage[\"content\"]);\n\t\tthis.refreshBroadcastRequested = false;\n\t}\n\n\tpublic processSignal(\n\t\t// Note: IInboundSignalMessage is used here in place of IExtensionMessage\n\t\t// as IExtensionMessage's strictly JSON `content` creates type compatibility\n\t\t// issues with `ClientSessionId` keys and really unknown value content.\n\t\t// IExtensionMessage is a subset of IInboundSignalMessage so this is safe.\n\t\t// Change types of DatastoreUpdateMessage | ClientJoinMessage to\n\t\t// IExtensionMessage<> derivatives to see the issues.\n\t\tmessage: IInboundSignalMessage | DatastoreUpdateMessage | ClientJoinMessage,\n\t\tlocal: boolean,\n\t): void {\n\t\tconst received = Date.now();\n\t\tassert(message.clientId !== null, 0xa3a /* Map received signal without clientId */);\n\t\tif (!isPresenceMessage(message)) {\n\t\t\treturn;\n\t\t}\n\t\tif (local) {\n\t\t\tconst deliveryDelta = received - message.content.sendTimestamp;\n\t\t\t// Limit returnedMessages count to 256 such that newest message\n\t\t\t// always contributes at least 1/256th to the average. Older\n\t\t\t// messages have more weight, but that diminishes as new messages\n\t\t\t// contribute.\n\t\t\tthis.returnedMessages = Math.min(this.returnedMessages + 1, 256);\n\t\t\tthis.averageLatency =\n\t\t\t\t(this.averageLatency * (this.returnedMessages - 1) + deliveryDelta) /\n\t\t\t\tthis.returnedMessages;\n\t\t\treturn;\n\t\t}\n\n\t\tconst timeModifier =\n\t\t\treceived -\n\t\t\t(this.averageLatency + message.content.avgLatency + message.content.sendTimestamp);\n\n\t\tif (message.type === joinMessageType) {\n\t\t\t// It is possible for some signals to come in while client is not connected due\n\t\t\t// to how work is scheduled. If we are not connected, we can't respond to the\n\t\t\t// join request. We will make our own Join request once we are connected.\n\t\t\tif (this.runtime.connected) {\n\t\t\t\tthis.prepareJoinResponse(message.content.updateProviders, message.clientId);\n\t\t\t}\n\t\t\t// It is okay to continue processing the contained updates even if we are not\n\t\t\t// connected.\n\t\t} else {\n\t\t\tassert(message.type === datastoreUpdateMessageType, 0xa3b /* Unexpected message type */);\n\t\t\tif (message.content.isComplete) {\n\t\t\t\tthis.refreshBroadcastRequested = false;\n\t\t\t}\n\t\t}\n\n\t\tfor (const [workspaceAddress, remoteDatastore] of Object.entries(message.content.data)) {\n\t\t\t// Direct to the appropriate Presence Workspace, if present.\n\t\t\tconst workspace = this.workspaces.get(workspaceAddress);\n\t\t\tif (workspace) {\n\t\t\t\tworkspace.internal.processUpdate(\n\t\t\t\t\treceived,\n\t\t\t\t\ttimeModifier,\n\t\t\t\t\tremoteDatastore,\n\t\t\t\t\tmessage.clientId,\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\t// All broadcast state is kept even if not currently registered, unless a value\n\t\t\t\t// notes itself to be ignored.\n\t\t\t\tlet workspaceDatastore = this.datastore[workspaceAddress];\n\t\t\t\tif (workspaceDatastore === undefined) {\n\t\t\t\t\tworkspaceDatastore = this.datastore[workspaceAddress] = {};\n\t\t\t\t\tif (!workspaceAddress.startsWith(\"system:\")) {\n\t\t\t\t\t\t// TODO: Emit workspaceActivated event for PresenceEvents\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor (const [key, remoteAllKnownState] of Object.entries(remoteDatastore)) {\n\t\t\t\t\tmergeUntrackedDatastore(key, remoteAllKnownState, workspaceDatastore, timeModifier);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Handles responding to another client joining the session.\n\t *\n\t * @param updateProviders - list of client connection id's that requestor selected\n\t * to provide response\n\t * @param requestor - `requestor` is only used in telemetry. While it is the requestor's\n\t * client connection id, that is not most important. It is important that this is a\n\t * unique shared id across all clients that might respond as we want to monitor the\n\t * response patterns. The convenience of being client connection id will allow\n\t * correlation with other telemetry where it is often called just `clientId`.\n\t */\n\tprivate prepareJoinResponse(\n\t\tupdateProviders: ClientConnectionId[],\n\t\trequestor: ClientConnectionId,\n\t): void {\n\t\tthis.refreshBroadcastRequested = true;\n\t\t// We must be connected to receive this message, so clientId should be defined.\n\t\t// If it isn't then, not really a problem; just won't be in provider or quorum list.\n\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\tconst clientId = this.runtime.clientId!;\n\t\t// const requestor = message.clientId;\n\t\tif (updateProviders.includes(clientId)) {\n\t\t\t// Send all current state to the new client\n\t\t\tthis.broadcastAllKnownState();\n\t\t\tthis.logger?.sendTelemetryEvent({\n\t\t\t\teventName: \"JoinResponse\",\n\t\t\t\tdetails: {\n\t\t\t\t\ttype: \"broadcastAll\",\n\t\t\t\t\trequestor,\n\t\t\t\t\trole: \"primary\",\n\t\t\t\t},\n\t\t\t});\n\t\t} else {\n\t\t\t// Schedule a broadcast to the new client after a delay only to send if\n\t\t\t// another broadcast hasn't been seen in the meantime. The delay is based\n\t\t\t// on the position in the quorum list. It doesn't have to be a stable\n\t\t\t// list across all clients. We need something to provide suggested order\n\t\t\t// to prevent a flood of broadcasts.\n\t\t\tlet relativeResponseOrder;\n\t\t\tconst quorumMembers = this.runtime.getQuorum().getMembers();\n\t\t\tconst self = quorumMembers.get(clientId);\n\t\t\tif (self) {\n\t\t\t\t// Compute order quorum join order (indicated by sequenceNumber)\n\t\t\t\trelativeResponseOrder = 0;\n\t\t\t\tfor (const { sequenceNumber } of quorumMembers.values()) {\n\t\t\t\t\tif (sequenceNumber < self.sequenceNumber) {\n\t\t\t\t\t\trelativeResponseOrder++;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Order past quorum members + arbitrary additional offset up to 10\n\t\t\t\trelativeResponseOrder = quorumMembers.size + Math.random() * 10;\n\t\t\t}\n\t\t\t// These numbers have been chosen arbitrarily to start with.\n\t\t\t// 20 is minimum wait time, 20 is the additional wait time per provider\n\t\t\t// given an chance before us with named providers given more time.\n\t\t\tconst waitTime = 20 + 20 * (3 * updateProviders.length + relativeResponseOrder);\n\t\t\tsetTimeout(() => {\n\t\t\t\t// Make sure a broadcast is still needed and we are currently connected.\n\t\t\t\t// If not connected, nothing we can do.\n\t\t\t\tif (this.refreshBroadcastRequested && this.runtime.connected) {\n\t\t\t\t\tthis.broadcastAllKnownState();\n\t\t\t\t\tthis.logger?.sendTelemetryEvent({\n\t\t\t\t\t\teventName: \"JoinResponse\",\n\t\t\t\t\t\tdetails: {\n\t\t\t\t\t\t\ttype: \"broadcastAll\",\n\t\t\t\t\t\t\trequestor,\n\t\t\t\t\t\t\trole: \"secondary\",\n\t\t\t\t\t\t\torder: relativeResponseOrder,\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}, waitTime);\n\t\t}\n\t}\n}\n"]}
@@ -0,0 +1,20 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ import type { IEphemeralRuntime } from "./internalTypes.js";
6
+ import type { ClientSessionId, IPresence } from "./presence.js";
7
+ import type { IContainerExtension } from "@fluidframework/presence/internal/container-definitions/internal";
8
+ /**
9
+ * Portion of the container extension requirements ({@link IContainerExtension}) that are delegated to presence manager.
10
+ *
11
+ * @internal
12
+ */
13
+ export type PresenceExtensionInterface = Required<Pick<IContainerExtension<never>, "processSignal">>;
14
+ /**
15
+ * Instantiates Presence Manager
16
+ *
17
+ * @internal
18
+ */
19
+ export declare function createPresenceManager(runtime: IEphemeralRuntime, clientSessionId?: ClientSessionId): IPresence & PresenceExtensionInterface;
20
+ //# sourceMappingURL=presenceManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presenceManager.d.ts","sourceRoot":"","sources":["../src/presenceManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAUH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,KAAK,EACX,eAAe,EACf,SAAS,EAGT,MAAM,eAAe,CAAC;AAWvB,OAAO,KAAK,EACX,mBAAmB,EAEnB,MAAM,kEAAkE,CAAC;AAI1E;;;;GAIG;AACH,MAAM,MAAM,0BAA0B,GAAG,QAAQ,CAChD,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC,EAAE,eAAe,CAAC,CACjD,CAAC;AAoIF;;;;GAIG;AACH,wBAAgB,qBAAqB,CACpC,OAAO,EAAE,iBAAiB,EAC1B,eAAe,GAAE,eAAsD,GACrE,SAAS,GAAG,0BAA0B,CAExC"}