@fluid-internal/presence-runtime 2.102.0 → 2.110.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 (85) hide show
  1. package/dist/packageVersion.d.ts +1 -1
  2. package/dist/packageVersion.js +1 -1
  3. package/dist/packageVersion.js.map +1 -1
  4. package/dist/runtime/extension/containerPresence.d.ts +1 -1
  5. package/dist/runtime/presenceDatastoreManager.d.ts +2 -2
  6. package/dist/runtime/presenceDatastoreManager.d.ts.map +1 -1
  7. package/dist/runtime/presenceDatastoreManager.js.map +1 -1
  8. package/dist/runtime/presenceManager.js.map +1 -1
  9. package/lib/packageVersion.d.ts +1 -1
  10. package/lib/packageVersion.js +1 -1
  11. package/lib/packageVersion.js.map +1 -1
  12. package/lib/runtime/extension/containerPresence.d.ts +1 -1
  13. package/lib/runtime/presenceDatastoreManager.d.ts +2 -2
  14. package/lib/runtime/presenceDatastoreManager.d.ts.map +1 -1
  15. package/lib/runtime/presenceDatastoreManager.js.map +1 -1
  16. package/lib/runtime/presenceManager.js.map +1 -1
  17. package/package.json +15 -15
  18. package/dist/runtime/test/presenceDatastoreManager.spec.js +0 -618
  19. package/dist/runtime/test/presenceDatastoreManager.spec.js.map +0 -1
  20. package/dist/runtime/test/presenceManager.spec.js +0 -651
  21. package/dist/runtime/test/presenceManager.spec.js.map +0 -1
  22. package/dist/states/test/batching.spec.js +0 -843
  23. package/dist/states/test/batching.spec.js.map +0 -1
  24. package/dist/states/test/broadcastControlsTests.js +0 -60
  25. package/dist/states/test/broadcastControlsTests.js.map +0 -1
  26. package/dist/states/test/eventing.spec.js +0 -576
  27. package/dist/states/test/eventing.spec.js.map +0 -1
  28. package/dist/states/test/latestMapValueManager.spec.js +0 -210
  29. package/dist/states/test/latestMapValueManager.spec.js.map +0 -1
  30. package/dist/states/test/latestValueManager.spec.js +0 -193
  31. package/dist/states/test/latestValueManager.spec.js.map +0 -1
  32. package/dist/states/test/mockEphemeralRuntime.js +0 -11
  33. package/dist/states/test/mockEphemeralRuntime.js.map +0 -1
  34. package/dist/states/test/notificationsManager.spec.js +0 -460
  35. package/dist/states/test/notificationsManager.spec.js.map +0 -1
  36. package/dist/states/test/presenceStates.spec.js +0 -73
  37. package/dist/states/test/presenceStates.spec.js.map +0 -1
  38. package/dist/states/test/schemaValidation/protocol.spec.js +0 -246
  39. package/dist/states/test/schemaValidation/protocol.spec.js.map +0 -1
  40. package/dist/states/test/schemaValidation/valueManagers.spec.js +0 -784
  41. package/dist/states/test/schemaValidation/valueManagers.spec.js.map +0 -1
  42. package/dist/states/test/testUtils.js +0 -21
  43. package/dist/states/test/testUtils.js.map +0 -1
  44. package/dist/test/mockEphemeralRuntime.js +0 -175
  45. package/dist/test/mockEphemeralRuntime.js.map +0 -1
  46. package/dist/test/testUtils.js +0 -262
  47. package/dist/test/testUtils.js.map +0 -1
  48. package/dist/test/utils/index.js +0 -27
  49. package/dist/test/utils/index.js.map +0 -1
  50. package/dist/utils/test/timerManager.spec.js +0 -93
  51. package/dist/utils/test/timerManager.spec.js.map +0 -1
  52. package/lib/runtime/test/presenceDatastoreManager.spec.js +0 -616
  53. package/lib/runtime/test/presenceDatastoreManager.spec.js.map +0 -1
  54. package/lib/runtime/test/presenceManager.spec.js +0 -649
  55. package/lib/runtime/test/presenceManager.spec.js.map +0 -1
  56. package/lib/states/test/batching.spec.js +0 -841
  57. package/lib/states/test/batching.spec.js.map +0 -1
  58. package/lib/states/test/broadcastControlsTests.js +0 -56
  59. package/lib/states/test/broadcastControlsTests.js.map +0 -1
  60. package/lib/states/test/eventing.spec.js +0 -574
  61. package/lib/states/test/eventing.spec.js.map +0 -1
  62. package/lib/states/test/latestMapValueManager.spec.js +0 -206
  63. package/lib/states/test/latestMapValueManager.spec.js.map +0 -1
  64. package/lib/states/test/latestValueManager.spec.js +0 -189
  65. package/lib/states/test/latestValueManager.spec.js.map +0 -1
  66. package/lib/states/test/mockEphemeralRuntime.js +0 -6
  67. package/lib/states/test/mockEphemeralRuntime.js.map +0 -1
  68. package/lib/states/test/notificationsManager.spec.js +0 -456
  69. package/lib/states/test/notificationsManager.spec.js.map +0 -1
  70. package/lib/states/test/presenceStates.spec.js +0 -69
  71. package/lib/states/test/presenceStates.spec.js.map +0 -1
  72. package/lib/states/test/schemaValidation/protocol.spec.js +0 -244
  73. package/lib/states/test/schemaValidation/protocol.spec.js.map +0 -1
  74. package/lib/states/test/schemaValidation/valueManagers.spec.js +0 -782
  75. package/lib/states/test/schemaValidation/valueManagers.spec.js.map +0 -1
  76. package/lib/states/test/testUtils.js +0 -6
  77. package/lib/states/test/testUtils.js.map +0 -1
  78. package/lib/test/mockEphemeralRuntime.js +0 -171
  79. package/lib/test/mockEphemeralRuntime.js.map +0 -1
  80. package/lib/test/testUtils.js +0 -251
  81. package/lib/test/testUtils.js.map +0 -1
  82. package/lib/test/utils/index.js +0 -8
  83. package/lib/test/utils/index.js.map +0 -1
  84. package/lib/utils/test/timerManager.spec.js +0 -91
  85. package/lib/utils/test/timerManager.spec.js.map +0 -1
