matrix-js-sdk 42.4.0 → 43.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 (138) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/lib/@types/PushRules.d.ts +3 -1
  3. package/lib/@types/PushRules.d.ts.map +1 -1
  4. package/lib/@types/PushRules.js +2 -0
  5. package/lib/@types/PushRules.js.map +1 -1
  6. package/lib/@types/event.d.ts +1 -0
  7. package/lib/@types/event.d.ts.map +1 -1
  8. package/lib/@types/event.js.map +1 -1
  9. package/lib/@types/matrix-sdk-crypto-wasm.d.js.map +1 -1
  10. package/lib/client.d.ts +88 -37
  11. package/lib/client.d.ts.map +1 -1
  12. package/lib/client.js +72 -47
  13. package/lib/client.js.map +1 -1
  14. package/lib/crypto-api/index.d.ts +3 -3
  15. package/lib/crypto-api/index.d.ts.map +1 -1
  16. package/lib/crypto-api/index.js.map +1 -1
  17. package/lib/embedded.d.ts +17 -1
  18. package/lib/embedded.d.ts.map +1 -1
  19. package/lib/embedded.js +46 -1
  20. package/lib/embedded.js.map +1 -1
  21. package/lib/errors.d.ts +1 -1
  22. package/lib/errors.d.ts.map +1 -1
  23. package/lib/errors.js +2 -2
  24. package/lib/errors.js.map +1 -1
  25. package/lib/http-api/fetch.d.ts +7 -1
  26. package/lib/http-api/fetch.d.ts.map +1 -1
  27. package/lib/http-api/fetch.js +14 -5
  28. package/lib/http-api/fetch.js.map +1 -1
  29. package/lib/http-api/interface.d.ts +34 -8
  30. package/lib/http-api/interface.d.ts.map +1 -1
  31. package/lib/http-api/interface.js +10 -3
  32. package/lib/http-api/interface.js.map +1 -1
  33. package/lib/http-api/refresh.d.ts +30 -11
  34. package/lib/http-api/refresh.d.ts.map +1 -1
  35. package/lib/http-api/refresh.js +72 -5
  36. package/lib/http-api/refresh.js.map +1 -1
  37. package/lib/matrix.d.ts +1 -1
  38. package/lib/matrix.d.ts.map +1 -1
  39. package/lib/matrix.js.map +1 -1
  40. package/lib/matrixrtc/CallMembership.d.ts.map +1 -1
  41. package/lib/matrixrtc/CallMembership.js +20 -6
  42. package/lib/matrixrtc/CallMembership.js.map +1 -1
  43. package/lib/matrixrtc/IMembershipManager.d.ts +5 -0
  44. package/lib/matrixrtc/IMembershipManager.d.ts.map +1 -1
  45. package/lib/matrixrtc/IMembershipManager.js.map +1 -1
  46. package/lib/matrixrtc/MatrixRTCSession.d.ts +27 -1
  47. package/lib/matrixrtc/MatrixRTCSession.d.ts.map +1 -1
  48. package/lib/matrixrtc/MatrixRTCSession.js +58 -36
  49. package/lib/matrixrtc/MatrixRTCSession.js.map +1 -1
  50. package/lib/matrixrtc/MatrixRTCSessionManager.js +1 -1
  51. package/lib/matrixrtc/MatrixRTCSessionManager.js.map +1 -1
  52. package/lib/matrixrtc/MembershipManager.d.ts +76 -0
  53. package/lib/matrixrtc/MembershipManager.d.ts.map +1 -1
  54. package/lib/matrixrtc/MembershipManager.js +264 -47
  55. package/lib/matrixrtc/MembershipManager.js.map +1 -1
  56. package/lib/matrixrtc/index.d.ts +1 -1
  57. package/lib/matrixrtc/index.d.ts.map +1 -1
  58. package/lib/matrixrtc/index.js +1 -1
  59. package/lib/matrixrtc/index.js.map +1 -1
  60. package/lib/matrixrtc/membershipData/common.d.ts +8 -0
  61. package/lib/matrixrtc/membershipData/common.d.ts.map +1 -1
  62. package/lib/matrixrtc/membershipData/common.js +10 -0
  63. package/lib/matrixrtc/membershipData/common.js.map +1 -1
  64. package/lib/matrixrtc/membershipData/index.d.ts +1 -1
  65. package/lib/matrixrtc/membershipData/index.d.ts.map +1 -1
  66. package/lib/matrixrtc/membershipData/index.js +1 -1
  67. package/lib/matrixrtc/membershipData/index.js.map +1 -1
  68. package/lib/matrixrtc/types.d.ts +104 -11
  69. package/lib/matrixrtc/types.d.ts.map +1 -1
  70. package/lib/matrixrtc/types.js +263 -19
  71. package/lib/matrixrtc/types.js.map +1 -1
  72. package/lib/matrixrtc/utils.d.ts +22 -1
  73. package/lib/matrixrtc/utils.d.ts.map +1 -1
  74. package/lib/matrixrtc/utils.js +38 -0
  75. package/lib/matrixrtc/utils.js.map +1 -1
  76. package/lib/models/room-sticky-events.d.ts +7 -0
  77. package/lib/models/room-sticky-events.d.ts.map +1 -1
  78. package/lib/models/room-sticky-events.js +27 -0
  79. package/lib/models/room-sticky-events.js.map +1 -1
  80. package/lib/models/thread.d.ts.map +1 -1
  81. package/lib/models/thread.js +8 -0
  82. package/lib/models/thread.js.map +1 -1
  83. package/lib/oauth/authorize.js +1 -1
  84. package/lib/oauth/authorize.js.map +1 -1
  85. package/lib/oauth/index.d.ts +3 -5
  86. package/lib/oauth/index.d.ts.map +1 -1
  87. package/lib/oauth/index.js +7 -7
  88. package/lib/oauth/index.js.map +1 -1
  89. package/lib/oauth/tokenRefresher.d.ts +10 -4
  90. package/lib/oauth/tokenRefresher.d.ts.map +1 -1
  91. package/lib/oauth/tokenRefresher.js +34 -24
  92. package/lib/oauth/tokenRefresher.js.map +1 -1
  93. package/lib/receipt-accumulator.d.ts +10 -11
  94. package/lib/receipt-accumulator.d.ts.map +1 -1
  95. package/lib/receipt-accumulator.js +26 -25
  96. package/lib/receipt-accumulator.js.map +1 -1
  97. package/lib/room-summary-api.d.ts +71 -0
  98. package/lib/room-summary-api.d.ts.map +1 -0
  99. package/lib/room-summary-api.js +82 -0
  100. package/lib/room-summary-api.js.map +1 -0
  101. package/lib/rust-crypto/backup.d.ts +7 -0
  102. package/lib/rust-crypto/backup.d.ts.map +1 -1
  103. package/lib/rust-crypto/backup.js +41 -25
  104. package/lib/rust-crypto/backup.js.map +1 -1
  105. package/lib/sync.d.ts.map +1 -1
  106. package/lib/sync.js +6 -1
  107. package/lib/sync.js.map +1 -1
  108. package/package.json +6 -6
  109. package/src/@types/PushRules.ts +2 -0
  110. package/src/@types/event.ts +1 -1
  111. package/src/@types/matrix-sdk-crypto-wasm.d.ts +15 -12
  112. package/src/client.ts +105 -62
  113. package/src/crypto-api/index.ts +3 -3
  114. package/src/embedded.ts +61 -2
  115. package/src/errors.ts +2 -1
  116. package/src/http-api/fetch.ts +14 -5
  117. package/src/http-api/interface.ts +36 -8
  118. package/src/http-api/refresh.ts +101 -17
  119. package/src/matrix.ts +1 -1
  120. package/src/matrixrtc/CallMembership.ts +22 -4
  121. package/src/matrixrtc/IMembershipManager.ts +6 -0
  122. package/src/matrixrtc/MatrixRTCSession.ts +72 -49
  123. package/src/matrixrtc/MatrixRTCSessionManager.ts +1 -1
  124. package/src/matrixrtc/MembershipManager.ts +284 -61
  125. package/src/matrixrtc/index.ts +7 -1
  126. package/src/matrixrtc/membershipData/common.ts +12 -0
  127. package/src/matrixrtc/membershipData/index.ts +1 -1
  128. package/src/matrixrtc/types.ts +351 -23
  129. package/src/matrixrtc/utils.ts +54 -1
  130. package/src/models/room-sticky-events.ts +26 -1
  131. package/src/models/thread.ts +9 -0
  132. package/src/oauth/authorize.ts +1 -1
  133. package/src/oauth/index.ts +7 -8
  134. package/src/oauth/tokenRefresher.ts +15 -5
  135. package/src/receipt-accumulator.ts +30 -26
  136. package/src/room-summary-api.ts +114 -0
  137. package/src/rust-crypto/backup.ts +46 -33
  138. package/src/sync.ts +9 -1
