matrix-js-sdk 42.0.0 → 42.1.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 (226) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/lib/@types/PushRules.d.ts.map +1 -1
  3. package/lib/@types/PushRules.js +0 -5
  4. package/lib/@types/PushRules.js.map +1 -1
  5. package/lib/@types/auth.d.ts.map +1 -1
  6. package/lib/@types/auth.js +0 -5
  7. package/lib/@types/auth.js.map +1 -1
  8. package/lib/@types/crypto.d.ts.map +1 -1
  9. package/lib/@types/crypto.js.map +1 -1
  10. package/lib/@types/event.d.ts +3 -1
  11. package/lib/@types/event.d.ts.map +1 -1
  12. package/lib/@types/event.js +1 -0
  13. package/lib/@types/event.js.map +1 -1
  14. package/lib/@types/extensible_events.js.map +1 -1
  15. package/lib/@types/matrix-sdk-crypto-wasm.d.js.map +1 -1
  16. package/lib/@types/media.js.map +1 -1
  17. package/lib/@types/partials.d.ts.map +1 -1
  18. package/lib/@types/partials.js.map +1 -1
  19. package/lib/@types/requests.d.ts.map +1 -1
  20. package/lib/@types/requests.js +0 -5
  21. package/lib/@types/requests.js.map +1 -1
  22. package/lib/@types/search.d.ts.map +1 -1
  23. package/lib/@types/search.js +0 -3
  24. package/lib/@types/search.js.map +1 -1
  25. package/lib/@types/spaces.d.ts.map +1 -1
  26. package/lib/@types/spaces.js.map +1 -1
  27. package/lib/@types/synapse.d.ts.map +1 -1
  28. package/lib/@types/synapse.js.map +1 -1
  29. package/lib/@types/threepids.js.map +1 -1
  30. package/lib/ToDeviceMessageQueue.d.ts.map +1 -1
  31. package/lib/ToDeviceMessageQueue.js +0 -2
  32. package/lib/ToDeviceMessageQueue.js.map +1 -1
  33. package/lib/browser-index.d.ts.map +1 -1
  34. package/lib/browser-index.js +3 -1
  35. package/lib/browser-index.js.map +1 -1
  36. package/lib/client.d.ts +5 -8
  37. package/lib/client.d.ts.map +1 -1
  38. package/lib/client.js +5 -46
  39. package/lib/client.js.map +1 -1
  40. package/lib/common-crypto/key-passphrase.d.ts.map +1 -1
  41. package/lib/common-crypto/key-passphrase.js +0 -3
  42. package/lib/common-crypto/key-passphrase.js.map +1 -1
  43. package/lib/crypto/store/base.d.ts.map +1 -1
  44. package/lib/crypto/store/base.js +0 -9
  45. package/lib/crypto/store/base.js.map +1 -1
  46. package/lib/crypto/store/indexeddb-crypto-store-backend.js.map +1 -1
  47. package/lib/crypto/store/localStorage-crypto-store.d.ts.map +1 -1
  48. package/lib/crypto/store/localStorage-crypto-store.js +0 -2
  49. package/lib/crypto/store/localStorage-crypto-store.js.map +1 -1
  50. package/lib/crypto-api/keybackup.d.ts +13 -6
  51. package/lib/crypto-api/keybackup.d.ts.map +1 -1
  52. package/lib/crypto-api/keybackup.js.map +1 -1
  53. package/lib/embedded.d.ts.map +1 -1
  54. package/lib/embedded.js +0 -5
  55. package/lib/embedded.js.map +1 -1
  56. package/lib/feature.d.ts.map +1 -1
  57. package/lib/feature.js +0 -3
  58. package/lib/feature.js.map +1 -1
  59. package/lib/filter-component.d.ts.map +1 -1
  60. package/lib/filter-component.js +2 -5
  61. package/lib/filter-component.js.map +1 -1
  62. package/lib/filter.d.ts.map +1 -1
  63. package/lib/filter.js +0 -4
  64. package/lib/filter.js.map +1 -1
  65. package/lib/http-api/interface.d.ts.map +1 -1
  66. package/lib/http-api/interface.js.map +1 -1
  67. package/lib/http-api/refresh.d.ts.map +1 -1
  68. package/lib/http-api/refresh.js +0 -3
  69. package/lib/http-api/refresh.js.map +1 -1
  70. package/lib/interactive-auth.d.ts.map +1 -1
  71. package/lib/interactive-auth.js +5 -6
  72. package/lib/interactive-auth.js.map +1 -1
  73. package/lib/logger.d.ts.map +1 -1
  74. package/lib/logger.js +0 -2
  75. package/lib/logger.js.map +1 -1
  76. package/lib/matrixrtc/CallMembership.d.ts +3 -3
  77. package/lib/matrixrtc/CallMembership.js +5 -5
  78. package/lib/matrixrtc/CallMembership.js.map +1 -1
  79. package/lib/matrixrtc/MatrixRTCSession.d.ts +18 -1
  80. package/lib/matrixrtc/MatrixRTCSession.d.ts.map +1 -1
  81. package/lib/matrixrtc/MatrixRTCSession.js +56 -4
  82. package/lib/matrixrtc/MatrixRTCSession.js.map +1 -1
  83. package/lib/matrixrtc/MatrixRTCSessionManager.d.ts +14 -1
  84. package/lib/matrixrtc/MatrixRTCSessionManager.d.ts.map +1 -1
  85. package/lib/matrixrtc/MatrixRTCSessionManager.js +20 -1
  86. package/lib/matrixrtc/MatrixRTCSessionManager.js.map +1 -1
  87. package/lib/matrixrtc/MembershipManager.d.ts.map +1 -1
  88. package/lib/matrixrtc/MembershipManager.js +9 -6
  89. package/lib/matrixrtc/MembershipManager.js.map +1 -1
  90. package/lib/matrixrtc/index.d.ts +1 -1
  91. package/lib/matrixrtc/index.d.ts.map +1 -1
  92. package/lib/matrixrtc/index.js +1 -1
  93. package/lib/matrixrtc/index.js.map +1 -1
  94. package/lib/matrixrtc/membershipData/rtc.d.ts +6 -3
  95. package/lib/matrixrtc/membershipData/rtc.d.ts.map +1 -1
  96. package/lib/matrixrtc/membershipData/rtc.js +7 -4
  97. package/lib/matrixrtc/membershipData/rtc.js.map +1 -1
  98. package/lib/matrixrtc/types.d.ts +19 -6
  99. package/lib/matrixrtc/types.d.ts.map +1 -1
  100. package/lib/matrixrtc/types.js +11 -1
  101. package/lib/matrixrtc/types.js.map +1 -1
  102. package/lib/models/event.d.ts.map +1 -1
  103. package/lib/models/event.js +0 -6
  104. package/lib/models/event.js.map +1 -1
  105. package/lib/models/room-state.d.ts.map +1 -1
  106. package/lib/models/room-state.js.map +1 -1
  107. package/lib/models/room.d.ts.map +1 -1
  108. package/lib/models/room.js +6 -6
  109. package/lib/models/room.js.map +1 -1
  110. package/lib/models/thread.d.ts +2 -2
  111. package/lib/models/thread.d.ts.map +1 -1
  112. package/lib/models/thread.js.map +1 -1
  113. package/lib/realtime-callbacks.js +0 -1
  114. package/lib/realtime-callbacks.js.map +1 -1
  115. package/lib/rendezvous/transports/MSC4108RendezvousSession.d.ts.map +1 -1
  116. package/lib/rendezvous/transports/MSC4108RendezvousSession.js +0 -1
  117. package/lib/rendezvous/transports/MSC4108RendezvousSession.js.map +1 -1
  118. package/lib/rust-crypto/OutgoingRequestProcessor.d.ts.map +1 -1
  119. package/lib/rust-crypto/OutgoingRequestProcessor.js +0 -2
  120. package/lib/rust-crypto/OutgoingRequestProcessor.js.map +1 -1
  121. package/lib/rust-crypto/backup.d.ts +21 -1
  122. package/lib/rust-crypto/backup.d.ts.map +1 -1
  123. package/lib/rust-crypto/backup.js +62 -21
  124. package/lib/rust-crypto/backup.js.map +1 -1
  125. package/lib/rust-crypto/device-converter.d.ts.map +1 -1
  126. package/lib/rust-crypto/device-converter.js +1 -0
  127. package/lib/rust-crypto/device-converter.js.map +1 -1
  128. package/lib/rust-crypto/index.d.ts +7 -0
  129. package/lib/rust-crypto/index.d.ts.map +1 -1
  130. package/lib/rust-crypto/index.js +3 -3
  131. package/lib/rust-crypto/index.js.map +1 -1
  132. package/lib/rust-crypto/libolm_migration.d.ts.map +1 -1
  133. package/lib/rust-crypto/libolm_migration.js +0 -2
  134. package/lib/rust-crypto/libolm_migration.js.map +1 -1
  135. package/lib/rust-crypto/rust-crypto.d.ts.map +1 -1
  136. package/lib/rust-crypto/rust-crypto.js +0 -3
  137. package/lib/rust-crypto/rust-crypto.js.map +1 -1
  138. package/lib/scheduler.d.ts.map +1 -1
  139. package/lib/scheduler.js +0 -3
  140. package/lib/scheduler.js.map +1 -1
  141. package/lib/sliding-sync-sdk.d.ts.map +1 -1
  142. package/lib/sliding-sync-sdk.js +6 -0
  143. package/lib/sliding-sync-sdk.js.map +1 -1
  144. package/lib/sliding-sync.d.ts.map +1 -1
  145. package/lib/sliding-sync.js +3 -0
  146. package/lib/sliding-sync.js.map +1 -1
  147. package/lib/store/indexeddb.d.ts.map +1 -1
  148. package/lib/store/indexeddb.js +0 -2
  149. package/lib/store/indexeddb.js.map +1 -1
  150. package/lib/store/memory.d.ts.map +1 -1
  151. package/lib/store/memory.js +7 -9
  152. package/lib/store/memory.js.map +1 -1
  153. package/lib/sync-accumulator.d.ts.map +1 -1
  154. package/lib/sync-accumulator.js +1 -5
  155. package/lib/sync-accumulator.js.map +1 -1
  156. package/lib/sync.d.ts.map +1 -1
  157. package/lib/sync.js +1 -1
  158. package/lib/sync.js.map +1 -1
  159. package/lib/timeline-window.d.ts.map +1 -1
  160. package/lib/timeline-window.js +0 -1
  161. package/lib/timeline-window.js.map +1 -1
  162. package/lib/utils/internal/deriveKeys.d.ts.map +1 -1
  163. package/lib/utils/internal/deriveKeys.js +0 -1
  164. package/lib/utils/internal/deriveKeys.js.map +1 -1
  165. package/lib/webrtc/callEventTypes.d.ts.map +1 -1
  166. package/lib/webrtc/callEventTypes.js +0 -5
  167. package/lib/webrtc/callEventTypes.js.map +1 -1
  168. package/package.json +5 -5
  169. package/src/@types/PushRules.ts +0 -5
  170. package/src/@types/auth.ts +0 -6
  171. package/src/@types/crypto.ts +2 -6
  172. package/src/@types/event.ts +3 -0
  173. package/src/@types/extensible_events.ts +4 -4
  174. package/src/@types/matrix-sdk-crypto-wasm.d.ts +0 -1
  175. package/src/@types/media.ts +1 -1
  176. package/src/@types/partials.ts +0 -2
  177. package/src/@types/requests.ts +0 -5
  178. package/src/@types/search.ts +0 -2
  179. package/src/@types/spaces.ts +0 -2
  180. package/src/@types/synapse.ts +0 -2
  181. package/src/@types/threepids.ts +2 -2
  182. package/src/ToDeviceMessageQueue.ts +0 -2
  183. package/src/browser-index.ts +3 -3
  184. package/src/client.ts +18 -61
  185. package/src/common-crypto/key-passphrase.ts +0 -1
  186. package/src/crypto/store/base.ts +1 -8
  187. package/src/crypto/store/indexeddb-crypto-store-backend.ts +1 -1
  188. package/src/crypto/store/localStorage-crypto-store.ts +0 -1
  189. package/src/crypto-api/keybackup.ts +14 -7
  190. package/src/embedded.ts +0 -5
  191. package/src/feature.ts +0 -3
  192. package/src/filter-component.ts +1 -2
  193. package/src/filter.ts +0 -3
  194. package/src/http-api/interface.ts +0 -1
  195. package/src/http-api/refresh.ts +0 -1
  196. package/src/interactive-auth.ts +4 -4
  197. package/src/logger.ts +0 -2
  198. package/src/matrixrtc/CallMembership.ts +5 -5
  199. package/src/matrixrtc/MatrixRTCSession.ts +70 -3
  200. package/src/matrixrtc/MatrixRTCSessionManager.ts +21 -2
  201. package/src/matrixrtc/MembershipManager.ts +8 -5
  202. package/src/matrixrtc/index.ts +1 -1
  203. package/src/matrixrtc/membershipData/rtc.ts +18 -7
  204. package/src/matrixrtc/types.ts +24 -7
  205. package/src/models/event.ts +0 -3
  206. package/src/models/room-state.ts +0 -3
  207. package/src/models/room.ts +6 -6
  208. package/src/models/thread.ts +2 -2
  209. package/src/realtime-callbacks.ts +0 -1
  210. package/src/rendezvous/transports/MSC4108RendezvousSession.ts +0 -1
  211. package/src/rust-crypto/OutgoingRequestProcessor.ts +0 -1
  212. package/src/rust-crypto/backup.ts +66 -28
  213. package/src/rust-crypto/device-converter.ts +1 -0
  214. package/src/rust-crypto/index.ts +11 -0
  215. package/src/rust-crypto/libolm_migration.ts +0 -2
  216. package/src/rust-crypto/rust-crypto.ts +0 -3
  217. package/src/scheduler.ts +0 -3
  218. package/src/sliding-sync-sdk.ts +3 -0
  219. package/src/sliding-sync.ts +2 -0
  220. package/src/store/indexeddb.ts +0 -2
  221. package/src/store/memory.ts +7 -9
  222. package/src/sync-accumulator.ts +1 -3
  223. package/src/sync.ts +1 -3
  224. package/src/timeline-window.ts +0 -1
  225. package/src/utils/internal/deriveKeys.ts +0 -1
  226. package/src/webrtc/callEventTypes.ts +0 -5
