matrix-js-sdk 43.0.0 → 44.0.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 (88) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/lib/matrixrtc/CallMembership.d.ts +30 -25
  3. package/lib/matrixrtc/CallMembership.d.ts.map +1 -1
  4. package/lib/matrixrtc/CallMembership.js +91 -100
  5. package/lib/matrixrtc/CallMembership.js.map +1 -1
  6. package/lib/matrixrtc/EncryptionManager.d.ts +6 -4
  7. package/lib/matrixrtc/EncryptionManager.d.ts.map +1 -1
  8. package/lib/matrixrtc/EncryptionManager.js.map +1 -1
  9. package/lib/matrixrtc/IMembershipManager.d.ts +2 -15
  10. package/lib/matrixrtc/IMembershipManager.d.ts.map +1 -1
  11. package/lib/matrixrtc/IMembershipManager.js.map +1 -1
  12. package/lib/matrixrtc/LivekitTransport.d.ts +36 -7
  13. package/lib/matrixrtc/LivekitTransport.d.ts.map +1 -1
  14. package/lib/matrixrtc/LivekitTransport.js +29 -2
  15. package/lib/matrixrtc/LivekitTransport.js.map +1 -1
  16. package/lib/matrixrtc/MatrixRTCSession.d.ts +12 -51
  17. package/lib/matrixrtc/MatrixRTCSession.d.ts.map +1 -1
  18. package/lib/matrixrtc/MatrixRTCSession.js +10 -66
  19. package/lib/matrixrtc/MatrixRTCSession.js.map +1 -1
  20. package/lib/matrixrtc/MembershipManager.d.ts +5 -10
  21. package/lib/matrixrtc/MembershipManager.d.ts.map +1 -1
  22. package/lib/matrixrtc/MembershipManager.js +25 -44
  23. package/lib/matrixrtc/MembershipManager.js.map +1 -1
  24. package/lib/matrixrtc/RTCEncryptionManager.d.ts +8 -8
  25. package/lib/matrixrtc/RTCEncryptionManager.d.ts.map +1 -1
  26. package/lib/matrixrtc/RTCEncryptionManager.js +30 -22
  27. package/lib/matrixrtc/RTCEncryptionManager.js.map +1 -1
  28. package/lib/matrixrtc/ToDeviceKeyTransport.js +7 -8
  29. package/lib/matrixrtc/ToDeviceKeyTransport.js.map +1 -1
  30. package/lib/matrixrtc/membershipData/rtc.d.ts +8 -13
  31. package/lib/matrixrtc/membershipData/rtc.d.ts.map +1 -1
  32. package/lib/matrixrtc/membershipData/rtc.js +2 -11
  33. package/lib/matrixrtc/membershipData/rtc.js.map +1 -1
  34. package/lib/matrixrtc/membershipData/session.d.ts +6 -2
  35. package/lib/matrixrtc/membershipData/session.d.ts.map +1 -1
  36. package/lib/matrixrtc/membershipData/session.js +3 -0
  37. package/lib/matrixrtc/membershipData/session.js.map +1 -1
  38. package/lib/matrixrtc/types.d.ts +5 -0
  39. package/lib/matrixrtc/types.d.ts.map +1 -1
  40. package/lib/matrixrtc/types.js +5 -0
  41. package/lib/matrixrtc/types.js.map +1 -1
  42. package/lib/models/event-timeline-set.d.ts +9 -2
  43. package/lib/models/event-timeline-set.d.ts.map +1 -1
  44. package/lib/models/event-timeline-set.js +8 -5
  45. package/lib/models/event-timeline-set.js.map +1 -1
  46. package/lib/models/room-sticky-events.d.ts +17 -2
  47. package/lib/models/room-sticky-events.d.ts.map +1 -1
  48. package/lib/models/room-sticky-events.js +88 -47
  49. package/lib/models/room-sticky-events.js.map +1 -1
  50. package/lib/models/room.d.ts +1 -1
  51. package/lib/models/room.d.ts.map +1 -1
  52. package/lib/models/room.js +8 -2
  53. package/lib/models/room.js.map +1 -1
  54. package/lib/models/thread.d.ts.map +1 -1
  55. package/lib/models/thread.js +23 -2
  56. package/lib/models/thread.js.map +1 -1
  57. package/lib/rendezvous/MSC4108SignInWithQR.d.ts +3 -3
  58. package/lib/rendezvous/MSC4108SignInWithQR.d.ts.map +1 -1
  59. package/lib/rendezvous/MSC4108SignInWithQR.js.map +1 -1
  60. package/lib/rendezvous/SignInWithQRFlow.d.ts +95 -0
  61. package/lib/rendezvous/SignInWithQRFlow.d.ts.map +1 -0
  62. package/lib/rendezvous/SignInWithQRFlow.js +3 -0
  63. package/lib/rendezvous/SignInWithQRFlow.js.map +1 -0
  64. package/lib/rendezvous/index.d.ts +1 -0
  65. package/lib/rendezvous/index.d.ts.map +1 -1
  66. package/lib/rendezvous/index.js.map +1 -1
  67. package/lib/rust-crypto/index.js +5 -1
  68. package/lib/rust-crypto/index.js.map +1 -1
  69. package/package.json +3 -3
  70. package/src/matrixrtc/CallMembership.ts +85 -95
  71. package/src/matrixrtc/EncryptionManager.ts +6 -4
  72. package/src/matrixrtc/IMembershipManager.ts +2 -15
  73. package/src/matrixrtc/LivekitTransport.ts +58 -9
  74. package/src/matrixrtc/MatrixRTCSession.ts +21 -80
  75. package/src/matrixrtc/MembershipManager.ts +23 -40
  76. package/src/matrixrtc/RTCEncryptionManager.ts +51 -39
  77. package/src/matrixrtc/ToDeviceKeyTransport.ts +7 -7
  78. package/src/matrixrtc/membershipData/rtc.ts +9 -23
  79. package/src/matrixrtc/membershipData/session.ts +14 -2
  80. package/src/matrixrtc/types.ts +6 -0
  81. package/src/models/event-timeline-set.ts +19 -4
  82. package/src/models/room-sticky-events.ts +92 -58
  83. package/src/models/room.ts +8 -2
  84. package/src/models/thread.ts +25 -3
  85. package/src/rendezvous/MSC4108SignInWithQR.ts +3 -3
  86. package/src/rendezvous/SignInWithQRFlow.ts +117 -0
  87. package/src/rendezvous/index.ts +1 -0
  88. package/src/rust-crypto/index.ts +5 -1
