matrix-js-sdk 34.2.0 → 34.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 (63) hide show
  1. package/git-revision.txt +1 -1
  2. package/lib/@types/global.d.js.map +1 -1
  3. package/lib/@types/requests.d.ts +37 -0
  4. package/lib/@types/requests.d.ts.map +1 -1
  5. package/lib/@types/requests.js +28 -1
  6. package/lib/@types/requests.js.map +1 -1
  7. package/lib/client.d.ts +48 -4
  8. package/lib/client.d.ts.map +1 -1
  9. package/lib/client.js +412 -257
  10. package/lib/client.js.map +1 -1
  11. package/lib/crypto/algorithms/base.d.ts +0 -2
  12. package/lib/crypto/algorithms/base.d.ts.map +1 -1
  13. package/lib/crypto/algorithms/base.js +0 -4
  14. package/lib/crypto/algorithms/base.js.map +1 -1
  15. package/lib/digest.d.ts +10 -0
  16. package/lib/digest.d.ts.map +1 -0
  17. package/lib/digest.js +40 -0
  18. package/lib/digest.js.map +1 -0
  19. package/lib/embedded.d.ts +24 -1
  20. package/lib/embedded.d.ts.map +1 -1
  21. package/lib/embedded.js +84 -26
  22. package/lib/embedded.js.map +1 -1
  23. package/lib/matrixrtc/MatrixRTCSession.d.ts +2 -0
  24. package/lib/matrixrtc/MatrixRTCSession.d.ts.map +1 -1
  25. package/lib/matrixrtc/MatrixRTCSession.js +44 -15
  26. package/lib/matrixrtc/MatrixRTCSession.js.map +1 -1
  27. package/lib/matrixrtc/index.d.ts +7 -0
  28. package/lib/matrixrtc/index.d.ts.map +1 -0
  29. package/lib/matrixrtc/index.js +23 -0
  30. package/lib/matrixrtc/index.js.map +1 -0
  31. package/lib/models/event.js.map +1 -1
  32. package/lib/oidc/authorize.d.ts.map +1 -1
  33. package/lib/oidc/authorize.js +4 -3
  34. package/lib/oidc/authorize.js.map +1 -1
  35. package/lib/rust-crypto/RoomEncryptor.d.ts +1 -1
  36. package/lib/rust-crypto/RoomEncryptor.d.ts.map +1 -1
  37. package/lib/rust-crypto/RoomEncryptor.js +6 -2
  38. package/lib/rust-crypto/RoomEncryptor.js.map +1 -1
  39. package/lib/rust-crypto/index.js +1 -0
  40. package/lib/rust-crypto/index.js.map +1 -1
  41. package/lib/rust-crypto/rust-crypto.d.ts +9 -0
  42. package/lib/rust-crypto/rust-crypto.d.ts.map +1 -1
  43. package/lib/rust-crypto/rust-crypto.js +83 -58
  44. package/lib/rust-crypto/rust-crypto.js.map +1 -1
  45. package/lib/sync-accumulator.js.map +1 -1
  46. package/lib/sync.d.ts.map +1 -1
  47. package/lib/sync.js.map +1 -1
  48. package/package.json +6 -6
  49. package/src/@types/global.d.ts +0 -45
  50. package/src/@types/requests.ts +50 -0
  51. package/src/client.ts +237 -46
  52. package/src/crypto/algorithms/base.ts +0 -4
  53. package/src/digest.ts +34 -0
  54. package/src/embedded.ts +109 -5
  55. package/src/matrixrtc/MatrixRTCSession.ts +52 -20
  56. package/src/matrixrtc/index.ts +22 -0
  57. package/src/models/event.ts +1 -1
  58. package/src/oidc/authorize.ts +4 -7
  59. package/src/rust-crypto/RoomEncryptor.ts +10 -6
  60. package/src/rust-crypto/index.ts +3 -0
  61. package/src/rust-crypto/rust-crypto.ts +51 -24
  62. package/src/sync-accumulator.ts +1 -1
  63. package/src/sync.ts +3 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "matrix-js-sdk",
3
- "version": "34.2.0",
3
+ "version": "34.3.0-rc.0",
4
4
  "description": "Matrix Client-Server SDK for Javascript",
5
5
  "engines": {
6
6
  "node": ">=20.0.0"
@@ -53,15 +53,14 @@
53
53
  ],