@@ -35,6 +35,7 @@ import type {
35
35
  RTCCallIntent,
36
36
  Transport,
37
37
  SlotDescription,
38
+ RtcSlotEventContent,
38
39
  } from "./types.ts";
39
40
  import {
40
41
  MembershipManagerEvent,
@@ -309,6 +310,25 @@ export class MatrixRTCSession extends TypedEventEmitter<
309
310
  return computeSlotId(this.slotDescription);
310
311
  }
311
312
 
313
+ /**
314
+ * Reads the current slot state event's content for this session.
315
+ *
316
+ * @returns The slot event's content, or `undefined` if no slot event exists for this session.
317
+ */
318
+ public getRtcSlot(): RtcSlotEventContent | undefined {
319
+ return getSlotEventContent(this.roomSubset, this.slotDescription);
320
+ }
321
+
322
+ /**
323
+ * Whether this session's slot is closed.
324
+ *
325
+ * @returns `true` if the slot is closed, `false` if the slot is open or `undefined`
326
+ * if no slot exists.
327
+ */
328
+ public isSlotClosed(): boolean | undefined {
329
+ return isSlotClosed(this.roomSubset, this.slotDescription);
330
+ }
331
+
312
332
  /**
313
333
  * Returns all the call memberships for a room that match the provided `sessionDescription`,
314
334
  * oldest first.
@@ -325,7 +345,7 @@ export class MatrixRTCSession extends TypedEventEmitter<
325
345
  const logger = rootLogger.getChild(
326
346
  `[MatrixRTCSession ${room.roomId} ${slotDescription.application}#${slotDescription.id}]`,
327
347
  );
328
- const callMemberEvents = collectMembersEvents(room, options, logger);
348
+ const callMemberEvents = collectMembersEvents(room, slotDescription, options, logger);
329
349
 
330
350
  const callMemberships = await computeBackendIdentityAndVerifyMemberEvents(
331
351
  room,
@@ -424,6 +444,7 @@ export class MatrixRTCSession extends TypedEventEmitter<
424
444
  );
425
445
 
426
446
  this.roomSubset.on(RoomStateEvent.Members, this.onRoomMemberUpdate);
447
+ this.roomSubset.on(RoomStateEvent.Events, this.onRoomStateEvent);
427
448
  this.roomSubset.on(RoomStickyEventsEvent.Update, this.onStickyEventUpdate);
428
449
 
429
450
  this.initialMembershipCalculated = this.ensureRecalculateSessionMembers();
@@ -448,6 +469,7 @@ export class MatrixRTCSession extends TypedEventEmitter<
448
469
  }
449
470
 
450
471
  this.roomSubset.off(RoomStateEvent.Members, this.onRoomMemberUpdate);
472
+ this.roomSubset.off(RoomStateEvent.Events, this.onRoomStateEvent);
451
473
  this.roomSubset.off(RoomStickyEventsEvent.Update, this.onStickyEventUpdate);
452
474
  }
453
475
 
@@ -714,6 +736,15 @@ export class MatrixRTCSession extends TypedEventEmitter<
714
736
  void this.ensureRecalculateSessionMembers();
715
737
  };
716
738
 
739
+ /**
740
+ * Call this when a room state event has been updated.
741
+ */
742
+ private readonly onRoomStateEvent = (event: MatrixEvent): void => {
743
+ if (event.getType() !== EventType.RTCSlot) return;
744
+ if (event.getStateKey() !== computeSlotId(this.slotDescription)) return;
745
+ void this.ensureRecalculateSessionMembers();
746
+ };
747
+
717
748
  /**
718
749
  * Call this when a sticky event update has occured.
719
750
  */