@@ -2253,7 +2253,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
2253
2253
  /**
2254
2254
  * Processes poll events:
2255
2255
  * If the event has a decryption failure, it will listen for decryption and tries again.
2256
- * If it is a poll start event (`m.poll.start`),
2256
+ * If it is a poll start event (`m.poll.start`) for a poll not yet known to the room,
2257
2257
  * it creates and stores a Poll model and emits a PollEvent.New event.
2258
2258
  * If the event is related to a poll, it will add it to the poll.
2259
2259
  * Noop for other cases.
@@ -2269,6 +2269,10 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
2269
2269
  }
2270
2270
 
2271
2271
  if (M_POLL_START.matches(event.getType())) {
2272
+ // Check that we don't already have the poll as events can arrive more than once
2273
+ // in some situations. If we add the poll twice, it will break the existing poll model.
2274
+ if (this.polls.has(event.getId()!)) return;
2275
+
2272
2276
  try {
2273
2277
  const poll = new Poll(event, this.client, this);
2274
2278
  this.polls.set(event.getId()!, poll);
@@ -3132,7 +3136,9 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
3132
3136
  threadId = "",
3133
3137
  } = this.eventShouldLiveIn(event, neighbouringEvents, threadRoots);
3134
3138
 
3135
- if (!shouldLiveInThread && !shouldLiveInRoom && event.isRelation()) {
3139
+ // Cached relations are aggregated below and attach when their parent is later hydrated.
3140
+ // Avoid blocking offline startup on a parent fetch.
3141
+ if (!fromCache && !shouldLiveInThread && !shouldLiveInRoom && event.isRelation()) {
3136
3142
  try {
3137
3143
  const parentEvent = new MatrixEvent(
3138
3144
  await this.client.fetchRoomEvent(this.roomId, event.relationEventId!),
@@ -68,6 +68,28 @@ export function determineFeatureSupport(stable: boolean, unstable: boolean): Fea
68
68
  }
69
69
  }
70
70
 
71
+ /**
72
+ * Cap on how many threads fetch their initial events at the same time. Each fetch is a (recursive) `/relations`
73
+ * request, and opening a thread list creates every thread in the room at once; without a cap a room with dozens
74
+ * of threads fires dozens of concurrent heavy requests, which can starve everything else the homeserver worker is doing.
75
+ */
76
+ const MAX_CONCURRENT_INITIAL_FETCHES = 3;
77
+ let runningInitialFetches = 0;
78
+ const waitingInitialFetches: (() => void)[] = [];
79
+
80
+ async function withInitialFetchSlot<T>(fn: () => Promise<T>): Promise<T> {
81
+ if (runningInitialFetches >= MAX_CONCURRENT_INITIAL_FETCHES) {
82
+ await new Promise<void>((resolve) => waitingInitialFetches.push(resolve));
83
+ }
84
+ runningInitialFetches++;
85
+ try {
86
+ return await fn();
87
+ } finally {
88
+ runningInitialFetches--;
89
+ waitingInitialFetches.shift()?.();
90
+ }
91
+ }
92
+
71
93
  export class Thread extends ReadReceipt<ThreadEmittedEvents, ThreadEventHandlerMap> {
72
94
  public static hasServerSideSupport = FeatureSupport.None;
73
95
  public static hasServerSideListSupport = FeatureSupport.None;
@@ -679,9 +701,9 @@ export class Thread extends ReadReceipt<ThreadEmittedEvents, ThreadEventHandlerM
679
701
  this.timelineSet.addEventsToTimeline([this.rootEvent], true, false, this.liveTimeline, null);
680
702
  this.liveTimeline.setPaginationToken(null, Direction.Backward);
681
703
  } else {
682
- this.initalEventFetchProm = this.client.paginateEventTimeline(this.liveTimeline, {
683
- backwards: true,
684
- });
704
+ this.initalEventFetchProm = withInitialFetchSlot(() =>
705
+ this.client.paginateEventTimeline(this.liveTimeline, { backwards: true }),
706
+ );
685
707
  await this.initalEventFetchProm;
686
708
  }
