Skip to content

Commit fb23833

Browse files
committed
Merge branch 'develop' into toger5/use-relation-based-CallMembership-create-ts
Signed-off-by: Timo K <[email protected]>
2 parents 9946143 + fd949fe commit fb23833

30 files changed

+1502
-209
lines changed

.github/workflows/pull_request.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ jobs:
4949
pull-requests: write
5050
steps:
5151
- name: Check membership
52-
if: github.event.pull_request.user.login != 'renovate[bot]'
52+
if: github.event.pull_request.user.login != 'renovate[bot]' && github.event.pull_request.user.login != 'dependabot[bot]'
5353
uses: tspascoal/get-user-teams-membership@57e9f42acd78f4d0f496b3be4368fc5f62696662 # v3
5454
id: teams
5555
with:

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
Changes in [38.4.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v38.4.0) (2025-10-07)
2+
==================================================================================================
3+
## ✨ Features
4+
5+
* Add call intent to RTC call notifications ([#5010](https://github.com/matrix-org/matrix-js-sdk/pull/5010)). Contributed by @Half-Shot.
6+
* Implement experimental encrypted state events. ([#4994](https://github.com/matrix-org/matrix-js-sdk/pull/4994)). Contributed by @kaylendog.
7+
8+
## 🐛 Bug Fixes
9+
10+
* Exclude cancelled requests from in-progress lists ([#5016](https://github.com/matrix-org/matrix-js-sdk/pull/5016)). Contributed by @andybalaam.
11+
12+
113
Changes in [38.3.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v38.3.0) (2025-09-23)
214
==================================================================================================
315
## 🐛 Bug Fixes

knip.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,6 @@ export default {
1212
"src/utils.ts", // not really an entrypoint but we have deprecated `defer` there
1313
"scripts/**",
1414
"spec/**",
15-
// XXX: these look entirely unused
16-
"src/crypto/aes.ts",
17-
"src/crypto/crypto.ts",
18-
"src/crypto/recoverykey.ts",
1915
// XXX: these should be re-exported by one of the supported exports
2016
"src/matrixrtc/index.ts",
2117
"src/sliding-sync.ts",

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "matrix-js-sdk",
3-
"version": "38.3.0",
3+
"version": "38.4.0",
44
"description": "Matrix Client-Server SDK for Javascript",
55
"engines": {
66
"node": ">=22.0.0"

spec/integ/crypto/verification.spec.ts

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ import {
4646
type Verifier,
4747
VerifierEvent,
4848
} from "../../../src/crypto-api/verification";
49-
import { escapeRegExp } from "../../../src/utils";
50-
import { awaitDecryption, emitPromise, getSyncResponse, syncPromise } from "../../test-utils/test-utils";
49+
import { escapeRegExp, sleep } from "../../../src/utils";
50+
import { awaitDecryption, emitPromise, getSyncResponse, syncPromise, waitFor } from "../../test-utils/test-utils";
5151
import { SyncResponder } from "../../test-utils/SyncResponder";
5252
import {
5353
BACKUP_DECRYPTION_KEY_BASE64,
@@ -79,11 +79,6 @@ import {
7979
import { type KeyBackupInfo, CryptoEvent } from "../../../src/crypto-api";
8080
import { encodeBase64 } from "../../../src/base64";
8181

82-
// The verification flows use javascript timers to set timeouts. We tell jest to use mock timer implementations
83-
// to ensure that we don't end up with dangling timeouts.
84-
// But the wasm bindings of matrix-sdk-crypto rely on a working `queueMicrotask`.
85-
jest.useFakeTimers({ doNotFake: ["queueMicrotask"] });
86-
8782
beforeAll(async () => {
8883
// we use the libolm primitives in the test, so init the Olm library
8984
await Olm.init();
@@ -96,6 +91,13 @@ beforeAll(async () => {
9691
await RustSdkCryptoJs.initAsync();
9792
}, 10000);
9893

94+
beforeEach(() => {
95+
// The verification flows use javascript timers to set timeouts. We tell jest to use mock timer implementations
96+
// to ensure that we don't end up with dangling timeouts.
97+
// But the wasm bindings of matrix-sdk-crypto rely on a working `queueMicrotask`.
98+
jest.useFakeTimers({ doNotFake: ["queueMicrotask"] });
99+
});
100+
99101
afterEach(() => {
100102
// reset fake-indexeddb after each test, to make sure we don't leak connections
101103
// cf https://github.com/dumbmatter/fakeIndexedDB#wipingresetting-the-indexeddb-for-a-fresh-state
@@ -1080,6 +1082,13 @@ describe("verification", () => {
10801082
});
10811083

10821084
it("ignores old verification requests", async () => {
1085+
const debug = jest.fn();
1086+
const info = jest.fn();
1087+
const warn = jest.fn();
1088+
1089+
// @ts-ignore overriding RustCrypto's logger
1090+
aliceClient.getCrypto()!.logger = { debug, info, warn };
1091+
10831092
const eventHandler = jest.fn();
10841093
aliceClient.on(CryptoEvent.VerificationRequestReceived, eventHandler);
10851094

@@ -1094,6 +1103,16 @@ describe("verification", () => {
10941103
const matrixEvent = room.getLiveTimeline().getEvents()[0];
10951104
expect(matrixEvent.getId()).toEqual(verificationRequestEvent.event_id);
10961105

1106+
// Wait until the request has been processed. We use a real sleep()
1107+
// here to make sure any background async tasks are completed.
1108+
jest.useRealTimers();
1109+
await waitFor(async () => {
1110+
expect(info).toHaveBeenCalledWith(
1111+
expect.stringMatching(/^Ignoring just-received verification request/),
1112+
);
1113+
sleep(100);
1114+
});
1115+
10971116
// check that an event has not been raised, and that the request is not found
10981117
expect(eventHandler).not.toHaveBeenCalled();
10991118
expect(

spec/unit/matrix-client.spec.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2366,6 +2366,61 @@ describe("MatrixClient", function () {
23662366
});
23672367
});
23682368

2369+
describe("disableVoip option", () => {
2370+
const baseUrl = "https://alice-server.com";
2371+
const userId = "@alice:bar";
2372+
const accessToken = "sometoken";
2373+
2374+
beforeEach(() => {
2375+
mocked(supportsMatrixCall).mockReturnValue(true);
2376+
});
2377+
2378+
afterAll(() => {
2379+
mocked(supportsMatrixCall).mockReset();
2380+
});
2381+
2382+
it("should not call /voip/turnServer when disableVoip = true", () => {
2383+
fetchMock.getOnce(`${baseUrl}/_matrix/client/unstable/voip/turnServer`, 200);
2384+
2385+
const client = createClient({
2386+
baseUrl,
2387+
accessToken,
2388+
userId,
2389+
disableVoip: true,
2390+
});
2391+
2392+
// Only check createCall / supportsVoip, avoid startClient
2393+
expect(client.createCall("!roomId:example.com")).toBeNull();
2394+
expect(client.supportsVoip?.()).toBe(false);
2395+
});
2396+
2397+
it("should call /voip/turnServer when disableVoip is not set", () => {
2398+
fetchMock.getOnce(`${baseUrl}/_matrix/client/unstable/voip/turnServer`, {
2399+
uris: ["turn:turn.example.org"],
2400+
});
2401+
2402+
createClient({
2403+
baseUrl,
2404+
accessToken,
2405+
userId,
2406+
});
2407+
2408+
// The call will trigger the request if VoIP is supported
2409+
expect(fetchMock.called(`${baseUrl}/_matrix/client/unstable/voip/turnServer`)).toBe(false);
2410+
});
2411+
2412+
it("should return null from createCall when disableVoip = true", () => {
2413+
const client = createClient({
2414+
baseUrl,
2415+
accessToken,
2416+
userId,
2417+
disableVoip: true,
2418+
});
2419+
2420+
expect(client.createCall("!roomId:example.com")).toBeNull();
2421+
});
2422+
});
2423+
23692424
describe("support for ignoring invites", () => {
23702425
beforeEach(() => {
23712426
// Mockup `getAccountData`/`setAccountData`.

spec/unit/matrixrtc/CallMembership.spec.ts

Lines changed: 90 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ limitations under the License.
1515
*/
1616

1717
import { type MatrixEvent } from "../../../src";
18-
import { CallMembership, DEFAULT_EXPIRE_DURATION } from "../../../src/matrixrtc/CallMembership";
1918
import { rtcMembershipTemplate, sessionMembershipTemplate } from "./mocks";
19+
import { CallMembership, DEFAULT_EXPIRE_DURATION } from "../../../src/matrixrtc/CallMembership";
2020

2121
function makeMockEvent(originTs = 0, content = {}): MatrixEvent {
2222
return {
@@ -187,12 +187,24 @@ describe("CallMembership", () => {
187187

188188
describe("RtcMembershipData", () => {
189189
const membershipTemplate = rtcMembershipTemplate;
190+
190191
it("rejects membership with no slot_id", () => {
191192
expect(() => {
192193
new CallMembership(makeMockEvent(0, { ...membershipTemplate, slot_id: undefined }));
193194
}).toThrow();
194195
});
195196

197+
it("rejects membership with invalid slot_id", () => {
198+
expect(() => {
199+
new CallMembership(makeMockEvent(0, { ...membershipTemplate, slot_id: "invalid_slot_id" }));
200+
}).toThrow();
201+
});
202+
it("accepts membership with valid slot_id", () => {
203+
expect(() => {
204+
new CallMembership(makeMockEvent(0, { ...membershipTemplate, slot_id: "m.call#" }));
205+
}).not.toThrow();
206+
});
207+
196208
it("rejects membership with no application", () => {
197209
expect(() => {
198210
new CallMembership(makeMockEvent(0, { ...membershipTemplate, application: undefined }));
@@ -248,18 +260,91 @@ describe("CallMembership", () => {
248260
new CallMembership(
249261
makeMockEvent(0, {
250262
...membershipTemplate,
251-
member: { id: "test", device_id: "test", user_id: "@test:user.id" },
263+
member: { id: "test", device_id: "test", user_id: "@test-wrong-user:user.id" },
264+
}),
265+
);
266+
}).toThrow();
267+
});
268+
it("rejects membership with incorrect sticky_key", () => {
269+
expect(() => {
270+
new CallMembership(makeMockEvent(0, membershipTemplate));
271+
}).not.toThrow();
272+
expect(() => {
273+
new CallMembership(
274+
makeMockEvent(0, {
275+
...membershipTemplate,
276+
sticky_key: 1,
277+
msc4354_sticky_key: undefined,
278+
}),
279+
);
280+
}).toThrow();
281+
expect(() => {
282+
new CallMembership(
283+
makeMockEvent(0, {
284+
...membershipTemplate,
285+
sticky_key: "1",
286+
msc4354_sticky_key: undefined,
252287
}),
253288
);
254289
}).not.toThrow();
290+
expect(() => {
291+
new CallMembership(makeMockEvent(0, { ...membershipTemplate, msc4354_sticky_key: undefined }));
292+
}).toThrow();
293+
expect(() => {
294+
new CallMembership(
295+
makeMockEvent(0, {
296+
...membershipTemplate,
297+
msc4354_sticky_key: 1,
298+
sticky_key: "valid",
299+
}),
300+
);
301+
}).toThrow();
302+
expect(() => {
303+
new CallMembership(
304+
makeMockEvent(0, {
305+
...membershipTemplate,
306+
msc4354_sticky_key: "valid",
307+
sticky_key: "valid",
308+
}),
309+
);
310+
}).not.toThrow();
311+
expect(() => {
312+
new CallMembership(
313+
makeMockEvent(0, {
314+
...membershipTemplate,
315+
msc4354_sticky_key: "valid_but_different",
316+
sticky_key: "valid",
317+
}),
318+
);
319+
}).toThrow();
255320
});
256321

257322
it("considers memberships unexpired if local age low enough", () => {
258-
// TODO link prev event
323+
const now = Date.now();
324+
const startEv = makeMockEvent(now - DEFAULT_EXPIRE_DURATION + 100, membershipTemplate);
325+
const membershipWithRel = new CallMembership(
326+
//update 900 ms later
327+
makeMockEvent(now - DEFAULT_EXPIRE_DURATION + 1000, membershipTemplate),
328+
startEv,
329+
);
330+
const membershipWithoutRel = new CallMembership(startEv);
331+
expect(membershipWithRel.isExpired()).toEqual(false);
332+
expect(membershipWithoutRel.isExpired()).toEqual(false);
333+
expect(membershipWithoutRel.createdTs()).toEqual(membershipWithRel.createdTs());
259334
});
260335

261336
it("considers memberships expired if local age large enough", () => {
262-
// TODO link prev event
337+
const now = Date.now();
338+
const startEv = makeMockEvent(now - DEFAULT_EXPIRE_DURATION - 100, membershipTemplate);
339+
const membershipWithRel = new CallMembership(
340+
//update 1100 ms later (so the update is still after expiry)
341+
makeMockEvent(now - DEFAULT_EXPIRE_DURATION + 1000, membershipTemplate),
342+
startEv,
343+
);
344+
const membershipWithoutRel = new CallMembership(startEv);
345+
expect(membershipWithRel.isExpired()).toEqual(true);
346+
expect(membershipWithoutRel.isExpired()).toEqual(true);
347+
expect(membershipWithoutRel.createdTs()).toEqual(membershipWithRel.createdTs());
263348
});
264349

265350
describe("getTransport", () => {
@@ -279,6 +364,7 @@ describe("CallMembership", () => {
279364
expect(membership.getTransport(oldestMembership)).toStrictEqual({ type: "livekit" });
280365
});
281366
});
367+
282368
describe("correct values from computed fields", () => {
283369
const membership = new CallMembership(makeMockEvent(0, membershipTemplate));
284370
it("returns correct sender", () => {

spec/unit/matrixrtc/MatrixRTCSession.spec.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -232,15 +232,16 @@ describe("MatrixRTCSession", () => {
232232
};
233233

234234
const mockRoom = makeMockRoom([testMembership]);
235+
const now = Date.now();
235236
mockRoom.findEventById = jest
236237
.fn()
237238
.mockImplementation((id) =>
238239
id === "id"
239-
? new MatrixEvent({ content: { ...rtcMembershipTemplate }, origin_server_ts: 100 })
240+
? new MatrixEvent({ content: { ...rtcMembershipTemplate }, origin_server_ts: now + 100 })
240241
: undefined,
241242
);
242243
sess = await MatrixRTCSession.sessionForSlot(client, mockRoom, callSession);
243-
expect(sess.memberships[0].createdTs()).toBe(100);
244+
expect(sess.memberships[0].createdTs()).toBe(now + 100);
244245
});
245246
it("fetches related events if needed from cs api", async () => {
246247
const testMembership = {
@@ -251,12 +252,14 @@ describe("MatrixRTCSession", () => {
251252
};
252253

253254
const mockRoom = makeMockRoom([testMembership]);
255+
const now = Date.now();
256+
254257
mockRoom.findEventById = jest.fn().mockReturnValue(undefined);
255258
client.fetchRoomEvent = jest
256259
.fn()
257-
.mockResolvedValue({ content: { ...rtcMembershipTemplate }, origin_server_ts: 100 });
260+
.mockResolvedValue({ content: { ...rtcMembershipTemplate }, origin_server_ts: now + 100 });
258261
sess = await MatrixRTCSession.sessionForSlot(client, mockRoom, callSession);
259-
expect(sess.memberships[0].createdTs()).toBe(100);
262+
expect(sess.memberships[0].createdTs()).toBe(now + 100);
260263
});
261264
});
262265

@@ -322,9 +325,8 @@ describe("MatrixRTCSession", () => {
322325
type: "livekit",
323326
focus_selection: "oldest_membership",
324327
});
325-
expect(sess.resolveActiveFocus(sess.memberships.find((m) => m.deviceId === "old"))).toBe(
326-
firstPreferredFocus,
327-
);
328+
const oldest = sess.memberships.find((m) => m.deviceId === "old");
329+
expect(oldest?.getTransport(sess.getOldestMembership()!)).toBe(firstPreferredFocus);
328330
jest.useRealTimers();
329331
});
330332
it("does not provide focus if the selection method is unknown", async () => {
@@ -344,7 +346,7 @@ describe("MatrixRTCSession", () => {
344346
type: "livekit",
345347
focus_selection: "unknown",
346348
});
347-
expect(sess.resolveActiveFocus(sess.memberships.find((m) => m.deviceId === "old"))).toBe(undefined);
349+
expect(sess.memberships.length).toBe(0);
348350
});
349351
});
350352

spec/unit/matrixrtc/MembershipManager.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ describe("MembershipManager", () => {
166166
room.roomId,
167167
"org.matrix.msc4143.rtc.member",
168168
{
169-
application: { type: "m.call", id: "" },
169+
application: { type: "m.call" },
170170
member: {
171171
user_id: "@alice:example.org",
172172
id: "_@alice:example.org_AAAAAAA_m.call",

spec/unit/matrixrtc/mocks.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,12 @@ export const sessionMembershipTemplate: SessionMembershipData & { user_id: strin
4949
};
5050

5151
export const rtcMembershipTemplate: RtcMembershipData = {
52-
"slot_id": "m.call#",
53-
"application": { "type": "m.call", "m.call.id": "", "m.call.intent": "voice" },
54-
"member": { user_id: "@alice:example.org", device_id: "AAAAAAA", id: "xyzHASHxyz" },
55-
"rtc_transports": [{ type: "livekit" }],
56-
"m.call.intent": "voice",
57-
"versions": [],
52+
slot_id: "m.call#",
53+
application: { "type": "m.call", "m.call.id": "", "m.call.intent": "voice" },
54+
member: { user_id: "@alice:example.org", device_id: "AAAAAAA", id: "xyzHASHxyz" },
55+
rtc_transports: [{ type: "livekit" }],
56+
msc4354_sticky_key: "my_sticky_key",
57+
versions: [],
5858
};
5959

6060
export type MockClient = Pick<

0 commit comments

Comments
 (0)