@@ -735,7 +766,6 @@ export class MatrixRTCSession extends TypedEventEmitter<
735
766
  * Call this when something changed that may impacts the current MatrixRTC members in this session.
736
767
  */
737
768
  // We allow this name schema since this function should only be used for testing purposes.
738
- // eslint-disable-next-line @typescript-eslint/naming-convention
739
769
  public _onRTCSessionMemberUpdate = async (): Promise<void> => {
740
770
  await this.recalculateSessionMembers();
741
771
  };
@@ -899,17 +929,54 @@ function isValidMembership(
899
929
  return true;
900
930
  }
901
931
 
932
+ /**
933
+ * Reads the slot state event's content for the given slot description.
934
+ *
935
+ * @returns The slot event's content, or `undefined` if no slot event exists for the given description.
936
+ */
937
+ function getSlotEventContent(
938
+ room: Pick<Room, "getLiveTimeline">,
939
+ slotDescription: SlotDescription,
940
+ ): RtcSlotEventContent | undefined {
941
+ const slotId = computeSlotId(slotDescription);
942
+ const slotEvent = room
943
+ .getLiveTimeline()
944
+ .getState(EventTimeline.FORWARDS)
945
+ ?.getStateEvents(EventType.RTCSlot, slotId);
946
+ if (!slotEvent) return undefined;
947
+
948
+ return slotEvent.getContent<RtcSlotEventContent>();
949
+ }
950
+
951
+ /**
952
+ * Whether the given slot is closed.
953
+ *
954
+ * @returns `true` if the slot is closed, `false` if the slot is open or `undefined`
955
+ * if no slot exists.
956
+ */
957
+ function isSlotClosed(room: Pick<Room, "getLiveTimeline">, slotDescription: SlotDescription): boolean | undefined {
958
+ const content = getSlotEventContent(room, slotDescription) as Partial<RtcSlotEventContent> | undefined;
959
+ if (content === undefined) return undefined;
960
+
961
+ return (
962
+ content.status !== "open" ||
963
+ typeof content.application !== "object" ||
964
+ content.application?.type !== slotDescription.application
965
+ );
966
+ }
967
+
902
968
  /**
903
969
  * Collects the raw member events from room state and sticky store.
904
970
  */
