@fluidframework/presence 2.91.0 → 2.93.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (259) hide show
  1. package/README.md +4 -4
  2. package/dist/alpha.d.ts +7 -5
  3. package/dist/beta.d.ts +6 -3
  4. package/dist/getPresence.d.ts +4 -9
  5. package/dist/getPresence.d.ts.map +1 -1
  6. package/dist/getPresence.js +6 -86
  7. package/dist/getPresence.js.map +1 -1
  8. package/dist/index.d.ts +4 -13
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +8 -8
  11. package/dist/index.js.map +1 -1
  12. package/dist/legacy.alpha.d.ts +7 -5
  13. package/dist/package.json +1 -15
  14. package/dist/public.d.ts +69 -0
  15. package/lib/alpha.d.ts +7 -5
  16. package/lib/beta.d.ts +6 -3
  17. package/lib/getPresence.d.ts +4 -9
  18. package/lib/getPresence.d.ts.map +1 -1
  19. package/lib/getPresence.js +6 -85
  20. package/lib/getPresence.js.map +1 -1
  21. package/lib/index.d.ts +4 -13
  22. package/lib/index.d.ts.map +1 -1
  23. package/lib/index.js +5 -4
  24. package/lib/index.js.map +1 -1
  25. package/lib/legacy.alpha.d.ts +7 -5
  26. package/lib/public.d.ts +69 -0
  27. package/package.json +31 -80
  28. package/dist/baseTypes.d.ts +0 -24
  29. package/dist/baseTypes.d.ts.map +0 -1
  30. package/dist/baseTypes.js +0 -7
  31. package/dist/baseTypes.js.map +0 -1
  32. package/dist/broadcastControls.d.ts +0 -37
  33. package/dist/broadcastControls.d.ts.map +0 -1
  34. package/dist/broadcastControls.js +0 -60
  35. package/dist/broadcastControls.js.map +0 -1
  36. package/dist/broadcastControlsTypes.d.ts +0 -38
  37. package/dist/broadcastControlsTypes.d.ts.map +0 -1
  38. package/dist/broadcastControlsTypes.js +0 -7
  39. package/dist/broadcastControlsTypes.js.map +0 -1
  40. package/dist/exposedInternalTypes.d.ts +0 -138
  41. package/dist/exposedInternalTypes.d.ts.map +0 -1
  42. package/dist/exposedInternalTypes.js +0 -19
  43. package/dist/exposedInternalTypes.js.map +0 -1
  44. package/dist/exposedUtilityTypes.d.ts +0 -108
  45. package/dist/exposedUtilityTypes.d.ts.map +0 -1
  46. package/dist/exposedUtilityTypes.js +0 -7
  47. package/dist/exposedUtilityTypes.js.map +0 -1
  48. package/dist/internalUtils.d.ts +0 -125
  49. package/dist/internalUtils.d.ts.map +0 -1
  50. package/dist/internalUtils.js +0 -99
  51. package/dist/internalUtils.js.map +0 -1
  52. package/dist/latestMapTypes.d.ts +0 -343
  53. package/dist/latestMapTypes.d.ts.map +0 -1
  54. package/dist/latestMapTypes.js +0 -7
  55. package/dist/latestMapTypes.js.map +0 -1
  56. package/dist/latestMapValueManager.d.ts +0 -52
  57. package/dist/latestMapValueManager.d.ts.map +0 -1
  58. package/dist/latestMapValueManager.js +0 -250
  59. package/dist/latestMapValueManager.js.map +0 -1
  60. package/dist/latestTypes.d.ts +0 -183
  61. package/dist/latestTypes.d.ts.map +0 -1
  62. package/dist/latestTypes.js +0 -7
  63. package/dist/latestTypes.js.map +0 -1
  64. package/dist/latestValueManager.d.ts +0 -10
  65. package/dist/latestValueManager.d.ts.map +0 -1
  66. package/dist/latestValueManager.js +0 -117
  67. package/dist/latestValueManager.js.map +0 -1
  68. package/dist/latestValueTypes.d.ts +0 -108
  69. package/dist/latestValueTypes.d.ts.map +0 -1
  70. package/dist/latestValueTypes.js +0 -7
  71. package/dist/latestValueTypes.js.map +0 -1
  72. package/dist/notificationsManager.d.ts +0 -27
  73. package/dist/notificationsManager.d.ts.map +0 -1
  74. package/dist/notificationsManager.js +0 -98
  75. package/dist/notificationsManager.js.map +0 -1
  76. package/dist/notificationsManagerTypes.d.ts +0 -144
  77. package/dist/notificationsManagerTypes.d.ts.map +0 -1
  78. package/dist/notificationsManagerTypes.js +0 -7
  79. package/dist/notificationsManagerTypes.js.map +0 -1
  80. package/dist/packageVersion.d.ts +0 -9
  81. package/dist/packageVersion.d.ts.map +0 -1
  82. package/dist/packageVersion.js +0 -12
  83. package/dist/packageVersion.js.map +0 -1
  84. package/dist/presence.d.ts +0 -229
  85. package/dist/presence.d.ts.map +0 -1
  86. package/dist/presence.js +0 -23
  87. package/dist/presence.js.map +0 -1
  88. package/dist/presenceDatastoreManager.d.ts +0 -149
  89. package/dist/presenceDatastoreManager.d.ts.map +0 -1
  90. package/dist/presenceDatastoreManager.js +0 -667
  91. package/dist/presenceDatastoreManager.js.map +0 -1
  92. package/dist/presenceManager.d.ts +0 -16
  93. package/dist/presenceManager.d.ts.map +0 -1
  94. package/dist/presenceManager.js +0 -140
  95. package/dist/presenceManager.js.map +0 -1
  96. package/dist/presenceStates.d.ts +0 -102
  97. package/dist/presenceStates.d.ts.map +0 -1
  98. package/dist/presenceStates.js +0 -231
  99. package/dist/presenceStates.js.map +0 -1
  100. package/dist/protocol.d.ts +0 -111
  101. package/dist/protocol.d.ts.map +0 -1
  102. package/dist/protocol.js +0 -20
  103. package/dist/protocol.js.map +0 -1
  104. package/dist/runtimeTypes.d.ts +0 -26
  105. package/dist/runtimeTypes.d.ts.map +0 -1
  106. package/dist/runtimeTypes.js +0 -7
  107. package/dist/runtimeTypes.js.map +0 -1
  108. package/dist/stateDatastore.d.ts +0 -56
  109. package/dist/stateDatastore.d.ts.map +0 -1
  110. package/dist/stateDatastore.js +0 -22
  111. package/dist/stateDatastore.js.map +0 -1
  112. package/dist/stateFactory.d.ts +0 -18
  113. package/dist/stateFactory.d.ts.map +0 -1
  114. package/dist/stateFactory.js +0 -23
  115. package/dist/stateFactory.js.map +0 -1
  116. package/dist/statesManagerTypes.d.ts +0 -27
  117. package/dist/statesManagerTypes.d.ts.map +0 -1
  118. package/dist/statesManagerTypes.js +0 -7
  119. package/dist/statesManagerTypes.js.map +0 -1
  120. package/dist/systemWorkspace.d.ts +0 -58
  121. package/dist/systemWorkspace.d.ts.map +0 -1
  122. package/dist/systemWorkspace.js +0 -265
  123. package/dist/systemWorkspace.js.map +0 -1
  124. package/dist/timerManager.d.ts +0 -37
  125. package/dist/timerManager.d.ts.map +0 -1
  126. package/dist/timerManager.js +0 -65
  127. package/dist/timerManager.js.map +0 -1
  128. package/dist/types.d.ts +0 -132
  129. package/dist/types.d.ts.map +0 -1
  130. package/dist/types.js +0 -7
  131. package/dist/types.js.map +0 -1
  132. package/dist/validatableTypes.d.ts +0 -69
  133. package/dist/validatableTypes.d.ts.map +0 -1
  134. package/dist/validatableTypes.js +0 -7
  135. package/dist/validatableTypes.js.map +0 -1
  136. package/dist/validatedGetter.d.ts +0 -18
  137. package/dist/validatedGetter.d.ts.map +0 -1
  138. package/dist/validatedGetter.js +0 -43
  139. package/dist/validatedGetter.js.map +0 -1
  140. package/dist/valueManager.d.ts +0 -15
  141. package/dist/valueManager.d.ts.map +0 -1
  142. package/dist/valueManager.js +0 -22
  143. package/dist/valueManager.js.map +0 -1
  144. package/lib/baseTypes.d.ts +0 -24
  145. package/lib/baseTypes.d.ts.map +0 -1
  146. package/lib/baseTypes.js +0 -6
  147. package/lib/baseTypes.js.map +0 -1
  148. package/lib/broadcastControls.d.ts +0 -37
  149. package/lib/broadcastControls.d.ts.map +0 -1
  150. package/lib/broadcastControls.js +0 -55
  151. package/lib/broadcastControls.js.map +0 -1
  152. package/lib/broadcastControlsTypes.d.ts +0 -38
  153. package/lib/broadcastControlsTypes.d.ts.map +0 -1
  154. package/lib/broadcastControlsTypes.js +0 -6
  155. package/lib/broadcastControlsTypes.js.map +0 -1
  156. package/lib/exposedInternalTypes.d.ts +0 -138
  157. package/lib/exposedInternalTypes.d.ts.map +0 -1
  158. package/lib/exposedInternalTypes.js +0 -16
  159. package/lib/exposedInternalTypes.js.map +0 -1
  160. package/lib/exposedUtilityTypes.d.ts +0 -108
  161. package/lib/exposedUtilityTypes.d.ts.map +0 -1
  162. package/lib/exposedUtilityTypes.js +0 -6
  163. package/lib/exposedUtilityTypes.js.map +0 -1
  164. package/lib/internalUtils.d.ts +0 -125
  165. package/lib/internalUtils.d.ts.map +0 -1
  166. package/lib/internalUtils.js +0 -90
  167. package/lib/internalUtils.js.map +0 -1
  168. package/lib/latestMapTypes.d.ts +0 -343
  169. package/lib/latestMapTypes.d.ts.map +0 -1
  170. package/lib/latestMapTypes.js +0 -6
  171. package/lib/latestMapTypes.js.map +0 -1
  172. package/lib/latestMapValueManager.d.ts +0 -52
  173. package/lib/latestMapValueManager.d.ts.map +0 -1
  174. package/lib/latestMapValueManager.js +0 -246
  175. package/lib/latestMapValueManager.js.map +0 -1
  176. package/lib/latestTypes.d.ts +0 -183
  177. package/lib/latestTypes.d.ts.map +0 -1
  178. package/lib/latestTypes.js +0 -6
  179. package/lib/latestTypes.js.map +0 -1
  180. package/lib/latestValueManager.d.ts +0 -10
  181. package/lib/latestValueManager.d.ts.map +0 -1
  182. package/lib/latestValueManager.js +0 -113
  183. package/lib/latestValueManager.js.map +0 -1
  184. package/lib/latestValueTypes.d.ts +0 -108
  185. package/lib/latestValueTypes.d.ts.map +0 -1
  186. package/lib/latestValueTypes.js +0 -6
  187. package/lib/latestValueTypes.js.map +0 -1
  188. package/lib/notificationsManager.d.ts +0 -27
  189. package/lib/notificationsManager.d.ts.map +0 -1
  190. package/lib/notificationsManager.js +0 -94
  191. package/lib/notificationsManager.js.map +0 -1
  192. package/lib/notificationsManagerTypes.d.ts +0 -144
  193. package/lib/notificationsManagerTypes.d.ts.map +0 -1
  194. package/lib/notificationsManagerTypes.js +0 -6
  195. package/lib/notificationsManagerTypes.js.map +0 -1
  196. package/lib/packageVersion.d.ts +0 -9
  197. package/lib/packageVersion.d.ts.map +0 -1
  198. package/lib/packageVersion.js +0 -9
  199. package/lib/packageVersion.js.map +0 -1
  200. package/lib/presence.d.ts +0 -229
  201. package/lib/presence.d.ts.map +0 -1
  202. package/lib/presence.js +0 -20
  203. package/lib/presence.js.map +0 -1
  204. package/lib/presenceDatastoreManager.d.ts +0 -149
  205. package/lib/presenceDatastoreManager.d.ts.map +0 -1
  206. package/lib/presenceDatastoreManager.js +0 -663
  207. package/lib/presenceDatastoreManager.js.map +0 -1
  208. package/lib/presenceManager.d.ts +0 -16
  209. package/lib/presenceManager.d.ts.map +0 -1
  210. package/lib/presenceManager.js +0 -136
  211. package/lib/presenceManager.js.map +0 -1
  212. package/lib/presenceStates.d.ts +0 -102
  213. package/lib/presenceStates.d.ts.map +0 -1
  214. package/lib/presenceStates.js +0 -225
  215. package/lib/presenceStates.js.map +0 -1
  216. package/lib/protocol.d.ts +0 -111
  217. package/lib/protocol.d.ts.map +0 -1
  218. package/lib/protocol.js +0 -17
  219. package/lib/protocol.js.map +0 -1
  220. package/lib/runtimeTypes.d.ts +0 -26
  221. package/lib/runtimeTypes.d.ts.map +0 -1
  222. package/lib/runtimeTypes.js +0 -6
  223. package/lib/runtimeTypes.js.map +0 -1
  224. package/lib/stateDatastore.d.ts +0 -56
  225. package/lib/stateDatastore.d.ts.map +0 -1
  226. package/lib/stateDatastore.js +0 -17
  227. package/lib/stateDatastore.js.map +0 -1
  228. package/lib/stateFactory.d.ts +0 -18
  229. package/lib/stateFactory.d.ts.map +0 -1
  230. package/lib/stateFactory.js +0 -20
  231. package/lib/stateFactory.js.map +0 -1
  232. package/lib/statesManagerTypes.d.ts +0 -27
  233. package/lib/statesManagerTypes.d.ts.map +0 -1
  234. package/lib/statesManagerTypes.js +0 -6
  235. package/lib/statesManagerTypes.js.map +0 -1
  236. package/lib/systemWorkspace.d.ts +0 -58
  237. package/lib/systemWorkspace.d.ts.map +0 -1
  238. package/lib/systemWorkspace.js +0 -261
  239. package/lib/systemWorkspace.js.map +0 -1
  240. package/lib/timerManager.d.ts +0 -37
  241. package/lib/timerManager.d.ts.map +0 -1
  242. package/lib/timerManager.js +0 -61
  243. package/lib/timerManager.js.map +0 -1
  244. package/lib/types.d.ts +0 -132
  245. package/lib/types.d.ts.map +0 -1
  246. package/lib/types.js +0 -6
  247. package/lib/types.js.map +0 -1
  248. package/lib/validatableTypes.d.ts +0 -69
  249. package/lib/validatableTypes.d.ts.map +0 -1
  250. package/lib/validatableTypes.js +0 -6
  251. package/lib/validatableTypes.js.map +0 -1
  252. package/lib/validatedGetter.d.ts +0 -18
  253. package/lib/validatedGetter.d.ts.map +0 -1
  254. package/lib/validatedGetter.js +0 -39
  255. package/lib/validatedGetter.js.map +0 -1
  256. package/lib/valueManager.d.ts +0 -15
  257. package/lib/valueManager.d.ts.map +0 -1
  258. package/lib/valueManager.js +0 -17
  259. package/lib/valueManager.js.map +0 -1