@@ -1,649 +0,0 @@
1
- /*!
2
- * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
- * Licensed under the MIT License.
4
- */
5
- import { strict as assert } from "node:assert";
6
- import { AttendeeStatus } from "@fluid-internal/presence-definitions";
7
- import { EventAndErrorTrackingLogger } from "@fluidframework/test-utils/internal";
8
- import { useFakeTimers } from "sinon";
9
- import { createPresenceManager } from "@fluid-internal/presence-runtime/internal/test";
10
- import { assertFinalExpectations, initialLocalClientConnectionId, localAttendeeId, createSpecificAttendeeId, generateBasicClientJoin, MockEphemeralRuntime, prepareConnectedPresence, prepareDisconnectedPresence, } from "@fluid-internal/presence-runtime/internal/test-utils";
11
- const collateralSessionId = createSpecificAttendeeId("collateral-id");
12
- /**
13
- * Mock {@link ClientConnectionId} for the local client after first reconnection.
14
- */
15
- const rejoinedLocalClientConnectionId1 = "localClientRejoin1";
16
- /**
17
- * Mock {@link ClientConnectionId} for the local client after second reconnection.
18
- */
19
- const rejoinedLocalClientConnectionId2 = "localClientRejoin2";
20
- function verify(value, message) {
21
- assert(value !== undefined, message ?? "Expected value to be defined");
22
- return value;
23
- }
24
- describe("Presence/Runtime", () => {
25
- describe("PresenceManager", () => {
26
- let runtime;
27
- let logger;
28
- const initialTime = 1000;
29
- let clock;
30
- before(async () => {
31
- clock = useFakeTimers();
32
- });
33
- beforeEach(() => {
34
- logger = new EventAndErrorTrackingLogger();
35
- runtime = new MockEphemeralRuntime(logger);
36
- clock.setSystemTime(initialTime);
37
- });
38
- afterEach(function (done) {
39
- clock.reset();
40
- // If the test passed so far, check final expectations.
41
- if (this.currentTest?.state === "passed") {
42
- assertFinalExpectations(runtime, logger);
43
- }
44
- done();
45
- });
46
- after(() => {
47
- clock.restore();
48
- });
49
- it("can be created", () => {
50
- // Act & Verify (does not throw)
51
- createPresenceManager(runtime);
52
- });
53
- it("creation logs initialization event", () => {
54
- // Setup
55
- logger.registerExpectedEvent({ eventName: "Presence:PresenceInstantiated" });
56
- // Act
57
- createPresenceManager(runtime);
58
- // Verify
59
- assertFinalExpectations(runtime, logger);
60
- });
61
- it("throws when unknown attendee is requested via `getAttendee`", () => {
62
- // Setup
63
- const presence = createPresenceManager(runtime);
64
- // Act & Verify
65
- assert.throws(() => presence.attendees.getAttendee("unknown"), /Attendee not found/);
66
- });
67
- describe("self attendee", () => {
68
- it("is announced via `attendeeConnected` upon connect while self is in audience", () => {
69
- // Setup - create presence in disconnected state
70
- const { presence, connect } = prepareDisconnectedPresence(runtime, localAttendeeId, initialLocalClientConnectionId, clock, logger);
71
- const connectedAttendees = [];
72
- presence.attendees.events.on("attendeeConnected", (attendee) => {
73
- connectedAttendees.push(attendee);
74
- });
75
- // Act - connect
76
- connect();
77
- // Verify - self attendee was announced
78
- const selfAttendee = presence.attendees.getMyself();
79
- assert.strictEqual(connectedAttendees.length, 1, "Expected exactly one attendee to be announced");
80
- assert.strictEqual(connectedAttendees[0], selfAttendee, "Expected self attendee to be announced");
81
- });
82
- it('has status "Disconnected" when presence initializes while container is disconnected', () => {
83
- // Setup - create presence while container is disconnected
84
- const { presence } = prepareDisconnectedPresence(runtime, localAttendeeId, initialLocalClientConnectionId, clock, logger);
85
- // Verify - self attendee has "Disconnected" status
86
- const selfAttendee = presence.attendees.getMyself();
87
- assert.strictEqual(selfAttendee.getConnectionStatus(), AttendeeStatus.Disconnected, "Self attendee should have status 'Disconnected' when presence initializes while container is disconnected");
88
- });
89
- it('has status "Disconnected" when runtime is connected but self is not yet in Audience', () => {
90
- // Setup - set runtime to connected state but remove self from audience
91
- runtime.clientId = initialLocalClientConnectionId;
92
- runtime.joined = true;
93
- runtime.removeMember(initialLocalClientConnectionId);
94
- // Create presence - it will see runtime as connected but self not in audience
95
- logger.registerExpectedEvent({ eventName: "Presence:PresenceInstantiated" });
96
- const presence = createPresenceManager(runtime, localAttendeeId);
97
- // Verify - self attendee should have "Disconnected" status since
98
- // we haven't received the ClientJoin signal yet
99
- const selfAttendee = presence.attendees.getMyself();
100
- assert.strictEqual(selfAttendee.getConnectionStatus(), AttendeeStatus.Disconnected, "Self attendee should have status 'Disconnected' when runtime is connected but self is not yet in Audience");
101
- assertFinalExpectations(runtime, logger);
102
- });
103
- it("is announced via `attendeeConnected` when added to Audience while runtime is connected", () => {
104
- // Setup - set runtime to connected state but remove self from audience
105
- runtime.clientId = initialLocalClientConnectionId;
106
- runtime.joined = true;
107
- runtime.removeMember(initialLocalClientConnectionId);
108
- // Create presence - it will see runtime as connected but self not in audience
109
- logger.registerExpectedEvent({ eventName: "Presence:PresenceInstantiated" });
110
- const presence = createPresenceManager(runtime, localAttendeeId);
111
- // Verify initial state - self is Disconnected
112
- const selfAttendee = presence.attendees.getMyself();
113
- assert.strictEqual(selfAttendee.getConnectionStatus(), AttendeeStatus.Disconnected, "Self attendee should initially have status 'Disconnected'");
114
- // Listen for attendeeConnected
115
- const connectedAttendees = [];
116
- presence.attendees.events.on("attendeeConnected", (attendee) => {
117
- connectedAttendees.push(attendee);
118
- });
119
- // Expect join signal when self is added to audience while connected
120
- const expectedJoin = generateBasicClientJoin(clock.now, {
121
- attendeeId: localAttendeeId,
122
- clientConnectionId: initialLocalClientConnectionId,
123
- updateProviders: ["client0", "client1", "client3"],
124
- });
125
- delete expectedJoin.clientId;
126
- runtime.signalsExpected.push([expectedJoin]);
127
- // Act - self added to audience
128
- runtime.audience.addMember(initialLocalClientConnectionId, {
129
- mode: "write",
130
- details: { capabilities: { interactive: true } },
131
- permission: [],
132
- user: { id: "test-user" },
133
- scopes: [],
134
- });
135
- // Verify - attendeeConnected was raised for self with Connected status
136
- assert.strictEqual(connectedAttendees.length, 1, "Expected exactly one attendee to be announced");
137
- assert.strictEqual(connectedAttendees[0], selfAttendee, "Expected self attendee to be announced");
138
- assert.strictEqual(selfAttendee.getConnectionStatus(), AttendeeStatus.Connected, "Self attendee should have status 'Connected' after being added to Audience");
139
- assertFinalExpectations(runtime, logger);
140
- });
141
- it('has status "Connected" when announced via `attendeeConnected`', () => {
142
- // Setup - create presence in disconnected state
143
- const { presence, connect } = prepareDisconnectedPresence(runtime, localAttendeeId, initialLocalClientConnectionId, clock, logger);
144
- let attendeeConnectedRaised = false;
145
- presence.attendees.events.on("attendeeConnected", (attendee) => {
146
- if (attendee === presence.attendees.getMyself()) {
147
- attendeeConnectedRaised = true;
148
- assert.strictEqual(attendee.getConnectionStatus(), AttendeeStatus.Connected, "Self attendee should have status 'Connected' when announced");
149
- }
150
- });
151
- // Act - connect
152
- connect();
153
- // Verify - attendeeConnected was raised
154
- assert(attendeeConnectedRaised, "attendeeConnected event should be raised for self attendee");
155
- });
156
- it("is announced via `attendeeDisconnected` when local client disconnects", () => {
157
- // Setup - create presence in disconnected state and connect
158
- const { presence, connect } = prepareDisconnectedPresence(runtime, localAttendeeId, initialLocalClientConnectionId, clock, logger);
159
- connect();
160
- const disconnectedAttendees = [];
161
- presence.attendees.events.on("attendeeDisconnected", (attendee) => {
162
- disconnectedAttendees.push(attendee);
163
- });
164
- // Act - disconnect
165
- runtime.disconnect();
166
- // Verify - self attendee was announced as disconnected
167
- const selfAttendee = presence.attendees.getMyself();
168
- assert.strictEqual(disconnectedAttendees.length, 1, "Expected exactly one attendee to be announced as disconnected");
169
- assert.strictEqual(disconnectedAttendees[0], selfAttendee, "Expected self attendee to be announced as disconnected");
170
- assert.strictEqual(selfAttendee.getConnectionStatus(), AttendeeStatus.Disconnected, "Self attendee should have status 'Disconnected' after disconnect");
171
- });
172
- it("is announced via `attendeeConnected` when local client reconnects", () => {
173
- // Setup - create presence in disconnected state, connect and then disconnect
174
- const { presence, connect } = prepareDisconnectedPresence(runtime, localAttendeeId, initialLocalClientConnectionId, clock, logger);
175
- // Initial connection
176
- connect();
177
- // Disconnect
178
- runtime.disconnect();
179
- // Ignore submitted signals for reconnection
180
- runtime.submitSignal = () => { };
181
- const connectedAttendees = [];
182
- presence.attendees.events.on("attendeeConnected", (attendee) => {
183
- connectedAttendees.push(attendee);
184
- });
185
- // Act - reconnect with new connection id
186
- runtime.connect(rejoinedLocalClientConnectionId1, initialLocalClientConnectionId);
187
- // Verify - self attendee was announced
188
- const selfAttendee = presence.attendees.getMyself();
189
- assert.strictEqual(connectedAttendees.length, 1, "Expected exactly one attendee to be announced on reconnect");
190
- assert.strictEqual(connectedAttendees[0], selfAttendee, "Expected self attendee to be announced on reconnect");
191
- assert.strictEqual(selfAttendee.getConnectionStatus(), AttendeeStatus.Connected, "Self attendee should have status 'Connected' after reconnect");
192
- });
193
- });
194
- describe("when connected", () => {
195
- let presence;
196
- let processSignal;
197
- const afterCleanUp = [];
198
- beforeEach(() => {
199
- ({ presence, processSignal } = prepareConnectedPresence(runtime, localAttendeeId, initialLocalClientConnectionId, clock, logger));
200
- });
201
- afterEach(() => {
202
- for (const cleanUp of afterCleanUp) {
203
- cleanUp();
204
- }
205
- afterCleanUp.length = 0;
206
- });
207
- describe("attendee", () => {
208
- const attendeeSessionId = "attendeeId-4";
209
- const initialAttendeeConnectionId = "client4";
210
- // Note: this connection id exists in the mock runtime audience since
211
- // initialization, but should go unnoticed by the presence manager
212
- // until there is a join signal related to it.
213
- const rejoinAttendeeConnectionId = "client7";
214
- let initialAttendeeSignal;
215
- let rejoinAttendeeSignal;
216
- // Processes join signals and returns the attendees that were announced via `attendeeConnected`
217
- function processJoinSignals(signals) {
218
- const joinedAttendees = [];
219
- const cleanUpListener = presence.attendees.events.on("attendeeConnected", (attendee) => {
220
- joinedAttendees.push(attendee);
221
- });
222
- for (const signal of signals) {
223
- processSignal([], signal, false);
224
- }
225
- cleanUpListener();
226
- return joinedAttendees;
227
- }
228
- function verifyAttendee(actualAttendee, expectedConnectionId, expectedSessionId, expectedConnectionStatus = AttendeeStatus.Connected) {
229
- assert.equal(actualAttendee.attendeeId, expectedSessionId, "Attendee has wrong session id");
230
- assert.equal(actualAttendee.getConnectionId(), expectedConnectionId, "Attendee has wrong client connection id");
231
- assert.equal(actualAttendee.getConnectionStatus(), expectedConnectionStatus, `Attendee connection status is not ${expectedConnectionStatus}`);
232
- }
233
- beforeEach(() => {
234
- // Ignore submitted signals
235
- runtime.submitSignal = () => { };
236
- initialAttendeeSignal = generateBasicClientJoin(clock.now - 50, {
237
- averageLatency: 50,
238
- attendeeId: attendeeSessionId,
239
- clientConnectionId: initialAttendeeConnectionId,
240
- updateProviders: [initialLocalClientConnectionId],
241
- });
242
- rejoinAttendeeSignal = generateBasicClientJoin(clock.now - 20, {
243
- averageLatency: 20,
244
- attendeeId: attendeeSessionId, // Same session id
245
- clientConnectionId: rejoinAttendeeConnectionId, // Different connection id
246
- connectionOrder: 1,
247
- updateProviders: [initialLocalClientConnectionId],
248
- priorClientToSessionId: initialAttendeeSignal.content.data["system:presence"].clientToSessionId,
249
- });
250
- });
251
- it("is not announced via `attendeeDisconnected` when unknown connection is removed", () => {
252
- // Setup
253
- presence.attendees.events.on("attendeeDisconnected", () => {
254
- assert.fail("`attendeeDisconnected` should not be emitted for unknown connection.");
255
- });
256
- // Act & Verify - remove connection unknown to presence
257
- runtime.removeMember("client5");
258
- });
259
- describe("that is joining", () => {
260
- it('first time is announced via `attendeeConnected` with status "Connected"', () => {
261
- // Act - simulate join message from client
262
- const joinedAttendees = processJoinSignals([initialAttendeeSignal]);
263
- // Verify
264
- assert.strictEqual(joinedAttendees.length, 1, "Expected exactly one attendee to be announced");
265
- verifyAttendee(joinedAttendees[0], initialAttendeeConnectionId, attendeeSessionId);
266
- });
267
- it('second time is announced once via `attendeeConnected` with status "Connected" when prior is unknown', () => {
268
- // Setup
269
- const priorClientData = verify(runtime.getAudience().getMember(initialAttendeeConnectionId));
270
- runtime.removeMember(initialAttendeeConnectionId);
271
- runtime.audience.addMember(rejoinAttendeeConnectionId, priorClientData);
272
- // Act - simulate join message from client
273
- const joinedAttendees = processJoinSignals([rejoinAttendeeSignal]);
274
- // Verify
275
- assert.strictEqual(joinedAttendees.length, 1, "Expected exactly one attendee to be announced");
276
- verifyAttendee(joinedAttendees[0], rejoinAttendeeConnectionId, attendeeSessionId);
277
- });
278
- it('second time is announced once via `attendeeConnected` with status "Connected" when prior is still connected', () => {
279
- const initialClientData = verify(runtime.getAudience().getMember(initialAttendeeConnectionId));
280
- runtime.audience.addMember(rejoinAttendeeConnectionId, initialClientData);
281
- // Act - simulate join message from client
282
- const joinedAttendees = processJoinSignals([rejoinAttendeeSignal]);
283
- // Verify
284
- assert.strictEqual(joinedAttendees.length, 1, "Expected exactly one attendee to be announced");
285
- verifyAttendee(joinedAttendees[0], rejoinAttendeeConnectionId, attendeeSessionId);
286
- });
287
- it('first time is announced via `attendeeConnected` with status "Connected" even if unknown to audience', () => {
288
- // Setup - remove connection from audience
289
- runtime.removeMember(initialAttendeeConnectionId);
290
- // Act - simulate join message from client
291
- const joinedAttendees = processJoinSignals([initialAttendeeSignal]);
292
- // Verify
293
- assert.strictEqual(joinedAttendees.length, 1, "Expected exactly one attendee to be announced");
294
- verifyAttendee(joinedAttendees[0], initialAttendeeConnectionId, attendeeSessionId);
295
- });
296
- it('second time is announced once via `attendeeConnected` with status "Connected" even if most recent unknown to audience', () => {
297
- // Setup - remove connection from audience
298
- runtime.removeMember(rejoinAttendeeConnectionId);
299
- // Act - simulate join message from client
300
- const joinedAttendees = processJoinSignals([rejoinAttendeeSignal]);
301
- assert.strictEqual(joinedAttendees.length, 1, "Expected exactly one attendee to be announced");
302
- verifyAttendee(joinedAttendees[0], rejoinAttendeeConnectionId, attendeeSessionId);
303
- });
304
- it("as collateral and disconnected is NOT announced via `attendeeConnected`", () => {
305
- // Setup - remove connections from audience
306
- const collateralAttendeeConnectionId = "client3";
307
- const collateralAttendeeSignal = generateBasicClientJoin(clock.now - 10, {
308
- averageLatency: 40,
309
- attendeeId: attendeeSessionId,
310
- clientConnectionId: rejoinAttendeeConnectionId,
311
- connectionOrder: 1,
312
- updateProviders: [initialLocalClientConnectionId],
313
- priorClientToSessionId: {
314
- ...initialAttendeeSignal.content.data["system:presence"].clientToSessionId,
315
- [collateralAttendeeConnectionId]: {
316
- rev: 0,
317
- timestamp: 0,
318
- value: collateralSessionId,
319
- },
320
- },
321
- });
322
- runtime.removeMember(initialAttendeeConnectionId);
323
- runtime.removeMember(collateralAttendeeConnectionId);
324
- // Act - simulate join message from client
325
- const joinedAttendees = processJoinSignals([collateralAttendeeSignal]);
326
- // Verify - only the rejoining attendee is announced
327
- assert.strictEqual(joinedAttendees.length, 1, "Expected exactly one attendee to be announced");
328
- verifyAttendee(joinedAttendees[0], rejoinAttendeeConnectionId, attendeeSessionId);
329
- });
330
- it("as collateral with old connection info and connected is NOT announced via `attendeeConnected`", () => {
331
- // Setup - generate signals
332
- // Both connection Id's unknown to audience
333
- const oldAttendeeConnectionId = "client9";
334
- const newAttendeeConnectionId = "client10";
335
- // Rejoin signal for the collateral attendee unknown to audience
336
- const rejoinSignal = generateBasicClientJoin(clock.now - 10, {
337
- averageLatency: 40,
338
- attendeeId: collateralSessionId,
339
- clientConnectionId: newAttendeeConnectionId,
340
- updateProviders: [initialAttendeeConnectionId],
341
- connectionOrder: 1,
342
- priorClientToSessionId: {
343
- [oldAttendeeConnectionId]: {
344
- rev: 0,
345
- timestamp: 0,
346
- value: collateralSessionId,
347
- },
348
- },
349
- });
350
- // Response signal sent by the initial attendee responding to the collateral attendees rejoin signal
351
- const responseSignal = generateBasicClientJoin(clock.now - 5, {
352
- averageLatency: 20,
353
- attendeeId: attendeeSessionId,
354
- clientConnectionId: initialAttendeeConnectionId,
355
- priorClientToSessionId: {
356
- ...initialAttendeeSignal.content.data["system:presence"].clientToSessionId,
357
- // Old connection id of rejoining attendee
358
- // This should be ignored by local client
359
- [oldAttendeeConnectionId]: {
360
- rev: 0,
361
- timestamp: 0,
362
- value: collateralSessionId,
363
- },
364
- },
365
- });
366
- // Process initial join signal so initial attendee is known
367
- const joinedAttendees = processJoinSignals([initialAttendeeSignal]);
368
- assert.strictEqual(joinedAttendees.length, 1, "Expected exactly one attendee to be announced");
369
- // Simulate rejoin message from remote client
370
- const rejoinAttendees = processJoinSignals([rejoinSignal]);
371
- // Confirm that rejoining attendee is announced so we can verify it remains the same after response
372
- assert.strictEqual(rejoinAttendees.length, 1, "Expected exactly one attendee to be announced");
373
- // Act - simulate response message from remote client
374
- const responseAttendees = processJoinSignals([responseSignal]);
375
- // Verify - No collateral attendee should be announced by response signal and rejoined attendee information should remain unchanged
376
- assert.strictEqual(responseAttendees.length, 0, "Expected no attendees to be announced");
377
- // Check attendee information remains unchanged
378
- verifyAttendee(rejoinAttendees[0], newAttendeeConnectionId, collateralSessionId);
379
- });
380
- });
381
- describe("that is already known", () => {
382
- let knownAttendee;
383
- beforeEach(() => {
384
- // Setup known attendee
385
- const joinedAttendees = processJoinSignals([initialAttendeeSignal]);
386
- assert(joinedAttendees.length === 1, "Expected exactly one attendee to be announced");
387
- knownAttendee = joinedAttendees[0];
388
- });
389
- it('is NOT announced when "rejoined" with same connection (duplicate signal)', () => {
390
- afterCleanUp.push(presence.attendees.events.on("attendeeConnected", (attendee) => {
391
- assert.fail("Attendee should not be announced when rejoining with same connection");
392
- }));
393
- clock.tick(10);
394
- // Act & Verify - simulate duplicate join message from client
395
- processJoinSignals([initialAttendeeSignal]);
396
- });
397
- // To retain symmetry across Joined and Disconnected events, do not announce
398
- // attendeeConnected when the attendee is already connected and we only see
399
- // a connection id update. This can happen when audience removal is late.
400
- it('is not announced via `attendeeConnected` when already "Connected"', () => {
401
- // Setup
402
- afterCleanUp.push(presence.attendees.events.on("attendeeConnected", () => {
403
- assert.fail("No attendee should be announced in join processing");
404
- }));
405
- // Act & Verify - simulate rejoin message from client
406
- processJoinSignals([rejoinAttendeeSignal]);
407
- });
408
- for (const [status, setup] of [
409
- [AttendeeStatus.Connected, () => { }],
410
- [
411
- AttendeeStatus.Disconnected,
412
- () => runtime.removeMember(initialAttendeeConnectionId),
413
- ],
414
- ]) {
415
- for (const [desc, id] of [
416
- ["connection id", initialAttendeeConnectionId],
417
- ["session id", attendeeSessionId],
418
- ]) {
419
- it(`with status "${status}" is available from \`getAttendee\` by ${desc}`, () => {
420
- // Setup
421
- setup();
422
- // Act
423
- const attendee = presence.attendees.getAttendee(id);
424
- // Verify
425
- assert.equal(attendee, knownAttendee, "`getAttendee` returned wrong attendee");
426
- assert.equal(attendee.getConnectionStatus(), status, "`getAttendee` returned attendee with wrong status");
427
- });
428
- }
429
- it(`with status "${status}" is available from \`getAttendees\``, () => {
430
- // Setup
431
- assert(knownAttendee !== undefined, "No attendee was set in beforeEach");
432
- setup();
433
- // Act
434
- const attendees = presence.attendees.getAttendees();
435
- assert(attendees.has(knownAttendee), "`getAttendees` set does not contain attendee");
436
- assert.equal(knownAttendee.getConnectionStatus(), status, "`getAttendees` set contains attendee with wrong status");
437
- });
438
- }
439
- // When local client disconnects, we lose the connectivity status updates for remote attendees in the session.
440
- // Upon reconnect, we mark all remote attendees connections as "stale".
441
- // Remote attendees with stale connections are given 30 seconds after local reconnection to prove they are connected
442
- // (e.g. being in audience, sending an update, or (re)joining the session) before their connection status set to "Disconnected".
443
- // If an attendee with a stale connection becomes active, their "stale" status is removed.
444
- describe("and then local client disconnects", () => {
445
- let remoteDisconnectedAttendees;
446
- beforeEach(() => {
447
- // Setup
448
- assert(knownAttendee !== undefined, "No attendee was set in beforeEach");
449
- remoteDisconnectedAttendees = [];
450
- afterCleanUp.push(presence.attendees.events.on("attendeeDisconnected", (attendee) => {
451
- if (attendee !== presence.attendees.getMyself()) {
452
- remoteDisconnectedAttendees.push(attendee);
453
- }
454
- }));
455
- });
456
- it("updates status of attendee with stale connection after 30s delay upon local reconnection", () => {
457
- assert(knownAttendee !== undefined, "No attendee was set in beforeEach");
458
- // Act - disconnect & reconnect local client
459
- runtime.disconnect(); // Simulate local client disconnect
460
- clock.tick(1000);
461
- // Simulate remote client disconnect (while local is disconnected)
462
- runtime.audience.removeMember(knownAttendee.getConnectionId());
463
- runtime.connect(rejoinedLocalClientConnectionId1, initialLocalClientConnectionId); // Simulate local client reconnect with new connection id
464
- // Verify - attendee with stale connection should still be 'Connected' after 15 seconds
465
- clock.tick(15_001);
466
- assert.strictEqual(knownAttendee.getConnectionStatus(), AttendeeStatus.Connected, "Attendee with stale connection should still be 'Connected' after 15s");
467
- // Verify - attendee with stale connection should be 'Disconnected' after 30 seconds and announced via `attendeeDisconnected`
468
- clock.tick(15_001);
469
- assert.strictEqual(knownAttendee.getConnectionStatus(), AttendeeStatus.Disconnected, "Attendee with stale connection should be 'Disconnected' 30s after reconnection");
470
- assert.strictEqual(remoteDisconnectedAttendees.length, 1, "Exactly one attendee should be announced as disconnected");
471
- });
472
- it("does not update status of attendee with stale connection if local client does not reconnect", () => {
473
- assert(knownAttendee !== undefined, "No attendee was set in beforeEach");
474
- // Act - disconnect local client and advance timer
475
- runtime.disconnect();
476
- clock.tick(600_000);
477
- // Verify - attendee with stale connection should still be 'Connected' if local client never reconnects
478
- assert.strictEqual(knownAttendee.getConnectionStatus(), AttendeeStatus.Connected, "Attendee with stale connection should still be 'Connected' after 30s");
479
- });
480
- it("does not update status of attendee with stale connection if local client reconnection lasts less than 30s", () => {
481
- assert(knownAttendee !== undefined, "No attendee was set in beforeEach");
482
- // Act - disconnect, reconnect for 15 second, disconnect local client again, then advance timer
483
- runtime.disconnect(); // First disconnect
484
- clock.tick(1000);
485
- runtime.connect(rejoinedLocalClientConnectionId1, initialLocalClientConnectionId); // Reconnect
486
- clock.tick(15_000); // Advance 15 seconds
487
- runtime.disconnect(); // Disconnect again
488
- clock.tick(600_000); // Advance 10 minutes
489
- // Verify - attendee with stale connection should still be 'Connected' if local client never reconnects for at least 30s
490
- assert.strictEqual(knownAttendee.getConnectionStatus(), AttendeeStatus.Connected, "Attendee with stale connection should still be 'Connected' after 30s");
491
- });
492
- it("does not update status of attendee with stale connection if attendee rejoins", () => {
493
- assert(knownAttendee !== undefined, "No attendee was set in beforeEach");
494
- // Setup - fail if non-self attendee joined is announced
495
- afterCleanUp.push(presence.attendees.events.on("attendeeConnected", (attendee) => {
496
- // Self attendee will be announced on reconnect, which is expected
497
- assert(attendee === presence.attendees.getMyself(), "No `attendeeConnected` should be announced for rejoining attendee that's already 'Connected'");
498
- }));
499
- // Act - disconnect, reconnect, process rejoin signal from known attendee after 15s, then advance timer
500
- runtime.disconnect();
501
- clock.tick(1000);
502
- runtime.connect(rejoinedLocalClientConnectionId1, initialLocalClientConnectionId);
503
- clock.tick(15_000);
504
- processJoinSignals([rejoinAttendeeSignal]);
505
- clock.tick(600_000);
506
- // Verify - rejoining attendee should still be 'Connected' with no `attendeeConnected` announced
507
- assert.strictEqual(knownAttendee.getConnectionStatus(), AttendeeStatus.Connected, "Active attendee should still be 'Connected' 30s after reconnection");
508
- });
509
- it("does not update status of attendee with stale connection if attendee sends datastore update", () => {
510
- assert(knownAttendee !== undefined, "No attendee was set in beforeEach");
511
- // Setup - fail if non-self attendee joined is announced
512
- afterCleanUp.push(presence.attendees.events.on("attendeeConnected", (attendee) => {
513
- // Self attendee will be announced on reconnect, which is expected
514
- assert(attendee === presence.attendees.getMyself(), "No `attendeeConnected` should be announced for active attendee that's already 'Connected'");
515
- }));
516
- // Act - disconnect, reconnect, process datatstore update signal from known attendee before 30s delay, then advance timer
517
- runtime.disconnect();
518
- clock.tick(1000);
519
- runtime.connect(rejoinedLocalClientConnectionId1, initialLocalClientConnectionId);
520
- clock.tick(15_000);
521
- processSignal([], {
522
- type: "Pres:DatastoreUpdate",
523
- content: {
524
- sendTimestamp: clock.now - 10,
525
- avgLatency: 20,
526
- data: {
527
- "system:presence": {
528
- clientToSessionId: initialAttendeeSignal.content.data["system:presence"]
529
- .clientToSessionId,
530
- },
531
- },
532
- },
533
- clientId: initialAttendeeConnectionId,
534
- }, false);
535
- clock.tick(600_000);
536
- // Verify - active attendee should still be 'Connected'
537
- assert.strictEqual(knownAttendee.getConnectionStatus(), AttendeeStatus.Connected, "Active attendee should still be 'Connected' 30s after reconnection");
538
- });
539
- it("announces `attendeeDisconnected` once when remote client disconnects after local client reconnects", () => {
540
- assert(knownAttendee !== undefined, "No attendee was set in beforeEach");
541
- // Setup - initial attendee joins before local client disconnects
542
- processJoinSignals([initialAttendeeSignal]);
543
- // Act - disconnect, reconnect, remove remote client connection, then advance timer
544
- runtime.disconnect();
545
- clock.tick(1000);
546
- runtime.connect(rejoinedLocalClientConnectionId1, initialLocalClientConnectionId);
547
- clock.tick(15_001);
548
- runtime.audience.removeMember(initialAttendeeConnectionId); // Remove remote client connection before 30s timeout
549
- // Confirm that `attendeeDisconnected` is announced for when active attendee disconnects
550
- assert.strictEqual(remoteDisconnectedAttendees.length, 1, "Exactly one attendee should be announced as disconnected");
551
- clock.tick(600_000);
552
- // Verify - active attendee status should be 'Disconnected' and no other `attendeeDisconnected` should be announced.
553
- assert.strictEqual(knownAttendee.getConnectionStatus(), AttendeeStatus.Disconnected, "Attendee should be 'Disconnected'");
554
- assert.strictEqual(remoteDisconnectedAttendees.length, 1, "Exactly one attendee should be announced as disconnected");
555
- });
556
- it("updates status of attendee with stale connection only 30s after most recent local reconnection", () => {
557
- // Setup
558
- assert(knownAttendee !== undefined, "No attendee was set in beforeEach");
559
- assert.strictEqual(knownAttendee.getConnectionStatus(), AttendeeStatus.Connected, "Known attendee is not connected");
560
- // Act - disconnect & reconnect local client multiple times with 15s delay
561
- runtime.disconnect();
562
- runtime.audience.removeMember(knownAttendee.getConnectionId()); // Simulate remote client disconnect (while local is disconnected)
563
- clock.tick(1000);
564
- runtime.connect(rejoinedLocalClientConnectionId1, initialLocalClientConnectionId);
565
- clock.tick(15_001);
566
- runtime.disconnect();
567
- clock.tick(1000);
568
- runtime.connect(rejoinedLocalClientConnectionId2, rejoinedLocalClientConnectionId1);
569
- // Verify - attendee with stale connection should still be connected after 15 seconds
570
- clock.tick(15_001);
571
- assert.strictEqual(knownAttendee.getConnectionStatus(), AttendeeStatus.Connected, "Attendee with stale connection should still be connected");
572
- // Verify - attendee with stale connection should be disconnected after 30 seconds
573
- clock.tick(15_001);
574
- assert.equal(knownAttendee.getConnectionStatus(), AttendeeStatus.Disconnected, "Attendee with stale connection has wrong status");
575
- assert.strictEqual(remoteDisconnectedAttendees.length, 1, "Exactly one attendee should be announced as disconnected");
576
- });
577
- });
578
- describe("and has their connection removed", () => {
579
- it("is announced via `attendeeDisconnected`", () => {
580
- // Setup
581
- assert(knownAttendee !== undefined, "No attendee was set in beforeEach");
582
- let disconnectedAttendee;
583
- afterCleanUp.push(presence.attendees.events.on("attendeeDisconnected", (attendee) => {
584
- assert(disconnectedAttendee === undefined, "Only one attendee should be disconnected");
585
- disconnectedAttendee = attendee;
586
- }));
587
- // Act - remove client connection id
588
- runtime.removeMember(initialAttendeeConnectionId);
589
- // Verify
590
- assert(disconnectedAttendee !== undefined, "No attendee was disconnected during `removeMember`");
591
- verifyAttendee(disconnectedAttendee, initialAttendeeConnectionId, attendeeSessionId, AttendeeStatus.Disconnected);
592
- });
593
- it('is not announced via `attendeeDisconnected` when already "Disconnected"', () => {
594
- // Setup
595
- const clientToDisconnect = runtime.audience.getMember(initialAttendeeConnectionId);
596
- assert(clientToDisconnect !== undefined, "No client to disconnect");
597
- // Remove client connection id
598
- runtime.removeMember(initialAttendeeConnectionId);
599
- afterCleanUp.push(presence.attendees.events.on("attendeeDisconnected", (attendee) => {
600
- assert.fail("`attendeeDisconnected` should not be emitted for already disconnected attendee");
601
- }));
602
- // Act & Verify - fake event to remove client connection id again
603
- runtime.audience.emit("removeMember", initialAttendeeConnectionId, clientToDisconnect);
604
- });
605
- });
606
- });
607
- describe("that is rejoining", () => {
608
- let priorClientData;
609
- let priorAttendee;
610
- beforeEach(() => {
611
- priorClientData = verify(runtime.getAudience().getMember(initialAttendeeConnectionId));
612
- // Setup prior attendee
613
- const joinedAttendees = processJoinSignals([initialAttendeeSignal]);
614
- assert(joinedAttendees.length === 1 && joinedAttendees[0] !== undefined, "Expected exactly one attendee to be announced");
615
- priorAttendee = joinedAttendees[0];
616
- // Disconnect the attendee
617
- runtime.removeMember(initialAttendeeConnectionId);
618
- });
619
- it("is NOT announced when rejoined with same connection (duplicate signal)", () => {
620
- // Setup
621
- afterCleanUp.push(presence.attendees.events.on("attendeeConnected", (attendee) => {
622
- assert.fail("Attendee should not be announced when rejoining with same connection");
623
- }));
624
- clock.tick(10);
625
- // Act & Verify - simulate duplicate join message from client
626
- processJoinSignals([initialAttendeeSignal]);
627
- });
628
- it("is announced when rejoined with different connection and current information is updated", () => {
629
- // Setup
630
- assert(priorAttendee !== undefined, "No attendee was set in beforeEach");
631
- clock.tick(20);
632
- // Act - add to audience and simulate new join message from same client (without disconnect)
633
- runtime.audience.addMember(rejoinAttendeeConnectionId, priorClientData);
634
- processJoinSignals([rejoinAttendeeSignal]);
635
- // Verify - session id is unchanged and connection id is updated
636
- verifyAttendee(priorAttendee, rejoinAttendeeConnectionId, attendeeSessionId);
637
- // Attendee is available via new connection id
638
- const attendeeViaUpdatedId = presence.attendees.getAttendee(rejoinAttendeeConnectionId);
639
- assert.equal(attendeeViaUpdatedId, priorAttendee, "getAttendee returned wrong attendee for updated connection id");
640
- // Attendee is available via old connection id
641
- const attendeeViaOriginalId = presence.attendees.getAttendee(initialAttendeeConnectionId);
642
- assert.equal(attendeeViaOriginalId, priorAttendee, "getAttendee returned wrong attendee for original connection id");
643
- });
644
- });
645
- });
646
- });
647
- });
648
- });
649
- //# sourceMappingURL=presenceManager.spec.js.map