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
@@ -35,7 +35,7 @@ import {
35
35
  } from "./event.ts";
36
36
  import { EventStatus } from "./event-status.ts";
37
37
  import { RoomMember } from "./room-member.ts";
38
- import { type IRoomSummary, RoomSummary } from "./room-summary.ts";
38
+ import { type IRoomSummary, type Hero, RoomSummary } from "./room-summary.ts";
39
39
  import { logger } from "../logger.ts";
40
40
  import { TypedReEmitter } from "../ReEmitter.ts";
41
41
  import {
@@ -77,6 +77,7 @@ import { compareEventOrdering } from "./compare-event-ordering.ts";
77
77
  import * as utils from "../utils.ts";
78
78
  import { KnownMembership, type Membership } from "../@types/membership.ts";
79
79
  import { type Capabilities, type IRoomVersionsCapability, RoomVersionStability } from "../serverCapabilities.ts";
80
+ import { type MSC4186Hero } from "../sliding-sync.ts";
80
81
 
81
82
  // These constants are used as sane defaults when the homeserver doesn't support
82
83
  // the m.room_versions capability. In practice, KNOWN_SAFE_ROOM_VERSION should be
@@ -335,6 +336,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
335
336
  public readonly reEmitter: TypedReEmitter<RoomEmittedEvents, RoomEventHandlerMap>;
336
337
  private txnToEvent: Map<string, MatrixEvent> = new Map(); // Pending in-flight requests { string: MatrixEvent }
337
338
  private notificationCounts: NotificationCount = {};
339
+ private bumpStamp: number | undefined = undefined;
338
340
  private readonly threadNotifications = new Map<string, NotificationCount>();
339
341
  public readonly cachedThreadReadReceipts = new Map<string, CachedReceiptStructure[]>();
340
342
  // Useful to know at what point the current user has started using threads in this room
@@ -361,7 +363,16 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
361
363
  // read by megolm via getter; boolean value - null indicates "use global value"
362
364
  private blacklistUnverifiedDevices?: boolean;
363
365
  private selfMembership?: Membership;
364
- private summaryHeroes: string[] | null = null;
366
+ /**
367
+ * A `Hero` is a stripped `m.room.member` event which contains the important renderable fields from the event.
368
+ *
369
+ * It is used in MSC4186 (Simplified Sliding Sync) as a replacement for the old `summary` field.
370
+ *
371
+ * When we are doing old-style (`/v3/sync`) sync, we simulate the SSS behaviour by constructing
372
+ * a `Hero` object based on the user id we get from the summary. Obviously, in that case,
373
+ * the `Hero` will lack a `displayName` or `avatarUrl`.
374
+ */
375
+ private heroes: Hero[] | null = null;
365
376
  // flags to stop logspam about missing m.room.create events
366
377
  private getTypeWarning = false;
367
378
  private getVersionWarning = false;
@@ -879,7 +890,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
879
890
  // fall back to summary information
880
891
  const memberCount = this.getInvitedAndJoinedMemberCount();
881
892
  if (memberCount === 2) {
882
- return this.summaryHeroes?.[0];
893
+ return this.heroes?.[0]?.userId;
883
894
  }
884
895
  }
885
896
  }
@@ -897,8 +908,8 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
897
908
  }
898
909
  }
899
910
  // Remember, we're assuming this room is a DM, so returning the first member we find should be fine
