From 2cbf92860b476aa8710625cdf33f6113e6c3ce22 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 20 Mar 2024 14:27:29 +0000 Subject: [PATCH] Improve types for `sendStateEvent` (#12331) --- playwright/e2e/room/room-header.spec.ts | 3 ++- playwright/pages/client.ts | 3 ++- src/@types/common.ts | 7 +------ src/ScalarMessaging.ts | 4 ++-- src/SlashCommands.tsx | 17 +++++++++-------- src/components/structures/SpaceHierarchy.tsx | 3 ++- .../views/dialogs/devtools/RoomState.tsx | 2 +- .../views/room_settings/AliasSettings.tsx | 7 ++++--- .../views/rooms/ThirdPartyMemberInfo.tsx | 4 ++-- .../tabs/room/RolesRoomSettingsTab.tsx | 7 ++++--- src/mjolnir/BanList.ts | 14 +++++++++----- src/mjolnir/ListRule.ts | 18 ++++++++++++++---- src/settings/handlers/RoomSettingsHandler.ts | 12 ++++++++++-- src/stores/widgets/StopGapWidgetDriver.ts | 15 ++++++++++++++- src/utils/RoomUpgrade.ts | 2 +- .../views/rooms/LegacyRoomHeader-test.tsx | 3 ++- test/models/Call-test.ts | 12 ++++++++++-- 17 files changed, 89 insertions(+), 44 deletions(-) diff --git a/playwright/e2e/room/room-header.spec.ts b/playwright/e2e/room/room-header.spec.ts index 2d0af8a6df..a3c5e8c8bc 100644 --- a/playwright/e2e/room/room-header.spec.ts +++ b/playwright/e2e/room/room-header.spec.ts @@ -18,6 +18,7 @@ import { Page } from "@playwright/test"; import { test, expect } from "../../element-web-test"; import { ElementAppPage } from "../../pages/ElementAppPage"; +import type { Container } from "../../../src/stores/widgets/types"; test.describe("Room Header", () => { test.use({ @@ -227,7 +228,7 @@ test.describe("Room Header", () => { { widgets: { [id]: { - container: "top", + container: "top" as Container, index: 1, width: 100, height: 0, diff --git a/playwright/pages/client.ts b/playwright/pages/client.ts index 054b946845..c10c050d9f 100644 --- a/playwright/pages/client.ts +++ b/playwright/pages/client.ts @@ -31,6 +31,7 @@ import type { Visibility, UploadOpts, Upload, + StateEvents, } from "matrix-js-sdk/src/matrix"; import { Credentials } from "../plugins/homeserver"; @@ -407,7 +408,7 @@ export class Client { const client = await this.prepareClient(); return client.evaluate( async (client, { roomId, eventType, content, stateKey }) => { - return client.sendStateEvent(roomId, eventType, content, stateKey); + return client.sendStateEvent(roomId, eventType as keyof StateEvents, content, stateKey); }, { roomId, eventType, content, stateKey }, ); diff --git a/src/@types/common.ts b/src/@types/common.ts index 4141418ac4..50e02e6a8f 100644 --- a/src/@types/common.ts +++ b/src/@types/common.ts @@ -16,12 +16,7 @@ limitations under the License. import { JSXElementConstructor } from "react"; -export type { NonEmptyArray } from "matrix-js-sdk/src/matrix"; - -// Based on https://stackoverflow.com/a/53229857/3532235 -export type Without = { [P in Exclude]?: never }; -export type XOR = T | U extends object ? (Without & U) | (Without & T) : T | U; -export type Writeable = { -readonly [P in keyof T]: T[P] }; +export type { NonEmptyArray, XOR, Writeable } from "matrix-js-sdk/src/matrix"; export type ComponentClass = keyof JSX.IntrinsicElements | JSXElementConstructor; diff --git a/src/ScalarMessaging.ts b/src/ScalarMessaging.ts index fb2801c9b6..28a257073b 100644 --- a/src/ScalarMessaging.ts +++ b/src/ScalarMessaging.ts @@ -291,7 +291,7 @@ Response: */ -import { IContent, MatrixEvent, IEvent } from "matrix-js-sdk/src/matrix"; +import { IContent, MatrixEvent, IEvent, StateEvents } from "matrix-js-sdk/src/matrix"; import { logger } from "matrix-js-sdk/src/logger"; import { MatrixClientPeg } from "./MatrixClientPeg"; @@ -721,7 +721,7 @@ async function getOpenIdToken(event: MessageEvent): Promise { async function sendEvent( event: MessageEvent<{ - type: string; + type: keyof StateEvents; state_key?: string; content?: IContent; }>, diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index f309ca4bc5..18ab9a8c64 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -18,8 +18,9 @@ limitations under the License. */ import * as React from "react"; -import { User, IContent, Direction, ContentHelpers, MRoomTopicEventContent } from "matrix-js-sdk/src/matrix"; +import { ContentHelpers, Direction, EventType, IContent, MRoomTopicEventContent, User } from "matrix-js-sdk/src/matrix"; import { logger } from "matrix-js-sdk/src/logger"; +import { RoomMemberEventContent } from "matrix-js-sdk/src/types"; import dis from "./dispatcher/dispatcher"; import { _t, _td, UserFriendlyError } from "./languageHandler"; @@ -239,12 +240,12 @@ export const Commands = [ isEnabled: (cli) => !isCurrentLocalRoom(cli), runFn: function (cli, roomId, threadId, args) { if (args) { - const ev = cli.getRoom(roomId)?.currentState.getStateEvents("m.room.member", cli.getSafeUserId()); - const content = { + const ev = cli.getRoom(roomId)?.currentState.getStateEvents(EventType.RoomMember, cli.getSafeUserId()); + const content: RoomMemberEventContent = { ...(ev ? ev.getContent() : { membership: "join" }), displayname: args, }; - return success(cli.sendStateEvent(roomId, "m.room.member", content, cli.getSafeUserId())); + return success(cli.sendStateEvent(roomId, EventType.RoomMember, content, cli.getSafeUserId())); } return reject(this.getUsage()); }, @@ -265,7 +266,7 @@ export const Commands = [ return success( promise.then((url) => { if (!url) return; - return cli.sendStateEvent(roomId, "m.room.avatar", { url }, ""); + return cli.sendStateEvent(roomId, EventType.RoomAvatar, { url }, ""); }), ); }, @@ -289,12 +290,12 @@ export const Commands = [ return success( promise.then((url) => { if (!url) return; - const ev = room?.currentState.getStateEvents("m.room.member", userId); - const content = { + const ev = room?.currentState.getStateEvents(EventType.RoomMember, userId); + const content: RoomMemberEventContent = { ...(ev ? ev.getContent() : { membership: "join" }), avatar_url: url, }; - return cli.sendStateEvent(roomId, "m.room.member", content, userId); + return cli.sendStateEvent(roomId, EventType.RoomMember, content, userId); }), ); }, diff --git a/src/components/structures/SpaceHierarchy.tsx b/src/components/structures/SpaceHierarchy.tsx index feeacb4581..58e77123f0 100644 --- a/src/components/structures/SpaceHierarchy.tsx +++ b/src/components/structures/SpaceHierarchy.tsx @@ -46,6 +46,7 @@ import { RoomHierarchy } from "matrix-js-sdk/src/room-hierarchy"; import classNames from "classnames"; import { sortBy, uniqBy } from "lodash"; import { logger } from "matrix-js-sdk/src/logger"; +import { SpaceChildEventContent } from "matrix-js-sdk/src/types"; import defaultDispatcher from "../../dispatcher/dispatcher"; import { _t } from "../../languageHandler"; @@ -726,7 +727,7 @@ const ManageButtons: React.FC = ({ hierarchy, selected, set const existingContent = hierarchy.getRelation(parentId, childId)?.content; if (!existingContent || existingContent.suggested === suggested) continue; - const content = { + const content: SpaceChildEventContent = { ...existingContent, suggested: !selectionAllSuggested, }; diff --git a/src/components/views/dialogs/devtools/RoomState.tsx b/src/components/views/dialogs/devtools/RoomState.tsx index ba8e3c75d9..51fdd1e830 100644 --- a/src/components/views/dialogs/devtools/RoomState.tsx +++ b/src/components/views/dialogs/devtools/RoomState.tsx @@ -38,7 +38,7 @@ export const StateEventEditor: React.FC = ({ mxEvent, onBack }) => ); const onSend = async ([eventType, stateKey]: string[], content: IContent): Promise => { - await cli.sendStateEvent(context.room.roomId, eventType, content, stateKey); + await cli.sendStateEvent(context.room.roomId, eventType as any, content, stateKey); }; const defaultContent = mxEvent ? stringify(mxEvent.getContent()) : undefined; diff --git a/src/components/views/room_settings/AliasSettings.tsx b/src/components/views/room_settings/AliasSettings.tsx index 7b1bc1be28..3b2f294e5c 100644 --- a/src/components/views/room_settings/AliasSettings.tsx +++ b/src/components/views/room_settings/AliasSettings.tsx @@ -15,8 +15,9 @@ limitations under the License. */ import React, { ChangeEvent, ContextType, createRef, SyntheticEvent } from "react"; -import { IContent, MatrixEvent, EventType } from "matrix-js-sdk/src/matrix"; +import { MatrixEvent, EventType } from "matrix-js-sdk/src/matrix"; import { logger } from "matrix-js-sdk/src/logger"; +import { RoomCanonicalAliasEventContent } from "matrix-js-sdk/src/types"; import EditableItemList from "../elements/EditableItemList"; import { _t } from "../../../languageHandler"; @@ -169,7 +170,7 @@ export default class AliasSettings extends React.Component { updatingCanonicalAlias: true, }); - const eventContent: IContent = { + const eventContent: RoomCanonicalAliasEventContent = { alt_aliases: this.state.altAliases, }; @@ -197,7 +198,7 @@ export default class AliasSettings extends React.Component { updatingCanonicalAlias: true, }); - const eventContent: IContent = {}; + const eventContent: RoomCanonicalAliasEventContent = {}; if (this.state.canonicalAlias) { eventContent["alias"] = this.state.canonicalAlias; diff --git a/src/components/views/rooms/ThirdPartyMemberInfo.tsx b/src/components/views/rooms/ThirdPartyMemberInfo.tsx index dcfc8f567a..5e02b3b9c8 100644 --- a/src/components/views/rooms/ThirdPartyMemberInfo.tsx +++ b/src/components/views/rooms/ThirdPartyMemberInfo.tsx @@ -15,7 +15,7 @@ limitations under the License. */ import React from "react"; -import { MatrixEvent, Room, RoomStateEvent, EventType } from "matrix-js-sdk/src/matrix"; +import { EventType, MatrixEvent, Room, RoomStateEvent } from "matrix-js-sdk/src/matrix"; import { logger } from "matrix-js-sdk/src/logger"; import { Button, Text } from "@vector-im/compound-web"; @@ -101,7 +101,7 @@ export default class ThirdPartyMemberInfo extends React.Component { MatrixClientPeg.safeGet() - .sendStateEvent(this.state.roomId, "m.room.third_party_invite", {}, this.state.stateKey) + .sendStateEvent(this.state.roomId, EventType.RoomThirdPartyInvite, {}, this.state.stateKey) .catch((err) => { logger.error(err); diff --git a/src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx index d1b6c4eaf1..708fbc73e2 100644 --- a/src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx +++ b/src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx @@ -18,6 +18,7 @@ import React from "react"; import { EventType, RoomMember, RoomState, RoomStateEvent, Room, IContent } from "matrix-js-sdk/src/matrix"; import { logger } from "matrix-js-sdk/src/logger"; import { throttle, get } from "lodash"; +import { RoomPowerLevelsEventContent } from "matrix-js-sdk/src/types"; import { _t, _td, TranslationKey } from "../../../../../languageHandler"; import AccessibleButton from "../../../elements/AccessibleButton"; @@ -178,7 +179,7 @@ export default class RolesRoomSettingsTab extends React.Component { const client = this.context; const room = this.props.room; const plEvent = room.currentState.getStateEvents(EventType.RoomPowerLevels, ""); - let plContent = plEvent?.getContent() ?? {}; + let plContent = plEvent?.getContent() ?? {}; // Clone the power levels just in case plContent = Object.assign({}, plContent); @@ -192,7 +193,7 @@ export default class RolesRoomSettingsTab extends React.Component { } else { const keyPath = powerLevelKey.split("."); let parentObj: IContent = {}; - let currentObj = plContent; + let currentObj: IContent = plContent; for (const key of keyPath) { if (!currentObj[key]) { currentObj[key] = {}; @@ -222,7 +223,7 @@ export default class RolesRoomSettingsTab extends React.Component { const client = this.context; const room = this.props.room; const plEvent = room.currentState.getStateEvents(EventType.RoomPowerLevels, ""); - let plContent = plEvent?.getContent() ?? {}; + let plContent = plEvent?.getContent() ?? {}; // Clone the power levels just in case plContent = Object.assign({}, plContent); diff --git a/src/mjolnir/BanList.ts b/src/mjolnir/BanList.ts index 68c15282c1..b0b63c0817 100644 --- a/src/mjolnir/BanList.ts +++ b/src/mjolnir/BanList.ts @@ -16,12 +16,14 @@ limitations under the License. // Inspiration largely taken from Mjolnir itself +import { EventType } from "matrix-js-sdk/src/matrix"; + import { ListRule, RECOMMENDATION_BAN, recommendationToStable } from "./ListRule"; import { MatrixClientPeg } from "../MatrixClientPeg"; -export const RULE_USER = "m.policy.rule.user"; -export const RULE_ROOM = "m.policy.rule.room"; -export const RULE_SERVER = "m.policy.rule.server"; +export const RULE_USER = EventType.PolicyRuleUser; +export const RULE_ROOM = EventType.PolicyRuleRoom; +export const RULE_SERVER = EventType.PolicyRuleServer; // m.room.* events are legacy from when MSC2313 changed to m.policy.* last minute. export const USER_RULE_TYPES = [RULE_USER, "m.room.rule.user", "org.matrix.mjolnir.rule.user"]; @@ -29,7 +31,9 @@ export const ROOM_RULE_TYPES = [RULE_ROOM, "m.room.rule.room", "org.matrix.mjoln export const SERVER_RULE_TYPES = [RULE_SERVER, "m.room.rule.server", "org.matrix.mjolnir.rule.server"]; export const ALL_RULE_TYPES = [...USER_RULE_TYPES, ...ROOM_RULE_TYPES, ...SERVER_RULE_TYPES]; -export function ruleTypeToStable(rule: string): string | null { +export function ruleTypeToStable( + rule: string, +): EventType.PolicyRuleUser | EventType.PolicyRuleRoom | EventType.PolicyRuleServer | null { if (USER_RULE_TYPES.includes(rule)) { return RULE_USER; } @@ -72,7 +76,7 @@ export class BanList { { entity: entity, reason: reason, - recommendation: recommendationToStable(RECOMMENDATION_BAN, true), + recommendation: recommendationToStable(RECOMMENDATION_BAN, true)!, }, "rule:" + entity, ); diff --git a/src/mjolnir/ListRule.ts b/src/mjolnir/ListRule.ts index 3fc1b00768..c6595b33b3 100644 --- a/src/mjolnir/ListRule.ts +++ b/src/mjolnir/ListRule.ts @@ -14,14 +14,24 @@ See the License for the specific language governing permissions and limitations under the License. */ +// We are using experimental APIs here, so we need to disable the linter +// eslint-disable-next-line no-restricted-imports +import { PolicyRecommendation } from "matrix-js-sdk/src/models/invites-ignorer"; + import { MatrixGlob } from "../utils/MatrixGlob"; // Inspiration largely taken from Mjolnir itself -export const RECOMMENDATION_BAN = "m.ban"; -export const RECOMMENDATION_BAN_TYPES = [RECOMMENDATION_BAN, "org.matrix.mjolnir.ban"]; +export const RECOMMENDATION_BAN = PolicyRecommendation.Ban; +export const RECOMMENDATION_BAN_TYPES: PolicyRecommendation[] = [ + RECOMMENDATION_BAN, + "org.matrix.mjolnir.ban" as PolicyRecommendation, +]; -export function recommendationToStable(recommendation: string, unstable = true): string | null { +export function recommendationToStable( + recommendation: PolicyRecommendation, + unstable = true, +): PolicyRecommendation | null { if (RECOMMENDATION_BAN_TYPES.includes(recommendation)) { return unstable ? RECOMMENDATION_BAN_TYPES[RECOMMENDATION_BAN_TYPES.length - 1] : RECOMMENDATION_BAN; } @@ -35,7 +45,7 @@ export class ListRule { private readonly _reason: string; private readonly _kind: string; - public constructor(entity: string, action: string, reason: string, kind: string) { + public constructor(entity: string, action: PolicyRecommendation, reason: string, kind: string) { this._glob = new MatrixGlob(entity); this._entity = entity; this._action = recommendationToStable(action, false); diff --git a/src/settings/handlers/RoomSettingsHandler.ts b/src/settings/handlers/RoomSettingsHandler.ts index 12c3124365..652c323a9e 100644 --- a/src/settings/handlers/RoomSettingsHandler.ts +++ b/src/settings/handlers/RoomSettingsHandler.ts @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { MatrixClient, MatrixEvent, RoomState, RoomStateEvent } from "matrix-js-sdk/src/matrix"; +import { MatrixClient, MatrixEvent, RoomState, RoomStateEvent, StateEvents } from "matrix-js-sdk/src/matrix"; import { defer } from "matrix-js-sdk/src/utils"; import MatrixClientBackedSettingsHandler from "./MatrixClientBackedSettingsHandler"; @@ -24,6 +24,9 @@ import { SettingLevel } from "../SettingLevel"; import { WatchManager } from "../WatchManager"; const DEFAULT_SETTINGS_EVENT_TYPE = "im.vector.web.settings"; +const PREVIEW_URLS_EVENT_TYPE = "org.matrix.room.preview_urls"; + +type RoomSettingsEventType = typeof DEFAULT_SETTINGS_EVENT_TYPE | typeof PREVIEW_URLS_EVENT_TYPE; /** * Gets and sets settings at the "room" level. @@ -88,7 +91,12 @@ export default class RoomSettingsHandler extends MatrixClientBackedSettingsHandl } // helper function to send state event then await it being echoed back - private async sendStateEvent(roomId: string, eventType: string, field: string, value: any): Promise { + private async sendStateEvent( + roomId: string, + eventType: K, + field: F, + value: StateEvents[K][F], + ): Promise { const content = this.getSettings(roomId, eventType) || {}; content[field] = value; diff --git a/src/stores/widgets/StopGapWidgetDriver.ts b/src/stores/widgets/StopGapWidgetDriver.ts index c2453ebccf..f80cd3f841 100644 --- a/src/stores/widgets/StopGapWidgetDriver.ts +++ b/src/stores/widgets/StopGapWidgetDriver.ts @@ -43,6 +43,7 @@ import { Room, Direction, THREAD_RELATION_TYPE, + StateEvents, } from "matrix-js-sdk/src/matrix"; import { logger } from "matrix-js-sdk/src/logger"; import { @@ -241,7 +242,19 @@ export class StopGapWidgetDriver extends WidgetDriver { return allAllowed; } + public async sendEvent( + eventType: K, + content: StateEvents[K], + stateKey?: string, + targetRoomId?: string, + ): Promise; public async sendEvent( + eventType: Exclude, + content: IContent, + stateKey: null, + targetRoomId?: string, + ): Promise; + public async sendEvent( eventType: string, content: IContent, stateKey?: string | null, @@ -255,7 +268,7 @@ export class StopGapWidgetDriver extends WidgetDriver { let r: { event_id: string } | null; if (stateKey !== null) { // state event - r = await client.sendStateEvent(roomId, eventType, content, stateKey); + r = await client.sendStateEvent(roomId, eventType as K, content as StateEvents[K], stateKey); } else if (eventType === EventType.RoomRedaction) { // special case: extract the `redacts` property and call redact r = await client.redactEvent(roomId, content["redacts"]); diff --git a/src/utils/RoomUpgrade.ts b/src/utils/RoomUpgrade.ts index 88fc904541..c67bd54bba 100644 --- a/src/utils/RoomUpgrade.ts +++ b/src/utils/RoomUpgrade.ts @@ -131,7 +131,7 @@ export async function upgradeRoom( EventType.SpaceChild, { ...(currentEv?.getContent() || {}), // copy existing attributes like suggested - via: [cli.getDomain()], + via: [cli.getDomain()!], }, newRoomId, ); diff --git a/test/components/views/rooms/LegacyRoomHeader-test.tsx b/test/components/views/rooms/LegacyRoomHeader-test.tsx index 067059801a..f9528a94d2 100644 --- a/test/components/views/rooms/LegacyRoomHeader-test.tsx +++ b/test/components/views/rooms/LegacyRoomHeader-test.tsx @@ -24,6 +24,7 @@ import { RoomStateEvent, PendingEventOrdering, ISearchResults, + IContent, } from "matrix-js-sdk/src/matrix"; import { CallType } from "matrix-js-sdk/src/webrtc/call"; import { ClientWidgetApi, Widget } from "matrix-widget-api"; @@ -111,7 +112,7 @@ describe("LegacyRoomHeader", () => { room: roomId, user: alice.userId, skey: stateKey, - content, + content: content as IContent, }); room.addLiveEvents([event]); return { event_id: event.getId()! }; diff --git a/test/models/Call-test.ts b/test/models/Call-test.ts index 1ebdfff5a5..4dc01265ef 100644 --- a/test/models/Call-test.ts +++ b/test/models/Call-test.ts @@ -17,7 +17,15 @@ limitations under the License. import EventEmitter from "events"; import { mocked } from "jest-mock"; import { waitFor } from "@testing-library/react"; -import { RoomType, Room, RoomEvent, MatrixEvent, RoomStateEvent, PendingEventOrdering } from "matrix-js-sdk/src/matrix"; +import { + RoomType, + Room, + RoomEvent, + MatrixEvent, + RoomStateEvent, + PendingEventOrdering, + IContent, +} from "matrix-js-sdk/src/matrix"; import { Widget } from "matrix-widget-api"; // eslint-disable-next-line no-restricted-imports import { MatrixRTCSessionManagerEvents } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSessionManager"; @@ -116,7 +124,7 @@ const setUpClientRoomAndStores = (): { room: roomId, user: alice.userId, skey: stateKey, - content, + content: content as IContent, }); room.addLiveEvents([event]); return { event_id: event.getId()! };