687
709
  // We have now fetched the initial events, so set the flag. We need to do this before
@@ -36,7 +36,7 @@ import {
36
36
  type ValidatedAuthMetadata,
37
37
  waitForDeviceAuthorization,
38
38
  } from "../oauth/index.ts";
39
- import { type CryptoApi } from "../crypto-api/index.ts";
39
+ import { type SignInWithQRSecrets, type SignInWithQRFlow } from "./SignInWithQRFlow.ts";
40
40
 
41
41
  /**
42
42
  * Enum representing the payload types transmissible over [MSC4108](https://github.com/matrix-org/matrix-spec-proposals/pull/4108)
@@ -106,7 +106,7 @@ interface AcceptedPayload extends MSC4108Payload {
106
106
  type: PayloadType.ProtocolAccepted;
107
107
  }
108
108
 
109
- interface SecretsPayload extends MSC4108Payload, Awaited<ReturnType<NonNullable<CryptoApi["exportSecretsBundle"]>>> {
109
+ interface SecretsPayload extends MSC4108Payload, SignInWithQRSecrets {
110
110
  type: PayloadType.Secrets;
111
111
  }
112
112
 
@@ -115,7 +115,7 @@ interface SecretsPayload extends MSC4108Payload, Awaited<ReturnType<NonNullable<
115
115
  * sign in with QR + OAuth2 flow.
116
116
  * @experimental Note that this is UNSTABLE and may have breaking changes without notice.
117
117
  */