900
- if (Array.isArray(this.summaryHeroes) && this.summaryHeroes.length) {
901
- return this.summaryHeroes[0];
911
+ if (Array.isArray(this.heroes) && this.heroes.length) {
912
+ return this.heroes[0].userId;
902
913
  }
903
914
  const members = this.currentState.getMembers();
904
915
  const anyMember = members.find((m) => m.userId !== this.myUserId);
@@ -940,12 +951,45 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
940
951
  if (nonFunctionalMemberCount > 2) return;
941
952
 
942
953
  // Prefer the list of heroes, if present. It should only include the single other user in the DM.
943
- const nonFunctionalHeroes = this.summaryHeroes?.filter((h) => !functionalMembers.includes(h));
954
+ const nonFunctionalHeroes = this.heroes?.filter((h) => !functionalMembers.includes(h.userId));
944
955
  const hasHeroes = Array.isArray(nonFunctionalHeroes) && nonFunctionalHeroes.length;
945
956
  if (hasHeroes) {
957
+ // use first hero that has a display name or avatar url, or whose user ID
958
+ // can be looked up as a member of the room
959
+ for (const hero of nonFunctionalHeroes) {
960
+ // If the hero was from a legacy sync (`/v3/sync`), we will need to look the user ID up in the room
961
+ // the display name and avatar URL will not be set.
962
+ if (!hero.fromMSC4186) {
963
+ // attempt to look up renderable fields from the m.room.member event if it exists
964
+ const member = this.getMember(hero.userId);
965
+ if (member) {
966
+ return member;
967
+ }
968
+ } else {
969
+ // use the Hero supplied values for the room member.
970
+ // TODO: It's unfortunate that this function, which clearly only cares about the
971
+ // avatar url, returns the entire RoomMember event. We need to fake an event
972
+ // to meet this API shape.
973
+ const heroMember = new RoomMember(this.roomId, hero.userId);
974
+ // set the display name and avatar url
975
+ heroMember.setMembershipEvent(
976
+ new MatrixEvent({
977
+ // ensure it's unique even if we hit the same millisecond
978
+ event_id: "$" + this.roomId + hero.userId + new Date().getTime(),
979
+ type: EventType.RoomMember,
980
+ state_key: hero.userId,
981
+ content: {
982
+ displayname: hero.displayName,
983
+ avatar_url: hero.avatarUrl,
984
+ },
985
+ }),
986
+ );
987
+ return heroMember;
988
+ }
989
+ }
946
990
  const availableMember = nonFunctionalHeroes
947
- .map((userId) => {
948
- return this.getMember(userId);
991
+ .map((hero) => {
992
+ return this.getMember(hero.userId);
949
993
  })
950
994
  .find((member) => !!member);
951
995
  if (availableMember) {
@@ -970,8 +1014,8 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
970
1014
  // trust and try falling back to a hero, creating a one-off member for it
971
1015
  if (hasHeroes) {
972
1016
  const availableUser = nonFunctionalHeroes
973
- .map((userId) => {
974
- return this.client.getUser(userId);
1017
+ .map((hero) => {
1018
+ return this.client.getUser(hero.userId);
975
1019
  })
976
1020
  .find((user) => !!user);
977
1021
  if (availableUser) {
@@ -1602,6 +1646,24 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
1602
1646
  this.emit(RoomEvent.UnreadNotifications);
1603
1647
  }
1604
1648
 
1649
+ /**
1650
+ * Set the bump stamp for this room. This can be used for sorting rooms when the timeline
1651
+ * entries are unknown. Used in MSC4186: Simplified Sliding Sync.
1652
+ * @param bumpStamp The bump_stamp value from the server
1653
+ */
1654
+ public setBumpStamp(bumpStamp: number): void {
1655
+ this.bumpStamp = bumpStamp;
1656
+ }
1657
+
1658
+ /**
1659
+ * Get the bump stamp for this room. This can be used for sorting rooms when the timeline
1660
+ * entries are unknown. Used in MSC4186: Simplified Sliding Sync.
1661
+ * @returns The bump stamp for the room, if it exists.
1662
+ */
1663
+ public getBumpStamp(): number | undefined {
1664
+ return this.bumpStamp;
1665
+ }
1666
+
1605
1667
  /**
1606
1668
  * Set one of the notification counts for this room
1607
1669
  * @param type - The type of notification count to set.
@@ -1616,8 +1678,13 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
1616
1678
  return this.setUnreadNotificationCount(type, count);
1617
1679
  }
1618
1680
 
1681
+ /**
1682
+ * Takes a legacy room summary (/v3/sync as opposed to MSC4186) and updates the room with it.
1683
+ *
1684
+ * @param summary - The room summary to update the room with
1685
+ */
1619
1686
  public setSummary(summary: IRoomSummary): void {
1620
- const heroes = summary["m.heroes"];
1687
+ const heroes = summary["m.heroes"]?.map((h) => ({ userId: h, fromMSC4186: false }));
1621
1688
  const joinedCount = summary["m.joined_member_count"];
1622
1689
  const invitedCount = summary["m.invited_member_count"];
1623
1690
  if (Number.isInteger(joinedCount)) {
@@ -1627,17 +1694,53 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
1627
1694
  this.currentState.setInvitedMemberCount(invitedCount!);
1628
1695
  }
1629
1696
  if (Array.isArray(heroes)) {
1630
- // be cautious about trusting server values,
1631
- // and make sure heroes doesn't contain our own id
1632
- // just to be sure
1633
- this.summaryHeroes = heroes.filter((userId) => {
1634
- return userId !== this.myUserId;
1697
+ // filter out ourselves just in case
1698
+ this.heroes = heroes.filter((h) => {
1699
+ return h.userId != this.myUserId;
1635
1700
  });
1636
1701
  }
1637
1702
 
1638
1703
  this.emit(RoomEvent.Summary, summary);
1639
1704
  }
1640
1705
 
1706
+ /**
1707
+ * Takes information from the MSC4186 room summary and updates the room with it.
1708
+ *
1709
+ * @param heroes - The room's hero members
1710
+ * @param joinedCount - The number of joined members
1711
+ * @param invitedCount - The number of invited members
1712
+ */
1713
+ public setMSC4186SummaryData(
1714
+ heroes: MSC4186Hero[] | undefined,
1715
+ joinedCount: number | undefined,
1716
+ invitedCount: number | undefined,
1717
+ ): void {
1718
+ if (heroes) {
1719
+ this.heroes = heroes
1720
+ .filter((h) => h.user_id !== this.myUserId)
1721
+ .map((h) => ({
1722
+ userId: h.user_id,
1723
+ displayName: h.displayname,
1724
+ avatarUrl: h.avatar_url,
1725
+ fromMSC4186: true,
1726
+ }));
1727
+ }
1728
+ if (joinedCount !== undefined && Number.isInteger(joinedCount)) {
1729
+ this.currentState.setJoinedMemberCount(joinedCount);
1730
+ }
1731
+ if (invitedCount !== undefined && Number.isInteger(invitedCount)) {
1732
+ this.currentState.setInvitedMemberCount(invitedCount);
1733
+ }
1734
+
1735
+ // Construct a summary object to emit as the event wants the info in a single object
1736
+ // more like old-style (/v3/sync) summaries.
1737
+ this.emit(RoomEvent.Summary, {
1738
+ "m.heroes": this.heroes ? this.heroes.map((h) => h.userId) : [],
1739
+ "m.joined_member_count": joinedCount,
1740
+ "m.invited_member_count": invitedCount,
1741
+ });
1742
+ }
1743
+
1641
1744
  /**
1642
1745
  * Whether to send encrypted messages to devices within this room.
1643
1746
  * @param value - true to blacklist unverified devices, null
@@ -3459,18 +3562,25 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
3459
3562
  // get service members (e.g. helper bots) for exclusion
3460
3563
  const excludedUserIds = this.getFunctionalMembers();
3461
3564
 
3462
- // get members that are NOT ourselves and are actually in the room.
3565
+ // get members from heroes that are NOT ourselves
3463
3566
  let otherNames: string[] = [];
3464
- if (this.summaryHeroes) {
3465
- // if we have a summary, the member state events should be in the room state
3466
- this.summaryHeroes.forEach((userId) => {
3567
+ if (this.heroes) {
3568
+ // if we have heroes, use those as the names
3569
+ this.heroes.forEach((hero) => {
3467
3570
  // filter service members
3468
- if (excludedUserIds.includes(userId)) {
3571
+ if (excludedUserIds.includes(hero.userId)) {
3469
3572
  inviteJoinCount--;
3470
3573
  return;
3471
3574
  }
3472
- const member = this.getMember(userId);
3473
- otherNames.push(member ? member.name : userId);
3575
+ // If the hero has a display name, use that.
3576
+ // Otherwise, look their user ID up in the membership and use
3577
+ // the name from there, or the user ID as a last resort.
3578
+ if (hero.displayName) {
3579
+ otherNames.push(hero.displayName);
3580
+ } else {
3581
+ const member = this.getMember(hero.userId);
3582
+ otherNames.push(member ? member.name : hero.userId);
3583
+ }
3474
3584
  });
3475
3585
  } else {
3476
3586
  let otherMembers = this.currentState.getMembers().filter((m) => {
@@ -14,9 +14,9 @@ See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import { type IdTokenClaims, OidcClient, WebStorageStateStore } from "oidc-client-ts";
17
+ import { type IdTokenClaims, OidcClient, WebStorageStateStore, ErrorResponse } from "oidc-client-ts";
18
18
 
19
- import { type AccessTokens } from "../http-api/index.ts";
19
+ import { type AccessTokens, TokenRefreshLogoutError } from "../http-api/index.ts";
20
20
  import { generateScope } from "./authorize.ts";
21
21
  import { discoverAndValidateOIDCIssuerWellKnown } from "./discovery.ts";
22
22
  import { logger } from "../logger.ts";
@@ -104,6 +104,12 @@ export class OidcTokenRefresher {
104
104
  try {
105
105
  const tokens = await this.inflightRefreshRequest;
106
106
  return tokens;
107
+ } catch (e) {
108
+ // If we encounter an OIDC error then signal that it should cause a logout by upgrading it to a TokenRefreshLogoutError
109
+ if (e instanceof ErrorResponse) {
110
+ throw new TokenRefreshLogoutError(e);
111
+ }
112
+ throw e;
107
113
  } finally {
108
114
  this.inflightRefreshRequest = undefined;
109
115
  }
@@ -308,6 +308,28 @@ export class PushProcessor {
308
308
  return newRules;
309
309
  }
310
310
 
311
+ /**
312
+ * Create a RegExp object for the given glob pattern with a single capture group around the pattern itself, caching the result.
313
+ * No cache invalidation is present currently,
314
+ * as this will be inherently bounded to the size of the user's own push rules.
315
+ * @param pattern - the glob pattern to convert to a RegExp
316
+ * @param alignToWordBoundary - whether to align the pattern to word boundaries,
317
+ * as specified for `content.body` matches, will use lookaround assertions to ensure the match only includes the pattern
318
+ * @param flags - the flags to pass to the RegExp constructor, defaults to case-insensitive
319
+ */
320
+ public static getPushRuleGlobRegex(pattern: string, alignToWordBoundary = false, flags = "i"): RegExp {
321
+ const [prefix, suffix] = alignToWordBoundary ? ["(?<=^|\\W)", "(?=\\W|$)"] : ["^", "$"];
322
+ const cacheKey = `${alignToWordBoundary}-${flags}-${pattern}`;
323
+
324
+ if (!PushProcessor.cachedGlobToRegex[cacheKey]) {
325
+ PushProcessor.cachedGlobToRegex[cacheKey] = new RegExp(
326
+ prefix + "(" + globToRegexp(pattern) + ")" + suffix,
327
+ flags,
328
+ );
329
+ }
330
+ return PushProcessor.cachedGlobToRegex[cacheKey];
331
+ }
332
+
311
333
  /**
312
334
  * Pre-caches the parsed keys for push rules and cleans out any obsolete cache
313
335
  * entries. Should be called after push rules are updated.
@@ -567,11 +589,9 @@ export class PushProcessor {
567
589
  return false;
568
590
  }
569
591
 
570
- const regex =
571
- cond.key === "content.body"
572
- ? this.createCachedRegex("(^|\\W)", cond.pattern, "(\\W|$)")
573
- : this.createCachedRegex("^", cond.pattern, "$");
574
-
592
+ // Align to word boundary on `content.body` matches, whole string otherwise
593
+ // https://spec.matrix.org/v1.13/client-server-api/#conditions-1
594
+ const regex = PushProcessor.getPushRuleGlobRegex(cond.pattern, cond.key === "content.body");
575
595
  return !!val.match(regex);
576
596
  }
577
597
 
@@ -621,17 +641,6 @@ export class PushProcessor {
621
641
  );
622
642
  }
623
643
 
624
- private createCachedRegex(prefix: string, glob: string, suffix: string): RegExp {
625
- if (PushProcessor.cachedGlobToRegex[glob]) {
626
- return PushProcessor.cachedGlobToRegex[glob];
627
- }
628
- PushProcessor.cachedGlobToRegex[glob] = new RegExp(
629
- prefix + globToRegexp(glob) + suffix,
630
- "i", // Case insensitive
631
- );
632
- return PushProcessor.cachedGlobToRegex[glob];
633
- }
634
-
635
644
  /**
636
645
  * Parse the key into the separate fields to search by splitting on
637
646
  * unescaped ".", and then removing any escape characters.
@@ -219,6 +219,12 @@ export class OutgoingRequestProcessor {
219
219
 
220
220
  // we use the full prefix
221
221
  prefix: "",
222
+
223
+ // We set a timeout of 60 seconds to guard against requests getting stuck forever and wedging the
224
+ // request loop (cf https://github.com/element-hq/element-web/issues/29534).
225
+ //
226
+ // (XXX: should we do this in the whole of the js-sdk?)
227
+ localTimeoutMs: 60000,
222
228
  };
223
229
 
224
230
  return await this.http.authedRequest<string>(method, path, queryParams, body, opts);
@@ -1635,7 +1635,6 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, CryptoEventH
1635
1635
 
1636
1636
  /** called by the sync loop after processing each sync.
1637
1637
  *
1638
- * TODO: figure out something equivalent for sliding sync.
1639
1638
  *
1640
1639
  * @param syncState - information on the completed sync.
1641
1640
  */
@@ -1647,6 +1646,13 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, CryptoEventH
1647
1646
  });
1648
1647
  }
1649
1648
 
1649
+ /**
1650
+ * Implementation of {@link CryptoApi#markAllTrackedUsersAsDirty}.
1651
+ */
1652
+ public async markAllTrackedUsersAsDirty(): Promise<void> {
1653
+ await this.olmMachine.markAllTrackedUsersAsDirty();
1654
+ }
1655
+
1650
1656
  /**
1651
1657
  * Handle an incoming m.key.verification.request event, received either in-room or in a to-device message.
1652
1658
  *
@@ -82,9 +82,16 @@ class ExtensionE2EE implements Extension<ExtensionE2EERequest, ExtensionE2EEResp
82
82
  return ExtensionState.PreProcess;
83
83
  }
84
84
 
85
- public onRequest(isInitial: boolean): ExtensionE2EERequest | undefined {
86
- if (!isInitial) {
87
- return undefined;
85
+ public async onRequest(isInitial: boolean): Promise<ExtensionE2EERequest> {
86
+ if (isInitial) {
87
+ // In SSS, the `?pos=` contains the stream position for device list updates.
88
+ // If we do not have a `?pos=` (e.g because we forgot it, or because the server
89
+ // invalidated our connection) then we MUST invlaidate all device lists because
90
+ // the server will not tell us the delta. This will then cause UTDs as we will fail
91
+ // to encrypt for new devices. This is an expensive call, so we should
92
+ // really really remember `?pos=` wherever possible.
93
+ logger.log("ExtensionE2EE: invalidating all device lists due to missing 'pos'");
94
+ await this.crypto.markAllTrackedUsersAsDirty();
88
95
  }
89
96
  return {
90
97
  enabled: true, // this is sticky so only send it on the initial request
@@ -134,15 +141,12 @@ class ExtensionToDevice implements Extension<ExtensionToDeviceRequest, Extension
134
141
  return ExtensionState.PreProcess;
135
142
  }
136
143
 
137
- public onRequest(isInitial: boolean): ExtensionToDeviceRequest {
138
- const extReq: ExtensionToDeviceRequest = {
144
+ public async onRequest(isInitial: boolean): Promise<ExtensionToDeviceRequest> {
145
+ return {
139
146
  since: this.nextBatch !== null ? this.nextBatch : undefined,
147
+ limit: 100,
148
+ enabled: true,
140
149
  };
141
- if (isInitial) {
142
- extReq["limit"] = 100;
143
- extReq["enabled"] = true;
144
- }
145
- return extReq;
146
150
  }
147
151
 
148
152
  public async onResponse(data: ExtensionToDeviceResponse): Promise<void> {
@@ -216,10 +220,7 @@ class ExtensionAccountData implements Extension<ExtensionAccountDataRequest, Ext
216
220
  return ExtensionState.PostProcess;
217
221
  }
218
222
 
219
- public onRequest(isInitial: boolean): ExtensionAccountDataRequest | undefined {
220
- if (!isInitial) {
221
- return undefined;
222
- }
223
+ public async onRequest(isInitial: boolean): Promise<ExtensionAccountDataRequest> {
223
224
  return {
224
225
  enabled: true,
225
226
  };
@@ -286,10 +287,7 @@ class ExtensionTyping implements Extension<ExtensionTypingRequest, ExtensionTypi
286
287
  return ExtensionState.PostProcess;
287
288
  }
288
289
 
289
- public onRequest(isInitial: boolean): ExtensionTypingRequest | undefined {
290
- if (!isInitial) {
291
- return undefined; // don't send a JSON object for subsequent requests, we don't need to.
292
- }
290
+ public async onRequest(isInitial: boolean): Promise<ExtensionTypingRequest> {
293
291
  return {
294
292
  enabled: true,
295
293
  };
@@ -325,13 +323,10 @@ class ExtensionReceipts implements Extension<ExtensionReceiptsRequest, Extension
325
323
  return ExtensionState.PostProcess;
326
324
  }
327
325
 
328
- public onRequest(isInitial: boolean): ExtensionReceiptsRequest | undefined {
329
- if (isInitial) {
330
- return {
331
- enabled: true,
332
- };
333
- }
334
- return undefined; // don't send a JSON object for subsequent requests, we don't need to.
326
+ public async onRequest(isInitial: boolean): Promise<ExtensionReceiptsRequest> {
327
+ return {
328
+ enabled: true,
329
+ };
335
330
  }
336
331
 
337
332
  public async onResponse(data: ExtensionReceiptsResponse): Promise<void> {
@@ -442,6 +437,7 @@ export class SlidingSyncSdk {
442
437
  }
443
438
  } else {
444
439
  this.failCount = 0;
440
+ logger.log(`SlidingSyncState.RequestFinished with ${Object.keys(resp?.rooms || []).length} rooms`);
445
441
  }
446
442
  break;
447
443
  }
@@ -580,7 +576,7 @@ export class SlidingSyncSdk {
580
576
 
581
577
  // TODO: handle threaded / beacon events
582
578
 
583
- if (roomData.initial) {
579
+ if (roomData.limited || roomData.initial) {
584
580
  // we should not know about any of these timeline entries if this is a genuinely new room.
585
581
  // If we do, then we've effectively done scrollback (e.g requesting timeline_limit: 1 for
586
582
  // this room, then timeline_limit: 50).
@@ -637,6 +633,9 @@ export class SlidingSyncSdk {
637
633
  room.setUnreadNotificationCount(NotificationCountType.Highlight, roomData.highlight_count);
638
634
  }
639
635
  }
636
+ if (roomData.bump_stamp) {
637
+ room.setBumpStamp(roomData.bump_stamp);
638
+ }
640
639
 
641
640
  if (Number.isInteger(roomData.invited_count)) {
642
641
  room.currentState.setInvitedMemberCount(roomData.invited_count!);
@@ -656,11 +655,10 @@ export class SlidingSyncSdk {
656
655
  inviteStateEvents.forEach((e) => {
657
656
  this.client.emit(ClientEvent.Event, e);
658
657
  });
659
- room.updateMyMembership(KnownMembership.Invite);
660
658
  return;
661
659
  }
662
660
 
663
- if (roomData.initial) {
661
+ if (roomData.limited) {
664
662
  // set the back-pagination token. Do this *before* adding any
665
663
  // events so that clients can start back-paginating.
666
664
  room.getLiveTimeline().setPaginationToken(roomData.prev_batch ?? null, EventTimeline.BACKWARDS);
@@ -728,6 +726,8 @@ export class SlidingSyncSdk {
728
726
  // synchronous execution prior to emitting SlidingSyncState.Complete
729
727
  room.updateMyMembership(KnownMembership.Join);
730
728
 
729
+ room.setMSC4186SummaryData(roomData.heroes, roomData.joined_count, roomData.invited_count);
730
+
731
731
  room.recalculate();
732
732
  if (roomData.initial) {
733
733
  client.store.storeRoom(room);