You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-11-23 17:02:25 +03:00
MatrixRTC: Disable room transport fallback for keys (#4929)
* matrixRTC: Disable room transport fallback for keys * post rebase fix * post merge fix --------- Co-authored-by: Will Hunt <will@half-shot.uk>
This commit is contained in:
@@ -22,9 +22,7 @@ import { type ToDeviceKeyTransport } from "../../../src/matrixrtc/ToDeviceKeyTra
|
|||||||
import { KeyTransportEvents, type KeyTransportEventsHandlerMap } from "../../../src/matrixrtc/IKeyTransport.ts";
|
import { KeyTransportEvents, type KeyTransportEventsHandlerMap } from "../../../src/matrixrtc/IKeyTransport.ts";
|
||||||
import { membershipTemplate, mockCallMembership } from "./mocks.ts";
|
import { membershipTemplate, mockCallMembership } from "./mocks.ts";
|
||||||
import { decodeBase64, TypedEventEmitter } from "../../../src";
|
import { decodeBase64, TypedEventEmitter } from "../../../src";
|
||||||
import { RoomAndToDeviceTransport } from "../../../src/matrixrtc/RoomAndToDeviceKeyTransport.ts";
|
import { logger } from "../../../src/logger.ts";
|
||||||
import { type RoomKeyTransport } from "../../../src/matrixrtc/RoomKeyTransport.ts";
|
|
||||||
import { logger, type Logger } from "../../../src/logger.ts";
|
|
||||||
import { getParticipantId } from "../../../src/matrixrtc/utils.ts";
|
import { getParticipantId } from "../../../src/matrixrtc/utils.ts";
|
||||||
|
|
||||||
describe("RTCEncryptionManager", () => {
|
describe("RTCEncryptionManager", () => {
|
||||||
@@ -782,86 +780,6 @@ describe("RTCEncryptionManager", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Should re-distribute key on transport switch", async () => {
|
|
||||||
const toDeviceEmitter = new TypedEventEmitter<KeyTransportEvents, KeyTransportEventsHandlerMap>();
|
|
||||||
const mockToDeviceTransport = {
|
|
||||||
start: jest.fn(),
|
|
||||||
stop: jest.fn(),
|
|
||||||
sendKey: jest.fn().mockResolvedValue(undefined),
|
|
||||||
on: toDeviceEmitter.on.bind(toDeviceEmitter),
|
|
||||||
off: toDeviceEmitter.off.bind(toDeviceEmitter),
|
|
||||||
emit: toDeviceEmitter.emit.bind(toDeviceEmitter),
|
|
||||||
setParentLogger: jest.fn(),
|
|
||||||
} as unknown as Mocked<ToDeviceKeyTransport>;
|
|
||||||
|
|
||||||
const roomEmitter = new TypedEventEmitter<KeyTransportEvents, KeyTransportEventsHandlerMap>();
|
|
||||||
const mockRoomTransport = {
|
|
||||||
start: jest.fn(),
|
|
||||||
stop: jest.fn(),
|
|
||||||
sendKey: jest.fn().mockResolvedValue(undefined),
|
|
||||||
on: roomEmitter.on.bind(roomEmitter),
|
|
||||||
off: roomEmitter.off.bind(roomEmitter),
|
|
||||||
emit: roomEmitter.emit.bind(roomEmitter),
|
|
||||||
setParentLogger: jest.fn(),
|
|
||||||
} as unknown as Mocked<RoomKeyTransport>;
|
|
||||||
|
|
||||||
const mockLogger = {
|
|
||||||
debug: jest.fn(),
|
|
||||||
warn: jest.fn(),
|
|
||||||
} as unknown as Mocked<Logger>;
|
|
||||||
|
|
||||||
const transport = new RoomAndToDeviceTransport(mockToDeviceTransport, mockRoomTransport, {
|
|
||||||
getChild: jest.fn().mockReturnValue(mockLogger),
|
|
||||||
} as unknown as Mocked<Logger>);
|
|
||||||
|
|
||||||
encryptionManager = new RTCEncryptionManager(
|
|
||||||
"@alice:example.org",
|
|
||||||
"DEVICE01",
|
|
||||||
getMembershipMock,
|
|
||||||
transport,
|
|
||||||
statistics,
|
|
||||||
onEncryptionKeysChanged,
|
|
||||||
);
|
|
||||||
|
|
||||||
const members = [
|
|
||||||
aCallMembership("@bob:example.org", "BOBDEVICE"),
|
|
||||||
aCallMembership("@bob:example.org", "BOBDEVICE2"),
|
|
||||||
aCallMembership("@carl:example.org", "CARLDEVICE"),
|
|
||||||
];
|
|
||||||
getMembershipMock.mockReturnValue(members);
|
|
||||||
|
|
||||||
// Let's join
|
|
||||||
encryptionManager.join(undefined);
|
|
||||||
encryptionManager.onMembershipsUpdate();
|
|
||||||
await jest.advanceTimersByTimeAsync(10);
|
|
||||||
|
|
||||||
// Should have sent the key to the toDevice transport
|
|
||||||
expect(mockToDeviceTransport.sendKey).toHaveBeenCalledTimes(1);
|
|
||||||
expect(mockRoomTransport.sendKey).not.toHaveBeenCalled();
|
|
||||||
|
|
||||||
// Simulate receiving a key by room transport
|
|
||||||
roomEmitter.emit(
|
|
||||||
KeyTransportEvents.ReceivedKeys,
|
|
||||||
"@bob:example.org",
|
|
||||||
"BOBDEVICE",
|
|
||||||
"AAAAAAAAAAA",
|
|
||||||
0 /* KeyId */,
|
|
||||||
0 /* Timestamp */,
|
|
||||||
);
|
|
||||||
|
|
||||||
await jest.runOnlyPendingTimersAsync();
|
|
||||||
|
|
||||||
// The key should have been re-distributed to the room transport
|
|
||||||
expect(mockRoomTransport.sendKey).toHaveBeenCalled();
|
|
||||||
expect(mockToDeviceTransport.sendKey).toHaveBeenCalledWith(
|
|
||||||
expect.any(String),
|
|
||||||
// It is the first key re-distributed
|
|
||||||
0,
|
|
||||||
// to all the members
|
|
||||||
members.map((m) => ({ userId: m.sender, deviceId: m.deviceId, membershipTs: m.createdTs() })),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
function aCallMembership(userId: string, deviceId: string, ts: number = 1000): CallMembership {
|
function aCallMembership(userId: string, deviceId: string, ts: number = 1000): CallMembership {
|
||||||
return mockCallMembership(
|
return mockCallMembership(
|
||||||
{ ...membershipTemplate, user_id: userId, device_id: deviceId, created_ts: ts },
|
{ ...membershipTemplate, user_id: userId, device_id: deviceId, created_ts: ts },
|
||||||
|
|||||||
@@ -1,187 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2025 The Matrix.org Foundation C.I.C.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { type Mocked } from "jest-mock";
|
|
||||||
|
|
||||||
import { makeKey, makeMockEvent, makeMockRoom } from "./mocks";
|
|
||||||
import { EventType, type IRoomTimelineData, type Room, RoomEvent, type MatrixClient } from "../../../src";
|
|
||||||
import { ToDeviceKeyTransport } from "../../../src/matrixrtc/ToDeviceKeyTransport.ts";
|
|
||||||
import {
|
|
||||||
getMockClientWithEventEmitter,
|
|
||||||
mockClientMethodsEvents,
|
|
||||||
mockClientMethodsUser,
|
|
||||||
} from "../../test-utils/client.ts";
|
|
||||||
import { type ParticipantDeviceInfo, type Statistics } from "../../../src/matrixrtc";
|
|
||||||
import { KeyTransportEvents } from "../../../src/matrixrtc/IKeyTransport.ts";
|
|
||||||
import { type Logger } from "../../../src/logger.ts";
|
|
||||||
import { RoomAndToDeviceEvents, RoomAndToDeviceTransport } from "../../../src/matrixrtc/RoomAndToDeviceKeyTransport.ts";
|
|
||||||
import { RoomKeyTransport } from "../../../src/matrixrtc/RoomKeyTransport.ts";
|
|
||||||
|
|
||||||
describe("RoomAndToDeviceTransport", () => {
|
|
||||||
const roomId = "!room:id";
|
|
||||||
|
|
||||||
let mockClient: Mocked<MatrixClient>;
|
|
||||||
let statistics: Statistics;
|
|
||||||
let mockLogger: Mocked<Logger>;
|
|
||||||
let transport: RoomAndToDeviceTransport;
|
|
||||||
let mockRoom: Room;
|
|
||||||
let sendEventMock: jest.Mock;
|
|
||||||
let roomKeyTransport: RoomKeyTransport;
|
|
||||||
let toDeviceKeyTransport: ToDeviceKeyTransport;
|
|
||||||
let toDeviceSendKeySpy: jest.SpyInstance;
|
|
||||||
let roomSendKeySpy: jest.SpyInstance;
|
|
||||||
beforeEach(() => {
|
|
||||||
sendEventMock = jest.fn();
|
|
||||||
mockClient = getMockClientWithEventEmitter({
|
|
||||||
encryptAndSendToDevice: jest.fn(),
|
|
||||||
getDeviceId: jest.fn().mockReturnValue("MYDEVICE"),
|
|
||||||
...mockClientMethodsEvents(),
|
|
||||||
...mockClientMethodsUser("@alice:example.org"),
|
|
||||||
sendEvent: sendEventMock,
|
|
||||||
});
|
|
||||||
mockRoom = makeMockRoom([]);
|
|
||||||
mockLogger = {
|
|
||||||
debug: jest.fn(),
|
|
||||||
warn: jest.fn(),
|
|
||||||
getChild: jest.fn(),
|
|
||||||
} as unknown as Mocked<Logger>;
|
|
||||||
mockLogger.getChild.mockReturnValue(mockLogger);
|
|
||||||
statistics = {
|
|
||||||
counters: {
|
|
||||||
roomEventEncryptionKeysSent: 0,
|
|
||||||
roomEventEncryptionKeysReceived: 0,
|
|
||||||
},
|
|
||||||
totals: {
|
|
||||||
roomEventEncryptionKeysReceivedTotalAge: 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
roomKeyTransport = new RoomKeyTransport(mockRoom, mockClient, statistics);
|
|
||||||
toDeviceKeyTransport = new ToDeviceKeyTransport(
|
|
||||||
"@alice:example.org",
|
|
||||||
"MYDEVICE",
|
|
||||||
mockRoom.roomId,
|
|
||||||
mockClient,
|
|
||||||
statistics,
|
|
||||||
);
|
|
||||||
transport = new RoomAndToDeviceTransport(toDeviceKeyTransport, roomKeyTransport, mockLogger);
|
|
||||||
toDeviceSendKeySpy = jest.spyOn(toDeviceKeyTransport, "sendKey");
|
|
||||||
roomSendKeySpy = jest.spyOn(roomKeyTransport, "sendKey");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should enable to device transport when starting", () => {
|
|
||||||
transport.start();
|
|
||||||
expect(transport.enabled.room).toBeFalsy();
|
|
||||||
expect(transport.enabled.toDevice).toBeTruthy();
|
|
||||||
});
|
|
||||||
it("only sends to device keys when sending a key", async () => {
|
|
||||||
transport.start();
|
|
||||||
await transport.sendKey("1235", 0, [
|
|
||||||
{ userId: "@alice:example.org", deviceId: "ALICEDEVICE", membershipTs: 1234 },
|
|
||||||
]);
|
|
||||||
expect(toDeviceSendKeySpy).toHaveBeenCalledTimes(1);
|
|
||||||
expect(roomSendKeySpy).toHaveBeenCalledTimes(0);
|
|
||||||
expect(transport.enabled.room).toBeFalsy();
|
|
||||||
expect(transport.enabled.toDevice).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("enables room transport and disables to device transport when receiving a room key", async () => {
|
|
||||||
transport.start();
|
|
||||||
const onNewKeyFromTransport = jest.fn();
|
|
||||||
const onTransportEnabled = jest.fn();
|
|
||||||
transport.on(KeyTransportEvents.ReceivedKeys, onNewKeyFromTransport);
|
|
||||||
transport.on(RoomAndToDeviceEvents.EnabledTransportsChanged, onTransportEnabled);
|
|
||||||
mockRoom.emit(
|
|
||||||
RoomEvent.Timeline,
|
|
||||||
makeMockEvent(EventType.CallEncryptionKeysPrefix, "@bob:example.org", roomId, {
|
|
||||||
call_id: "",
|
|
||||||
keys: [makeKey(0, "testKey")],
|
|
||||||
sent_ts: Date.now(),
|
|
||||||
device_id: "AAAAAAA",
|
|
||||||
}),
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
false,
|
|
||||||
{} as IRoomTimelineData,
|
|
||||||
);
|
|
||||||
await jest.advanceTimersByTimeAsync(1);
|
|
||||||
expect(transport.enabled.room).toBeTruthy();
|
|
||||||
expect(transport.enabled.toDevice).toBeFalsy();
|
|
||||||
|
|
||||||
await transport.sendKey("1235", 0, [
|
|
||||||
{ userId: "@alice:example.org", deviceId: "AlICEDEV", membershipTs: 1234 },
|
|
||||||
]);
|
|
||||||
expect(sendEventMock).toHaveBeenCalledTimes(1);
|
|
||||||
expect(roomSendKeySpy).toHaveBeenCalledTimes(1);
|
|
||||||
expect(toDeviceSendKeySpy).toHaveBeenCalledTimes(0);
|
|
||||||
expect(onTransportEnabled).toHaveBeenCalledWith({ toDevice: false, room: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
it("enables room transport and disables to device transport on widget driver error", async () => {
|
|
||||||
mockClient.encryptAndSendToDevice.mockRejectedValue({
|
|
||||||
message:
|
|
||||||
"unknown variant `send_to_device`, expected one of `supported_api_versions`, `content_loaded`, `get_openid`, `org.matrix.msc2876.read_events`, `send_event`, `org.matrix.msc4157.update_delayed_event` at line 1 column 22",
|
|
||||||
});
|
|
||||||
|
|
||||||
transport.start();
|
|
||||||
const membership: ParticipantDeviceInfo = {
|
|
||||||
userId: "@alice:example.org",
|
|
||||||
deviceId: "ALICEDEVICE",
|
|
||||||
membershipTs: 1234,
|
|
||||||
};
|
|
||||||
const onTransportEnabled = jest.fn();
|
|
||||||
transport.on(RoomAndToDeviceEvents.EnabledTransportsChanged, onTransportEnabled);
|
|
||||||
|
|
||||||
// We start with toDevice transport enabled
|
|
||||||
expect(transport.enabled.room).toBeFalsy();
|
|
||||||
expect(transport.enabled.toDevice).toBeTruthy();
|
|
||||||
|
|
||||||
await transport.sendKey("1235", 0, [membership]);
|
|
||||||
|
|
||||||
// We switched transport, now room transport is enabled
|
|
||||||
expect(onTransportEnabled).toHaveBeenCalledWith({ toDevice: false, room: true });
|
|
||||||
expect(transport.enabled.room).toBeTruthy();
|
|
||||||
expect(transport.enabled.toDevice).toBeFalsy();
|
|
||||||
|
|
||||||
// sanity check that we called the failang to device send key.
|
|
||||||
expect(toDeviceKeyTransport.sendKey).toHaveBeenCalledWith("1235", 0, [membership]);
|
|
||||||
expect(toDeviceKeyTransport.sendKey).toHaveBeenCalledTimes(1);
|
|
||||||
// We re-sent the key via the room transport
|
|
||||||
expect(roomKeyTransport.sendKey).toHaveBeenCalledWith("1235", 0, [membership]);
|
|
||||||
expect(roomKeyTransport.sendKey).toHaveBeenCalledTimes(1);
|
|
||||||
|
|
||||||
mockClient.encryptAndSendToDevice.mockRestore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("does log that it did nothing when disabled", () => {
|
|
||||||
transport.start();
|
|
||||||
const onNewKeyFromTransport = jest.fn();
|
|
||||||
const onTransportEnabled = jest.fn();
|
|
||||||
transport.on(KeyTransportEvents.ReceivedKeys, onNewKeyFromTransport);
|
|
||||||
transport.on(RoomAndToDeviceEvents.EnabledTransportsChanged, onTransportEnabled);
|
|
||||||
|
|
||||||
transport.setEnabled({ toDevice: false, room: false });
|
|
||||||
const dateNow = Date.now();
|
|
||||||
roomKeyTransport.emit(KeyTransportEvents.ReceivedKeys, "user", "device", "roomKey", 0, dateNow);
|
|
||||||
toDeviceKeyTransport.emit(KeyTransportEvents.ReceivedKeys, "user", "device", "toDeviceKey", 0, Date.now());
|
|
||||||
|
|
||||||
expect(mockLogger.debug).toHaveBeenCalledWith("To Device transport is disabled, ignoring received keys");
|
|
||||||
// for room key transport we will never get a disabled message because its will always just turn on
|
|
||||||
expect(onTransportEnabled).toHaveBeenNthCalledWith(1, { toDevice: false, room: false });
|
|
||||||
expect(onTransportEnabled).toHaveBeenNthCalledWith(2, { toDevice: false, room: true });
|
|
||||||
expect(onNewKeyFromTransport).toHaveBeenCalledTimes(1);
|
|
||||||
expect(onNewKeyFromTransport).toHaveBeenCalledWith("user", "device", "roomKey", 0, dateNow);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -7,11 +7,6 @@ import { type CallMembership } from "./CallMembership.ts";
|
|||||||
import { type KeyTransportEventListener, KeyTransportEvents, type IKeyTransport } from "./IKeyTransport.ts";
|
import { type KeyTransportEventListener, KeyTransportEvents, type IKeyTransport } from "./IKeyTransport.ts";
|
||||||
import { isMyMembership, type ParticipantId, type Statistics } from "./types.ts";
|
import { isMyMembership, type ParticipantId, type Statistics } from "./types.ts";
|
||||||
import { getParticipantId } from "./utils.ts";
|
import { getParticipantId } from "./utils.ts";
|
||||||
import {
|
|
||||||
type EnabledTransports,
|
|
||||||
RoomAndToDeviceEvents,
|
|
||||||
RoomAndToDeviceTransport,
|
|
||||||
} from "./RoomAndToDeviceKeyTransport.ts";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface is for testing and for making it possible to interchange the encryption manager.
|
* This interface is for testing and for making it possible to interchange the encryption manager.
|
||||||
@@ -119,10 +114,7 @@ export class EncryptionManager implements IEncryptionManager {
|
|||||||
this.manageMediaKeys = this.joinConfig?.manageMediaKeys ?? this.manageMediaKeys;
|
this.manageMediaKeys = this.joinConfig?.manageMediaKeys ?? this.manageMediaKeys;
|
||||||
|
|
||||||
this.transport.on(KeyTransportEvents.ReceivedKeys, this.onNewKeyReceived);
|
this.transport.on(KeyTransportEvents.ReceivedKeys, this.onNewKeyReceived);
|
||||||
// Deprecate RoomKeyTransport: this can get removed.
|
|
||||||
if (this.transport instanceof RoomAndToDeviceTransport) {
|
|
||||||
this.transport.on(RoomAndToDeviceEvents.EnabledTransportsChanged, this.onTransportChanged);
|
|
||||||
}
|
|
||||||
this.transport.start();
|
this.transport.start();
|
||||||
if (this.joinConfig?.manageMediaKeys) {
|
if (this.joinConfig?.manageMediaKeys) {
|
||||||
this.makeNewSenderKey();
|
this.makeNewSenderKey();
|
||||||
@@ -315,10 +307,6 @@ export class EncryptionManager implements IEncryptionManager {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private onTransportChanged: (enabled: EnabledTransports) => void = () => {
|
|
||||||
this.requestSendCurrentKey();
|
|
||||||
};
|
|
||||||
|
|
||||||
public onNewKeyReceived: KeyTransportEventListener = (userId, deviceId, keyBase64Encoded, index, timestamp) => {
|
public onNewKeyReceived: KeyTransportEventListener = (userId, deviceId, keyBase64Encoded, index, timestamp) => {
|
||||||
this.logger.debug(`Received key over key transport ${userId}:${deviceId} at index ${index}`);
|
this.logger.debug(`Received key over key transport ${userId}:${deviceId} at index ${index}`);
|
||||||
this.setEncryptionKey(userId, deviceId, index, keyBase64Encoded, timestamp);
|
this.setEncryptionKey(userId, deviceId, index, keyBase64Encoded, timestamp);
|
||||||
|
|||||||
@@ -36,22 +36,17 @@ import type {
|
|||||||
RTCCallIntent,
|
RTCCallIntent,
|
||||||
Transport,
|
Transport,
|
||||||
} from "./types.ts";
|
} from "./types.ts";
|
||||||
import { RoomKeyTransport } from "./RoomKeyTransport.ts";
|
|
||||||
import {
|
import {
|
||||||
MembershipManagerEvent,
|
MembershipManagerEvent,
|
||||||
type MembershipManagerEventHandlerMap,
|
type MembershipManagerEventHandlerMap,
|
||||||
type IMembershipManager,
|
type IMembershipManager,
|
||||||
} from "./IMembershipManager.ts";
|
} from "./IMembershipManager.ts";
|
||||||
import { RTCEncryptionManager } from "./RTCEncryptionManager.ts";
|
import { RTCEncryptionManager } from "./RTCEncryptionManager.ts";
|
||||||
import {
|
|
||||||
RoomAndToDeviceEvents,
|
|
||||||
type RoomAndToDeviceEventsHandlerMap,
|
|
||||||
RoomAndToDeviceTransport,
|
|
||||||
} from "./RoomAndToDeviceKeyTransport.ts";
|
|
||||||
import { TypedReEmitter } from "../ReEmitter.ts";
|
|
||||||
import { ToDeviceKeyTransport } from "./ToDeviceKeyTransport.ts";
|
import { ToDeviceKeyTransport } from "./ToDeviceKeyTransport.ts";
|
||||||
|
import { TypedReEmitter } from "../ReEmitter.ts";
|
||||||
import { type MatrixEvent } from "../models/event.ts";
|
import { type MatrixEvent } from "../models/event.ts";
|
||||||
import { RoomStickyEventsEvent, type RoomStickyEventsMap } from "../models/room-sticky-events.ts";
|
import { RoomStickyEventsEvent, type RoomStickyEventsMap } from "../models/room-sticky-events.ts";
|
||||||
|
import { RoomKeyTransport } from "./RoomKeyTransport.ts";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Events emitted by MatrixRTCSession
|
* Events emitted by MatrixRTCSession
|
||||||
@@ -257,8 +252,8 @@ interface SessionMembershipsForRoomOpts {
|
|||||||
* This class doesn't deal with media at all, just membership & properties of a session.
|
* This class doesn't deal with media at all, just membership & properties of a session.
|
||||||
*/
|
*/
|
||||||
export class MatrixRTCSession extends TypedEventEmitter<
|
export class MatrixRTCSession extends TypedEventEmitter<
|
||||||
MatrixRTCSessionEvent | RoomAndToDeviceEvents | MembershipManagerEvent,
|
MatrixRTCSessionEvent | MembershipManagerEvent,
|
||||||
MatrixRTCSessionEventHandlerMap & RoomAndToDeviceEventsHandlerMap & MembershipManagerEventHandlerMap
|
MatrixRTCSessionEventHandlerMap & MembershipManagerEventHandlerMap
|
||||||
> {
|
> {
|
||||||
private membershipManager?: IMembershipManager;
|
private membershipManager?: IMembershipManager;
|
||||||
private encryptionManager?: IEncryptionManager;
|
private encryptionManager?: IEncryptionManager;
|
||||||
@@ -571,8 +566,8 @@ export class MatrixRTCSession extends TypedEventEmitter<
|
|||||||
}
|
}
|
||||||
|
|
||||||
private reEmitter = new TypedReEmitter<
|
private reEmitter = new TypedReEmitter<
|
||||||
MatrixRTCSessionEvent | RoomAndToDeviceEvents | MembershipManagerEvent,
|
MatrixRTCSessionEvent | MembershipManagerEvent,
|
||||||
MatrixRTCSessionEventHandlerMap & RoomAndToDeviceEventsHandlerMap & MembershipManagerEventHandlerMap
|
MatrixRTCSessionEventHandlerMap & MembershipManagerEventHandlerMap
|
||||||
>(this);
|
>(this);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -620,13 +615,7 @@ export class MatrixRTCSession extends TypedEventEmitter<
|
|||||||
this.logger.info("Using to-device with room fallback transport for encryption keys");
|
this.logger.info("Using to-device with room fallback transport for encryption keys");
|
||||||
const [uId, dId] = [this.client.getUserId()!, this.client.getDeviceId()!];
|
const [uId, dId] = [this.client.getUserId()!, this.client.getDeviceId()!];
|
||||||
const [room, client, statistics] = [this.roomSubset, this.client, this.statistics];
|
const [room, client, statistics] = [this.roomSubset, this.client, this.statistics];
|
||||||
// Deprecate RoomKeyTransport: only ToDeviceKeyTransport is needed once deprecated
|
const transport = new ToDeviceKeyTransport(uId, dId, room.roomId, client, statistics);
|
||||||
const roomKeyTransport = new RoomKeyTransport(room, client, statistics);
|
|
||||||
const toDeviceTransport = new ToDeviceKeyTransport(uId, dId, room.roomId, client, statistics);
|
|
||||||
transport = new RoomAndToDeviceTransport(toDeviceTransport, roomKeyTransport, this.logger);
|
|
||||||
|
|
||||||
// Expose the changes so the ui can display the currently used transport.
|
|
||||||
this.reEmitter.reEmit(transport, [RoomAndToDeviceEvents.EnabledTransportsChanged]);
|
|
||||||
this.encryptionManager = new RTCEncryptionManager(
|
this.encryptionManager = new RTCEncryptionManager(
|
||||||
this.client.getUserId()!,
|
this.client.getUserId()!,
|
||||||
this.client.getDeviceId()!,
|
this.client.getDeviceId()!,
|
||||||
|
|||||||
@@ -29,11 +29,6 @@ import type {
|
|||||||
Statistics,
|
Statistics,
|
||||||
} from "./types.ts";
|
} from "./types.ts";
|
||||||
import { getParticipantId, OutdatedKeyFilter } from "./utils.ts";
|
import { getParticipantId, OutdatedKeyFilter } from "./utils.ts";
|
||||||
import {
|
|
||||||
type EnabledTransports,
|
|
||||||
RoomAndToDeviceEvents,
|
|
||||||
RoomAndToDeviceTransport,
|
|
||||||
} from "./RoomAndToDeviceKeyTransport.ts";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RTCEncryptionManager is used to manage the encryption keys for a call.
|
* RTCEncryptionManager is used to manage the encryption keys for a call.
|
||||||
@@ -137,10 +132,6 @@ export class RTCEncryptionManager implements IEncryptionManager {
|
|||||||
this.useKeyDelay = joinConfig?.useKeyDelay ?? 1000;
|
this.useKeyDelay = joinConfig?.useKeyDelay ?? 1000;
|
||||||
this.keyRotationGracePeriodMs = joinConfig?.keyRotationGracePeriodMs ?? 10_000;
|
this.keyRotationGracePeriodMs = joinConfig?.keyRotationGracePeriodMs ?? 10_000;
|
||||||
this.transport.on(KeyTransportEvents.ReceivedKeys, this.onNewKeyReceived);
|
this.transport.on(KeyTransportEvents.ReceivedKeys, this.onNewKeyReceived);
|
||||||
// Deprecate RoomKeyTransport: this can get removed.
|
|
||||||
if (this.transport instanceof RoomAndToDeviceTransport) {
|
|
||||||
this.transport.on(RoomAndToDeviceEvents.EnabledTransportsChanged, this.onTransportChanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.transport.start();
|
this.transport.start();
|
||||||
}
|
}
|
||||||
@@ -151,29 +142,6 @@ export class RTCEncryptionManager implements IEncryptionManager {
|
|||||||
this.participantKeyRings.clear();
|
this.participantKeyRings.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Temporary for backwards compatibility
|
|
||||||
// TODO: Remove this in the future
|
|
||||||
private onTransportChanged: (enabled: EnabledTransports) => void = () => {
|
|
||||||
this.logger?.info("Transport change detected, restarting key distribution");
|
|
||||||
if (this.currentKeyDistributionPromise) {
|
|
||||||
this.currentKeyDistributionPromise
|
|
||||||
.then(() => {
|
|
||||||
if (this.outboundSession) {
|
|
||||||
this.outboundSession.sharedWith = [];
|
|
||||||
this.ensureKeyDistribution();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
this.logger?.error("Failed to restart key distribution", e);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (this.outboundSession) {
|
|
||||||
this.outboundSession.sharedWith = [];
|
|
||||||
this.ensureKeyDistribution();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will ensure that a new key is distributed and used to encrypt our media.
|
* Will ensure that a new key is distributed and used to encrypt our media.
|
||||||
* If there is already a key distribution in progress, it will schedule a new distribution round just after the current one is completed.
|
* If there is already a key distribution in progress, it will schedule a new distribution round just after the current one is completed.
|
||||||
|
|||||||
@@ -1,131 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2025 The Matrix.org Foundation C.I.C.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { logger as rootLogger, type Logger } from "../logger.ts";
|
|
||||||
import { KeyTransportEvents, type KeyTransportEventsHandlerMap, type IKeyTransport } from "./IKeyTransport.ts";
|
|
||||||
import type { RoomKeyTransport } from "./RoomKeyTransport.ts";
|
|
||||||
import { NotSupportedError, type ToDeviceKeyTransport } from "./ToDeviceKeyTransport.ts";
|
|
||||||
import { TypedEventEmitter } from "../models/typed-event-emitter.ts";
|
|
||||||
import { type ParticipantDeviceInfo } from "./types.ts";
|
|
||||||
|
|
||||||
// Deprecate RoomAndToDeviceTransport: This whole class is only a stop gap until we remove RoomKeyTransport.
|
|
||||||
export interface EnabledTransports {
|
|
||||||
toDevice: boolean;
|
|
||||||
room: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum RoomAndToDeviceEvents {
|
|
||||||
EnabledTransportsChanged = "enabled_transports_changed",
|
|
||||||
}
|
|
||||||
export type RoomAndToDeviceEventsHandlerMap = {
|
|
||||||
[RoomAndToDeviceEvents.EnabledTransportsChanged]: (enabledTransports: EnabledTransports) => void;
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* A custom transport that subscribes to room key events (via `RoomKeyTransport`) and to device key events (via: `ToDeviceKeyTransport`)
|
|
||||||
* The public setEnabled method allows to turn one or the other on or off on the fly.
|
|
||||||
* It will emit `RoomAndToDeviceEvents.EnabledTransportsChanged` if the enabled transport changes to allow comminitcating this to
|
|
||||||
* the user in the ui.
|
|
||||||
*
|
|
||||||
* Since it will always subscribe to both (room and to device) but only emit for the enabled ones, it can detect
|
|
||||||
* if a room key event was received and autoenable it.
|
|
||||||
*/
|
|
||||||
export class RoomAndToDeviceTransport
|
|
||||||
extends TypedEventEmitter<
|
|
||||||
KeyTransportEvents | RoomAndToDeviceEvents,
|
|
||||||
KeyTransportEventsHandlerMap & RoomAndToDeviceEventsHandlerMap
|
|
||||||
>
|
|
||||||
implements IKeyTransport
|
|
||||||
{
|
|
||||||
private readonly logger: Logger;
|
|
||||||
private _enabled: EnabledTransports = { toDevice: true, room: false };
|
|
||||||
public constructor(
|
|
||||||
private toDeviceTransport: ToDeviceKeyTransport,
|
|
||||||
private roomKeyTransport: RoomKeyTransport,
|
|
||||||
parentLogger?: Logger,
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
this.logger = (parentLogger ?? rootLogger).getChild(`[RoomAndToDeviceTransport]`);
|
|
||||||
// update parent loggers for the sub transports so filtering for `RoomAndToDeviceTransport` contains their logs too
|
|
||||||
this.toDeviceTransport.setParentLogger(this.logger);
|
|
||||||
this.roomKeyTransport.setParentLogger(this.logger);
|
|
||||||
|
|
||||||
this.roomKeyTransport.on(KeyTransportEvents.ReceivedKeys, (...props) => {
|
|
||||||
// Turn on the room transport if we receive a roomKey from another participant
|
|
||||||
// and disable the toDevice transport.
|
|
||||||
if (!this._enabled.room) {
|
|
||||||
this.logger.debug("Received room key, enabling room key transport, disabling toDevice transport");
|
|
||||||
this.setEnabled({ toDevice: false, room: true });
|
|
||||||
}
|
|
||||||
this.emit(KeyTransportEvents.ReceivedKeys, ...props);
|
|
||||||
});
|
|
||||||
this.toDeviceTransport.on(KeyTransportEvents.ReceivedKeys, (...props) => {
|
|
||||||
if (this._enabled.toDevice) {
|
|
||||||
this.emit(KeyTransportEvents.ReceivedKeys, ...props);
|
|
||||||
} else {
|
|
||||||
this.logger.debug("To Device transport is disabled, ignoring received keys");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Set which transport type should be used to send and receive keys.*/
|
|
||||||
public setEnabled(enabled: { toDevice: boolean; room: boolean }): void {
|
|
||||||
if (this.enabled.toDevice !== enabled.toDevice || this.enabled.room !== enabled.room) {
|
|
||||||
this._enabled = enabled;
|
|
||||||
this.emit(RoomAndToDeviceEvents.EnabledTransportsChanged, enabled);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** The currently enabled transports that are used to send and receive keys.*/
|
|
||||||
public get enabled(): EnabledTransports {
|
|
||||||
return this._enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public start(): void {
|
|
||||||
// always start the underlying transport since we need to enable room transport
|
|
||||||
// when someone else sends us a room key. (we need to listen to roomKeyTransport)
|
|
||||||
this.roomKeyTransport.start();
|
|
||||||
this.toDeviceTransport.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public stop(): void {
|
|
||||||
// always stop since it is always running
|
|
||||||
this.roomKeyTransport.stop();
|
|
||||||
this.toDeviceTransport.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async sendKey(keyBase64Encoded: string, index: number, members: ParticipantDeviceInfo[]): Promise<void> {
|
|
||||||
this.logger.debug(
|
|
||||||
`Sending key with index ${index} to call members (count=${members.length}) via:` +
|
|
||||||
(this._enabled.room ? "room transport" : "") +
|
|
||||||
(this._enabled.room && this._enabled.toDevice ? "and" : "") +
|
|
||||||
(this._enabled.toDevice ? "to device transport" : ""),
|
|
||||||
);
|
|
||||||
if (this._enabled.room) await this.roomKeyTransport.sendKey(keyBase64Encoded, index, members);
|
|
||||||
if (this._enabled.toDevice) {
|
|
||||||
try {
|
|
||||||
await this.toDeviceTransport.sendKey(keyBase64Encoded, index, members);
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof NotSupportedError && !this._enabled.room) {
|
|
||||||
this.logger.warn(
|
|
||||||
"To device is not supported enabling room key transport, disabling toDevice transport",
|
|
||||||
);
|
|
||||||
this.setEnabled({ toDevice: false, room: true });
|
|
||||||
await this.sendKey(keyBase64Encoded, index, members);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user