matrix-js-sdk 37.2.0 → 37.3.0-rc.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 (83) hide show
  1. package/git-revision.txt +1 -1
  2. package/lib/client.d.ts +8 -2
  3. package/lib/client.d.ts.map +1 -1
  4. package/lib/client.js +11 -3
  5. package/lib/client.js.map +1 -1
  6. package/lib/common-crypto/CryptoBackend.d.ts +8 -0
  7. package/lib/common-crypto/CryptoBackend.d.ts.map +1 -1
  8. package/lib/common-crypto/CryptoBackend.js.map +1 -1
  9. package/lib/http-api/errors.d.ts +8 -0
  10. package/lib/http-api/errors.d.ts.map +1 -1
  11. package/lib/http-api/errors.js +14 -0
  12. package/lib/http-api/errors.js.map +1 -1
  13. package/lib/http-api/fetch.d.ts.map +1 -1
  14. package/lib/http-api/fetch.js +3 -3
  15. package/lib/http-api/fetch.js.map +1 -1
  16. package/lib/matrixrtc/LegacyMembershipManager.d.ts +9 -1
  17. package/lib/matrixrtc/LegacyMembershipManager.d.ts.map +1 -1
  18. package/lib/matrixrtc/LegacyMembershipManager.js +20 -0
  19. package/lib/matrixrtc/LegacyMembershipManager.js.map +1 -1
  20. package/lib/matrixrtc/MatrixRTCSession.d.ts.map +1 -1
  21. package/lib/matrixrtc/MatrixRTCSession.js.map +1 -1
  22. package/lib/matrixrtc/NewMembershipManager.d.ts +7 -58
  23. package/lib/matrixrtc/NewMembershipManager.d.ts.map +1 -1
  24. package/lib/matrixrtc/NewMembershipManager.js +130 -137
  25. package/lib/matrixrtc/NewMembershipManager.js.map +1 -1
  26. package/lib/matrixrtc/NewMembershipManagerActionScheduler.js +3 -3
  27. package/lib/matrixrtc/NewMembershipManagerActionScheduler.js.map +1 -1
  28. package/lib/matrixrtc/index.d.ts +1 -0
  29. package/lib/matrixrtc/index.d.ts.map +1 -1
  30. package/lib/matrixrtc/index.js +1 -0
  31. package/lib/matrixrtc/index.js.map +1 -1
  32. package/lib/matrixrtc/types.d.ts +77 -1
  33. package/lib/matrixrtc/types.d.ts.map +1 -1
  34. package/lib/matrixrtc/types.js +40 -1
  35. package/lib/matrixrtc/types.js.map +1 -1
  36. package/lib/models/room-summary.d.ts +30 -0
  37. package/lib/models/room-summary.d.ts.map +1 -1
  38. package/lib/models/room-summary.js +11 -0
  39. package/lib/models/room-summary.js.map +1 -1
  40. package/lib/models/room.d.ts +37 -1
  41. package/lib/models/room.d.ts.map +1 -1
  42. package/lib/models/room.js +133 -28
  43. package/lib/models/room.js.map +1 -1
  44. package/lib/oidc/tokenRefresher.d.ts.map +1 -1
  45. package/lib/oidc/tokenRefresher.js +8 -1
  46. package/lib/oidc/tokenRefresher.js.map +1 -1
  47. package/lib/pushprocessor.d.ts +10 -1
  48. package/lib/pushprocessor.d.ts.map +1 -1
  49. package/lib/pushprocessor.js +24 -9
  50. package/lib/pushprocessor.js.map +1 -1
  51. package/lib/rust-crypto/OutgoingRequestProcessor.d.ts.map +1 -1
  52. package/lib/rust-crypto/OutgoingRequestProcessor.js +6 -1
  53. package/lib/rust-crypto/OutgoingRequestProcessor.js.map +1 -1
  54. package/lib/rust-crypto/rust-crypto.d.ts +4 -1
  55. package/lib/rust-crypto/rust-crypto.d.ts.map +1 -1
  56. package/lib/rust-crypto/rust-crypto.js +55 -46
  57. package/lib/rust-crypto/rust-crypto.js.map +1 -1
  58. package/lib/sliding-sync-sdk.d.ts.map +1 -1
  59. package/lib/sliding-sync-sdk.js +86 -75
  60. package/lib/sliding-sync-sdk.js.map +1 -1
  61. package/lib/sliding-sync.d.ts +23 -60
  62. package/lib/sliding-sync.d.ts.map +1 -1
  63. package/lib/sliding-sync.js +62 -321
  64. package/lib/sliding-sync.js.map +1 -1
  65. package/package.json +6 -6
  66. package/src/client.ts +12 -5
  67. package/src/common-crypto/CryptoBackend.ts +9 -0
  68. package/src/http-api/errors.ts +14 -0
  69. package/src/http-api/fetch.ts +3 -4
  70. package/src/matrixrtc/LegacyMembershipManager.ts +27 -1
  71. package/src/matrixrtc/MatrixRTCSession.ts +2 -1
  72. package/src/matrixrtc/NewMembershipManager.ts +120 -165
  73. package/src/matrixrtc/NewMembershipManagerActionScheduler.ts +3 -3
  74. package/src/matrixrtc/index.ts +1 -0
  75. package/src/matrixrtc/types.ts +84 -1
  76. package/src/models/room-summary.ts +31 -0
  77. package/src/models/room.ts +133 -23
  78. package/src/oidc/tokenRefresher.ts +8 -2
  79. package/src/pushprocessor.ts +25 -16
  80. package/src/rust-crypto/OutgoingRequestProcessor.ts +6 -0
  81. package/src/rust-crypto/rust-crypto.ts +7 -1
  82. package/src/sliding-sync-sdk.ts +28 -28
  83. package/src/sliding-sync.ts +38 -329
