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