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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "matrix-js-sdk",
3
- "version": "42.4.0",
3
+ "version": "43.0.0",
4
4
  "description": "Matrix Client-Server SDK for Javascript",
5
5
  "engines": {
6
6
  "node": ">=22.0.0"
@@ -8,7 +8,7 @@
8
8
  "devEngines": {
9
9
  "packageManager": {
10
10
  "name": "pnpm",
11
- "version": "11.9.0",
11
+ "version": "12.3.4",
12
12
  "onFail": "download"
13
13
  }
14
14
  },
@@ -68,8 +68,8 @@
68
68
  "@types/node": "22",
69
69
  "@types/sdp-transform": "^3.0.0",
70
70
  "@typescript/native": "npm:typescript@7.0.2",
71
- "@vitest/coverage-v8": "^4.0.17",
72
- "@vitest/ui": "^4.0.17",
71
+ "@vitest/coverage-v8": "^5.0.0",
72
+ "@vitest/ui": "^5.0.0",
73
73
  "concurrently": "^10.0.0",
74
74
  "debug": "^4.3.4",
75
75
  "fake-indexeddb": "^6.0.0",
@@ -79,7 +79,7 @@
79
79
  "knip": "^6.0.0",
80
80
  "lint-staged": "^17.0.0",
81
81
  "matrix-mock-request": "^2.5.0",
82
- "oxfmt": "^0.65.0",
82
+ "oxfmt": "^0.67.0",
83
83
  "oxlint": "^1.70.0",
84
84
  "oxlint-tsgolint": "^7.0.0",
85
85
  "typedoc": "^0.28.1",
@@ -87,7 +87,7 @@
87
87
  "typedoc-plugin-mdn-links": "^5.0.0",
88
88
  "typedoc-plugin-missing-exports": "^4.0.0",
89
89
  "typescript": "npm:@typescript/typescript6@6.0.2",
90
- "vitest": "^4.0.17"
90
+ "vitest": "^5.0.0"
91
91
  },