@@ -24,110 +24,80 @@ import { type Room } from "../models/room.ts";
24
24
  import { defer, type IDeferred } from "../utils.ts";
25
25
  import { type CallMembership, DEFAULT_EXPIRE_DURATION, type SessionMembershipData } from "./CallMembership.ts";
26
26
  import { type Focus } from "./focus.ts";
27
+ import {
28
+ type IMembershipManager,
29
+ type MembershipManagerEventHandlerMap,
30
+ MembershipManagerEvent,
31
+ Status,
32
+ } from "./types.ts";
27
33
  import { isLivekitFocusActive } from "./LivekitFocus.ts";
28
34
  import { type MembershipConfig } from "./MatrixRTCSession.ts";
29
35
  import { ActionScheduler, type ActionUpdate } from "./NewMembershipManagerActionScheduler.ts";
36
+ import { TypedEventEmitter } from "../models/typed-event-emitter.ts";
30
37
 
31
38
  const logger = rootLogger.getChild("MatrixRTCSession");
32
39
 
33
- /**
34
- * This interface defines what a MembershipManager uses and exposes.
35
- * This interface is what we use to write tests and allows changing the actual implementation
36
- * without breaking tests because of some internal method renaming.
37
- *
38
- * @internal
39
- */
40
- export interface IMembershipManager {
41
- /**
42
- * If we are trying to join, or have successfully joined the session.
43
- * It does not reflect if the room state is already configured to represent us being joined.
44
- * It only means that the Manager should be trying to connect or to disconnect running.
45
- * The Manager is still running right after isJoined becomes false to send the disconnect events.
46
- * (A more accurate name would be `isActivated`)
47
- * @returns true if we intend to be participating in the MatrixRTC session
48
- */
49
- isJoined(): boolean;
50
- /**
51
- * Start sending all necessary events to make this user participate in the RTC session.
52
- * @param fociPreferred the list of preferred foci to use in the joined RTC membership event.
53
- * @param fociActive the active focus to use in the joined RTC membership event.
54
- * @throws can throw if it exceeds a configured maximum retry.
55
- */
56
- join(fociPreferred: Focus[], fociActive?: Focus, onError?: (error: unknown) => void): void;
57
- /**
58
- * Send all necessary events to make this user leave the RTC session.
59
- * @param timeout the maximum duration in ms until the promise is forced to resolve.
60
- * @returns It resolves with true in case the leave was sent successfully.
61
- * It resolves with false in case we hit the timeout before sending successfully.
62
- */
63
- leave(timeout?: number): Promise<boolean>;
64
- /**
65
- * Call this if the MatrixRTC session members have changed.
66
- */
67
- onRTCSessionMemberUpdate(memberships: CallMembership[]): Promise<void>;
68
- /**
69
- * The used active focus in the currently joined session.
70
- * @returns the used active focus in the currently joined session or undefined if not joined.
71
- */
72
- getActiveFocus(): Focus | undefined;
73
- }
74
-
75
40
  /* MembershipActionTypes:
76
-
77
- ┌─────────────────────┐
78
- SendFirstDelayedEvent│
79
- └─────────────────────┘
80
-
81
-
82
- ┌─────────────┐
83
- ┌────────────│SendJoinEvent│────────────┐
84
- └─────────────┘
85
- ┌─────┐ ┌──────┐ ┌──────┐
86
- ▼ ▼ ▼ │
41
+
42
+ On Join: ───────────────┐ ┌───────────────(1)───────────┐
43
+ ▼ ▼
44
+ ┌────────────────┐ │
45
+ SendDelayedEvent│ ──────(2)───┐ │
46
+ └────────────────┘ │ │
47
+ │(3) │ │
48
+ ▼ │ │
49
+ ┌─────────────┐
50
+ ┌──────(4)───│SendJoinEvent│────(4)─────┐
51
+ │ └─────────────┘ │
52
+ │ ┌─────┐ ┌──────┐ │ │ │
53
+ ▼ ▼ │ │ ▼ ▼ ▼ │
87
54
  ┌────────────┐ │ │ ┌───────────────────┐ │
88
- │UpdateExpiry│ │ │ RestartDelayedEvent│ │
55
+ │UpdateExpiry│ (s) (s)|RestartDelayedEvent│ │
89
56
  └────────────┘ │ │ └───────────────────┘ │
90
- │ │ │ │
91
- └─────┘ └──────┘ │ │
92
- │ │
93
- ┌────────────────────┐ │ │
94
- │SendMainDelayedEvent│◄───────┘ │
95
- └───────────────────┬┘ │
96
- │ │
97
- └─────────────────────┘
98
- STOP ALL ABOVE
57
+ │ │ │ │
58
+ └─────┘ └──────┘ └───────┘
59
+
60
+ On Leave: ───────── STOP ALL ABOVE
99
61
 
100
- ┌───────────────────────────────┐
101
- │ SendScheduledDelayedLeaveEvent│
102
- └───────────────────────────────┘
103
-
62
+ ┌────────────────────────────────┐
63
+ │ SendScheduledDelayedLeaveEvent
64
+ └────────────────────────────────┘
65
+ (5)
104
66
 
105
67
  ┌──────────────┐
106
68
  │SendLeaveEvent│
107
69
  └──────────────┘
108
-
70
+ (1) [Not found error] results in resending the delayed event
71
+ (2) [hasMemberEvent = true] Sending the delayed event if we
72
+ already have a call member event results jumping to the
73
+ RestartDelayedEvent loop directly
74
+ (3) [hasMemberEvent = false] if there is not call member event
75
+ sending it is the next step
76
+ (4) Both (UpdateExpiry and RestartDelayedEvent) actions are
77
+ scheduled when successfully sending the state event
78
+ (5) Only if delayed event sending failed (fallback)
79
+ (s) Successful restart/resend
109
80
  */
