1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-07-30 04:23:07 +03:00

crypto: Add new ClientEvent.ReceivedToDeviceMessage with proper OlmEncryptionInfo support (#4891)

* crypto: Add new ClientEvent.ReceivedToDeviceMessage

refactor rename ProcessedToDeviceEvent to ReceivedToDeviceEvent

* fix: Restore legacy isEncrypted() for to-device messages

* Update test for new preprocessToDeviceMessages API

* quick fix on doc

* quick update docs and renaming

* review: Better doc and names for OlmEncryptionInfo

* review: Remove IToDeviceMessage alias and only keep IToDeviceEvent

* review: improve comments of processToDeviceMessages

* review: pass up encrypted event when no crypto callbacks

* review: use single payload for ReceivedToDeviceMessage

* fix linter

* review: minor comment update
This commit is contained in:
Valere Fedronic
2025-07-02 10:02:23 +02:00
committed by GitHub
parent de659d6431
commit 161c12f5d5
9 changed files with 304 additions and 61 deletions

View File

@ -17,13 +17,22 @@ limitations under the License.
import fetchMock from "fetch-mock-jest";
import "fake-indexeddb/auto";
import { IDBFactory } from "fake-indexeddb";
import Olm from "@matrix-org/olm";
import { getSyncResponse, syncPromise } from "../../test-utils/test-utils";
import { createClient, type MatrixClient } from "../../../src";
import {
ClientEvent,
createClient,
type IToDeviceEvent,
type MatrixClient,
type MatrixEvent,
type ReceivedToDeviceMessage,
} from "../../../src";
import * as testData from "../../test-utils/test-data";
import { E2EKeyResponder } from "../../test-utils/E2EKeyResponder";
import { SyncResponder } from "../../test-utils/SyncResponder";
import { E2EKeyReceiver } from "../../test-utils/E2EKeyReceiver";
import { encryptOlmEvent, establishOlmSession, getTestOlmAccountKeys } from "./olm-utils.ts";
afterEach(() => {
// reset fake-indexeddb after each test, to make sure we don't leak connections
@ -43,6 +52,8 @@ describe("to-device-messages", () => {
/** an object which intercepts `/keys/query` requests on the test homeserver */
let e2eKeyResponder: E2EKeyResponder;
let e2eKeyReceiver: E2EKeyReceiver;
let syncResponder: SyncResponder;
beforeEach(
async () => {
@ -59,8 +70,8 @@ describe("to-device-messages", () => {
});
e2eKeyResponder = new E2EKeyResponder(homeserverUrl);
new E2EKeyReceiver(homeserverUrl);
const syncResponder = new SyncResponder(homeserverUrl);
e2eKeyReceiver = new E2EKeyReceiver(homeserverUrl);
syncResponder = new SyncResponder(homeserverUrl);
// add bob as known user
syncResponder.sendOrQueueSyncResponse(getSyncResponse([testData.BOB_TEST_USER_ID]));
@ -149,4 +160,111 @@ describe("to-device-messages", () => {
// for future: check that bob's device can decrypt the ciphertext?
});
});
describe("receive to-device-messages", () => {
it("Should receive decrypted to-device message via ClientEvent", async () => {
// create a test olm device which we will use to communicate with alice. We use libolm to implement this.
await Olm.init();
const testOlmAccount = new Olm.Account();
testOlmAccount.create();
const testDeviceKeys = getTestOlmAccountKeys(testOlmAccount, "@bob:xyz", "DEVICE_ID");
e2eKeyResponder.addDeviceKeys(testDeviceKeys);
await aliceClient.startClient();
await syncPromise(aliceClient);
syncResponder.sendOrQueueSyncResponse(getSyncResponse(["@bob:xyz"]));
await syncPromise(aliceClient);
const p2pSession = await establishOlmSession(aliceClient, e2eKeyReceiver, syncResponder, testOlmAccount);
const toDeviceEvent = encryptOlmEvent({
sender: "@bob:xyz",
senderKey: testDeviceKeys.keys[`curve25519:DEVICE_ID`],
senderSigningKey: testDeviceKeys.keys[`ed25519:DEVICE_ID`],
p2pSession: p2pSession,
recipient: aliceClient.getUserId()!,
recipientCurve25519Key: e2eKeyReceiver.getDeviceKey(),
recipientEd25519Key: e2eKeyReceiver.getSigningKey(),
plaincontent: {
body: "foo",
},
plaintype: "m.test.type",
});
const processedToDeviceResolver: PromiseWithResolvers<ReceivedToDeviceMessage> = Promise.withResolvers();
aliceClient.on(ClientEvent.ReceivedToDeviceMessage, (payload) => {
processedToDeviceResolver.resolve(payload);
});
const oldToDeviceResolver: PromiseWithResolvers<MatrixEvent> = Promise.withResolvers();
aliceClient.on(ClientEvent.ToDeviceEvent, (event) => {
oldToDeviceResolver.resolve(event);
});
expect(toDeviceEvent.type).toBe("m.room.encrypted");
syncResponder.sendOrQueueSyncResponse({ to_device: { events: [toDeviceEvent] } });
await syncPromise(aliceClient);
const { message, encryptionInfo } = await processedToDeviceResolver.promise;
expect(message.type).toBe("m.test.type");
expect(message.content["body"]).toBe("foo");
expect(encryptionInfo).not.toBeNull();
expect(encryptionInfo!.senderVerified).toBe(false);
expect(encryptionInfo!.sender).toBe("@bob:xyz");
expect(encryptionInfo!.senderDevice).toBe("DEVICE_ID");
const oldFormat = await oldToDeviceResolver.promise;
expect(oldFormat.isEncrypted()).toBe(true);
expect(oldFormat.getType()).toBe("m.test.type");
expect(oldFormat.getContent()["body"]).toBe("foo");
});
it("Should receive clear to-device message via ClientEvent", async () => {
await aliceClient.startClient();
await syncPromise(aliceClient);
const toDeviceEvent: IToDeviceEvent = {
sender: "@bob:xyz",
type: "m.test.type",
content: {
body: "foo",
},
};
const processedToDeviceResolver: PromiseWithResolvers<ReceivedToDeviceMessage> = Promise.withResolvers();
aliceClient.on(ClientEvent.ReceivedToDeviceMessage, (payload) => {
processedToDeviceResolver.resolve(payload);
});
const oldToDeviceResolver: PromiseWithResolvers<MatrixEvent> = Promise.withResolvers();
aliceClient.on(ClientEvent.ToDeviceEvent, (event) => {
oldToDeviceResolver.resolve(event);
});
syncResponder.sendOrQueueSyncResponse({ to_device: { events: [toDeviceEvent] } });
await syncPromise(aliceClient);
const { message, encryptionInfo } = await processedToDeviceResolver.promise;
expect(message.type).toBe("m.test.type");
expect(message.content["body"]).toBe("foo");
// When the message is not encrypted, we don't have the encryptionInfo.
expect(encryptionInfo).toBeNull();
const oldFormat = await oldToDeviceResolver.promise;
expect(oldFormat.isEncrypted()).toBe(false);
expect(oldFormat.getType()).toBe("m.test.type");
expect(oldFormat.getContent()["body"]).toBe("foo");
});
});
});

View File

@ -551,11 +551,11 @@ describe("RustCrypto", () => {
const inputs: IToDeviceEvent[] = [
{ content: { key: "value" }, type: "org.matrix.test", sender: "@alice:example.com" },
];
const res = await rustCrypto.preprocessToDeviceMessages(inputs);
const res = (await rustCrypto.preprocessToDeviceMessages(inputs)).map((p) => p.message);
expect(res).toEqual(inputs);
});
it("should pass through bad encrypted messages", async () => {
it("should fail to process bad encrypted messages", async () => {
const olmMachine: OlmMachine = rustCrypto["olmMachine"];
const keys = olmMachine.identityKeys;
const inputs: IToDeviceEvent[] = [
@ -576,7 +576,7 @@ describe("RustCrypto", () => {
];
const res = await rustCrypto.preprocessToDeviceMessages(inputs);
expect(res).toEqual(inputs);
expect(res.length).toEqual(0);
});
it("emits VerificationRequestReceived on incoming m.key.verification.request", async () => {

View File

@ -87,7 +87,12 @@ import { type IIdentityServerProvider } from "./@types/IIdentityServerProvider.t
import { type MatrixScheduler } from "./scheduler.ts";
import { type BeaconEvent, type BeaconEventHandlerMap } from "./models/beacon.ts";
import { type AuthDict } from "./interactive-auth.ts";
import { type IMinimalEvent, type IRoomEvent, type IStateEvent } from "./sync-accumulator.ts";
import {
type IMinimalEvent,
type IRoomEvent,
type IStateEvent,
type ReceivedToDeviceMessage,
} from "./sync-accumulator.ts";
import type { EventTimelineSet } from "./models/event-timeline-set.ts";
import * as ContentHelpers from "./content-helpers.ts";
import {
@ -885,7 +890,9 @@ const EVENT_ID_PREFIX = "$";
export enum ClientEvent {
Sync = "sync",
Event = "event",
/** @deprecated Use {@link ReceivedToDeviceMessage}. */
ToDeviceEvent = "toDeviceEvent",
ReceivedToDeviceMessage = "receivedToDeviceMessage",
AccountData = "accountData",
Room = "Room",
DeleteRoom = "deleteRoom",
@ -1088,6 +1095,20 @@ export type ClientEventHandlerMap = {
* ```
*/
[ClientEvent.ToDeviceEvent]: (event: MatrixEvent) => void;
/**
* Fires whenever the SDK receives a new to-device message.
* @param payload - The message and encryptionInfo for this message (See {@link ReceivedToDeviceMessage}) which caused this event to fire.
* @example
* ```
* matrixClient.on("receivedToDeviceMessage", function(payload){
* const { message, encryptionInfo } = payload;
* var claimed_sender = encryptionInfo ? encryptionInfo.sender : message.sender;
* var isVerified = encryptionInfo ? encryptionInfo.verified : false;
* var type = message.type;
* });
* ```
*/
[ClientEvent.ReceivedToDeviceMessage]: (payload: ReceivedToDeviceMessage) => void;
/**
* Fires if a to-device event is received that cannot be decrypted.
* Encrypted to-device events will (generally) use plain Olm encryption,

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import type { IDeviceLists, IToDeviceEvent } from "../sync-accumulator.ts";
import type { IDeviceLists, IToDeviceEvent, ReceivedToDeviceMessage } from "../sync-accumulator.ts";
import { type IClearEvent, type MatrixEvent } from "../models/event.ts";
import { type Room } from "../models/room.ts";
import { type CryptoApi, type DecryptionFailureCode, type ImportRoomKeysOpts } from "../crypto-api/index.ts";
@ -96,9 +96,11 @@ export interface SyncCryptoCallbacks {
* messages, rather than the results of any decryption attempts.
*
* @param events - the received to-device messages
* @returns A list of preprocessed to-device messages.
* @returns A list of preprocessed to-device messages. This will not map 1:1 to the input list, as some messages may be invalid or
* failed to decrypt, and so will be omitted from the output list.
*
*/
preprocessToDeviceMessages(events: IToDeviceEvent[]): Promise<IToDeviceEvent[]>;
preprocessToDeviceMessages(events: IToDeviceEvent[]): Promise<ReceivedToDeviceMessage[]>;
/**
* Called by the /sync loop when one time key counts and unused fallback key details are received.

View File

@ -1365,6 +1365,27 @@ export interface OwnDeviceKeys {
curve25519: string;
}
/**
* Information about the encryption of a successfully decrypted to-device message.
*/
export interface OlmEncryptionInfo {
/** The user ID of the event sender, note this is untrusted data unless `isVerified` is true **/
sender: string;
/**
* The device ID of the device that sent us the event.
* Note this is untrusted data unless {@link senderVerified} is true.
* If the device ID is not known, this will be `null`.
**/
senderDevice?: string;
/** The sender device's public Curve25519 key, base64 encoded **/
senderCurve25519KeyBase64: string;
/**
* If true, this message is guaranteed to be authentic as it is coming from a device belonging to a user that we have verified.
* This is the state at the time of decryption (the user could be verified later).
*/
senderVerified: boolean;
}
export * from "./verification.ts";
export type * from "./keybackup.ts";
export * from "./recovery-key.ts";

View File

@ -19,7 +19,7 @@ import * as RustSdkCryptoJs from "@matrix-org/matrix-sdk-crypto-wasm";
import type { IEventDecryptionResult, IMegolmSessionData } from "../@types/crypto.ts";
import { KnownMembership } from "../@types/membership.ts";
import type { IDeviceLists, IToDeviceEvent } from "../sync-accumulator.ts";
import { type IDeviceLists, type IToDeviceEvent, type ReceivedToDeviceMessage } from "../sync-accumulator.ts";
import type { ToDevicePayload, ToDeviceBatch } from "../models/ToDeviceMessage.ts";
import { type MatrixEvent, MatrixEventEvent } from "../models/event.ts";
import { type Room } from "../models/room.ts";
@ -1481,7 +1481,7 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, CryptoEventH
* @param oneTimeKeysCounts - the received one time key counts
* @param unusedFallbackKeys - the received unused fallback keys
* @param devices - the received device list updates
* @returns A list of preprocessed to-device messages.
* @returns A list of processed to-device messages.
*/
private async receiveSyncChanges({
events,
@ -1493,15 +1493,13 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, CryptoEventH
oneTimeKeysCounts?: Map<string, number>;
unusedFallbackKeys?: Set<string>;
devices?: RustSdkCryptoJs.DeviceLists;
}): Promise<IToDeviceEvent[]> {
const result = await this.olmMachine.receiveSyncChanges(
}): Promise<RustSdkCryptoJs.ProcessedToDeviceEvent[]> {
return await this.olmMachine.receiveSyncChanges(
events ? JSON.stringify(events) : "[]",
devices,
oneTimeKeysCounts,
unusedFallbackKeys,
);
return result.map((processed) => JSON.parse(processed.rawEvent));
}
/** called by the sync loop to preprocess incoming to-device messages
@ -1509,22 +1507,56 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, CryptoEventH
* @param events - the received to-device messages
* @returns A list of preprocessed to-device messages.
*/
public async preprocessToDeviceMessages(events: IToDeviceEvent[]): Promise<IToDeviceEvent[]> {
public async preprocessToDeviceMessages(events: IToDeviceEvent[]): Promise<ReceivedToDeviceMessage[]> {
// send the received to-device messages into receiveSyncChanges. We have no info on device-list changes,
// one-time-keys, or fallback keys, so just pass empty data.
const processed = await this.receiveSyncChanges({ events });
// look for interesting to-device messages
const received: ReceivedToDeviceMessage[] = [];
for (const message of processed) {
if (message.type === EventType.KeyVerificationRequest) {
const sender = message.sender;
const transactionId = message.content.transaction_id;
const parsedMessage: IToDeviceEvent = JSON.parse(message.rawEvent);
// look for interesting to-device messages
if (parsedMessage.type === EventType.KeyVerificationRequest) {
const sender = parsedMessage.sender;
const transactionId = parsedMessage.content.transaction_id;
if (transactionId && sender) {
this.onIncomingKeyVerificationRequest(sender, transactionId);
}
}
switch (message.type) {
case RustSdkCryptoJs.ProcessedToDeviceEventType.Decrypted: {
const encryptionInfo = (message as RustSdkCryptoJs.DecryptedToDeviceEvent).encryptionInfo;
received.push({
message: parsedMessage,
encryptionInfo: {
sender: encryptionInfo.sender.toString(),
senderDevice: encryptionInfo.senderDevice?.toString(),
senderCurve25519KeyBase64: encryptionInfo.senderCurve25519Key,
senderVerified: encryptionInfo.isSenderVerified(),
},
});
break;
}
case RustSdkCryptoJs.ProcessedToDeviceEventType.PlainText: {
received.push({
message: parsedMessage,
encryptionInfo: null,
});
break;
}
case RustSdkCryptoJs.ProcessedToDeviceEventType.UnableToDecrypt:
// ignore messages we cannot decrypt
break;
case RustSdkCryptoJs.ProcessedToDeviceEventType.Invalid:
// ignore invalid messages
break;
}
}
return processed;
return received;
}
/** called by the sync loop to process one time key counts and unused fallback keys

View File

@ -37,6 +37,7 @@ import {
type IStateEvent,
type IStrippedState,
type ISyncResponse,
type ReceivedToDeviceMessage,
} from "./sync-accumulator.ts";
import { MatrixError } from "./http-api/index.ts";
import {
@ -150,11 +151,20 @@ class ExtensionToDevice implements Extension<ExtensionToDeviceRequest, Extension
}
public async onResponse(data: ExtensionToDeviceResponse): Promise<void> {
let events = data["events"] || [];
if (events.length > 0 && this.cryptoCallbacks) {
events = await this.cryptoCallbacks.preprocessToDeviceMessages(events);
const events = data["events"] || [];
let receivedToDeviceMessages: ReceivedToDeviceMessage[];
if (this.cryptoCallbacks) {
receivedToDeviceMessages = await this.cryptoCallbacks.preprocessToDeviceMessages(events);
} else {
receivedToDeviceMessages = events.map((rawEvent) =>
// Crypto is not enabled, so we just return the events.
({
message: rawEvent,
encryptionInfo: null,
}),
);
}
processToDeviceMessages(events, this.client);
processToDeviceMessages(receivedToDeviceMessages, this.client);
this.nextBatch = data.next_batch;
}

View File

@ -25,6 +25,7 @@ import { type IRoomSummary } from "./models/room-summary.ts";
import { type EventType } from "./@types/event.ts";
import { UNREAD_THREAD_NOTIFICATIONS } from "./@types/sync.ts";
import { ReceiptAccumulator } from "./receipt-accumulator.ts";
import { type OlmEncryptionInfo } from "./crypto-api/index.ts";
interface IOpts {
/**
@ -134,12 +135,31 @@ interface IAccountData {
events: IMinimalEvent[];
}
/** A to-device message as received from the sync. */
export interface IToDeviceEvent {
content: IContent;
sender: string;
type: string;
}
/**
* A (possibly decrypted) to-device message after it has been successfully processed by the sdk.
*
* If the message was encrypted, the `encryptionInfo` field will contain the encryption information.
* If the message was sent in clear, this field will be null.
*
* The `message` field contains the message `type`, `content`, and `sender` as if the message was sent in clear.
*/
export interface ReceivedToDeviceMessage {
/** The message type, content, and sender as if the message was sent in clear. */
message: IToDeviceEvent;
/**
* Information about the encryption of the message.
* Will be null if the message was sent in clear
*/
encryptionInfo: OlmEncryptionInfo | null;
}
interface IToDevice {
events: IToDeviceEvent[];
}

View File

@ -44,8 +44,8 @@ import {
type IInvitedRoom,
type IInviteState,
type IJoinedRoom,
type ILeftRoom,
type IKnockedRoom,
type ILeftRoom,
type IMinimalEvent,
type IRoomEvent,
type IStateEvent,
@ -53,13 +53,14 @@ import {
type ISyncResponse,
type ITimeline,
type IToDeviceEvent,
type ReceivedToDeviceMessage,
} from "./sync-accumulator.ts";
import { MatrixEvent, type IEvent } from "./models/event.ts";
import { MatrixEvent } from "./models/event.ts";
import { type MatrixError, Method } from "./http-api/index.ts";
import { type ISavedSync } from "./store/index.ts";
import { EventType } from "./@types/event.ts";
import { type IPushRules } from "./@types/PushRules.ts";
import { RoomStateEvent, type IMarkerFoundOptions } from "./models/room-state.ts";
import { type IMarkerFoundOptions, RoomStateEvent } from "./models/room-state.ts";
import { RoomMemberEvent } from "./models/room-member.ts";
import { BeaconEvent } from "./models/beacon.ts";
import { type IEventsResponse } from "./@types/requests.ts";
@ -1144,13 +1145,23 @@ export class SyncApi {
// handle to-device events
if (data.to_device && Array.isArray(data.to_device.events) && data.to_device.events.length > 0) {
let toDeviceMessages: IToDeviceEvent[] = data.to_device.events.filter(noUnsafeEventProps);
const toDeviceMessages: IToDeviceEvent[] = data.to_device.events.filter(noUnsafeEventProps);
let receivedToDeviceMessages: ReceivedToDeviceMessage[];
if (this.syncOpts.cryptoCallbacks) {
toDeviceMessages = await this.syncOpts.cryptoCallbacks.preprocessToDeviceMessages(toDeviceMessages);
receivedToDeviceMessages =
await this.syncOpts.cryptoCallbacks.preprocessToDeviceMessages(toDeviceMessages);
} else {
receivedToDeviceMessages = toDeviceMessages.map((rawEvent) =>
// Crypto is not enabled, so we just return the events.
({
message: rawEvent,
encryptionInfo: null,
}),
);
}
processToDeviceMessages(toDeviceMessages, client);
processToDeviceMessages(receivedToDeviceMessages, client);
} else {
// no more to-device events: we can stop polling with a short timeout.
this.catchingUp = false;
@ -1909,21 +1920,21 @@ export function _createAndReEmitRoom(client: MatrixClient, roomId: string, opts:
/**
* Process a list of (decrypted, where possible) received to-device events.
*
* Converts the events into `MatrixEvent`s, and emits appropriate {@link ClientEvent.ToDeviceEvent} events.
* Emits the appropriate {@link ClientEvent.ReceivedToDeviceMessage} event.
* Also converts the events into `MatrixEvent`s, and emits the now deprecated {@link ClientEvent.ToDeviceEvent} events for compatibility.
* */
export function processToDeviceMessages(toDeviceMessages: IToDeviceEvent[], client: MatrixClient): void {
export function processToDeviceMessages(toDeviceMessages: ReceivedToDeviceMessage[], client: MatrixClient): void {
const cancelledKeyVerificationTxns: string[] = [];
toDeviceMessages
.map(mapToDeviceEvent)
.map((toDeviceEvent) => {
.map((processedMessage) => {
// map is a cheap inline forEach
// We want to flag m.key.verification.start events as cancelled
// if there's an accompanying m.key.verification.cancel event, so
// we pull out the transaction IDs from the cancellation events
// so we can flag the verification events as cancelled in the loop
// below.
if (toDeviceEvent.getType() === "m.key.verification.cancel") {
const txnId: string = toDeviceEvent.getContent()["transaction_id"];
if (processedMessage.message.type === "m.key.verification.cancel") {
const txnId: string = processedMessage.message.content["transaction_id"];
if (txnId) {
cancelledKeyVerificationTxns.push(txnId);
}
@ -1931,32 +1942,40 @@ export function processToDeviceMessages(toDeviceMessages: IToDeviceEvent[], clie
// as mentioned above, .map is a cheap inline forEach, so return
// the unmodified event.
return toDeviceEvent;
return processedMessage;
})
.forEach(function (toDeviceEvent) {
const content = toDeviceEvent.getContent();
if (toDeviceEvent.getType() == "m.room.message" && content.msgtype == "m.bad.encrypted") {
// the mapper already logged a warning.
logger.log("Ignoring undecryptable to-device event from " + toDeviceEvent.getSender());
return;
}
if (
toDeviceEvent.getType() === "m.key.verification.start" ||
toDeviceEvent.getType() === "m.key.verification.request"
) {
const txnId = content["transaction_id"];
if (cancelledKeyVerificationTxns.includes(txnId)) {
toDeviceEvent.flagCancelled();
.forEach(function (processedEvent) {
// For backwards compatibility, we also emit the event as a `MatrixEvent` using `ClientEvent.ToDeviceEvent`.
{
const toDeviceEvent = processedEvent.message;
const content = toDeviceEvent.content;
// The message is cloned before being passed to the MatrixEvent constructor, because
// the `makeEncrypted` method will mutate the type and content properties of the original message and will interfere
// with the emitted event for `ReceivedToDeviceMessage`.
const deprecatedCompatibilityEvent = new MatrixEvent(Object.assign({}, toDeviceEvent));
if (
toDeviceEvent.type === "m.key.verification.start" ||
toDeviceEvent.type === "m.key.verification.request"
) {
const txnId = content["transaction_id"];
if (cancelledKeyVerificationTxns.includes(txnId)) {
deprecatedCompatibilityEvent.flagCancelled();
}
}
if (processedEvent.encryptionInfo) {
// Restore partially the legacy behavior to detect encrypted messages.
// Now `event.isEncrypted()` will return true.
deprecatedCompatibilityEvent.makeEncrypted(
EventType.RoomMessageEncrypted,
{ ciphertext: "" },
processedEvent.encryptionInfo.senderCurve25519KeyBase64,
"",
);
}
client.emit(ClientEvent.ToDeviceEvent, deprecatedCompatibilityEvent);
}
client.emit(ClientEvent.ToDeviceEvent, toDeviceEvent);
client.emit(ClientEvent.ReceivedToDeviceMessage, processedEvent);
});
}
function mapToDeviceEvent(plainOldJsObject: Partial<IEvent>): MatrixEvent {
// to-device events should not have a `room_id` property, but let's be sure
delete plainOldJsObject.room_id;
return new MatrixEvent(plainOldJsObject);
}