118
- export class MSC4108SignInWithQR {
118
+ export class MSC4108SignInWithQR implements SignInWithQRFlow {
119
119
  private readonly ourIntent: QrCodeIntent;
120
120
  private _code?: Uint8Array;
121
121
  private expectingNewDeviceId?: string;
@@ -0,0 +1,117 @@
1
+ /*
2
+ Copyright 2026 Element Creations Ltd.
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import {
18
+ type ClientRendezvousFailureReason,
19
+ type MSC4108FailureReason,
20
+ type RendezvousFailureListener,
21
+ } from "./RendezvousFailureReason.ts";
22
+ import { type DeviceAccessTokenResponse, type ValidatedAuthMetadata } from "../oauth/index.ts";
23
+ import type { SecretsBundleJson } from "../@types/matrix-sdk-crypto-wasm.d.ts";
24
+
25
+ /**
26
+ * The secrets bundle shared from the existing device to the new device.
27
+ * @experimental Note that this is UNSTABLE and may have breaking changes without notice.
28
+ */
29
+ export type { SecretsBundleJson as SignInWithQRSecrets } from "../@types/matrix-sdk-crypto-wasm.d.ts";
30
+
31
+ /**
32
+ * Common interface for the sign in with QR + OAuth2 flows, e.g. the 2024 and 2025 versions of
33
+ * [MSC4108](https://github.com/matrix-org/matrix-spec-proposals/pull/4108).
34
+ * @experimental Note that this is UNSTABLE and may have breaking changes without notice.
35
+ */
36
+ export interface SignInWithQRFlow {
37
+ /**
38
+ * Callback for when the rendezvous fails.
39
+ */
40
+ onFailure?: RendezvousFailureListener;
41
+
42
+ /**
43
+ * Returns the check code for the secure channel or undefined if not generated yet.
44
+ */
45
+ readonly checkCode: string | undefined;
46
+
47
+ /**
48
+ * Returns the code representing the rendezvous suitable for rendering in a QR code or undefined if not generated yet.
49
+ */
50
+ readonly code: Uint8Array | undefined;
51
+
52
+ /**
53
+ * Returns true if the device is the already logged in device reciprocating a new login on the other side of the channel.
54
+ */
55
+ readonly isExistingDevice: boolean;
56
+
57
+ /**
58
+ * Returns true if the device is the new device logging in being reciprocated by the device on the other side of the channel.
59
+ */
60
+ readonly isNewDevice: boolean;
61
+
62
+ /**
63
+ * Generate the code including doing partial set up of the channel where required.
64
+ */
65
+ generateCode(): Promise<void>;
66
+
67
+ /**
68
+ * The first step in the OAuth2 QR login process.
69
+ * To be called after the QR code has been rendered or scanned.
70
+ * The scanning device has to discover the homeserver details, if they scanned the code then they already have it.
71
+ * If the new device is the one rendering the QR code then it has to wait be sent the homeserver details via the rendezvous channel.
72
+ *
73
+ * Depending on the version of the flow, the homeserver is identified either by `serverName` or `baseUrl`.
74
+ */
75
+ negotiateProtocols(): Promise<{ serverName?: string; baseUrl?: string }>;
76
+
77
+ /**
78
+ * The second & third step in the OAuth2 QR login process.
79
+ * To be called after `negotiateProtocols` for the existing device.
80
+ * To be called after OAuth2 negotiation for the new device.
81
+ *
82
+ * @param input - Required for the new device to start the device authorization grant, not required for the existing device reciprocating the login
83
+ */
84
+ deviceAuthorizationGrant(input?: { metadata: ValidatedAuthMetadata; clientId: string; deviceId: string }): Promise<{
85
+ verificationUri?: string;
86
+ userCode?: string;
87
+ }>;
88
+
89
+ /**
90
+ * The fourth step in the OAuth2 QR login process.
91
+ * The reciprocating device must perform step 5 for this method to resolve.
92
+ * To be called after {@link deviceAuthorizationGrant} only on the new device.
93
+ */
94
+ completeLoginOnNewDevice(input: { clientId: string }): Promise<DeviceAccessTokenResponse | undefined>;
95
+
96
+ /**
97
+ * The fifth (and final) step in the OAuth2 QR login process.
98
+ * To be called after the new device has completed authentication.
99
+ */
100
+ shareSecrets(): Promise<{ secrets?: SecretsBundleJson }>;
101
+
102
+ /**
103
+ * Decline the login on the existing device.
104
+ */
105
+ declineLoginOnExistingDevice(): Promise<void>;
106
+
107
+ /**
108
+ * Cancels the rendezvous session.
109
+ * @param reason the reason for the cancellation
110
+ */
111
+ cancel(reason: MSC4108FailureReason | ClientRendezvousFailureReason): Promise<void>;
112
+
113
+ /**
114
+ * Closes the rendezvous session.
115
+ */
116
+ close(): Promise<void>;
117
+ }
@@ -23,6 +23,7 @@ import { RendezvousIntent } from "./RendezvousIntent.ts";
23
23
  import { logger } from "../logger.ts";
24
24
 
25
25
  export * from "./MSC4108SignInWithQR.ts";
26
+ export type * from "./SignInWithQRFlow.ts";
26
27
  export type * from "./RendezvousChannel.ts";
27
28
  export type * from "./RendezvousCode.ts";
28
29
  export * from "./RendezvousError.ts";
@@ -286,7 +286,11 @@ async function initOlmMachine(
286
286
  logger.info(
287
287
  `Checking for pending key bundle for recently-joined room ${roomId} (joined ${new Date(pendingDetails.inviteAcceptedAtMillis).toISOString()})`,
288
288
  );
289
- await rustCrypto.maybeAcceptKeyBundle(roomId, pendingDetails.inviterId.toString());
289
+ // Launch `maybeAcceptKeyBundle` without awaiting it so it cannot block crypto initialisation.
290
+ // Failed or interrupted key queries/downloads leave the pending marker for a later startup to retry.
291
+ rustCrypto.maybeAcceptKeyBundle(roomId, pendingDetails.inviterId.toString()).catch((err) => {
292
+ logger.error("Error attempting to download a pending room key bundle", err);
293
+ });
290
294
  } else {
291
295
  logger.info(
292
296
  `Clearing pending-key-bundle flag for room ${roomId} (too old: joined ${new Date(pendingDetails.inviteAcceptedAtMillis).toISOString()})`,