81
+
110
82
  /**
111
83
  * The different types of actions the MembershipManager can take.
112
84
  * @internal
113
85
  */
114
86
  export enum MembershipActionType {
115
- SendFirstDelayedEvent = "SendFirstDelayedEvent",
87
+ SendDelayedEvent = "SendDelayedEvent",
116
88
  // -> MembershipActionType.SendJoinEvent if successful
117
- // -> DelayedLeaveActionType.SendFirstDelayedEvent on error, retry sending the first delayed event.
89
+ // -> DelayedLeaveActionType.SendDelayedEvent on error, retry sending the first delayed event.
90
+ // -> DelayedLeaveActionType.RestartDelayedEvent on success start updating the delayed event
118
91
  SendJoinEvent = "SendJoinEvent",
119
92
  // -> MembershipActionType.SendJoinEvent if we run into a rate limit and need to retry
120
93
  // -> MembershipActionType.Update if we successfully send the join event then schedule the expire event update
121
94
  // -> DelayedLeaveActionType.RestartDelayedEvent to recheck the delayed event
122
95
  RestartDelayedEvent = "RestartDelayedEvent",
123
96
  // -> DelayedLeaveActionType.SendMainDelayedEvent on missing delay id but there is a rtc state event
124
- // -> DelayedLeaveActionType.SendFirstDelayedEvent on missing delay id and there is no state event
97
+ // -> DelayedLeaveActionType.SendDelayedEvent on missing delay id and there is no state event
125
98
  // -> DelayedLeaveActionType.RestartDelayedEvent on success we schedule the next restart
126
99
  UpdateExpiry = "UpdateExpiry",
127
100
  // -> MembershipActionType.Update if the timeout has passed so the next update is required.
128
- SendMainDelayedEvent = "SendMainDelayedEvent",
129
- // -> DelayedLeaveActionType.RestartDelayedEvent on success start updating the delayed event
130
- // -> DelayedLeaveActionType.SendMainDelayedEvent on error try again
131
101
  SendScheduledDelayedLeaveEvent = "SendScheduledDelayedLeaveEvent",
132
102
  // -> MembershipActionType.SendLeaveEvent on failiour (not found) we need to send the leave manually and cannot use the scheduled delayed event
133
103
  // -> DelayedLeaveActionType.SendScheduledDelayedLeaveEvent on error we try again.
@@ -138,7 +108,7 @@ export enum MembershipActionType {
138
108
  /**
139
109
  * @internal
140
110
  */
141
- export interface ActionSchedulerState {
111
+ export interface MembershipManagerState {
142
112
  /** The delayId we got when successfully sending the delayed leave event.
143
113
  * Gets set to undefined if the server claims it cannot find the delayed event anymore. */
144
114
  delayId?: string;
@@ -158,17 +128,6 @@ export interface ActionSchedulerState {
158
128
  networkErrorRetries: Map<MembershipActionType, number>;
159
129
  }
160
130
 
161
- enum Status {
162
- Disconnected = "Disconnected",
163
- Connecting = "Connecting",
164
- ConnectingFailed = "ConnectingFailed",
165
- Connected = "Connected",
166
- Reconnecting = "Reconnecting",
167
- Disconnecting = "Disconnecting",
168
- Stuck = "Stuck",
169
- Unknown = "Unknown",
170
- }
171
-
172
131
  /**
173
132
  * This class is responsible for sending all events relating to the own membership of a matrixRTC call.
174
133
  * It has the following tasks:
@@ -182,11 +141,18 @@ enum Status {
182
141
  * - Stop the timer for the delay refresh
183
142
  * - Stop the timer for updating the state event
184
143
  */
185
- export class MembershipManager implements IMembershipManager {
144
+ export class MembershipManager
145
+ extends TypedEventEmitter<MembershipManagerEvent, MembershipManagerEventHandlerMap>
146
+ implements IMembershipManager
147
+ {
186
148
  private activated = false;
187
- public isJoined(): boolean {
149
+ public isActivated(): boolean {
188
150
  return this.activated;
189
151
  }
152
+ // DEPRECATED use isActivated
153
+ public isJoined(): boolean {
154
+ return this.isActivated();
155
+ }
190
156
 
191
157
  /**
192
158
  * Puts the MembershipManager in a state where it tries to be joined.
@@ -205,23 +171,27 @@ export class MembershipManager implements IMembershipManager {
205
171
  this.focusActive = focusActive;
206
172
  this.leavePromiseDefer = undefined;
207
173
  this.activated = true;
208
-
174
+ this.oldStatus = this.status;
209
175
  this.state = MembershipManager.defaultState;
210
176
 
211
177
  this.scheduler
212
178
  .startWithJoin()
213
- .then(() => {
214
- if (!this.scheduler.running) {
215
- this.leavePromiseDefer?.resolve(true);
216
- this.leavePromiseDefer = undefined;
217
- }
218
- })
219
179
  .catch((e) => {
220
180
  logger.error("MembershipManager stopped because: ", e);
221
181
  onError?.(e);
222
182
  })
223
- // Should already be set to false when calling `leave` in non error cases.
224
- .finally(() => (this.activated = false));
183
+ .finally(() => {
184
+ // Should already be set to false when calling `leave` in non error cases.
185
+ this.activated = false;
186
+ // Here the scheduler is not running anymore so we the `membershipLoopHandler` is not called to emit.
187
+ if (this.oldStatus && this.oldStatus !== this.status) {
188
+ this.emit(MembershipManagerEvent.StatusChanged, this.oldStatus, this.status);
189
+ }
190
+ if (!this.scheduler.running) {
191
+ this.leavePromiseDefer?.resolve(true);
192
+ this.leavePromiseDefer = undefined;
193
+ }
194
+ });
225
195
  }
226
196
 
227
197
  /**
@@ -256,7 +226,7 @@ export class MembershipManager implements IMembershipManager {
256
226
  // If one of these actions are scheduled or are getting inserted in the next iteration, we should already
257
227
  // take care of our missing membership.
258
228
  const sendingMembershipActions = [
259
- MembershipActionType.SendFirstDelayedEvent,
229
+ MembershipActionType.SendDelayedEvent,
260
230
  MembershipActionType.SendJoinEvent,
261
231
  ];
262
232
  logger.warn("Missing own membership: force re-join");
@@ -313,6 +283,7 @@ export class MembershipManager implements IMembershipManager {
313
283
  >,
314
284
  private getOldestMembership: () => CallMembership | undefined,
315
285
  ) {
286
+ super();
316
287
  const [userId, deviceId] = [this.client.getUserId(), this.client.getDeviceId()];
317
288
  if (userId === null) throw Error("Missing userId in client");
318
289
  if (deviceId === null) throw Error("Missing deviceId in client");
@@ -322,8 +293,8 @@ export class MembershipManager implements IMembershipManager {
322
293
  }
323
294
 
324
295
  // MembershipManager mutable state.
325
- private state: ActionSchedulerState;
326
- private static get defaultState(): ActionSchedulerState {
296
+ private state: MembershipManagerState;
297
+ private static get defaultState(): MembershipManagerState {
327
298
  return {
328
299
  hasMemberStateEvent: false,
329
300
  delayId: undefined,
@@ -380,9 +351,13 @@ export class MembershipManager implements IMembershipManager {
380
351
  private oldStatus?: Status;
381
352
  private scheduler = new ActionScheduler((type): Promise<ActionUpdate> => {
382
353
  if (this.oldStatus) {
383
- // we put this at the beginning of the actions scheduler loop handle callback since it is a loop this
354
+ // we put this at the beginning of the actions scheduler loop handle callback since it is a loop this
384
355
  // is equivalent to running it at the end of the loop. (just after applying the status/action list changes)
356
+ // This order is required because this method needs to return the action updates.
385
357
  logger.debug(`MembershipManager applied action changes. Status: ${this.oldStatus} -> ${this.status}`);
358
+ if (this.oldStatus !== this.status) {
359
+ this.emit(MembershipManagerEvent.StatusChanged, this.oldStatus, this.status);
360
+ }
386
361
  }
387
362
  this.oldStatus = this.status;
388
363
  logger.debug(`MembershipManager before processing action. status=${this.oldStatus}`);
@@ -391,17 +366,16 @@ export class MembershipManager implements IMembershipManager {
391
366
 
392
367
  // LOOP HANDLER:
393
368
  private async membershipLoopHandler(type: MembershipActionType): Promise<ActionUpdate> {
394
- this.oldStatus = this.status;
395
369
  switch (type) {
396
- case MembershipActionType.SendFirstDelayedEvent: {
370
+ case MembershipActionType.SendDelayedEvent: {
397
371
  // Before we start we check if we come from a state where we have a delay id.
398
372
  if (!this.state.delayId) {
399
- return this.sendFirstDelayedLeaveEvent(); // Normal case without any previous delayed id.
373
+ return this.sendOrResendDelayedLeaveEvent(); // Normal case without any previous delayed id.
400
374
  } else {
401
375
  // This can happen if someone else (or another client) removes our own membership event.
402
376
  // It will trigger `onRTCSessionMemberUpdate` queue `MembershipActionType.SendFirstDelayedEvent`.
403
377
  // We might still have our delayed event from the previous participation and dependent on the server this might not
404
- // get automatically removed if the state changes. Hence It would remove our membership unexpectedly shortly after the rejoin.
378
+ // get removed automatically if the state changes. Hence, it would remove our membership unexpectedly shortly after the rejoin.
405
379
  //
406
380
  // In this block we will try to cancel this delayed event before setting up a new one.
407
381
 
@@ -411,17 +385,10 @@ export class MembershipManager implements IMembershipManager {
411
385
  case MembershipActionType.RestartDelayedEvent: {
412
386
  if (!this.state.delayId) {
413
387
  // Delay id got reset. This action was used to check if the hs canceled the delayed event when the join state got sent.
414
- return createInsertActionUpdate(
415
- this.state.hasMemberStateEvent
416
- ? MembershipActionType.SendMainDelayedEvent
417
- : MembershipActionType.SendFirstDelayedEvent,
418
- );
388
+ return createInsertActionUpdate(MembershipActionType.SendDelayedEvent);
419
389
  }
420
390
  return this.restartDelayedEvent(this.state.delayId);
421
391
  }
422
- case MembershipActionType.SendMainDelayedEvent: {
423
- return this.sendMainDelayedEvent();
424
- }
425
392
  case MembershipActionType.SendScheduledDelayedLeaveEvent: {
426
393
  // We are already good
427
394
  if (!this.state.hasMemberStateEvent) {
@@ -452,7 +419,11 @@ export class MembershipManager implements IMembershipManager {
452
419
  }
453
420
 
454
421
  // HANDLERS (used in the membershipLoopHandler)
455
- private async sendFirstDelayedLeaveEvent(): Promise<ActionUpdate> {
422
+ private async sendOrResendDelayedLeaveEvent(): Promise<ActionUpdate> {
423
+ // We can reach this at the start of a call (where we do not yet have a membership: state.hasMemberStateEvent=false)
424
+ // or during a call if the state event canceled our delayed event or caused by an unexpected error that removed our delayed event.
425
+ // (Another client could have canceled it, the homeserver might have removed/lost it due to a restart, ...)
426
+ // In the `then` and `catch` block we treat both cases differently. "if (this.state.hasMemberStateEvent) {} else {}"
456
427
  return await this.client
457
428
  ._unstable_sendDelayedStateEvent(
458
429
  this.room.roomId,
@@ -465,27 +436,46 @@ export class MembershipManager implements IMembershipManager {
465
436
  )
466
437
  .then((response) => {
467
438
  // On success we reset retries and set delayId.
468
- this.state.rateLimitRetries.set(MembershipActionType.SendFirstDelayedEvent, 0);
469
- this.state.networkErrorRetries.set(MembershipActionType.SendFirstDelayedEvent, 0);
439
+ this.resetRateLimitCounter(MembershipActionType.SendDelayedEvent);
470
440
  this.state.delayId = response.delay_id;
471
- return createInsertActionUpdate(MembershipActionType.SendJoinEvent);
441
+ if (this.state.hasMemberStateEvent) {
442
+ // This action was scheduled because the previous delayed event was cancelled
443
+ // due to lack of https://github.com/element-hq/synapse/pull/17810
444
+ return createInsertActionUpdate(
445
+ MembershipActionType.RestartDelayedEvent,
446
+ this.membershipKeepAlivePeriod,
447
+ );
448
+ } else {
449
+ // This action was scheduled because we are in the process of joining
450
+ return createInsertActionUpdate(MembershipActionType.SendJoinEvent);
451
+ }
472
452
  })
473
453
  .catch((e) => {
474
- const repeatActionType = MembershipActionType.SendFirstDelayedEvent;
454
+ const repeatActionType = MembershipActionType.SendDelayedEvent;
475
455
  if (this.manageMaxDelayExceededSituation(e)) {
476
456
  return createInsertActionUpdate(repeatActionType);
477
457
  }
478
458
  const update = this.actionUpdateFromErrors(e, repeatActionType, "sendDelayedStateEvent");
479
459
  if (update) return update;
480
460
 
481
- // log and fall through
482
- if (this.isUnsupportedDelayedEndpoint(e)) {
483
- logger.info("Not using delayed event because the endpoint is not supported");
461
+ if (this.state.hasMemberStateEvent) {
462
+ // This action was scheduled because the previous delayed event was cancelled
463
+ // due to lack of https://github.com/element-hq/synapse/pull/17810
464
+
465
+ // Don't do any other delayed event work if its not supported.
466
+ if (this.isUnsupportedDelayedEndpoint(e)) return {};
467
+ throw Error("Could not send delayed event, even though delayed events are supported. " + e);
484
468
  } else {
485
- logger.info("Not using delayed event because: " + e);
469
+ // This action was scheduled because we are in the process of joining
470
+ // log and fall through
471
+ if (this.isUnsupportedDelayedEndpoint(e)) {
472
+ logger.info("Not using delayed event because the endpoint is not supported");
473
+ } else {
474
+ logger.info("Not using delayed event because: " + e);
475
+ }
476
+ // On any other error we fall back to not using delayed events and send the join state event immediately
477
+ return createInsertActionUpdate(MembershipActionType.SendJoinEvent);
486
478
  }
487
- // On any other error we fall back to not using delayed events and send the join state event immediately
488
- return createInsertActionUpdate(MembershipActionType.SendJoinEvent);
489
479
  });
490
480
  }
491
481
 
@@ -495,11 +485,11 @@ export class MembershipManager implements IMembershipManager {
495
485
  ._unstable_updateDelayedEvent(delayId, UpdateDelayedEventAction.Cancel)
496
486
  .then(() => {
497
487
  this.state.delayId = undefined;
498
- this.resetRateLimitCounter(MembershipActionType.SendFirstDelayedEvent);
499
- return createReplaceActionUpdate(MembershipActionType.SendFirstDelayedEvent);
488
+ this.resetRateLimitCounter(MembershipActionType.SendDelayedEvent);
489
+ return createReplaceActionUpdate(MembershipActionType.SendDelayedEvent);
500
490
  })
501
491
  .catch((e) => {
502
- const repeatActionType = MembershipActionType.SendFirstDelayedEvent;
492
+ const repeatActionType = MembershipActionType.SendDelayedEvent;
503
493
  const update = this.actionUpdateFromErrors(e, repeatActionType, "updateDelayedEvent");
504
494
  if (update) return update;
505
495
 
@@ -538,7 +528,7 @@ export class MembershipManager implements IMembershipManager {
538
528
  const repeatActionType = MembershipActionType.RestartDelayedEvent;
539
529
  if (this.isNotFoundError(e)) {
540
530
  this.state.delayId = undefined;
541
- return createInsertActionUpdate(MembershipActionType.SendMainDelayedEvent);
531
+ return createInsertActionUpdate(MembershipActionType.SendDelayedEvent);
542
532
  }
543
533
  // If the HS does not support delayed events we wont reschedule.
544
534
  if (this.isUnsupportedDelayedEndpoint(e)) return {};
@@ -552,40 +542,6 @@ export class MembershipManager implements IMembershipManager {
552
542
  });
553
543
  }
554
544
 
555
- private async sendMainDelayedEvent(): Promise<ActionUpdate> {
556
- return await this.client
557
- ._unstable_sendDelayedStateEvent(
558
- this.room.roomId,
559
- {
560
- delay: this.membershipServerSideExpiryTimeout,
561
- },
562
- EventType.GroupCallMemberPrefix,
563
- {}, // leave event
564
- this.stateKey,
565
- )
566
- .then((response) => {
567
- this.state.delayId = response.delay_id;
568
- this.resetRateLimitCounter(MembershipActionType.SendMainDelayedEvent);
569
- return createInsertActionUpdate(
570
- MembershipActionType.RestartDelayedEvent,
571
- this.membershipKeepAlivePeriod,
572
- );
573
- })
574
- .catch((e) => {
575
- const repeatActionType = MembershipActionType.SendMainDelayedEvent;
576
- // Don't do any other delayed event work if its not supported.
577
- if (this.isUnsupportedDelayedEndpoint(e)) return {};
578
-
579
- if (this.manageMaxDelayExceededSituation(e)) {
580
- return createInsertActionUpdate(repeatActionType);
581
- }
582
- const update = this.actionUpdateFromErrors(e, repeatActionType, "updateDelayedEvent");
583
- if (update) return update;
584
-
585
- throw Error("Could not send delayed event, even though delayed events are supported. " + e);
586
- });
587
- }
588
-
589
545
  private async sendScheduledDelayedLeaveEventOrFallbackToSendLeaveEvent(delayId: string): Promise<ActionUpdate> {
590
546
  return await this.client
591
547
  ._unstable_updateDelayedEvent(delayId, UpdateDelayedEventAction.Send)
@@ -887,9 +843,8 @@ export class MembershipManager implements IMembershipManager {
887
843
  if (actions.length === 1) {
888
844
  const { type } = actions[0];
889
845
  switch (type) {
890
- case MembershipActionType.SendFirstDelayedEvent:
846
+ case MembershipActionType.SendDelayedEvent:
891
847
  case MembershipActionType.SendJoinEvent:
892
- case MembershipActionType.SendMainDelayedEvent:
893
848
  return Status.Connecting;
894
849
  case MembershipActionType.UpdateExpiry: // where no delayed events
895
850
  return Status.Connected;
@@ -904,7 +859,7 @@ export class MembershipManager implements IMembershipManager {
904
859
  // normal state for connected with delayed events
905
860
  if (
906
861
  (types.includes(MembershipActionType.RestartDelayedEvent) ||
907
- types.includes(MembershipActionType.SendMainDelayedEvent)) &&
862
+ (types.includes(MembershipActionType.SendDelayedEvent) && this.state.hasMemberStateEvent)) &&
908
863
  types.includes(MembershipActionType.UpdateExpiry)
909
864
  ) {
910
865
  return Status.Connected;
@@ -73,7 +73,7 @@ export class ActionScheduler {
73
73
  return;
74
74
  }
75
75
  this.running = true;
76
- this._actions = [{ ts: Date.now(), type: MembershipActionType.SendFirstDelayedEvent }];
76
+ this._actions = [{ ts: Date.now(), type: MembershipActionType.SendDelayedEvent }];
77
77
  try {
78
78
  while (this._actions.length > 0) {
79
79
  // Sort so next (smallest ts) action is at the beginning
@@ -98,7 +98,7 @@ export class ActionScheduler {
98
98
  `\nDate.now: "${Date.now()}`,
99
99
  );
100
100
  try {
101
- // `this.wakeup` can also be called and sets the `wakupUpdate` object while we are in the handler.
101
+ // `this.wakeup` can also be called and sets the `wakeupUpdate` object while we are in the handler.
102
102
  handlerResult = await this.membershipLoopHandler(nextAction.type as MembershipActionType);
103
103
  } catch (e) {
104
104
  throw Error(`The MembershipManager shut down because of the end condition: ${e}`);
@@ -125,7 +125,7 @@ export class ActionScheduler {
125
125
  }
126
126
 
127
127
  public initiateJoin(): void {
128
- this.wakeup?.({ replace: [{ ts: Date.now(), type: MembershipActionType.SendFirstDelayedEvent }] });
128
+ this.wakeup?.({ replace: [{ ts: Date.now(), type: MembershipActionType.SendDelayedEvent }] });
129
129
  }
130
130
  public initiateLeave(): void {
131
131
  this.wakeup?.({ replace: [{ ts: Date.now(), type: MembershipActionType.SendScheduledDelayedLeaveEvent }] });
@@ -20,3 +20,4 @@ export * from "./LivekitFocus.ts";
20
20
  export * from "./MatrixRTCSession.ts";
21
21
  export * from "./MatrixRTCSessionManager.ts";
22
22
  export type * from "./types.ts";
23
+ export { Status, MembershipManagerEvent } from "./types.ts";
@@ -13,7 +13,10 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */
16
- import { type IMentions } from "../matrix.ts";
16
+ import type { IMentions } from "../matrix.ts";
17
+ import type { CallMembership } from "./CallMembership.ts";
18
+ import type { Focus } from "./focus.ts";
19
+
17
20
  export interface EncryptionKeyEntry {
18
21
  index: number;
19
22
  key: string;
@@ -34,3 +37,83 @@ export interface ICallNotifyContent {
34
37
  "notify_type": CallNotifyType;
35
38
  "call_id": string;
36
39
  }
40
+
41
+ export enum Status {
42
+ Disconnected = "Disconnected",
43
+ Connecting = "Connecting",
44
+ ConnectingFailed = "ConnectingFailed",
45
+ Connected = "Connected",
46
+ Reconnecting = "Reconnecting",
47
+ Disconnecting = "Disconnecting",
48
+ Stuck = "Stuck",
49
+ Unknown = "Unknown",
50
+ }
51
+
52
+ export enum MembershipManagerEvent {
53
+ StatusChanged = "StatusChanged",
54
+ }
55
+
56
+ export type MembershipManagerEventHandlerMap = {
57
+ [MembershipManagerEvent.StatusChanged]: (prefStatus: Status, newStatus: Status) => void;
58
+ };
59
+
60
+ /**
61
+ * This interface defines what a MembershipManager uses and exposes.
62
+ * This interface is what we use to write tests and allows changing the actual implementation
63
+ * without breaking tests because of some internal method renaming.
64
+ *
65
+ * @internal
66
+ */
67
+ export interface IMembershipManager {
68
+ /**
69
+ * If we are trying to join, or have successfully joined the session.
70
+ * It does not reflect if the room state is already configured to represent us being joined.
71
+ * It only means that the Manager should be trying to connect or to disconnect running.
72
+ * The Manager is still running right after isJoined becomes false to send the disconnect events.
73
+ * @returns true if we intend to be participating in the MatrixRTC session
74
+ * @deprecated This name is confusing and replaced by `isActivated()`. (Returns the same as `isActivated()`)
75
+ */
76
+ isJoined(): boolean;
77
+ /**
78
+ * If the manager is activated. This means it tries to do its job to join the call, resend state events...
79
+ * It does not imply that the room state is already configured to represent being joined.
80
+ * It means that the Manager tries to connect or is connected. ("the manager is still active")
81
+ * Once `leave()` is called the manager is not activated anymore but still running until `leave()` resolves.
82
+ * @returns `true` if we intend to be participating in the MatrixRTC session
83
+ */
84
+ isActivated(): boolean;
85
+ /**
86
+ * Get the actual connection status of the manager.
87
+ */
88
+ get status(): Status;
89
+ /**
90
+ * The current status while the manager is activated
91
+ */
92
+ /**
93
+ * Start sending all necessary events to make this user participate in the RTC session.
94
+ * @param fociPreferred the list of preferred foci to use in the joined RTC membership event.
95
+ * @param fociActive the active focus to use in the joined RTC membership event.
96
+ * @throws can throw if it exceeds a configured maximum retry.
97
+ */
98
+ join(fociPreferred: Focus[], fociActive?: Focus, onError?: (error: unknown) => void): void;
99
+ /**
100
+ * Send all necessary events to make this user leave the RTC session.
101
+ * @param timeout the maximum duration in ms until the promise is forced to resolve.
102
+ * @returns It resolves with true in case the leave was sent successfully.
103
+ * It resolves with false in case we hit the timeout before sending successfully.
104
+ */
105
+ leave(timeout?: number): Promise<boolean>;
106
+ /**
107
+ * Call this if the MatrixRTC session members have changed.
108
+ */
109
+ onRTCSessionMemberUpdate(memberships: CallMembership[]): Promise<void>;
110
+ /**
111
+ * The used active focus in the currently joined session.
112
+ * @returns the used active focus in the currently joined session or undefined if not joined.
113
+ */
114
+ getActiveFocus(): Focus | undefined;
115
+
116
+ // TypedEventEmitter methods:
117
+ on(event: MembershipManagerEvent.StatusChanged, listener: (oldStatus: Status, newStatus: Status) => void): this;
118
+ off(event: MembershipManagerEvent.StatusChanged, listener: (oldStatus: Status, newStatus: Status) => void): this;
119
+ }
@@ -14,9 +14,40 @@ See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */
16
16
 
17
+ /**
18
+ * A stripped m.room.member event which contains the key renderable fields from the event,
19
+ * sent only in simplified sliding sync (not `/v3/sync`).
20
+ * This is very similar to MSC4186Hero from sliding-sync.ts but an internal format with
21
+ * camelCase rather than underscores.
22
+ */
23
+ export type Hero = {
24
+ userId: string;
25
+ displayName?: string;
26
+ avatarUrl?: string;
27
+ /**
28
+ * If true, the hero is from an MSC4186 summary, in which case `displayName` and `avatarUrl` will
29
+ * have been set by the server if available. If false, the `Hero` has been constructed from a `/v3/sync` response,
30
+ * so these fields will always be undefined.
31
+ */
32
+ fromMSC4186: boolean;
33
+ };
34
+
35
+ /**
36
+ * High level summary information for a room, as returned by `/v3/sync`.
37
+ */
17
38
  export interface IRoomSummary {
39
+ /**
40
+ * The room heroes: a selected set of members that can be used when summarising or
41
+ * generating a name for a room. List of user IDs.
42
+ */
18
43
  "m.heroes": string[];
44
+ /**
45
+ * The number of joined members in the room.
46
+ */
19
47
  "m.joined_member_count"?: number;
48
+ /**
49
+ * The number of invited members in the room.
50
+ */
20
51
  "m.invited_member_count"?: number;
21
52
  }
22
53