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

Improve types for sendEvent (#4108)

This commit is contained in:
Michael Telatynski
2024-03-25 12:48:49 +00:00
committed by GitHub
parent 85a55c79cd
commit 97844f0e47
17 changed files with 278 additions and 71 deletions

View File

@@ -50,6 +50,7 @@ import {
ClientEvent,
createClient,
CryptoEvent,
HistoryVisibility,
IClaimOTKsResult,
IContent,
IDownloadKeyResult,
@@ -59,11 +60,11 @@ import {
MatrixClient,
MatrixEvent,
MatrixEventEvent,
MsgType,
PendingEventOrdering,
Room,
RoomMember,
RoomStateEvent,
HistoryVisibility,
} from "../../../src/matrix";
import { DeviceInfo } from "../../../src/crypto/deviceinfo";
import { E2EKeyReceiver } from "../../test-utils/E2EKeyReceiver";
@@ -1925,7 +1926,7 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
expectAliceKeyQuery({ device_keys: { "@other:user": {} }, failures: {} });
aliceClient.on(RoomStateEvent.NewMember, (_e, _s, member: RoomMember) => {
if (member.userId == "@other:user") {
aliceClient.sendMessage(testRoomId, { msgtype: "m.text", body: "Hello, World" });
aliceClient.sendMessage(testRoomId, { msgtype: MsgType.Text, body: "Hello, World" });
}
});

View File

@@ -34,7 +34,7 @@ import { logger } from "../../../src/logger";
import * as testUtils from "../../test-utils/test-utils";
import { TestClient } from "../../TestClient";
import { CRYPTO_ENABLED, IClaimKeysRequest, IQueryKeysRequest, IUploadKeysRequest } from "../../../src/client";
import { ClientEvent, IContent, ISendEventResponse, MatrixClient, MatrixEvent } from "../../../src/matrix";
import { ClientEvent, IContent, ISendEventResponse, MatrixClient, MatrixEvent, MsgType } from "../../../src/matrix";
import { DeviceInfo } from "../../../src/crypto/deviceinfo";
import { KnownMembership } from "../../../src/@types/membership";
@@ -217,7 +217,7 @@ async function expectBobSendMessageRequest(): Promise<OlmPayload> {
}
function sendMessage(client: MatrixClient): Promise<ISendEventResponse> {
return client.sendMessage(roomId, { msgtype: "m.text", body: "Hello, World" });
return client.sendMessage(roomId, { msgtype: MsgType.Text, body: "Hello, World" });
}
async function expectSendMessageRequest(httpBackend: TestClient["httpBackend"]): Promise<IContent> {

View File

@@ -16,7 +16,7 @@ limitations under the License.
import HttpBackend from "matrix-mock-request";
import { EventStatus, RoomEvent, MatrixClient, MatrixScheduler } from "../../src/matrix";
import { EventStatus, MatrixClient, MatrixScheduler, MsgType, RoomEvent } from "../../src/matrix";
import { Room } from "../../src/models/room";
import { TestClient } from "../TestClient";
@@ -60,7 +60,7 @@ describe("MatrixClient retrying", function () {
// send a couple of events; the second will be queued
const p1 = client!
.sendMessage(roomId, {
msgtype: "m.text",
msgtype: MsgType.Text,
body: "m1",
})
.then(
@@ -77,7 +77,7 @@ describe("MatrixClient retrying", function () {
// never gets resolved.
// https://github.com/matrix-org/matrix-js-sdk/issues/496
client!.sendMessage(roomId, {
msgtype: "m.text",
msgtype: MsgType.Text,
body: "m2",
});

View File

@@ -152,7 +152,7 @@ describe("MatrixClient syncing", () => {
await client!.sendEvent(roomId, EventType.Reaction, {
"m.relates_to": {
rel_type: RelationType.Annotation,
event_id: threadReply.getId(),
event_id: threadReply.getId()!,
key: "",
},
});

View File

@@ -81,6 +81,12 @@ declare module "../../src/types" {
hello: string;
};
}
interface TimelineEvents {
"org.matrix.rageshake_request": {
request_id: number;
};
}
}
describe("RoomWidgetClient", () => {

View File

@@ -22,6 +22,7 @@ import { Filter } from "../../src/filter";
import { DEFAULT_TREE_POWER_LEVELS_TEMPLATE } from "../../src/models/MSC3089TreeSpace";
import {
EventType,
MsgType,
RelationType,
RoomCreateTypeField,
RoomType,
@@ -73,6 +74,7 @@ import { StubStore } from "../../src/store/stub";
import { SecretStorageKeyDescriptionAesV1, ServerSideSecretStorageImpl } from "../../src/secret-storage";
import { CryptoBackend } from "../../src/common-crypto/CryptoBackend";
import { KnownMembership } from "../../src/@types/membership";
import { RoomMessageEventContent } from "../../src/@types/events";
jest.useFakeTimers();
@@ -567,7 +569,7 @@ describe("MatrixClient", function () {
describe("sendEvent", () => {
const roomId = "!room:example.org";
const body = "This is the body";
const content = { body };
const content = { body, msgtype: MsgType.Text } satisfies RoomMessageEventContent;
it("overload without threadId works", async () => {
const eventId = "$eventId:example.org";
@@ -662,12 +664,13 @@ describe("MatrixClient", function () {
const content = {
body,
"msgtype": MsgType.Text,
"m.relates_to": {
"m.in_reply_to": {
event_id: "$other:event",
},
},
};
} satisfies RoomMessageEventContent;
const room = new Room(roomId, client, userId);
mocked(store.getRoom).mockReturnValue(room);

View File

@@ -26,6 +26,7 @@ import {
import { DEFAULT_ALPHABET } from "../../../src/utils";
import { MatrixError } from "../../../src/http-api";
import { KnownMembership } from "../../../src/@types/membership";
import { EncryptedFile } from "../../../src/@types/media";
describe("MSC3089TreeSpace", () => {
let client: MatrixClient;
@@ -947,7 +948,7 @@ describe("MSC3089TreeSpace", () => {
const fileInfo = {
mimetype: "text/plain",
// other fields as required by encryption, but ignored here
};
} as unknown as EncryptedFile;
const fileEventId = "$file";
const fileName = "My File.txt";
const fileContents = "This is a test file";
@@ -1007,7 +1008,7 @@ describe("MSC3089TreeSpace", () => {
const fileInfo = {
mimetype: "text/plain",
// other fields as required by encryption, but ignored here
};
} as unknown as EncryptedFile;
const fileEventId = "$file";
const fileName = "My File.txt";
const fileContents = "This is a test file";

View File

@@ -41,8 +41,23 @@ import {
IGroupCallRoomState,
} from "../webrtc/groupCall";
import { MSC3089EventContent } from "../models/MSC3089Branch";
import { M_BEACON_INFO, MBeaconInfoEventContent } from "./beacon";
import { M_BEACON, M_BEACON_INFO, MBeaconEventContent, MBeaconInfoEventContent } from "./beacon";
import { XOR } from "./common";
import { ReactionEventContent, RoomMessageEventContent, StickerEventContent } from "./events";
import {
MCallAnswer,
MCallBase,
MCallCandidates,
MCallHangupReject,
MCallInviteNegotiate,
MCallReplacesEvent,
MCallSelectAnswer,
SDPStreamMetadata,
SDPStreamMetadataKey,
} from "../webrtc/callEventTypes";
import { EncryptionKeysEventContent, ICallNotifyContent } from "../matrixrtc/types";
import { EncryptedFile } from "./media";
import { M_POLL_END, M_POLL_START, PollEndEventContent, PollStartEventContent } from "./polls";
export enum EventType {
// Room state events
@@ -283,21 +298,37 @@ export const LOCAL_NOTIFICATION_SETTINGS_PREFIX = new UnstableValue(
*/
export const UNSIGNED_THREAD_ID_FIELD = new UnstableValue("thread_id", "org.matrix.msc4023.thread_id");
export interface IEncryptedFile {
url: string;
mimetype?: string;
key: {
alg: string;
key_ops: string[]; // eslint-disable-line camelcase
kty: string;
k: string;
ext: boolean;
};
iv: string;
hashes: { [alg: string]: string };
v: string;
/**
* @deprecated in favour of {@link EncryptedFile}
*/
export type IEncryptedFile = EncryptedFile;
/**
* Mapped type from event type to content type for all specified non-state room events.
*/
export interface TimelineEvents {
[EventType.RoomMessage]: RoomMessageEventContent;
[EventType.Sticker]: StickerEventContent;
[EventType.Reaction]: ReactionEventContent;
[EventType.CallReplaces]: MCallReplacesEvent;
[EventType.CallAnswer]: MCallAnswer;
[EventType.CallSelectAnswer]: MCallSelectAnswer;
[EventType.CallNegotiate]: Omit<MCallInviteNegotiate, "offer">;
[EventType.CallInvite]: MCallInviteNegotiate;
[EventType.CallCandidates]: MCallCandidates;
[EventType.CallHangup]: MCallHangupReject;
[EventType.CallReject]: MCallHangupReject;
[EventType.CallSDPStreamMetadataChangedPrefix]: MCallBase & { [SDPStreamMetadataKey]: SDPStreamMetadata };
[EventType.CallEncryptionKeysPrefix]: EncryptionKeysEventContent;
[EventType.CallNotify]: ICallNotifyContent;
[M_BEACON.name]: MBeaconEventContent;
[M_POLL_START.name]: PollStartEventContent;
[M_POLL_END.name]: PollEndEventContent;
}
/**
* Mapped type from event type to content type for all specified room state events.
*/
export interface StateEvents {
[EventType.RoomCanonicalAlias]: RoomCanonicalAliasEventContent;
[EventType.RoomCreate]: RoomCreateEventContent;

119
src/@types/events.ts Normal file
View File

@@ -0,0 +1,119 @@
/*
Copyright 2024 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 { MsgType, RelationType } from "./event";
import { FileInfo, ImageInfo, MediaEventContent } from "./media";
import { XOR } from "./common";
interface BaseTimelineEvent {
"body": string;
"m.mentions"?: {
user_ids?: string[];
room?: boolean;
};
}
interface ReplyEvent {
"m.relates_to"?: {
"m.in_reply_to"?: {
event_id: string;
};
};
}
interface NoRelationEvent {
"m.new_content"?: never;
"m.relates_to"?: never;
}
/**
* Partial content format of timeline events with rel_type `m.replace`
*
* @see https://spec.matrix.org/v1.9/client-server-api/#event-replacements
*/
export interface ReplacementEvent<T> {
"m.new_content": T;
"m.relates_to": {
event_id: string;
rel_type: RelationType.Replace;
};
}
/**
* Partial content format of timeline events with rel_type other than `m.replace`
*
* @see https://spec.matrix.org/v1.9/client-server-api/#forming-relationships-between-events
*/
export interface RelationEvent {
"m.new_content"?: never;
"m.relates_to": {
event_id: string;
rel_type: Exclude<RelationType, RelationType.Replace>;
};
}
/**
* Content format of timeline events with type `m.room.message` and `msgtype` `m.text`, `m.emote`, or `m.notice`
*
* @see https://spec.matrix.org/v1.9/client-server-api/#mroommessage
*/
export interface RoomMessageTextEventContent extends BaseTimelineEvent {
msgtype: MsgType.Text | MsgType.Emote | MsgType.Notice;
format?: "org.matrix.custom.html";
formatted_body?: string;
}
/**
* Content format of timeline events with type `m.room.message` and `msgtype` `m.location`
*
* @see https://spec.matrix.org/v1.9/client-server-api/#mlocation
*/
export interface RoomMessageLocationEventContent extends BaseTimelineEvent {
body: string;
geo_uri: string;
info: Pick<FileInfo, "thumbnail_info" | "thumbnail_file" | "thumbnail_url">;
msgtype: MsgType.Location;
}
type MessageEventContent = RoomMessageTextEventContent | RoomMessageLocationEventContent | MediaEventContent;
export type RoomMessageEventContent = BaseTimelineEvent &
XOR<XOR<ReplacementEvent<MessageEventContent>, RelationEvent>, XOR<ReplyEvent, NoRelationEvent>> &
MessageEventContent;
/**
* Content format of timeline events with type `m.sticker`
*
* @see https://spec.matrix.org/v1.9/client-server-api/#msticker
*/
export interface StickerEventContent extends BaseTimelineEvent {
body: string;
info: ImageInfo;
url: string;
}
/**
* Content format of timeline events with type `m.reaction`
*
* @see https://spec.matrix.org/v1.9/client-server-api/#mreaction
*/
export interface ReactionEventContent {
"m.relates_to": {
event_id: string;
key: string;
rel_type: RelationType.Annotation;
};
}

View File

@@ -145,6 +145,7 @@ import {
RoomCreateTypeField,
RoomType,
StateEvents,
TimelineEvents,
UNSTABLE_MSC3088_ENABLED,
UNSTABLE_MSC3088_PURPOSE,
UNSTABLE_MSC3089_TREE_SUBTYPE,
@@ -223,6 +224,7 @@ import { RegisterRequest, RegisterResponse } from "./@types/registration";
import { MatrixRTCSessionManager } from "./matrixrtc/MatrixRTCSessionManager";
import { getRelationsThreadFilter } from "./thread-utils";
import { KnownMembership, Membership } from "./@types/membership";
import { RoomMessageEventContent, StickerEventContent } from "./@types/events";
import { ImageInfo } from "./@types/media";
export type Store = IStore;
@@ -4551,12 +4553,17 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
return this.sendStateEvent(roomId, M_BEACON_INFO.name, beaconInfoContent, this.getUserId()!);
}
public sendEvent(roomId: string, eventType: string, content: IContent, txnId?: string): Promise<ISendEventResponse>;
public sendEvent(
public sendEvent<K extends keyof TimelineEvents>(
roomId: string,
eventType: K,
content: TimelineEvents[K],
txnId?: string,
): Promise<ISendEventResponse>;
public sendEvent<K extends keyof TimelineEvents>(
roomId: string,
threadId: string | null,
eventType: string,
content: IContent,
eventType: K,
content: TimelineEvents[K],
txnId?: string,
): Promise<ISendEventResponse>;
public sendEvent(
@@ -4943,27 +4950,27 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
* @returns Promise which resolves: to an ISendEventResponse object
* @returns Rejects: with an error response.
*/
public sendMessage(roomId: string, content: IContent, txnId?: string): Promise<ISendEventResponse>;
public sendMessage(roomId: string, content: RoomMessageEventContent, txnId?: string): Promise<ISendEventResponse>;
public sendMessage(
roomId: string,
threadId: string | null,
content: IContent,
content: RoomMessageEventContent,
txnId?: string,
): Promise<ISendEventResponse>;
public sendMessage(
roomId: string,
threadId: string | null | IContent,
content?: IContent | string,
threadId: string | null | RoomMessageEventContent,
content?: RoomMessageEventContent | string,
txnId?: string,
): Promise<ISendEventResponse> {
if (typeof threadId !== "string" && threadId !== null) {
txnId = content as string;
content = threadId as IContent;
content = threadId as RoomMessageEventContent;
threadId = null;
}
const eventType: string = EventType.RoomMessage;
const sendContent: IContent = content as IContent;
const eventType = EventType.RoomMessage;
const sendContent = content as RoomMessageEventContent;
return this.sendEvent(roomId, threadId as string | null, eventType, sendContent, txnId);
}
@@ -5076,10 +5083,10 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
}
const content = {
msgtype: MsgType.Image,
url: url,
info: info,
url: url as string,
info: info as ImageInfo,
body: text,
};
} satisfies RoomMessageEventContent;
return this.sendMessage(roomId, threadId, content);
}
@@ -5114,10 +5121,10 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
threadId = null;
}
const content = {
url: url,
info: info,
url: url as string,
info: info as ImageInfo,
body: text,
};
} satisfies StickerEventContent;
return this.sendEvent(roomId, threadId, EventType.Sticker, content);
}

View File

@@ -30,7 +30,7 @@ import {
LegacyLocationEventContent,
} from "./@types/location";
import { MRoomTopicEventContent, MTopicContent, M_TOPIC } from "./@types/topic";
import { IContent } from "./models/event";
import { RoomMessageEventContent } from "./@types/events";
/**
* Generates the content for a HTML Message event
@@ -38,7 +38,7 @@ import { IContent } from "./models/event";
* @param htmlBody - the HTML representation of the message
* @returns
*/
export function makeHtmlMessage(body: string, htmlBody: string): IContent {
export function makeHtmlMessage(body: string, htmlBody: string): RoomMessageEventContent {
return {
msgtype: MsgType.Text,
format: "org.matrix.custom.html",
@@ -53,7 +53,7 @@ export function makeHtmlMessage(body: string, htmlBody: string): IContent {
* @param htmlBody - the HTML representation of the notice
* @returns
*/
export function makeHtmlNotice(body: string, htmlBody: string): IContent {
export function makeHtmlNotice(body: string, htmlBody: string): RoomMessageEventContent {
return {
msgtype: MsgType.Notice,
format: "org.matrix.custom.html",
@@ -68,7 +68,7 @@ export function makeHtmlNotice(body: string, htmlBody: string): IContent {
* @param htmlBody - the HTML representation of the emote
* @returns
*/
export function makeHtmlEmote(body: string, htmlBody: string): IContent {
export function makeHtmlEmote(body: string, htmlBody: string): RoomMessageEventContent {
return {
msgtype: MsgType.Emote,
format: "org.matrix.custom.html",
@@ -82,7 +82,7 @@ export function makeHtmlEmote(body: string, htmlBody: string): IContent {
* @param body - the plaintext body of the emote
* @returns
*/
export function makeTextMessage(body: string): IContent {
export function makeTextMessage(body: string): RoomMessageEventContent {
return {
msgtype: MsgType.Text,
body: body,
@@ -94,7 +94,7 @@ export function makeTextMessage(body: string): IContent {
* @param body - the plaintext body of the notice
* @returns
*/
export function makeNotice(body: string): IContent {
export function makeNotice(body: string): RoomMessageEventContent {
return {
msgtype: MsgType.Notice,
body: body,
@@ -106,7 +106,7 @@ export function makeNotice(body: string): IContent {
* @param body - the plaintext body of the emote
* @returns
*/
export function makeEmoteMessage(body: string): IContent {
export function makeEmoteMessage(body: string): RoomMessageEventContent {
return {
msgtype: MsgType.Emote,
body: body,

View File

@@ -18,7 +18,7 @@ limitations under the License.
import { VerificationRequest, REQUEST_TYPE, READY_TYPE, START_TYPE } from "./VerificationRequest";
import { logger } from "../../../logger";
import { IVerificationChannel } from "./Channel";
import { EventType } from "../../../@types/event";
import { EventType, TimelineEvents } from "../../../@types/event";
import { MatrixClient } from "../../../client";
import { MatrixEvent } from "../../../models/event";
import { IRequestsMap } from "../..";
@@ -299,7 +299,11 @@ export class InRoomChannel implements IVerificationChannel {
if (type === REQUEST_TYPE) {
sendType = MESSAGE_TYPE;
}
const response = await this.client.sendEvent(this.roomId, sendType, content);
const response = await this.client.sendEvent(
this.roomId,
sendType as keyof TimelineEvents,
content as TimelineEvents[keyof TimelineEvents],
);
if (type === REQUEST_TYPE) {
this.requestEventId = response.event_id;
}

View File

@@ -15,12 +15,20 @@ limitations under the License.
*/
import { MatrixClient } from "../client";
import { IEncryptedFile, RelationType, UNSTABLE_MSC3089_BRANCH } from "../@types/event";
import { RelationType, UNSTABLE_MSC3089_BRANCH } from "../@types/event";
import { IContent, MatrixEvent } from "./event";
import { MSC3089TreeSpace } from "./MSC3089TreeSpace";
import { EventTimeline } from "./event-timeline";
import { FileType } from "../http-api";
import type { ISendEventResponse } from "../@types/requests";
import { EncryptedFile } from "../@types/media";
export interface MSC3089EventContent {
active?: boolean;
name?: string;
locked?: boolean;
version?: number;
}
export interface MSC3089EventContent {
active?: boolean;
@@ -138,7 +146,7 @@ export class MSC3089Branch {
* Gets information about the file needed to download it.
* @returns Information about the file.
*/
public async getFileInfo(): Promise<{ info: IEncryptedFile; httpUrl: string }> {
public async getFileInfo(): Promise<{ info: EncryptedFile; httpUrl: string }> {
const event = await this.getFileEvent();
const file = event.getOriginalContent()["file"];
@@ -186,7 +194,7 @@ export class MSC3089Branch {
public async createNewVersion(
name: string,
encryptedContents: FileType,
info: Partial<IEncryptedFile>,
info: EncryptedFile,
additionalContent?: IContent,
): Promise<ISendEventResponse> {
const fileEventResponse = await this.directory.createFile(name, encryptedContents, info, {

View File

@@ -17,7 +17,7 @@ limitations under the License.
import promiseRetry from "p-retry";
import { MatrixClient } from "../client";
import { EventType, IEncryptedFile, MsgType, UNSTABLE_MSC3089_BRANCH, UNSTABLE_MSC3089_LEAF } from "../@types/event";
import { EventType, MsgType, UNSTABLE_MSC3089_BRANCH, UNSTABLE_MSC3089_LEAF } from "../@types/event";
import { Room } from "./room";
import { logger } from "../logger";
import { IContent, MatrixEvent } from "./event";
@@ -35,6 +35,7 @@ import { ISendEventResponse } from "../@types/requests";
import { FileType } from "../http-api";
import { KnownMembership } from "../@types/membership";
import { RoomPowerLevelsEventContent, SpaceChildEventContent } from "../@types/state_events";
import { EncryptedFile, FileContent } from "../@types/media";
/**
* The recommended defaults for a tree space's power levels. Note that this
@@ -79,6 +80,12 @@ export enum TreePermissions {
Owner = "owner", // "Admin" or PL100
}
declare module "../@types/media" {
interface FileContent {
[UNSTABLE_MSC3089_LEAF.name]?: {};
}
}
/**
* Represents a [MSC3089](https://github.com/matrix-org/matrix-doc/pull/3089)
* file tree Space. Note that this is UNSTABLE and subject to breaking changes
@@ -502,7 +509,7 @@ export class MSC3089TreeSpace {
public async createFile(
name: string,
encryptedContents: FileType,
info: Partial<IEncryptedFile>,
info: EncryptedFile,
additionalContent?: IContent,
): Promise<ISendEventResponse> {
const { content_uri: mxc } = await this.client.uploadContent(encryptedContents, {
@@ -510,7 +517,7 @@ export class MSC3089TreeSpace {
});
info.url = mxc;
const fileContent = {
const fileContent: FileContent = {
msgtype: MsgType.File,
body: name,
url: mxc,
@@ -529,7 +536,7 @@ export class MSC3089TreeSpace {
...additionalContent,
...fileContent,
[UNSTABLE_MSC3089_LEAF.name]: {},
});
} as FileContent);
await this.client.sendStateEvent(
this.roomId,

View File

@@ -24,6 +24,7 @@ limitations under the License.
export type * from "./@types/media";
export * from "./@types/membership";
export type * from "./@types/event";
export type * from "./@types/events";
export type * from "./@types/state_events";
/** The different methods for device and user verification */

View File

@@ -26,8 +26,8 @@ import { parse as parseSdp, write as writeSdp } from "sdp-transform";
import { logger } from "../logger";
import { checkObjectHasKeys, isNullOrUndefined, recursivelyAssign } from "../utils";
import { IContent, MatrixEvent } from "../models/event";
import { EventType, ToDeviceMessageId } from "../@types/event";
import { MatrixEvent } from "../models/event";
import { EventType, TimelineEvents, ToDeviceMessageId } from "../@types/event";
import { RoomMember } from "../models/room-member";
import { randomString } from "../randomstring";
import {
@@ -293,13 +293,24 @@ function getCodecParamMods(isPtt: boolean): CodecParamsMod[] {
return mods;
}
type CallEventType =
| EventType.CallReplaces
| EventType.CallAnswer
| EventType.CallSelectAnswer
| EventType.CallNegotiate
| EventType.CallInvite
| EventType.CallCandidates
| EventType.CallHangup
| EventType.CallReject
| EventType.CallSDPStreamMetadataChangedPrefix;
export interface VoipEvent {
type: "toDevice" | "sendEvent";
eventType: string;
userId?: string;
opponentDeviceId?: string;
roomId?: string;
content: Record<string, unknown>;
content: TimelineEvents[CallEventType];
}
/**
@@ -406,7 +417,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
// If candidates arrive before we've picked an opponent (which, in particular,
// will happen if the opponent sends candidates eagerly before the user answers
// the call) we buffer them up here so we can then add the ones from the party we pick
private remoteCandidateBuffer = new Map<string, RTCIceCandidate[]>();
private remoteCandidateBuffer = new Map<string, MCallCandidates["candidates"]>();
private remoteAssertedIdentity?: AssertedIdentity;
private remoteSDPStreamMetadata?: SDPStreamMetadata;
@@ -1156,7 +1167,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
this.terminate(CallParty.Local, reason, !suppressEvent);
// We don't want to send hangup here if we didn't even get to sending an invite
if ([CallState.Fledgling, CallState.WaitLocalMedia].includes(this.state)) return;
const content: IContent = {};
const content: Omit<MCallHangupReject, "version" | "call_id" | "party_id" | "conf_id"> = {};
// Don't send UserHangup reason to older clients
if ((this.opponentVersion && this.opponentVersion !== 0) || reason !== CallErrorCode.UserHangup) {
content["reason"] = reason;
@@ -1916,7 +1927,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
if (this.opponentPartyId !== null) {
try {
await this.sendVoipEvent(EventType.CallSelectAnswer, {
selected_party_id: this.opponentPartyId,
selected_party_id: this.opponentPartyId!,
});
} catch (err) {
// This isn't fatal, and will just mean that if another party has raced to answer
@@ -2012,6 +2023,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
logger.debug(`Call ${this.callId} onNegotiateReceived() create an answer`);
this.sendVoipEvent(EventType.CallNegotiate, {
lifetime: CALL_TIMEOUT_MS,
description: this.peerConn!.localDescription?.toJSON(),
[SDPStreamMetadataKey]: this.getLocalSDPStreamMetadata(true),
});
@@ -2444,13 +2456,17 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
/**
* @internal
*/
private async sendVoipEvent(eventType: string, content: object): Promise<void> {
const realContent = Object.assign({}, content, {
private async sendVoipEvent<K extends keyof Pick<TimelineEvents, CallEventType>>(
eventType: K,
content: Omit<TimelineEvents[K], "version" | "call_id" | "party_id" | "conf_id">,
): Promise<void> {
const realContent = {
...content,
version: VOIP_PROTO_VERSION,
call_id: this.callId,
party_id: this.ourPartyId,
conf_id: this.groupCallId,
});
} as TimelineEvents[K];
if (this.opponentDeviceId) {
const toDeviceSeq = this.toDeviceSeq++;
@@ -2729,7 +2745,9 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
const candidates = this.candidateSendQueue;
this.candidateSendQueue = [];
++this.candidateSendTries;
const content = { candidates: candidates.map((candidate) => candidate.toJSON()) };
const content: Pick<MCallCandidates, "candidates"> = {
candidates: candidates.map((candidate) => candidate.toJSON()),
};
if (this.candidatesEnded) {
// If there are no more candidates, signal this by adding an empty string candidate
content.candidates.push({
@@ -2923,7 +2941,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
this.remoteCandidateBuffer.clear();
}
private async addIceCandidates(candidates: RTCIceCandidate[]): Promise<void> {
private async addIceCandidates(candidates: RTCIceCandidate[] | MCallCandidates["candidates"]): Promise<void> {
for (const candidate of candidates) {
if (
(candidate.sdpMid === null || candidate.sdpMid === undefined) &&

View File

@@ -34,6 +34,7 @@ export interface CallReplacesTarget {
export interface MCallBase {
call_id: string;
conf_id?: string;
version: string | number;
party_id?: string;
sender_session_id?: string;
@@ -82,7 +83,7 @@ export interface MCAllAssertedIdentity extends MCallBase {
}
export interface MCallCandidates extends MCallBase {
candidates: RTCIceCandidate[];
candidates: Omit<RTCIceCandidateInit, "usernameFragment">[];
}
export interface MCallHangupReject extends MCallBase {