@@ -1,667 +0,0 @@
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 = exports.broadcastJoinResponseDelaysMs = void 0;
8
- const internal_1 = require("@fluidframework/core-utils/internal");
9
- const internalUtils_js_1 = require("./internalUtils.js");
10
- const presenceStates_js_1 = require("./presenceStates.js");
11
- const protocol_js_1 = require("./protocol.js");
12
- const timerManager_js_1 = require("./timerManager.js");
13
- const internalWorkspaceTypes = {
14
- s: "States",
15
- n: "Notifications",
16
- };
17
- const knownMessageTypes = new Set([
18
- protocol_js_1.joinMessageType,
19
- protocol_js_1.datastoreUpdateMessageType,
20
- protocol_js_1.acknowledgementMessageType,
21
- ]);
22
- function isPresenceMessage(message) {
23
- return knownMessageTypes.has(message.type);
24
- }
25
- /**
26
- * Type guard to check if a value hierarchy object is a directory (has "items"
27
- * property).
28
- *
29
- * @param obj - The object to check
30
- * @returns True if the object is a {@link ValidatableValueDirectory}
31
- */
32
- function isValueDirectory(obj) {
33
- return "items" in obj;
34
- }
35
- function mergeGeneralDatastoreMessageContent(base, newData) {
36
- // This function-local "datastore" will hold the merged message data.
37
- const queueDatastore = base ?? {};
38
- // Merge the current data with the existing data, if any exists.
39
- // Iterate over the current message data; individual items are workspaces.
40
- for (const [workspaceName, workspaceData] of (0, internalUtils_js_1.objectEntries)(newData)) {
41
- // Initialize the merged data as the queued datastore entry for the workspace.
42
- // Since the key might not exist, create an empty object in that case. It will
43
- // be set explicitly after the loop.
44
- const mergedData = queueDatastore[workspaceName] ?? {};
45
- // Iterate over each value manager and its data, merging it as needed.
46
- for (const [valueManagerKey, valueManagerValue] of (0, internalUtils_js_1.objectEntries)(workspaceData)) {
47
- for (const [attendeeId, value] of (0, internalUtils_js_1.objectEntries)(valueManagerValue)) {
48
- const mergeObject = (mergedData[valueManagerKey] ??= {});
49
- const oldData = mergeObject[attendeeId];
50
- mergeObject[attendeeId] = (0, presenceStates_js_1.mergeValueDirectory)(oldData, value, 0);
51
- }
52
- }
53
- // Store the merged data in the function-local queue workspace. The whole contents of this
54
- // datastore will be sent as the message data.
55
- queueDatastore[workspaceName] = mergedData;
56
- }
57
- return queueDatastore;
58
- }
59
- /**
60
- * Delays used for broadcasting join responses to clients.
61
- *
62
- * @remarks
63
- * Exported for test coordination.
64
- * These could be made customizable in the future to accommodate different
65
- * session configurations.
66
- */
67
- exports.broadcastJoinResponseDelaysMs = {
68
- /**
69
- * The delay in milliseconds before a join response is sent to any client.
70
- * This is used to accumulate other join response requests and reduce
71
- * network traffic.
72
- */
73
- namedResponder: 200,
74
- /**
75
- * The additional delay in milliseconds a backup responder waits before sending
76
- * a join response to allow others to respond first.
77
- */
78
- backupResponderIncrement: 40,
79
- };
80
- /**
81
- * Manages singleton datastore for all Presence.
82
- */
83
- class PresenceDatastoreManagerImpl {
84
- constructor(attendeeId, runtime, logger, events, presence, systemWorkspaceDatastore, systemWorkspace) {
85
- this.attendeeId = attendeeId;
86
- this.runtime = runtime;
87
- this.logger = logger;
88
- this.events = events;
89
- this.presence = presence;
90
- this.averageLatency = 0;
91
- this.returnedMessages = 0;
92
- this.sendMessageTimer = new timerManager_js_1.TimerManager();
93
- this.workspaces = new Map();
94
- /**
95
- * Map of outstanding broadcast (join response) requests.
96
- */
97
- this.broadcastRequests = new Map();
98
- /**
99
- * Timer for managing broadcast (join response) request timing.
100
- */
101
- this.broadcastRequestsTimer = new timerManager_js_1.TimerManager();
102
- /**
103
- * Broadcasts a join response (complete datastore update message)
104
- * if there is an outstanding join response request.
105
- */
106
- this.sendJoinResponseIfStillNeeded = () => {
107
- // Make sure we are currently connected and a broadcast is still needed.
108
- // If not connected, nothing we can do.
109
- if (this.runtime.getJoinedStatus() !== "disconnected" && this.broadcastRequests.size > 0) {
110
- // Confirm that of remaining requests, now is the time to respond.
111
- const now = Date.now();
112
- let minResponseTime = Number.POSITIVE_INFINITY;
113
- for (const { deadlineTime } of this.broadcastRequests.values()) {
114
- minResponseTime = Math.min(minResponseTime, deadlineTime);
115
- }
116
- if (minResponseTime <= now) {
117
- if (this.reasonForCompleteSnapshot) {
118
- this.broadcastAllKnownState();
119
- }
120
- }
121
- else {
122
- // No response needed yet - schedule a later attempt
123
- this.broadcastRequestsTimer.setTimeout(this.sendJoinResponseIfStillNeeded, minResponseTime - now);
124
- }
125
- }
126
- };
127
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
128
- this.datastore = { "system:presence": systemWorkspaceDatastore };
129
- this.workspaces.set("system:presence", systemWorkspace);
130
- this.targetedSignalSupport = this.runtime.supportedFeatures.has("submit_signals_v2");
131
- // If audience member is removed, they won't need a broadcast response.
132
- this.runtime.getAudience().on("removeMember", (clientId) => {
133
- this.broadcastRequests.delete(clientId);
134
- });
135
- }
136
- getAudienceInformation(selfClientId) {
137
- const audience = this.runtime.getAudience();
138
- const members = audience.getMembers();
139
- const all = new Set();
140
- const writers = new Set();
141
- const selfPresent = members.has(selfClientId);
142
- if (selfPresent) {
143
- // Remove self
144
- members.delete(selfClientId);
145
- }
146
- // Gather interactive client IDs
147
- for (const [id, client] of members) {
148
- if (client.details.capabilities.interactive) {
149
- all.add(id);
150
- if (client.mode === "write") {
151
- writers.add(id);
152
- }
153
- }
154
- }
155
- return {
156
- selfPresent,
157
- interactiveMembersExcludingSelf: {
158
- all,
159
- writers,
160
- },
161
- };
162
- }
163
- joinSession(selfClientId, alternateProvider = undefined) {
164
- // Before broadcasting the join message, check that there is at least
165
- // one audience member present (self or another). This is useful to
166
- // optimize join messages while not using targeted join responses.
167
- // (We need at least one other to be able to elect them as update
168
- // provider.)
169
- // Lack of anyone likely means that this client is very freshly joined
170
- // and has not received any Join Signals (type="join") from the service
171
- // yet.
172
- const { selfPresent, interactiveMembersExcludingSelf } = this.getAudienceInformation(selfClientId);
173
- if (selfPresent) {
174
- if (interactiveMembersExcludingSelf.all.size === 0) {
175
- // If there aren't any members connected except self, then this client
176
- // must have complete information.
177
- this.reasonForCompleteSnapshot = "alone";
178
- // It would be possible to return at this time and skip ClientJoin
179
- // signal. Instead continue in case audience information is
180
- // inaccurate. This client might temporarily erroneously believe it
181
- // has complete information, but the other(s) should respond to
182
- // ClientJoin soon rectifying that and covering for bad incomplete
183
- // responses this client sent in the meantime.
184
- }
185
- }
186
- else {
187
- // When self is not represented, audience is an unreliable state,
188
- // especially during a reconnect. An alternateProvider is expected
189
- // to have been provided for this call to be useful (efficient).
190
- (0, internal_1.assert)(alternateProvider !== undefined, 0xcba /* Self is not in audience and no alternateProvider given */);
191
- }
192
- // Broadcast join message to all clients
193
- // Select primary update providers
194
- // Use write members if any, then fallback to read-only members.
195
- const updateProviders = [
196
- ...(interactiveMembersExcludingSelf.writers.size > 0
197
- ? interactiveMembersExcludingSelf.writers
198
- : interactiveMembersExcludingSelf.all),
199
- ];
200
- // Limit to three providers to prevent flooding the network.
201
- // If none respond, others present will (should) after a delay.
202
- if (updateProviders.length > 3) {
203
- updateProviders.length = 3;
204
- }
205
- else if (updateProviders.length === 0 && alternateProvider !== undefined) {
206
- updateProviders.push(alternateProvider);
207
- }
208
- this.runtime.submitSignal({
209
- type: protocol_js_1.joinMessageType,
210
- content: {
211
- sendTimestamp: Date.now(),
212
- avgLatency: this.averageLatency,
213
- data: this.stripValidationMetadata(this.datastore),
214
- updateProviders,
215
- },
216
- });
217
- this.logger.sendTelemetryEvent({
218
- eventName: "JoinRequested",
219
- details: {
220
- attendeeId: this.attendeeId,
221
- connectionId: selfClientId,
222
- // Empty updateProviders is indicative of join when alone.
223
- updateProviders: JSON.stringify(updateProviders),
224
- // If false and providers is single entry, then join was probably forced.
225
- selfPresent,
226
- },
227
- });
228
- }
229
- onDisconnected() {
230
- delete this.reasonForCompleteSnapshot;
231
- }
232
- getWorkspace(internalWorkspaceAddress, requestedContent, controls) {
233
- const existing = this.workspaces.get(internalWorkspaceAddress);
234
- if (existing) {
235
- return existing.internal.ensureContent(requestedContent, controls);
236
- }
237
- const workspaceDatastore = (this.datastore[internalWorkspaceAddress] ??= {});
238
- const localUpdate = (states, options) => {
239
- // Check for connectivity before sending updates.
240
- if (this.runtime.getJoinedStatus() === "disconnected") {
241
- return;
242
- }
243
- const updates = {};
244
- for (const [key, value] of Object.entries(states)) {
245
- updates[key] = { [this.attendeeId]: value };
246
- }
247
- this.enqueueMessage({
248
- [internalWorkspaceAddress]: updates,
249
- }, options);
250
- };
251
- const entry = (0, presenceStates_js_1.createPresenceStates)({
252
- presence: this.presence,
253
- attendeeId: this.attendeeId,
254
- localUpdate,
255
- }, workspaceDatastore, requestedContent, controls);
256
- this.workspaces.set(internalWorkspaceAddress, entry);
257
- return entry.public;
258
- }
259
- /**
260
- * Enqueues a new message to be sent. The message may be queued or may be sent immediately depending on the state of
261
- * the send timer, other messages in the queue, the configured allowed latency, etc.
262
- */
263
- enqueueMessage(data, options) {
264
- if (this.queuedData !== "sendAll") {
265
- this.queuedData =
266
- data === "sendAll"
267
- ? "sendAll"
268
- : // Merging the message with any queued messages effectively queues the message.
269
- // It is OK to queue all incoming messages as long as when we send, we send the queued data.
270
- mergeGeneralDatastoreMessageContent(this.queuedData, data);
271
- }
272
- const { allowableUpdateLatencyMs } = options;
273
- const now = Date.now();
274
- const thisMessageDeadline = now + allowableUpdateLatencyMs;
275
- if (
276
- // If the timer has not expired, we can short-circuit because the timer will fire
277
- // and cover this update. In other words, queuing this will be fast enough to
278
- // meet its deadline, because a timer is already scheduled to fire before its deadline.
279
- !this.sendMessageTimer.hasExpired() &&
280
- // If the deadline for this message is later than the overall send deadline, then
281
- // we can exit early since a timer will take care of sending it.
282
- thisMessageDeadline >= this.sendMessageTimer.expireTime) {
283
- return;
284
- }
285
- // Either we need to send this message immediately, or we need to schedule a timer
286
- // to fire at the send deadline that will take care of it.
287
- // Note that timeoutInMs === allowableUpdateLatencyMs, but the calculation is done this way for clarity.
288
- const timeoutInMs = thisMessageDeadline - now;
289
- const scheduleForLater = timeoutInMs > 0;
290
- if (scheduleForLater) {
291
- // Schedule the queued messages to be sent at the updateDeadline
292
- this.sendMessageTimer.setTimeout(this.sendQueuedMessage.bind(this), timeoutInMs);
293
- }
294
- else {
295
- this.sendQueuedMessage();
296
- }
297
- }
298
- /**
299
- * Send any queued signal immediately. Does nothing if no message is queued.
300
- */
301
- sendQueuedMessage() {
302
- this.sendMessageTimer.clearTimeout();
303
- if (this.queuedData === undefined) {
304
- return;
305
- }
306
- // Check for connectivity before sending updates.
307
- if (this.runtime.getJoinedStatus() === "disconnected") {
308
- // Clear the queued data since we're disconnected. We don't want messages
309
- // to queue infinitely while disconnected.
310
- this.queuedData = undefined;
311
- return;
312
- }
313
- if (this.queuedData === "sendAll") {
314
- this.broadcastAllKnownState();
315
- return;
316
- }
317
- const clientConnectionId = this.runtime.getClientId();
318
- (0, internal_1.assert)(clientConnectionId !== undefined, 0xa59 /* Client connected without clientId */);
319
- const currentClientToSessionValueState =
320
- // When connected, `clientToSessionId` must always have current connection entry.
321
- this.datastore["system:presence"].clientToSessionId[clientConnectionId];
322
- (0, internal_1.assert)(currentClientToSessionValueState !== undefined, 0xcbb /* Client connection update missing */);
323
- const newMessage = {
324
- sendTimestamp: Date.now(),
325
- avgLatency: this.averageLatency,
326
- // isComplete: false,
327
- data: {
328
- // Always send current connection mapping for some resiliency against
329
- // lost signals. This ensures that client session id found in `updates`
330
- // (which is this client's client session id) is always represented in
331
- // system workspace of recipient clients.
332
- "system:presence": {
333
- clientToSessionId: {
334
- [clientConnectionId]: { ...currentClientToSessionValueState },
335
- },
336
- },
337
- ...this.queuedData,
338
- },
339
- };
340
- this.queuedData = undefined;
341
- this.runtime.submitSignal({ type: protocol_js_1.datastoreUpdateMessageType, content: newMessage });
342
- }
343
- /**
344
- * Recursively strips validation metadata (validatedValue) from datastore before broadcasting.
345
- * This ensures that validation metadata doesn't leak into signals sent to other clients.
346
- */
347
- stripValidationMetadata(datastore) {
348
- const messageContent = {
349
- ["system:presence"]: datastore["system:presence"],
350
- };
351
- for (const [workspaceAddress, workspace] of (0, internalUtils_js_1.objectEntries)(datastore)) {
352
- // System workspace has no validation metadata and is already
353
- // set in messageContent; so, it can be skipped.
354
- if (workspaceAddress === "system:presence")
355
- continue;
356
- const workspaceData = {};
357
- for (const [stateName, clientRecord] of (0, internalUtils_js_1.objectEntries)(workspace)) {
358
- const cleanClientRecord = {};
359
- for (const [attendeeId, valueData] of (0, internalUtils_js_1.objectEntries)(clientRecord)) {
360
- cleanClientRecord[attendeeId] = this.stripValidationFromValueData(valueData);
361
- }
362
- workspaceData[stateName] = cleanClientRecord;
363
- }
364
- messageContent[workspaceAddress] = workspaceData;
365
- }
366
- return messageContent;
367
- }
368
- /**
369
- * Strips validation metadata from individual value data entries.
370
- */
371
- stripValidationFromValueData(valueDataIn) {
372
- // Clone the input object since we may mutate it
373
- const valueData = { ...valueDataIn };
374
- // Handle directory structures (with "items" property)
375
- if (isValueDirectory(valueData)) {
376
- for (const [key, item] of Object.entries(valueData.items)) {
377
- valueData.items[key] = this.stripValidationFromValueData(item);
378
- }
379
- // This `satisfies` test is rather weak while ValidatableValueDirectory
380
- // only has optional properties over InternalTypes.ValueDirectory and
381
- // thus readily does satisfy. If `validatedValue?: never` is uncommented
382
- // in Value*State then this will fail.
383
- valueData;
384
- return valueData;
385
- }
386
- delete valueData.validatedValue;
387
- // This `satisfies` test is rather weak while Validatable*State
388
- // only has optional properties over InternalTypes.Value*State and
389
- // thus readily does satisfy. If `validatedValue?: never` is uncommented
390
- // in Value*State then this will fail.
391
- valueData;
392
- return valueData;
393
- }
394
- broadcastAllKnownState() {
395
- const content = {
396
- sendTimestamp: Date.now(),
397
- avgLatency: this.averageLatency,
398
- isComplete: true,
399
- data: this.stripValidationMetadata(this.datastore),
400
- };
401
- const primaryRequestors = [];
402
- const secondaryRequestors = [];
403
- if (this.broadcastRequests.size > 0) {
404
- content.joinResponseFor = [...this.broadcastRequests.keys()];
405
- // Build telemetry data
406
- for (const [requestor, { responseOrder }] of this.broadcastRequests.entries()) {
407
- if (responseOrder === undefined) {
408
- primaryRequestors.push(requestor);
409
- }
410
- else {
411
- secondaryRequestors.push([requestor, responseOrder]);
412
- }
413
- }
414
- this.broadcastRequests.clear();
415
- }
416
- // This broadcast will satisfy all requests; clear any remaining timer.
417
- this.broadcastRequestsTimer.clearTimeout();
418
- this.sendMessageTimer.clearTimeout();
419
- this.runtime.submitSignal({
420
- type: protocol_js_1.datastoreUpdateMessageType,
421
- content,
422
- });
423
- if (content.joinResponseFor) {
424
- this.logger.sendTelemetryEvent({
425
- eventName: "JoinResponse",
426
- details: {
427
- type: "broadcastAll",
428
- attendeeId: this.attendeeId,
429
- connectionId: this.runtime.getClientId(),
430
- primaryResponses: JSON.stringify(primaryRequestors),
431
- secondaryResponses: JSON.stringify(secondaryRequestors),
432
- },
433
- });
434
- }
435
- // Sending all must account for anything queued before.
436
- this.queuedData = undefined;
437
- }
438
- processSignal(message, local, optional) {
439
- const received = Date.now();
440
- if (!isPresenceMessage(message)) {
441
- (0, internal_1.assert)(optional, 0xcbc /* Unrecognized message type in critical message */);
442
- return;
443
- }
444
- if (local) {
445
- const deliveryDelta = received - message.content.sendTimestamp;
446
- // Limit returnedMessages count to 256 such that newest message
447
- // always contributes at least 1/256th to the average. Older
448
- // messages have more weight, but that diminishes as new messages
449
- // contribute.
450
- this.returnedMessages = Math.min(this.returnedMessages + 1, 256);
451
- this.averageLatency =
452
- (this.averageLatency * (this.returnedMessages - 1) + deliveryDelta) /
453
- this.returnedMessages;
454
- return;
455
- }
456
- const timeModifier = received -
457
- (this.averageLatency + message.content.avgLatency + message.content.sendTimestamp);
458
- const postUpdateActions = [];
459
- if (message.type === protocol_js_1.joinMessageType) {
460
- // It is possible for some signals to come in while client is not connected due
461
- // to how work is scheduled. If we are not connected, we can't respond to the
462
- // join request. We will make our own Join request once we are connected.
463
- if (this.runtime.getJoinedStatus() !== "disconnected") {
464
- this.prepareJoinResponse(message.content.updateProviders, message.clientId);
465
- }
466
- // It is okay to continue processing the contained updates even if we are not
467
- // connected.
468
- }
469
- else {
470
- // Update join response requests that are now satisfied.
471
- const joinResponseFor = message.content.joinResponseFor;
472
- if (joinResponseFor) {
473
- const selfClientId = this.runtime.getClientId();
474
- (0, internal_1.assert)(selfClientId !== undefined, 0xcbd /* Received signal without clientId */);
475
- let justGainedCompleteSnapshot = false;
476
- if (joinResponseFor.includes(selfClientId)) {
477
- if (this.reasonForCompleteSnapshot) {
478
- if (this.reasonForCompleteSnapshot === "alone") {
479
- // No response was expected. This might happen when
480
- // either cautionary ClientJoin signal is received
481
- // by audience member that was unknown.
482
- this.logger.sendTelemetryEvent({
483
- eventName: "JoinResponseWhenAlone",
484
- details: {
485
- attendeeId: this.attendeeId,
486
- connectionId: this.runtime.getClientId(),
487
- },
488
- });
489
- }
490
- }
491
- else {
492
- // If we are the intended recipient of the join response,
493
- // we can consider our knowledge complete and can respond
494
- // to others join requests.
495
- justGainedCompleteSnapshot = true;
496
- }
497
- this.reasonForCompleteSnapshot = "join response";
498
- }
499
- if (this.broadcastRequests.size > 0) {
500
- for (const responseFor of joinResponseFor) {
501
- this.broadcastRequests.delete(responseFor);
502
- }
503
- if (this.broadcastRequests.size === 0) {
504
- // If no more requests are pending, clear any timer.
505
- this.broadcastRequestsTimer.clearTimeout();
506
- }
507
- else if (justGainedCompleteSnapshot) {
508
- // May or may not be time to respond to remaining requests.
509
- // Clear the timer and recheck after processing.
510
- this.broadcastRequestsTimer.clearTimeout();
511
- postUpdateActions.push(this.sendJoinResponseIfStillNeeded);
512
- }
513
- }
514
- }
515
- // If the message requests an acknowledgement, we will send a targeted acknowledgement message back to just the requestor.
516
- if (message.content.acknowledgementId !== undefined) {
517
- (0, internal_1.assert)(this.targetedSignalSupport, 0xcbe /* Acknowledgment message was requested while targeted signal capability is not supported */);
518
- this.runtime.submitSignal({
519
- type: protocol_js_1.acknowledgementMessageType,
520
- content: { id: message.content.acknowledgementId },
521
- targetClientId: message.clientId,
522
- });
523
- }
524
- }
525
- // Handle activation of unregistered workspaces before processing updates.
526
- for (const [workspaceAddress] of (0, internalUtils_js_1.objectEntries)(message.content.data)) {
527
- // The first part of OR condition checks if workspace is already registered.
528
- // The second part checks if the workspace has already been seen before.
529
- // In either case we can skip emitting 'workspaceActivated' event.
530
- if (this.workspaces.has(workspaceAddress) || this.datastore[workspaceAddress]) {
531
- continue;
532
- }
533
- // Separate internal type prefix from public workspace address
534
- const match = /^([^:]):([^:]+:.+)$/.exec(workspaceAddress);
535
- if (match === null) {
536
- continue;
537
- }
538
- const prefix = match[1];
539
- const publicWorkspaceAddress = match[2];
540
- const internalWorkspaceType = internalWorkspaceTypes[prefix] ?? "Unknown";
541
- this.events.emit("workspaceActivated", publicWorkspaceAddress, internalWorkspaceType);
542
- }
543
- // While the system workspace is processed here too, it is declared as
544
- // conforming to the general schema. So drop its override.
545
- const data = message.content.data;
546
- for (const [workspaceAddress, remoteDatastore] of (0, internalUtils_js_1.objectEntries)(data)) {
547
- // Direct to the appropriate Presence Workspace, if present.
548
- const workspace = this.workspaces.get(workspaceAddress);
549
- if (workspace) {
550
- postUpdateActions.push(...workspace.internal.processUpdate(received, timeModifier, remoteDatastore, message.clientId));
551
- }
552
- else {
553
- // All broadcast state is kept even if not currently registered, unless a value
554
- // notes itself to be ignored.
555
- // Ensure there is a datastore at this address and get it.
556
- const workspaceDatastore = (this.datastore[workspaceAddress] ??= {});
557
- for (const [key, remoteAllKnownState] of Object.entries(remoteDatastore)) {
558
- (0, presenceStates_js_1.mergeUntrackedDatastore)(key, remoteAllKnownState, workspaceDatastore, timeModifier);
559
- }
560
- }
561
- }
562
- for (const action of postUpdateActions) {
563
- action();
564
- }
565
- }
566
- /**
567
- * Handles responding to another client joining the session.
568
- *
569
- * @param updateProviders - list of client connection id's that requestor selected
570
- * to provide response
571
- * @param requestor - `requestor` is only used in telemetry. While it is the requestor's
572
- * client connection id, that is not most important. It is important that this is a
573
- * unique shared id across all clients that might respond as we want to monitor the
574
- * response patterns. The convenience of being client connection id will allow
575
- * correlation with other telemetry where it is often called just `clientId`.
576
- */
577
- prepareJoinResponse(updateProviders, requestor) {
578
- // We must be connected to receive this message, so clientId should be defined.
579
- // If it isn't then, not really a problem; just won't be in provider or audience list.
580
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
581
- const selfClientId = this.runtime.getClientId();
582
- let joinResponseDelayMs = exports.broadcastJoinResponseDelaysMs.namedResponder;
583
- let relativeResponseOrder;
584
- if (!updateProviders.includes(selfClientId)) {
585
- // Schedule a broadcast to the new client after a delay only to send if
586
- // another broadcast satisfying the request hasn't been seen in the
587
- // meantime. The delay is based on the position in the quorum list. It
588
- // doesn't have to be a stable list across all clients. We need
589
- // something to provide suggested order to prevent a flood of broadcasts.
590
- const quorumMembers = this.runtime.getQuorum().getMembers();
591
- const self = quorumMembers.get(selfClientId);
592
- if (self) {
593
- // Compute order quorum join order (indicated by sequenceNumber)
594
- relativeResponseOrder = 0;
595
- for (const { client, sequenceNumber } of quorumMembers.values()) {
596
- if (sequenceNumber < self.sequenceNumber &&
597
- client.details.capabilities.interactive) {
598
- relativeResponseOrder++;
599
- }
600
- }
601
- }
602
- else {
603
- // Order past quorum members + arbitrary additional offset up to 10
604
- let possibleQuorumRespondents = 0;
605
- for (const { client } of quorumMembers.values()) {
606
- if (client.details.capabilities.interactive) {
607
- possibleQuorumRespondents++;
608
- }
609
- }
610
- relativeResponseOrder = possibleQuorumRespondents + Math.random() * 10;
611
- }
612
- // When not named to provide update, wait an additional amount
613
- // of time for those named or others to respond.
614
- joinResponseDelayMs +=
615
- exports.broadcastJoinResponseDelaysMs.backupResponderIncrement *
616
- (3 * updateProviders.length + relativeResponseOrder);
617
- }
618
- // Add the requestor to the list of clients that will receive the broadcast.
619
- const deadlineTime = Date.now() + joinResponseDelayMs;
620
- this.broadcastRequests.set(requestor, {
621
- deadlineTime,
622
- responseOrder: relativeResponseOrder,
623
- });
624
- if (!this.reasonForCompleteSnapshot) {
625
- // Check if requestor count meets or exceeds count of other audience
626
- // members indicating that we effectively have a complete snapshot
627
- // (once the current message being processed is processed).
628
- const { selfPresent, interactiveMembersExcludingSelf } = this.getAudienceInformation(selfClientId);
629
- if (
630
- // Self-present check is done to help ensure that audience
631
- // information is accurate. If self is not present, audience
632
- // information might be incomplete.
633
- selfPresent &&
634
- this.broadcastRequests.size >= interactiveMembersExcludingSelf.all.size) {
635
- // Note that no action is taken here specifically.
636
- // We want action to be queued so that it takes place after
637
- // current message is completely processed. All of the actions
638
- // below should be delayed (not immediate).
639
- this.reasonForCompleteSnapshot = "full requests";
640
- }
641
- }
642
- // Check if capable of full primary response. If requested to provide
643
- // primary response, but do not yet have complete snapshot, we need to
644
- // delay a full response, until we think we have complete snapshot. In
645
- // the meantime we will send partial updates as usual.
646
- if (this.reasonForCompleteSnapshot && updateProviders.includes(selfClientId)) {
647
- // Use regular message queue to handle timing of the broadcast.
648
- // Any more immediate broadcasts will accelerate the response time.
649
- // As a primary responder, it is expected that broadcast will happen and
650
- // using the regular queue allows other updates to avoid merge work.
651
- this.enqueueMessage("sendAll", {
652
- allowableUpdateLatencyMs: joinResponseDelayMs,
653
- });
654
- }
655
- else {
656
- // Check if there isn't already a timer scheduled to send a join
657
- // response with in this request's deadline.
658
- if (this.broadcastRequestsTimer.hasExpired() ||
659
- deadlineTime < this.broadcastRequestsTimer.expireTime) {
660
- // Set or update the timer.
661
- this.broadcastRequestsTimer.setTimeout(this.sendJoinResponseIfStillNeeded, joinResponseDelayMs);
662
- }
663
- }
664
- }
665
- }
666
- exports.PresenceDatastoreManagerImpl = PresenceDatastoreManagerImpl;
667
- //# sourceMappingURL=presenceDatastoreManager.js.map