From ef2bd7ae0439493a3b0616b8ea1474a133996cb7 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 25 Mar 2024 12:48:48 +0000 Subject: [PATCH] Improve types for `sendEvent` (#12335) --- playwright/pages/client.ts | 11 ++++- src/@types/matrix-js-sdk.d.ts | 34 +++++++++++++- src/ContentMessages.ts | 2 +- src/components/structures/MatrixChat.tsx | 3 +- .../views/dialogs/EndPollDialog.tsx | 8 +++- .../views/dialogs/ForwardDialog.tsx | 9 ++-- .../views/dialogs/ReportEventDialog.tsx | 25 ++++++++-- .../views/dialogs/devtools/Event.tsx | 8 ++-- .../views/elements/PollCreateDialog.tsx | 5 +- .../views/emojipicker/ReactionPicker.tsx | 2 +- .../views/location/shareLocation.ts | 4 +- src/components/views/messages/MPollBody.tsx | 19 +++++--- .../views/messages/ReactionsRowButton.tsx | 9 ++-- .../views/rooms/EditMessageComposer.tsx | 31 ++++++++----- .../views/rooms/SendMessageComposer.tsx | 9 ++-- .../utils/createMessageContent.ts | 7 +-- .../utils/isContentModified.ts | 14 ++++-- .../rooms/wysiwyg_composer/utils/message.ts | 8 ++-- src/editor/commands.tsx | 7 +-- src/slash-commands/interface.ts | 4 +- src/stores/widgets/StopGapWidgetDriver.ts | 22 ++++++--- src/utils/createVoiceMessageContent.ts | 5 +- .../models/VoiceBroadcastRecording.ts | 4 +- .../views/elements/PollCreateDialog-test.tsx | 5 +- .../views/rooms/EditMessageComposer-test.tsx | 46 +++++++++++-------- .../wysiwyg_composer/utils/message-test.ts | 7 ++- 26 files changed, 209 insertions(+), 99 deletions(-) diff --git a/playwright/pages/client.ts b/playwright/pages/client.ts index c10c050d9f..94ee5d8813 100644 --- a/playwright/pages/client.ts +++ b/playwright/pages/client.ts @@ -32,7 +32,9 @@ import type { UploadOpts, Upload, StateEvents, + TimelineEvents, } from "matrix-js-sdk/src/matrix"; +import type { RoomMessageEventContent } from "matrix-js-sdk/src/types"; import { Credentials } from "../plugins/homeserver"; export class Client { @@ -98,7 +100,12 @@ export class Client { const client = await this.prepareClient(); return client.evaluate( async (client, { roomId, threadId, eventType, content }) => { - return client.sendEvent(roomId, threadId, eventType, content); + return client.sendEvent( + roomId, + threadId, + eventType as keyof TimelineEvents, + content as TimelineEvents[keyof TimelineEvents], + ); }, { roomId, threadId, eventType, content }, ); @@ -125,7 +132,7 @@ export class Client { const client = await this.prepareClient(); return client.evaluate( (client, { roomId, content, threadId }) => { - return client.sendMessage(roomId, threadId, content); + return client.sendMessage(roomId, threadId, content as RoomMessageEventContent); }, { roomId, diff --git a/src/@types/matrix-js-sdk.d.ts b/src/@types/matrix-js-sdk.d.ts index 4a32ee0f37..a58eea55bc 100644 --- a/src/@types/matrix-js-sdk.d.ts +++ b/src/@types/matrix-js-sdk.d.ts @@ -19,8 +19,9 @@ import type { BLURHASH_FIELD } from "../utils/image-media"; import type { JitsiCallMemberEventType, JitsiCallMemberContent } from "../call-types"; import type { ILayoutStateEvent, WIDGET_LAYOUT_EVENT_TYPE } from "../stores/widgets/types"; import type { VoiceBroadcastInfoEventContent, VoiceBroadcastInfoEventType } from "../voice-broadcast/types"; +import type { EncryptedFile } from "matrix-js-sdk/src/types"; -// Matrix JS SDK extensions +// Extend Matrix JS SDK types via Typescript declaration merging to support unspecced event fields and types declare module "matrix-js-sdk/src/types" { export interface FileInfo { /** @@ -50,4 +51,35 @@ declare module "matrix-js-sdk/src/types" { }; "m.room.bot.options": unknown; } + + export interface TimelineEvents { + "io.element.performance_metric": { + "io.element.performance_metrics": { + forEventId: string; + responseTs: number; + kind: "send_time"; + }; + }; + } + + export interface AudioContent { + // MSC1767 + Ideals of MSC2516 as MSC3245 + // https://github.com/matrix-org/matrix-doc/pull/3245 + "org.matrix.msc1767.text"?: string; + "org.matrix.msc1767.file"?: { + url?: string; + file?: EncryptedFile; + name: string; + mimetype: string; + size: number; + }; + "org.matrix.msc1767.audio"?: { + duration: number; + // https://github.com/matrix-org/matrix-doc/pull/3246 + waveform?: number[]; + }; + "org.matrix.msc3245.voice"?: {}; + + "io.element.voice_broadcast_chunk"?: { sequence: number }; + } } diff --git a/src/ContentMessages.ts b/src/ContentMessages.ts index b0f29ba044..7999095681 100644 --- a/src/ContentMessages.ts +++ b/src/ContentMessages.ts @@ -622,7 +622,7 @@ export default class ContentMessages { if (upload.cancelled) throw new UploadCanceledError(); const threadId = relation?.rel_type === THREAD_RELATION_TYPE.name ? relation.event_id : null; - const response = await matrixClient.sendMessage(roomId, threadId ?? null, content); + const response = await matrixClient.sendMessage(roomId, threadId ?? null, content as MediaEventContent); if (SettingsStore.getValue("Performance.addSendMessageTimingMetadata")) { sendRoundTripMetric(matrixClient, roomId, response.event_id); diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 5184553d1c..2cf41215a7 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -26,6 +26,7 @@ import { RoomType, SyncStateData, SyncState, + TimelineEvents, } from "matrix-js-sdk/src/matrix"; import { defer, IDeferred, QueryDict } from "matrix-js-sdk/src/utils"; import { logger } from "matrix-js-sdk/src/logger"; @@ -1930,7 +1931,7 @@ export default class MatrixChat extends React.PureComponent { const cli = MatrixClientPeg.get(); if (!cli) return; - cli.sendEvent(roomId, event.getType(), event.getContent()).then(() => { + cli.sendEvent(roomId, event.getType() as keyof TimelineEvents, event.getContent()).then(() => { dis.dispatch({ action: "message_sent" }); }); } diff --git a/src/components/views/dialogs/EndPollDialog.tsx b/src/components/views/dialogs/EndPollDialog.tsx index cc68e80191..6d1abc4a33 100644 --- a/src/components/views/dialogs/EndPollDialog.tsx +++ b/src/components/views/dialogs/EndPollDialog.tsx @@ -15,7 +15,7 @@ limitations under the License. */ import React from "react"; -import { MatrixEvent, MatrixClient } from "matrix-js-sdk/src/matrix"; +import { MatrixEvent, MatrixClient, TimelineEvents } from "matrix-js-sdk/src/matrix"; import { PollEndEvent } from "matrix-js-sdk/src/extensible_events_v1/PollEndEvent"; import { _t } from "../../../languageHandler"; @@ -51,7 +51,11 @@ export default class EndPollDialog extends React.Component { const endEvent = PollEndEvent.from(this.props.event.getId()!, message).serialize(); - await this.props.matrixClient.sendEvent(this.props.event.getRoomId()!, endEvent.type, endEvent.content); + await this.props.matrixClient.sendEvent( + this.props.event.getRoomId()!, + endEvent.type as keyof TimelineEvents, + endEvent.content as TimelineEvents[keyof TimelineEvents], + ); } catch (e) { console.error("Failed to submit poll response event:", e); Modal.createDialog(ErrorDialog, { diff --git a/src/components/views/dialogs/ForwardDialog.tsx b/src/components/views/dialogs/ForwardDialog.tsx index a72b2b6296..d59e23fe4c 100644 --- a/src/components/views/dialogs/ForwardDialog.tsx +++ b/src/components/views/dialogs/ForwardDialog.tsx @@ -28,6 +28,7 @@ import { LocationAssetType, M_TIMESTAMP, M_BEACON, + TimelineEvents, } from "matrix-js-sdk/src/matrix"; import { KnownMembership } from "matrix-js-sdk/src/types"; @@ -80,10 +81,10 @@ interface IProps { onFinished(): void; } -interface IEntryProps { +interface IEntryProps { room: Room; - type: EventType | string; - content: IContent; + type: K; + content: TimelineEvents[K]; matrixClient: MatrixClient; onFinished(success: boolean): void; } @@ -95,7 +96,7 @@ enum SendState { Failed, } -const Entry: React.FC = ({ room, type, content, matrixClient: cli, onFinished }) => { +const Entry: React.FC> = ({ room, type, content, matrixClient: cli, onFinished }) => { const [sendState, setSendState] = useState(SendState.CanSend); const [onFocus, isActive, ref] = useRovingTabIndex(); diff --git a/src/components/views/dialogs/ReportEventDialog.tsx b/src/components/views/dialogs/ReportEventDialog.tsx index 52859c55f6..0e0b231b3f 100644 --- a/src/components/views/dialogs/ReportEventDialog.tsx +++ b/src/components/views/dialogs/ReportEventDialog.tsx @@ -32,6 +32,12 @@ import Field from "../elements/Field"; import Spinner from "../elements/Spinner"; import LabelledCheckbox from "../elements/LabelledCheckbox"; +declare module "matrix-js-sdk/src/types" { + interface TimelineEvents { + [ABUSE_EVENT_TYPE]: AbuseEventContent; + } +} + interface IProps { mxEvent: MatrixEvent; onFinished(report?: boolean): void; @@ -56,7 +62,16 @@ const MODERATED_BY_STATE_EVENT_TYPE = [ */ ]; -const ABUSE_EVENT_TYPE = "org.matrix.msc3215.abuse.report"; +export const ABUSE_EVENT_TYPE = "org.matrix.msc3215.abuse.report"; + +interface AbuseEventContent { + event_id: string; + room_id: string; + moderated_by_id: string; + nature?: ExtendedNature; + reporter: string; + comment: string; +} // Standard abuse natures. enum Nature { @@ -250,13 +265,13 @@ export default class ReportEventDialog extends React.Component { } await client.sendEvent(dmRoomId, ABUSE_EVENT_TYPE, { - event_id: ev.getId(), - room_id: ev.getRoomId(), + event_id: ev.getId()!, + room_id: ev.getRoomId()!, moderated_by_id: this.moderation.moderationRoomId, nature, - reporter: client.getUserId(), + reporter: client.getUserId()!, comment: this.state.reason.trim(), - }); + } satisfies AbuseEventContent); } else { // Report to homeserver admin through the dedicated Matrix API. await client.reportEvent(ev.getRoomId()!, ev.getId()!, -100, this.state.reason.trim()); diff --git a/src/components/views/dialogs/devtools/Event.tsx b/src/components/views/dialogs/devtools/Event.tsx index 4b85dbe3f6..e1e0e469a3 100644 --- a/src/components/views/dialogs/devtools/Event.tsx +++ b/src/components/views/dialogs/devtools/Event.tsx @@ -16,7 +16,7 @@ limitations under the License. */ import React, { ChangeEvent, ReactNode, useContext, useMemo, useRef, useState } from "react"; -import { IContent, MatrixEvent } from "matrix-js-sdk/src/matrix"; +import { IContent, MatrixEvent, TimelineEvents } from "matrix-js-sdk/src/matrix"; import { _t, _td, TranslationKey } from "../../../../languageHandler"; import Field from "../../elements/Field"; @@ -32,7 +32,7 @@ export const stringify = (object: object): string => { interface IEventEditorProps extends Pick { fieldDefs: IFieldDef[]; // immutable defaultContent?: string; - onSend(fields: string[], content?: IContent): Promise; + onSend(fields: string[], content: IContent): Promise; } interface IFieldDef { @@ -180,8 +180,8 @@ export const TimelineEventEditor: React.FC = ({ mxEvent, onBack }) const fields = useMemo(() => [eventTypeField(mxEvent?.getType())], [mxEvent]); - const onSend = ([eventType]: string[], content?: IContent): Promise => { - return cli.sendEvent(context.room.roomId, eventType, content || {}); + const onSend = ([eventType]: string[], content: TimelineEvents[keyof TimelineEvents]): Promise => { + return cli.sendEvent(context.room.roomId, eventType as keyof TimelineEvents, content); }; let defaultContent: string | undefined; diff --git a/src/components/views/elements/PollCreateDialog.tsx b/src/components/views/elements/PollCreateDialog.tsx index 5049a3b016..0fd1b7c21e 100644 --- a/src/components/views/elements/PollCreateDialog.tsx +++ b/src/components/views/elements/PollCreateDialog.tsx @@ -23,6 +23,7 @@ import { M_POLL_KIND_UNDISCLOSED, M_POLL_START, IPartialEvent, + TimelineEvents, } from "matrix-js-sdk/src/matrix"; import { PollStartEvent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent"; @@ -166,8 +167,8 @@ export default class PollCreateDialog extends ScrollableBaseModal { MatrixClientPeg.safeGet().sendEvent(this.props.mxEvent.getRoomId()!, EventType.Reaction, { "m.relates_to": { rel_type: RelationType.Annotation, - event_id: this.props.mxEvent.getId(), + event_id: this.props.mxEvent.getId()!, key: reaction, }, }); diff --git a/src/components/views/location/shareLocation.ts b/src/components/views/location/shareLocation.ts index cf44a77c73..42a8e25c5b 100644 --- a/src/components/views/location/shareLocation.ts +++ b/src/components/views/location/shareLocation.ts @@ -16,13 +16,13 @@ limitations under the License. import { MatrixClient, - IContent, IEventRelation, MatrixError, THREAD_RELATION_TYPE, ContentHelpers, LocationAssetType, } from "matrix-js-sdk/src/matrix"; +import { RoomMessageEventContent } from "matrix-js-sdk/src/types"; import { logger } from "matrix-js-sdk/src/logger"; import { _t } from "../../../languageHandler"; @@ -146,7 +146,7 @@ export const shareLocation = timestamp, undefined, assetType, - ) as IContent; + ) as RoomMessageEventContent; await doMaybeLocalRoomAction( roomId, (actualRoomId: string) => client.sendMessage(actualRoomId, threadId, content), diff --git a/src/components/views/messages/MPollBody.tsx b/src/components/views/messages/MPollBody.tsx index fcf92c7f62..d777ed9d77 100644 --- a/src/components/views/messages/MPollBody.tsx +++ b/src/components/views/messages/MPollBody.tsx @@ -25,6 +25,7 @@ import { M_POLL_KIND_DISCLOSED, M_POLL_RESPONSE, M_POLL_START, + TimelineEvents, } from "matrix-js-sdk/src/matrix"; import { RelatedRelations } from "matrix-js-sdk/src/models/related-relations"; import { PollStartEvent, PollAnswerSubevent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent"; @@ -225,14 +226,20 @@ export default class MPollBody extends React.Component { const response = PollResponseEvent.from([answerId], this.props.mxEvent.getId()!).serialize(); - this.context.sendEvent(this.props.mxEvent.getRoomId()!, response.type, response.content).catch((e: any) => { - console.error("Failed to submit poll response event:", e); + this.context + .sendEvent( + this.props.mxEvent.getRoomId()!, + response.type as keyof TimelineEvents, + response.content as TimelineEvents[keyof TimelineEvents], + ) + .catch((e: any) => { + console.error("Failed to submit poll response event:", e); - Modal.createDialog(ErrorDialog, { - title: _t("poll|error_voting_title"), - description: _t("poll|error_voting_description"), + Modal.createDialog(ErrorDialog, { + title: _t("poll|error_voting_title"), + description: _t("poll|error_voting_description"), + }); }); - }); this.setState({ selected: answerId }); } diff --git a/src/components/views/messages/ReactionsRowButton.tsx b/src/components/views/messages/ReactionsRowButton.tsx index 99a1a6088b..2737212d33 100644 --- a/src/components/views/messages/ReactionsRowButton.tsx +++ b/src/components/views/messages/ReactionsRowButton.tsx @@ -16,7 +16,7 @@ limitations under the License. import React from "react"; import classNames from "classnames"; -import { MatrixEvent } from "matrix-js-sdk/src/matrix"; +import { EventType, MatrixEvent, RelationType } from "matrix-js-sdk/src/matrix"; import { mediaFromMxc } from "../../../customisations/Media"; import { _t } from "../../../languageHandler"; @@ -26,6 +26,7 @@ import ReactionsRowButtonTooltip from "./ReactionsRowButtonTooltip"; import AccessibleButton from "../elements/AccessibleButton"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import { REACTION_SHORTCODE_KEY } from "./ReactionsRow"; + export interface IProps { // The event we're displaying reactions for mxEvent: MatrixEvent; @@ -62,10 +63,10 @@ export default class ReactionsRowButton extends React.PureComponent, "m.relates_to"> = { "msgtype": newContent.msgtype, "body": `${plainPrefix} * ${body}`, "m.new_content": newContent, @@ -111,7 +116,7 @@ export function createEditContent(model: EditorModel, editedEvent: MatrixEvent, attachMentions(editedEvent.sender!.userId, contentBody, model, replyToEvent, editedEvent.getContent()); attachRelation(contentBody, { rel_type: "m.replace", event_id: editedEvent.getId() }); - return contentBody; + return contentBody as RoomMessageEventContent; } interface IEditMessageComposerProps extends MatrixClientProps { @@ -142,7 +147,7 @@ class EditMessageComposer extends React.Component(); if ( oldContent["msgtype"] === newContent["msgtype"] && oldContent["body"] === newContent["body"] && - oldContent["format"] === newContent["format"] && - oldContent["formatted_body"] === newContent["formatted_body"] + (oldContent as RoomMessageTextEventContent)["format"] === + (newContent as RoomMessageTextEventContent)["format"] && + (oldContent as RoomMessageTextEventContent)["formatted_body"] === + (newContent as RoomMessageTextEventContent)["formatted_body"] ) { return false; } @@ -318,7 +325,7 @@ class EditMessageComposer extends React.Component { +): Promise { const isEditing = isMatrixEvent(editedEvent); const isReply = isEditing ? Boolean(editedEvent.replyEventId) : isMatrixEvent(replyToEvent); const isReplyAndEditing = isEditing && isReply; @@ -100,10 +101,10 @@ export async function createMessageContent( const bodyPrefix = (isReplyAndEditing && getTextReplyFallback(editedEvent)) || ""; const formattedBodyPrefix = (isReplyAndEditing && getHtmlReplyFallback(editedEvent)) || ""; - const content: IContent = { + const content = { msgtype: isEmote ? MsgType.Emote : MsgType.Text, body: isEditing ? `${bodyPrefix} * ${body}` : body, - }; + } as RoomMessageTextEventContent & ReplacementEvent; // TODO markdown support diff --git a/src/components/views/rooms/wysiwyg_composer/utils/isContentModified.ts b/src/components/views/rooms/wysiwyg_composer/utils/isContentModified.ts index 0def386278..62af83b826 100644 --- a/src/components/views/rooms/wysiwyg_composer/utils/isContentModified.ts +++ b/src/components/views/rooms/wysiwyg_composer/utils/isContentModified.ts @@ -14,18 +14,22 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { IContent } from "matrix-js-sdk/src/matrix"; +import { RoomMessageEventContent, RoomMessageTextEventContent } from "matrix-js-sdk/src/types"; import EditorStateTransfer from "../../../../../utils/EditorStateTransfer"; -export function isContentModified(newContent: IContent, editorStateTransfer: EditorStateTransfer): boolean { +export function isContentModified( + newContent: RoomMessageEventContent, + editorStateTransfer: EditorStateTransfer, +): boolean { // if nothing has changed then bail - const oldContent = editorStateTransfer.getEvent().getContent(); + const oldContent = editorStateTransfer.getEvent().getContent(); if ( oldContent["msgtype"] === newContent["msgtype"] && oldContent["body"] === newContent["body"] && - oldContent["format"] === newContent["format"] && - oldContent["formatted_body"] === newContent["formatted_body"] + (oldContent)["format"] === (newContent)["format"] && + (oldContent)["formatted_body"] === + (newContent)["formatted_body"] ) { return false; } diff --git a/src/components/views/rooms/wysiwyg_composer/utils/message.ts b/src/components/views/rooms/wysiwyg_composer/utils/message.ts index a3c625048b..3122a9d2ef 100644 --- a/src/components/views/rooms/wysiwyg_composer/utils/message.ts +++ b/src/components/views/rooms/wysiwyg_composer/utils/message.ts @@ -16,13 +16,13 @@ limitations under the License. import { Composer as ComposerEvent } from "@matrix-org/analytics-events/types/typescript/Composer"; import { - IContent, IEventRelation, MatrixEvent, ISendEventResponse, MatrixClient, THREAD_RELATION_TYPE, } from "matrix-js-sdk/src/matrix"; +import { RoomMessageEventContent } from "matrix-js-sdk/src/types"; import { PosthogAnalytics } from "../../../../../PosthogAnalytics"; import SettingsStore from "../../../../../settings/SettingsStore"; @@ -82,7 +82,7 @@ export async function sendMessage( }*/ PosthogAnalytics.instance.trackEvent(posthogEvent); - let content: IContent | null = null; + let content: RoomMessageEventContent | null = null; // Slash command handling here approximates what can be found in SendMessageComposer.sendMessage() // but note that the /me and // special cases are handled by the call to createMessageContent @@ -145,7 +145,7 @@ export async function sendMessage( const prom = doMaybeLocalRoomAction( roomId, - (actualRoomId: string) => mxClient.sendMessage(actualRoomId, threadId, content as IContent), + (actualRoomId: string) => mxClient.sendMessage(actualRoomId, threadId, content!), mxClient, ); @@ -218,7 +218,7 @@ export async function editMessage( this.editorRef.current?.replaceEmoticon(position, REGEX_EMOTICON); }*/ const editContent = await createMessageContent(html, true, { editedEvent }); - const newContent = editContent["m.new_content"]; + const newContent = editContent["m.new_content"]!; const shouldSend = true; diff --git a/src/editor/commands.tsx b/src/editor/commands.tsx index 52ab881693..27f64f32be 100644 --- a/src/editor/commands.tsx +++ b/src/editor/commands.tsx @@ -16,7 +16,8 @@ limitations under the License. import React from "react"; import { logger } from "matrix-js-sdk/src/logger"; -import { IContent, MatrixClient } from "matrix-js-sdk/src/matrix"; +import { MatrixClient } from "matrix-js-sdk/src/matrix"; +import { RoomMessageEventContent } from "matrix-js-sdk/src/types"; import EditorModel from "./model"; import { Type } from "./parts"; @@ -63,9 +64,9 @@ export async function runSlashCommand( args: string | undefined, roomId: string, threadId: string | null, -): Promise<[content: IContent | null, success: boolean]> { +): Promise<[content: RoomMessageEventContent | null, success: boolean]> { const result = cmd.run(matrixClient, roomId, threadId, args); - let messageContent: IContent | null = null; + let messageContent: RoomMessageEventContent | null = null; let error: any = result.error; if (result.promise) { try { diff --git a/src/slash-commands/interface.ts b/src/slash-commands/interface.ts index 94e95126c0..8efc2b3ce9 100644 --- a/src/slash-commands/interface.ts +++ b/src/slash-commands/interface.ts @@ -17,7 +17,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { IContent } from "matrix-js-sdk/src/matrix"; +import { RoomMessageEventContent } from "matrix-js-sdk/src/types"; import { _td } from "../languageHandler"; import { XOR } from "../@types/common"; @@ -31,4 +31,4 @@ export const CommandCategories = { other: _td("slash_command|category_other"), }; -export type RunResult = XOR<{ error: Error }, { promise: Promise }>; +export type RunResult = XOR<{ error: Error }, { promise: Promise }>; diff --git a/src/stores/widgets/StopGapWidgetDriver.ts b/src/stores/widgets/StopGapWidgetDriver.ts index f80cd3f841..905b2c151c 100644 --- a/src/stores/widgets/StopGapWidgetDriver.ts +++ b/src/stores/widgets/StopGapWidgetDriver.ts @@ -44,6 +44,7 @@ import { Direction, THREAD_RELATION_TYPE, StateEvents, + TimelineEvents, } from "matrix-js-sdk/src/matrix"; import { logger } from "matrix-js-sdk/src/logger"; import { @@ -248,13 +249,13 @@ export class StopGapWidgetDriver extends WidgetDriver { stateKey?: string, targetRoomId?: string, ): Promise; - public async sendEvent( - eventType: Exclude, - content: IContent, + public async sendEvent( + eventType: K, + content: TimelineEvents[K], stateKey: null, targetRoomId?: string, ): Promise; - public async sendEvent( + public async sendEvent( eventType: string, content: IContent, stateKey?: string | null, @@ -268,13 +269,22 @@ export class StopGapWidgetDriver extends WidgetDriver { let r: { event_id: string } | null; if (stateKey !== null) { // state event - r = await client.sendStateEvent(roomId, eventType as K, content as StateEvents[K], stateKey); + r = await client.sendStateEvent( + roomId, + eventType as keyof StateEvents, + content as StateEvents[keyof StateEvents], + stateKey, + ); } else if (eventType === EventType.RoomRedaction) { // special case: extract the `redacts` property and call redact r = await client.redactEvent(roomId, content["redacts"]); } else { // message event - r = await client.sendEvent(roomId, eventType, content); + r = await client.sendEvent( + roomId, + eventType as keyof TimelineEvents, + content as TimelineEvents[keyof TimelineEvents], + ); if (eventType === EventType.RoomMessage) { CHAT_EFFECTS.forEach((effect) => { diff --git a/src/utils/createVoiceMessageContent.ts b/src/utils/createVoiceMessageContent.ts index 28ba5befac..06bd335389 100644 --- a/src/utils/createVoiceMessageContent.ts +++ b/src/utils/createVoiceMessageContent.ts @@ -14,7 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { IContent, IEncryptedFile, MsgType } from "matrix-js-sdk/src/matrix"; +import { IEncryptedFile, MsgType } from "matrix-js-sdk/src/matrix"; +import { RoomMessageEventContent } from "matrix-js-sdk/src/types"; /** * @param {string} mxc MXC URL of the file @@ -31,7 +32,7 @@ export const createVoiceMessageContent = ( size: number, file?: IEncryptedFile, waveform?: number[], -): IContent => { +): RoomMessageEventContent => { return { "body": "Voice message", //"msgtype": "org.matrix.msc2516.voice", diff --git a/src/voice-broadcast/models/VoiceBroadcastRecording.ts b/src/voice-broadcast/models/VoiceBroadcastRecording.ts index bafb910b8a..c36e3f75b3 100644 --- a/src/voice-broadcast/models/VoiceBroadcastRecording.ts +++ b/src/voice-broadcast/models/VoiceBroadcastRecording.ts @@ -26,7 +26,7 @@ import { RelationType, TypedEventEmitter, } from "matrix-js-sdk/src/matrix"; -import { EncryptedFile } from "matrix-js-sdk/src/types"; +import { AudioContent, EncryptedFile } from "matrix-js-sdk/src/types"; import { ChunkRecordedPayload, @@ -387,7 +387,7 @@ export class VoiceBroadcastRecording rel_type: RelationType.Reference, event_id: this.infoEventId, }; - content["io.element.voice_broadcast_chunk"] = { + (content)["io.element.voice_broadcast_chunk"] = { sequence, }; diff --git a/test/components/views/elements/PollCreateDialog-test.tsx b/test/components/views/elements/PollCreateDialog-test.tsx index 930af8fc32..fe2e4fd6d5 100644 --- a/test/components/views/elements/PollCreateDialog-test.tsx +++ b/test/components/views/elements/PollCreateDialog-test.tsx @@ -25,6 +25,7 @@ import { M_TEXT, } from "matrix-js-sdk/src/matrix"; import { PollStartEvent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent"; +import { ReplacementEvent } from "matrix-js-sdk/src/types"; import { getMockClientWithEventEmitter } from "../../../test-utils"; import { MatrixClientPeg } from "../../../../src/MatrixClientPeg"; @@ -273,7 +274,9 @@ describe("PollCreateDialog", () => { const [, , eventType, sentEventContent] = mockClient.sendEvent.mock.calls[0]; expect(M_POLL_START.matches(eventType)).toBeTruthy(); // didnt change - expect(sentEventContent["m.new_content"][M_POLL_START.name].kind).toEqual(M_POLL_KIND_DISCLOSED.name); + expect((sentEventContent as ReplacementEvent)["m.new_content"][M_POLL_START.name].kind).toEqual( + M_POLL_KIND_DISCLOSED.name, + ); }); }); diff --git a/test/components/views/rooms/EditMessageComposer-test.tsx b/test/components/views/rooms/EditMessageComposer-test.tsx index fce34299f6..d739fc6ad6 100644 --- a/test/components/views/rooms/EditMessageComposer-test.tsx +++ b/test/components/views/rooms/EditMessageComposer-test.tsx @@ -18,6 +18,7 @@ import React from "react"; import { fireEvent, render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { Room } from "matrix-js-sdk/src/matrix"; +import { ReplacementEvent, RoomMessageEventContent } from "matrix-js-sdk/src/types"; import EditMessageComposerWithMatrixClient, { createEditContent, @@ -296,11 +297,12 @@ describe("", () => { fireEvent.click(screen.getByText("Save")); - const messageContent = mockClient.sendMessage.mock.calls[0][2]; + const messageContent = mockClient.sendMessage.mock.calls[0][2] as RoomMessageEventContent & + ReplacementEvent; // both content.mentions and new_content.mentions are empty expect(messageContent["m.mentions"]).toEqual({}); - expect(messageContent["m.new_content"]["m.mentions"]).toEqual({}); + expect(messageContent["m.new_content"]!["m.mentions"]).toEqual({}); }); it("should retain mentions in the original message that are not removed by the edit", async () => { @@ -312,12 +314,13 @@ describe("", () => { fireEvent.click(screen.getByText("Save")); - const messageContent = mockClient.sendMessage.mock.calls[0][2]; + const messageContent = mockClient.sendMessage.mock.calls[0][2] as RoomMessageEventContent & + ReplacementEvent; // no new mentions were added, so nothing in top level mentions expect(messageContent["m.mentions"]).toEqual({}); // bob is still mentioned, charlie removed - expect(messageContent["m.new_content"]["m.mentions"]).toEqual({ + expect(messageContent["m.new_content"]!["m.mentions"]).toEqual({ user_ids: ["@bob:server.org"], }); }); @@ -331,12 +334,13 @@ describe("", () => { fireEvent.click(screen.getByText("Save")); - const messageContent = mockClient.sendMessage.mock.calls[0][2]; + const messageContent = mockClient.sendMessage.mock.calls[0][2] as RoomMessageEventContent & + ReplacementEvent; // no new mentions were added, so nothing in top level mentions expect(messageContent["m.mentions"]).toEqual({}); // bob is not longer mentioned in the edited message, so empty mentions in new_content - expect(messageContent["m.new_content"]["m.mentions"]).toEqual({}); + expect(messageContent["m.new_content"]!["m.mentions"]).toEqual({}); }); it("should add mentions that were added in the edit", async () => { @@ -352,13 +356,14 @@ describe("", () => { fireEvent.click(screen.getByText("Save")); - const messageContent = mockClient.sendMessage.mock.calls[0][2]; + const messageContent = mockClient.sendMessage.mock.calls[0][2] as RoomMessageEventContent & + ReplacementEvent; // new mention in the edit expect(messageContent["m.mentions"]).toEqual({ user_ids: ["@dan:server.org"], }); - expect(messageContent["m.new_content"]["m.mentions"]).toEqual({ + expect(messageContent["m.new_content"]!["m.mentions"]).toEqual({ user_ids: ["@dan:server.org"], }); }); @@ -377,14 +382,15 @@ describe("", () => { fireEvent.click(screen.getByText("Save")); - const messageContent = mockClient.sendMessage.mock.calls[0][2]; + const messageContent = mockClient.sendMessage.mock.calls[0][2] as RoomMessageEventContent & + ReplacementEvent; // new mention in the edit expect(messageContent["m.mentions"]).toEqual({ user_ids: ["@dan:server.org"], }); // all mentions in the edited version of the event - expect(messageContent["m.new_content"]["m.mentions"]).toEqual({ + expect(messageContent["m.new_content"]!["m.mentions"]).toEqual({ user_ids: ["@bob:server.org", "@dan:server.org"], }); }); @@ -454,12 +460,13 @@ describe("", () => { fireEvent.click(screen.getByText("Save")); - const messageContent = mockClient.sendMessage.mock.calls[0][2]; + const messageContent = mockClient.sendMessage.mock.calls[0][2] as RoomMessageEventContent & + ReplacementEvent; // no new mentions from edit expect(messageContent["m.mentions"]).toEqual({}); // edited reply still mentions the parent event sender - expect(messageContent["m.new_content"]["m.mentions"]).toEqual({ + expect(messageContent["m.new_content"]!["m.mentions"]).toEqual({ user_ids: [originalEvent.getSender()], }); }); @@ -475,7 +482,8 @@ describe("", () => { fireEvent.click(screen.getByText("Save")); - const messageContent = mockClient.sendMessage.mock.calls[0][2]; + const messageContent = mockClient.sendMessage.mock.calls[0][2] as RoomMessageEventContent & + ReplacementEvent; // new mention in edit expect(messageContent["m.mentions"]).toEqual({ @@ -483,7 +491,7 @@ describe("", () => { }); // edited reply still mentions the parent event sender // plus new mention @dan - expect(messageContent["m.new_content"]["m.mentions"]).toEqual({ + expect(messageContent["m.new_content"]!["m.mentions"]).toEqual({ user_ids: [originalEvent.getSender(), "@dan:server.org"], }); }); @@ -496,13 +504,14 @@ describe("", () => { fireEvent.click(screen.getByText("Save")); - const messageContent = mockClient.sendMessage.mock.calls[0][2]; + const messageContent = mockClient.sendMessage.mock.calls[0][2] as RoomMessageEventContent & + ReplacementEvent; // no mentions in edit expect(messageContent["m.mentions"]).toEqual({}); // edited reply still mentions the parent event sender // existing @bob mention removed - expect(messageContent["m.new_content"]["m.mentions"]).toEqual({ + expect(messageContent["m.new_content"]!["m.mentions"]).toEqual({ user_ids: [originalEvent.getSender()], }); }); @@ -536,12 +545,13 @@ describe("", () => { fireEvent.click(screen.getByText("Save")); - const messageContent = mockClient.sendMessage.mock.calls[0][2]; + const messageContent = mockClient.sendMessage.mock.calls[0][2] as RoomMessageEventContent & + ReplacementEvent; // no mentions in edit expect(messageContent["m.mentions"]).toEqual({}); // edited reply still mentions the parent event sender - expect(messageContent["m.new_content"]["m.mentions"]).toEqual({ + expect(messageContent["m.new_content"]!["m.mentions"]).toEqual({ user_ids: [originalEvent.getSender()], }); }); diff --git a/test/components/views/rooms/wysiwyg_composer/utils/message-test.ts b/test/components/views/rooms/wysiwyg_composer/utils/message-test.ts index 58fc6b7184..010c982f84 100644 --- a/test/components/views/rooms/wysiwyg_composer/utils/message-test.ts +++ b/test/components/views/rooms/wysiwyg_composer/utils/message-test.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { EventStatus, IEventRelation } from "matrix-js-sdk/src/matrix"; +import { EventStatus, IEventRelation, MsgType } from "matrix-js-sdk/src/matrix"; import { IRoomState } from "../../../../../../src/components/structures/RoomView"; import { editMessage, sendMessage } from "../../../../../../src/components/views/rooms/wysiwyg_composer/utils/message"; @@ -272,7 +272,10 @@ describe("message", () => { it("returns undefined when the command is not successful", async () => { // When const validCommand = "/spoiler"; - jest.spyOn(Commands, "runSlashCommand").mockResolvedValueOnce([{ content: "mock content" }, false]); + jest.spyOn(Commands, "runSlashCommand").mockResolvedValueOnce([ + { body: "mock content", msgtype: MsgType.Text }, + false, + ]); const result = await sendMessage(validCommand, true, { roomContext: defaultRoomContext,