From 21ffc50f1e44341109b789a785d27070952ddf32 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 1 Jun 2023 14:43:24 +0100 Subject: [PATCH] Pass around MatrixClients instead of using MatrixClientPeg (#10984) Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> --- src/DeviceListener.ts | 94 ++++++++++--------- src/IdentityAuthClient.tsx | 2 +- src/Lifecycle.ts | 8 +- src/Livestream.ts | 14 ++- src/PosthogAnalytics.ts | 10 +- src/RoomNotifs.ts | 22 ++--- src/Rooms.ts | 13 +-- src/ScalarAuthClient.ts | 1 + src/ScalarMessaging.ts | 2 +- src/Terms.ts | 12 ++- src/components/structures/RoomView.tsx | 2 +- .../views/context_menus/WidgetContextMenu.tsx | 2 +- src/components/views/right_panel/UserInfo.tsx | 6 +- .../tabs/user/GeneralUserSettingsTab.tsx | 5 + src/createRoom.ts | 2 +- src/rageshake/submit-rageshake.ts | 2 +- src/stores/local-echo/RoomEchoChamber.ts | 2 +- src/stores/right-panel/RightPanelStore.ts | 7 +- src/verification.ts | 49 +++++----- test/DeviceListener-test.ts | 6 +- test/Rooms-test.ts | 16 ++-- test/Terms-test.tsx | 8 +- .../views/right_panel/UserInfo-test.tsx | 2 +- 23 files changed, 152 insertions(+), 135 deletions(-) diff --git a/src/DeviceListener.ts b/src/DeviceListener.ts index 43ec33bb11..40ce534ce0 100644 --- a/src/DeviceListener.ts +++ b/src/DeviceListener.ts @@ -17,11 +17,10 @@ limitations under the License. import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { logger } from "matrix-js-sdk/src/logger"; import { CryptoEvent } from "matrix-js-sdk/src/crypto"; -import { ClientEvent, EventType, RoomStateEvent } from "matrix-js-sdk/src/matrix"; +import { ClientEvent, EventType, MatrixClient, RoomStateEvent } from "matrix-js-sdk/src/matrix"; import { SyncState } from "matrix-js-sdk/src/sync"; import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup"; -import { MatrixClientPeg } from "./MatrixClientPeg"; import dis from "./dispatcher/dispatcher"; import { hideToast as hideBulkUnverifiedSessionsToast, @@ -67,6 +66,8 @@ export default class DeviceListener { // The set of device IDs we're currently displaying toasts for private displayingToastsForDeviceIds = new Set(); private running = false; + // The client with which the instance is running. Only set if `running` is true, otherwise undefined. + private client?: MatrixClient; private shouldRecordClientInformation = false; private enableBulkUnverifiedSessionsReminder = true; private deviceClientInformationSettingWatcherRef: string | undefined; @@ -76,16 +77,17 @@ export default class DeviceListener { return window.mxDeviceListener; } - public start(): void { + public start(matrixClient: MatrixClient): void { this.running = true; - MatrixClientPeg.get().on(CryptoEvent.WillUpdateDevices, this.onWillUpdateDevices); - MatrixClientPeg.get().on(CryptoEvent.DevicesUpdated, this.onDevicesUpdated); - MatrixClientPeg.get().on(CryptoEvent.DeviceVerificationChanged, this.onDeviceVerificationChanged); - MatrixClientPeg.get().on(CryptoEvent.UserTrustStatusChanged, this.onUserTrustStatusChanged); - MatrixClientPeg.get().on(CryptoEvent.KeysChanged, this.onCrossSingingKeysChanged); - MatrixClientPeg.get().on(ClientEvent.AccountData, this.onAccountData); - MatrixClientPeg.get().on(ClientEvent.Sync, this.onSync); - MatrixClientPeg.get().on(RoomStateEvent.Events, this.onRoomStateEvents); + this.client = matrixClient; + this.client.on(CryptoEvent.WillUpdateDevices, this.onWillUpdateDevices); + this.client.on(CryptoEvent.DevicesUpdated, this.onDevicesUpdated); + this.client.on(CryptoEvent.DeviceVerificationChanged, this.onDeviceVerificationChanged); + this.client.on(CryptoEvent.UserTrustStatusChanged, this.onUserTrustStatusChanged); + this.client.on(CryptoEvent.KeysChanged, this.onCrossSingingKeysChanged); + this.client.on(ClientEvent.AccountData, this.onAccountData); + this.client.on(ClientEvent.Sync, this.onSync); + this.client.on(RoomStateEvent.Events, this.onRoomStateEvents); this.shouldRecordClientInformation = SettingsStore.getValue("deviceClientInformationOptIn"); // only configurable in config, so we don't need to watch the value this.enableBulkUnverifiedSessionsReminder = SettingsStore.getValue(UIFeature.BulkUnverifiedSessionsReminder); @@ -101,18 +103,15 @@ export default class DeviceListener { public stop(): void { this.running = false; - if (MatrixClientPeg.get()) { - MatrixClientPeg.get().removeListener(CryptoEvent.WillUpdateDevices, this.onWillUpdateDevices); - MatrixClientPeg.get().removeListener(CryptoEvent.DevicesUpdated, this.onDevicesUpdated); - MatrixClientPeg.get().removeListener( - CryptoEvent.DeviceVerificationChanged, - this.onDeviceVerificationChanged, - ); - MatrixClientPeg.get().removeListener(CryptoEvent.UserTrustStatusChanged, this.onUserTrustStatusChanged); - MatrixClientPeg.get().removeListener(CryptoEvent.KeysChanged, this.onCrossSingingKeysChanged); - MatrixClientPeg.get().removeListener(ClientEvent.AccountData, this.onAccountData); - MatrixClientPeg.get().removeListener(ClientEvent.Sync, this.onSync); - MatrixClientPeg.get().removeListener(RoomStateEvent.Events, this.onRoomStateEvents); + if (this.client) { + this.client.removeListener(CryptoEvent.WillUpdateDevices, this.onWillUpdateDevices); + this.client.removeListener(CryptoEvent.DevicesUpdated, this.onDevicesUpdated); + this.client.removeListener(CryptoEvent.DeviceVerificationChanged, this.onDeviceVerificationChanged); + this.client.removeListener(CryptoEvent.UserTrustStatusChanged, this.onUserTrustStatusChanged); + this.client.removeListener(CryptoEvent.KeysChanged, this.onCrossSingingKeysChanged); + this.client.removeListener(ClientEvent.AccountData, this.onAccountData); + this.client.removeListener(ClientEvent.Sync, this.onSync); + this.client.removeListener(RoomStateEvent.Events, this.onRoomStateEvents); } if (this.deviceClientInformationSettingWatcherRef) { SettingsStore.unwatchSetting(this.deviceClientInformationSettingWatcherRef); @@ -128,6 +127,7 @@ export default class DeviceListener { this.keyBackupStatusChecked = false; this.ourDeviceIdsAtStart = null; this.displayingToastsForDeviceIds = new Set(); + this.client = undefined; } /** @@ -160,22 +160,23 @@ export default class DeviceListener { * @returns the set of device IDs */ private async getDeviceIds(): Promise> { - const cli = MatrixClientPeg.get(); - const crypto = cli.getCrypto(); + const cli = this.client; + const crypto = cli?.getCrypto(); if (crypto === undefined) return new Set(); - const userId = cli.getSafeUserId(); + const userId = cli!.getSafeUserId(); const devices = await crypto.getUserDeviceInfo([userId]); return new Set(devices.get(userId)?.keys() ?? []); } private onWillUpdateDevices = async (users: string[], initialFetch?: boolean): Promise => { + if (!this.client) return; // If we didn't know about *any* devices before (ie. it's fresh login), // then they are all pre-existing devices, so ignore this and set the // devicesAtStart list to the devices that we see after the fetch. if (initialFetch) return; - const myUserId = MatrixClientPeg.get().getUserId()!; + const myUserId = this.client.getSafeUserId(); if (users.includes(myUserId)) await this.ensureDeviceIdsAtStartPopulated(); // No need to do a recheck here: we just need to get a snapshot of our devices @@ -183,17 +184,20 @@ export default class DeviceListener { }; private onDevicesUpdated = (users: string[]): void => { - if (!users.includes(MatrixClientPeg.get().getUserId()!)) return; + if (!this.client) return; + if (!users.includes(this.client.getSafeUserId())) return; this.recheck(); }; private onDeviceVerificationChanged = (userId: string): void => { - if (userId !== MatrixClientPeg.get().getUserId()) return; + if (!this.client) return; + if (userId !== this.client.getUserId()) return; this.recheck(); }; private onUserTrustStatusChanged = (userId: string): void => { - if (userId !== MatrixClientPeg.get().getUserId()) return; + if (!this.client) return; + if (userId !== this.client.getUserId()) return; this.recheck(); }; @@ -239,13 +243,14 @@ export default class DeviceListener { // The server doesn't tell us when key backup is set up, so we poll // & cache the result private async getKeyBackupInfo(): Promise { + if (!this.client) return null; const now = new Date().getTime(); if ( !this.keyBackupInfo || !this.keyBackupFetchedAt || this.keyBackupFetchedAt < now - KEY_BACKUP_POLL_INTERVAL ) { - this.keyBackupInfo = await MatrixClientPeg.get().getKeyBackupVersion(); + this.keyBackupInfo = await this.client.getKeyBackupVersion(); this.keyBackupFetchedAt = now; } return this.keyBackupInfo; @@ -256,13 +261,13 @@ export default class DeviceListener { // modifying the state involved here, so don't add new toasts to setup. if (isSecretStorageBeingAccessed()) return false; // Show setup toasts once the user is in at least one encrypted room. - const cli = MatrixClientPeg.get(); - return cli && cli.getRooms().some((r) => cli.isRoomEncrypted(r.roomId)); + const cli = this.client; + return cli?.getRooms().some((r) => cli.isRoomEncrypted(r.roomId)) ?? false; } private async recheck(): Promise { - if (!this.running) return; // we have been stopped - const cli = MatrixClientPeg.get(); + if (!this.running || !this.client) return; // we have been stopped + const cli = this.client; // cross-signing support was added to Matrix in MSC1756, which landed in spec v1.1 if (!(await cli.isVersionSupported("v1.1"))) return; @@ -285,11 +290,11 @@ export default class DeviceListener { this.checkKeyBackupStatus(); } else if (this.shouldShowSetupEncryptionToast()) { // make sure our keys are finished downloading - await crypto.getUserDeviceInfo([cli.getUserId()!]); + await crypto.getUserDeviceInfo([cli.getSafeUserId()]); // cross signing isn't enabled - nag to enable it // There are 3 different toasts for: - if (!(await crypto.getCrossSigningKeyId()) && cli.getStoredCrossSigningForUser(cli.getUserId()!)) { + if (!(await crypto.getCrossSigningKeyId()) && cli.getStoredCrossSigningForUser(cli.getSafeUserId())) { // Cross-signing on account but this device doesn't trust the master key (verify this session) showSetupEncryptionToast(SetupKind.VERIFY_THIS_SESSION); this.checkKeyBackupStatus(); @@ -327,7 +332,9 @@ export default class DeviceListener { const isCurrentDeviceTrusted = crossSigningReady && - Boolean((await crypto.getDeviceVerificationStatus(cli.getUserId()!, cli.deviceId!))?.crossSigningVerified); + Boolean( + (await crypto.getDeviceVerificationStatus(cli.getSafeUserId(), cli.deviceId!))?.crossSigningVerified, + ); // as long as cross-signing isn't ready, // you can't see or dismiss any device toasts @@ -336,7 +343,7 @@ export default class DeviceListener { for (const deviceId of devices) { if (deviceId === cli.deviceId) continue; - const deviceTrust = await crypto.getDeviceVerificationStatus(cli.getUserId()!, deviceId); + const deviceTrust = await crypto.getDeviceVerificationStatus(cli.getSafeUserId(), deviceId); if (!deviceTrust?.crossSigningVerified && !this.dismissed.has(deviceId)) { if (this.ourDeviceIdsAtStart?.has(deviceId)) { oldUnverifiedDeviceIds.add(deviceId); @@ -383,11 +390,11 @@ export default class DeviceListener { } private checkKeyBackupStatus = async (): Promise => { - if (this.keyBackupStatusChecked) { + if (this.keyBackupStatusChecked || !this.client) { return; } // returns null when key backup status hasn't finished being checked - const isKeyBackupEnabled = MatrixClientPeg.get().getKeyBackupEnabled(); + const isKeyBackupEnabled = this.client.getKeyBackupEnabled(); this.keyBackupStatusChecked = isKeyBackupEnabled !== null; if (isKeyBackupEnabled === false) { @@ -412,11 +419,12 @@ export default class DeviceListener { }; private updateClientInformation = async (): Promise => { + if (!this.client) return; try { if (this.shouldRecordClientInformation) { - await recordClientInformation(MatrixClientPeg.get(), SdkConfig.get(), PlatformPeg.get() ?? undefined); + await recordClientInformation(this.client, SdkConfig.get(), PlatformPeg.get() ?? undefined); } else { - await removeClientInformation(MatrixClientPeg.get()); + await removeClientInformation(this.client); } } catch (error) { // this is a best effort operation diff --git a/src/IdentityAuthClient.tsx b/src/IdentityAuthClient.tsx index 05a61eab05..28e88a49f7 100644 --- a/src/IdentityAuthClient.tsx +++ b/src/IdentityAuthClient.tsx @@ -129,7 +129,7 @@ export default class IdentityAuthClient { } catch (e) { if (e instanceof MatrixError && e.errcode === "M_TERMS_NOT_SIGNED") { logger.log("Identity server requires new terms to be agreed to"); - await startTermsFlow([new Service(SERVICE_TYPES.IS, identityServerUrl, token)]); + await startTermsFlow(this.matrixClient, [new Service(SERVICE_TYPES.IS, identityServerUrl, token)]); return; } throw e; diff --git a/src/Lifecycle.ts b/src/Lifecycle.ts index 889684814c..6f49a0a0b6 100644 --- a/src/Lifecycle.ts +++ b/src/Lifecycle.ts @@ -553,7 +553,7 @@ export async function hydrateSession(credentials: IMatrixClientCreds): Promise { - const openIdToken = await MatrixClientPeg.get().getOpenIdToken(); +async function createLiveStream(matrixClient: MatrixClient, roomId: string): Promise { + const openIdToken = await matrixClient.getOpenIdToken(); const url = getConfigLivestreamUrl() + "/createStream"; @@ -47,8 +47,12 @@ async function createLiveStream(roomId: string): Promise { return respBody["stream_id"]; } -export async function startJitsiAudioLivestream(widgetMessaging: ClientWidgetApi, roomId: string): Promise { - const streamId = await createLiveStream(roomId); +export async function startJitsiAudioLivestream( + matrixClient: MatrixClient, + widgetMessaging: ClientWidgetApi, + roomId: string, +): Promise { + const streamId = await createLiveStream(matrixClient, roomId); await widgetMessaging.transport.send(ElementWidgetActions.StartLiveStream, { rtmpStreamKey: AUDIOSTREAM_DUMMY_URL + streamId, diff --git a/src/PosthogAnalytics.ts b/src/PosthogAnalytics.ts index 2a7e24294e..9e76ebb097 100644 --- a/src/PosthogAnalytics.ts +++ b/src/PosthogAnalytics.ts @@ -254,7 +254,7 @@ export class PosthogAnalytics { }; } - // eslint-disable-nextline no-unused-varsx + // eslint-disable-nextline no-unused-vars private capture(eventName: string, properties: Properties, options?: IPostHogEventOptions): void { if (!this.enabled) { return; @@ -375,12 +375,12 @@ export class PosthogAnalytics { this.registerSuperProperties(this.platformSuperProperties); } - public async updateAnonymityFromSettings(pseudonymousOptIn: boolean): Promise { + public async updateAnonymityFromSettings(client: MatrixClient, pseudonymousOptIn: boolean): Promise { // Update this.anonymity based on the user's analytics opt-in settings const anonymity = pseudonymousOptIn ? Anonymity.Pseudonymous : Anonymity.Disabled; this.setAnonymity(anonymity); if (anonymity === Anonymity.Pseudonymous) { - await this.identifyUser(MatrixClientPeg.get(), PosthogAnalytics.getRandomAnalyticsId); + await this.identifyUser(client, PosthogAnalytics.getRandomAnalyticsId); if (MatrixClientPeg.currentUserIsJustRegistered()) { this.trackNewUserEvent(); } @@ -391,7 +391,7 @@ export class PosthogAnalytics { } } - public startListeningToSettingsChanges(): void { + public startListeningToSettingsChanges(client: MatrixClient): void { // Listen to account data changes from sync so we can observe changes to relevant flags and update. // This is called - // * On page load, when the account data is first received by sync @@ -404,7 +404,7 @@ export class PosthogAnalytics { "pseudonymousAnalyticsOptIn", null, (originalSettingName, changedInRoomId, atLevel, newValueAtLevel, newValue) => { - this.updateAnonymityFromSettings(!!newValue); + this.updateAnonymityFromSettings(client, !!newValue); }, ); } diff --git a/src/RoomNotifs.ts b/src/RoomNotifs.ts index f386f50ada..c484d68f18 100644 --- a/src/RoomNotifs.ts +++ b/src/RoomNotifs.ts @@ -21,7 +21,6 @@ import { ConditionKind, PushRuleActionName, PushRuleKind, TweakName } from "matr import type { IPushRule } from "matrix-js-sdk/src/@types/PushRules"; import type { Room } from "matrix-js-sdk/src/models/room"; import type { MatrixClient } from "matrix-js-sdk/src/matrix"; -import { MatrixClientPeg } from "./MatrixClientPeg"; import { NotificationColor } from "./stores/notifications/NotificationColor"; import { getUnsentMessages } from "./components/structures/RoomStatusBar"; import { doesRoomHaveUnreadMessages, doesRoomOrThreadHaveUnreadMessages } from "./Unread"; @@ -40,7 +39,7 @@ export function getRoomNotifsState(client: MatrixClient, roomId: string): RoomNo // look through the override rules for a rule affecting this room: // if one exists, it will take precedence. - const muteRule = findOverrideMuteRule(roomId); + const muteRule = findOverrideMuteRule(client, roomId); if (muteRule) { return RoomNotifState.Mute; } @@ -70,11 +69,11 @@ export function getRoomNotifsState(client: MatrixClient, roomId: string): RoomNo return null; } -export function setRoomNotifsState(roomId: string, newState: RoomNotifState): Promise { +export function setRoomNotifsState(client: MatrixClient, roomId: string, newState: RoomNotifState): Promise { if (newState === RoomNotifState.Mute) { - return setRoomNotifsStateMuted(roomId); + return setRoomNotifsStateMuted(client, roomId); } else { - return setRoomNotifsStateUnmuted(roomId, newState); + return setRoomNotifsStateUnmuted(client, roomId, newState); } } @@ -91,7 +90,7 @@ export function getUnreadNotificationCount(room: Room, type: NotificationCountTy // Exclude threadId, as the same thread can't continue over a room upgrade if (!threadId && predecessor?.roomId) { const oldRoomId = predecessor.roomId; - const oldRoom = MatrixClientPeg.get().getRoom(oldRoomId); + const oldRoom = room.client.getRoom(oldRoomId); if (oldRoom) { // We only ever care if there's highlights in the old room. No point in // notifying the user for unread messages because they would have extreme @@ -104,8 +103,7 @@ export function getUnreadNotificationCount(room: Room, type: NotificationCountTy return notificationCount; } -function setRoomNotifsStateMuted(roomId: string): Promise { - const cli = MatrixClientPeg.get(); +function setRoomNotifsStateMuted(cli: MatrixClient, roomId: string): Promise { const promises: Promise[] = []; // delete the room rule @@ -135,11 +133,10 @@ function setRoomNotifsStateMuted(roomId: string): Promise { return Promise.all(promises); } -function setRoomNotifsStateUnmuted(roomId: string, newState: RoomNotifState): Promise { - const cli = MatrixClientPeg.get(); +function setRoomNotifsStateUnmuted(cli: MatrixClient, roomId: string, newState: RoomNotifState): Promise { const promises: Promise[] = []; - const overrideMuteRule = findOverrideMuteRule(roomId); + const overrideMuteRule = findOverrideMuteRule(cli, roomId); if (overrideMuteRule) { promises.push(cli.deletePushRule("global", PushRuleKind.Override, overrideMuteRule.rule_id)); } @@ -176,8 +173,7 @@ function setRoomNotifsStateUnmuted(roomId: string, newState: RoomNotifState): Pr return Promise.all(promises); } -function findOverrideMuteRule(roomId: string): IPushRule | null { - const cli = MatrixClientPeg.get(); +function findOverrideMuteRule(cli: MatrixClient | undefined, roomId: string): IPushRule | null { if (!cli?.pushRules?.global?.override) { return null; } diff --git a/src/Rooms.ts b/src/Rooms.ts index b92afbaa7f..e5b38f5ba9 100644 --- a/src/Rooms.ts +++ b/src/Rooms.ts @@ -17,8 +17,8 @@ limitations under the License. import { Room } from "matrix-js-sdk/src/models/room"; import { EventType } from "matrix-js-sdk/src/@types/event"; import { RoomMember } from "matrix-js-sdk/src/models/room-member"; +import { MatrixClient } from "matrix-js-sdk/src/matrix"; -import { MatrixClientPeg } from "./MatrixClientPeg"; import AliasCustomisations from "./customisations/Alias"; /** @@ -52,20 +52,21 @@ export function guessAndSetDMRoom(room: Room, isDirect: boolean): Promise newTarget = null; } - return setDMRoom(room.roomId, newTarget); + return setDMRoom(room.client, room.roomId, newTarget); } /** * Marks or unmarks the given room as being as a DM room. + * @param client the Matrix Client instance of the logged-in user * @param {string} roomId The ID of the room to modify * @param {string | null} userId The user ID of the desired DM room target user or * null to un-mark this room as a DM room * @returns {object} A promise */ -export async function setDMRoom(roomId: string, userId: string | null): Promise { - if (MatrixClientPeg.get().isGuest()) return; +export async function setDMRoom(client: MatrixClient, roomId: string, userId: string | null): Promise { + if (client.isGuest()) return; - const mDirectEvent = MatrixClientPeg.get().getAccountData(EventType.Direct); + const mDirectEvent = client.getAccountData(EventType.Direct); const currentContent = mDirectEvent?.getContent() || {}; const dmRoomMap = new Map(Object.entries(currentContent)); @@ -98,7 +99,7 @@ export async function setDMRoom(roomId: string, userId: string | null): Promise< // prevent unnecessary calls to setAccountData if (!modified) return; - await MatrixClientPeg.get().setAccountData(EventType.Direct, Object.fromEntries(dmRoomMap)); + await client.setAccountData(EventType.Direct, Object.fromEntries(dmRoomMap)); } /** diff --git a/src/ScalarAuthClient.ts b/src/ScalarAuthClient.ts index 23f7f91b2c..ca181ebb8f 100644 --- a/src/ScalarAuthClient.ts +++ b/src/ScalarAuthClient.ts @@ -157,6 +157,7 @@ export default class ScalarAuthClient { const parsedImRestUrl = parseUrl(this.apiUrl); parsedImRestUrl.pathname = ""; return startTermsFlow( + MatrixClientPeg.get(), [new Service(SERVICE_TYPES.IM, parsedImRestUrl.toString(), token)], this.termsInteractionCallback, ).then(() => { diff --git a/src/ScalarMessaging.ts b/src/ScalarMessaging.ts index 08cdbff70e..fa0849bf30 100644 --- a/src/ScalarMessaging.ts +++ b/src/ScalarMessaging.ts @@ -876,7 +876,7 @@ const onMessage = function (event: MessageEvent): void { // No integrations UI URL, ignore silently. return; } - let eventOriginUrl; + let eventOriginUrl: URL; try { eventOriginUrl = new URL(event.origin); } catch (e) { diff --git a/src/Terms.ts b/src/Terms.ts index b986a5e83f..d31b6357f8 100644 --- a/src/Terms.ts +++ b/src/Terms.ts @@ -17,8 +17,8 @@ limitations under the License. import classNames from "classnames"; import { SERVICE_TYPES } from "matrix-js-sdk/src/service-types"; import { logger } from "matrix-js-sdk/src/logger"; +import { MatrixClient } from "matrix-js-sdk/src/matrix"; -import { MatrixClientPeg } from "./MatrixClientPeg"; import Modal from "./Modal"; import TermsDialog from "./components/views/dialogs/TermsDialog"; @@ -66,6 +66,7 @@ export type TermsInteractionCallback = ( /** * Start a flow where the user is presented with terms & conditions for some services * + * @param client The Matrix Client instance of the logged-in user * @param {Service[]} services Object with keys 'serviceType', 'baseUrl', 'accessToken' * @param {function} interactionCallback Function called with: * * an array of { service: {Service}, policies: {terms response from API} } @@ -75,10 +76,11 @@ export type TermsInteractionCallback = ( * if they cancel. */ export async function startTermsFlow( + client: MatrixClient, services: Service[], interactionCallback: TermsInteractionCallback = dialogTermsInteractionCallback, ): Promise { - const termsPromises = services.map((s) => MatrixClientPeg.get().getTerms(s.serviceType, s.baseUrl)); + const termsPromises = services.map((s) => client.getTerms(s.serviceType, s.baseUrl)); /* * a /terms response looks like: @@ -105,7 +107,7 @@ export async function startTermsFlow( }); // fetch the set of agreed policy URLs from account data - const currentAcceptedTerms = await MatrixClientPeg.get().getAccountData("m.accepted_terms"); + const currentAcceptedTerms = await client.getAccountData("m.accepted_terms"); let agreedUrlSet: Set; if (!currentAcceptedTerms || !currentAcceptedTerms.getContent() || !currentAcceptedTerms.getContent().accepted) { agreedUrlSet = new Set(); @@ -152,7 +154,7 @@ export async function startTermsFlow( // We only ever add to the set of URLs, so if anything has changed then we'd see a different length if (agreedUrlSet.size !== numAcceptedBeforeAgreement) { const newAcceptedTerms = { accepted: Array.from(agreedUrlSet) }; - await MatrixClientPeg.get().setAccountData("m.accepted_terms", newAcceptedTerms); + await client.setAccountData("m.accepted_terms", newAcceptedTerms); } const agreePromises = policiesAndServicePairs.map((policiesAndService) => { @@ -171,7 +173,7 @@ export async function startTermsFlow( if (urlsForService.length === 0) return Promise.resolve(); - return MatrixClientPeg.get().agreeToTerms( + return client.agreeToTerms( policiesAndService.service.serviceType, policiesAndService.service.baseUrl, policiesAndService.service.accessToken, diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 786344e281..9c7cd645d6 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -1512,7 +1512,7 @@ export class RoomView extends React.Component { } const dmInviter = room.getDMInviter(); if (dmInviter) { - Rooms.setDMRoom(room.roomId, dmInviter); + Rooms.setDMRoom(room.client, room.roomId, dmInviter); } } diff --git a/src/components/views/context_menus/WidgetContextMenu.tsx b/src/components/views/context_menus/WidgetContextMenu.tsx index e01fd11dc0..8dc6ca62eb 100644 --- a/src/components/views/context_menus/WidgetContextMenu.tsx +++ b/src/components/views/context_menus/WidgetContextMenu.tsx @@ -67,7 +67,7 @@ export const WidgetContextMenu: React.FC = ({ if (roomId && getConfigLivestreamUrl() && WidgetType.JITSI.matches(app.type)) { const onStreamAudioClick = async (): Promise => { try { - await startJitsiAudioLivestream(widgetMessaging!, roomId); + await startJitsiAudioLivestream(cli, widgetMessaging!, roomId); } catch (err) { logger.error("Failed to start livestream", err); // XXX: won't i18n well, but looks like widget api only support 'message'? diff --git a/src/components/views/right_panel/UserInfo.tsx b/src/components/views/right_panel/UserInfo.tsx index dcd447b344..caa1692b20 100644 --- a/src/components/views/right_panel/UserInfo.tsx +++ b/src/components/views/right_panel/UserInfo.tsx @@ -191,7 +191,7 @@ export function DeviceItem({ userId, device }: { userId: string; device: IDevice const onDeviceClick = (): void => { const user = cli.getUser(userId); if (user) { - verifyDevice(user, device); + verifyDevice(cli, user, device); } }; @@ -1446,9 +1446,9 @@ const BasicUserInfo: React.FC<{ className="mx_UserInfo_field mx_UserInfo_verifyButton" onClick={() => { if (hasCrossSigningKeys) { - verifyUser(member as User); + verifyUser(cli, member as User); } else { - legacyVerifyUser(member as User); + legacyVerifyUser(cli, member as User); } }} > diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx index 3e17c8d6b8..13a44fcaa6 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx @@ -61,6 +61,7 @@ import SettingsSubsection, { SettingsSubsectionText } from "../../shared/Setting import { SettingsSubsectionHeading } from "../../shared/SettingsSubsectionHeading"; import Heading from "../../../typography/Heading"; import InlineSpinner from "../../../elements/InlineSpinner"; +import MatrixClientContext from "../../../../../contexts/MatrixClientContext"; interface IProps { closeSettingsFn: () => void; @@ -96,6 +97,9 @@ interface IState { } export default class GeneralUserSettingsTab extends React.Component { + public static contextType = MatrixClientContext; + public context!: React.ContextType; + private readonly dispatcherRef: string; public constructor(props: IProps) { @@ -217,6 +221,7 @@ export default class GeneralUserSettingsTab extends React.Component { return new Promise((resolve, reject) => { diff --git a/src/createRoom.ts b/src/createRoom.ts index 16ca1593ad..6781a66e95 100644 --- a/src/createRoom.ts +++ b/src/createRoom.ts @@ -321,7 +321,7 @@ export default async function createRoom(client: MatrixClient, opts: IOpts): Pro } }); - if (opts.dmUserId) await Rooms.setDMRoom(roomId, opts.dmUserId); + if (opts.dmUserId) await Rooms.setDMRoom(client, roomId, opts.dmUserId); }) .then(() => { if (opts.parentSpace) { diff --git a/src/rageshake/submit-rageshake.ts b/src/rageshake/submit-rageshake.ts index 47b76625cd..afc02037ad 100644 --- a/src/rageshake/submit-rageshake.ts +++ b/src/rageshake/submit-rageshake.ts @@ -189,7 +189,7 @@ async function collectBugReport(opts: IOpts = {}, gzipLogs = true): Promise
=> { - return setRoomNotifsState(this.context.room.roomId, v); + return setRoomNotifsState(this.context.room.client, this.context.room.roomId, v); }, implicitlyReverted, ); diff --git a/src/stores/right-panel/RightPanelStore.ts b/src/stores/right-panel/RightPanelStore.ts index e3e0183d40..a4b37e226c 100644 --- a/src/stores/right-panel/RightPanelStore.ts +++ b/src/stores/right-panel/RightPanelStore.ts @@ -35,6 +35,7 @@ import { ActionPayload } from "../../dispatcher/payloads"; import { Action } from "../../dispatcher/actions"; import { ActiveRoomChangedPayload } from "../../dispatcher/payloads/ActiveRoomChangedPayload"; import { SdkContextClass } from "../../contexts/SDKContext"; +import { MatrixClientPeg } from "../../MatrixClientPeg"; /** * A class for tracking the state of the right panel between layouts and @@ -308,7 +309,9 @@ export default class RightPanelStore extends ReadyWatchingStore { if (card.phase === RightPanelPhases.RoomMemberInfo && card.state) { // RightPanelPhases.RoomMemberInfo -> needs to be changed to RightPanelPhases.EncryptionPanel if there is a pending verification request const { member } = card.state; - const pendingRequest = member ? pendingVerificationRequestForUser(member) : undefined; + const pendingRequest = member + ? pendingVerificationRequestForUser(MatrixClientPeg.get(), member) + : undefined; if (pendingRequest) { return { phase: RightPanelPhases.EncryptionPanel, @@ -341,7 +344,7 @@ export default class RightPanelStore extends ReadyWatchingStore { if (!this.currentCard?.state) return; const { member } = this.currentCard.state; if (!member) return; - const pendingRequest = pendingVerificationRequestForUser(member); + const pendingRequest = pendingVerificationRequestForUser(MatrixClientPeg.get(), member); if (pendingRequest) { this.currentCard.state.verificationRequest = pendingRequest; this.emitAndUpdateSettings(); diff --git a/src/verification.ts b/src/verification.ts index 50456ce3d4..6c6fc8780d 100644 --- a/src/verification.ts +++ b/src/verification.ts @@ -16,11 +16,10 @@ limitations under the License. import { User } from "matrix-js-sdk/src/models/user"; import { verificationMethods as VerificationMethods } from "matrix-js-sdk/src/crypto"; -import { RoomMember } from "matrix-js-sdk/src/matrix"; +import { MatrixClient, RoomMember } from "matrix-js-sdk/src/matrix"; import { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest"; import { CrossSigningKey } from "matrix-js-sdk/src/crypto-api"; -import { MatrixClientPeg } from "./MatrixClientPeg"; import dis from "./dispatcher/dispatcher"; import Modal from "./Modal"; import { RightPanelPhases } from "./stores/right-panel/RightPanelStorePhases"; @@ -32,9 +31,8 @@ import RightPanelStore from "./stores/right-panel/RightPanelStore"; import { IRightPanelCardState } from "./stores/right-panel/RightPanelStoreIPanelState"; import { findDMForUser } from "./utils/dm/findDMForUser"; -async function enable4SIfNeeded(): Promise { - const cli = MatrixClientPeg.get(); - const crypto = cli.getCrypto(); +async function enable4SIfNeeded(matrixClient: MatrixClient): Promise { + const crypto = matrixClient.getCrypto(); if (!crypto) return false; const usk = await crypto.getCrossSigningKeyId(CrossSigningKey.UserSigning); if (!usk) { @@ -45,15 +43,14 @@ async function enable4SIfNeeded(): Promise { return true; } -export async function verifyDevice(user: User, device: IDevice): Promise { - const cli = MatrixClientPeg.get(); - if (cli.isGuest()) { +export async function verifyDevice(matrixClient: MatrixClient, user: User, device: IDevice): Promise { + if (matrixClient.isGuest()) { dis.dispatch({ action: "require_registration" }); return; } // if cross-signing is not explicitly disabled, check if it should be enabled first. - if (cli.getCryptoTrustCrossSignedDevices()) { - if (!(await enable4SIfNeeded())) { + if (matrixClient.getCryptoTrustCrossSignedDevices()) { + if (!(await enable4SIfNeeded(matrixClient))) { return; } } @@ -63,7 +60,7 @@ export async function verifyDevice(user: User, device: IDevice): Promise { device, onFinished: async (action): Promise => { if (action === "sas") { - const verificationRequestPromise = cli.legacyDeviceVerification( + const verificationRequestPromise = matrixClient.legacyDeviceVerification( user.userId, device.deviceId, VerificationMethods.SAS, @@ -79,32 +76,30 @@ export async function verifyDevice(user: User, device: IDevice): Promise { }); } -export async function legacyVerifyUser(user: User): Promise { - const cli = MatrixClientPeg.get(); - if (cli.isGuest()) { +export async function legacyVerifyUser(matrixClient: MatrixClient, user: User): Promise { + if (matrixClient.isGuest()) { dis.dispatch({ action: "require_registration" }); return; } // if cross-signing is not explicitly disabled, check if it should be enabled first. - if (cli.getCryptoTrustCrossSignedDevices()) { - if (!(await enable4SIfNeeded())) { + if (matrixClient.getCryptoTrustCrossSignedDevices()) { + if (!(await enable4SIfNeeded(matrixClient))) { return; } } - const verificationRequestPromise = cli.requestVerification(user.userId); + const verificationRequestPromise = matrixClient.requestVerification(user.userId); setRightPanel({ member: user, verificationRequestPromise }); } -export async function verifyUser(user: User): Promise { - const cli = MatrixClientPeg.get(); - if (cli.isGuest()) { +export async function verifyUser(matrixClient: MatrixClient, user: User): Promise { + if (matrixClient.isGuest()) { dis.dispatch({ action: "require_registration" }); return; } - if (!(await enable4SIfNeeded())) { + if (!(await enable4SIfNeeded(matrixClient))) { return; } - const existingRequest = pendingVerificationRequestForUser(user); + const existingRequest = pendingVerificationRequestForUser(matrixClient, user); setRightPanel({ member: user, verificationRequest: existingRequest }); } @@ -120,10 +115,12 @@ function setRightPanel(state: IRightPanelCardState): void { } } -export function pendingVerificationRequestForUser(user: User | RoomMember): VerificationRequest | undefined { - const cli = MatrixClientPeg.get(); - const dmRoom = findDMForUser(cli, user.userId); +export function pendingVerificationRequestForUser( + matrixClient: MatrixClient, + user: User | RoomMember, +): VerificationRequest | undefined { + const dmRoom = findDMForUser(matrixClient, user.userId); if (dmRoom) { - return cli.findVerificationRequestDMInProgress(dmRoom.roomId); + return matrixClient.findVerificationRequestDMInProgress(dmRoom.roomId); } } diff --git a/test/DeviceListener-test.ts b/test/DeviceListener-test.ts index 34c3d56a5f..f98386d068 100644 --- a/test/DeviceListener-test.ts +++ b/test/DeviceListener-test.ts @@ -59,8 +59,8 @@ const mockDispatcher = mocked(dis); const flushPromises = async () => await new Promise(process.nextTick); describe("DeviceListener", () => { - let mockClient: Mocked | undefined; - let mockCrypto: Mocked | undefined; + let mockClient: Mocked; + let mockCrypto: Mocked; // spy on various toasts' hide and show functions // easier than mocking @@ -111,7 +111,7 @@ describe("DeviceListener", () => { const createAndStart = async (): Promise => { const instance = new DeviceListener(); - instance.start(); + instance.start(mockClient); await flushPromises(); return instance; }; diff --git a/test/Rooms-test.ts b/test/Rooms-test.ts index e37bab7132..b29d577046 100644 --- a/test/Rooms-test.ts +++ b/test/Rooms-test.ts @@ -52,7 +52,7 @@ describe("setDMRoom", () => { describe("when logged in as a guest and marking a room as DM", () => { beforeEach(() => { mocked(client.isGuest).mockReturnValue(true); - setDMRoom(roomId1, userId1); + setDMRoom(client, roomId1, userId1); }); it("should not update the account data", () => { @@ -62,7 +62,7 @@ describe("setDMRoom", () => { describe("when adding a new room to an existing DM relation", () => { beforeEach(() => { - setDMRoom(roomId4, userId1); + setDMRoom(client, roomId4, userId1); }); it("should update the account data accordingly", () => { @@ -75,7 +75,7 @@ describe("setDMRoom", () => { describe("when adding a new DM room", () => { beforeEach(() => { - setDMRoom(roomId4, userId3); + setDMRoom(client, roomId4, userId3); }); it("should update the account data accordingly", () => { @@ -89,7 +89,7 @@ describe("setDMRoom", () => { describe("when trying to add a DM, that already exists", () => { beforeEach(() => { - setDMRoom(roomId1, userId1); + setDMRoom(client, roomId1, userId1); }); it("should not update the account data", () => { @@ -99,7 +99,7 @@ describe("setDMRoom", () => { describe("when removing an existing DM", () => { beforeEach(() => { - setDMRoom(roomId1, null); + setDMRoom(client, roomId1, null); }); it("should update the account data accordingly", () => { @@ -112,7 +112,7 @@ describe("setDMRoom", () => { describe("when removing an unknown room", () => { beforeEach(() => { - setDMRoom(roomId4, null); + setDMRoom(client, roomId4, null); }); it("should not update the account data", () => { @@ -123,7 +123,7 @@ describe("setDMRoom", () => { describe("when the direct event is undefined", () => { beforeEach(() => { mocked(client.getAccountData).mockReturnValue(undefined); - setDMRoom(roomId1, userId1); + setDMRoom(client, roomId1, userId1); }); it("should update the account data accordingly", () => { @@ -139,7 +139,7 @@ describe("setDMRoom", () => { mocked(client.getAccountData).mockReturnValue({ getContent: jest.fn(), }); - setDMRoom(roomId1, userId1); + setDMRoom(client, roomId1, userId1); }); it("should update the account data accordingly", () => { diff --git a/test/Terms-test.tsx b/test/Terms-test.tsx index c04ddd03dd..772bb5da3d 100644 --- a/test/Terms-test.tsx +++ b/test/Terms-test.tsx @@ -66,7 +66,7 @@ describe("Terms", function () { }, }); const interactionCallback = jest.fn().mockResolvedValue([]); - await startTermsFlow([IM_SERVICE_ONE], interactionCallback); + await startTermsFlow(mockClient, [IM_SERVICE_ONE], interactionCallback); expect(interactionCallback).toHaveBeenCalledWith( [ @@ -97,7 +97,7 @@ describe("Terms", function () { mockClient.agreeToTerms; const interactionCallback = jest.fn(); - await startTermsFlow([IM_SERVICE_ONE], interactionCallback); + await startTermsFlow(mockClient, [IM_SERVICE_ONE], interactionCallback); expect(interactionCallback).not.toHaveBeenCalled(); expect(mockClient.agreeToTerms).toHaveBeenCalledWith(SERVICE_TYPES.IM, "https://imone.test", "a token token", [ @@ -122,7 +122,7 @@ describe("Terms", function () { }); const interactionCallback = jest.fn().mockResolvedValue(["http://example.com/one", "http://example.com/two"]); - await startTermsFlow([IM_SERVICE_ONE], interactionCallback); + await startTermsFlow(mockClient, [IM_SERVICE_ONE], interactionCallback); expect(interactionCallback).toHaveBeenCalledWith( [ @@ -168,7 +168,7 @@ describe("Terms", function () { }); const interactionCallback = jest.fn().mockResolvedValue(["http://example.com/one", "http://example.com/two"]); - await startTermsFlow([IM_SERVICE_ONE, IM_SERVICE_TWO], interactionCallback); + await startTermsFlow(mockClient, [IM_SERVICE_ONE, IM_SERVICE_TWO], interactionCallback); expect(interactionCallback).toHaveBeenCalledWith( [ diff --git a/test/components/views/right_panel/UserInfo-test.tsx b/test/components/views/right_panel/UserInfo-test.tsx index 9c8fba88e8..bf1771c515 100644 --- a/test/components/views/right_panel/UserInfo-test.tsx +++ b/test/components/views/right_panel/UserInfo-test.tsx @@ -500,7 +500,7 @@ describe("", () => { await userEvent.click(button); expect(mockVerifyDevice).toHaveBeenCalledTimes(1); - expect(mockVerifyDevice).toHaveBeenCalledWith(defaultUser, device); + expect(mockVerifyDevice).toHaveBeenCalledWith(mockClient, defaultUser, device); }); it("with display name", async () => {