@@ -14,7 +14,8 @@ See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import { type AccessTokens, HTTPError, type TokenRefreshFunction, TokenRefreshLogoutError } from "../http-api/index.ts";
17
+ import { HTTPError, TokenRefreshLogoutError } from "../http-api/errors.ts";
18
+ import { type AccessTokens } from "../http-api/interface.ts";
18
19
  import { OAuth2HTTPError } from "./error.ts";
19
20
  import { type OAuth2 } from "./index.ts";
20
21
 
@@ -26,16 +27,15 @@ export class TokenRefresher {
26
27
 
27
28
  public constructor(
28
29
  private readonly auth: OAuth2,
29
- private readonly onRefresh: (tokens: AccessTokens) => Promise<void>,
30
+ private readonly onRefresh: (tokens: AccessTokens) => void | Promise<void>,
30
31
  ) {}
31
32
 
32
33
  /**
33
34
  * Attempt token refresh using given refresh token
34
35
  * @param refreshToken - refresh token to use in request with token issuer
35
- * @returns tokens - Promise that resolves with new access and refresh tokens
36
36
  * @throws when token refresh fails
37
37
  */
38
- public tokenRefreshFunction: TokenRefreshFunction = async (refreshToken: string): Promise<AccessTokens> => {
38
+ public async refreshTokens(refreshToken: string): Promise<AccessTokens> {
39
39
  if (!this.inflightRefreshRequest) {
40
40
  this.inflightRefreshRequest = this.getNewTokens(refreshToken);
41
41
  }
@@ -52,7 +52,17 @@ export class TokenRefresher {
52
52
  } finally {
53
53
  this.inflightRefreshRequest = undefined;
54
54
  }
55
- };
55
+ }
56
+
57
+ /**
58
+ * Revoke a token with the OP, e.g. as part of logging out.
59
+ * @param token - the token to revoke
60
+ * @param type - the type of token, acts as a hint to the OP
61
+ * @throws when revocation fails
62
+ */
63
+ public async revokeToken(token: string, type?: "access_token" | "refresh_token"): Promise<void> {
64
+ await this.auth.revokeToken(token, type);
65
+ }
56
66
 
57
67
  private shouldLogoutOnError(error: HTTPError): boolean {
58
68
  // Treat as logout as per https://spec.matrix.org/v1.18/client-server-api/#refresh-token-grant
@@ -37,50 +37,57 @@ interface AccumulatedReceipt {
37
37
  * the most recently received receipt in each thread.
38
38
  */
39
39
  export class ReceiptAccumulator {
40
- /** user_id -\> most-recently-received unthreaded receipt */
41
- private unthreadedReadReceipts: Map<string, AccumulatedReceipt> = new Map();
42
-
43
- /** thread_id -\> user_id -\> most-recently-received receipt for this thread */
44
- private threadedReadReceipts: MapWithDefault<string, Map<string, AccumulatedReceipt>> = new MapWithDefault(
40
+ /** user_id -\> receipt_type -\> most-recently-received unthreaded receipt */
41
+ private unthreadedReadReceipts: MapWithDefault<string, Map<ReceiptType, AccumulatedReceipt>> = new MapWithDefault(
45
42
  () => new Map(),
46
43
  );
47
44
 
45
+ /** thread_id -\> user_id -\> receipt_type -\> most-recently-received receipt for this thread */
46
+ private threadedReadReceipts: MapWithDefault<string, MapWithDefault<string, Map<ReceiptType, AccumulatedReceipt>>> =
47
+ new MapWithDefault(() => new MapWithDefault(() => new Map()));
48
+
48
49
  /**
49
50
  * Provide an unthreaded receipt for this user. Overwrites any other
50
- * unthreaded receipt we have for this user.
51
+ * unthreaded receipt of this type we have for this user.
51
52
  */
52
53
  private setUnthreaded(userId: string, receipt: AccumulatedReceipt): void {
53
- this.unthreadedReadReceipts.set(userId, receipt);
54
+ this.unthreadedReadReceipts.getOrCreate(userId).set(receipt.type, receipt);
54
55
  }
55
56
 
56
57
  /**
57
58
  * Provide a receipt for this user in this thread. Overwrites any other
58
- * receipt we have for this user in this thread.
59
+ * receipt of this type we have for this user in this thread.
59
60
  */
60
61
  private setThreaded(threadId: string, userId: string, receipt: AccumulatedReceipt): void {
61
- this.threadedReadReceipts.getOrCreate(threadId).set(userId, receipt);
62
+ this.threadedReadReceipts.getOrCreate(threadId).getOrCreate(userId).set(receipt.type, receipt);
62
63
  }
63
64
 
64
65
  /**
65
- * @returns an iterator of pairs of [userId, AccumulatedReceipt] - all the
66
- * most recently-received unthreaded receipts for each user.
67
- * @yields pairs of [userId, AccumulatedReceipt]
66
+ * @yields pairs of [userId, AccumulatedReceipt] for every receipt type.
68
67
  */
69
- private allUnthreaded(): IterableIterator<[string, AccumulatedReceipt]> {
70
- return this.unthreadedReadReceipts.entries();
68
+ private *allForUsers(
69
+ receiptsByUser: ReadonlyMap<string, ReadonlyMap<ReceiptType, AccumulatedReceipt>>,
70
+ ): IterableIterator<[string, AccumulatedReceipt]> {
71
+ for (const [userId, receiptsForUser] of receiptsByUser) {
72
+ for (const receipt of receiptsForUser.values()) {
73
+ yield [userId, receipt];
74
+ }
75
+ }
71
76
  }
72
77
 
73
78
  /**
74
- * @returns an iterator of pairs of [userId, AccumulatedReceipt] - all the
75
- * most recently-received threaded receipts for each user, in all
76
- * threads.
77
- * @yields pairs of [userId, AccumulatedReceipt]
79
+ * @yields all unthreaded receipts of each type for each user.
80
+ */
81
+ private *allUnthreaded(): IterableIterator<[string, AccumulatedReceipt]> {
82
+ yield* this.allForUsers(this.unthreadedReadReceipts);
83
+ }
84
+
85
+ /**
86
+ * @yields all threaded receipts of each type for each user, in all threads.
78
87
  */
79
88
  private *allThreaded(): IterableIterator<[string, AccumulatedReceipt]> {
80
89
  for (const receiptsForThread of this.threadedReadReceipts.values()) {
81
- for (const e of receiptsForThread.entries()) {
82
- yield e;
83
- }
90
+ yield* this.allForUsers(receiptsForThread);
84
91
  }
85
92
  }
86
93
 
@@ -101,11 +108,8 @@ export class ReceiptAccumulator {
101
108
  // but they are keyed in the event as:
102
109
  // content:{ $event_id: { $receipt_type: { $user_id: {json} }}}
103
110
  // so store them in the former so we can accumulate receipt deltas
104
- // quickly and efficiently (we expect a lot of them). Fold the
105
- // receipt type into the key name since we only have 1 at the
106
- // moment (m.read) and nested JSON objects are slower and more
107
- // of a hassle to work with. We'll inflate this back out when
108
- // getJSON() is called.
111
+ // quickly and efficiently (we expect a lot of them). We'll inflate
112
+ // this back out when getJSON() is called.
109
113
  Object.keys(e.content).forEach((eventId) => {
110
114
  Object.entries<ReceiptContent>(e.content[eventId]).forEach(([key, value]) => {
111
115
  if (!isSupportedReceiptType(key)) return;
@@ -0,0 +1,114 @@
1
+ /*
2
+ Copyright 2026 The Matrix.org Foundation C.I.C.
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
+ /**
18
+ * The room summary endpoint, which describes a room the user may not be in.
19
+ * https://spec.matrix.org/latest/client-server-api/#room-summaries
20
+ *
21
+ * Not to be confused with {@link RoomSummary} in `./models/room-summary.ts`, which holds the
22
+ * heroes and member counts that `/sync` sends for rooms we are in.
23
+ */
24
+
25
+ import { type IPublicRoomsChunkRoom, type IServerVersions } from "./client.ts";
26
+ import { ClientPrefix, type IHttpOpts, type MatrixHttpApi, Method } from "./http-api/index.ts";
27
+ import { type JoinRule } from "./@types/partials.ts";
28
+ import { type Membership } from "./@types/membership.ts";
29
+ import * as utils from "./utils.ts";
30
+
31
+ /**
32
+ * The summary of a room, as returned by the room summary endpoint.
33
+ * https://spec.matrix.org/latest/client-server-api/#get_matrixclientv1room_summaryroomidoralias
34
+ *
35
+ * Also covers the initial version of MSC3266 implemented in older versions of Synapse, which
36
+ * prefixed some of the fields. See {@link MatrixClient.getRoomSummary}.
37
+ */
38
+ export interface RoomSummary extends Omit<IPublicRoomsChunkRoom, "canonical_alias" | "aliases" | "join_rule"> {
39
+ /**
40
+ * The current membership of this user in the room.
41
+ */
42
+ "membership"?: Membership;
43
+ /**
44
+ * The join rule of the room. Unlike the join rule of a public rooms chunk, this may be any
45
+ * join rule, since the room does not have to be published to the room directory.
46
+ */
47
+ "join_rule"?: JoinRule;
48
+ /**
49
+ * The canonical alias of the room, if any.
50
+ */
51
+ "canonical_alias"?: string;
52
+ /**
53
+ * Version of the room.
54
+ */
55
+ "room_version"?: string;
56
+ /**
57
+ * The encryption algorithm used for this room, if the room is encrypted.
58
+ */
59
+ "encryption"?: string;
60
+ /**
61
+ * For restricted rooms, the room IDs which are specified by the join rule.
62
+ */
63
+ "allowed_room_ids"?: string[];
64
+ /**
65
+ * Version of the room.
66
+ * @deprecated Only returned by the unstable endpoint; use {@link RoomSummary.room_version}.
67
+ */
68
+ "im.nheko.summary.room_version"?: string;
69
+ /**
70
+ * The encryption algorithm used for this room, if the room is encrypted.
71
+ * @deprecated Only returned by the unstable endpoint; use {@link RoomSummary.encryption}.
72
+ */
73
+ "im.nheko.summary.encryption"?: string;
74
+ }
75
+
76
+ /** The endpoint stabilised in Matrix 1.15. */
77
+ const STABLE_ENDPOINT = { path: "/room_summary/$roomid", prefix: ClientPrefix.V1 };
78
+
79
+ /** The endpoint used by the initial version of MSC3266, as implemented in older versions of Synapse. */
80
+ const UNSTABLE_ENDPOINT = { path: "/summary/$roomid", prefix: "/_matrix/client/unstable/im.nheko.summary" };
81
+
82
+ /** The spec version which stabilised MSC3266 as `/_matrix/client/v1/room_summary/{roomIdOrAlias}`. */
83
+ const STABLE_VERSION = "v1.15";
84
+
85
+ /** The unstable feature flag advertised for MSC3266. */
86
+ const UNSTABLE_FEATURE = "org.matrix.msc3266";
87
+
88
+ /** Whether the server advertises the stable room summary endpoint in its `/versions` response. */
89
+ function supportsStableEndpoint(versions: IServerVersions): boolean {
90
+ return versions.versions?.includes(STABLE_VERSION) || !!versions.unstable_features?.[UNSTABLE_FEATURE];
91
+ }
92
+
93
+ /**
94
+ * Fetch the summary of a room.
95
+ *
96
+ * Uses the path from the initial version of MSC3266, as implemented in older versions of Synapse,
97
+ * unless the server advertises support for the spec version which stabilised it.
98
+ *
99
+ * @param http - The HTTP API to make the request with.
100
+ * @param versions - The server's `/versions` response, used to pick the endpoint.
101
+ * @param roomIdOrAlias - The ID or alias of the room to get the summary of.
102
+ * @param via - The servers to attempt to request the summary from, when the local server cannot
103
+ * generate it (for instance, because it has no local user in the room).
104
+ */
105
+ export function fetchRoomSummary(
106
+ http: MatrixHttpApi<IHttpOpts & { onlyData: true }>,
107
+ versions: IServerVersions,
108
+ roomIdOrAlias: string,
109
+ via?: string[],
110
+ ): Promise<RoomSummary> {
111
+ const endpoint = supportsStableEndpoint(versions) ? STABLE_ENDPOINT : UNSTABLE_ENDPOINT;
112
+ const path = utils.encodeUri(endpoint.path, { $roomid: roomIdOrAlias });
113
+ return http.authedRequest<RoomSummary>(Method.Get, path, { via }, undefined, { prefix: endpoint.prefix });
114
+ }
@@ -31,7 +31,7 @@ import {
31
31
  import { type Logger } from "../logger.ts";
32
32
  import { ClientPrefix, type IHttpOpts, MatrixError, type MatrixHttpApi, Method } from "../http-api/index.ts";
33
33
  import { TypedEventEmitter } from "../models/typed-event-emitter.ts";
34
- import { encodeUri, logDuration } from "../utils.ts";
34
+ import { encodeUri } from "../utils.ts";
35
35
  import { type OutgoingRequestProcessor } from "./OutgoingRequestProcessor.ts";
36
36
  import { sleep } from "../utils.ts";
37
37
  import { type BackupDecryptor } from "../common-crypto/CryptoBackend.ts";
@@ -61,6 +61,14 @@ interface KeyBackupCreationInfo {
61
61
  * @internal
62
62
  */
63
63
  export class RustBackupManager extends TypedEventEmitter<RustBackupCryptoEvents, RustBackupCryptoEventMap> {
64
+ /**
65
+ * When the backup upload loop starts, we delay the first request by a random amount to avoid backup
66
+ * requests from different clients hitting the server all at the same time when a new key is sent.
67
+ *
68
+ * This defines the maximum delay. It can be reduced in tests to make the test run faster.
69
+ */
70
+ public static readonly maxBackupLoopStartDelayMillis: number = 10000;
71
+
64
72
  /** Have we checked if there is a backup on the server which we can use */
65
73
  private checkedForBackup = false;
66
74
 
@@ -418,19 +426,21 @@ export class RustBackupManager extends TypedEventEmitter<RustBackupCryptoEvents,
418
426
  this.emit(CryptoEvent.KeyBackupStatus, false);
419
427
  }
420
428
 
421
- private async backupKeysLoop(maxDelay = 10000): Promise<void> {
429
+ private async backupKeysLoop(): Promise<void> {
430
+ const logger = this.logger.getChild("[backupKeysLoop]");
431
+
422
432
  if (this.backupKeysLoopRunning) {
423
- this.logger.debug(`Backup loop already running`);
433
+ logger.debug(`Backup loop already running`);
424
434
  return;
425
435
  }
426
436
  this.backupKeysLoopRunning = true;
427
437
 
428
- this.logger.debug(`Backup: Starting keys upload loop for backup version:${this.activeBackupVersion}.`);
429
-
430
- // wait between 0 and `maxDelay` seconds, to avoid backup
431
- // requests from different clients hitting the server all at
432
- // the same time when a new key is sent
433
- const delay = Math.random() * maxDelay;
438
+ // Wait between 0 and `maxBackupLoopStartDelayMillis` milliseconds, to avoid backup requests from different
439
+ // clients hitting the server all at the same time when a new key is sent.
440
+ const delay = Math.random() * RustBackupManager.maxBackupLoopStartDelayMillis;
441
+ logger.debug(
442
+ `Starting keys upload loop for backup version ${this.activeBackupVersion}, but delaying startup by ${delay}ms`,
443
+ );
434
444
  await sleep(delay);
435
445
 
436
446
  try {
@@ -444,25 +454,28 @@ export class RustBackupManager extends TypedEventEmitter<RustBackupCryptoEvents,
444
454
 
445
455
  while (!this.stopped) {
446
456
  // Get a batch of room keys to upload
447
- let request: RustSdkCryptoJs.KeysBackupRequest | undefined = undefined;
457
+ let request;
448
458
  try {
449
- request = await logDuration(
450
- this.logger,
451
- "BackupRoomKeys: Get keys to backup from rust crypto-sdk",
452
- async () => {
453
- return await this.olmMachine.backupRoomKeys();
454
- },
455
- );
459
+ request = await this.olmMachine.backupRoomKeys();
460
+ if (request) {
461
+ logger.debug("Got keys to back up from crypto-sdk");
462
+ } else {
463
+ logger.debug(`No more keys to back up: ending loop for version ${this.activeBackupVersion}.`);
464
+ this.emit(CryptoEvent.KeyBackupSessionsRemaining, 0);
465
+ return;
466
+ }
456
467
  } catch (err) {
457
- this.logger.error("Backup: Failed to get keys to backup from rust crypto-sdk", err);
468
+ logger.error("Failed to get keys to backup from rust crypto-sdk: ending backup loop", err);
469
+ return;
458
470
  }
459
471
 
460
- if (!request || this.stopped || !this.activeBackupVersion) {
461
- this.logger.debug(`Backup: Ending loop for version ${this.activeBackupVersion}.`);
462
- if (!request) {
463
- // nothing more to upload
464
- this.emit(CryptoEvent.KeyBackupSessionsRemaining, 0);
465
- }
472
+ if (this.stopped) {
473
+ logger.debug(`Client stopping: ending loop for version ${this.activeBackupVersion}.`);
474
+ return;
475
+ }
476
+
477
+ if (!this.activeBackupVersion) {
478
+ logger.debug(`Backup no longer active: ending loop.`);
466
479
  return;
467
480
  }
468
481
 
@@ -485,7 +498,7 @@ export class RustBackupManager extends TypedEventEmitter<RustBackupCryptoEvents,
485
498
  const keyCount = await this.olmMachine.roomKeyCounts();
486
499
  remainingToUploadCount = keyCount.total - keyCount.backedUp;
487
500
  } catch (err) {
488
- this.logger.error("Backup: Failed to get key counts from rust crypto-sdk", err);
501
+ logger.error("Failed to get key counts from rust crypto-sdk", err);
489
502
  }
490
503
  }
491
504
 
@@ -501,15 +514,15 @@ export class RustBackupManager extends TypedEventEmitter<RustBackupCryptoEvents,
501
514
  }
502
515
  } catch (err) {
503
516
  numFailures++;
504
- this.logger.error("Backup: Error processing backup request for rust crypto-sdk", err);
517
+ logger.error("Error processing backup request for rust crypto-sdk", err);
505
518
  if (err instanceof MatrixError) {
506
519
  const errCode = err.data.errcode;
507
520
  if (errCode == "M_NOT_FOUND" || errCode == "M_WRONG_ROOM_KEYS_VERSION") {
508
- this.logger.debug(`Backup: Failed to upload keys to current vesion: ${errCode}.`);
521
+ logger.debug(`Failed to upload keys to current version: ${errCode}.`);
509
522
  try {
510
523
  await this.disableKeyBackup();
511
524
  } catch (error) {
512
- this.logger.error("Backup: An error occurred while disabling key backup:", error);
525
+ logger.error("An error occurred while disabling key backup:", error);
513
526
  }
514
527
  this.emit(CryptoEvent.KeyBackupFailed, err.data.errcode!);
515
528
  // There was an active backup and we are out of sync with the server
@@ -522,21 +535,21 @@ export class RustBackupManager extends TypedEventEmitter<RustBackupCryptoEvents,
522
535
  try {
523
536
  const waitTime = err.getRetryAfterMs();
524
537
  if (waitTime && waitTime > 0) {
538
+ logger.debug(`Sleeping ${waitTime}ms after ratelimit`);
525
539
  await sleep(waitTime);
526
540
  continue;
527
541
  }
528
542
  } catch (error) {
529
- this.logger.warn(
530
- "Backup: An error occurred while retrieving a rate-limit retry delay",
531
- error,
532
- );
543
+ logger.warn("An error occurred while retrieving a rate-limit retry delay", error);
533
544
  } // else go to the normal backoff
534
545
  }
535
546
  }
536
547
 
537
548
  // Some other errors (mx, network, or CORS or invalid urls?) anyhow backoff
538
549
  // exponential backoff if we have failures
539
- await sleep(1000 * Math.pow(2, Math.min(numFailures - 1, 4)));
550
+ const waitTime = 1000 * Math.pow(2, Math.min(numFailures - 1, 4));
551
+ logger.debug(`Sleeping ${waitTime}ms after failure #${numFailures}`);
552
+ await sleep(waitTime);
540
553
  }
541
554
  isFirstIteration = false;
542
555
  }
package/src/sync.ts CHANGED
@@ -1486,7 +1486,15 @@ export class SyncApi {
1486
1486
  // this fires a couple of times for some events. (eg state events are in the timeline and the state)
1487
1487
  // should this get a sync section as an additional event emission param (e, syncSection))?
1488
1488
  stateEvents.forEach(emitEvent);
1489
- timelineEvents.forEach(emitEvent);
1489
+ stateAfterEvents.forEach(emitEvent);
1490
+ // have to filter out all state events from the timeline when MSC4222 is enabled, in this case:
1491
+ // all state events are emitted by "state_after"
1492
+ // all state events in the timeline are either duplicates or are outdated and should be ignored
1493
+ const filteredTimelineEvents =
1494
+ "org.matrix.msc4222.state_after" in joinObj
1495
+ ? timelineEvents.filter((timelineEvent) => !timelineEvent.isState())
1496
+ : timelineEvents;
1497
+ filteredTimelineEvents.forEach(emitEvent);
1490
1498
  ephemeralEvents.forEach(emitEvent);
1491
1499
  accountDataEvents.forEach(emitEvent);
1492
1500
  stickyEvents