905
971
  function collectMembersEvents(
906
972
  room: Pick<Room, "getLiveTimeline" | "roomId" | "_unstable_getStickyEvents">,
973
+ slotDescription: SlotDescription,
907
974
  options: SessionMembershipsForSlotOpts,
908
975
  logger: Logger,
909
976
  ): MatrixEvent[] {
910
977
  const { listenForStickyEvents, listenForMemberStateEvents } = options;
911
978
  let callMemberEvents: MatrixEvent[] = [];
912
- if (listenForStickyEvents) {
979
+ if (listenForStickyEvents && !isSlotClosed(room, slotDescription)) {
913
980
  // prefill with sticky events
914
981
  callMemberEvents = [...room._unstable_getStickyEvents()].filter((e) => e.getType() === EventType.RTCMembership);
915
982
  }
@@ -22,7 +22,7 @@ import { RoomStateEvent } from "../models/room-state.ts";
22
22
  import { type MatrixEvent } from "../models/event.ts";
23
23
  import { MatrixRTCSession } from "./MatrixRTCSession.ts";
24
24
  import { EventType } from "../@types/event.ts";
25
- import { type SlotDescription } from "./types.ts";
25
+ import { type RtcSlotEventContent, type SlotDescription } from "./types.ts";
26
26
  import { computeSlotId } from "./utils.ts";
27
27
 
28
28
  export enum MatrixRTCSessionManagerEvents {
@@ -98,6 +98,25 @@ export class MatrixRTCSessionManager extends TypedEventEmitter<MatrixRTCSessionM
98
98
  return this.roomSessions.get(room.roomId)!;
99
99
  }
100
100
 
101
+ /**
102
+ * Reads the current slot state event's content for the given room's session.
103
+ *
104
+ * @returns The slot event's content, or `undefined` if no slot event exists for the session.
105
+ */
106
+ public getRtcSlot(room: Room): RtcSlotEventContent | undefined {
107
+ return this.getRoomSession(room).getRtcSlot();
108
+ }
109
+
110
+ /**
111
+ * Whether the given room's slot is closed.
112
+ *
113
+ * @returns `true` if the slot is closed, `false` if the slot is open or `undefined`
114
+ * if no slot exists.
115
+ */
116
+ public isSlotClosed(room: Room): boolean | undefined {
117
+ return this.getRoomSession(room).isSlotClosed();
118
+ }
119
+
101
120
  /**
102
121
  * Gets the main MatrixRTC session for a room, returning an empty session
103
122
  * if no members are currently participating
@@ -129,7 +148,7 @@ export class MatrixRTCSessionManager extends TypedEventEmitter<MatrixRTCSessionM
129
148
  };
130
149
 
131
150
  private readonly onRoomState = (event: MatrixEvent): void => {
132
- if (event.getType() !== EventType.GroupCallMemberPrefix) {
151
+ if (event.getType() !== EventType.GroupCallMemberPrefix && event.getType() !== EventType.RTCSlot) {
133
152
  return;
134
153
  }
135
154
  const room = this.client.getRoom(event.getRoomId());
@@ -1111,11 +1111,14 @@ export class StickyEventMembershipManager extends MembershipManager {
1111
1111
  ...(this.callIntent ? { "m.call.intent": this.callIntent } : {}),
1112
1112
  },
1113
1113
  slot_id: computeSlotId(this.slotDescription),
1114
- // Make sure we do not add the alias to the transport.
1115
- // It is not needed in matrix2.0. The additional session information will be used to find the right alias on the sfu.
1116
- rtc_transports: livekitTransport
1117
- ? [{ type: livekitTransport.type, livekit_service_url: livekitTransport.livekit_service_url }]
1118
- : [],
1114
+ transports: {
1115
+ // Make sure we do not add the alias to the transport.
1116
+ // It is not needed in matrix2.0. The additional session information will be used to find the right alias on the sfu.
1117
+ published: livekitTransport
1118
+ ? [{ type: livekitTransport.type, livekit_service_url: livekitTransport.livekit_service_url }]
1119
+ : [],
1120
+ can_subscribe: ["livekit"],
1121
+ },
1119
1122
  member: { device_id: this.deviceId, user_id: this.userId, id: this.memberId },
1120
1123
  versions: [],
1121
1124
  ...relationObject,
@@ -20,5 +20,5 @@ export * from "./MatrixRTCSession.ts";
20
20
  export * from "./MatrixRTCSessionManager.ts";
21
21
  export type * from "./types.ts";
22
22
  export { type SessionMembershipData, type RtcMembershipData } from "./membershipData/index.ts";
23
- export { Status, parseCallNotificationContent, isMyMembership } from "./types.ts";
23
+ export { Status, parseCallNotificationContent, isMyMembership, RTC_SLOT_ENCRYPTION_PER_MEMBER } from "./types.ts";
24
24
  export { MembershipManagerEvent } from "./IMembershipManager.ts";
@@ -17,7 +17,7 @@ limitations under the License.
17
17
  import { MXID_PATTERN } from "../../models/room-member.ts";
18
18
  import type { IContent } from "../../models/event.ts";
19
19
  import type { RelationType } from "../../types.ts";
20
- import { type RtcSlotEventContent, type Transport } from "../types.ts";
20
+ import { type RtcSlotApplicationContent, type Transport } from "../types.ts";
21
21
  import { MatrixRTCMembershipParseError } from "./common.ts";
22
22
  import { sha256 } from "../../digest.ts";
23
23
  import { encodeUnpaddedBase64 } from "../../base64.ts";
@@ -37,8 +37,11 @@ export interface RtcMembershipData {
37
37
  event_id: string;
38
38
  rel_type: RelationType.Reference;
39
39
  };
40
- "application": RtcSlotEventContent["application"];
41
- "rtc_transports": Transport[];
40
+ "application": RtcSlotApplicationContent;
41
+ "transports": {
42
+ published: Transport[];
43
+ can_subscribe: string[];
44
+ };
42
45
  "versions": string[];
43
46
  "msc4354_sticky_key"?: string;
44
47
  "sticky_key"?: string;
@@ -97,17 +100,25 @@ export const checkRtcMembershipData = (data: IContent, sender: string): data is
97
100
  if (data.application.type.includes("#")) errors.push(prefix + 'application.type must not include "#"');
98
101
  }
99
102
  }
100
- if (data.rtc_transports === undefined || !Array.isArray(data.rtc_transports)) {
101
- errors.push(prefix + "rtc_transports must be an array");
103
+ if (typeof data.transports !== "object" || data.transports === null || !Array.isArray(data.transports.published)) {
104
+ errors.push(prefix + "transports.published must be an array");
102
105
  } else {
103
106
  // validate that each transport has at least a string 'type'
104
- for (const t of data.rtc_transports) {
107
+ for (const t of data.transports.published) {
105
108
  if (typeof t !== "object" || t === null || typeof (t as any).type !== "string") {
106
- errors.push(prefix + "rtc_transports entries must be objects with a string type");
109
+ errors.push(prefix + "transports.published entries must be objects with a string type");
107
110
  break;
108
111
  }
109
112
  }
110
113
  }
114
+ if (
115
+ typeof data.transports !== "object" ||
116
+ data.transports === null ||
117
+ !Array.isArray(data.transports.can_subscribe) ||
118
+ !data.transports.can_subscribe.every((t: unknown) => typeof t === "string")
119
+ ) {
120
+ errors.push(prefix + "transports.can_subscribe must be an array of strings");
121
+ }
111
122
  if (data.versions === undefined || !Array.isArray(data.versions)) {
112
123
  errors.push(prefix + "versions must be an array");
113
124
  } else if (!data.versions.every((v) => typeof v === "string")) {
@@ -201,15 +201,32 @@ export interface Transport {
201
201
  }
202
202
 
203
203
  /**
204
- * Event content for a `m.rtc.slot` state event.
204
+ * Event content for `org.matrix.msc4143.rtc.slot` state events.
205
205
  */
206
206
  export interface RtcSlotEventContent<T extends string = string> {
207
- application: {
208
- type: T;
209
- // other application specific keys
210
- [key: string]: unknown;
211
- };
212
- slot_id: string;
207
+ status: "open" | "closed";
208
+ application?: RtcSlotApplicationContent<T>;
209
+ encryption?: RtcSlotEncryptionContent;
210
+ }
211
+
212
+ /**
213
+ * Content of the `application` object within `org.matrix.msc4143.rtc.slot` events.
214
+ */
215
+ export interface RtcSlotApplicationContent<T extends string = string> {
216
+ type: T;
217
+ // Other application specific keys.
218
+ [key: string]: unknown;
219
+ }
220
+
221
+ export const RTC_SLOT_ENCRYPTION_PER_MEMBER = "org.matrix.msc4143.per_member";
222
+
223
+ /**
224
+ * Content of the `encryption` object within `org.matrix.msc4143.rtc.slot` events.
225
+ */
226
+ export interface RtcSlotEncryptionContent {
227
+ type: typeof RTC_SLOT_ENCRYPTION_PER_MEMBER;
228
+ // Other encryption-mechanism specific keys.
229
+ [key: string]: unknown;
213
230
  }
214
231
 
215
232
  /**
@@ -50,7 +50,6 @@ import { type EmptyObject } from "../@types/common.ts";
50
50
 
51
51
  export { EventStatus } from "./event-status.ts";
52
52
 
53
- /* eslint-disable camelcase */
54
53
  export interface IContent {
55
54
  [key: string]: any;
56
55
  "msgtype"?: MsgType | string;
@@ -165,7 +164,6 @@ export interface IClearEvent {
165
164
  content: Omit<IContent, "membership" | "avatar_url" | "displayname" | "m.relates_to">;
166
165
  unsigned?: IUnsigned;
167
166
  }
168
- /* eslint-enable camelcase */
169
167
 
170
168
  interface IKeyRequestRecipient {
171
169
  userId: string;
@@ -938,7 +936,6 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
938
936
  // `decryptionPromise`).
939
937
  await Promise.resolve();
940
938
 
941
- // eslint-disable-next-line no-constant-condition
942
939
  while (true) {
943
940
  this.retryDecryption = false;
944
941
 
@@ -62,11 +62,8 @@ export interface IPowerLevelsContent {
62
62
  users?: Record<string, number>;
63
63
  events?: Record<string, number>;
64
64
  notifications?: Partial<Record<"room", number>>;
65
- // eslint-disable-next-line camelcase
66
65
  users_default?: number;
67
- // eslint-disable-next-line camelcase
68
66
  events_default?: number;
69
- // eslint-disable-next-line camelcase
70
67
  state_default?: number;
71
68
  ban?: number;
72
69
  invite?: number;
@@ -640,7 +640,9 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
640
640
  let capabilities: Capabilities = {};
641
641
  try {
642
642
  capabilities = await this.client.getCapabilities();
643
- } catch {}
643
+ } catch {
644
+ // Ignore errors - we'll just use the default safe room version
645
+ }
644
646
  let versionCap = capabilities["m.room_versions"];
645
647
  if (!versionCap) {
646
648
  versionCap = {
@@ -2276,7 +2278,9 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
2276
2278
  event.once(MatrixEventEvent.BeforeRedaction, (redactedEvent: MatrixEvent) => {
2277
2279
  this.polls.delete(redactedEvent.getId()!);
2278
2280
  });
2279
- } catch {}
2281
+ } catch {
2282
+ // Do nothing
2283
+ }
2280
2284
  // poll creation can fail for malformed poll start events
2281
2285
  return;
2282
2286
  }
@@ -3444,7 +3448,6 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
3444
3448
  /**
3445
3449
  * Get an iterator of currently active sticky events.
3446
3450
  */
3447
- // eslint-disable-next-line
3448
3451
  public _unstable_getStickyEvents(): ReturnType<RoomStickyEventsStore["getStickyEvents"]> {
3449
3452
  return this.stickyEvents.getStickyEvents();
3450
3453
  }
@@ -3456,7 +3459,6 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
3456
3459
  * @param stickyKey The sticky key used by the event.
3457
3460
  * @returns A matching active sticky event, or undefined.
3458
3461
  */
3459
- // eslint-disable-next-line
3460
3462
  public _unstable_getKeyedStickyEvent(
3461
3463
  sender: string,
3462
3464
  type: string,
@@ -3471,7 +3473,6 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
3471
3473
  * @param sender The sender of the sticky event.
3472
3474
  * @returns An array of matching sticky events.
3473
3475
  */
3474
- // eslint-disable-next-line
3475
3476
  public _unstable_getUnkeyedStickyEvent(
3476
3477
  sender: string,
3477
3478
  type: string,
@@ -3485,7 +3486,6 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
3485
3486
  * @param events A set of new sticky events.
3486
3487
  * @internal
3487
3488
  */
3488
- // eslint-disable-next-line
3489
3489
  public _unstable_addStickyEvents(events: MatrixEvent[]): ReturnType<RoomStickyEventsStore["addStickyEvents"]> {
3490
3490
  // ALWAYS filter out any events that are past retention
3491
3491
  events = events.filter((e) => this.retention?.shouldEventBeRetained(e) ?? true);
@@ -914,8 +914,8 @@ export const FILTER_RELATED_BY_REL_TYPES = new ServerControlledNamespacedValue(
914
914
  export const THREAD_RELATION_TYPE = new ServerControlledNamespacedValue("m.thread", "io.element.thread");
915
915
 
916
916
  export enum ThreadFilterType {
917
- "My",
918
- "All",
917
+ My,
918
+ All,
919
919
  }
920
920
 
921
921
  export function threadFilterTypeToFilter(type: ThreadFilterType | null): "all" | "participated" {
@@ -140,7 +140,6 @@ function runCallbacks(): void {
140
140
 
141
141
  // get the list of things to call
142
142
  const callbacksToRun: Callback[] = [];
143
- // eslint-disable-next-line
144
143
  while (true) {
145
144
  const first = callbackList[0];
146
145
  if (!first || first.runAt > timestamp) {
@@ -181,7 +181,6 @@ export class MSC4108RendezvousSession {
181
181
  if (!this.url) {
182
182
  throw new Error("Rendezvous not set up");
183
183
  }
184
- // eslint-disable-next-line no-constant-condition
185
184
  while (true) {
186
185
  if (this._cancelled) {
187
186
  return undefined;
@@ -190,7 +190,6 @@ export class OutgoingRequestProcessor {
190
190
  ): Promise<string> {
191
191
  let currentRetryCount = 0;
192
192
 
193
- // eslint-disable-next-line no-constant-condition
194
193
  while (true) {
195
194
  try {
196
195
  return await this.rawJsonRequest(method, path, queryParams, body);
@@ -27,6 +27,7 @@ import {
27
27
  type KeyBackupRestoreOpts,
28
28
  type KeyBackupRestoreResult,
29
29
  type KeyBackupRoomSessions,
30
+ type NewKeyBackupInfo,
30
31
  } from "../crypto-api/keybackup.ts";
31
32
  import { type Logger } from "../logger.ts";
32
33
  import { ClientPrefix, type IHttpOpts, MatrixError, type MatrixHttpApi, Method } from "../http-api/index.ts";
@@ -325,11 +326,6 @@ export class RustBackupManager extends TypedEventEmitter<RustBackupCryptoEvents,
325
326
  return null;
326
327
  }
327
328
  this.checkedForBackup = true;
328
-
329
- if (backupInfo && !backupInfo.version) {
330
- this.logger.warn("active backup lacks a useful 'version'; ignoring it");
331
- backupInfo = undefined;
332
- }
333
329
  this.serverBackupInfo = backupInfo;
334
330
 
335
331
  const activeVersion = await this.getActiveBackupVersion();
@@ -356,34 +352,51 @@ export class RustBackupManager extends TypedEventEmitter<RustBackupCryptoEvents,
356
352
  this.logger.debug("Key backup present on server but not trusted: not enabling key backup");
357
353
  }
358
354
  } else {
359
- if (activeVersion === null) {
360
- this.logger.debug(`Found usable key backup v${backupInfo.version}: enabling key backups`);
361
- await this.enableKeyBackup(backupInfo);
362
- } else if (activeVersion !== backupInfo.version) {
363
- this.logger.debug(
364
- `On backup version ${activeVersion} but found version ${backupInfo.version}: switching.`,
365
- );
366
- // This will remove any pending backup request, remove the backup key and reset the backup state of each room key we have.
367
- await this.disableKeyBackup();
368
- // Enabling will now trigger re-upload of all the keys
369
- await this.enableKeyBackup(backupInfo);
370
- } else {
371
- this.logger.debug(`Backup version ${backupInfo.version} still current`);
372
- }
355
+ await this.enableOrSwitchKeyBackup(backupInfo, activeVersion);
373
356
  }
374
357
  return { backupInfo, trustInfo };
375
358
  }
376
359
 
360
+ /**
361
+ * Enable key backup upload for the given backup version, if it is not already.
362
+ *
363
+ * If backup is currently enabled for a different version, disables it first.
364
+ *
365
+ * Also emits one or more {@link CryptoEvent.KeyBackupStatus} events if the backup status changes.
366
+ *
367
+ * @param backupInfo - the desired backup version (and the encryption key).
368
+ * @param activeVersion - the current active backup version (or `null`, if none).
369
+ */
370
+ private async enableOrSwitchKeyBackup(backupInfo: KeyBackupInfo, activeVersion: string | null): Promise<void> {
371
+ if (activeVersion === null) {
372
+ this.logger.debug(`Found usable key backup v${backupInfo.version}: enabling key backups`);
373
+ await this.enableKeyBackup(backupInfo);
374
+ } else if (activeVersion !== backupInfo.version) {
375
+ this.logger.debug(`On backup version ${activeVersion} but found version ${backupInfo.version}: switching.`);
376
+ // This will remove any pending backup request, remove the backup upload key from the OlmMachine and reset
377
+ // the backup state of each room key we have.
378
+ await this.disableKeyBackup();
379
+ // Enabling will now trigger re-upload of all the keys
380
+ await this.enableKeyBackup(backupInfo);
381
+ } else {
382
+ this.logger.debug(`Backup version ${backupInfo.version} still current`);
383
+ }
384
+ }
385
+
386
+ /**
387
+ * Helper for {@link enableOrSwitchKeyBackup}.
388
+ *
389
+ * Enables key backup upload for the given backup version. Also emits
390
+ * a {@link CryptoEvent.KeyBackupStatus} event.
391
+ */
377
392
  private async enableKeyBackup(backupInfo: KeyBackupInfo): Promise<void> {
378
393
  // we know for certain it must be a Curve25519 key, because we have verified it and only Curve25519
379
394
  // keys can be verified.
380
- //
381
- // we also checked it has a valid `version`.
382
395
  await this.olmMachine.enableBackupV1(
383
396
  (backupInfo.auth_data as Curve25519AuthData).public_key,
384
- backupInfo.version!,
397
+ backupInfo.version,
385
398
  );
386
- this.activeBackupVersion = backupInfo.version!;
399
+ this.activeBackupVersion = backupInfo.version;
387
400
 
388
401
  this.emit(CryptoEvent.KeyBackupStatus, true);
389
402
 
@@ -560,16 +573,24 @@ export class RustBackupManager extends TypedEventEmitter<RustBackupCryptoEvents,
560
573
  }
561
574
 
562
575
  /**
563
- * Creates a new key backup by generating a new random private key.
576
+ * Creates a new key backup by generating a new random private key, and then enable key backup upload and download
577
+ * using the new backup version.
564
578
  *
565
579
  * If there is an existing backup server side it will be deleted and replaced
566
580
  * by the new one.
567
581
  *
582
+ * Saves the decryption key in the Rust SDK's CryptoStore.
583
+ *
568
584
  * @param signObject - Method that should sign the backup with existing device and
569
585
  * existing identity.
570
586
  * @returns a KeyBackupCreationInfo - All information related to the backup.
571
587
  */
572
588
  public async setupKeyBackup(signObject: (authData: AuthData) => Promise<void>): Promise<KeyBackupCreationInfo> {
589
+ // Wait for any active call to `checkKeyBackupAndEnable` to complete, to avoid racing with it
590
+ if (this.keyBackupCheckInProgress) {
591
+ await this.keyBackupCheckInProgress;
592
+ }
593
+
573
594
  // Clean up any existing backup
574
595
  await this.deleteAllKeyBackupVersions();
575
596
 
@@ -580,19 +601,36 @@ export class RustBackupManager extends TypedEventEmitter<RustBackupCryptoEvents,
580
601
 
581
602
  await signObject(authData);
582
603
 
604
+ const backupData: NewKeyBackupInfo = {
605
+ algorithm: pubKey.algorithm,
606
+ auth_data: authData,
607
+ };
608
+
583
609
  const res = await this.http.authedRequest<{ version: string }>(
584
610
  Method.Post,
585
611
  "/room_keys/version",
586
612
  undefined,
587
- {
588
- algorithm: pubKey.algorithm,
589
- auth_data: authData,
590
- },
613
+ backupData,
591
614
  {
592
615
  prefix: ClientPrefix.V3,
593
616
  },
594
617
  );
595
618
 
619
+ // This backup was just created and signed locally, so use the creation response to make up a full
620
+ // `KeyBackupInfo` struct representing the new backup, instead of doing another discovery/trust check.
621
+ const backupInfo: KeyBackupInfo = {
622
+ algorithm: pubKey.algorithm,
623
+ auth_data: authData,
624
+ version: res.version,
625
+ count: 0,
626
+ etag: "", // we never actually use the etag, so we can just make up a value
627
+ };
628
+
629
+ // saveBackupDecryptionKey emits KeyBackupDecryptionKeyCached. Cache and
630
+ // enable the created backup first so listeners observe the new version.
631
+ this.serverBackupInfo = backupInfo;
632
+ this.checkedForBackup = true;
633
+ await this.enableOrSwitchKeyBackup(backupInfo, await this.getActiveBackupVersion());
596
634
  await this.saveBackupDecryptionKey(randomKey, res.version);
597
635
 
598
636
  return {
@@ -111,6 +111,7 @@ export function downloadDeviceToJsDevice(device: QueryDevice): Device {
111
111
 
112
112
  const signatures = new Map<string, Map<string, string>>();
113
113
  if (device.signatures) {
114
+ // oxlint-disable-next-line guard-for-in
114
115
  for (const userId in device.signatures) {
115
116
  signatures.set(userId, new Map(Object.entries(device.signatures[userId])));
116
117
  }
@@ -96,6 +96,14 @@ export async function initRustCrypto(args: {
96
96
  * Whether to enable support for encrypting state events.
97
97
  */
98
98
  enableEncryptedStateEvents?: boolean;
99
+
100
+ /**
101
+ * Optional PEM-formatted string that provides CA certificates. These will
102
+ * be used to check X.509 signatures on user identities. Any user identity
103
+ * that has a valid signature according to the supplied CAs will be
104
+ * considered verified, without any manual verification taking place.
105
+ */
106
+ caCertsPem?: string;
99
107
  }): Promise<RustCrypto> {
100
108
  const { logger } = args;
101
109
 
@@ -134,6 +142,7 @@ export async function initRustCrypto(args: {
134
142
  storeHandle,
135
143
  args.legacyCryptoStore,
136
144
  args.enableEncryptedStateEvents,
145
+ args.caCertsPem,
137
146
  );
138
147
 
139
148
  storeHandle.free();
@@ -152,6 +161,7 @@ async function initOlmMachine(
152
161
  storeHandle: StoreHandle,
153
162
  legacyCryptoStore?: CryptoStore,
154
163
  enableEncryptedStateEvents?: boolean,
164
+ caCertsPem?: string,
155
165
  ): Promise<RustCrypto> {
156
166
  logger.debug("Init OlmMachine");
157
167
 
@@ -160,6 +170,7 @@ async function initOlmMachine(
160
170
  new RustSdkCryptoJs.DeviceId(deviceId),
161
171
  storeHandle,
162
172
  logger,
173
+ caCertsPem,
163
174
  );
164
175
 
165
176
  // A final migration step, now that we have an OlmMachine.
@@ -251,7 +251,6 @@ async function migrateOlmSessions(
251
251
  storeHandle: RustSdkCryptoJs.StoreHandle,
252
252
  onBatchDone: (batchSize: number) => void,
253
253
  ): Promise<void> {
254
- // eslint-disable-next-line no-constant-condition
255
254
  while (true) {
256
255
  const batch = await legacyStore.getEndToEndSessionsBatch();
257
256
  if (batch === null) return;
@@ -279,7 +278,6 @@ async function migrateMegolmSessions(
279
278
  storeHandle: RustSdkCryptoJs.StoreHandle,
280
279
  onBatchDone: (batchSize: number) => void,
281
280
  ): Promise<void> {
282
- // eslint-disable-next-line no-constant-condition
283
281
  while (true) {
284
282
  const batch = await legacyStore.getEndToEndInboundGroupSessionsBatch();
285
283
  if (batch === null) return;
@@ -1407,9 +1407,6 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, CryptoEventH
1407
1407
  if (await this.secretStorageHasAESKey()) {
1408
1408
  await this.secretStorage.store("m.megolm_backup.v1", backupInfo.decryptionKey.toBase64());
1409
1409
  }
1410
-
1411
- // we can check and start async
1412
- void this.checkKeyBackupAndEnable();
1413
1410
  }
1414
1411
 
1415
1412
  /**