92
92
  "scripts": {
93
93
  "start": "concurrently 'pnpm run build:compile --watch' 'pnpm run build:types --watch'",
@@ -155,6 +155,8 @@ export enum RuleId {
155
155
  PollStartOneToOneUnstable = ".org.matrix.msc3930.rule.poll_start_one_to_one",
156
156
  PollEndOneToOne = ".m.rule.poll_end_one_to_one",
157
157
  PollEndOneToOneUnstable = ".org.matrix.msc3930.rule.poll_end_one_to_one",
158
+ RtcInviteForMe = ".org.matrix.msc4075.rule.rtc.invite_for_me",
159
+ RtcInviteForRoom = ".org.matrix.msc4075.rule.rtc.invite_for_room",
158
160
  }
159
161
 
160
162
  export type PushRuleSet = {
@@ -361,7 +361,7 @@ export interface TimelineEvents {
361
361
  [M_BEACON.name]: MBeaconEventContent;
362
362
  [M_POLL_START.name]: PollStartEventContent;
363
363
  [M_POLL_END.name]: PollEndEventContent;
364
- [EventType.RTCMembership]: RtcMembershipData | { msc4354_sticky_key: string }; // An object containing just the sticky key is empty.
364
+ [EventType.RTCMembership]: RtcMembershipData | { slot_id: string; msc4354_sticky_key: string };
365
365
  }
366
366
 
367
367
  /**
@@ -16,20 +16,23 @@ limitations under the License.
16
16
 
17
17
  import type * as RustSdkCryptoJs from "@matrix-org/matrix-sdk-crypto-wasm";
18
18
 
19
+ /** Return type of `SecretsBundle.to_json` */
20
+ type SecretsBundleJson = {
21
+ cross_signing: {
22
+ master_key: string;
23
+ self_signing_key: string;
24
+ user_signing_key: string;
25
+ };
26
+ backup?: {
27
+ algorithm: string;
28
+ key: string;
29
+ backup_version: string;
30
+ };
31
+ };
32
+
19
33
  declare module "@matrix-org/matrix-sdk-crypto-wasm" {
20
34
  interface SecretsBundle {
21
- to_json(): Promise<{
22
- cross_signing: {
23
- master_key: string;
24
- self_signing_key: string;
25
- user_signing_key: string;
26
- };
27
- backup?: {
28
- algorithm: string;
29
- key: string;
30
- backup_version: string;
31
- };
32
- }>;
35
+ to_json(): Promise<SecretsBundleJson>;
33
36
  }
34
37
 
35
38
  interface Device {
package/src/client.ts CHANGED
@@ -73,7 +73,7 @@ import {
73
73
  MediaPrefix,
74
74
  Method,
75
75
  retryNetworkOperation,
76
- type TokenRefreshFunction,
76
+ type TokenRefreshCallback,
77
77
  type Upload,
78
78
  type UploadOpts,
79
79
  type UploadResponse,
@@ -251,7 +251,9 @@ import {
251
251
  type LivekitGetTokenResponse,
252
252
  type Transport,
253
253
  } from "./matrixrtc/index.ts";
254
+ import { type IRTCDeclineContent, RTC_NOTIFICATION_MAX_LIFETIME_MS } from "./matrixrtc/types.ts";
254
255
  import { RetentionPolicyService } from "./retentionPolicy.ts";
256
+ import { fetchRoomSummary, type RoomSummary } from "./room-summary-api.ts";
255
257
  import { createRtcTransportsCachedValue } from "./rtcTransportsCachedValue.ts";
256
258
  import { createWellKnownCachedValue } from "./wellKnownCachedValue.ts";
257
259
  import { type PollingCachedValue } from "./pollingCachedValue.ts";
@@ -319,15 +321,32 @@ export interface ICreateClientOpts {
319
321
  */
320
322
  deviceId?: string;
321
323
 
324
+ /**
325
+ * The access token to use. Can be omitted if to call only unauthenticated APIs, or provided to
326
+ * create an authenticated client.
327
+ */
322
328
  accessToken?: string;
329
+
330
+ /**
331
+ * The current refresh token, or omit if the session has no refresh token in which case the tokens
332
+ * will not be refreshed.
333
+ */
323
334
  refreshToken?: string;
324
335
 
325
336
  /**
326
- * Function used to attempt refreshing access and refresh tokens
327
- * Called by http-api when a possibly expired token is encountered
328
- * and a refreshToken is found
337
+ * Called when the access tokens are refreshed.
338
+ * The client must replace the tokens it had stored previously which will no
339
+ * longer be valid.
340
+ *
341
+ * Required if a {@link refreshToken} is provided.
329
342
  */
330
- tokenRefreshFunction?: TokenRefreshFunction;
343
+ onTokenRefresh?: TokenRefreshCallback;
344
+
345
+ /**
346
+ * If this is an OAuth2-native session (as per MSC2965/MSC3861), the OAuth client ID this
347
+ * application is registered with the delegated auth server under.
348
+ */
349
+ oauthClientId?: string;
331
350
 
332
351
  /**
333
352
  * Identity server provider to retrieve the user's access token when accessing
@@ -865,25 +884,8 @@ interface IThirdPartyUser {
865
884
  fields: object;
866
885
  }
867
886
 
868
- /**
869
- * The summary of a room as defined by an initial version of MSC3266 and implemented in Synapse
870
- * Proposed at https://github.com/matrix-org/matrix-doc/pull/3266
871
- */
872
- export interface RoomSummary extends Omit<IPublicRoomsChunkRoom, "canonical_alias" | "aliases"> {
873
- /**
874
- * The current membership of this user in the room.
875
- * Usually "leave" if the room is fetched over federation.
876
- */
877
- "membership"?: Membership;
878
- /**
879
- * Version of the room.
880
- */
881
- "im.nheko.summary.room_version"?: string;
882
- /**
883
- * The encryption algorithm used for this room, if the room is encrypted.
884
- */
885
- "im.nheko.summary.encryption"?: string;
886
- }
887
+ // Re-export for backwards compatibility
888
+ export { type RoomSummary };
887
889
 
888
890
  interface IRoomHierarchy {
889
891
  rooms: IHierarchyRoom[];
@@ -1358,7 +1360,14 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
1358
1360
  idBaseUrl: opts.idBaseUrl,
1359
1361
  accessToken: opts.accessToken,
1360
1362
  refreshToken: opts.refreshToken,
1361
- tokenRefreshFunction: opts.tokenRefreshFunction,
1363
+ onTokenRefresh: opts.onTokenRefresh,
1364
+ oauth2ClientConfig: opts.oauthClientId
1365
+ ? {
1366
+ clientId: opts.oauthClientId,
1367
+ deviceId: this.deviceId ?? undefined,
1368
+ }
1369
+ : undefined,
1370
+ authMetadataCallback: this.getAuthMetadata.bind(this),
1362
1371
  prefix: ClientPrefix.V3,
1363
1372
  onlyData: true,
1364
1373
  extraParams: opts.queryParams,
@@ -2769,7 +2778,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
2769
2778
  * If we expect that an event is part of a thread but is missing the relation
2770
2779
  * we need to add it manually, as well as the reply fallback
2771
2780
  */
2772
- private addThreadRelationIfNeeded(content: IContent, threadId: string | null, roomId: string): void {
2781
+ protected addThreadRelationIfNeeded(content: IContent, threadId: string | null, roomId: string): void {
2773
2782
  if (threadId && !content["m.relates_to"]?.rel_type) {
2774
2783
  const isReply = !!content["m.relates_to"]?.["m.in_reply_to"];
2775
2784
  content["m.relates_to"] = {
@@ -2794,12 +2803,18 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
2794
2803
  }
2795
2804
 
2796
2805
  /**
2797
- * @param eventObject - An object with the partial structure of an event, to which event_id, user_id, room_id and origin_server_ts will be added.
2798
- * @param txnId - Optional.
2799
- * @returns Promise which resolves: to an empty object `{}`
2806
+ * Sends an event, adding it to the local echo and encrypting it if the room requires it.
2807
+ *
2808
+ * @param params - What to send and where.
2809
+ * @param params.roomId - The room to send the event to.
2810
+ * @param params.threadId - The thread to send the event in, or `null` for the main timeline.
2811
+ * @param params.eventObject - An object with the partial structure of an event, to which event_id, user_id, room_id and origin_server_ts will be added.
2812
+ * @param params.queryDict - Optional query parameters for the send request.
2813
+ * @param params.txnId - Optional transaction ID. Generated if omitted.
2814
+ * @returns Promise which resolves: to an object with the ID of the sent event.
2800
2815
  * @returns Rejects: with an error response.
2801
2816
  */
2802
- private sendCompleteEvent(params: {
2817
+ protected sendCompleteEvent(params: {
2803
2818
  roomId: string;
2804
2819
  threadId: string | null;
2805
2820
  eventObject: Partial<IEvent>;
@@ -2808,13 +2823,18 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
2808
2823
  }): Promise<ISendEventResponse>;
2809
2824
  /**
2810
2825
  * Sends a delayed event (MSC4140).
2811
- * @param eventObject - An object with the partial structure of an event, to which event_id, user_id, room_id and origin_server_ts will be added.
2812
- * @param delayOpts - Properties of the delay for this event.
2813
- * @param txnId - Optional.
2814
- * @returns Promise which resolves: to an empty object `{}`
2826
+ *
2827
+ * @param params - What to send and where.
2828
+ * @param params.roomId - The room to send the event to.
2829
+ * @param params.threadId - The thread to send the event in, or `null` for the main timeline.
2830
+ * @param params.eventObject - An object with the partial structure of an event, to which event_id, user_id, room_id and origin_server_ts will be added.
2831
+ * @param params.delayOpts - Properties of the delay for this event.
2832
+ * @param params.queryDict - Optional query parameters for the send request.
2833
+ * @param params.txnId - Optional transaction ID. Generated if omitted.
2834
+ * @returns Promise which resolves: to an object with the ID of the scheduled delayed event.
2815
2835
  * @returns Rejects: with an error response.
2816
2836
  */
2817
- private sendCompleteEvent(params: {
2837
+ protected sendCompleteEvent(params: {
2818
2838
  roomId: string;
2819
2839
  threadId: string | null;
2820
2840
  eventObject: Partial<IEvent>;
@@ -2822,7 +2842,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
2822
2842
  queryDict?: QueryDict;
2823
2843
  txnId?: string;
2824
2844
  }): Promise<SendDelayedEventResponse>;
2825
- private sendCompleteEvent({
2845
+ protected sendCompleteEvent({
2826
2846
  roomId,
2827
2847
  threadId,
2828
2848
  eventObject,
@@ -3952,17 +3972,38 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
3952
3972
  }
3953
3973
 
3954
3974
  /**
3975
+ * Declines a MatrixRTC notification.
3955
3976
  *
3956
- * @param roomId
3957
- * @param notificationEventId
3958
- * @returns
3977
+ * The decline is sent as a sticky event (MSC4354) keyed on the notification's event ID, so that the
3978
+ * user's other devices stop notifying. If the server doesn't support sticky events, a regular event is
3979
+ * sent instead. That one still counts as a decline for any device that receives it, but it isn't
3980
+ * re-delivered after a gappy or initial sync the way a sticky event is, so a device that was offline
3981
+ * when it was sent keeps notifying until the notification's lifetime elapses.
3982
+ *
3983
+ * @param roomId The room the notification was received in.
3984
+ * @param notificationEventId The event ID of the notification being declined.
3985
+ * @param stickyDurationMs How long (in milliseconds) the decline should stay sticky. Must not be smaller
3986
+ * than the notification's `lifetime`, otherwise the notification can become valid again once the decline
3987
+ * expires. Defaults to the maximum lifetime a notification may have.
3959
3988
  * @throws May throw a `MatrixSafetyError` if content is deemed unsafe.
3960
3989
  * @see MatrixSafetyError
3961
3990
  */
3962
- public sendRtcDecline(roomId: string, notificationEventId: string): Promise<ISendEventResponse> {
3963
- return this.sendEvent(roomId, EventType.RTCDecline, {
3991
+ public async sendRtcDecline(
3992
+ roomId: string,
3993
+ notificationEventId: string,
3994
+ stickyDurationMs: number = RTC_NOTIFICATION_MAX_LIFETIME_MS,
3995
+ ): Promise<ISendEventResponse> {
3996
+ const content: IRTCDeclineContent = {
3964
3997
  "m.relates_to": { event_id: notificationEventId, rel_type: RelationType.Reference },
3965
- });
3998
+ "msc4354_sticky_key": notificationEventId,
3999
+ };
4000
+ try {
4001
+ return await this._unstable_sendStickyEvent(roomId, stickyDurationMs, null, EventType.RTCDecline, content);
4002
+ } catch (error) {
4003
+ if (!(error instanceof UnsupportedStickyEventsEndpointError)) throw error;
4004
+ this.logger.debug("Server does not support sticky events, sending decline as a regular event");
4005
+ return await this.sendEvent(roomId, EventType.RTCDecline, content);
4006
+ }
3966
4007
  }
3967
4008
 
3968
4009
  /**
@@ -6299,6 +6340,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
6299
6340
  /**
6300
6341
  * Get the API versions supported by the server, along with any
6301
6342
  * unstable APIs it supports
6343
+ *
6302
6344
  * @returns The server /versions response
6303
6345
  */
6304
6346
  public async getVersions(): Promise<IServerVersions> {
@@ -6847,7 +6889,12 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
6847
6889
  * method is called. The state of the MatrixClient object is not affected:
6848
6890
  * it is up to the caller to either reset or destroy the MatrixClient after
6849
6891
  * this method succeeds.
6850
- * @param stopClient - whether to stop the client before calling /logout to prevent invalid token errors.
6892
+ *
6893
+ * For an OAuth2-native session (ie. one created with `oauth2ClientConfig`), this revokes the access
6894
+ * and refresh tokens directly with the delegated auth server instead of calling the `/logout` API,
6895
+ * since the homeserver does not own the tokens in that case.
6896
+ *
6897
+ * @param stopClient - whether to stop the client before logging out to prevent invalid token errors.
6851
6898
  * @returns Promise which resolves: On success, the empty object `{}`
6852
6899
  */
6853
6900
  public async logout(stopClient = false): Promise<EmptyObject> {
@@ -6856,6 +6903,11 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
6856
6903
  this.http.abort();
6857
6904
  }
6858
6905
 
6906
+ if (this.http.opts.oauth2ClientConfig) {
6907
+ await this.http.revokeOAuthTokens();
6908
+ return {};
6909
+ }
6910
+
6859
6911
  return this.http.authedRequest(Method.Post, "/logout");
6860
6912
  }
6861
6913
 
@@ -8856,26 +8908,18 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
8856
8908
  }
8857
8909
 
8858
8910
  /**
8859
- * Fetches the summary of a room as defined by an initial version of MSC3266 and implemented in Synapse
8860
- * Proposed at https://github.com/matrix-org/matrix-doc/pull/3266
8911
+ * Fetches the summary of a room.
8912
+ * https://spec.matrix.org/latest/client-server-api/#get_matrixclientv1room_summaryroomidoralias
8913
+ *
8914
+ * Uses the initial version of MSC3266, as implemented in older versions of Synapse, unless the
8915
+ * server advertises support for the spec version which stabilised it.
8916
+ *
8861
8917
  * @param roomIdOrAlias - The ID or alias of the room to get the summary of.
8862
- * @param via - The list of servers which know about the room if only an ID was provided.
8918
+ * @param via - The servers to attempt to request the summary from, when the local server cannot
8919
+ * generate it (for instance, because it has no local user in the room).
8863
8920
  */
8864
8921
  public async getRoomSummary(roomIdOrAlias: string, via?: string[]): Promise<RoomSummary> {
8865
- const paramOpts = {
8866
- prefix: "/_matrix/client/unstable/im.nheko.summary",
8867
- };
8868
- try {
8869
- const path = utils.encodeUri("/summary/$roomid", { $roomid: roomIdOrAlias });
8870
- return await this.http.authedRequest(Method.Get, path, { via }, undefined, paramOpts);
8871
- } catch (e) {
8872
- if (e instanceof MatrixError && e.errcode === "M_UNRECOGNIZED") {
8873
- const path = utils.encodeUri("/rooms/$roomid/summary", { $roomid: roomIdOrAlias });
8874
- return await this.http.authedRequest(Method.Get, path, { via }, undefined, paramOpts);
8875
- } else {
8876
- throw e;
8877
- }
8878
- }
8922
+ return fetchRoomSummary(this.http, await this.getVersions(), roomIdOrAlias, via);
8879
8923
  }
8880
8924
 
8881
8925
  /**
@@ -8982,9 +9026,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
8982
9026
  * @throws when delegated auth config is invalid or unreachable
8983
9027
  */
8984
9028
  public async getAuthMetadata(): Promise<ValidatedAuthMetadata> {
8985
- const useStable = await this.isVersionSupported("v1.15");
8986
9029
  const authMetadata = await this.http.request(Method.Get, "/auth_metadata", undefined, undefined, {
8987
- prefix: useStable ? ClientPrefix.V1 : ClientPrefix.Unstable + "/org.matrix.msc2965",
9030
+ prefix: ClientPrefix.V1,
8988
9031
  });
8989
9032
 
8990
9033
  if (isValidAuthMetadata(authMetadata)) {
@@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import type { SecretsBundle } from "@matrix-org/matrix-sdk-crypto-wasm";
18
17
  import type { IMegolmSessionData } from "../@types/crypto.ts";
19
18
  import type { ToDeviceBatch, ToDevicePayload } from "../models/ToDeviceMessage.ts";
20
19
  import { type Room } from "../models/room.ts";
@@ -31,6 +30,7 @@ import {
31
30
  } from "./keybackup.ts";
32
31
  import { type ISignatures } from "../@types/signed.ts";
33
32
  import { type MatrixEvent } from "../models/event.ts";
33
+ import type { SecretsBundleJson } from "../@types/matrix-sdk-crypto-wasm.d.ts";
34
34
 
35
35
  /**
36
36
  * `matrix-js-sdk/lib/crypto-api`: End-to-end encryption support.
@@ -723,13 +723,13 @@ export interface CryptoApi {
723
723
  /**
724
724
  * Export secrets bundle for transmitting to another device as part of OAuth2 QR login
725
725
  */
726
- exportSecretsBundle?(): Promise<Awaited<ReturnType<SecretsBundle["to_json"]>>>;
726
+ exportSecretsBundle?(): Promise<SecretsBundleJson>;
727
727
 
728
728
  /**
729
729
  * Import secrets bundle transmitted from another device.
730
730
  * @param secrets - The secrets bundle received from the other device
731
731
  */
732
- importSecretsBundle?(secrets: Awaited<ReturnType<SecretsBundle["to_json"]>>): Promise<void>;
732
+ importSecretsBundle?(secrets: SecretsBundleJson): Promise<void>;
733
733
  }
734
734
 
735
735
  /** A reason code for a failure to decrypt an event. */
package/src/embedded.ts CHANGED
@@ -46,7 +46,7 @@ import {
46
46
  UpdateDelayedEventAction,
47
47
  isSendDelayedEventRequestOpts,
48
48
  } from "./@types/requests.ts";
49
- import { EventType, type StateEvents } from "./@types/event.ts";
49
+ import { EventType, type StateEvents, type TimelineEvents } from "./@types/event.ts";
50
50
  import { logger } from "./logger.ts";
51
51
  import {
52
52
  MatrixClient,
@@ -64,7 +64,12 @@ import { User } from "./models/user.ts";
64
64
  import { type Room } from "./models/room.ts";
65
65
  import { type ToDeviceBatch, type ToDevicePayload } from "./models/ToDeviceMessage.ts";
66
66
  import { MapWithDefault, type QueryDict, recursiveMapToObject } from "./utils.ts";
67
- import { type EmptyObject, TypedEventEmitter, UnsupportedDelayedEventsEndpointError } from "./matrix.ts";
67
+ import {
68
+ type EmptyObject,
69
+ TypedEventEmitter,
70
+ UnsupportedDelayedEventsEndpointError,
71
+ UnsupportedStickyEventsEndpointError,
72
+ } from "./matrix.ts";
68
73
 
69
74
  interface IStateEventRequest {
70
75
  eventType: string;
@@ -539,6 +544,60 @@ export class RoomWidgetClient extends MatrixClient {
539
544
  return this.validateSendDelayedEventResponse(response);
540
545
  }
541
546
 
547
+ /**
548
+ * Sends a sticky timeline event through the widget API.
549
+ *
550
+ * A widget can't probe the homeserver for MSC4354 support and doesn't need to: the host either granted it
551
+ * the capability to send sticky events or it didn't. A missing capability is reported the same way a
552
+ * missing server feature is, so that callers fall back to a regular event in both cases alike.
553
+ *
554
+ * The host may still refuse the request, typically because its own homeserver turns out not to support
555
+ * sticky events. Such a refusal reaches the widget without Matrix API error details, as those only accompany
556
+ * errors from requests the host actually made to the homeserver. It is therefore reported as unsupported too,
557
+ * with the host's error as `cause`. A refusal that does carry Matrix API error details came from the
558
+ * homeserver itself and is rethrown as is.
559
+ */
560
+ public async _unstable_sendStickyEvent<K extends keyof TimelineEvents>(
561
+ roomId: string,
562
+ stickDuration: number,
563
+ threadId: string | null,
564
+ eventType: K,
565
+ content: TimelineEvents[K] & { msc4354_sticky_key?: string },
566
+ txnId?: string,
567
+ ): Promise<ISendEventResponse> {
568
+ if (!this.widgetApi.hasCapability(MatrixCapabilities.MSC4407SendStickyEvent)) {
569
+ throw new UnsupportedStickyEventsEndpointError(
570
+ "Widget was not granted the capability to send sticky events",
571
+ "sendStickyEvent",
572
+ );
573
+ }
574
+
575
+ this.addThreadRelationIfNeeded(content, threadId, roomId);
576
+ txnId ??= this.makeTxnId();
577
+ try {
578
+ return await this.sendCompleteEvent({
579
+ roomId,
580
+ threadId,
581
+ eventObject: { type: eventType, content },
582
+ queryDict: { "org.matrix.msc4354.sticky_duration_ms": stickDuration },
583
+ txnId,
584
+ });
585
+ } catch (error) {
586
+ if (!(error instanceof WidgetApiResponseError) || error.data.matrix_api_error !== undefined) throw error;
587
+
588
+ // Callers fall back to sending a regular event, so drop the local echo of the failed sticky one
589
+ // rather than leaving it in the timeline as unsent.
590
+ const localEvent = this.getRoom(roomId)?.getEventForTxnId(txnId);
591
+ if (localEvent?.status === EventStatus.NOT_SENT) this.cancelPendingEvent(localEvent);
592
+
593
+ throw new UnsupportedStickyEventsEndpointError(
594
+ `Host could not send the sticky event: ${error.message}`,
595
+ "sendStickyEvent",
596
+ { cause: error },
597
+ );
598
+ }
599
+ }
600
+
542
601
  private validateSendDelayedEventResponse(response: ISendEventFromWidgetResponseData): SendDelayedEventResponse {
543
602
  if (response.delay_id === undefined) {
544
603
  throw new Error("'delay_id' absent from response to a delayed event request");
package/src/errors.ts CHANGED
@@ -80,8 +80,9 @@ export class UnsupportedStickyEventsEndpointError extends Error {
80
80
  public constructor(
81
81
  message: string,
82
82
  public clientEndpoint: "sendStickyEvent" | "sendStickyStateEvent",
83
+ options?: ErrorOptions,
83
84
  ) {
84
- super(message);
85
+ super(message, options);
85
86
  this.name = "UnsupportedStickyEventsEndpointError";
86
87
  }
87
88
  }
@@ -33,11 +33,11 @@ import {
33
33
  import { anySignal, parseErrorResponse, timeoutSignal } from "./utils.ts";
34
34
  import { sanitizeUrlForLogs } from "./logging.ts";
35
35
  import { type QueryDict } from "../utils.ts";
36
- import { TokenRefresher, TokenRefreshOutcome } from "./refresh.ts";
36
+ import { TokenManager, TokenRefreshOutcome } from "./refresh.ts";
37
37
 
38
38
  export class FetchHttpApi<O extends IHttpOpts> {
39
39
  private abortController = new AbortController();
40
- private readonly tokenRefresher: TokenRefresher;
40
+ private readonly tokenManager: TokenManager;
41
41
 
42
42
  public constructor(
43
43
  private eventEmitter: TypedEventEmitter<HttpApiEvent, HttpApiEventHandlerMap>,
@@ -49,7 +49,7 @@ export class FetchHttpApi<O extends IHttpOpts> {
49
49
  }
50
50
  opts.useAuthorizationHeader = opts.useAuthorizationHeader ?? true;
51
51
 
52
- this.tokenRefresher = new TokenRefresher(opts);
52
+ this.tokenManager = new TokenManager(opts);
53
53
  }
54
54
 
55
55
  public abort(): void {
@@ -57,6 +57,15 @@ export class FetchHttpApi<O extends IHttpOpts> {
57
57
  this.abortController = new AbortController();
58
58
  }
59
59
 
60
+ /**
61
+ * Attempt to revoke the current access and refresh tokens with the OAuth2 authorization server, e.g. as
62
+ * part of logging out an OAuth2-native session. Does nothing if `opts.oauth2ClientConfig` was not supplied.
63
+ * @throws when discovery of the auth server metadata, or revocation of either token, fails
64
+ */
65
+ public async revokeOAuthTokens(): Promise<void> {
66
+ return this.tokenManager.revokeTokens();
67
+ }
68
+
60
69
  public fetch(resource: URL | string, options?: RequestInit): ReturnType<typeof globalThis.fetch> {
61
70
  if (this.opts.fetchFn) {
62
71
  return this.opts.fetchFn(resource, options);
@@ -147,7 +156,7 @@ export class FetchHttpApi<O extends IHttpOpts> {
147
156
  opts.abortSignal = paramOpts.abortSignal;
148
157
 
149
158
  // Take a snapshot of the current token state before we start the request so we can reference it if we error
150
- const requestSnapshot = await this.tokenRefresher.prepareForRequest();
159
+ const requestSnapshot = await this.tokenManager.prepareForRequest();
151
160
  if (requestSnapshot.accessToken) {
152
161
  if (this.opts.useAuthorizationHeader) {
153
162
  if (!opts.headers) {
@@ -173,7 +182,7 @@ export class FetchHttpApi<O extends IHttpOpts> {
173
182
  }
174
183
 
175
184
  if (error.errcode === "M_UNKNOWN_TOKEN") {
176
- const outcome = await this.tokenRefresher.handleUnknownToken(requestSnapshot, attempt);
185
+ const outcome = await this.tokenManager.handleUnknownToken(requestSnapshot, attempt);
177
186
  if (outcome === TokenRefreshOutcome.Success) {
178
187
  // if we got a new token retry the request
179
188
  return this.doAuthedRequest(attempt + 1, method, path, queryParams, body, paramOpts);
@@ -17,6 +17,7 @@ limitations under the License.
17
17
  import { type MatrixError } from "./errors.ts";
18
18
  import { type Logger } from "../logger.ts";
19
19
  import { type QueryDict } from "../utils.ts";
20
+ import { type ValidatedAuthMetadata } from "../oauth/discover.ts";
20
21
 
21
22
  export type Body = Record<string, any> | BodyInit;
22
23
 
@@ -39,12 +40,25 @@ export type AccessTokens = {
39
40
  };
40
41
 
41
42
  /**
42
- * Function that performs token refresh using the given refreshToken.
43
- * Returns a promise that resolves to the refreshed access and (optional) refresh tokens.
43
+ * A callback function called when tokens are refreshed such that stored tokens can be updated with
44
+ * the refreshed ones.
45
+ */
46
+ export type TokenRefreshCallback = (newTokens: AccessTokens) => void;
47
+
48
+ /**
49
+ * Configuration required for {@link IHttpOpts} to allow the SDK to manage the full lifecycle of tokens for an
50
+ * OAuth2-native session (as per MSC2965/MSC3861): discovering the delegated auth server's metadata, refreshing
51
+ * access/refresh tokens, and revoking them (e.g. on logout).
44
52
  *
45
- * Can be passed to HttpApi instance as {@link IHttpOpts.tokenRefreshFunction} during client creation {@link ICreateClientOpts}
53
+ * The SDK constructs the underlying OAuth2 client itself, lazily, the first time it is actually needed (so that,
54
+ * for example, a client started with no network connection does not fail to ever set up token refresh).
46
55
  */
47
- export type TokenRefreshFunction = (refreshToken: string) => Promise<AccessTokens>;
56
+ export interface OAuth2ClientConfig {
57
+ /** The OAuth2 client ID this application is registered with the delegated auth server under. */
58
+ clientId: string;
59
+ /** The device ID of the current session, used to scope refreshed/revoked tokens to this session. */
60
+ deviceId?: string;
61
+ }
48
62
 
49
63
  /** Options object for `FetchHttpApi` and {@link MatrixHttpApi}. */
50
64
  export interface IHttpOpts {
@@ -57,14 +71,28 @@ export interface IHttpOpts {
57
71
 
58
72
  accessToken?: string;
59
73
  /**
60
- * Used in conjunction with tokenRefreshFunction to attempt token refresh
74
+ * Used in conjunction with oauth2ClientConfig to attempt token refresh
61
75
  */
62
76
  refreshToken?: string;
63
77
  /**
64
- * Function to attempt token refresh when a possibly expired token is encountered
65
- * Optional, only called when a refreshToken is present
78
+ * Callback called when tokens are refreshed. Must be supplied if refreshToken is supplied.
79
+ */
80
+ onTokenRefresh?: TokenRefreshCallback;
81
+ /**
82
+ * Configuration needed to refresh (given refreshToken) and revoke (e.g. on logout) tokens for an
83
+ * OAuth2-native session. Optional; if omitted, tokens will not be refreshed or revoked by the SDK.
84
+ * authMetadataCallback must also be supplied to refresh tokens.
85
+ */
86
+ oauth2ClientConfig?: OAuth2ClientConfig;
87
+
88
+ /**
89
+ * Discovers and validates the delegated auth server's metadata for the current homeserver.
90
+ * Must be supplied along with oauth2ClientConfig in order to refresh tokens.
91
+ *
92
+ * @returns validated authentication metadata
93
+ * @throws when delegated auth config is invalid or unreachable
66
94
  */
67
- tokenRefreshFunction?: TokenRefreshFunction;
95
+ authMetadataCallback?: () => Promise<ValidatedAuthMetadata>;
68
96
 
69
97
  /**
70
98
  * Whether to use the HTTP Authorization header over the `access_token` query parameter