54
54
  "dependencies": {
55
55
  "@babel/runtime": "^7.12.5",
56
- "@matrix-org/matrix-sdk-crypto-wasm": "^6.0.0",
57
- "@matrix-org/olm": "3.2.15",
56
+ "@matrix-org/matrix-sdk-crypto-wasm": "^7.0.0",
58
57
  "another-json": "^0.2.0",
59
58
  "bs58": "^6.0.0",
60
59
  "content-type": "^1.0.4",
61
60
  "jwt-decode": "^4.0.0",
62
61
  "loglevel": "^1.7.1",
63
62
  "matrix-events-sdk": "0.0.1",
64
- "matrix-widget-api": "^1.6.0",
63
+ "matrix-widget-api": "^1.8.2",
65
64
  "oidc-client-ts": "^3.0.1",
66
65
  "p-retry": "4",
67
66
  "sdp-transform": "^2.14.1",
@@ -83,6 +82,7 @@
83
82
  "@babel/preset-env": "^7.12.11",
84
83
  "@babel/preset-typescript": "^7.12.7",
85
84
  "@casualbot/jest-sonar-reporter": "2.2.7",
85
+ "@matrix-org/olm": "3.2.15",
86
86
  "@peculiar/webcrypto": "^1.4.5",
87
87
  "@types/bs58": "^4.0.1",
88
88
  "@types/content-type": "^1.1.5",
@@ -106,7 +106,7 @@
106
106
  "eslint-plugin-tsdoc": "^0.3.0",
107
107
  "eslint-plugin-unicorn": "^54.0.0",
108
108
  "fake-indexeddb": "^5.0.2",
109
- "fetch-mock": "10.0.7",
109
+ "fetch-mock": "10.1.0",
110
110
  "fetch-mock-jest": "^1.5.1",
111
111
  "husky": "^9.0.0",
112
112
  "jest": "^29.0.0",
@@ -117,7 +117,7 @@
117
117
  "lint-staged": "^15.0.2",
118
118
  "matrix-mock-request": "^2.5.0",
119
119
  "node-fetch": "^2.7.0",
120
- "prettier": "3.3.2",
120
+ "prettier": "3.3.3",
121
121
  "rimraf": "^6.0.0",
122
122
  "ts-node": "^10.9.2",
123
123
  "typedoc": "^0.26.0",
@@ -29,20 +29,11 @@ declare global {
29
29
 
30
30
  namespace NodeJS {
31
31
  interface Global {
32
- localStorage: Storage;
33
32
  // marker variable used to detect both the browser & node entrypoints being used at once
34
33
  __js_sdk_entrypoint: unknown;
35
34
  }
36
35
  }
37
36
 
38
- interface Window {
39
- webkitAudioContext: typeof AudioContext;
40
- }
41
-
42
- interface Crypto {
43
- webkitSubtle?: Window["crypto"]["subtle"];
44
- }
45
-
46
37
  interface MediaDevices {
47
38
  // This is experimental and types don't know about it yet
48
39
  // https://github.com/microsoft/TypeScript/issues/33232
@@ -76,40 +67,4 @@ declare global {
76
67
  // on webkit: we should check if we still need to do this
77
68
  webkitGetUserMedia: DummyInterfaceWeShouldntBeUsingThis;
78
69
  }
79
-
80
- export interface ISettledFulfilled<T> {
81
- status: "fulfilled";
82
- value: T;
83
- }
84
- export interface ISettledRejected {
85
- status: "rejected";
86
- reason: any;
87
- }
88
-
89
- interface PromiseConstructor {
90
- allSettled<T>(promises: Promise<T>[]): Promise<Array<ISettledFulfilled<T> | ISettledRejected>>;
91
- }
92
-
93
- interface RTCRtpTransceiver {
94
- // This has been removed from TS
95
- // (https://github.com/microsoft/TypeScript-DOM-lib-generator/issues/1029),
96
- // but we still need this for MatrixCall::getRidOfRTXCodecs()
97
- setCodecPreferences(codecs: RTCRtpCodecCapability[]): void;
98
- }
99
-
100
- interface RequestInit {
101
- /**
102
- * Specifies the priority of the fetch request relative to other requests of the same type.
103
- * Must be one of the following strings:
104
- * high: A high priority fetch request relative to other requests of the same type.
105
- * low: A low priority fetch request relative to other requests of the same type.
106
- * auto: Automatically determine the priority of the fetch request relative to other requests of the same type (default).
107
- *
108
- * @see https://html.spec.whatwg.org/multipage/urls-and-fetching.html#fetch-priority-attribute
109
- * @see https://github.com/microsoft/TypeScript/issues/54472
110
- * @see https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API#browser_compatibility
111
- * Not yet supported in Safari or Firefox
112
- */
113
- priority?: "high" | "low" | "auto";
114
- }
115
70
  }
@@ -76,6 +76,56 @@ export interface ISendEventResponse {
76
76
  event_id: string;
77
77
  }
78
78
 
79
+ export type TimeoutDelay = {
80
+ delay: number;
81
+ };
82
+
83
+ export type ParentDelayId = {
84
+ parent_delay_id: string;
85
+ };
86
+
87
+ export type SendTimeoutDelayedEventRequestOpts = TimeoutDelay & Partial<ParentDelayId>;
88
+ export type SendActionDelayedEventRequestOpts = ParentDelayId;
89
+
90
+ export type SendDelayedEventRequestOpts = SendTimeoutDelayedEventRequestOpts | SendActionDelayedEventRequestOpts;
91
+
92
+ export type SendDelayedEventResponse = {
93
+ delay_id: string;
94
+ };
95
+
96
+ export enum UpdateDelayedEventAction {
97
+ Cancel = "cancel",
98
+ Restart = "restart",
99
+ Send = "send",
100
+ }
101
+
102
+ export type UpdateDelayedEventRequestOpts = SendDelayedEventResponse & {
103
+ action: UpdateDelayedEventAction;
104
+ };
105
+
106
+ type DelayedPartialTimelineEvent = {
107
+ room_id: string;
108
+ type: string;
109
+ content: IContent;
110
+ };
111
+
112
+ type DelayedPartialStateEvent = DelayedPartialTimelineEvent & {
113
+ state_key: string;
114
+ transaction_id: string;
115
+ };
116
+
117
+ type DelayedPartialEvent = DelayedPartialTimelineEvent | DelayedPartialStateEvent;
118
+
119
+ export type DelayedEventInfo = {
120
+ delayed_events: DelayedPartialEvent &
121
+ SendDelayedEventResponse &
122
+ SendDelayedEventRequestOpts &
123
+ {
124
+ running_since: number;
125
+ }[];
126
+ next_batch?: string;
127
+ };
128
+
79
129
  export interface IPresenceOpts {
80
130
  // One of "online", "offline" or "unavailable"
81
131
  presence: "online" | "offline" | "unavailable";
package/src/client.ts CHANGED
@@ -47,7 +47,7 @@ import { Direction, EventTimeline } from "./models/event-timeline";
47
47
  import { IActionsObject, PushProcessor } from "./pushprocessor";
48
48
  import { AutoDiscovery, AutoDiscoveryAction } from "./autodiscovery";
49
49
  import * as olmlib from "./crypto/olmlib";
50
- import { decodeBase64, encodeBase64 } from "./base64";
50
+ import { decodeBase64, encodeBase64, encodeUnpaddedBase64Url } from "./base64";
51
51
  import { IExportedDevice as IExportedOlmDevice } from "./crypto/OlmDevice";
52
52
  import { IOlmDevice } from "./crypto/algorithms/megolm";
53
53
  import { TypedReEmitter } from "./ReEmitter";
@@ -114,6 +114,7 @@ import { NotificationCountType, Room, RoomEvent, RoomEventHandlerMap, RoomNameSt
114
114
  import { RoomMemberEvent, RoomMemberEventHandlerMap } from "./models/room-member";
115
115
  import { IPowerLevelsContent, RoomStateEvent, RoomStateEventHandlerMap } from "./models/room-state";
116
116
  import {
117
+ DelayedEventInfo,
117
118
  IAddThreePidOnlyBody,
118
119
  IBindThreePidBody,
119
120
  IContextResponse,
@@ -134,6 +135,9 @@ import {
134
135
  IStatusResponse,
135
136
  ITagsResponse,
136
137
  KnockRoomOpts,
138
+ SendDelayedEventRequestOpts,
139
+ SendDelayedEventResponse,
140
+ UpdateDelayedEventAction,
137
141
  } from "./@types/requests";
138
142
  import {
139
143
  EventType,
@@ -227,6 +231,7 @@ import { KnownMembership, Membership } from "./@types/membership";
227
231
  import { RoomMessageEventContent, StickerEventContent } from "./@types/events";
228
232
  import { ImageInfo } from "./@types/media";
229
233
  import { Capabilities, ServerCapabilities } from "./serverCapabilities";
234
+ import { sha256 } from "./digest";
230
235
 
231
236
  export type Store = IStore;
232
237
 
@@ -324,10 +329,13 @@ export interface ICreateClientOpts {
324
329
  localTimeoutMs?: number;
325
330
 
326
331
  /**
327
- * Set to true to use
328
- * Authorization header instead of query param to send the access token to the server.
332
+ * Set to false to send the access token to the server via a query parameter rather
333
+ * than the Authorization HTTP header.
329
334
  *
330
- * Default false.
335
+ * Note that as of v1.11 of the Matrix spec, sending the access token via a query
336
+ * is deprecated.
337
+ *
338
+ * Default true.
331
339
  */
332
340
  useAuthorizationHeader?: boolean;
333
341
 
@@ -527,6 +535,8 @@ export const UNSTABLE_MSC2666_SHARED_ROOMS = "uk.half-shot.msc2666";
527
535
  export const UNSTABLE_MSC2666_MUTUAL_ROOMS = "uk.half-shot.msc2666.mutual_rooms";
528
536
  export const UNSTABLE_MSC2666_QUERY_MUTUAL_ROOMS = "uk.half-shot.msc2666.query_mutual_rooms";
529
537
 
538
+ export const UNSTABLE_MSC4140_DELAYED_EVENTS = "org.matrix.msc4140";
539
+
530
540
  enum CrossSigningKeyType {
531
541
  MasterKey = "master_key",
532
542
  SelfSigningKey = "self_signing_key",
@@ -4570,12 +4580,19 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
4570
4580
  threadId = threadIdOrEventType;
4571
4581
  }
4572
4582
 
4573
- // If we expect that an event is part of a thread but is missing the relation
4574
- // we need to add it manually, as well as the reply fallback
4575
- if (threadId && !content!["m.relates_to"]?.rel_type) {
4576
- const isReply = !!content!["m.relates_to"]?.["m.in_reply_to"];
4577
- content!["m.relates_to"] = {
4578
- ...content!["m.relates_to"],
4583
+ this.addThreadRelationIfNeeded(content, threadId, roomId);
4584
+ return this.sendCompleteEvent(roomId, threadId, { type: eventType, content }, txnId);
4585
+ }
4586
+
4587
+ /**
4588
+ * If we expect that an event is part of a thread but is missing the relation
4589
+ * we need to add it manually, as well as the reply fallback
4590
+ */
4591
+ private addThreadRelationIfNeeded(content: IContent, threadId: string | null, roomId: string): void {
4592
+ if (threadId && !content["m.relates_to"]?.rel_type) {
4593
+ const isReply = !!content["m.relates_to"]?.["m.in_reply_to"];
4594
+ content["m.relates_to"] = {
4595
+ ...content["m.relates_to"],
4579
4596
  rel_type: THREAD_RELATION_TYPE.name,
4580
4597
  event_id: threadId,
4581
4598
  // Set is_falling_back to true unless this is actually intended to be a reply
@@ -4583,7 +4600,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
4583
4600
  };
4584
4601
  const thread = this.getRoom(roomId)?.getThread(threadId);
4585
4602
  if (thread && !isReply) {
4586
- content!["m.relates_to"]["m.in_reply_to"] = {
4603
+ content["m.relates_to"]["m.in_reply_to"] = {
4587
4604
  event_id:
4588
4605
  thread
4589
4606
  .lastReply((ev: MatrixEvent) => {
@@ -4593,8 +4610,6 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
4593
4610
  };
4594
4611
  }
4595
4612
  }
4596
-
4597
- return this.sendCompleteEvent(roomId, threadId, { type: eventType, content }, txnId);
4598
4613
  }
4599
4614
 
4600
4615
  /**
@@ -4608,7 +4623,38 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
4608
4623
  threadId: string | null,
4609
4624
  eventObject: Partial<IEvent>,
4610
4625
  txnId?: string,
4611
- ): Promise<ISendEventResponse> {
4626
+ ): Promise<ISendEventResponse>;
4627
+ /**
4628
+ * Sends a delayed event (MSC4140).
4629
+ * @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.
4630
+ * @param delayOpts - Properties of the delay for this event.
4631
+ * @param txnId - Optional.
4632
+ * @returns Promise which resolves: to an empty object `{}`
4633
+ * @returns Rejects: with an error response.
4634
+ */
4635
+ private sendCompleteEvent(
4636
+ roomId: string,
4637
+ threadId: string | null,
4638
+ eventObject: Partial<IEvent>,
4639
+ delayOpts: SendDelayedEventRequestOpts,
4640
+ txnId?: string,
4641
+ ): Promise<SendDelayedEventResponse>;
4642
+ private sendCompleteEvent(
4643
+ roomId: string,
4644
+ threadId: string | null,
4645
+ eventObject: Partial<IEvent>,
4646
+ delayOptsOrTxnId?: SendDelayedEventRequestOpts | string,
4647
+ txnIdOrVoid?: string,
4648
+ ): Promise<ISendEventResponse | SendDelayedEventResponse> {
4649
+ let delayOpts: SendDelayedEventRequestOpts | undefined;
4650
+ let txnId: string | undefined;
4651
+ if (typeof delayOptsOrTxnId === "string") {
4652
+ txnId = delayOptsOrTxnId;
4653
+ } else {
4654
+ delayOpts = delayOptsOrTxnId;
4655
+ txnId = txnIdOrVoid;
4656
+ }
4657
+
4612
4658
  if (!txnId) {
4613
4659
  txnId = this.makeTxnId();
4614
4660
  }
@@ -4631,9 +4677,11 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
4631
4677
  localEvent.setThread(thread);
4632
4678
  }
4633
4679
 
4634
- // set up re-emitter for this new event - this is normally the job of EventMapper but we don't use it here
4635
- this.reEmitter.reEmit(localEvent, [MatrixEventEvent.Replaced, MatrixEventEvent.VisibilityChange]);
4636
- room?.reEmitter.reEmit(localEvent, [MatrixEventEvent.BeforeRedaction]);
4680
+ if (!delayOpts) {
4681
+ // set up re-emitter for this new event - this is normally the job of EventMapper but we don't use it here
4682
+ this.reEmitter.reEmit(localEvent, [MatrixEventEvent.Replaced, MatrixEventEvent.VisibilityChange]);
4683
+ room?.reEmitter.reEmit(localEvent, [MatrixEventEvent.BeforeRedaction]);
4684
+ }
4637
4685
 
4638
4686
  // if this is a relation or redaction of an event
4639
4687
  // that hasn't been sent yet (e.g. with a local id starting with a ~)
@@ -4648,29 +4696,56 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
4648
4696
  }
4649
4697
 
4650
4698
  const type = localEvent.getType();
4651
- this.logger.debug(`sendEvent of type ${type} in ${roomId} with txnId ${txnId}`);
4699
+ this.logger.debug(
4700
+ `sendEvent of type ${type} in ${roomId} with txnId ${txnId}${delayOpts ? " (delayed event)" : ""}`,
4701
+ );
4652
4702
 
4653
4703
  localEvent.setTxnId(txnId);
4654
4704
  localEvent.setStatus(EventStatus.SENDING);
4655
4705
 
4656
- // add this event immediately to the local store as 'sending'.
4657
- room?.addPendingEvent(localEvent, txnId);
4706
+ // TODO: separate store for delayed events?
4707
+ if (!delayOpts) {
4708
+ // add this event immediately to the local store as 'sending'.
4709
+ room?.addPendingEvent(localEvent, txnId);
4658
4710
 
4659
- // addPendingEvent can change the state to NOT_SENT if it believes
4660
- // that there's other events that have failed. We won't bother to
4661
- // try sending the event if the state has changed as such.
4662
- if (localEvent.status === EventStatus.NOT_SENT) {
4663
- return Promise.reject(new Error("Event blocked by other events not yet sent"));
4664
- }
4711
+ // addPendingEvent can change the state to NOT_SENT if it believes
4712
+ // that there's other events that have failed. We won't bother to
4713
+ // try sending the event if the state has changed as such.
4714
+ if (localEvent.status === EventStatus.NOT_SENT) {
4715
+ return Promise.reject(new Error("Event blocked by other events not yet sent"));
4716
+ }
4665
4717
 
4666
- return this.encryptAndSendEvent(room, localEvent);
4718
+ return this.encryptAndSendEvent(room, localEvent);
4719
+ } else {
4720
+ return this.encryptAndSendEvent(room, localEvent, delayOpts);
4721
+ }
4667
4722
  }
4668
4723
 
4669
4724
  /**
4670
4725
  * encrypts the event if necessary; adds the event to the queue, or sends it; marks the event as sent/unsent
4671
4726
  * @returns returns a promise which resolves with the result of the send request
4672
4727
  */
4673
- protected async encryptAndSendEvent(room: Room | null, event: MatrixEvent): Promise<ISendEventResponse> {
4728
+ protected async encryptAndSendEvent(room: Room | null, event: MatrixEvent): Promise<ISendEventResponse>;
4729
+ /**
4730
+ * Simply sends a delayed event without encrypting it.
4731
+ * TODO: Allow encrypted delayed events, and encrypt them properly
4732
+ * @param delayOpts - Properties of the delay for this event.
4733
+ * @returns returns a promise which resolves with the result of the delayed send request
4734
+ */
4735
+ protected async encryptAndSendEvent(
4736
+ room: Room | null,
4737
+ event: MatrixEvent,
4738
+ delayOpts: SendDelayedEventRequestOpts,
4739
+ ): Promise<SendDelayedEventResponse>;
4740
+ protected async encryptAndSendEvent(
4741
+ room: Room | null,
4742
+ event: MatrixEvent,
4743
+ delayOpts?: SendDelayedEventRequestOpts,
4744
+ ): Promise<ISendEventResponse | SendDelayedEventResponse> {
4745
+ if (delayOpts) {
4746
+ return this.sendEventHttpRequest(event, delayOpts);
4747
+ }
4748
+
4674
4749
  try {
4675
4750
  let cancelled: boolean;
4676
4751
  this.eventsBeingEncrypted.add(event.getId()!);
@@ -4821,7 +4896,15 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
4821
4896
  }
4822
4897
  }
4823
4898
 
4824
- private sendEventHttpRequest(event: MatrixEvent): Promise<ISendEventResponse> {
4899
+ private sendEventHttpRequest(event: MatrixEvent): Promise<ISendEventResponse>;
4900
+ private sendEventHttpRequest(
4901
+ event: MatrixEvent,
4902
+ delayOpts: SendDelayedEventRequestOpts,
4903
+ ): Promise<SendDelayedEventResponse>;
4904
+ private sendEventHttpRequest(
4905
+ event: MatrixEvent,
4906
+ delayOpts?: SendDelayedEventRequestOpts,
4907
+ ): Promise<ISendEventResponse | SendDelayedEventResponse> {
4825
4908
  let txnId = event.getTxnId();
4826
4909
  if (!txnId) {
4827
4910
  txnId = this.makeTxnId();
@@ -4853,12 +4936,20 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
4853
4936
  path = utils.encodeUri("/rooms/$roomId/send/$eventType/$txnId", pathParams);
4854
4937
  }
4855
4938
 
4856
- return this.http
4857
- .authedRequest<ISendEventResponse>(Method.Put, path, undefined, event.getWireContent())
4858
- .then((res) => {
4939
+ const content = event.getWireContent();
4940
+ if (!delayOpts) {
4941
+ return this.http.authedRequest<ISendEventResponse>(Method.Put, path, undefined, content).then((res) => {
4859
4942
  this.logger.debug(`Event sent to ${event.getRoomId()} with event id ${res.event_id}`);
4860
4943
  return res;
4861
4944
  });
4945
+ } else {
4946
+ return this.http.authedRequest<SendDelayedEventResponse>(
4947
+ Method.Put,
4948
+ path,
4949
+ getUnstableDelayQueryOpts(delayOpts),
4950
+ content,
4951
+ );
4952
+ }
4862
4953
  }
4863
4954
 
4864
4955
  /**
@@ -5188,6 +5279,101 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
5188
5279
  return this.sendMessage(roomId, threadId, content);
5189
5280
  }
5190
5281
 
5282
+ /**
5283
+ * Send a delayed timeline event.
5284
+ *
5285
+ * Note: This endpoint is unstable, and can throw an `Error`.
5286
+ * Check progress on [MSC4140](https://github.com/matrix-org/matrix-spec-proposals/pull/4140) for more details.
5287
+ */
5288
+ // eslint-disable-next-line
5289
+ public async _unstable_sendDelayedEvent<K extends keyof TimelineEvents>(
5290
+ roomId: string,
5291
+ delayOpts: SendDelayedEventRequestOpts,
5292
+ threadId: string | null,
5293
+ eventType: K,
5294
+ content: TimelineEvents[K],
5295
+ txnId?: string,
5296
+ ): Promise<SendDelayedEventResponse> {
5297
+ if (!(await this.doesServerSupportUnstableFeature(UNSTABLE_MSC4140_DELAYED_EVENTS))) {
5298
+ throw Error("Server does not support the delayed events API");
5299
+ }
5300
+
5301
+ this.addThreadRelationIfNeeded(content, threadId, roomId);
5302
+ return this.sendCompleteEvent(roomId, threadId, { type: eventType, content }, delayOpts, txnId);
5303
+ }
5304
+
5305
+ /**
5306
+ * Send a delayed state event.
5307
+ *
5308
+ * Note: This endpoint is unstable, and can throw an `Error`.
5309
+ * Check progress on [MSC4140](https://github.com/matrix-org/matrix-spec-proposals/pull/4140) for more details.
5310
+ */
5311
+ // eslint-disable-next-line
5312
+ public async _unstable_sendDelayedStateEvent<K extends keyof StateEvents>(
5313
+ roomId: string,
5314
+ delayOpts: SendDelayedEventRequestOpts,
5315
+ eventType: K,
5316
+ content: StateEvents[K],
5317
+ stateKey = "",
5318
+ opts: IRequestOpts = {},
5319
+ ): Promise<SendDelayedEventResponse> {
5320
+ if (!(await this.doesServerSupportUnstableFeature(UNSTABLE_MSC4140_DELAYED_EVENTS))) {
5321
+ throw Error("Server does not support the delayed events API");
5322
+ }
5323
+
5324
+ const pathParams = {
5325
+ $roomId: roomId,
5326
+ $eventType: eventType,
5327
+ $stateKey: stateKey,
5328
+ };
5329
+ let path = utils.encodeUri("/rooms/$roomId/state/$eventType", pathParams);
5330
+ if (stateKey !== undefined) {
5331
+ path = utils.encodeUri(path + "/$stateKey", pathParams);
5332
+ }
5333
+ return this.http.authedRequest(Method.Put, path, getUnstableDelayQueryOpts(delayOpts), content as Body, opts);
5334
+ }
5335
+
5336
+ /**
5337
+ * Get all pending delayed events for the calling user.
5338
+ *
5339
+ * Note: This endpoint is unstable, and can throw an `Error`.
5340
+ * Check progress on [MSC4140](https://github.com/matrix-org/matrix-spec-proposals/pull/4140) for more details.
5341
+ */
5342
+ // eslint-disable-next-line
5343
+ public async _unstable_getDelayedEvents(fromToken?: string): Promise<DelayedEventInfo> {
5344
+ if (!(await this.doesServerSupportUnstableFeature(UNSTABLE_MSC4140_DELAYED_EVENTS))) {
5345
+ throw Error("Server does not support the delayed events API");
5346
+ }
5347
+
5348
+ const queryDict = fromToken ? { from: fromToken } : undefined;
5349
+ return await this.http.authedRequest(Method.Get, "/delayed_events", queryDict, undefined, {
5350
+ prefix: `${ClientPrefix.Unstable}/${UNSTABLE_MSC4140_DELAYED_EVENTS}`,
5351
+ });
5352
+ }
5353
+
5354
+ /**
5355
+ * Manage a delayed event associated with the given delay_id.
5356
+ *
5357
+ * Note: This endpoint is unstable, and can throw an `Error`.
5358
+ * Check progress on [MSC4140](https://github.com/matrix-org/matrix-spec-proposals/pull/4140) for more details.
5359
+ */
5360
+ // eslint-disable-next-line
5361
+ public async _unstable_updateDelayedEvent(delayId: string, action: UpdateDelayedEventAction): Promise<{}> {
5362
+ if (!(await this.doesServerSupportUnstableFeature(UNSTABLE_MSC4140_DELAYED_EVENTS))) {
5363
+ throw Error("Server does not support the delayed events API");
5364
+ }
5365
+
5366
+ const path = utils.encodeUri("/delayed_events/$delayId", {
5367
+ $delayId: delayId,
5368
+ });
5369
+ const data = {
5370
+ action,
5371
+ };
5372
+ return await this.http.authedRequest(Method.Post, path, undefined, data, {
5373
+ prefix: `${ClientPrefix.Unstable}/${UNSTABLE_MSC4140_DELAYED_EVENTS}`,
5374
+ });
5375
+ }
5376
+
5191
5377
  /**
5192
5378
  * Send a receipt.
5193
5379
  * @param event - The event being acknowledged
@@ -9299,20 +9485,19 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
9299
9485
 
9300
9486
  // When picking an algorithm, we pick the hashed over no hashes
9301
9487
  if (hashes["algorithms"].includes("sha256")) {
9302
- // Abuse the olm hashing
9303
- const olmutil = new global.Olm.Utility();
9304
- params["addresses"] = addressPairs.map((p) => {
9305
- const addr = p[0].toLowerCase(); // lowercase to get consistent hashes
9306
- const med = p[1].toLowerCase();
9307
- const hashed = olmutil
9308
- .sha256(`${addr} ${med} ${params["pepper"]}`)
9309
- .replace(/\+/g, "-")
9310
- .replace(/\//g, "_"); // URL-safe base64
9311
- // Map the hash to a known (case-sensitive) address. We use the case
9312
- // sensitive version because the caller might be expecting that.
9313
- localMapping[hashed] = p[0];
9314
- return hashed;
9315
- });
9488
+ params["addresses"] = await Promise.all(
9489
+ addressPairs.map(async (p) => {
9490
+ const addr = p[0].toLowerCase(); // lowercase to get consistent hashes
9491
+ const med = p[1].toLowerCase();
9492
+ const hashBuffer = await sha256(`${addr} ${med} ${params["pepper"]}`);
9493
+ const hashed = encodeUnpaddedBase64Url(hashBuffer);
9494
+
9495
+ // Map the hash to a known (case-sensitive) address. We use the case
9496
+ // sensitive version because the caller might be expecting that.
9497
+ localMapping[hashed] = p[0];
9498
+ return hashed;
9499
+ }),
9500
+ );
9316
9501
  params["algorithm"] = "sha256";
9317
9502
  } else if (hashes["algorithms"].includes("none")) {
9318
9503
  params["addresses"] = addressPairs.map((p) => {
@@ -9889,6 +10074,12 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
9889
10074
  }
9890
10075
  }
9891
10076
 
10077
+ function getUnstableDelayQueryOpts(delayOpts: SendDelayedEventRequestOpts): QueryDict {
10078
+ return Object.fromEntries(
10079
+ Object.entries(delayOpts).map(([k, v]) => [`${UNSTABLE_MSC4140_DELAYED_EVENTS}.${k}`, v]),
10080
+ );
10081
+ }
10082
+
9892
10083
  /**
9893
10084
  * recalculates an accurate notifications count on event decryption.
9894
10085
  * Servers do not have enough knowledge about encrypted events to calculate an
@@ -66,7 +66,6 @@ export abstract class EncryptionAlgorithm {
66
66
  protected readonly crypto: Crypto;
67
67
  protected readonly olmDevice: OlmDevice;
68
68
  protected readonly baseApis: MatrixClient;
69
- protected readonly roomId?: string;
70
69
 
71
70
  /**
72
71
  * @param params - parameters
@@ -77,7 +76,6 @@ export abstract class EncryptionAlgorithm {
77
76
  this.crypto = params.crypto;
78
77
  this.olmDevice = params.olmDevice;
79
78
  this.baseApis = params.baseApis;
80
- this.roomId = params.roomId;
81
79
  }
82
80
 
83
81
  /**
@@ -127,14 +125,12 @@ export abstract class DecryptionAlgorithm {
127
125
  protected readonly crypto: Crypto;
128
126
  protected readonly olmDevice: OlmDevice;
129
127
  protected readonly baseApis: MatrixClient;
130
- protected readonly roomId?: string;
131
128
 
132
129
  public constructor(params: DecryptionClassParams) {
133
130
  this.userId = params.userId;
134
131
  this.crypto = params.crypto;
135
132
  this.olmDevice = params.olmDevice;
136
133
  this.baseApis = params.baseApis;
137
- this.roomId = params.roomId;
138
134
  }
139
135
 
140
136
  /**
package/src/digest.ts ADDED
@@ -0,0 +1,34 @@
1
+ /*
2
+ Copyright 2024 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
+ * Computes a SHA-256 hash of a string (after utf-8 encoding) and returns it as an ArrayBuffer.
19
+ *
20
+ * @param plaintext The string to hash
21
+ * @returns An ArrayBuffer containing the SHA-256 hash of the input string
22
+ * @throws If the subtle crypto API is not available, for example if the code is running
23
+ * in a web page with an insecure context (eg. served over plain HTTP).
24
+ */
25
+ export async function sha256(plaintext: string): Promise<ArrayBuffer> {
26
+ if (!globalThis.crypto.subtle) {
27
+ throw new Error("Crypto.subtle is not available: insecure context?");
28
+ }
29
+ const utf8 = new TextEncoder().encode(plaintext);
30
+
31
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", utf8);
32
+
33
+ return digest;
34
+ }