From 402e5dbc414b350f37b02da5d1fc75db2b0c9d31 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 15 May 2024 09:06:08 +0000 Subject: [PATCH 01/73] Upgrade dependency to matrix-js-sdk@32.3.0-rc.0 --- package.json | 2 +- yarn.lock | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 62eca6b3447..0eae092a3fa 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,7 @@ "maplibre-gl": "^2.0.0", "matrix-encrypt-attachment": "^1.0.3", "matrix-events-sdk": "0.0.1", - "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop", + "matrix-js-sdk": "32.3.0-rc.0", "matrix-widget-api": "^1.5.0", "memoize-one": "^6.0.0", "minimist": "^1.2.5", diff --git a/yarn.lock b/yarn.lock index 2ed1cbc0abd..e247a7bbcd4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7065,9 +7065,10 @@ matrix-events-sdk@0.0.1: resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz#c8c38911e2cb29023b0bbac8d6f32e0de2c957dd" integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA== -"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop": - version "32.2.0" - resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/2a716bd076459f48fed967f3eb4158ebdc1f3600" +matrix-js-sdk@32.3.0-rc.0: + version "32.3.0-rc.0" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-32.3.0-rc.0.tgz#31f8281420db91a4a60b5bd4a1336771e466e560" + integrity sha512-WssOMKp7yDjpIBEW/nCVYgzLl5ndpti3ZxvLgg1yetjsFN89HMp7Kbd+3sWYMfkqUvIyTM6i4dDtHsnvYLDZ7Q== dependencies: "@babel/runtime" "^7.12.5" "@matrix-org/matrix-sdk-crypto-wasm" "^4.9.0" From d84bcbc2155a138bcfafc3efa60c4abf7b4a4d14 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 15 May 2024 09:11:36 +0000 Subject: [PATCH 02/73] v3.100.0-rc.0 --- package.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 0eae092a3fa..fe9d24bff89 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "3.99.0", + "version": "3.100.0-rc.0", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { @@ -23,7 +23,7 @@ "package.json", ".stylelintrc.js" ], - "main": "./src/index.ts", + "main": "./lib/index.ts", "matrix_src_main": "./src/index.ts", "matrix_lib_main": "./lib/index.ts", "matrix_lib_typings": "./lib/index.d.ts", @@ -233,5 +233,6 @@ "outputDirectory": "coverage", "outputName": "jest-sonar-report.xml", "relativePaths": true - } + }, + "typings": "./lib/index.d.ts" } From 4f3dcb6bc98804983c8f39f89681c4d81a27d22c Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 21 May 2024 12:27:24 +0000 Subject: [PATCH 03/73] Upgrade dependency to matrix-js-sdk@32.3.0 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index fe9d24bff89..d86b331afd7 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,7 @@ "maplibre-gl": "^2.0.0", "matrix-encrypt-attachment": "^1.0.3", "matrix-events-sdk": "0.0.1", - "matrix-js-sdk": "32.3.0-rc.0", + "matrix-js-sdk": "32.3.0", "matrix-widget-api": "^1.5.0", "memoize-one": "^6.0.0", "minimist": "^1.2.5", diff --git a/yarn.lock b/yarn.lock index e247a7bbcd4..f188e7448fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7065,10 +7065,10 @@ matrix-events-sdk@0.0.1: resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz#c8c38911e2cb29023b0bbac8d6f32e0de2c957dd" integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA== -matrix-js-sdk@32.3.0-rc.0: - version "32.3.0-rc.0" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-32.3.0-rc.0.tgz#31f8281420db91a4a60b5bd4a1336771e466e560" - integrity sha512-WssOMKp7yDjpIBEW/nCVYgzLl5ndpti3ZxvLgg1yetjsFN89HMp7Kbd+3sWYMfkqUvIyTM6i4dDtHsnvYLDZ7Q== +matrix-js-sdk@32.3.0: + version "32.3.0" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-32.3.0.tgz#af4c279de3c684ec03950d21d410a3ccef62cfee" + integrity sha512-C9QvKBf0ZvoNbwhMQT8XgvF5O1+SMA9yJwdYQb95xUv/5ziFme0DSoqGm1AYtZtI6WmMOi112v1PuV75sowqWw== dependencies: "@babel/runtime" "^7.12.5" "@matrix-org/matrix-sdk-crypto-wasm" "^4.9.0" From 0c28d37f4e0c0cc2a9a23b37c1080138ff7c69d4 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 22 May 2024 12:05:45 +0000 Subject: [PATCH 04/73] Upgrade dependency to matrix-js-sdk@32.4.0 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index d86b331afd7..70c78813a67 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,7 @@ "maplibre-gl": "^2.0.0", "matrix-encrypt-attachment": "^1.0.3", "matrix-events-sdk": "0.0.1", - "matrix-js-sdk": "32.3.0", + "matrix-js-sdk": "32.4.0", "matrix-widget-api": "^1.5.0", "memoize-one": "^6.0.0", "minimist": "^1.2.5", diff --git a/yarn.lock b/yarn.lock index f188e7448fc..7e679817521 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7065,10 +7065,10 @@ matrix-events-sdk@0.0.1: resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz#c8c38911e2cb29023b0bbac8d6f32e0de2c957dd" integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA== -matrix-js-sdk@32.3.0: - version "32.3.0" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-32.3.0.tgz#af4c279de3c684ec03950d21d410a3ccef62cfee" - integrity sha512-C9QvKBf0ZvoNbwhMQT8XgvF5O1+SMA9yJwdYQb95xUv/5ziFme0DSoqGm1AYtZtI6WmMOi112v1PuV75sowqWw== +matrix-js-sdk@32.4.0: + version "32.4.0" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-32.4.0.tgz#2a308e158a8dc2ccdfc14ae6c655c4059ea27bbd" + integrity sha512-mzWfF4rJaTFLDfkedqP2jh/i1v5pv6xRHPkAQLn1ytXi72TFKLlKQmjaNUXfQYkmriIYnGYYQwBXQeJgwaT8SQ== dependencies: "@babel/runtime" "^7.12.5" "@matrix-org/matrix-sdk-crypto-wasm" "^4.9.0" From 148a360598dbc13385f9fe8630eb8007744c65f7 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 28 May 2024 08:41:20 +0100 Subject: [PATCH 05/73] Avoid using deprecated exports, fields, and duplicate code (#12555) --- src/MatrixClientPeg.ts | 17 -- src/SecurityManager.ts | 10 +- .../views/dialogs/CreateRoomDialog.tsx | 2 +- .../views/dialogs/ServerOfflineDialog.tsx | 2 +- .../views/dialogs/devtools/ServerInfo.tsx | 2 +- .../views/directory/NetworkDropdown.tsx | 2 +- src/components/views/right_panel/UserInfo.tsx | 4 +- src/hooks/usePublicRoomDirectory.ts | 4 +- src/rageshake/submit-rageshake.ts | 6 +- src/utils/AutoDiscoveryUtils.tsx | 2 +- src/utils/PasswordScorer.ts | 13 +- src/utils/device/dehydration.ts | 4 +- src/utils/dm/createDmLocalRoom.ts | 3 - test/DeviceListener-test.ts | 12 +- test/PosthogAnalytics-test.ts | 3 +- test/TextForEvent-test.ts | 32 ++-- test/components/structures/RoomView-test.tsx | 1 - .../views/dialogs/SpotlightDialog-test.tsx | 2 +- .../CreateSecretStorageDialog-test.tsx | 4 +- .../security/ExportE2eKeysDialog-test.tsx | 4 +- .../security/ImportE2eKeysDialog-test.tsx | 4 +- .../views/right_panel/UserInfo-test.tsx | 28 +-- .../__snapshots__/UserInfo-test.tsx.snap | 173 ++++++++++++++++++ .../components/views/rooms/EventTile-test.tsx | 3 +- .../tabs/user/SessionManagerTab-test.tsx | 4 +- test/createRoom-test.ts | 3 +- test/hooks/usePublicRoomDirectory-test.tsx | 2 +- test/hooks/useUserDirectory-test.tsx | 2 +- test/stores/SetupEncryptionStore-test.ts | 3 +- test/submit-rageshake-test.ts | 4 +- test/test-utils/test-utils.ts | 6 +- test/toasts/UnverifiedSessionToast-test.tsx | 3 +- test/utils/AutoDiscoveryUtils-test.tsx | 2 +- 33 files changed, 258 insertions(+), 108 deletions(-) diff --git a/src/MatrixClientPeg.ts b/src/MatrixClientPeg.ts index d14003dbfac..a1c277fc28b 100644 --- a/src/MatrixClientPeg.ts +++ b/src/MatrixClientPeg.ts @@ -78,15 +78,6 @@ export interface IMatrixClientPeg { */ opts: IStartClientOpts; - /** - * Return the server name of the user's homeserver - * Throws an error if unable to deduce the homeserver name - * (e.g. if the user is not logged in) - * - * @returns {string} The homeserver name, if present. - */ - getHomeserverName(): string; - /** * Get the current MatrixClient, if any */ @@ -384,14 +375,6 @@ class MatrixClientPegClass implements IMatrixClientPeg { logger.log(`MatrixClientPeg: MatrixClient started`); } - public getHomeserverName(): string { - const matches = /^@[^:]+:(.+)$/.exec(this.safeGet().getSafeUserId()); - if (matches === null || matches.length < 1) { - throw new Error("Failed to derive homeserver name from user ID!"); - } - return matches[1]; - } - private namesToRoomName(names: string[], count: number): string | undefined { const countWithoutMe = count - 1; if (!names.length) { diff --git a/src/SecurityManager.ts b/src/SecurityManager.ts index 6d43d83f617..c2254d3dfe0 100644 --- a/src/SecurityManager.ts +++ b/src/SecurityManager.ts @@ -14,13 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { - DeviceVerificationStatus, - ICryptoCallbacks, - MatrixClient, - encodeBase64, - SecretStorage, -} from "matrix-js-sdk/src/matrix"; +import { Crypto, ICryptoCallbacks, MatrixClient, encodeBase64, SecretStorage } from "matrix-js-sdk/src/matrix"; import { deriveKey } from "matrix-js-sdk/src/crypto/key_passphrase"; import { decodeRecoveryKey } from "matrix-js-sdk/src/crypto/recoverykey"; import { logger } from "matrix-js-sdk/src/logger"; @@ -249,7 +243,7 @@ async function onSecretRequested( deviceId: string, requestId: string, name: string, - deviceTrust: DeviceVerificationStatus, + deviceTrust: Crypto.DeviceVerificationStatus, ): Promise { logger.log("onSecretRequested", userId, deviceId, requestId, name, deviceTrust); const client = MatrixClientPeg.safeGet(); diff --git a/src/components/views/dialogs/CreateRoomDialog.tsx b/src/components/views/dialogs/CreateRoomDialog.tsx index a1fdc13f299..fa0eef5086c 100644 --- a/src/components/views/dialogs/CreateRoomDialog.tsx +++ b/src/components/views/dialogs/CreateRoomDialog.tsx @@ -436,7 +436,7 @@ export default class CreateRoomDialog extends React.Component { { timeline = [
{_t("server_offline|empty_timeline")}
]; } - const serverName = MatrixClientPeg.getHomeserverName(); + const serverName = MatrixClientPeg.safeGet().getDomain(); return ( (SdkConfig.getObject("room_directory")?.get("servers") ?? []); removeAll(configServers, homeServer); // configured servers take preference over user-defined ones, if one occurs in both ignore the latter one. diff --git a/src/components/views/right_panel/UserInfo.tsx b/src/components/views/right_panel/UserInfo.tsx index dbc6acb29be..d9839252f90 100644 --- a/src/components/views/right_panel/UserInfo.tsx +++ b/src/components/views/right_panel/UserInfo.tsx @@ -43,7 +43,6 @@ import DMRoomMap from "../../../utils/DMRoomMap"; import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton"; import SdkConfig from "../../../SdkConfig"; import MultiInviter from "../../../utils/MultiInviter"; -import { MatrixClientPeg } from "../../../MatrixClientPeg"; import E2EIcon from "../rooms/E2EIcon"; import { useTypedEventEmitter } from "../../../hooks/useEventEmitter"; import { textualPowerLevel } from "../../../Roles"; @@ -1413,8 +1412,7 @@ const BasicUserInfo: React.FC<{ // We don't need a perfect check here, just something to pass as "probably not our homeserver". If // someone does figure out how to bypass this check the worst that happens is an error. - // FIXME this should be using cli instead of MatrixClientPeg.matrixClient - if (isSynapseAdmin && member.userId.endsWith(`:${MatrixClientPeg.getHomeserverName()}`)) { + if (isSynapseAdmin && member.userId.endsWith(`:${cli.getDomain()}`)) { synapseDeactivateButton = ( => { const opts: IRoomDirectoryOptions = { limit }; - if (config?.roomServer != MatrixClientPeg.getHomeserverName()) { + if (config?.roomServer != MatrixClientPeg.safeGet().getDomain()) { opts.server = config?.roomServer; } @@ -139,7 +139,7 @@ export const usePublicRoomDirectory = (): { return; } - const myHomeserver = MatrixClientPeg.getHomeserverName(); + const myHomeserver = MatrixClientPeg.safeGet().getDomain()!; const lsRoomServer = localStorage.getItem(LAST_SERVER_KEY); const lsInstanceId: string | undefined = localStorage.getItem(LAST_INSTANCE_KEY) ?? undefined; diff --git a/src/rageshake/submit-rageshake.ts b/src/rageshake/submit-rageshake.ts index 5f2a3990ad2..92c2aa3e339 100644 --- a/src/rageshake/submit-rageshake.ts +++ b/src/rageshake/submit-rageshake.ts @@ -17,7 +17,7 @@ limitations under the License. */ import { logger } from "matrix-js-sdk/src/logger"; -import { Method, MatrixClient, CryptoApi } from "matrix-js-sdk/src/matrix"; +import { Method, MatrixClient, Crypto } from "matrix-js-sdk/src/matrix"; import type * as Pako from "pako"; import { MatrixClientPeg } from "../MatrixClientPeg"; @@ -177,7 +177,7 @@ async function collectSynapseSpecific(client: MatrixClient, body: FormData): Pro /** * Collects crypto related information. */ -async function collectCryptoInfo(cryptoApi: CryptoApi, body: FormData): Promise { +async function collectCryptoInfo(cryptoApi: Crypto.CryptoApi, body: FormData): Promise { body.append("crypto_version", cryptoApi.getVersion()); const ownDeviceKeys = await cryptoApi.getOwnDeviceKeys(); @@ -206,7 +206,7 @@ async function collectCryptoInfo(cryptoApi: CryptoApi, body: FormData): Promise< /** * Collects information about secret storage and backup. */ -async function collectRecoveryInfo(client: MatrixClient, cryptoApi: CryptoApi, body: FormData): Promise { +async function collectRecoveryInfo(client: MatrixClient, cryptoApi: Crypto.CryptoApi, body: FormData): Promise { const secretStorage = client.secretStorage; body.append("secret_storage_ready", String(await cryptoApi.isSecretStorageReady())); body.append("secret_storage_key_in_account", String(await secretStorage.hasKey())); diff --git a/src/utils/AutoDiscoveryUtils.tsx b/src/utils/AutoDiscoveryUtils.tsx index c1f2a7a7d08..fb25870c38f 100644 --- a/src/utils/AutoDiscoveryUtils.tsx +++ b/src/utils/AutoDiscoveryUtils.tsx @@ -68,7 +68,7 @@ const mapAutoDiscoveryErrorTranslation = (err: AutoDiscoveryError): TranslationK return _td("auth|autodiscovery_no_well_known"); case AutoDiscoveryError.InvalidJson: return _td("auth|autodiscovery_invalid_json"); - case AutoDiscoveryError.HomeserverTooOld: + case AutoDiscoveryError.UnsupportedHomeserverSpecVersion: return _td("auth|autodiscovery_hs_incompatible"); } }; diff --git a/src/utils/PasswordScorer.ts b/src/utils/PasswordScorer.ts index 19506d84771..ed69be527fe 100644 --- a/src/utils/PasswordScorer.ts +++ b/src/utils/PasswordScorer.ts @@ -20,7 +20,6 @@ import * as zxcvbnEnPackage from "@zxcvbn-ts/language-en"; import { MatrixClient } from "matrix-js-sdk/src/matrix"; import { _t } from "../languageHandler"; -import { MatrixClientPeg } from "../MatrixClientPeg"; import SdkConfig from "../SdkConfig"; zxcvbnOptions.setOptions({ @@ -96,13 +95,13 @@ export function scorePassword( if (matrixClient) { inputs.push(matrixClient.getUserIdLocalpart()!); - } - try { - const domain = MatrixClientPeg.getHomeserverName(); - inputs.push(domain); - } catch { - // This is fine + try { + const domain = matrixClient.getDomain()!; + inputs.push(domain); + } catch { + // This is fine + } } zxcvbnOptions.setTranslations(getTranslations()); diff --git a/src/utils/device/dehydration.ts b/src/utils/device/dehydration.ts index 83297f42a4e..38b7019fe98 100644 --- a/src/utils/device/dehydration.ts +++ b/src/utils/device/dehydration.ts @@ -15,7 +15,7 @@ limitations under the License. */ import { logger } from "matrix-js-sdk/src/logger"; -import { CryptoApi } from "matrix-js-sdk/src/matrix"; +import { Crypto } from "matrix-js-sdk/src/matrix"; import { MatrixClientPeg } from "../../MatrixClientPeg"; @@ -29,7 +29,7 @@ import { MatrixClientPeg } from "../../MatrixClientPeg"; * * Dehydration can currently only be enabled by setting a flag in the .well-known file. */ -async function deviceDehydrationEnabled(crypto: CryptoApi | undefined): Promise { +async function deviceDehydrationEnabled(crypto: Crypto.CryptoApi | undefined): Promise { if (!crypto) { return false; } diff --git a/src/utils/dm/createDmLocalRoom.ts b/src/utils/dm/createDmLocalRoom.ts index 5ffa491bcfc..ffc76550c7b 100644 --- a/src/utils/dm/createDmLocalRoom.ts +++ b/src/utils/dm/createDmLocalRoom.ts @@ -46,7 +46,6 @@ export async function createDmLocalRoom(client: MatrixClient, targets: Member[]) room_version: KNOWN_SAFE_ROOM_VERSION, }, state_key: "", - user_id: userId, sender: userId, room_id: localRoom.roomId, origin_server_ts: Date.now(), @@ -62,7 +61,6 @@ export async function createDmLocalRoom(client: MatrixClient, targets: Member[]) content: { algorithm: MEGOLM_ALGORITHM, }, - user_id: userId, sender: userId, state_key: "", room_id: localRoom.roomId, @@ -80,7 +78,6 @@ export async function createDmLocalRoom(client: MatrixClient, targets: Member[]) membership: KnownMembership.Join, }, state_key: userId, - user_id: userId, sender: userId, room_id: localRoom.roomId, }), diff --git a/test/DeviceListener-test.ts b/test/DeviceListener-test.ts index 7f447d36822..6c1c059cdd6 100644 --- a/test/DeviceListener-test.ts +++ b/test/DeviceListener-test.ts @@ -15,20 +15,12 @@ limitations under the License. */ import { Mocked, mocked } from "jest-mock"; -import { - MatrixEvent, - Room, - MatrixClient, - DeviceVerificationStatus, - CryptoApi, - Device, - ClientStoppedError, -} from "matrix-js-sdk/src/matrix"; +import { MatrixEvent, Room, MatrixClient, Device, ClientStoppedError } from "matrix-js-sdk/src/matrix"; import { logger } from "matrix-js-sdk/src/logger"; import { CryptoEvent } from "matrix-js-sdk/src/crypto"; import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup"; import { CryptoSessionStateChange } from "@matrix-org/analytics-events/types/typescript/CryptoSessionStateChange"; -import { CrossSigningStatus, KeyBackupInfo } from "matrix-js-sdk/src/crypto-api"; +import { CrossSigningStatus, CryptoApi, DeviceVerificationStatus, KeyBackupInfo } from "matrix-js-sdk/src/crypto-api"; import DeviceListener from "../src/DeviceListener"; import { MatrixClientPeg } from "../src/MatrixClientPeg"; diff --git a/test/PosthogAnalytics-test.ts b/test/PosthogAnalytics-test.ts index c131e536d1f..31822e1c0d1 100644 --- a/test/PosthogAnalytics-test.ts +++ b/test/PosthogAnalytics-test.ts @@ -16,7 +16,8 @@ limitations under the License. import { mocked } from "jest-mock"; import { PostHog } from "posthog-js"; -import { CryptoApi, MatrixClient } from "matrix-js-sdk/src/matrix"; +import { MatrixClient } from "matrix-js-sdk/src/matrix"; +import { CryptoApi } from "matrix-js-sdk/src/crypto-api"; import { Anonymity, getRedactedCurrentLocation, IPosthogEvent, PosthogAnalytics } from "../src/PosthogAnalytics"; import SdkConfig from "../src/SdkConfig"; diff --git a/test/TextForEvent-test.ts b/test/TextForEvent-test.ts index ff1e0a06bc9..ce5c4a97f69 100644 --- a/test/TextForEvent-test.ts +++ b/test/TextForEvent-test.ts @@ -49,8 +49,10 @@ function mockPinnedEvent(pinnedMessageIds?: string[], prevPinnedMessageIds?: str content: { pinned: pinnedMessageIds, }, - prev_content: { - pinned: prevPinnedMessageIds, + unsigned: { + prev_content: { + pinned: prevPinnedMessageIds, + }, }, }); } @@ -183,9 +185,11 @@ describe("TextForEvent", () => { users_default: usersDefault, users, }, - prev_content: { - users: prevUsers, - users_default: prevDefault, + unsigned: { + prev_content: { + users: prevUsers, + users_default: prevDefault, + }, }, }); mxEvent.sender = { name: userA.name } as RoomMember; @@ -315,9 +319,11 @@ describe("TextForEvent", () => { alias, alt_aliases: altAliases, }, - prev_content: { - alias: prevAlias, - alt_aliases: prevAltAliases, + unsigned: { + prev_content: { + alias: prevAlias, + alt_aliases: prevAltAliases, + }, }, }); @@ -512,10 +518,12 @@ describe("TextForEvent", () => { avatar_url: "b", displayname: "Bob", }, - prev_content: { - membership: KnownMembership.Join, - avatar_url: "a", - displayname: "Andy", + unsigned: { + prev_content: { + membership: KnownMembership.Join, + avatar_url: "a", + displayname: "Andy", + }, }, state_key: "@a:foo", }), diff --git a/test/components/structures/RoomView-test.tsx b/test/components/structures/RoomView-test.tsx index 31f5c896aec..8624e562468 100644 --- a/test/components/structures/RoomView-test.tsx +++ b/test/components/structures/RoomView-test.tsx @@ -353,7 +353,6 @@ describe("RoomView", () => { content: { algorithm: MEGOLM_ALGORITHM, }, - user_id: cli.getUserId()!, sender: cli.getUserId()!, state_key: "", room_id: localRoom.roomId, diff --git a/test/components/views/dialogs/SpotlightDialog-test.tsx b/test/components/views/dialogs/SpotlightDialog-test.tsx index 5bf1029bc9e..f8fe3c00a7d 100644 --- a/test/components/views/dialogs/SpotlightDialog-test.tsx +++ b/test/components/views/dialogs/SpotlightDialog-test.tsx @@ -82,8 +82,8 @@ function mockClient({ }: MockClientOptions = {}): MatrixClient { stubClient(); const cli = MatrixClientPeg.safeGet(); - MatrixClientPeg.getHomeserverName = jest.fn(() => homeserver); cli.getUserId = jest.fn(() => userId); + cli.getDomain = jest.fn(() => homeserver); cli.getHomeserverUrl = jest.fn(() => homeserver); cli.getThirdpartyProtocols = jest.fn(() => Promise.resolve(thirdPartyProtocols)); cli.publicRooms = jest.fn((options) => { diff --git a/test/components/views/dialogs/security/CreateSecretStorageDialog-test.tsx b/test/components/views/dialogs/security/CreateSecretStorageDialog-test.tsx index 1412074ed9d..06b13f1df7a 100644 --- a/test/components/views/dialogs/security/CreateSecretStorageDialog-test.tsx +++ b/test/components/views/dialogs/security/CreateSecretStorageDialog-test.tsx @@ -18,7 +18,7 @@ import { render, RenderResult, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import React from "react"; import { mocked, MockedObject } from "jest-mock"; -import { CryptoApi, MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix"; +import { Crypto, MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix"; import { defer, IDeferred, sleep } from "matrix-js-sdk/src/utils"; import { BackupTrustInfo, KeyBackupInfo } from "matrix-js-sdk/src/crypto-api"; @@ -35,7 +35,7 @@ import RestoreKeyBackupDialog from "../../../../../src/components/views/dialogs/ describe("CreateSecretStorageDialog", () => { let mockClient: MockedObject; - let mockCrypto: MockedObject; + let mockCrypto: MockedObject; beforeEach(() => { mockClient = getMockClientWithEventEmitter({ diff --git a/test/components/views/dialogs/security/ExportE2eKeysDialog-test.tsx b/test/components/views/dialogs/security/ExportE2eKeysDialog-test.tsx index 0436fb2bf25..c4a5ef1ee1b 100644 --- a/test/components/views/dialogs/security/ExportE2eKeysDialog-test.tsx +++ b/test/components/views/dialogs/security/ExportE2eKeysDialog-test.tsx @@ -17,7 +17,7 @@ limitations under the License. import React from "react"; import { screen, fireEvent, render, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; -import { CryptoApi, IMegolmSessionData } from "matrix-js-sdk/src/matrix"; +import { Crypto, IMegolmSessionData } from "matrix-js-sdk/src/matrix"; import * as MegolmExportEncryption from "../../../../../src/utils/MegolmExportEncryption"; import ExportE2eKeysDialog from "../../../../../src/async-components/views/dialogs/security/ExportE2eKeysDialog"; @@ -70,7 +70,7 @@ describe("ExportE2eKeysDialog", () => { cli.getCrypto = () => { return { exportRoomKeysAsJson, - } as unknown as CryptoApi; + } as unknown as Crypto.CryptoApi; }; // Mock the result of encrypting the sessions. If we don't do this, the diff --git a/test/components/views/dialogs/security/ImportE2eKeysDialog-test.tsx b/test/components/views/dialogs/security/ImportE2eKeysDialog-test.tsx index af7b85b0c2d..f1199660301 100644 --- a/test/components/views/dialogs/security/ImportE2eKeysDialog-test.tsx +++ b/test/components/views/dialogs/security/ImportE2eKeysDialog-test.tsx @@ -17,7 +17,7 @@ limitations under the License. import React from "react"; import { fireEvent, render, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; -import { CryptoApi } from "matrix-js-sdk/src/matrix"; +import { Crypto } from "matrix-js-sdk/src/matrix"; import ImportE2eKeysDialog from "../../../../../src/async-components/views/dialogs/security/ImportE2eKeysDialog"; import * as MegolmExportEncryption from "../../../../../src/utils/MegolmExportEncryption"; @@ -75,7 +75,7 @@ describe("ImportE2eKeysDialog", () => { cli.getCrypto = () => { return { importRoomKeysAsJson, - } as unknown as CryptoApi; + } as unknown as Crypto.CryptoApi; }; // Mock the result of decrypting the sessions, to avoid needing to diff --git a/test/components/views/right_panel/UserInfo-test.tsx b/test/components/views/right_panel/UserInfo-test.tsx index 1c9e375e04b..bc314e9e32a 100644 --- a/test/components/views/right_panel/UserInfo-test.tsx +++ b/test/components/views/right_panel/UserInfo-test.tsx @@ -18,17 +18,7 @@ import React from "react"; import { fireEvent, render, screen, waitFor, cleanup, act, within } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { Mocked, mocked } from "jest-mock"; -import { - Room, - User, - MatrixClient, - RoomMember, - MatrixEvent, - EventType, - CryptoApi, - DeviceVerificationStatus, - Device, -} from "matrix-js-sdk/src/matrix"; +import { Room, User, MatrixClient, RoomMember, MatrixEvent, EventType, Device } from "matrix-js-sdk/src/matrix"; import { KnownMembership } from "matrix-js-sdk/src/types"; import { defer } from "matrix-js-sdk/src/utils"; import { EventEmitter } from "events"; @@ -37,6 +27,8 @@ import { VerificationRequest, VerificationPhase as Phase, VerificationRequestEvent, + CryptoApi, + DeviceVerificationStatus, } from "matrix-js-sdk/src/crypto-api"; import UserInfo, { @@ -157,6 +149,7 @@ beforeEach(() => { isCryptoEnabled: jest.fn(), getUserId: jest.fn(), getSafeUserId: jest.fn(), + getDomain: jest.fn(), on: jest.fn(), off: jest.fn(), isSynapseAdministrator: jest.fn().mockResolvedValue(false), @@ -584,6 +577,19 @@ describe("", () => { expect(within(device2Button).getByText("dehydrated device 2")).toBeInTheDocument(); }); }); + + it("should render a deactivate button for users of the same server if we are a server admin", async () => { + mockClient.isSynapseAdministrator.mockResolvedValue(true); + mockClient.getDomain.mockReturnValue("example.com"); + + const { container } = renderComponent({ + phase: RightPanelPhases.RoomMemberInfo, + room: mockRoom, + }); + + await waitFor(() => expect(screen.getByRole("button", { name: "Deactivate user" })).toBeInTheDocument()); + expect(container).toMatchSnapshot(); + }); }); describe("with an encrypted room", () => { diff --git a/test/components/views/right_panel/__snapshots__/UserInfo-test.tsx.snap b/test/components/views/right_panel/__snapshots__/UserInfo-test.tsx.snap index 95f75eadc88..8989e0c6be6 100644 --- a/test/components/views/right_panel/__snapshots__/UserInfo-test.tsx.snap +++ b/test/components/views/right_panel/__snapshots__/UserInfo-test.tsx.snap @@ -223,3 +223,176 @@ exports[` with crypto enabled renders 1`] = ` `; + +exports[` with crypto enabled should render a deactivate button for users of the same server if we are a server admin 1`] = ` +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+

+ + @user:example.com + +

+
+
+ customUserIdentifier +
+
+
+ Unknown +
+
+
+
+
+

+ Security +

+

+ Messages in this room are not end-to-end encrypted. +

+
+ +
+
+
+
+ +
+
+
+

+ Options +

+
+ + + +
+
+
+

+ Admin Tools +

+
+ +
+
+
+
+
+`; diff --git a/test/components/views/rooms/EventTile-test.tsx b/test/components/views/rooms/EventTile-test.tsx index cb4cbc56f0f..9697e29671d 100644 --- a/test/components/views/rooms/EventTile-test.tsx +++ b/test/components/views/rooms/EventTile-test.tsx @@ -18,7 +18,6 @@ import * as React from "react"; import { act, fireEvent, render, screen, waitFor } from "@testing-library/react"; import { mocked } from "jest-mock"; import { - CryptoApi, EventType, IEventDecryptionResult, MatrixClient, @@ -28,7 +27,7 @@ import { Room, TweakName, } from "matrix-js-sdk/src/matrix"; -import { EventEncryptionInfo, EventShieldColour, EventShieldReason } from "matrix-js-sdk/src/crypto-api"; +import { CryptoApi, EventEncryptionInfo, EventShieldColour, EventShieldReason } from "matrix-js-sdk/src/crypto-api"; import { mkEncryptedMatrixEvent } from "matrix-js-sdk/src/testing"; import EventTile, { EventTileProps } from "../../../../src/components/views/rooms/EventTile"; diff --git a/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx b/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx index c34b1159455..b31e5cb0d8a 100644 --- a/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx +++ b/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx @@ -18,7 +18,7 @@ import React from "react"; import { act, fireEvent, render, RenderResult, screen } from "@testing-library/react"; import { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo"; import { logger } from "matrix-js-sdk/src/logger"; -import { VerificationRequest } from "matrix-js-sdk/src/crypto-api"; +import { CryptoApi, DeviceVerificationStatus, VerificationRequest } from "matrix-js-sdk/src/crypto-api"; import { defer, sleep } from "matrix-js-sdk/src/utils"; import { ClientEvent, @@ -30,8 +30,6 @@ import { PUSHER_ENABLED, IAuthData, GET_LOGIN_TOKEN_CAPABILITY, - CryptoApi, - DeviceVerificationStatus, MatrixError, MatrixClient, } from "matrix-js-sdk/src/matrix"; diff --git a/test/createRoom-test.ts b/test/createRoom-test.ts index eeca9532698..8fcf1df586a 100644 --- a/test/createRoom-test.ts +++ b/test/createRoom-test.ts @@ -15,7 +15,8 @@ limitations under the License. */ import { mocked, Mocked } from "jest-mock"; -import { CryptoApi, MatrixClient, Device, Preset, RoomType } from "matrix-js-sdk/src/matrix"; +import { MatrixClient, Device, Preset, RoomType } from "matrix-js-sdk/src/matrix"; +import { CryptoApi } from "matrix-js-sdk/src/crypto-api"; // eslint-disable-next-line no-restricted-imports import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; diff --git a/test/hooks/usePublicRoomDirectory-test.tsx b/test/hooks/usePublicRoomDirectory-test.tsx index c313e54d9b9..fe814162085 100644 --- a/test/hooks/usePublicRoomDirectory-test.tsx +++ b/test/hooks/usePublicRoomDirectory-test.tsx @@ -33,7 +33,7 @@ describe("usePublicRoomDirectory", () => { stubClient(); cli = MatrixClientPeg.safeGet(); - MatrixClientPeg.getHomeserverName = () => "matrix.org"; + cli.getDomain = () => "matrix.org"; cli.getThirdpartyProtocols = () => Promise.resolve({}); cli.publicRooms = ({ filter }: IRoomDirectoryOptions) => { const chunk = filter?.generic_search_term diff --git a/test/hooks/useUserDirectory-test.tsx b/test/hooks/useUserDirectory-test.tsx index 8d2906f349b..7bda5cfe0b3 100644 --- a/test/hooks/useUserDirectory-test.tsx +++ b/test/hooks/useUserDirectory-test.tsx @@ -33,7 +33,7 @@ describe("useUserDirectory", () => { stubClient(); cli = MatrixClientPeg.safeGet(); - MatrixClientPeg.getHomeserverName = () => "matrix.org"; + cli.getDomain = () => "matrix.org"; cli.getThirdpartyProtocols = () => Promise.resolve({}); cli.searchUserDirectory = ({ term: query }) => Promise.resolve({ diff --git a/test/stores/SetupEncryptionStore-test.ts b/test/stores/SetupEncryptionStore-test.ts index d220d7db9fa..8186e5e2649 100644 --- a/test/stores/SetupEncryptionStore-test.ts +++ b/test/stores/SetupEncryptionStore-test.ts @@ -16,9 +16,10 @@ limitations under the License. import { mocked, Mocked } from "jest-mock"; import { IBootstrapCrossSigningOpts } from "matrix-js-sdk/src/crypto"; -import { CryptoApi, DeviceVerificationStatus, MatrixClient, Device } from "matrix-js-sdk/src/matrix"; +import { MatrixClient, Device } from "matrix-js-sdk/src/matrix"; import { SecretStorageKeyDescriptionAesV1, ServerSideSecretStorage } from "matrix-js-sdk/src/secret-storage"; import { IDehydratedDevice } from "matrix-js-sdk/src/crypto/dehydration"; +import { CryptoApi, DeviceVerificationStatus } from "matrix-js-sdk/src/crypto-api"; import { SdkContextClass } from "../../src/contexts/SDKContext"; import { accessSecretStorage } from "../../src/SecurityManager"; diff --git a/test/submit-rageshake-test.ts b/test/submit-rageshake-test.ts index b2496c1d30c..3a6e15aaedb 100644 --- a/test/submit-rageshake-test.ts +++ b/test/submit-rageshake-test.ts @@ -27,7 +27,6 @@ import fetchMock from "fetch-mock-jest"; import { getMockClientWithEventEmitter, mockClientMethodsCrypto, mockPlatformPeg } from "./test-utils"; import { collectBugReport } from "../src/rageshake/submit-rageshake"; -import { MatrixClientPeg } from "../src/MatrixClientPeg"; import SettingsStore from "../src/settings/SettingsStore"; import { ConsoleLogger } from "../src/rageshake/rageshake"; @@ -45,13 +44,12 @@ describe("Rageshakes", () => { ); beforeEach(() => { - jest.spyOn(MatrixClientPeg, "getHomeserverName").mockReturnValue("alice-server.com"); - mockClient = getMockClientWithEventEmitter({ credentials: { userId: "@test:example.com" }, deviceId: "AAAAAAAAAA", baseUrl: "https://alice-server.com", getHomeserverUrl: jest.fn().mockReturnValue("https://alice-server.com"), + getDomain: jest.fn().mockReturnValue("alice-server.com"), ...mockClientMethodsCrypto(), http: mockHttpAPI, }); diff --git a/test/test-utils/test-utils.ts b/test/test-utils/test-utils.ts index 0759f2e739d..a9e61e3f365 100644 --- a/test/test-utils/test-utils.ts +++ b/test/test-utils/test-utils.ts @@ -364,10 +364,12 @@ export function mkEvent(opts: MakeEventProps): MatrixEvent { room_id: opts.room, sender: opts.user, content: opts.content, - prev_content: opts.prev_content, event_id: opts.id ?? "$" + Math.random() + "-" + Math.random(), origin_server_ts: opts.ts ?? 0, - unsigned: opts.unsigned, + unsigned: { + ...opts.unsigned, + prev_content: opts.prev_content, + }, redacts: opts.redacts, }; if (opts.skey !== undefined) { diff --git a/test/toasts/UnverifiedSessionToast-test.tsx b/test/toasts/UnverifiedSessionToast-test.tsx index 2799f0f7916..1c282f9693b 100644 --- a/test/toasts/UnverifiedSessionToast-test.tsx +++ b/test/toasts/UnverifiedSessionToast-test.tsx @@ -18,8 +18,9 @@ import React from "react"; import { render, RenderResult, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { mocked, Mocked } from "jest-mock"; -import { CryptoApi, DeviceVerificationStatus, IMyDevice, MatrixClient } from "matrix-js-sdk/src/matrix"; +import { IMyDevice, MatrixClient } from "matrix-js-sdk/src/matrix"; import { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo"; +import { CryptoApi, DeviceVerificationStatus } from "matrix-js-sdk/src/crypto-api"; import dis from "../../src/dispatcher/dispatcher"; import { showToast } from "../../src/toasts/UnverifiedSessionToast"; diff --git a/test/utils/AutoDiscoveryUtils-test.tsx b/test/utils/AutoDiscoveryUtils-test.tsx index 452287225be..f78c166d9a1 100644 --- a/test/utils/AutoDiscoveryUtils-test.tsx +++ b/test/utils/AutoDiscoveryUtils-test.tsx @@ -218,7 +218,7 @@ describe("AutoDiscoveryUtils", () => { ...validIsConfig, "m.homeserver": { state: AutoDiscoveryAction.FAIL_ERROR, - error: AutoDiscovery.ERROR_HOMESERVER_TOO_OLD, + error: AutoDiscovery.ERROR_UNSUPPORTED_HOMESERVER_SPEC_VERSION, base_url: "https://matrix.org", }, }; From e8bb2419c9db3330ef4712fd6049e1adb075bb84 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 28 May 2024 11:59:31 +0100 Subject: [PATCH 06/73] Fix tabbedview breakpoint width (#12556) Which should be 1024 according to the designs, not 768 --- res/css/structures/_TabbedView.pcss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/structures/_TabbedView.pcss b/res/css/structures/_TabbedView.pcss index 34a1766c19d..04f0587b0a8 100644 --- a/res/css/structures/_TabbedView.pcss +++ b/res/css/structures/_TabbedView.pcss @@ -167,7 +167,7 @@ limitations under the License. } /* Hide the labels on tabs, showing only the icons, on narrow viewports. */ -@media (max-width: 768px) { +@media (max-width: 1024px) { .mx_TabbedView_tabsOnLeft.mx_TabbedView_responsive { .mx_TabbedView_tabLabel_text { display: none; From 17ab52294223d4c75d8a8f5f95476f64b28c2603 Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Tue, 28 May 2024 14:55:47 +0200 Subject: [PATCH 07/73] Tooltip: close field tooltip when ESC is pressed (#12553) * Close field tooltip when ESC is pressed * Use `Key.ESCAPE` --- src/components/views/elements/Field.tsx | 24 +++++++++++++++++-- test/components/views/elements/Field-test.tsx | 12 ++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/components/views/elements/Field.tsx b/src/components/views/elements/Field.tsx index f76b945712b..d60af8025d9 100644 --- a/src/components/views/elements/Field.tsx +++ b/src/components/views/elements/Field.tsx @@ -14,12 +14,20 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { InputHTMLAttributes, SelectHTMLAttributes, TextareaHTMLAttributes, RefObject, createRef } from "react"; +import React, { + InputHTMLAttributes, + SelectHTMLAttributes, + TextareaHTMLAttributes, + RefObject, + createRef, + KeyboardEvent, +} from "react"; import classNames from "classnames"; import { debounce } from "lodash"; import { IFieldState, IValidationResult } from "./Validation"; import Tooltip from "./Tooltip"; +import { Key } from "../../../Keyboard"; // Invoke validation from user input (when typing, etc.) at most once every N ms. const VALIDATION_THROTTLE_MS = 200; @@ -232,6 +240,18 @@ export default class Field extends React.PureComponent { return this.props.inputRef ?? this._inputRef; } + private onKeyDown = (evt: KeyboardEvent): void => { + // If the tooltip is displayed to show a feedback and Escape is pressed + // The tooltip is hided + if (this.state.feedbackVisible && evt.key === Key.ESCAPE) { + evt.preventDefault(); + evt.stopPropagation(); + this.setState({ + feedbackVisible: false, + }); + } + }; + public render(): React.ReactNode { /* eslint @typescript-eslint/no-unused-vars: ["error", { "ignoreRestSiblings": true }] */ const { @@ -318,7 +338,7 @@ export default class Field extends React.PureComponent { }); return ( -
+
{prefixContainer} {fieldInput} diff --git a/test/components/views/elements/Field-test.tsx b/test/components/views/elements/Field-test.tsx index ce826282aca..7cb3074927a 100644 --- a/test/components/views/elements/Field-test.tsx +++ b/test/components/views/elements/Field-test.tsx @@ -69,6 +69,10 @@ describe("Field", () => { // Expect 'alert' role expect(screen.queryByRole("alert")).toBeInTheDocument(); + + // Close the feedback is Escape is pressed + fireEvent.keyDown(screen.getByRole("textbox"), { key: "Escape" }); + expect(screen.queryByRole("alert")).toBeNull(); }); it("Should mark the feedback as status if valid", async () => { @@ -87,6 +91,10 @@ describe("Field", () => { // Expect 'status' role expect(screen.queryByRole("status")).toBeInTheDocument(); + + // Close the feedback is Escape is pressed + fireEvent.keyDown(screen.getByRole("textbox"), { key: "Escape" }); + expect(screen.queryByRole("status")).toBeNull(); }); it("Should mark the feedback as tooltip if custom tooltip set", async () => { @@ -106,6 +114,10 @@ describe("Field", () => { // Expect 'tooltip' role expect(screen.queryByRole("tooltip")).toBeInTheDocument(); + + // Close the feedback is Escape is pressed + fireEvent.keyDown(screen.getByRole("textbox"), { key: "Escape" }); + expect(screen.queryByRole("tooltip")).toBeNull(); }); }); }); From 679b170bc5ae5eeb9c04c47fc5eeb663cb45b30c Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Wed, 29 May 2024 09:22:50 +0200 Subject: [PATCH 08/73] Close the release announcement when a dialog is opened (#12559) * Fire `ModalManagerEvent.Closed` when a dialog is closed * Listen to modal events in the RA * Fix first RA test --- src/Modal.tsx | 11 +++++++ src/hooks/useIsReleaseAnnouncementOpen.ts | 21 ++++++++++-- src/stores/ReleaseAnnouncementStore.ts | 4 ++- .../structures/ReleaseAnnouncement-test.tsx | 32 +++++++++++++++++-- 4 files changed, 63 insertions(+), 5 deletions(-) diff --git a/src/Modal.tsx b/src/Modal.tsx index 2ac12d280f6..f39372d532a 100644 --- a/src/Modal.tsx +++ b/src/Modal.tsx @@ -65,10 +65,12 @@ interface IOptions { export enum ModalManagerEvent { Opened = "opened", + Closed = "closed", } type HandlerMap = { [ModalManagerEvent.Opened]: () => void; + [ModalManagerEvent.Closed]: () => void; }; export class ModalManager extends TypedEventEmitter { @@ -232,6 +234,7 @@ export class ModalManager extends TypedEventEmitter { const modal = this.getCurrentModal(); if (!modal) { diff --git a/src/hooks/useIsReleaseAnnouncementOpen.ts b/src/hooks/useIsReleaseAnnouncementOpen.ts index ab8bf07c5e9..f48df3fccfe 100644 --- a/src/hooks/useIsReleaseAnnouncementOpen.ts +++ b/src/hooks/useIsReleaseAnnouncementOpen.ts @@ -16,17 +16,34 @@ * / */ -import { useTypedEventEmitterState } from "./useEventEmitter"; +import { useState } from "react"; + +import { useTypedEventEmitter, useTypedEventEmitterState } from "./useEventEmitter"; import { Feature, ReleaseAnnouncementStore } from "../stores/ReleaseAnnouncementStore"; +import Modal, { ModalManagerEvent } from "../Modal"; + +/** + * Hook to return true if a modal is opened + */ +function useModalOpened(): boolean { + const [opened, setOpened] = useState(false); + useTypedEventEmitter(Modal, ModalManagerEvent.Opened, () => setOpened(true)); + // Modal can be stacked, we need to check if all dialogs are closed + useTypedEventEmitter(Modal, ModalManagerEvent.Closed, () => !Modal.hasDialogs() && setOpened(false)); + return opened; +} /** * Return true if the release announcement of the given feature is enabled * @param feature */ export function useIsReleaseAnnouncementOpen(feature: Feature): boolean { - return useTypedEventEmitterState( + const modalOpened = useModalOpened(); + const releaseAnnouncementOpened = useTypedEventEmitterState( ReleaseAnnouncementStore.instance, "releaseAnnouncementChanged", () => ReleaseAnnouncementStore.instance.getReleaseAnnouncement() === feature, ); + + return !modalOpened && releaseAnnouncementOpened; } diff --git a/src/stores/ReleaseAnnouncementStore.ts b/src/stores/ReleaseAnnouncementStore.ts index 9beeed4f700..604c13fc496 100644 --- a/src/stores/ReleaseAnnouncementStore.ts +++ b/src/stores/ReleaseAnnouncementStore.ts @@ -18,6 +18,7 @@ import { TypedEventEmitter } from "matrix-js-sdk/src/matrix"; import { logger } from "matrix-js-sdk/src/logger"; +import { cloneDeep } from "lodash"; import SettingsStore from "../settings/SettingsStore"; import { SettingLevel } from "../settings/SettingLevel"; @@ -90,7 +91,8 @@ export class ReleaseAnnouncementStore extends TypedEventEmitter("releaseAnnouncementData"); + // Clone the settings to avoid to mutate the internal stored value in the SettingsStore + return cloneDeep(SettingsStore.getValue("releaseAnnouncementData")); } /** diff --git a/test/components/structures/ReleaseAnnouncement-test.tsx b/test/components/structures/ReleaseAnnouncement-test.tsx index 3477e54d4b5..799aa017e9c 100644 --- a/test/components/structures/ReleaseAnnouncement-test.tsx +++ b/test/components/structures/ReleaseAnnouncement-test.tsx @@ -17,11 +17,19 @@ */ import React from "react"; -import { render, screen, waitFor } from "@testing-library/react"; +import { act, render, screen, waitFor } from "@testing-library/react"; import { ReleaseAnnouncement } from "../../../src/components/structures/ReleaseAnnouncement"; +import Modal, { ModalManagerEvent } from "../../../src/Modal"; +import { ReleaseAnnouncementStore } from "../../../src/stores/ReleaseAnnouncementStore"; describe("ReleaseAnnouncement", () => { + beforeEach(async () => { + // Reset the singleton instance of the ReleaseAnnouncementStore + // @ts-ignore + ReleaseAnnouncementStore.internalInstance = new ReleaseAnnouncementStore(); + }); + function renderReleaseAnnouncement() { return render( { renderReleaseAnnouncement(); // The release announcement is displayed - expect(screen.queryByRole("dialog", { name: "header" })).toBeDefined(); + expect(screen.queryByRole("dialog", { name: "header" })).toBeVisible(); // Click on the close button in the release announcement screen.getByRole("button", { name: "close" }).click(); // The release announcement should be hidden after the close button is clicked await waitFor(() => expect(screen.queryByRole("dialog", { name: "header" })).toBeNull()); }); + + test("when a dialog is opened, the release announcement should not be displayed", async () => { + renderReleaseAnnouncement(); + // The release announcement is displayed + expect(screen.queryByRole("dialog", { name: "header" })).toBeVisible(); + + // Open a dialog + act(() => { + Modal.emit(ModalManagerEvent.Opened); + }); + // The release announcement should be hidden after the dialog is opened + expect(screen.queryByRole("dialog", { name: "header" })).toBeNull(); + + // Close the dialog + act(() => { + Modal.emit(ModalManagerEvent.Closed); + }); + // The release announcement should be displayed after the dialog is closed + expect(screen.queryByRole("dialog", { name: "header" })).toBeVisible(); + }); }); From 2e1c1d15fc9b0e54f635f03bf90ea6b7723f9965 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 29 May 2024 12:56:19 +0000 Subject: [PATCH 09/73] Upgrade dependency to matrix-js-sdk@33.0.0-rc.0 --- package.json | 2 +- yarn.lock | 49 +++++++++++++++++++++++++++++++++++++------------ 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 0d17674bfe5..a8ee1040d3d 100644 --- a/package.json +++ b/package.json @@ -110,7 +110,7 @@ "maplibre-gl": "^2.0.0", "matrix-encrypt-attachment": "^1.0.3", "matrix-events-sdk": "0.0.1", - "matrix-js-sdk": "32.4.0", + "matrix-js-sdk": "33.0.0-rc.0", "matrix-widget-api": "^1.5.0", "memoize-one": "^6.0.0", "minimist": "^1.2.5", diff --git a/yarn.lock b/yarn.lock index 0f3e059c9a4..a81a49fad8c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1912,10 +1912,10 @@ emojibase "^15.0.0" emojibase-data "^15.0.0" -"@matrix-org/matrix-sdk-crypto-wasm@^4.9.0": - version "4.9.0" - resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-4.9.0.tgz#9dfed83e33f760650596c4e5c520e5e4c53355d2" - integrity sha512-/bgA4QfE7qkK6GFr9hnhjAvRSebGrmEJxukU0ukbudZcYvbzymoBBM8j3HeULXZT8kbw8WH6z63txYTMCBSDOA== +"@matrix-org/matrix-sdk-crypto-wasm@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-5.0.0.tgz#f45a7bccaad218c05bcf9e7c8ca783c9d9a07af4" + integrity sha512-37ASjCKSTU5ycGfkP+LUXG4Ok6OAf6vE+1qU6uwWhe6FwadCS3vVWzJYd/3d9BQFwsx4GhFTIAXrW4iLG85rmQ== "@matrix-org/matrix-wysiwyg@2.17.0": version "2.17.0" @@ -7151,13 +7151,13 @@ matrix-events-sdk@0.0.1: resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz#c8c38911e2cb29023b0bbac8d6f32e0de2c957dd" integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA== -matrix-js-sdk@32.4.0: - version "32.4.0" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-32.4.0.tgz#2a308e158a8dc2ccdfc14ae6c655c4059ea27bbd" - integrity sha512-mzWfF4rJaTFLDfkedqP2jh/i1v5pv6xRHPkAQLn1ytXi72TFKLlKQmjaNUXfQYkmriIYnGYYQwBXQeJgwaT8SQ== +matrix-js-sdk@33.0.0-rc.0: + version "33.0.0-rc.0" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-33.0.0-rc.0.tgz#f4d5d2148669edeadc870245850c17a88c826251" + integrity sha512-QpciVF6ZOYgTjnR+Og0LvVivGfSube4vmF9qGcdRvqmwc3xK0w5pFW2D8y/pZdzQ8gYfD/LGIG9GkbtW3TrYKA== dependencies: "@babel/runtime" "^7.12.5" - "@matrix-org/matrix-sdk-crypto-wasm" "^4.9.0" + "@matrix-org/matrix-sdk-crypto-wasm" "^5.0.0" another-json "^0.2.0" bs58 "^5.0.0" content-type "^1.0.4" @@ -8790,7 +8790,16 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -8884,7 +8893,14 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -9677,7 +9693,7 @@ which@^2.0.1: dependencies: isexe "^2.0.0" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -9695,6 +9711,15 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From 7f49a3009e0440152d7532352fe3070d13e367b5 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 29 May 2024 13:06:23 +0000 Subject: [PATCH 10/73] v3.100.0-rc.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a8ee1040d3d..57f67573933 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "3.100.0-rc.0", + "version": "3.100.0-rc.1", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { From aec15a6ac18f381b5a3b761b87a90f9ec700b946 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 30 May 2024 11:34:34 +0100 Subject: [PATCH 11/73] Pin playwright synapse docker image to 2 days ago (#12571) * Pin playwright synapse docker image to 2 days ago Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Automatically update Synapse docker image using GHA PRs Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .../workflows/playwright-image-updates.yaml | 45 +++++++++++++++++++ .../plugins/homeserver/synapse/index.ts | 7 ++- 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/playwright-image-updates.yaml diff --git a/.github/workflows/playwright-image-updates.yaml b/.github/workflows/playwright-image-updates.yaml new file mode 100644 index 00000000000..15bea28e0f9 --- /dev/null +++ b/.github/workflows/playwright-image-updates.yaml @@ -0,0 +1,45 @@ +name: Update Playwright docker images +on: + workflow_dispatch: {} + schedule: + - cron: "0 6 * * *" # Every day at 6am UTC +jobs: + update: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Update matrixdotorg/synapse image + run: | + docker pull "$IMAGE" + INSPECT=$(docker inspect --format='{{index .RepoDigests 0}}' "$IMAGE") + DIGEST=${INSPECT#*@} + sed -i "s/const DOCKER_TAG.*/const DOCKER_TAG = \"develop@$DIGEST\";/" playwright/plugins/homeserver/synapse/index.ts + env: + IMAGE: matrixdotorg/synapse:develop + + - name: Create Pull Request + id: cpr + uses: peter-evans/create-pull-request@153407881ec5c347639a548ade7d8ad1d6740e38 # v5 + with: + token: ${{ secrets.ELEMENT_BOT_TOKEN }} + branch: actions/playwright-image-updates + delete-branch: true + title: Playwright Docker image updates + labels: | + T-Task + + - name: Enable automerge + run: gh pr merge --merge --auto "$PR_NUMBER" + if: steps.cpr.outputs.pull-request-operation == 'created' + env: + GH_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} + PR_NUMBER: ${{ steps.cpr.outputs.pull-request-number }} + + - name: Enable autoapprove + run: | + gh pr review --approve "$PR_NUMBER" + if: steps.cpr.outputs.pull-request-operation == 'created' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ steps.cpr.outputs.pull-request-number }} diff --git a/playwright/plugins/homeserver/synapse/index.ts b/playwright/plugins/homeserver/synapse/index.ts index c11f937cf3e..968968a8a66 100644 --- a/playwright/plugins/homeserver/synapse/index.ts +++ b/playwright/plugins/homeserver/synapse/index.ts @@ -25,6 +25,11 @@ import { Docker } from "../../docker"; import { HomeserverConfig, HomeserverInstance, Homeserver, StartHomeserverOpts, Credentials } from ".."; import { randB64Bytes } from "../../utils/rand"; +// Docker tag to use for `matrixdotorg/synapse` image. +// We target a specific digest as every now and then a Synapse update will break our CI. +// This digest is updated by the playwright-image-updates.yaml workflow periodically. +const DOCKER_TAG = "develop@sha256:b41149e8bacb8c3b22b06bba186402a64b1810c92e70ed5518735f0e57ed8f06"; + async function cfgDirFromTemplate(opts: StartHomeserverOpts): Promise> { const templateDir = path.join(__dirname, "templates", opts.template); @@ -103,7 +108,7 @@ export class Synapse implements Homeserver, HomeserverInstance { console.log(`Starting synapse with config dir ${synCfg.configDir}...`); const dockerSynapseParams = ["-v", `${synCfg.configDir}:/data`, "-p", `${synCfg.port}:8008/tcp`]; const synapseId = await this.docker.run({ - image: "matrixdotorg/synapse:develop", + image: `matrixdotorg/synapse:${DOCKER_TAG}`, containerName: `react-sdk-playwright-synapse`, params: dockerSynapseParams, cmd: ["run"], From 18edb2e8117b41e4ebeeb884ac66d4da42997ff6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 30 May 2024 15:47:57 +0000 Subject: [PATCH 12/73] Update all non-major dependencies (#12560) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 4 +-- playwright/Dockerfile | 2 +- yarn.lock | 68 ++++++++++++++++++++++++++++--------------- 3 files changed, 47 insertions(+), 27 deletions(-) diff --git a/package.json b/package.json index 8509bda0bb2..00c1cc2e4b3 100644 --- a/package.json +++ b/package.json @@ -118,7 +118,7 @@ "opus-recorder": "^8.0.3", "pako": "^2.0.3", "png-chunks-extract": "^1.0.0", - "posthog-js": "1.131.4", + "posthog-js": "1.135.2", "proposal-temporal": "^0.9.0", "qrcode": "1.5.3", "re-resizable": "^6.9.0", @@ -196,7 +196,7 @@ "eslint": "8.57.0", "eslint-config-google": "^0.14.0", "eslint-config-prettier": "^9.0.0", - "eslint-plugin-deprecate": "0.8.4", + "eslint-plugin-deprecate": "0.8.5", "eslint-plugin-import": "^2.25.4", "eslint-plugin-jest": "^28.0.0", "eslint-plugin-jsx-a11y": "^6.5.1", diff --git a/playwright/Dockerfile b/playwright/Dockerfile index e93826b52f1..7179e08ab0b 100644 --- a/playwright/Dockerfile +++ b/playwright/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/playwright:v1.44.0-jammy +FROM mcr.microsoft.com/playwright:v1.44.1-jammy WORKDIR /work/matrix-react-sdk VOLUME ["/work/element-web/node_modules"] diff --git a/yarn.lock b/yarn.lock index fa5498e9e9b..bf2d55683eb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -33,11 +33,11 @@ "@jridgewell/trace-mapping" "^0.3.24" "@axe-core/playwright@^4.8.1": - version "4.9.0" - resolved "https://registry.yarnpkg.com/@axe-core/playwright/-/playwright-4.9.0.tgz#3538c5af8868f1704eedeb58a6b5a84feb0ccfe1" - integrity sha512-Q1Lz75dNsX38jof+aev7RficDMdH/HLOLySkDdXR0fUoeFcLdw4UNgDO2CNNP4CTpoesEdfYRdd6VmDXjhBgbA== + version "4.9.1" + resolved "https://registry.yarnpkg.com/@axe-core/playwright/-/playwright-4.9.1.tgz#9c17c2976464d31d96d310c56df8ba100d87d772" + integrity sha512-8m4WZbZq7/aq7ZY5IG8GqV+ZdvtGn/iJdom+wBg+iv/3BAOBIfNQtIu697a41438DzEEyptXWmC3Xl5Kx/o9/g== dependencies: - axe-core "~4.9.0" + axe-core "~4.9.1" "@babel/cli@^7.12.10": version "7.24.5" @@ -3478,7 +3478,7 @@ await-lock@^2.1.0: resolved "https://registry.yarnpkg.com/await-lock/-/await-lock-2.2.2.tgz#a95a9b269bfd2f69d22b17a321686f551152bcef" integrity sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw== -axe-core@4.9.1: +axe-core@4.9.1, axe-core@~4.9.1: version "4.9.1" resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.9.1.tgz#fcd0f4496dad09e0c899b44f6c4bb7848da912ae" integrity sha512-QbUdXJVTpvUTHU7871ppZkdOLBeGUKBQWHkHrvN2V9IQWGMt61zf3B45BtzjxEJzYuj0JBjBZP/hmYS/R9pmAw== @@ -3488,11 +3488,6 @@ axe-core@=4.7.0: resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.7.0.tgz#34ba5a48a8b564f67e103f0aa5768d76e15bbbbf" integrity sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ== -axe-core@~4.9.0: - version "4.9.0" - resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.9.0.tgz#b18971494551ab39d4ff5f7d4c6411bd20cc7c2a" - integrity sha512-H5orY+M2Fr56DWmMFpMrq5Ge93qjNdPVqzBv5gWK3aD1OvjBEJlEzxf09z93dGVQeI0LiW+aCMIx1QtShC/zUw== - axobject-query@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.2.1.tgz#39c378a6e3b06ca679f29138151e45b2b32da62a" @@ -4924,10 +4919,10 @@ eslint-module-utils@^2.8.0: dependencies: debug "^3.2.7" -eslint-plugin-deprecate@0.8.4: - version "0.8.4" - resolved "https://registry.yarnpkg.com/eslint-plugin-deprecate/-/eslint-plugin-deprecate-0.8.4.tgz#1bbedca80f763cadf228c66a4cf639eb16aeca68" - integrity sha512-bzpQTyXNWXbMWRH77XiuzfAthOhQhizEZrTf7krRiMYrq6ENUsWfbCe8A3SeRNa4eW8T2QrHsg/lXmxLq9xXXA== +eslint-plugin-deprecate@0.8.5: + version "0.8.5" + resolved "https://registry.yarnpkg.com/eslint-plugin-deprecate/-/eslint-plugin-deprecate-0.8.5.tgz#3297e2f0535e01af4ffbebda74824a951d3f073b" + integrity sha512-nBH14smi2O1qsQY/deVf1xg1XPMB5ADPPcXWpUDp5ptGrWuNGD7Ghe0mIQKmsoFr6VjjDxJZ5tt78hb5GaCScA== eslint-plugin-import@^2.25.4: version "2.29.1" @@ -7846,10 +7841,10 @@ postcss@^8.4.38: picocolors "^1.0.0" source-map-js "^1.2.0" -posthog-js@1.131.4: - version "1.131.4" - resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.131.4.tgz#b29de94a26132e7cb5abcc16c7b57fd6d918e277" - integrity sha512-pKa1p6Q9jRU6s+xSluqGifODMncWTXRaeQw7yVet5U+0U56P0srdMO8NpzllIgDjYL9WLgUjDInucOBw5Cl/tA== +posthog-js@1.135.2: + version "1.135.2" + resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.135.2.tgz#1da1508760521e6f0fe1ab908bc4ffbe04c2952c" + integrity sha512-kqix067CyrlcNKUhVxrys8Qp0O/8FUtlkp7lfM+tkJFJAMZsKjIDVslz2AjI9y79CvyyZX+pddfA7F3YFYlS0Q== dependencies: fflate "^0.4.8" preact "^10.19.3" @@ -8038,9 +8033,9 @@ raw-loader@^4.0.2: schema-utils "^3.0.0" re-resizable@^6.9.0: - version "6.9.16" - resolved "https://registry.yarnpkg.com/re-resizable/-/re-resizable-6.9.16.tgz#d040a3ba9ccb25a3cc85b7622d4eafdee48cf2c2" - integrity sha512-D9+ofwgPQRC6PL6cwavCZO9MUR8TKKxV1nHjbutSdNaFHK9v5k8m6DcESMXrw1+mRJn7fBHJRhZpa7EQ1ZWEEA== + version "6.9.17" + resolved "https://registry.yarnpkg.com/re-resizable/-/re-resizable-6.9.17.tgz#78e4349934ff24a8fcb4b6b5a43ff9ed5f319d2a" + integrity sha512-OBqd1BwVXpEJJn/yYROG+CbeqIDBWIp6wathlpB0kzZWWZIY1gPTsgK2yJEui5hOvkCdC2mcexF2V3DZVfLq2g== react-beautiful-dnd@^13.1.0: version "13.1.1" @@ -8789,7 +8784,16 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -8883,7 +8887,14 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -9676,7 +9687,7 @@ which@^2.0.1: dependencies: isexe "^2.0.0" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -9694,6 +9705,15 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From 3912401db5642ace1c0427904182051dcd8fe56c Mon Sep 17 00:00:00 2001 From: ElementRobot Date: Fri, 31 May 2024 07:23:11 +0100 Subject: [PATCH 13/73] [create-pull-request] automated change (#12557) Co-authored-by: github-merge-queue --- src/i18n/strings/pl.json | 65 ++++++++++++++++++++++++++++++++-------- 1 file changed, 53 insertions(+), 12 deletions(-) diff --git a/src/i18n/strings/pl.json b/src/i18n/strings/pl.json index 3f495c81f25..10f3b085f1f 100644 --- a/src/i18n/strings/pl.json +++ b/src/i18n/strings/pl.json @@ -249,13 +249,29 @@ "completing_setup": "Kończenie konfiguracji nowego urządzenia", "confirm_code_match": "Potwierdź, że kod poniżej pasuje z Twoim drugim urządzeniem:", "connecting": "Łączenie…", + "error_expired": "Logowanie wygasło. Spróbuj ponownie.", + "error_expired_title": "Logowanie nie zostało zakończone na czas", + "error_insecure_channel_detected": "Nie udało się nawiązać bezpiecznego połączenia z nowym urządzeniem. Twoje pozostałe urządzania nadal są bezpieczne i nie musisz się o nie martwić.", + "error_insecure_channel_detected_instructions": "Co teraz?", + "error_insecure_channel_detected_instructions_1": "Spróbuj zalogować się ponownie na drugie urządzenie za pomocą kodu QR, jeśli był to błąd sieci.", + "error_insecure_channel_detected_instructions_2": "Jeśli występuje ten sam problem, użyj innej sieci Wi-Fi lub włącz dane mobilne", + "error_insecure_channel_detected_instructions_3": "Jeśli to nie zadziała, zaloguj się ręcznie", + "error_insecure_channel_detected_title": "Połączenie niezabezpieczone", + "error_other_device_already_signed_in": "Nie musisz już robić nic więcej.", + "error_other_device_already_signed_in_title": "Twoje drugie urządzenie jest już zalogowane", "error_rate_limited": "Za dużo prób w krótkim odstępie czasu. Odczekaj trochę, zanim spróbujesz ponownie.", - "error_unexpected": "Wystąpił niespodziewany błąd.", - "follow_remaining_instructions": "Podążaj zgodnie z pozostałymi instrukcjami, aby zweryfikować drugie urządzenie", + "error_unexpected": "Wystąpił nieoczekiwany błąd. Żądanie podłączenia drugiego urządzenia zostało anulowane.", + "error_unsupported_protocol": "To urządzenie nie wspiera logowania się do drugiego urządzenia za pomocą kodu QR.", + "error_unsupported_protocol_title": "Drugie urządzenie nie jest kompatybilne", + "error_user_cancelled": "Logowanie zostało anulowane na drugim urządzeniu.", + "error_user_cancelled_title": "Żądanie logowania anulowane", + "error_user_declined": "Odrzucono prośbę o zalogowanie z drugiego urządzenia.", + "error_user_declined_title": "Logowanie odrzucone", + "follow_remaining_instructions": "Postępuj zgodnie z instrukcjami, aby połączyć drugie urządzenie", "open_element_other_device": "Otwórz %(brand)s na swoim drugim urządzeniu", "point_the_camera": "Skieruj kamerę na widoczny kod QR", "scan_code_instruction": "Skanuj kod QR za pomocą innego urządzenia", - "scan_qr_code": "Skanuj kod QR", + "scan_qr_code": "Zaloguj się kodem QR", "select_qr_code": "Wybierz \"%(scanQRCode)s\"", "sign_in_new_device": "Zaloguj nowe urządzenie", "waiting_for_device": "Oczekiwanie na logowanie urządzenia" @@ -460,7 +476,7 @@ "guest": "Gość", "help": "Pomoc", "historical": "Historyczne", - "home": "Strona główna", + "home": "Główna", "homeserver": "Serwer domowy", "identity_server": "Serwer tożsamości", "image": "Obraz", @@ -509,7 +525,7 @@ "private_room": "Pokój prywatny", "private_space": "Przestrzeń prywatna", "profile": "Profil", - "public": "Publiczna", + "public": "Publiczny", "public_room": "Pokój publiczny", "public_space": "Przestrzeń publiczna", "qr_code": "Kod QR", @@ -1335,7 +1351,7 @@ "enter": "Enter", "escape": "Esc", "go_home_view": "Przejdź do widoku Strony głównej", - "home": "Strona główna", + "home": "Główna", "jump_first_message": "Przeskocz do pierwszej wiadomości", "jump_last_message": "Przejdź do ostatniej wiadomości", "jump_room_search": "Przejdź do szukania pokoju", @@ -1448,7 +1464,7 @@ "sliding_sync": "Tryb synchronizacji przesuwanej", "sliding_sync_description": "W trakcie aktywnego rozwoju, nie można wyłączyć.", "sliding_sync_disabled_notice": "Zaloguj się ponownie, aby wyłączyć", - "sliding_sync_server_no_support": "Twój serwer nie posiada wsparcia natywnego", + "sliding_sync_server_no_support": "Twój serwer nie ma wsparcia", "under_active_development": "W trakcie aktywnego rozwoju.", "unrealiable_e2e": "Problematyczny w pokojach szyfrowanych", "video_rooms": "Pokoje wideo", @@ -1807,6 +1823,7 @@ }, "right_panel": { "add_integrations": "Dodaj widżety, mostki i boty", + "add_topic": "Dodaj temat", "edit_integrations": "Edytuj widżety, mostki i boty", "export_chat_button": "Eksportuj czat", "files_button": "Pliki", @@ -2169,7 +2186,7 @@ "no_aliases_room": "Ten pokój nie ma lokalnych adresów", "no_aliases_space": "Ta przestrzeń nie ma adresu lokalnego", "other_section": "Inne", - "publish_toggle": "Czy opublikować ten pokój dla ogółu w spisie pokojów domeny %(domain)s?", + "publish_toggle": "Czy udostępnić ten pokój publicznie w katalogu pokojów %(domain)s?", "published_aliases_description": "Aby opublikować adres, musi być on najpierw ustawiony jako adres lokalny.", "published_aliases_explainer_room": "Opublikowane adresy mogą być używane przez każdego, kto dołączył do Twojego pokoju.", "published_aliases_explainer_space": "Opublikowane adresy mogą być używane przez każdego, kto dołączył do Twojej przestrzeni.", @@ -2378,10 +2395,11 @@ }, "setting": { "help_about": { - "access_token_detail": "Twój token dostępu daje pełen dostęp do Twojego konta. Nie dziel się nim z nikim.", + "access_token_detail": "Twój token dostępu daje pełny dostęp do Twojego konta. Nie dziel się nim z nikim.", "brand_version": "Wersja %(brand)s:", "clear_cache_reload": "Wyczyść pamięć podręczną i przeładuj", "crypto_version": "Wersja krypto:", + "dialog_title": "Ustawienia: Pomoc i O aplikacji", "help_link": "Aby uzyskać pomoc w używaniu %(brand)s, naciśnij tutaj.", "homeserver": "Serwer domowy to %(homeserverUrl)s", "identity_server": "Serwer tożsamości to %(identityServerUrl)s", @@ -2404,6 +2422,7 @@ "custom_theme_invalid": "Nieprawidłowy schemat motywu.", "custom_theme_success": "Dodano motyw!", "custom_theme_url": "Niestandardowy adres URL motywu", + "dialog_title": "Ustawienia: Wygląd", "font_size": "Rozmiar czcionki", "font_size_default": "%(fontSize)s (domyślny)", "image_size_default": "Zwykły", @@ -2452,6 +2471,7 @@ "deactivate_confirm_erase_label": "Ukryj moje wiadomości dla nowych osób", "deactivate_section": "Dezaktywuj konto", "deactivate_warning": "Dezaktywacja konta jest akcją trwałą — bądź ostrożny!", + "dialog_title": "Ustawienia: Ogólne", "discovery_email_empty": "Opcje odkrywania pojawią się, gdy dodasz adres e-mail powyżej.", "discovery_email_verification_instructions": "Zweryfikuj link w swojej skrzynce odbiorczej", "discovery_msisdn_empty": "Opcje odkrywania pojawią się, gdy dodasz numer telefonu powyżej.", @@ -2559,12 +2579,20 @@ "phrase_strong_enough": "Świetnie! To hasło wygląda na wystarczająco silne" }, "keyboard": { + "dialog_title": "Ustawienia: Klawiatura", "title": "Skróty klawiszowe" }, + "labs": { + "dialog_title": "Ustawienia: Laboratoria" + }, + "labs_mjolnir": { + "dialog_title": "Ustawienia: Ignorowani użytkownicy" + }, "notifications": { "default_setting_description": "To ustawienie zastosuje się do wszystkich Twoich pokoi.", "default_setting_section": "Chce otrzymywać powiadomienia (Domyślne ustawienie)", "desktop_notification_message_preview": "Pokaż podgląd wiadomości w powiadomieniach na pulpicie", + "dialog_title": "Ustawienia: Powiadomienia", "email_description": "Otrzymuj e-maile z podsumowaniem pominiętych powiadomień", "email_section": "Podsumowanie e-mail", "email_select": "Wybierz, które e-maile mają otrzymać podsumowania. Zarządzaj swoimi e-mailami w sekcji .", @@ -2613,7 +2641,7 @@ "rule_roomnotif": "Wiadomości zawierające @room", "rule_suppress_notices": "Wiadomości wysłane przez bota", "rule_tombstone": "Kiedy pokoje są uaktualniane", - "show_message_desktop_notification": "Pokaż wiadomość w notyfikacji na pulpicie", + "show_message_desktop_notification": "Pokaż wiadomość w powiadomieniu na pulpicie", "voip": "Połączenia audio i wideo" }, "preferences": { @@ -2623,6 +2651,7 @@ "code_blocks_heading": "Bloki kodu", "compact_modern": "Użyj bardziej kompaktowego, \"nowoczesnego\" wyglądu", "composer_heading": "Kompozytor", + "dialog_title": "Ustawienia: Preferencje", "enable_hardware_acceleration": "Włącz przyspieszenie sprzętowe", "enable_tray_icon": "Pokaż ikonę w zasobniku systemowym i zminimalizuj okno do niej zamiast zamknięcia", "keyboard_heading": "Skróty klawiszowe", @@ -2672,6 +2701,7 @@ "dehydrated_device_enabled": "Urządzenie offline włączone", "delete_backup": "Usuń kopię zapasową", "delete_backup_confirm_description": "Czy jesteś pewien? Stracisz dostęp do wszystkich swoich zaszyfrowanych wiadomości, jeżeli nie utworzyłeś poprawnej kopii zapasowej kluczy.", + "dialog_title": "Ustawienia: Bezpieczeństwo i prywatność", "e2ee_default_disabled_warning": "Twój administrator serwera wyłączył szyfrowanie end-to-end domyślnie w pokojach prywatnych i wiadomościach bezpośrednich.", "enable_message_search": "Włącz wyszukiwanie wiadomości w szyfrowanych pokojach", "encryption_individual_verification_mode": "Indywidualnie weryfikuj każdą sesję używaną przez użytkownika, aby oznaczyć ją jako zaufaną, nie ufając urządzeniom weryfikowanym krzyżowo.", @@ -2751,6 +2781,7 @@ "device_unverified_description_current": "Zweryfikuj swoją bieżącą sesję dla wzmocnienia bezpiecznych wiadomości.", "device_verified_description": "Sesja jest gotowa do wysyłania bezpiecznych wiadomości.", "device_verified_description_current": "Twoja bieżąca sesja jest gotowa do wysyłania bezpiecznych wiadomości.", + "dialog_title": "Ustawienia: Sesje", "error_pusher_state": "Nie udało się ustawić stanu pushera", "error_set_name": "Nie udało się ustawić nazwy sesji", "filter_all": "Wszystkie", @@ -2834,10 +2865,11 @@ "show_typing_notifications": "Pokazuj powiadomienia o pisaniu", "showbold": "Pokaż całą aktywność na liście pomieszczeń (kropki lub liczbę nieprzeczytanych wiadomości)", "sidebar": { + "dialog_title": "Ustawienia: Pasek boczny", "metaspaces_favourites_description": "Pogrupuj wszystkie swoje ulubione pokoje i osoby w jednym miejscu.", "metaspaces_home_all_rooms": "Pokaż wszystkie pokoje", "metaspaces_home_all_rooms_description": "Pokaż wszystkie swoje pokoje na głównej, nawet jeśli znajdują się w przestrzeni.", - "metaspaces_home_description": "Strona główna to przydatne miejsce dla podsumowania wszystkiego.", + "metaspaces_home_description": "Główna to przydatne miejsce, gdzie znajdziesz przegląd wszystkiego.", "metaspaces_orphans": "Pokoje poza przestrzenią", "metaspaces_orphans_description": "Pogrupuj wszystkie pokoje, które nie są częścią przestrzeni, w jednym miejscu.", "metaspaces_people_description": "Pogrupuj wszystkie osoby w jednym miejscu.", @@ -2863,6 +2895,7 @@ "audio_output_empty": "Nie wykryto wyjść audio", "auto_gain_control": "Automatyczna regulacja głośności", "connection_section": "Połączenie", + "dialog_title": "Ustawienia: Głos i wideo", "echo_cancellation": "Usuwanie echa", "enable_fallback_ice_server": "Zezwól na alternatywny serwer wspierający (%(server)s)", "enable_fallback_ice_server_description": "Stosuje się go tylko wtedy, kiedy Twój serwer domowy go nie oferuje. Twój adres IP zostanie współdzielony w trakcie połączenia.", @@ -3196,6 +3229,13 @@ }, "creation_summary_dm": "%(creator)s utworzył tę wiadomość prywatną.", "creation_summary_room": "%(creator)s stworzył i skonfigurował pokój.", + "decryption_failure": { + "blocked": "Nadawca zablokował Ci możliwość otrzymania tej wiadomości", + "historical_event_no_key_backup": "Historia wiadomości nie jest dostępna na tym urządzeniu", + "historical_event_unverified_device": "Musisz zweryfikować to urządzenie, aby wyświetlić historię wiadomości", + "historical_event_user_not_joined": "Nie masz dostępu do tej wiadomości", + "unable_to_decrypt": "Nie można rozszyfrować wiadomości" + }, "disambiguated_profile": "%(displayName)s (%(matrixId)s)", "download_action_decrypting": "Rozszyfrowuję", "download_action_downloading": "Pobieranie", @@ -3445,7 +3485,8 @@ "reactions": { "add_reaction_prompt": "Dodaj reakcje", "custom_reaction_fallback_label": "Reakcja niestandardowa", - "label": "%(reactors)s zareagował z %(content)s" + "label": "%(reactors)s zareagował z %(content)s", + "tooltip_caption": "zareagował z %(shortName)s" }, "read_receipt_title": { "one": "Odczytane przez %(count)s osobę", From dff05f4ca8c4a1b1ed58501b14516fbc13fcbff7 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 31 May 2024 10:07:23 +0100 Subject: [PATCH 14/73] Update CODEOWNERS --- .github/CODEOWNERS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 91764ba7f5a..e7963c26735 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -11,4 +11,7 @@ /src/stores/SetupEncryptionStore.ts @matrix-org/element-crypto-web-reviewers /test/stores/SetupEncryptionStore-test.ts @matrix-org/element-crypto-web-reviewers +# Ignore translations as those will be updated by GHA for Localazy download /src/i18n/strings +# Ignore the synapse plugin as this is updated by GHA for docker image updating +/playwright/plugins/homeserver/synapse/index.ts From fa7486e415bc29ef49ce8b78d569bad186b6d1ca Mon Sep 17 00:00:00 2001 From: ElementRobot Date: Fri, 31 May 2024 10:28:18 +0100 Subject: [PATCH 15/73] [create-pull-request] automated change (#12572) Co-authored-by: github-merge-queue Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> --- playwright/plugins/homeserver/synapse/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright/plugins/homeserver/synapse/index.ts b/playwright/plugins/homeserver/synapse/index.ts index 968968a8a66..352bf9404d1 100644 --- a/playwright/plugins/homeserver/synapse/index.ts +++ b/playwright/plugins/homeserver/synapse/index.ts @@ -28,7 +28,7 @@ import { randB64Bytes } from "../../utils/rand"; // Docker tag to use for `matrixdotorg/synapse` image. // We target a specific digest as every now and then a Synapse update will break our CI. // This digest is updated by the playwright-image-updates.yaml workflow periodically. -const DOCKER_TAG = "develop@sha256:b41149e8bacb8c3b22b06bba186402a64b1810c92e70ed5518735f0e57ed8f06"; +const DOCKER_TAG = "develop@sha256:dfa6a3de699d35a60c357dc3ad8b5ef523af89d1e08b0783893b79e133616117"; async function cfgDirFromTemplate(opts: StartHomeserverOpts): Promise> { const templateDir = path.join(__dirname, "templates", opts.template); From c3769addd3b8c555e4fe6a5a024e77ee7030568c Mon Sep 17 00:00:00 2001 From: ElementRobot Date: Sat, 1 Jun 2024 07:27:15 +0100 Subject: [PATCH 16/73] [create-pull-request] automated change (#12573) Co-authored-by: github-merge-queue --- playwright/plugins/homeserver/synapse/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright/plugins/homeserver/synapse/index.ts b/playwright/plugins/homeserver/synapse/index.ts index 352bf9404d1..60c1183b7e1 100644 --- a/playwright/plugins/homeserver/synapse/index.ts +++ b/playwright/plugins/homeserver/synapse/index.ts @@ -28,7 +28,7 @@ import { randB64Bytes } from "../../utils/rand"; // Docker tag to use for `matrixdotorg/synapse` image. // We target a specific digest as every now and then a Synapse update will break our CI. // This digest is updated by the playwright-image-updates.yaml workflow periodically. -const DOCKER_TAG = "develop@sha256:dfa6a3de699d35a60c357dc3ad8b5ef523af89d1e08b0783893b79e133616117"; +const DOCKER_TAG = "develop@sha256:92c4678375d5c71647b66853fe238e1224465b74fbc2d3422b2ba719aa0e7d40"; async function cfgDirFromTemplate(opts: StartHomeserverOpts): Promise> { const templateDir = path.join(__dirname, "templates", opts.template); From 30850709ef6fce7f8abc4a8e2cb2ef5bf39439e9 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 3 Jun 2024 11:03:26 +0100 Subject: [PATCH 17/73] Build the playwright html report in the main workflow (#12570) * Build the playwright html report in the main workflow so we can get the report for MQ Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * fix Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .../workflows/end-to-end-tests-netlify.yaml | 29 ++--------------- .github/workflows/end-to-end-tests.yaml | 31 +++++++++++++++++++ 2 files changed, 33 insertions(+), 27 deletions(-) diff --git a/.github/workflows/end-to-end-tests-netlify.yaml b/.github/workflows/end-to-end-tests-netlify.yaml index 4667bfb02b3..a488cbbfb09 100644 --- a/.github/workflows/end-to-end-tests-netlify.yaml +++ b/.github/workflows/end-to-end-tests-netlify.yaml @@ -21,38 +21,13 @@ jobs: statuses: write deployments: write steps: - - uses: actions/checkout@v4 - with: - persist-credentials: false - - - uses: actions/setup-node@v4 - with: - cache: "yarn" - - - name: Install dependencies - run: yarn install --frozen-lockfile - - - name: Download blob reports from GitHub Actions Artifacts + - name: Download HTML report uses: actions/download-artifact@v4 with: github-token: ${{ secrets.ELEMENT_BOT_TOKEN }} run-id: ${{ github.event.workflow_run.id }} - pattern: all-blob-reports-* - path: all-blob-reports - merge-multiple: true - - - name: Merge into HTML Report - run: yarn playwright merge-reports --reporter=html,./playwright/flaky-reporter.ts ./all-blob-reports - env: - # Only pass creds to the flaky-reporter on main branch runs - GITHUB_TOKEN: ${{ github.event.workflow_run.head_branch == 'develop' && secrets.ELEMENT_BOT_TOKEN || '' }} - - - name: Upload HTML report - uses: actions/upload-artifact@v4 - with: - name: html-report--attempt-${{ github.run_attempt }} + name: html-report path: playwright-report - retention-days: 14 - name: 📤 Deploy to Netlify uses: matrix-org/netlify-pr-preview@v3 diff --git a/.github/workflows/end-to-end-tests.yaml b/.github/workflows/end-to-end-tests.yaml index 0e224c04db5..7901bfb343a 100644 --- a/.github/workflows/end-to-end-tests.yaml +++ b/.github/workflows/end-to-end-tests.yaml @@ -165,3 +165,34 @@ jobs: steps: - if: needs.playwright.result != 'skipped' && needs.playwright.result != 'success' run: exit 1 + + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - uses: actions/setup-node@v4 + with: + cache: "yarn" + + - name: Install dependencies + run: yarn install --frozen-lockfile + + - name: Download blob reports from GitHub Actions Artifacts + uses: actions/download-artifact@v4 + with: + pattern: all-blob-reports-* + path: all-blob-reports + merge-multiple: true + + - name: Merge into HTML Report + run: yarn playwright merge-reports --reporter=html,./playwright/flaky-reporter.ts ./all-blob-reports + env: + # Only pass creds to the flaky-reporter on main branch runs + GITHUB_TOKEN: ${{ github.event.workflow_run.head_branch == 'develop' && secrets.ELEMENT_BOT_TOKEN || '' }} + + - name: Upload HTML report + uses: actions/upload-artifact@v4 + with: + name: html-report + path: playwright-report + retention-days: 14 From f2d9f13f1b2125939458f11e7b70111b06f97ff2 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 3 Jun 2024 21:56:33 +0100 Subject: [PATCH 18/73] Update end-to-end-tests.yaml --- .github/workflows/end-to-end-tests.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/end-to-end-tests.yaml b/.github/workflows/end-to-end-tests.yaml index 7901bfb343a..f7506f83243 100644 --- a/.github/workflows/end-to-end-tests.yaml +++ b/.github/workflows/end-to-end-tests.yaml @@ -46,6 +46,7 @@ jobs: build: name: "Build Element-Web" runs-on: ubuntu-latest + if: inputs.skip != true steps: - name: Checkout code uses: actions/checkout@v4 @@ -167,17 +168,21 @@ jobs: run: exit 1 - uses: actions/checkout@v4 + if: inputs.skip != true with: persist-credentials: false - uses: actions/setup-node@v4 + if: inputs.skip != true with: cache: "yarn" - name: Install dependencies + if: inputs.skip != true run: yarn install --frozen-lockfile - name: Download blob reports from GitHub Actions Artifacts + if: inputs.skip != true uses: actions/download-artifact@v4 with: pattern: all-blob-reports-* @@ -185,12 +190,14 @@ jobs: merge-multiple: true - name: Merge into HTML Report + if: inputs.skip != true run: yarn playwright merge-reports --reporter=html,./playwright/flaky-reporter.ts ./all-blob-reports env: # Only pass creds to the flaky-reporter on main branch runs GITHUB_TOKEN: ${{ github.event.workflow_run.head_branch == 'develop' && secrets.ELEMENT_BOT_TOKEN || '' }} - name: Upload HTML report + if: inputs.skip != true uses: actions/upload-artifact@v4 with: name: html-report From ab6a94aa8f76a1293a3cbfc3a0930eed730cafad Mon Sep 17 00:00:00 2001 From: ElementRobot Date: Tue, 4 Jun 2024 07:27:48 +0100 Subject: [PATCH 19/73] [create-pull-request] automated change (#12574) Co-authored-by: github-merge-queue --- playwright/plugins/homeserver/synapse/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright/plugins/homeserver/synapse/index.ts b/playwright/plugins/homeserver/synapse/index.ts index 60c1183b7e1..ecb4e64c806 100644 --- a/playwright/plugins/homeserver/synapse/index.ts +++ b/playwright/plugins/homeserver/synapse/index.ts @@ -28,7 +28,7 @@ import { randB64Bytes } from "../../utils/rand"; // Docker tag to use for `matrixdotorg/synapse` image. // We target a specific digest as every now and then a Synapse update will break our CI. // This digest is updated by the playwright-image-updates.yaml workflow periodically. -const DOCKER_TAG = "develop@sha256:92c4678375d5c71647b66853fe238e1224465b74fbc2d3422b2ba719aa0e7d40"; +const DOCKER_TAG = "develop@sha256:223f5e054ead418e6ab2ac8480ae85d63dc02423642015b43e56efb12a057d59"; async function cfgDirFromTemplate(opts: StartHomeserverOpts): Promise> { const templateDir = path.join(__dirname, "templates", opts.template); From f894ae6b68f19bbf492a740dbc104e1104652c5b Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 4 Jun 2024 13:20:23 +0000 Subject: [PATCH 20/73] Upgrade dependency to matrix-js-sdk@33.0.0 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 57f67573933..353dbd0eb85 100644 --- a/package.json +++ b/package.json @@ -110,7 +110,7 @@ "maplibre-gl": "^2.0.0", "matrix-encrypt-attachment": "^1.0.3", "matrix-events-sdk": "0.0.1", - "matrix-js-sdk": "33.0.0-rc.0", + "matrix-js-sdk": "33.0.0", "matrix-widget-api": "^1.5.0", "memoize-one": "^6.0.0", "minimist": "^1.2.5", diff --git a/yarn.lock b/yarn.lock index a81a49fad8c..13435410910 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7151,10 +7151,10 @@ matrix-events-sdk@0.0.1: resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz#c8c38911e2cb29023b0bbac8d6f32e0de2c957dd" integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA== -matrix-js-sdk@33.0.0-rc.0: - version "33.0.0-rc.0" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-33.0.0-rc.0.tgz#f4d5d2148669edeadc870245850c17a88c826251" - integrity sha512-QpciVF6ZOYgTjnR+Og0LvVivGfSube4vmF9qGcdRvqmwc3xK0w5pFW2D8y/pZdzQ8gYfD/LGIG9GkbtW3TrYKA== +matrix-js-sdk@33.0.0: + version "33.0.0" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-33.0.0.tgz#556c20b4ee0b1b7f59a98d126fdce3818c6ecfc4" + integrity sha512-uv2Mx4j9hw1E+98wi7KUKn04+3jCaMM1BnLp7k8hkL1dQyibHTzB8evlR6idzatPChxgdTSeD+zTUesYy3i9gw== dependencies: "@babel/runtime" "^7.12.5" "@matrix-org/matrix-sdk-crypto-wasm" "^5.0.0" From 3ec0fe166fbf4c428d53ae8ffd9ca673da22bd5d Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 4 Jun 2024 13:27:08 +0000 Subject: [PATCH 21/73] v3.100.0 --- CHANGELOG.md | 21 +++++++++++++++++++++ package.json | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea324f0e031..05dc3faa702 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,24 @@ +Changes in [3.100.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.100.0) (2024-06-04) +======================================================================================================= +## ✨ Features + +* Tooltip: Improve accessibility for context menus ([#12462](https://github.com/matrix-org/matrix-react-sdk/pull/12462)). Contributed by @florianduros. +* Tooltip: Improve accessibility of space panel ([#12525](https://github.com/matrix-org/matrix-react-sdk/pull/12525)). Contributed by @florianduros. + +## 🐛 Bug Fixes + +* Close the release announcement when a dialog is opened ([#12559](https://github.com/matrix-org/matrix-react-sdk/pull/12559)). Contributed by @florianduros. +* Tooltip: close field tooltip when ESC is pressed ([#12553](https://github.com/matrix-org/matrix-react-sdk/pull/12553)). Contributed by @florianduros. +* Fix tabbedview breakpoint width ([#12556](https://github.com/matrix-org/matrix-react-sdk/pull/12556)). Contributed by @dbkr. +* Fix E2E icon display in room header ([#12545](https://github.com/matrix-org/matrix-react-sdk/pull/12545)). Contributed by @florianduros. +* Tooltip: Improve placement for space settings ([#12541](https://github.com/matrix-org/matrix-react-sdk/pull/12541)). Contributed by @florianduros. +* Fix deformed avatar in a call in a narrow timeline ([#12538](https://github.com/matrix-org/matrix-react-sdk/pull/12538)). Contributed by @florianduros. +* Shown own sent state indicator even when showReadReceipts is disabled ([#12540](https://github.com/matrix-org/matrix-react-sdk/pull/12540)). Contributed by @t3chguy. +* Ensure we do not fire the verification mismatch modal multiple times ([#12526](https://github.com/matrix-org/matrix-react-sdk/pull/12526)). Contributed by @t3chguy. +* Fix avatar in chat export ([#12537](https://github.com/matrix-org/matrix-react-sdk/pull/12537)). Contributed by @florianduros. +* Use `*` for italics as it doesn't break when used mid-word ([#12523](https://github.com/matrix-org/matrix-react-sdk/pull/12523)). Contributed by @t3chguy. + + Changes in [3.99.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.99.0) (2024-05-07) ===================================================================================================== ## ✨ Features diff --git a/package.json b/package.json index 353dbd0eb85..7a3b463cdf8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "3.100.0-rc.1", + "version": "3.100.0", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { From c68c33a884d786a7296a61267c99f51f94ce7d4f Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 4 Jun 2024 13:28:13 +0000 Subject: [PATCH 22/73] Resetting package fields for development --- package.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 7e8b1163801..5b57bcbb33d 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "package.json", ".stylelintrc.js" ], - "main": "./lib/index.ts", + "main": "./src/index.ts", "matrix_src_main": "./src/index.ts", "matrix_lib_main": "./lib/index.ts", "matrix_lib_typings": "./lib/index.d.ts", @@ -236,6 +236,5 @@ "outputDirectory": "coverage", "outputName": "jest-sonar-report.xml", "relativePaths": true - }, - "typings": "./lib/index.d.ts" + } } From 48a291d5b0e4418d4bd4f99bd86d20a75ec36c7e Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 4 Jun 2024 13:28:20 +0000 Subject: [PATCH 23/73] Reset matrix-js-sdk back to develop branch --- package.json | 2 +- yarn.lock | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 5b57bcbb33d..34cb581025b 100644 --- a/package.json +++ b/package.json @@ -110,7 +110,7 @@ "maplibre-gl": "^2.0.0", "matrix-encrypt-attachment": "^1.0.3", "matrix-events-sdk": "0.0.1", - "matrix-js-sdk": "33.0.0", + "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop", "matrix-widget-api": "^1.5.0", "memoize-one": "^6.0.0", "minimist": "^1.2.5", diff --git a/yarn.lock b/yarn.lock index 37f6182b582..6f03505b3db 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7146,10 +7146,9 @@ matrix-events-sdk@0.0.1: resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz#c8c38911e2cb29023b0bbac8d6f32e0de2c957dd" integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA== -matrix-js-sdk@33.0.0: +"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop": version "33.0.0" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-33.0.0.tgz#556c20b4ee0b1b7f59a98d126fdce3818c6ecfc4" - integrity sha512-uv2Mx4j9hw1E+98wi7KUKn04+3jCaMM1BnLp7k8hkL1dQyibHTzB8evlR6idzatPChxgdTSeD+zTUesYy3i9gw== + resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/89875b8e314288856f9736ae84ee889991c64b34" dependencies: "@babel/runtime" "^7.12.5" "@matrix-org/matrix-sdk-crypto-wasm" "^5.0.0" From 7cd822fa080d5a471e1f76bc4282675ffacd69be Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 15:03:38 +0100 Subject: [PATCH 24/73] Update definitelyTyped (#12551) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 6f03505b3db..b78962b91b5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2748,9 +2748,9 @@ integrity sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ== "@types/lodash@^4.14.168": - version "4.17.1" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.1.tgz#0fabfcf2f2127ef73b119d98452bd317c4a17eb8" - integrity sha512-X+2qazGS3jxLAIz5JDXDzglAF3KpijdhFxlf/V1+hEsOUc+HnWi81L/uv/EvGuV90WY+7mPGFCUDGfQC3Gj95Q== + version "4.17.4" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.4.tgz#0303b64958ee070059e3a7184048a55159fe20b7" + integrity sha512-wYCP26ZLxaT3R39kiN2+HcJ4kTd3U1waI/cY7ivWYqFP6pW3ZNpvi6Wd6PHZx7T/t8z0vlkXMg3QYLa7DZ/IJQ== "@types/mapbox__point-geometry@*", "@types/mapbox__point-geometry@^0.1.2": version "0.1.4" From 9680a36b14533de18289b5f548427c6fa3ba8d74 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 4 Jun 2024 17:06:03 +0100 Subject: [PATCH 25/73] Update end-to-end-tests.yaml --- .github/workflows/end-to-end-tests.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/end-to-end-tests.yaml b/.github/workflows/end-to-end-tests.yaml index f7506f83243..08eca4372b8 100644 --- a/.github/workflows/end-to-end-tests.yaml +++ b/.github/workflows/end-to-end-tests.yaml @@ -171,6 +171,7 @@ jobs: if: inputs.skip != true with: persist-credentials: false + repository: ${{ inputs.react-sdk-repository || github.repository }} - uses: actions/setup-node@v4 if: inputs.skip != true From 5004456d82f48b0618ae1b311b3cba44ac9da7e5 Mon Sep 17 00:00:00 2001 From: ElementRobot Date: Wed, 5 Jun 2024 07:28:32 +0100 Subject: [PATCH 26/73] [create-pull-request] automated change (#12577) Co-authored-by: github-merge-queue --- playwright/plugins/homeserver/synapse/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright/plugins/homeserver/synapse/index.ts b/playwright/plugins/homeserver/synapse/index.ts index ecb4e64c806..00807a0b8a4 100644 --- a/playwright/plugins/homeserver/synapse/index.ts +++ b/playwright/plugins/homeserver/synapse/index.ts @@ -28,7 +28,7 @@ import { randB64Bytes } from "../../utils/rand"; // Docker tag to use for `matrixdotorg/synapse` image. // We target a specific digest as every now and then a Synapse update will break our CI. // This digest is updated by the playwright-image-updates.yaml workflow periodically. -const DOCKER_TAG = "develop@sha256:223f5e054ead418e6ab2ac8480ae85d63dc02423642015b43e56efb12a057d59"; +const DOCKER_TAG = "develop@sha256:13d471114317080014318b4fe41b67ffda6ac11fdfd866e03be7a77e6cb08a4e"; async function cfgDirFromTemplate(opts: StartHomeserverOpts): Promise> { const templateDir = path.join(__dirname, "templates", opts.template); From 0a01320fca4d80d39035206c5175b65e86c74ea6 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> Date: Wed, 5 Jun 2024 09:52:28 +0100 Subject: [PATCH 27/73] Element-R: pass pickleKey in as raw key for indexeddb encryption (#12543) * Element-R: pass pickleKey in as raw key for indexeddb encryption Currently, we pass the `pickleKey` to the rust library for use as a passphrase for encrypting its crypto store. The Rust libary then passes that passphrase through 200000 rounds of PBKDF2 to generate an encryption key, which is (deliberately) slow. However, the pickleKey is actually 32 bytes of random data (base64-encoded). By passing the raw key into the rust library, we can therefore save the PBKDF operation. Backwards-compatibility with existing sessions is maintained, because if the rust library discovers that the store was previously encrypted with a key based on a PBKDF, it will re-base64 and PBKDF the key we provide, thus reconstructing the right key. * Update src/Lifecycle.ts Co-authored-by: Florian Duros * Lifecycle-test: clean up test setup Rely less on the unit under test for setting up the test preconditions -- not least because we don't really want to fire up matrix clients and the like during test setup. * Factor out "encryptPickleKey" method For a start it makes it easier to grok what's going on, but also I went to use this in a test * Improve tests for `Lifecycle.restoreFromLocalStorage` --------- Co-authored-by: Florian Duros --- src/BasePlatform.ts | 22 ++----- src/Lifecycle.ts | 38 ++++++++--- src/MatrixClientPeg.ts | 61 ++++++++++++++--- src/utils/tokens/pickling.ts | 42 +++++++++++- test/Lifecycle-test.ts | 123 ++++++++++++++++++++++++++++------- test/MatrixClientPeg-test.ts | 11 ++-- 6 files changed, 233 insertions(+), 64 deletions(-) diff --git a/src/BasePlatform.ts b/src/BasePlatform.ts index e7e4ff7e3cd..3c515dff84a 100644 --- a/src/BasePlatform.ts +++ b/src/BasePlatform.ts @@ -38,7 +38,7 @@ import { idbLoad, idbSave, idbDelete } from "./utils/StorageAccess"; import { ViewRoomPayload } from "./dispatcher/payloads/ViewRoomPayload"; import { IConfigOptions } from "./IConfigOptions"; import SdkConfig from "./SdkConfig"; -import { buildAndEncodePickleKey, getPickleAdditionalData } from "./utils/tokens/pickling"; +import { buildAndEncodePickleKey, encryptPickleKey } from "./utils/tokens/pickling"; export const SSO_HOMESERVER_URL_KEY = "mx_sso_hs_url"; export const SSO_ID_SERVER_URL_KEY = "mx_sso_is_url"; @@ -378,24 +378,16 @@ export default abstract class BasePlatform { * support storing pickle keys. */ public async createPickleKey(userId: string, deviceId: string): Promise { - if (!window.crypto || !window.crypto.subtle) { - return null; - } - const crypto = window.crypto; const randomArray = new Uint8Array(32); crypto.getRandomValues(randomArray); - const cryptoKey = await crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, false, [ - "encrypt", - "decrypt", - ]); - const iv = new Uint8Array(32); - crypto.getRandomValues(iv); - - const additionalData = getPickleAdditionalData(userId, deviceId); - const encrypted = await crypto.subtle.encrypt({ name: "AES-GCM", iv, additionalData }, cryptoKey, randomArray); + const data = await encryptPickleKey(randomArray, userId, deviceId); + if (data === undefined) { + // no crypto support + return null; + } try { - await idbSave("pickleKey", [userId, deviceId], { encrypted, iv, cryptoKey }); + await idbSave("pickleKey", [userId, deviceId], data); } catch (e) { return null; } diff --git a/src/Lifecycle.ts b/src/Lifecycle.ts index 8b04f74afcb..90f320409fc 100644 --- a/src/Lifecycle.ts +++ b/src/Lifecycle.ts @@ -18,12 +18,12 @@ limitations under the License. */ import { ReactNode } from "react"; -import { createClient, MatrixClient, SSOAction, OidcTokenRefresher } from "matrix-js-sdk/src/matrix"; +import { createClient, MatrixClient, SSOAction, OidcTokenRefresher, decodeBase64 } from "matrix-js-sdk/src/matrix"; import { IEncryptedPayload } from "matrix-js-sdk/src/crypto/aes"; import { QueryDict } from "matrix-js-sdk/src/utils"; import { logger } from "matrix-js-sdk/src/logger"; -import { IMatrixClientCreds, MatrixClientPeg } from "./MatrixClientPeg"; +import { IMatrixClientCreds, MatrixClientPeg, MatrixClientPegAssignOpts } from "./MatrixClientPeg"; import { ModuleRunner } from "./modules/ModuleRunner"; import EventIndexPeg from "./indexing/EventIndexPeg"; import createMatrixClient from "./utils/createMatrixClient"; @@ -422,6 +422,7 @@ async function onSuccessfulDelegatedAuthLogin(credentials: IMatrixClientCreds): } type TryAgainFunction = () => void; + /** * Display a friendly error to the user when token login or OIDC authorization fails * @param description error description @@ -821,7 +822,23 @@ async function doSetLoggedIn(credentials: IMatrixClientCreds, clearStorageEnable checkSessionLock(); dis.fire(Action.OnLoggedIn); - await startMatrixClient(client, /*startSyncing=*/ !softLogout); + + const clientPegOpts: MatrixClientPegAssignOpts = {}; + if (credentials.pickleKey) { + // The pickleKey, if provided, is probably a base64-encoded 256-bit key, so can be used for the crypto store. + if (credentials.pickleKey.length === 43) { + clientPegOpts.rustCryptoStoreKey = decodeBase64(credentials.pickleKey); + } else { + // We have some legacy pickle key. Continue using it as a password. + clientPegOpts.rustCryptoStorePassword = credentials.pickleKey; + } + } + + try { + await startMatrixClient(client, /*startSyncing=*/ !softLogout, clientPegOpts); + } finally { + clientPegOpts.rustCryptoStoreKey?.fill(0); + } return client; } @@ -955,11 +972,16 @@ export function isLoggingOut(): boolean { /** * Starts the matrix client and all other react-sdk services that * listen for events while a session is logged in. + * * @param client the matrix client to start - * @param {boolean} startSyncing True (default) to actually start - * syncing the client. + * @param startSyncing - `true` to actually start syncing the client. + * @param clientPegOpts - Options to pass through to {@link MatrixClientPeg.start}. */ -async function startMatrixClient(client: MatrixClient, startSyncing = true): Promise { +async function startMatrixClient( + client: MatrixClient, + startSyncing: boolean, + clientPegOpts: MatrixClientPegAssignOpts, +): Promise { logger.log(`Lifecycle: Starting MatrixClient`); // dispatch this before starting the matrix client: it's used @@ -990,10 +1012,10 @@ async function startMatrixClient(client: MatrixClient, startSyncing = true): Pro // index (e.g. the FilePanel), therefore initialize the event index // before the client. await EventIndexPeg.init(); - await MatrixClientPeg.start(); + await MatrixClientPeg.start(clientPegOpts); } else { logger.warn("Caller requested only auxiliary services be started"); - await MatrixClientPeg.assign(); + await MatrixClientPeg.assign(clientPegOpts); } checkSessionLock(); diff --git a/src/MatrixClientPeg.ts b/src/MatrixClientPeg.ts index a1c277fc28b..72340cb35fc 100644 --- a/src/MatrixClientPeg.ts +++ b/src/MatrixClientPeg.ts @@ -66,6 +66,27 @@ export interface IMatrixClientCreds { freshLogin?: boolean; } +export interface MatrixClientPegAssignOpts { + /** + * If we are using Rust crypto, a key with which to encrypt the indexeddb. + * + * If provided, it must be exactly 32 bytes of data. If both this and + * {@link MatrixClientPegAssignOpts.rustCryptoStorePassword} are undefined, + * the store will be unencrypted. + */ + rustCryptoStoreKey?: Uint8Array; + + /** + * If we are using Rust crypto, a password which will be used to derive a key to encrypt the store with. + * + * An alternative to {@link MatrixClientPegAssignOpts.rustCryptoStoreKey}. Ignored if `rustCryptoStoreKey` is set. + * + * Deriving a key from a password is (deliberately) a slow operation, so prefer to pass a `rustCryptoStoreKey` + * directly where possible. + */ + rustCryptoStorePassword?: string; +} + /** * Holds the current instance of the `MatrixClient` to use across the codebase. * Looking for an `MatrixClient`? Just look for the `MatrixClientPeg` on the peg @@ -94,14 +115,14 @@ export interface IMatrixClientPeg { unset(): void; /** - * Prepare the MatrixClient for use, including initialising the store and crypto, but do not start it + * Prepare the MatrixClient for use, including initialising the store and crypto, but do not start it. */ - assign(): Promise; + assign(opts?: MatrixClientPegAssignOpts): Promise; /** - * Prepare the MatrixClient for use, including initialising the store and crypto, and start it + * Prepare the MatrixClient for use, including initialising the store and crypto, and start it. */ - start(): Promise; + start(opts?: MatrixClientPegAssignOpts): Promise; /** * If we've registered a user ID we set this to the ID of the @@ -248,7 +269,10 @@ class MatrixClientPegClass implements IMatrixClientPeg { PlatformPeg.get()?.reload(); }; - public async assign(): Promise { + /** + * Implementation of {@link IMatrixClientPeg.assign}. + */ + public async assign(assignOpts: MatrixClientPegAssignOpts = {}): Promise { if (!this.matrixClient) { throw new Error("createClient must be called first"); } @@ -275,7 +299,7 @@ class MatrixClientPegClass implements IMatrixClientPeg { // try to initialise e2e on the new client if (!SettingsStore.getValue("lowBandwidth")) { - await this.initClientCrypto(); + await this.initClientCrypto(assignOpts.rustCryptoStoreKey, assignOpts.rustCryptoStorePassword); } const opts = utils.deepCopy(this.opts); @@ -301,8 +325,16 @@ class MatrixClientPegClass implements IMatrixClientPeg { /** * Attempt to initialize the crypto layer on a newly-created MatrixClient + * + * @param rustCryptoStoreKey - If we are using Rust crypto, a key with which to encrypt the indexeddb. + * If provided, it must be exactly 32 bytes of data. If both this and `rustCryptoStorePassword` are + * undefined, the store will be unencrypted. + * + * @param rustCryptoStorePassword - An alternative to `rustCryptoStoreKey`. Ignored if `rustCryptoStoreKey` is set. + * A password which will be used to derive a key to encrypt the store with. Deriving a key from a password is + * (deliberately) a slow operation, so prefer to pass a `rustCryptoStoreKey` directly where possible. */ - private async initClientCrypto(): Promise { + private async initClientCrypto(rustCryptoStoreKey?: Uint8Array, rustCryptoStorePassword?: string): Promise { if (!this.matrixClient) { throw new Error("createClient must be called first"); } @@ -338,7 +370,13 @@ class MatrixClientPegClass implements IMatrixClientPeg { // Now we can initialise the right crypto impl. if (useRustCrypto) { - await this.matrixClient.initRustCrypto(); + if (!rustCryptoStoreKey && !rustCryptoStorePassword) { + logger.error("Warning! Not using an encryption key for rust crypto store."); + } + await this.matrixClient.initRustCrypto({ + storageKey: rustCryptoStoreKey, + storagePassword: rustCryptoStorePassword, + }); StorageManager.setCryptoInitialised(true); // TODO: device dehydration and whathaveyou @@ -367,8 +405,11 @@ class MatrixClientPegClass implements IMatrixClientPeg { } } - public async start(): Promise { - const opts = await this.assign(); + /** + * Implementation of {@link IMatrixClientPeg.start}. + */ + public async start(assignOpts?: MatrixClientPegAssignOpts): Promise { + const opts = await this.assign(assignOpts); logger.log(`MatrixClientPeg: really starting MatrixClient`); await this.matrixClient!.startClient(opts); diff --git a/src/utils/tokens/pickling.ts b/src/utils/tokens/pickling.ts index c113559a69a..9e096bedef4 100644 --- a/src/utils/tokens/pickling.ts +++ b/src/utils/tokens/pickling.ts @@ -20,6 +20,20 @@ limitations under the License. import { encodeUnpaddedBase64 } from "matrix-js-sdk/src/matrix"; import { logger } from "matrix-js-sdk/src/logger"; +/** + * Encrypted format of a pickle key, as stored in IndexedDB. + */ +export interface EncryptedPickleKey { + /** The encrypted payload. */ + encrypted?: BufferSource; + + /** Initialisation vector for the encryption. */ + iv?: BufferSource; + + /** The encryption key which was used to encrypt the payload. */ + cryptoKey?: CryptoKey; +} + /** * Calculates the `additionalData` for the AES-GCM key used by the pickling processes. This * additional data is *not* encrypted, but *is* authenticated. The additional data is constructed @@ -46,6 +60,32 @@ export function getPickleAdditionalData(userId: string, deviceId: string): Uint8 return additionalData; } +/** + * Encrypt the given pickle key, ready for storage in the database. + * + * @param pickleKey - The key to be encrypted. + * @param userId - The user ID the pickle key belongs to. + * @param deviceId - The device ID the pickle key belongs to. + * + * @returns Data object ready for storing in indexeddb. + */ +export async function encryptPickleKey( + pickleKey: Uint8Array, + userId: string, + deviceId: string, +): Promise { + if (!crypto?.subtle) { + return undefined; + } + const cryptoKey = await crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, false, ["encrypt", "decrypt"]); + const iv = new Uint8Array(32); + crypto.getRandomValues(iv); + + const additionalData = getPickleAdditionalData(userId, deviceId); + const encrypted = await crypto.subtle.encrypt({ name: "AES-GCM", iv, additionalData }, cryptoKey, pickleKey); + return { encrypted, iv, cryptoKey }; +} + /** * Decrypts the provided data into a pickle key and base64-encodes it ready for use elsewhere. * @@ -59,7 +99,7 @@ export function getPickleAdditionalData(userId: string, deviceId: string): Uint8 * @returns A promise that resolves to the encoded pickle key, or undefined if the key cannot be built and encoded. */ export async function buildAndEncodePickleKey( - data: { encrypted?: BufferSource; iv?: BufferSource; cryptoKey?: CryptoKey } | undefined, + data: EncryptedPickleKey | undefined, userId: string, deviceId: string, ): Promise { diff --git a/test/Lifecycle-test.ts b/test/Lifecycle-test.ts index 4a6122f4709..55fbb0f1ff1 100644 --- a/test/Lifecycle-test.ts +++ b/test/Lifecycle-test.ts @@ -17,9 +17,10 @@ limitations under the License. import { Crypto } from "@peculiar/webcrypto"; import { logger } from "matrix-js-sdk/src/logger"; import * as MatrixJs from "matrix-js-sdk/src/matrix"; +import { decodeBase64, encodeUnpaddedBase64 } from "matrix-js-sdk/src/matrix"; import { setCrypto } from "matrix-js-sdk/src/crypto/crypto"; import * as MatrixCryptoAes from "matrix-js-sdk/src/crypto/aes"; -import { MockedObject } from "jest-mock"; +import { mocked, MockedObject } from "jest-mock"; import fetchMock from "fetch-mock-jest"; import StorageEvictedDialog from "../src/components/views/dialogs/StorageEvictedDialog"; @@ -27,11 +28,15 @@ import { logout, restoreFromLocalStorage, setLoggedIn } from "../src/Lifecycle"; import { MatrixClientPeg } from "../src/MatrixClientPeg"; import Modal from "../src/Modal"; import * as StorageAccess from "../src/utils/StorageAccess"; +import { idbSave } from "../src/utils/StorageAccess"; import { flushPromises, getMockClientWithEventEmitter, mockClientMethodsUser, mockPlatformPeg } from "./test-utils"; import { OidcClientStore } from "../src/stores/oidc/OidcClientStore"; import { makeDelegatedAuthConfig } from "./test-utils/oidc"; import { persistOidcAuthenticatedSettings } from "../src/utils/oidc/persistOidcSettings"; import { Action } from "../src/dispatcher/actions"; +import PlatformPeg from "../src/PlatformPeg"; +import { persistAccessTokenInStorage, persistRefreshTokenInStorage } from "../src/utils/tokens/tokens"; +import { encryptPickleKey } from "../src/utils/tokens/pickling"; const webCrypto = new Crypto(); @@ -220,22 +225,18 @@ describe("Lifecycle", () => { }); describe("when session is found in storage", () => { - beforeEach(() => { - initLocalStorageMock(localStorageSession); - initIdbMock(idbStorageSession); - }); - describe("guest account", () => { - it("should ignore guest accounts when ignoreGuest is true", async () => { + beforeEach(() => { initLocalStorageMock({ ...localStorageSession, mx_is_guest: "true" }); + initIdbMock(idbStorageSession); + }); + it("should ignore guest accounts when ignoreGuest is true", async () => { expect(await restoreFromLocalStorage({ ignoreGuest: true })).toEqual(false); expect(logger.log).toHaveBeenCalledWith(`Ignoring stored guest account: ${userId}`); }); it("should restore guest accounts when ignoreGuest is false", async () => { - initLocalStorageMock({ ...localStorageSession, mx_is_guest: "true" }); - expect(await restoreFromLocalStorage({ ignoreGuest: false })).toEqual(true); expect(MatrixClientPeg.replaceUsingCreds).toHaveBeenCalledWith( @@ -250,6 +251,11 @@ describe("Lifecycle", () => { }); describe("without a pickle key", () => { + beforeEach(() => { + initLocalStorageMock(localStorageSession); + initIdbMock(idbStorageSession); + }); + it("should persist credentials", async () => { expect(await restoreFromLocalStorage()).toEqual(true); @@ -272,7 +278,7 @@ describe("Lifecycle", () => { expect(localStorage.setItem).toHaveBeenCalledWith("mx_access_token", accessToken); }); - it("should create new matrix client with credentials", async () => { + it("should create and start new matrix client with credentials", async () => { expect(await restoreFromLocalStorage()).toEqual(true); expect(MatrixClientPeg.replaceUsingCreds).toHaveBeenCalledWith( @@ -288,6 +294,8 @@ describe("Lifecycle", () => { }, undefined, ); + + expect(MatrixClientPeg.start).toHaveBeenCalledWith({}); }); it("should remove fresh login flag from session storage", async () => { @@ -341,12 +349,20 @@ describe("Lifecycle", () => { }); }); - describe("with a pickle key", () => { + describe("with a normal pickle key", () => { + let pickleKey: string; + beforeEach(async () => { - initLocalStorageMock({}); + initLocalStorageMock(localStorageSession); initIdbMock({}); - // setup storage with a session with encrypted token - await setLoggedIn(credentials); + + // Create a pickle key, and store it, encrypted, in IDB. + pickleKey = (await PlatformPeg.get()!.createPickleKey(credentials.userId, credentials.deviceId))!; + + // Indicate that we should have a pickle key + localStorage.setItem("mx_has_pickle_key", "true"); + + await persistAccessTokenInStorage(credentials.accessToken, pickleKey); }); it("should persist credentials", async () => { @@ -383,9 +399,17 @@ describe("Lifecycle", () => { expect(localStorage.setItem).toHaveBeenCalledWith("mx_access_token", accessToken); }); - it("should create new matrix client with credentials", async () => { + it("should create and start new matrix client with credentials", async () => { + // Check that the rust crypto key is as expected. We have to do this during the call, as + // the buffer is cleared afterwards. + mocked(MatrixClientPeg.start).mockImplementation(async (opts) => { + expect(opts?.rustCryptoStoreKey).toEqual(decodeBase64(pickleKey)); + }); + + // Perform the restore expect(await restoreFromLocalStorage()).toEqual(true); + // Ensure that the expected calls were made expect(MatrixClientPeg.replaceUsingCreds).toHaveBeenCalledWith( { userId, @@ -394,23 +418,19 @@ describe("Lifecycle", () => { homeserverUrl, identityServerUrl, deviceId, - freshLogin: true, + freshLogin: false, guest: false, - pickleKey: expect.any(String), + pickleKey, }, undefined, ); + + expect(MatrixClientPeg.start).toHaveBeenCalledWith({ rustCryptoStoreKey: expect.any(Buffer) }); }); describe("with a refresh token", () => { beforeEach(async () => { - initLocalStorageMock({}); - initIdbMock({}); - // setup storage with a session with encrypted token - await setLoggedIn({ - ...credentials, - refreshToken, - }); + await persistRefreshTokenInStorage(refreshToken, pickleKey); }); it("should persist credentials", async () => { @@ -439,7 +459,7 @@ describe("Lifecycle", () => { deviceId, freshLogin: false, guest: false, - pickleKey: expect.any(String), + pickleKey: pickleKey, }, undefined, ); @@ -447,7 +467,60 @@ describe("Lifecycle", () => { }); }); + describe("with a non-standard pickle key", () => { + // Most pickle keys are 43 bytes of base64. Test what happens when it is something else. + let pickleKey: string; + + beforeEach(async () => { + initLocalStorageMock(localStorageSession); + initIdbMock({}); + + // Generate the pickle key. I don't *think* it's possible for there to be a pickle key + // which is not some amount of base64. + const rawPickleKey = new Uint8Array(10); + crypto.getRandomValues(rawPickleKey); + pickleKey = encodeUnpaddedBase64(rawPickleKey); + + // Store it, encrypted, in the db + await idbSave( + "pickleKey", + [userId, deviceId], + (await encryptPickleKey(rawPickleKey, userId, deviceId))!, + ); + + // Indicate that we should have a pickle key + localStorage.setItem("mx_has_pickle_key", "true"); + + await persistAccessTokenInStorage(credentials.accessToken, pickleKey); + }); + + it("should create and start new matrix client with credentials", async () => { + // Perform the restore + expect(await restoreFromLocalStorage()).toEqual(true); + + // Ensure that the expected calls were made + expect(MatrixClientPeg.replaceUsingCreds).toHaveBeenCalledWith( + { + userId, + // decrypted accessToken + accessToken, + homeserverUrl, + identityServerUrl, + deviceId, + freshLogin: false, + guest: false, + pickleKey, + }, + undefined, + ); + + expect(MatrixClientPeg.start).toHaveBeenCalledWith({ rustCryptoStorePassword: pickleKey }); + }); + }); + it("should proceed if server is not accessible", async () => { + initLocalStorageMock(localStorageSession); + initIdbMock(idbStorageSession); mockClient.isVersionSupported.mockRejectedValue(new Error("Oh, noes, the server is down!")); expect(await restoreFromLocalStorage()).toEqual(true); diff --git a/test/MatrixClientPeg-test.ts b/test/MatrixClientPeg-test.ts index 2ed08e0a21f..e5585f8cc3c 100644 --- a/test/MatrixClientPeg-test.ts +++ b/test/MatrixClientPeg-test.ts @@ -217,7 +217,7 @@ describe("MatrixClientPeg", () => { testPeg.safeGet().store.on = emitter.on.bind(emitter); const platform: any = { reload: jest.fn() }; PlatformPeg.set(platform); - await testPeg.assign(); + await testPeg.assign({}); emitter.emit("closed" as any); expect(platform.reload).toHaveBeenCalled(); }); @@ -229,7 +229,7 @@ describe("MatrixClientPeg", () => { PlatformPeg.set(platform); testPeg.safeGet().store.on = emitter.on.bind(emitter); const spy = jest.spyOn(Modal, "createDialog"); - await testPeg.assign(); + await testPeg.assign({}); emitter.emit("closed" as any); expect(spy).toHaveBeenCalled(); }); @@ -243,9 +243,10 @@ describe("MatrixClientPeg", () => { const mockInitCrypto = jest.spyOn(testPeg.safeGet(), "initCrypto").mockResolvedValue(undefined); const mockInitRustCrypto = jest.spyOn(testPeg.safeGet(), "initRustCrypto").mockResolvedValue(undefined); - await testPeg.start(); + const cryptoStoreKey = new Uint8Array([1, 2, 3, 4]); + await testPeg.start({ rustCryptoStoreKey: cryptoStoreKey }); expect(mockInitCrypto).not.toHaveBeenCalled(); - expect(mockInitRustCrypto).toHaveBeenCalledTimes(1); + expect(mockInitRustCrypto).toHaveBeenCalledWith({ storageKey: cryptoStoreKey }); // we should have stashed the setting in the settings store expect(mockSetValue).toHaveBeenCalledWith("feature_rust_crypto", null, SettingLevel.DEVICE, true); @@ -271,7 +272,7 @@ describe("MatrixClientPeg", () => { await testPeg.start(); expect(mockInitCrypto).toHaveBeenCalled(); - expect(mockInitRustCrypto).not.toHaveBeenCalledTimes(1); + expect(mockInitRustCrypto).not.toHaveBeenCalled(); // we should have stashed the setting in the settings store expect(mockSetValue).toHaveBeenCalledWith("feature_rust_crypto", null, SettingLevel.DEVICE, false); From 58664e7e7d90276f3bc2d7610313e9081b8b7b54 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 5 Jun 2024 13:50:01 +0100 Subject: [PATCH 28/73] Update dependency @playwright/test to v1.44.1 (#12562) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/yarn.lock b/yarn.lock index b78962b91b5..a04cc92348f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2005,11 +2005,11 @@ integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== "@playwright/test@^1.40.1": - version "1.44.0" - resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.44.0.tgz#ac7a764b5ee6a80558bdc0fcbc525fcb81f83465" - integrity sha512-rNX5lbNidamSUorBhB4XZ9SQTjAqfe5M+p37Z8ic0jPFBMo5iCtQz1kRWkEMg+rYOKSlVycpQmpqjSFq7LXOfg== + version "1.44.1" + resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.44.1.tgz#cc874ec31342479ad99838040e99b5f604299bcb" + integrity sha512-1hZ4TNvD5z9VuhNJ/walIjvMVvYkZKf71axoF/uiAqpntQJXpG64dlXhoDXE3OczPuTuvjf/M5KWFg5VAVUS3Q== dependencies: - playwright "1.44.0" + playwright "1.44.1" "@radix-ui/primitive@1.0.1": version "1.0.1" @@ -7749,17 +7749,17 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" -playwright-core@1.44.0: - version "1.44.0" - resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.44.0.tgz#316c4f0bca0551ffb88b6eb1c97bc0d2d861b0d5" - integrity sha512-ZTbkNpFfYcGWohvTTl+xewITm7EOuqIqex0c7dNZ+aXsbrLj0qI8XlGKfPpipjm0Wny/4Lt4CJsWJk1stVS5qQ== +playwright-core@1.44.1: + version "1.44.1" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.44.1.tgz#53ec975503b763af6fc1a7aa995f34bc09ff447c" + integrity sha512-wh0JWtYTrhv1+OSsLPgFzGzt67Y7BE/ZS3jEqgGBlp2ppp1ZDj8c+9IARNW4dwf1poq5MgHreEM2KV/GuR4cFA== -playwright@1.44.0: - version "1.44.0" - resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.44.0.tgz#22894e9b69087f6beb639249323d80fe2b5087ff" - integrity sha512-F9b3GUCLQ3Nffrfb6dunPOkE5Mh68tR7zN32L4jCk4FjQamgesGay7/dAAe1WaMEGV04DkdJfcJzjoCKygUaRQ== +playwright@1.44.1: + version "1.44.1" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.44.1.tgz#5634369d777111c1eea9180430b7a184028e7892" + integrity sha512-qr/0UJ5CFAtloI3avF95Y0L1xQo6r3LQArLIg/z/PoGJ6xa+EwzrwO5lpNr/09STxdHuUoP2mvuELJS+hLdtgg== dependencies: - playwright-core "1.44.0" + playwright-core "1.44.1" optionalDependencies: fsevents "2.3.2" From c95fd179ba3c81960b0e3ec5647f4f2d53e2a9c5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 5 Jun 2024 14:51:36 +0100 Subject: [PATCH 29/73] Update dependency @sentry/browser to v7.116.0 (#12563) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 146 +++++++++++++++++++++++++++--------------------------- 1 file changed, 73 insertions(+), 73 deletions(-) diff --git a/yarn.lock b/yarn.lock index a04cc92348f..6ce6076c0be 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2343,87 +2343,87 @@ dependencies: "@babel/runtime" "^7.13.10" -"@sentry-internal/feedback@7.114.0": - version "7.114.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-7.114.0.tgz#8a1c3d8bbd014c1823d30b9b1128eb244d357c3e" - integrity sha512-kUiLRUDZuh10QE9JbSVVLgqxFoD9eDPOzT0MmzlPuas8JlTmJuV4FtSANNcqctd5mBuLt2ebNXH0MhRMwyae4A== - dependencies: - "@sentry/core" "7.114.0" - "@sentry/types" "7.114.0" - "@sentry/utils" "7.114.0" - -"@sentry-internal/replay-canvas@7.114.0": - version "7.114.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-7.114.0.tgz#b81e2c2ebec01c436ad6e687e563ba456e33b615" - integrity sha512-6rTiqmKi/FYtesdM2TM2U+rh6BytdPjLP65KTUodtxohJ+r/3m+termj2o4BhIYPE1YYOZNmbZfwebkuQPmWeg== - dependencies: - "@sentry/core" "7.114.0" - "@sentry/replay" "7.114.0" - "@sentry/types" "7.114.0" - "@sentry/utils" "7.114.0" - -"@sentry-internal/tracing@7.114.0": - version "7.114.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.114.0.tgz#bdcd364f511e2de45db6e3004faf5685ca2e0f86" - integrity sha512-dOuvfJN7G+3YqLlUY4HIjyWHaRP8vbOgF+OsE5w2l7ZEn1rMAaUbPntAR8AF9GBA6j2zWNoSo8e7GjbJxVofSg== - dependencies: - "@sentry/core" "7.114.0" - "@sentry/types" "7.114.0" - "@sentry/utils" "7.114.0" +"@sentry-internal/feedback@7.116.0": + version "7.116.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-7.116.0.tgz#f1352b1a0d5fd7b7167775330ccf03bcc1b7892b" + integrity sha512-tmfO+RTCrhIWMs3yg8X0axhbjWRZLsldSfoXBgfjNCk/XwkYiVGp7WnYVbb+IO+01mHCsis9uaYOBggLgFRB5Q== + dependencies: + "@sentry/core" "7.116.0" + "@sentry/types" "7.116.0" + "@sentry/utils" "7.116.0" + +"@sentry-internal/replay-canvas@7.116.0": + version "7.116.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-7.116.0.tgz#1cd4a85f99dd3cd61120e087232f5cbea21d5eb2" + integrity sha512-Sy0ydY7A97JY/IFTIj8U25kHqR5rL9oBk3HFE5EK9Phw56irVhHzEwLWae0jlFeCQEWoBYqpPgO5vXsaYzrWvw== + dependencies: + "@sentry/core" "7.116.0" + "@sentry/replay" "7.116.0" + "@sentry/types" "7.116.0" + "@sentry/utils" "7.116.0" + +"@sentry-internal/tracing@7.116.0": + version "7.116.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.116.0.tgz#af3e4e264c440aa5525b5877a10b9a0f870b40e3" + integrity sha512-y5ppEmoOlfr77c/HqsEXR72092qmGYS4QE5gSz5UZFn9CiinEwGfEorcg2xIrrCuU7Ry/ZU2VLz9q3xd04drRA== + dependencies: + "@sentry/core" "7.116.0" + "@sentry/types" "7.116.0" + "@sentry/utils" "7.116.0" "@sentry/browser@^7.0.0": - version "7.114.0" - resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.114.0.tgz#b0741bff89189d16c8b19f0775fe6e078147ec33" - integrity sha512-ijJ0vOEY6U9JJADVYGkUbLrAbpGSQgA4zV+KW3tcsBLX9M1jaWq4BV1PWHdzDPPDhy4OgfOjIfaMb5BSPn1U+g== - dependencies: - "@sentry-internal/feedback" "7.114.0" - "@sentry-internal/replay-canvas" "7.114.0" - "@sentry-internal/tracing" "7.114.0" - "@sentry/core" "7.114.0" - "@sentry/integrations" "7.114.0" - "@sentry/replay" "7.114.0" - "@sentry/types" "7.114.0" - "@sentry/utils" "7.114.0" - -"@sentry/core@7.114.0": - version "7.114.0" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.114.0.tgz#3efe86b92a5379c44dfd0fd4685266b1a30fa898" - integrity sha512-YnanVlmulkjgZiVZ9BfY9k6I082n+C+LbZo52MTvx3FY6RE5iyiPMpaOh67oXEZRWcYQEGm+bKruRxLVP6RlbA== - dependencies: - "@sentry/types" "7.114.0" - "@sentry/utils" "7.114.0" - -"@sentry/integrations@7.114.0": - version "7.114.0" - resolved "https://registry.yarnpkg.com/@sentry/integrations/-/integrations-7.114.0.tgz#baf249cfa9e359510f41e486a75bf184db18927d" - integrity sha512-BJIBWXGKeIH0ifd7goxOS29fBA8BkEgVVCahs6xIOXBjX1IRS6PmX0zYx/GP23nQTfhJiubv2XPzoYOlZZmDxg== - dependencies: - "@sentry/core" "7.114.0" - "@sentry/types" "7.114.0" - "@sentry/utils" "7.114.0" + version "7.116.0" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.116.0.tgz#950c1a9672bf886c556c2c7b9198b90189e3f0c2" + integrity sha512-2aosATT5qE+QLKgTmyF9t5Emsluy1MBczYNuPmLhDxGNfB+MA86S8u7Hb0CpxdwjS0nt14gmbiOtJHoeAF3uTw== + dependencies: + "@sentry-internal/feedback" "7.116.0" + "@sentry-internal/replay-canvas" "7.116.0" + "@sentry-internal/tracing" "7.116.0" + "@sentry/core" "7.116.0" + "@sentry/integrations" "7.116.0" + "@sentry/replay" "7.116.0" + "@sentry/types" "7.116.0" + "@sentry/utils" "7.116.0" + +"@sentry/core@7.116.0": + version "7.116.0" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.116.0.tgz#7cff43134878a696b2b3b981ae384ec3db9ac8c3" + integrity sha512-J6Wmjjx+o7RwST0weTU1KaKUAlzbc8MGkJV1rcHM9xjNTWTva+nrcCM3vFBagnk2Gm/zhwv3h0PvWEqVyp3U1Q== + dependencies: + "@sentry/types" "7.116.0" + "@sentry/utils" "7.116.0" + +"@sentry/integrations@7.116.0": + version "7.116.0" + resolved "https://registry.yarnpkg.com/@sentry/integrations/-/integrations-7.116.0.tgz#b641342249da76cd2feb2fb5511424b66f967449" + integrity sha512-UZb60gaF+7veh1Yv79RiGvgGYOnU6xA97H+hI6tKgc1uT20YpItO4X56Vhp0lvyEyUGFZzBRRH1jpMDPNGPkqw== + dependencies: + "@sentry/core" "7.116.0" + "@sentry/types" "7.116.0" + "@sentry/utils" "7.116.0" localforage "^1.8.1" -"@sentry/replay@7.114.0": - version "7.114.0" - resolved "https://registry.yarnpkg.com/@sentry/replay/-/replay-7.114.0.tgz#f552e4803cacb233a2f5f2a4afbf5bed9052a744" - integrity sha512-UvEajoLIX9n2poeW3R4Ybz7D0FgCGXoFr/x/33rdUEMIdTypknxjJWxg6fJngIduzwrlrvWpvP8QiZXczYQy2Q== +"@sentry/replay@7.116.0": + version "7.116.0" + resolved "https://registry.yarnpkg.com/@sentry/replay/-/replay-7.116.0.tgz#cde921133c8927be92d60baf03c2b0aea73380f1" + integrity sha512-OrpDtV54pmwZuKp3g7PDiJg6ruRMJKOCzK08TF7IPsKrr4x4UQn56rzMOiABVuTjuS8lNfAWDar6c6vxXFz5KA== dependencies: - "@sentry-internal/tracing" "7.114.0" - "@sentry/core" "7.114.0" - "@sentry/types" "7.114.0" - "@sentry/utils" "7.114.0" + "@sentry-internal/tracing" "7.116.0" + "@sentry/core" "7.116.0" + "@sentry/types" "7.116.0" + "@sentry/utils" "7.116.0" -"@sentry/types@7.114.0": - version "7.114.0" - resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.114.0.tgz#ab8009d5f6df23b7342121083bed34ee2452e856" - integrity sha512-tsqkkyL3eJtptmPtT0m9W/bPLkU7ILY7nvwpi1hahA5jrM7ppoU0IMaQWAgTD+U3rzFH40IdXNBFb8Gnqcva4w== +"@sentry/types@7.116.0": + version "7.116.0" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.116.0.tgz#0be3434e7e53c86db4993e668af1c3a65bfb7519" + integrity sha512-QCCvG5QuQrwgKzV11lolNQPP2k67Q6HHD9vllZ/C4dkxkjoIym8Gy+1OgAN3wjsR0f/kG9o5iZyglgNpUVRapQ== -"@sentry/utils@7.114.0": - version "7.114.0" - resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.114.0.tgz#59d30a79f4acff3c9268de0b345f0bcbc6335112" - integrity sha512-319N90McVpupQ6vws4+tfCy/03AdtsU0MurIE4+W5cubHME08HtiEWlfacvAxX+yuKFhvdsO4K4BB/dj54ideg== +"@sentry/utils@7.116.0": + version "7.116.0" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.116.0.tgz#f32463ab10f76f464274233a9df202e5357d17ff" + integrity sha512-Vn9fcvwTq91wJvCd7WTMWozimqMi+dEZ3ie3EICELC2diONcN16ADFdzn65CQQbYwmUzRjN9EjDN2k41pKZWhQ== dependencies: - "@sentry/types" "7.114.0" + "@sentry/types" "7.116.0" "@sinclair/typebox@^0.27.8": version "0.27.8" From 99b24851d86e5260dc517f741b423f6f2c9f2c1c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 5 Jun 2024 17:29:47 +0100 Subject: [PATCH 30/73] Update dependency stylelint to v16.6.1 (#12565) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 154 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 97 insertions(+), 57 deletions(-) diff --git a/yarn.lock b/yarn.lock index 6ce6076c0be..844e37d4882 100644 --- a/yarn.lock +++ b/yarn.lock @@ -55,12 +55,12 @@ "@nicolo-ribaudo/chokidar-2" "2.1.8-no-fsevents.3" chokidar "^3.4.0" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.23.5", "@babel/code-frame@^7.24.2": - version "7.24.2" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.2.tgz#718b4b19841809a58b29b68cde80bc5e1aa6d9ae" - integrity sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ== +"@babel/code-frame@^7.0.0": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.7.tgz#882fd9e09e8ee324e496bd040401c6f046ef4465" + integrity sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA== dependencies: - "@babel/highlight" "^7.24.2" + "@babel/highlight" "^7.24.7" picocolors "^1.0.0" "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.22.13": @@ -79,6 +79,14 @@ "@babel/highlight" "^7.22.13" chalk "^2.4.2" +"@babel/code-frame@^7.23.5", "@babel/code-frame@^7.24.2": + version "7.24.2" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.2.tgz#718b4b19841809a58b29b68cde80bc5e1aa6d9ae" + integrity sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ== + dependencies: + "@babel/highlight" "^7.24.2" + picocolors "^1.0.0" + "@babel/compat-data@^7.20.5", "@babel/compat-data@^7.21.5": version "7.21.7" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.21.7.tgz#61caffb60776e49a57ba61a88f02bedd8714f6bc" @@ -425,11 +433,16 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz#f99c36d3593db9540705d0739a1f10b5e20c696e" integrity sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ== -"@babel/helper-validator-identifier@^7.22.20", "@babel/helper-validator-identifier@^7.24.5": +"@babel/helper-validator-identifier@^7.22.20": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz#918b1a7fa23056603506370089bd990d8720db62" integrity sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA== +"@babel/helper-validator-identifier@^7.24.5", "@babel/helper-validator-identifier@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db" + integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w== + "@babel/helper-validator-option@^7.21.0": version "7.21.0" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz#8224c7e13ace4bafdc4004da2cf064ef42673180" @@ -486,12 +499,12 @@ js-tokens "^4.0.0" picocolors "^1.0.0" -"@babel/highlight@^7.24.2": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.5.tgz#bc0613f98e1dd0720e99b2a9ee3760194a704b6e" - integrity sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw== +"@babel/highlight@^7.24.2", "@babel/highlight@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.7.tgz#a05ab1df134b286558aae0ed41e6c5f731bf409d" + integrity sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw== dependencies: - "@babel/helper-validator-identifier" "^7.24.5" + "@babel/helper-validator-identifier" "^7.24.7" chalk "^2.4.2" js-tokens "^4.0.0" picocolors "^1.0.0" @@ -1449,27 +1462,27 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" -"@csstools/css-parser-algorithms@^2.6.1": +"@csstools/css-parser-algorithms@^2.6.3": version "2.6.3" resolved "https://registry.yarnpkg.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.6.3.tgz#b5e7eb2bd2a42e968ef61484f1490a8a4148a8eb" integrity sha512-xI/tL2zxzEbESvnSxwFgwvy5HS00oCXxL4MLs6HUiDcYfwowsoQaABKxUElp1ARITrINzBnsECOc1q0eg2GOrA== -"@csstools/css-tokenizer@^2.2.4": +"@csstools/css-tokenizer@^2.3.1": version "2.3.1" resolved "https://registry.yarnpkg.com/@csstools/css-tokenizer/-/css-tokenizer-2.3.1.tgz#3d47e101ad48d815a4bdce8159fb5764f087f17a" integrity sha512-iMNHTyxLbBlWIfGtabT157LH9DUx9X8+Y3oymFEuMj8HNc+rpE3dPFGFgHjpKfjeFDjLjYIAIhXPGvS2lKxL9g== -"@csstools/media-query-list-parser@^2.1.9": +"@csstools/media-query-list-parser@^2.1.11": version "2.1.11" resolved "https://registry.yarnpkg.com/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.11.tgz#465aa42f268599729350e305e1ae14a30c1daf51" integrity sha512-uox5MVhvNHqitPP+SynrB1o8oPxPMt2JLgp5ghJOWf54WGQ5OKu47efne49r1SWqs3wRP8xSWjnO9MBKxhB1dA== -"@csstools/selector-specificity@^3.0.3": +"@csstools/selector-specificity@^3.1.1": version "3.1.1" resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-3.1.1.tgz#63085d2995ca0f0e55aa8b8a07d69bfd48b844fe" integrity sha512-a7cxGcJ2wIlMFLlh8z2ONm+715QkPHiyJcxwQlKOz/03GPw1COpfhcmC9wm4xlZfp//jWHNNMwzjtqHXVWU9KA== -"@dual-bundle/import-meta-resolve@^4.0.0": +"@dual-bundle/import-meta-resolve@^4.1.0": version "4.1.0" resolved "https://registry.yarnpkg.com/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz#519c1549b0e147759e7825701ecffd25e5819f7b" integrity sha512-+nxncfwHM5SgAtrVzgpzJOI1ol0PkumhVo469KCf9lUi21IGcY90G98VuHm9VRrUypmAzawAHO9bs6hqeADaVg== @@ -3227,9 +3240,9 @@ ajv@^6.12.4, ajv@^6.12.5: uri-js "^4.2.2" ajv@^8.0.1: - version "8.13.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.13.0.tgz#a3939eaec9fb80d217ddf0c3376948c023f28c91" - integrity sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA== + version "8.16.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.16.0.tgz#22e2a92b94f005f7e0f9c9d39652ef0b8f6f0cb4" + integrity sha512-F0twR8U1ZU67JIEtekUcLkXkoO5mMMmgGD8sK/xUFzJ805jxHQl92hImFAqqXMyMYjSPOyUPAwHYhB72g5sTXw== dependencies: fast-deep-equal "^3.1.3" json-schema-traverse "^1.0.0" @@ -3677,7 +3690,14 @@ brace-expansion@^2.0.1: dependencies: balanced-match "^1.0.0" -braces@^3.0.2, braces@~3.0.2: +braces@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== @@ -4233,7 +4253,7 @@ debug@2.6.9: dependencies: ms "2.0.0" -debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -4247,6 +4267,13 @@ debug@^3.2.7: dependencies: ms "^2.1.1" +debug@^4.3.4: + version "4.3.5" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e" + integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== + dependencies: + ms "2.1.2" + decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -5339,12 +5366,12 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" -file-entry-cache@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f" - integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== +file-entry-cache@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-9.0.0.tgz#4478e7ceaa5191fa9676a2daa7030211c31b1e7e" + integrity sha512-6MgEugi8p2tiUhqO7GnPsmbCCzj0YRCwwaTbpGRyKZesjRSzkqkAE9fPp7V2yMs5hwfgbQLgdvSSkGNg1s5Uvw== dependencies: - flat-cache "^4.0.0" + flat-cache "^5.0.0" file-saver@^2.0.5: version "2.0.5" @@ -5356,10 +5383,10 @@ filesize@10.1.2: resolved "https://registry.yarnpkg.com/filesize/-/filesize-10.1.2.tgz#33bb71c5c134102499f1bc36e6f2863137f6cb0c" integrity sha512-Dx770ai81ohflojxhU+oG+Z2QGvKdYxgEr9OSA8UVrqhwNHjfH9A8f5NKfg83fEH8ZFA5N5llJo5T3PIoZ4CRA== -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== +fill-range@^7.0.1, fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== dependencies: to-regex-range "^5.0.1" @@ -5416,12 +5443,12 @@ flat-cache@^3.0.4: flatted "^3.1.0" rimraf "^3.0.2" -flat-cache@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c" - integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== +flat-cache@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-5.0.0.tgz#26c4da7b0f288b408bb2b506b2cb66c240ddf062" + integrity sha512-JrqFmyUl2PnPi1OvLyTVHnQvwQ0S+e6lGSwu8OkAZlSaNIZciTY2H/cOOROxsBA1m/LZNHDsqAgDZt6akWcjsQ== dependencies: - flatted "^3.2.9" + flatted "^3.3.1" keyv "^4.5.4" flatted@^3.1.0: @@ -5429,7 +5456,7 @@ flatted@^3.1.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== -flatted@^3.2.9: +flatted@^3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== @@ -6884,6 +6911,11 @@ known-css-properties@^0.30.0: resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.30.0.tgz#34dd1f39c805c65a6dfa6ea76206b20dc523dd96" integrity sha512-VSWXYUnsPu9+WYKkfmJyLKtIvaRJi1kXUqVmBACORXZQxT5oZDsoZ2vQP+bQFDnWtpI/4eq3MLoRMjI2fnLzTQ== +known-css-properties@^0.31.0: + version "0.31.0" + resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.31.0.tgz#5c8d9d8777b3ca09482b2397f6a241e5d69a1023" + integrity sha512-sBPIUGTNF0czz0mwGGUoKKJC8Q7On1GPbCSFPfyEsfHb2DyBG0Y4QtV+EVWpINSaiGKZblDNuF5AezxSgOhesQ== + language-subtag-registry@^0.3.20: version "0.3.22" resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz#2e1500861b2e457eba7e7ae86877cbd08fa1fd1d" @@ -7248,12 +7280,12 @@ methods@~1.1.2: resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== -micromatch@^4.0.4, micromatch@^4.0.5: - version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== +micromatch@^4.0.4, micromatch@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.7.tgz#33e8190d9fe474a9895525f5618eee136d46c2e5" + integrity sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q== dependencies: - braces "^3.0.2" + braces "^3.0.3" picomatch "^2.3.1" mime-db@1.52.0: @@ -7715,7 +7747,7 @@ pbf@^3.2.1: ieee754 "^1.1.12" resolve-protobuf-schema "^2.1.0" -picocolors@^1.0.0: +picocolors@^1.0.0, picocolors@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== @@ -7810,7 +7842,7 @@ postcss-scss@^4.0.4: resolved "https://registry.yarnpkg.com/postcss-scss/-/postcss-scss-4.0.9.tgz#a03c773cd4c9623cb04ce142a52afcec74806685" integrity sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A== -postcss-selector-parser@^6.0.15, postcss-selector-parser@^6.0.16: +postcss-selector-parser@^6.0.15: version "6.0.16" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz#3b88b9f5c5abd989ef4e2fc9ec8eedd34b20fb04" integrity sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw== @@ -7818,6 +7850,14 @@ postcss-selector-parser@^6.0.15, postcss-selector-parser@^6.0.16: cssesc "^3.0.0" util-deprecate "^1.0.2" +postcss-selector-parser@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.0.tgz#49694cb4e7c649299fea510a29fa6577104bcf53" + integrity sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + postcss-value-parser@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" @@ -8959,15 +8999,15 @@ stylelint-scss@^6.0.0: postcss-value-parser "^4.2.0" stylelint@^16.1.0: - version "16.5.0" - resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-16.5.0.tgz#4e3aff7cc2294fa54da729b972a6c38bf2a584a0" - integrity sha512-IlCBtVrG+qTy3v+tZTk50W8BIomjY/RUuzdrDqdnlCYwVuzXtPbiGfxYqtyYAyOMcb+195zRsuHn6tgfPmFfbw== - dependencies: - "@csstools/css-parser-algorithms" "^2.6.1" - "@csstools/css-tokenizer" "^2.2.4" - "@csstools/media-query-list-parser" "^2.1.9" - "@csstools/selector-specificity" "^3.0.3" - "@dual-bundle/import-meta-resolve" "^4.0.0" + version "16.6.1" + resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-16.6.1.tgz#84735aca2bb5cde535572b7a9b878d2ec983a570" + integrity sha512-yNgz2PqWLkhH2hw6X9AweV9YvoafbAD5ZsFdKN9BvSDVwGvPh+AUIrn7lYwy1S7IHmtFin75LLfX1m0D2tHu8Q== + dependencies: + "@csstools/css-parser-algorithms" "^2.6.3" + "@csstools/css-tokenizer" "^2.3.1" + "@csstools/media-query-list-parser" "^2.1.11" + "@csstools/selector-specificity" "^3.1.1" + "@dual-bundle/import-meta-resolve" "^4.1.0" balanced-match "^2.0.0" colord "^2.9.3" cosmiconfig "^9.0.0" @@ -8976,7 +9016,7 @@ stylelint@^16.1.0: debug "^4.3.4" fast-glob "^3.3.2" fastest-levenshtein "^1.0.16" - file-entry-cache "^8.0.0" + file-entry-cache "^9.0.0" global-modules "^2.0.0" globby "^11.1.0" globjoin "^0.1.4" @@ -8984,16 +9024,16 @@ stylelint@^16.1.0: ignore "^5.3.1" imurmurhash "^0.1.4" is-plain-object "^5.0.0" - known-css-properties "^0.30.0" + known-css-properties "^0.31.0" mathml-tag-names "^2.1.3" meow "^13.2.0" - micromatch "^4.0.5" + micromatch "^4.0.7" normalize-path "^3.0.0" - picocolors "^1.0.0" + picocolors "^1.0.1" postcss "^8.4.38" postcss-resolve-nested-selector "^0.1.1" postcss-safe-parser "^7.0.0" - postcss-selector-parser "^6.0.16" + postcss-selector-parser "^6.1.0" postcss-value-parser "^4.2.0" resolve-from "^5.0.0" string-width "^4.2.3" From e867196c15c5c5acbcbdaf80bbefae6e6404dad4 Mon Sep 17 00:00:00 2001 From: ElementRobot Date: Thu, 6 Jun 2024 07:29:42 +0100 Subject: [PATCH 31/73] [create-pull-request] automated change (#12581) Co-authored-by: github-merge-queue --- playwright/plugins/homeserver/synapse/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright/plugins/homeserver/synapse/index.ts b/playwright/plugins/homeserver/synapse/index.ts index 00807a0b8a4..b7c7b7075cf 100644 --- a/playwright/plugins/homeserver/synapse/index.ts +++ b/playwright/plugins/homeserver/synapse/index.ts @@ -28,7 +28,7 @@ import { randB64Bytes } from "../../utils/rand"; // Docker tag to use for `matrixdotorg/synapse` image. // We target a specific digest as every now and then a Synapse update will break our CI. // This digest is updated by the playwright-image-updates.yaml workflow periodically. -const DOCKER_TAG = "develop@sha256:13d471114317080014318b4fe41b67ffda6ac11fdfd866e03be7a77e6cb08a4e"; +const DOCKER_TAG = "develop@sha256:a832fd5bcf98a0056b23007d1ef6c749bfa922267c7638a32b447e8c14e30588"; async function cfgDirFromTemplate(opts: StartHomeserverOpts): Promise> { const templateDir = path.join(__dirname, "templates", opts.template); From ca7760789b786c9c45e86012b1f53a85f2f7d66e Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 6 Jun 2024 09:19:49 +0100 Subject: [PATCH 32/73] Use LegacyRendezvousFailureReason over RendezvousFailureReason (#12578) --- src/components/views/auth/LoginWithQR.tsx | 24 +++++++++++-------- src/components/views/auth/LoginWithQRFlow.tsx | 2 +- .../settings/devices/LoginWithQR-test.tsx | 10 ++++---- .../settings/devices/LoginWithQRFlow-test.tsx | 4 ++-- 4 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/components/views/auth/LoginWithQR.tsx b/src/components/views/auth/LoginWithQR.tsx index 68060f9f284..e1e87104d24 100644 --- a/src/components/views/auth/LoginWithQR.tsx +++ b/src/components/views/auth/LoginWithQR.tsx @@ -15,7 +15,11 @@ limitations under the License. */ import React from "react"; -import { MSC3906Rendezvous, MSC3906RendezvousPayload, RendezvousFailureReason } from "matrix-js-sdk/src/rendezvous"; +import { + MSC3906Rendezvous, + MSC3906RendezvousPayload, + LegacyRendezvousFailureReason, +} from "matrix-js-sdk/src/rendezvous"; import { MSC3886SimpleHttpRendezvousTransport } from "matrix-js-sdk/src/rendezvous/transports"; import { MSC3903ECDHPayload, MSC3903ECDHv2RendezvousChannel } from "matrix-js-sdk/src/rendezvous/channels"; import { logger } from "matrix-js-sdk/src/logger"; @@ -44,7 +48,7 @@ export enum LoginWithQRFailureReason { RateLimited = "rate_limited", } -export type FailureReason = RendezvousFailureReason | LoginWithQRFailureReason; +export type FailureReason = LegacyRendezvousFailureReason | LoginWithQRFailureReason; // n.b MSC3886/MSC3903/MSC3906 that this is based on are now closed. // However, we want to keep this implementation around for some time. @@ -81,7 +85,7 @@ export default class LoginWithQR extends React.Component { if (this.state.rendezvous) { const rendezvous = this.state.rendezvous; rendezvous.onFailure = undefined; - await rendezvous.cancel(RendezvousFailureReason.UserCancelled); + await rendezvous.cancel(LegacyRendezvousFailureReason.UserCancelled); this.setState({ rendezvous: undefined }); } if (mode === Mode.Show) { @@ -94,7 +98,7 @@ export default class LoginWithQR extends React.Component { // eslint-disable-next-line react/no-direct-mutation-state this.state.rendezvous.onFailure = undefined; // calling cancel will call close() as well to clean up the resources - this.state.rendezvous.cancel(RendezvousFailureReason.UserCancelled).then(() => {}); + this.state.rendezvous.cancel(LegacyRendezvousFailureReason.UserCancelled).then(() => {}); } } @@ -140,7 +144,7 @@ export default class LoginWithQR extends React.Component { this.setState({ phase: Phase.Error, failureReason: LoginWithQRFailureReason.RateLimited }); return; } - this.setState({ phase: Phase.Error, failureReason: RendezvousFailureReason.Unknown }); + this.setState({ phase: Phase.Error, failureReason: LegacyRendezvousFailureReason.Unknown }); } }; @@ -170,7 +174,7 @@ export default class LoginWithQR extends React.Component { }); } catch (e) { logger.error("Error whilst generating QR code", e); - this.setState({ phase: Phase.Error, failureReason: RendezvousFailureReason.HomeserverLacksSupport }); + this.setState({ phase: Phase.Error, failureReason: LegacyRendezvousFailureReason.HomeserverLacksSupport }); return; } @@ -181,12 +185,12 @@ export default class LoginWithQR extends React.Component { logger.error("Error whilst doing QR login", e); // only set to error phase if it hasn't already been set by onFailure or similar if (this.state.phase !== Phase.Error) { - this.setState({ phase: Phase.Error, failureReason: RendezvousFailureReason.Unknown }); + this.setState({ phase: Phase.Error, failureReason: LegacyRendezvousFailureReason.Unknown }); } } }; - private onFailure = (reason: RendezvousFailureReason): void => { + private onFailure = (reason: LegacyRendezvousFailureReason): void => { logger.info(`Rendezvous failed: ${reason}`); this.setState({ phase: Phase.Error, failureReason: reason }); }; @@ -202,7 +206,7 @@ export default class LoginWithQR extends React.Component { private onClick = async (type: Click): Promise => { switch (type) { case Click.Cancel: - await this.state.rendezvous?.cancel(RendezvousFailureReason.UserCancelled); + await this.state.rendezvous?.cancel(LegacyRendezvousFailureReason.UserCancelled); this.reset(); this.props.onFinished(false); break; @@ -219,7 +223,7 @@ export default class LoginWithQR extends React.Component { await this.updateMode(this.props.mode); break; case Click.Back: - await this.state.rendezvous?.cancel(RendezvousFailureReason.UserCancelled); + await this.state.rendezvous?.cancel(LegacyRendezvousFailureReason.UserCancelled); this.props.onFinished(false); break; } diff --git a/src/components/views/auth/LoginWithQRFlow.tsx b/src/components/views/auth/LoginWithQRFlow.tsx index 6a6b78a29b8..c8a786992af 100644 --- a/src/components/views/auth/LoginWithQRFlow.tsx +++ b/src/components/views/auth/LoginWithQRFlow.tsx @@ -15,7 +15,7 @@ limitations under the License. */ import React, { ReactNode } from "react"; -import { RendezvousFailureReason as LegacyRendezvousFailureReason } from "matrix-js-sdk/src/rendezvous"; +import { LegacyRendezvousFailureReason } from "matrix-js-sdk/src/rendezvous"; import { Icon as ChevronLeftIcon } from "@vector-im/compound-design-tokens/icons/chevron-left.svg"; import { Icon as CheckCircleSolidIcon } from "@vector-im/compound-design-tokens/icons/check-circle-solid.svg"; import { Icon as ErrorIcon } from "@vector-im/compound-design-tokens/icons/error.svg"; diff --git a/test/components/views/settings/devices/LoginWithQR-test.tsx b/test/components/views/settings/devices/LoginWithQR-test.tsx index 7a1a269064e..727ab6a017e 100644 --- a/test/components/views/settings/devices/LoginWithQR-test.tsx +++ b/test/components/views/settings/devices/LoginWithQR-test.tsx @@ -17,7 +17,7 @@ limitations under the License. import { cleanup, render, waitFor } from "@testing-library/react"; import { MockedObject, mocked } from "jest-mock"; import React from "react"; -import { MSC3906Rendezvous, RendezvousFailureReason } from "matrix-js-sdk/src/rendezvous"; +import { MSC3906Rendezvous, LegacyRendezvousFailureReason } from "matrix-js-sdk/src/rendezvous"; import { HTTPError, LoginTokenPostResponse } from "matrix-js-sdk/src/matrix"; import LoginWithQR from "../../../../../src/components/views/auth/LoginWithQR"; @@ -111,7 +111,7 @@ describe("", () => { await waitFor(() => expect(mockedFlow).toHaveBeenLastCalledWith({ phase: Phase.Error, - failureReason: RendezvousFailureReason.HomeserverLacksSupport, + failureReason: LegacyRendezvousFailureReason.HomeserverLacksSupport, onClick: expect.any(Function), }), ); @@ -125,7 +125,7 @@ describe("", () => { await waitFor(() => expect(mockedFlow).toHaveBeenLastCalledWith({ phase: Phase.Error, - failureReason: RendezvousFailureReason.Unknown, + failureReason: LegacyRendezvousFailureReason.Unknown, onClick: expect.any(Function), }), ); @@ -160,7 +160,7 @@ describe("", () => { const onClick = mockedFlow.mock.calls[0][0].onClick; await onClick(Click.Cancel); expect(onFinished).toHaveBeenCalledWith(false); - expect(rendezvous.cancel).toHaveBeenCalledWith(RendezvousFailureReason.UserCancelled); + expect(rendezvous.cancel).toHaveBeenCalledWith(LegacyRendezvousFailureReason.UserCancelled); // try again onClick(Click.TryAgain); @@ -205,7 +205,7 @@ describe("", () => { const onClick = mockedFlow.mock.calls[0][0].onClick; await onClick(Click.Back); expect(onFinished).toHaveBeenCalledWith(false); - expect(rendezvous.cancel).toHaveBeenCalledWith(RendezvousFailureReason.UserCancelled); + expect(rendezvous.cancel).toHaveBeenCalledWith(LegacyRendezvousFailureReason.UserCancelled); }); test("render QR then decline", async () => { diff --git a/test/components/views/settings/devices/LoginWithQRFlow-test.tsx b/test/components/views/settings/devices/LoginWithQRFlow-test.tsx index 21863c84a49..91004abc264 100644 --- a/test/components/views/settings/devices/LoginWithQRFlow-test.tsx +++ b/test/components/views/settings/devices/LoginWithQRFlow-test.tsx @@ -16,7 +16,7 @@ limitations under the License. import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react"; import React from "react"; -import { RendezvousFailureReason } from "matrix-js-sdk/src/rendezvous"; +import { LegacyRendezvousFailureReason } from "matrix-js-sdk/src/rendezvous"; import LoginWithQRFlow from "../../../../../src/components/views/auth/LoginWithQRFlow"; import { LoginWithQRFailureReason, FailureReason } from "../../../../../src/components/views/auth/LoginWithQR"; @@ -99,7 +99,7 @@ describe("", () => { describe("errors", () => { for (const failureReason of [ - ...Object.values(RendezvousFailureReason), + ...Object.values(LegacyRendezvousFailureReason), ...Object.values(LoginWithQRFailureReason), ]) { it(`renders ${failureReason}`, async () => { From 1677ed1be0e45206f31f3c75736c55c06b7fc817 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 6 Jun 2024 09:57:28 +0100 Subject: [PATCH 33/73] MSC4108 support OIDC QR code login (#12370) Co-authored-by: Hugh Nimmo-Smith --- .eslintrc.js | 2 - .github/workflows/static_analysis.yaml | 2 +- .github/workflows/tests.yml | 2 +- res/css/structures/_UserMenu.pcss | 4 + src/components/structures/MatrixChat.tsx | 2 +- src/components/structures/UserMenu.tsx | 60 +- .../views/auth/LoginWithQR-types.ts | 10 +- src/components/views/auth/LoginWithQR.tsx | 251 ++++-- src/components/views/auth/LoginWithQRFlow.tsx | 149 +++- .../views/dialogs/UserSettingsDialog.tsx | 14 +- .../settings/devices/LoginWithQRSection.tsx | 44 +- .../settings/tabs/user/SessionManagerTab.tsx | 42 +- src/dispatcher/payloads/OpenToTabPayload.ts | 5 + src/i18n/strings/en_EN.json | 17 +- test/components/structures/UserMenu-test.tsx | 54 +- .../settings/devices/LoginWithQR-test.tsx | 597 ++++++++------ .../settings/devices/LoginWithQRFlow-test.tsx | 34 +- .../devices/LoginWithQRSection-test.tsx | 146 ++-- .../LoginWithQRFlow-test.tsx.snap | 746 +++++++++++------- .../LoginWithQRSection-test.tsx.snap | 10 +- .../tabs/user/SessionManagerTab-test.tsx | 74 +- test/test-utils/client.ts | 4 +- test/test-utils/oidc.ts | 1 + test/test-utils/test-utils.ts | 1 + 24 files changed, 1548 insertions(+), 723 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index caeeca403d2..4bec4e83203 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -98,8 +98,6 @@ module.exports = { "!matrix-js-sdk/src/secret-storage", "!matrix-js-sdk/src/room-hierarchy", "!matrix-js-sdk/src/rendezvous", - "!matrix-js-sdk/src/rendezvous/transports", - "!matrix-js-sdk/src/rendezvous/channels", "!matrix-js-sdk/src/indexeddb-worker", "!matrix-js-sdk/src/pushprocessor", "!matrix-js-sdk/src/extensible_events_v1", diff --git a/.github/workflows/static_analysis.yaml b/.github/workflows/static_analysis.yaml index 070ac5f8544..6e225467afa 100644 --- a/.github/workflows/static_analysis.yaml +++ b/.github/workflows/static_analysis.yaml @@ -27,7 +27,7 @@ jobs: cache: "yarn" - name: Install Deps - run: "./scripts/ci/install-deps.sh --ignore-scripts" + run: "./scripts/ci/install-deps.sh" - name: Typecheck run: "yarn run lint:types" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7089569f73e..3815c4fb4cc 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -47,7 +47,7 @@ jobs: cache: "yarn" - name: Install Deps - run: "./scripts/ci/install-deps.sh --ignore-scripts" + run: "./scripts/ci/install-deps.sh" env: JS_SDK_GITHUB_BASE_REF: ${{ inputs.matrix-js-sdk-sha }} diff --git a/res/css/structures/_UserMenu.pcss b/res/css/structures/_UserMenu.pcss index f25c15e48e6..5f8a6a70a1b 100644 --- a/res/css/structures/_UserMenu.pcss +++ b/res/css/structures/_UserMenu.pcss @@ -207,6 +207,10 @@ limitations under the License. .mx_UserMenu_iconSignOut::before { mask-image: url("$(res)/img/element-icons/leave.svg"); } + + .mx_UserMenu_iconQr::before { + mask-image: url("@vector-im/compound-design-tokens/icons/qr-code.svg"); + } } .mx_UserMenu_CustomStatusSection { diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 5e50f68b48c..4049248111f 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -764,7 +764,7 @@ export default class MatrixChat extends React.PureComponent { const tabPayload = payload as OpenToTabPayload; Modal.createDialog( UserSettingsDialog, - { initialTabId: tabPayload.initialTabId as UserTab, sdkContext: this.stores }, + { ...payload.props, initialTabId: tabPayload.initialTabId as UserTab, sdkContext: this.stores }, /*className=*/ undefined, /*isPriority=*/ false, /*isStatic=*/ true, diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index 2e8b5d91a3c..0e6b17ccc07 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -15,7 +15,7 @@ limitations under the License. */ import React, { createRef, ReactNode } from "react"; -import { Room } from "matrix-js-sdk/src/matrix"; +import { discoverAndValidateOIDCIssuerWellKnown, Room } from "matrix-js-sdk/src/matrix"; import { MatrixClientPeg } from "../../MatrixClientPeg"; import defaultDispatcher from "../../dispatcher/dispatcher"; @@ -52,6 +52,8 @@ import { Icon as LiveIcon } from "../../../res/img/compound/live-8px.svg"; import { VoiceBroadcastRecording, VoiceBroadcastRecordingsStoreEvent } from "../../voice-broadcast"; import { SDKContext } from "../../contexts/SDKContext"; import { shouldShowFeedback } from "../../utils/Feedback"; +import { shouldShowQr } from "../views/settings/devices/LoginWithQRSection"; +import { Features } from "../../settings/Settings"; interface IProps { isPanelCollapsed: boolean; @@ -66,6 +68,8 @@ interface IState { isHighContrast: boolean; selectedSpace?: Room | null; showLiveAvatarAddon: boolean; + showQrLogin: boolean; + supportsQrLogin: boolean; } const toRightOf = (rect: PartialDOMRect): MenuProps => { @@ -103,6 +107,8 @@ export default class UserMenu extends React.Component { isHighContrast: this.isUserOnHighContrastTheme(), selectedSpace: SpaceStore.instance.activeSpaceRoom, showLiveAvatarAddon: this.context.voiceBroadcastRecordingsStore.hasCurrent(), + showQrLogin: false, + supportsQrLogin: false, }; OwnProfileStore.instance.on(UPDATE_EVENT, this.onProfileUpdate); @@ -126,6 +132,7 @@ export default class UserMenu extends React.Component { ); this.dispatcherRef = defaultDispatcher.register(this.onAction); this.themeWatcherRef = SettingsStore.watchSetting("theme", null, this.onThemeChanged); + this.checkQrLoginSupport(); } public componentWillUnmount(): void { @@ -140,6 +147,29 @@ export default class UserMenu extends React.Component { ); } + private checkQrLoginSupport = async (): Promise => { + if (!this.context.client || !SettingsStore.getValue(Features.OidcNativeFlow)) return; + + const { issuer } = await this.context.client.getAuthIssuer().catch(() => ({ issuer: undefined })); + if (issuer) { + const [oidcClientConfig, versions, wellKnown, isCrossSigningReady] = await Promise.all([ + discoverAndValidateOIDCIssuerWellKnown(issuer), + this.context.client.getVersions(), + this.context.client.waitForClientWellKnown(), + this.context.client.getCrypto()?.isCrossSigningReady(), + ]); + + const supportsQrLogin = shouldShowQr( + this.context.client, + !!isCrossSigningReady, + oidcClientConfig, + versions, + wellKnown, + ); + this.setState({ supportsQrLogin, showQrLogin: true }); + } + }; + private isUserOnDarkTheme(): boolean { if (SettingsStore.getValue("use_system_theme")) { return window.matchMedia("(prefers-color-scheme: dark)").matches; @@ -237,11 +267,11 @@ export default class UserMenu extends React.Component { SettingsStore.setValue("theme", null, SettingLevel.DEVICE, newTheme); // set at same level as Appearance tab }; - private onSettingsOpen = (ev: ButtonEvent, tabId?: string): void => { + private onSettingsOpen = (ev: ButtonEvent, tabId?: string, props?: Record): void => { ev.preventDefault(); ev.stopPropagation(); - const payload: OpenToTabPayload = { action: Action.ViewUserSettings, initialTabId: tabId }; + const payload: OpenToTabPayload = { action: Action.ViewUserSettings, initialTabId: tabId, props }; defaultDispatcher.dispatch(payload); this.setState({ contextMenuPosition: null }); // also close the menu }; @@ -363,9 +393,33 @@ export default class UserMenu extends React.Component { ); } + let linkNewDeviceButton: JSX.Element | undefined; + if (this.state.showQrLogin) { + const extraProps: Omit< + React.ComponentProps, + "iconClassname" | "label" | "onClick" + > = {}; + if (!this.state.supportsQrLogin) { + extraProps.disabled = true; + extraProps.title = _t("user_menu|link_new_device_not_supported"); + extraProps.caption = _t("user_menu|link_new_device_not_supported_caption"); + extraProps.placement = "right"; + } + + linkNewDeviceButton = ( + this.onSettingsOpen(e, UserTab.SessionManager, { showMsc4108QrCode: true })} + /> + ); + } + let primaryOptionList = ( {homeButton} + {linkNewDeviceButton} { + private finished = false; + public constructor(props: IProps) { super(props); @@ -70,6 +94,10 @@ export default class LoginWithQR extends React.Component { }; } + private get ourIntent(): RendezvousIntent { + return RendezvousIntent.RECIPROCATE_LOGIN_ON_EXISTING_DEVICE; + } + public componentDidMount(): void { this.updateMode(this.props.mode).then(() => {}); } @@ -85,27 +113,36 @@ export default class LoginWithQR extends React.Component { if (this.state.rendezvous) { const rendezvous = this.state.rendezvous; rendezvous.onFailure = undefined; - await rendezvous.cancel(LegacyRendezvousFailureReason.UserCancelled); + if (rendezvous instanceof MSC3906Rendezvous) { + await rendezvous.cancel(LegacyRendezvousFailureReason.UserCancelled); + } this.setState({ rendezvous: undefined }); } if (mode === Mode.Show) { - await this.generateCode(); + await this.generateAndShowCode(); } } public componentWillUnmount(): void { - if (this.state.rendezvous) { + if (this.state.rendezvous && !this.finished) { // eslint-disable-next-line react/no-direct-mutation-state this.state.rendezvous.onFailure = undefined; // calling cancel will call close() as well to clean up the resources - this.state.rendezvous.cancel(LegacyRendezvousFailureReason.UserCancelled).then(() => {}); + if (this.state.rendezvous instanceof MSC3906Rendezvous) { + this.state.rendezvous.cancel(LegacyRendezvousFailureReason.UserCancelled); + } else { + this.state.rendezvous.cancel(MSC4108FailureReason.UserCancelled); + } } } - private approveLogin = async (): Promise => { - if (!this.state.rendezvous) { + private async legacyApproveLogin(): Promise { + if (!(this.state.rendezvous instanceof MSC3906Rendezvous)) { throw new Error("Rendezvous not found"); } + if (!this.props.client) { + throw new Error("No client to approve login with"); + } this.setState({ phase: Phase.Loading }); try { @@ -125,7 +162,7 @@ export default class LoginWithQR extends React.Component { } if (!this.props.client.getCrypto()) { // no E2EE to set up - this.props.onFinished(true); + this.onFinished(true); return; } this.setState({ phase: Phase.Verifying }); @@ -136,7 +173,7 @@ export default class LoginWithQR extends React.Component { } finally { this.setState({ rendezvous: undefined }); } - this.props.onFinished(true); + this.onFinished(true); } catch (e) { logger.error("Error whilst approving sign in", e); if (e instanceof HTTPError && e.httpStatus === 429) { @@ -144,27 +181,38 @@ export default class LoginWithQR extends React.Component { this.setState({ phase: Phase.Error, failureReason: LoginWithQRFailureReason.RateLimited }); return; } - this.setState({ phase: Phase.Error, failureReason: LegacyRendezvousFailureReason.Unknown }); + this.setState({ phase: Phase.Error, failureReason: ClientRendezvousFailureReason.Unknown }); } - }; + } - private generateCode = async (): Promise => { - let rendezvous: MSC3906Rendezvous; - try { - const fallbackRzServer = this.props.client.getClientWellKnown()?.["io.element.rendezvous"]?.server; - const transport = new MSC3886SimpleHttpRendezvousTransport({ - onFailure: this.onFailure, - client: this.props.client, - fallbackRzServer, - }); + private onFinished(success: boolean): void { + this.finished = true; + this.props.onFinished(success); + } - const channel = new MSC3903ECDHv2RendezvousChannel( - transport, - undefined, - this.onFailure, - ); + private generateAndShowCode = async (): Promise => { + let rendezvous: MSC4108SignInWithQR | MSC3906Rendezvous; + try { + const fallbackRzServer = this.props.client?.getClientWellKnown()?.["io.element.rendezvous"]?.server; - rendezvous = new MSC3906Rendezvous(channel, this.props.client, this.onFailure); + if (this.props.legacy) { + const transport = new MSC3886SimpleHttpRendezvousTransport({ + onFailure: this.onFailure, + client: this.props.client, + fallbackRzServer, + }); + const channel = new MSC3903ECDHv2RendezvousChannel(transport, undefined, this.onFailure); + rendezvous = new MSC3906Rendezvous(channel, this.props.client, this.onFailure); + } else { + const transport = new MSC4108RendezvousSession({ + onFailure: this.onFailure, + client: this.props.client, + fallbackRzServer, + }); + await transport.send(""); + const channel = new MSC4108SecureChannel(transport, undefined, this.onFailure); + rendezvous = new MSC4108SignInWithQR(channel, false, this.props.client, this.onFailure); + } await rendezvous.generateCode(); this.setState({ @@ -174,23 +222,84 @@ export default class LoginWithQR extends React.Component { }); } catch (e) { logger.error("Error whilst generating QR code", e); - this.setState({ phase: Phase.Error, failureReason: LegacyRendezvousFailureReason.HomeserverLacksSupport }); + this.setState({ phase: Phase.Error, failureReason: ClientRendezvousFailureReason.HomeserverLacksSupport }); return; } try { - const confirmationDigits = await rendezvous.startAfterShowingCode(); - this.setState({ phase: Phase.Connected, confirmationDigits }); - } catch (e) { - logger.error("Error whilst doing QR login", e); - // only set to error phase if it hasn't already been set by onFailure or similar - if (this.state.phase !== Phase.Error) { - this.setState({ phase: Phase.Error, failureReason: LegacyRendezvousFailureReason.Unknown }); + if (rendezvous instanceof MSC3906Rendezvous) { + const confirmationDigits = await rendezvous.startAfterShowingCode(); + this.setState({ phase: Phase.LegacyConnected, confirmationDigits }); + } else if (this.ourIntent === RendezvousIntent.RECIPROCATE_LOGIN_ON_EXISTING_DEVICE) { + // MSC4108-Flow: NewScanned + await rendezvous.negotiateProtocols(); + const { verificationUri } = await rendezvous.deviceAuthorizationGrant(); + this.setState({ + phase: Phase.OutOfBandConfirmation, + verificationUri, + }); + } + + // we ask the user to confirm that the channel is secure + } catch (e: RendezvousError | unknown) { + logger.error("Error whilst approving login", e); + if (rendezvous instanceof MSC3906Rendezvous) { + // only set to error phase if it hasn't already been set by onFailure or similar + if (this.state.phase !== Phase.Error) { + this.setState({ phase: Phase.Error, failureReason: LegacyRendezvousFailureReason.Unknown }); + } + } else { + await rendezvous?.cancel( + e instanceof RendezvousError + ? (e.code as MSC4108FailureReason) + : ClientRendezvousFailureReason.Unknown, + ); } } }; - private onFailure = (reason: LegacyRendezvousFailureReason): void => { + private approveLogin = async (checkCode: string | undefined): Promise => { + if (!(this.state.rendezvous instanceof MSC4108SignInWithQR)) { + this.setState({ phase: Phase.Error, failureReason: ClientRendezvousFailureReason.Unknown }); + throw new Error("Rendezvous not found"); + } + + if (!this.state.lastScannedCode && this.state.rendezvous?.checkCode !== checkCode) { + this.setState({ failureReason: LoginWithQRFailureReason.CheckCodeMismatch }); + return; + } + + try { + if (this.ourIntent === RendezvousIntent.RECIPROCATE_LOGIN_ON_EXISTING_DEVICE) { + // MSC4108-Flow: NewScanned + this.setState({ phase: Phase.Loading }); + + if (this.state.verificationUri) { + window.open(this.state.verificationUri, "_blank"); + } + + this.setState({ phase: Phase.WaitingForDevice }); + + // send secrets + await this.state.rendezvous.shareSecrets(); + + // done + this.onFinished(true); + } else { + this.setState({ phase: Phase.Error, failureReason: ClientRendezvousFailureReason.Unknown }); + throw new Error("New device flows around OIDC are not yet implemented"); + } + } catch (e: RendezvousError | unknown) { + logger.error("Error whilst approving sign in", e); + this.setState({ + phase: Phase.Error, + failureReason: e instanceof RendezvousError ? e.code : ClientRendezvousFailureReason.Unknown, + }); + } + }; + + private onFailure = (reason: RendezvousFailureReason): void => { + if (this.state.phase === Phase.Error) return; // Already in failed state logger.info(`Rendezvous failed: ${reason}`); this.setState({ phase: Phase.Error, failureReason: reason }); }; @@ -199,44 +308,72 @@ export default class LoginWithQR extends React.Component { this.setState({ rendezvous: undefined, confirmationDigits: undefined, + verificationUri: undefined, failureReason: undefined, + userCode: undefined, + checkCode: undefined, + homeserverBaseUrl: undefined, + lastScannedCode: undefined, + mediaPermissionError: false, }); } - private onClick = async (type: Click): Promise => { + private onClick = async (type: Click, checkCode?: string): Promise => { switch (type) { case Click.Cancel: - await this.state.rendezvous?.cancel(LegacyRendezvousFailureReason.UserCancelled); + if (this.state.rendezvous instanceof MSC3906Rendezvous) { + await this.state.rendezvous?.cancel(LegacyRendezvousFailureReason.UserCancelled); + } else { + await this.state.rendezvous?.cancel(MSC4108FailureReason.UserCancelled); + } this.reset(); - this.props.onFinished(false); + this.onFinished(false); break; case Click.Approve: - await this.approveLogin(); + await (this.props.legacy ? this.legacyApproveLogin() : this.approveLogin(checkCode)); break; case Click.Decline: await this.state.rendezvous?.declineLoginOnExistingDevice(); this.reset(); - this.props.onFinished(false); - break; - case Click.TryAgain: - this.reset(); - await this.updateMode(this.props.mode); + this.onFinished(false); break; case Click.Back: - await this.state.rendezvous?.cancel(LegacyRendezvousFailureReason.UserCancelled); - this.props.onFinished(false); + if (this.state.rendezvous instanceof MSC3906Rendezvous) { + await this.state.rendezvous?.cancel(LegacyRendezvousFailureReason.UserCancelled); + } else { + await this.state.rendezvous?.cancel(MSC4108FailureReason.UserCancelled); + } + this.onFinished(false); + break; + case Click.ShowQr: + await this.updateMode(Mode.Show); break; } }; public render(): React.ReactNode { + if (this.state.rendezvous instanceof MSC3906Rendezvous) { + return ( + + ); + } + return ( ); } diff --git a/src/components/views/auth/LoginWithQRFlow.tsx b/src/components/views/auth/LoginWithQRFlow.tsx index c8a786992af..036dc1b451f 100644 --- a/src/components/views/auth/LoginWithQRFlow.tsx +++ b/src/components/views/auth/LoginWithQRFlow.tsx @@ -14,12 +14,16 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { ReactNode } from "react"; -import { LegacyRendezvousFailureReason } from "matrix-js-sdk/src/rendezvous"; +import React, { createRef, ReactNode } from "react"; +import { + ClientRendezvousFailureReason, + LegacyRendezvousFailureReason, + MSC4108FailureReason, +} from "matrix-js-sdk/src/rendezvous"; import { Icon as ChevronLeftIcon } from "@vector-im/compound-design-tokens/icons/chevron-left.svg"; import { Icon as CheckCircleSolidIcon } from "@vector-im/compound-design-tokens/icons/check-circle-solid.svg"; import { Icon as ErrorIcon } from "@vector-im/compound-design-tokens/icons/error.svg"; -import { Heading, Text } from "@vector-im/compound-web"; +import { Heading, MFAInput, Text } from "@vector-im/compound-web"; import classNames from "classnames"; import { _t } from "../../../languageHandler"; @@ -30,13 +34,24 @@ import { Icon as InfoIcon } from "../../../../res/img/element-icons/i.svg"; import { Click, Phase } from "./LoginWithQR-types"; import SdkConfig from "../../../SdkConfig"; import { FailureReason, LoginWithQRFailureReason } from "./LoginWithQR"; +import { XOR } from "../../../@types/common"; +import { ErrorMessage } from "../../structures/ErrorMessage"; + +/** + * @deprecated the MSC3906 implementation is deprecated in favour of MSC4108. + */ +interface MSC3906Props extends Pick { + code?: string; + confirmationDigits?: string; +} interface Props { phase: Phase; - code?: string; - onClick(type: Click): Promise; + code?: Uint8Array; + onClick(type: Click, checkCodeEntered?: string): Promise; failureReason?: FailureReason; - confirmationDigits?: string; + userCode?: string; + checkCode?: string; } // n.b MSC3886/MSC3903/MSC3906 that this is based on are now closed. @@ -46,17 +61,19 @@ interface Props { /** * A component that implements the UI for sign in and E2EE set up with a QR code. * - * This uses the unstable feature of MSC3906: https://github.com/matrix-org/matrix-spec-proposals/pull/3906 + * This supports the unstable features of MSC3906 and MSC4108 */ -export default class LoginWithQRFlow extends React.Component { - public constructor(props: Props) { +export default class LoginWithQRFlow extends React.Component> { + private checkCodeInput = createRef(); + + public constructor(props: XOR) { super(props); } private handleClick = (type: Click): ((e: React.FormEvent) => Promise) => { return async (e: React.FormEvent): Promise => { e.preventDefault(); - await this.props.onClick(type); + await this.props.onClick(type, type === Click.Approve ? this.checkCodeInput.current?.value : undefined); }; }; @@ -90,24 +107,26 @@ export default class LoginWithQRFlow extends React.Component { let message: ReactNode | undefined; switch (this.props.failureReason) { - case LegacyRendezvousFailureReason.UnsupportedAlgorithm: - case LegacyRendezvousFailureReason.UnsupportedTransport: - case LegacyRendezvousFailureReason.HomeserverLacksSupport: + case MSC4108FailureReason.UnsupportedProtocol: + case LegacyRendezvousFailureReason.UnsupportedProtocol: title = _t("auth|qr_code_login|error_unsupported_protocol_title"); message = _t("auth|qr_code_login|error_unsupported_protocol"); break; + case MSC4108FailureReason.UserCancelled: case LegacyRendezvousFailureReason.UserCancelled: title = _t("auth|qr_code_login|error_user_cancelled_title"); message = _t("auth|qr_code_login|error_user_cancelled"); break; + case MSC4108FailureReason.AuthorizationExpired: + case ClientRendezvousFailureReason.Expired: case LegacyRendezvousFailureReason.Expired: title = _t("auth|qr_code_login|error_expired_title"); message = _t("auth|qr_code_login|error_expired"); break; - case LegacyRendezvousFailureReason.InvalidCode: + case ClientRendezvousFailureReason.InsecureChannelDetected: title = _t("auth|qr_code_login|error_insecure_channel_detected_title"); message = ( <> @@ -125,13 +144,13 @@ export default class LoginWithQRFlow extends React.Component { ); break; - case LegacyRendezvousFailureReason.OtherDeviceAlreadySignedIn: + case ClientRendezvousFailureReason.OtherDeviceAlreadySignedIn: success = true; title = _t("auth|qr_code_login|error_other_device_already_signed_in_title"); message = _t("auth|qr_code_login|error_other_device_already_signed_in"); break; - case LegacyRendezvousFailureReason.UserDeclined: + case ClientRendezvousFailureReason.UserDeclined: title = _t("auth|qr_code_login|error_user_declined_title"); message = _t("auth|qr_code_login|error_user_declined"); break; @@ -141,8 +160,16 @@ export default class LoginWithQRFlow extends React.Component { message = _t("auth|qr_code_login|error_rate_limited"); break; - case LegacyRendezvousFailureReason.OtherDeviceNotSignedIn: - case LegacyRendezvousFailureReason.Unknown: + case ClientRendezvousFailureReason.ETagMissing: + title = _t("error|something_went_wrong"); + message = _t("auth|qr_code_login|error_etag_missing"); + break; + + case MSC4108FailureReason.DeviceAlreadyExists: + case MSC4108FailureReason.DeviceNotFound: + case MSC4108FailureReason.UnexpectedMessageReceived: + case ClientRendezvousFailureReason.OtherDeviceNotSignedIn: + case ClientRendezvousFailureReason.Unknown: default: title = _t("error|something_went_wrong"); message = _t("auth|qr_code_login|error_unexpected"); @@ -150,18 +177,6 @@ export default class LoginWithQRFlow extends React.Component { } className = "mx_LoginWithQR_error"; backButton = false; - buttons = ( - <> - - {_t("action|try_again")} - - {this.cancelButton()} - - ); main = ( <>
{ ); break; } - case Phase.Connected: + case Phase.LegacyConnected: backButton = false; main = ( <> @@ -213,9 +228,62 @@ export default class LoginWithQRFlow extends React.Component { ); break; + case Phase.OutOfBandConfirmation: + backButton = false; + main = ( + <> + + {_t("auth|qr_code_login|check_code_heading")} + + {_t("auth|qr_code_login|check_code_explainer")} + + + + + ); + + buttons = ( + <> + + {_t("action|continue")} + + + {_t("action|cancel")} + + + ); + break; case Phase.ShowingQR: if (this.props.code) { - const data = Buffer.from(this.props.code ?? ""); + const data = + typeof this.props.code !== "string" ? this.props.code : Buffer.from(this.props.code ?? ""); main = ( <> @@ -249,12 +317,19 @@ export default class LoginWithQRFlow extends React.Component { case Phase.Loading: main = this.simpleSpinner(); break; - case Phase.Connecting: - main = this.simpleSpinner(_t("auth|qr_code_login|connecting")); - buttons = this.cancelButton(); - break; case Phase.WaitingForDevice: - main = this.simpleSpinner(_t("auth|qr_code_login|waiting_for_device")); + main = ( + <> + {this.simpleSpinner(_t("auth|qr_code_login|waiting_for_device"))} + {this.props.userCode ? ( +
+

{_t("auth|qr_code_login|security_code")}

+

{_t("auth|qr_code_login|security_code_prompt")}

+

{this.props.userCode}

+
+ ) : null} + + ); buttons = this.cancelButton(); break; case Phase.Verifying: diff --git a/src/components/views/dialogs/UserSettingsDialog.tsx b/src/components/views/dialogs/UserSettingsDialog.tsx index bb97b36fc96..afcbc182d01 100644 --- a/src/components/views/dialogs/UserSettingsDialog.tsx +++ b/src/components/views/dialogs/UserSettingsDialog.tsx @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { useState } from "react"; import TabbedView, { Tab, useActiveTabWithDefault } from "../../structures/TabbedView"; import { _t, _td } from "../../../languageHandler"; @@ -41,6 +41,7 @@ import { useSettingValue } from "../../../hooks/useSettings"; interface IProps { initialTabId?: UserTab; + showMsc4108QrCode?: boolean; sdkContext: SdkContextClass; onFinished(): void; } @@ -80,6 +81,8 @@ function titleForTabID(tabId: UserTab): React.ReactNode { export default function UserSettingsDialog(props: IProps): JSX.Element { const voipEnabled = useSettingValue(UIFeature.Voip); const mjolnirEnabled = useSettingValue("feature_mjolnir"); + // store this prop in state as changing tabs back and forth should clear it + const [showMsc4108QrCode, setShowMsc4108QrCode] = useState(props.showMsc4108QrCode); const getTabs = (): NonEmptyArray> => { const tabs: Tab[] = []; @@ -98,7 +101,7 @@ export default function UserSettingsDialog(props: IProps): JSX.Element { UserTab.SessionManager, _td("settings|sessions|title"), "mx_UserSettingsDialog_sessionsIcon", - , + , undefined, ), ); @@ -205,7 +208,12 @@ export default function UserSettingsDialog(props: IProps): JSX.Element { return tabs as NonEmptyArray>; }; - const [activeTabId, setActiveTabId] = useActiveTabWithDefault(getTabs(), UserTab.General, props.initialTabId); + const [activeTabId, _setActiveTabId] = useActiveTabWithDefault(getTabs(), UserTab.General, props.initialTabId); + const setActiveTabId = (tabId: UserTab): void => { + _setActiveTabId(tabId); + // Clear this so switching away from the tab and back to it will not show the QR code again + setShowMsc4108QrCode(false); + }; return ( // XXX: SDKContext is provided within the LoggedInView subtree. diff --git a/src/components/views/settings/devices/LoginWithQRSection.tsx b/src/components/views/settings/devices/LoginWithQRSection.tsx index b83668b6b84..9c7ed9efe68 100644 --- a/src/components/views/settings/devices/LoginWithQRSection.tsx +++ b/src/components/views/settings/devices/LoginWithQRSection.tsx @@ -21,18 +21,26 @@ import { GET_LOGIN_TOKEN_CAPABILITY, Capabilities, IClientWellKnown, + OidcClientConfig, + MatrixClient, + DEVICE_CODE_SCOPE, } from "matrix-js-sdk/src/matrix"; import { Icon as QrCodeIcon } from "@vector-im/compound-design-tokens/icons/qr-code.svg"; import { _t } from "../../../../languageHandler"; import AccessibleButton from "../../elements/AccessibleButton"; import SettingsSubsection from "../shared/SettingsSubsection"; +import SettingsStore from "../../../../settings/SettingsStore"; +import { Features } from "../../../../settings/Settings"; +import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext"; interface IProps { onShowQr: () => void; versions?: IServerVersions; capabilities?: Capabilities; wellKnown?: IClientWellKnown; + oidcClientConfig?: OidcClientConfig; + isCrossSigningReady?: boolean; } function shouldShowQrLegacy( @@ -50,8 +58,40 @@ function shouldShowQrLegacy( return getLoginTokenSupported && msc3886Supported; } -const LoginWithQRSection: React.FC = ({ onShowQr, versions, capabilities, wellKnown }) => { - const offerShowQr = shouldShowQrLegacy(versions, wellKnown, capabilities); +export function shouldShowQr( + cli: MatrixClient, + isCrossSigningReady: boolean, + oidcClientConfig?: OidcClientConfig, + versions?: IServerVersions, + wellKnown?: IClientWellKnown, +): boolean { + const msc4108Supported = + !!versions?.unstable_features?.["org.matrix.msc4108"] || !!wellKnown?.["io.element.rendezvous"]?.server; + + const deviceAuthorizationGrantSupported = + oidcClientConfig?.metadata?.grant_types_supported.includes(DEVICE_CODE_SCOPE); + + return ( + deviceAuthorizationGrantSupported && + msc4108Supported && + SettingsStore.getValue(Features.OidcNativeFlow) && + !!cli.getCrypto()?.exportSecretsBundle && + isCrossSigningReady + ); +} + +const LoginWithQRSection: React.FC = ({ + onShowQr, + versions, + capabilities, + wellKnown, + oidcClientConfig, + isCrossSigningReady, +}) => { + const cli = useMatrixClientContext(); + const offerShowQr = oidcClientConfig + ? shouldShowQr(cli, !!isCrossSigningReady, oidcClientConfig, versions, wellKnown) + : shouldShowQrLegacy(versions, wellKnown, capabilities); // don't show anything if no method is available if (!offerShowQr) { diff --git a/src/components/views/settings/tabs/user/SessionManagerTab.tsx b/src/components/views/settings/tabs/user/SessionManagerTab.tsx index ec1d658b5b9..ee51d0680fb 100644 --- a/src/components/views/settings/tabs/user/SessionManagerTab.tsx +++ b/src/components/views/settings/tabs/user/SessionManagerTab.tsx @@ -14,8 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react"; -import { MatrixClient } from "matrix-js-sdk/src/matrix"; +import React, { lazy, Suspense, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react"; +import { discoverAndValidateOIDCIssuerWellKnown, MatrixClient } from "matrix-js-sdk/src/matrix"; import { logger } from "matrix-js-sdk/src/logger"; import { _t } from "../../../../../languageHandler"; @@ -32,7 +32,6 @@ import { ExtendedDevice } from "../../devices/types"; import { deleteDevicesWithInteractiveAuth } from "../../devices/deleteDevices"; import SettingsTab from "../SettingsTab"; import LoginWithQRSection from "../../devices/LoginWithQRSection"; -import LoginWithQR from "../../../auth/LoginWithQR"; import { Mode } from "../../../auth/LoginWithQR-types"; import { useAsyncMemo } from "../../../../../hooks/useAsyncMemo"; import QuestionDialog from "../../../dialogs/QuestionDialog"; @@ -41,6 +40,10 @@ import { OtherSessionsSectionHeading } from "../../devices/OtherSessionsSectionH import { SettingsSection } from "../../shared/SettingsSection"; import { OidcLogoutDialog } from "../../../dialogs/oidc/OidcLogoutDialog"; import { SDKContext } from "../../../../../contexts/SDKContext"; +import Spinner from "../../../elements/Spinner"; + +// We import `LoginWithQR` asynchronously to avoid importing the entire Rust Crypto WASM into the main bundle. +const LoginWithQR = lazy(() => import("../../../auth/LoginWithQR")); const confirmSignOut = async (sessionsToSignOutCount: number): Promise => { const { finished } = Modal.createDialog(QuestionDialog, { @@ -148,7 +151,9 @@ const useSignOut = ( }; }; -const SessionManagerTab: React.FC = () => { +const SessionManagerTab: React.FC<{ + showMsc4108QrCode?: boolean; +}> = ({ showMsc4108QrCode }) => { const { devices, dehydratedDeviceId, @@ -186,6 +191,20 @@ const SessionManagerTab: React.FC = () => { const clientVersions = useAsyncMemo(() => matrixClient.getVersions(), [matrixClient]); const capabilities = useAsyncMemo(async () => matrixClient?.getCapabilities(), [matrixClient]); const wellKnown = useMemo(() => matrixClient?.getClientWellKnown(), [matrixClient]); + const oidcClientConfig = useAsyncMemo(async () => { + try { + const authIssuer = await matrixClient?.getAuthIssuer(); + if (authIssuer) { + return discoverAndValidateOIDCIssuerWellKnown(authIssuer.issuer); + } + } catch (e) { + logger.error("Failed to discover OIDC metadata", e); + } + }, [matrixClient]); + const isCrossSigningReady = useAsyncMemo( + async () => matrixClient.getCrypto()?.isCrossSigningReady() ?? false, + [matrixClient], + ); const onDeviceExpandToggle = (deviceId: ExtendedDevice["device_id"]): void => { if (expandedDeviceIds.includes(deviceId)) { @@ -268,7 +287,7 @@ const SessionManagerTab: React.FC = () => { } : undefined; - const [signInWithQrMode, setSignInWithQrMode] = useState(); + const [signInWithQrMode, setSignInWithQrMode] = useState(showMsc4108QrCode ? Mode.Show : null); const onQrFinish = useCallback(() => { setSignInWithQrMode(null); @@ -279,7 +298,16 @@ const SessionManagerTab: React.FC = () => { }, [setSignInWithQrMode]); if (signInWithQrMode) { - return ; + return ( + }> + + + ); } return ( @@ -290,6 +318,8 @@ const SessionManagerTab: React.FC = () => { versions={clientVersions} capabilities={capabilities} wellKnown={wellKnown} + oidcClientConfig={oidcClientConfig} + isCrossSigningReady={isCrossSigningReady} /> ; } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 7db9435c00e..d7304887713 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -246,9 +246,13 @@ "phone_optional_label": "Phone (optional)", "qr_code_login": { "approve_access_warning": "By approving access for this device, it will have full access to your account.", + "check_code_explainer": "This will verify that the connection to your other device is secure.", + "check_code_heading": "Enter the number shown on your other device", + "check_code_input_label": "2-digit code", + "check_code_mismatch": "The numbers don't match", "completing_setup": "Completing set up of your new device", "confirm_code_match": "Check that the code below matches with your other device:", - "connecting": "Connecting…", + "error_etag_missing": "An unexpected error occurred. This may be due to a browser extension, proxy server, or server misconfiguration.", "error_expired": "Sign in expired. Please try again.", "error_expired_title": "The sign in was not completed in time", "error_insecure_channel_detected": "A secure connection could not be made to the new device. Your existing devices are still safe and you don't need to worry about them.", @@ -265,13 +269,15 @@ "error_unsupported_protocol_title": "Other device not compatible", "error_user_cancelled": "The sign in was cancelled on the other device.", "error_user_cancelled_title": "Sign in request cancelled", - "error_user_declined": "You declined the request from your other device to sign in.", + "error_user_declined": "You or the account provider declined the sign in request.", "error_user_declined_title": "Sign in declined", - "follow_remaining_instructions": "Follow the instructions to link your other device", + "follow_remaining_instructions": "Follow the remaining instructions", "open_element_other_device": "Open %(brand)s on your other device", - "point_the_camera": "Point the camera at the QR code shown here", + "point_the_camera": "Scan the QR code shown here", "scan_code_instruction": "Scan the QR code with another device", "scan_qr_code": "Sign in with QR code", + "security_code": "Security code", + "security_code_prompt": "If asked, enter the code below on your other device.", "select_qr_code": "Select \"%(scanQRCode)s\"", "sign_in_new_device": "Sign in new device", "waiting_for_device": "Waiting for device to sign in" @@ -3788,6 +3794,9 @@ "verify_explainer": "For extra security, verify this user by checking a one-time code on both of your devices." }, "user_menu": { + "link_new_device": "Link new device", + "link_new_device_not_supported": "Not supported", + "link_new_device_not_supported_caption": "You need to sign in manually", "settings": "All settings", "switch_theme_dark": "Switch to dark mode", "switch_theme_light": "Switch to light mode" diff --git a/test/components/structures/UserMenu-test.tsx b/test/components/structures/UserMenu-test.tsx index 22addc5a359..24b75a87d15 100644 --- a/test/components/structures/UserMenu-test.tsx +++ b/test/components/structures/UserMenu-test.tsx @@ -16,8 +16,10 @@ limitations under the License. import React from "react"; import { act, render, RenderResult, screen, waitFor } from "@testing-library/react"; -import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix"; +import { DEVICE_CODE_SCOPE, MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix"; +import { CryptoApi } from "matrix-js-sdk/src/crypto-api"; import { mocked } from "jest-mock"; +import fetchMock from "fetch-mock-jest"; import UnwrappedUserMenu from "../../../src/components/structures/UserMenu"; import { stubClient, wrapInSdkContext } from "../../test-utils"; @@ -31,6 +33,12 @@ import { TestSdkContext } from "../../TestSdkContext"; import defaultDispatcher from "../../../src/dispatcher/dispatcher"; import LogoutDialog from "../../../src/components/views/dialogs/LogoutDialog"; import Modal from "../../../src/Modal"; +import SettingsStore from "../../../src/settings/SettingsStore"; +import { Features } from "../../../src/settings/Settings"; +import { SettingLevel } from "../../../src/settings/SettingLevel"; +import { mockOpenIdConfiguration } from "../../test-utils/oidc"; +import { Action } from "../../../src/dispatcher/actions"; +import { UserTab } from "../../../src/components/views/dialogs/UserTab"; describe("", () => { let client: MatrixClient; @@ -177,4 +185,48 @@ describe("", () => { }); }); }); + + it("should render 'Link new device' button in OIDC native mode", async () => { + sdkContext.client = stubClient(); + mocked(sdkContext.client.getAuthIssuer).mockResolvedValue({ issuer: "https://issuer/" }); + const openIdMetadata = mockOpenIdConfiguration("https://issuer/"); + openIdMetadata.grant_types_supported.push(DEVICE_CODE_SCOPE); + fetchMock.get("https://issuer/.well-known/openid-configuration", openIdMetadata); + fetchMock.get("https://issuer/jwks", { + status: 200, + headers: { + "Content-Type": "application/json", + }, + keys: [], + }); + mocked(sdkContext.client.getVersions).mockResolvedValue({ + versions: [], + unstable_features: { + "org.matrix.msc4108": true, + }, + }); + mocked(sdkContext.client.waitForClientWellKnown).mockResolvedValue({}); + mocked(sdkContext.client.getCrypto).mockReturnValue({ + isCrossSigningReady: jest.fn().mockResolvedValue(true), + exportSecretsBundle: jest.fn().mockResolvedValue({}), + } as unknown as CryptoApi); + await SettingsStore.setValue(Features.OidcNativeFlow, null, SettingLevel.DEVICE, true); + const spy = jest.spyOn(defaultDispatcher, "dispatch"); + + const UserMenu = wrapInSdkContext(UnwrappedUserMenu, sdkContext); + render(); + + screen.getByRole("button", { name: /User menu/i }).click(); + await expect(screen.findByText("Link new device")).resolves.toBeInTheDocument(); + + // Assert the QR code is shown directly + screen.getByRole("menuitem", { name: "Link new device" }).click(); + await waitFor(() => { + expect(spy).toHaveBeenCalledWith({ + action: Action.ViewUserSettings, + initialTabId: UserTab.SessionManager, + props: { showMsc4108QrCode: true }, + }); + }); + }); }); diff --git a/test/components/views/settings/devices/LoginWithQR-test.tsx b/test/components/views/settings/devices/LoginWithQR-test.tsx index 727ab6a017e..6e7636d0cfa 100644 --- a/test/components/views/settings/devices/LoginWithQR-test.tsx +++ b/test/components/views/settings/devices/LoginWithQR-test.tsx @@ -17,7 +17,13 @@ limitations under the License. import { cleanup, render, waitFor } from "@testing-library/react"; import { MockedObject, mocked } from "jest-mock"; import React from "react"; -import { MSC3906Rendezvous, LegacyRendezvousFailureReason } from "matrix-js-sdk/src/rendezvous"; +import { + MSC3906Rendezvous, + LegacyRendezvousFailureReason, + ClientRendezvousFailureReason, + MSC4108SignInWithQR, + MSC4108FailureReason, +} from "matrix-js-sdk/src/rendezvous"; import { HTTPError, LoginTokenPostResponse } from "matrix-js-sdk/src/matrix"; import LoginWithQR from "../../../../../src/components/views/auth/LoginWithQR"; @@ -65,6 +71,7 @@ function unresolvedPromise(): Promise { describe("", () => { let client!: MockedObject; const defaultProps = { + legacy: true, mode: Mode.Show, onFinished: jest.fn(), }; @@ -72,29 +79,10 @@ describe("", () => { const mockRendezvousCode = "mock-rendezvous-code"; const newDeviceId = "new-device-id"; - const getComponent = (props: { client: MatrixClient; onFinished?: () => void }) => ( - - - - ); - beforeEach(() => { mockedFlow.mockReset(); jest.resetAllMocks(); client = makeClient(); - jest.spyOn(MSC3906Rendezvous.prototype, "generateCode").mockResolvedValue(); - // @ts-ignore - // workaround for https://github.com/facebook/jest/issues/9675 - MSC3906Rendezvous.prototype.code = mockRendezvousCode; - jest.spyOn(MSC3906Rendezvous.prototype, "cancel").mockResolvedValue(); - jest.spyOn(MSC3906Rendezvous.prototype, "startAfterShowingCode").mockResolvedValue(mockConfirmationDigits); - jest.spyOn(MSC3906Rendezvous.prototype, "declineLoginOnExistingDevice").mockResolvedValue(); - jest.spyOn(MSC3906Rendezvous.prototype, "approveLoginOnExistingDevice").mockResolvedValue(newDeviceId); - jest.spyOn(MSC3906Rendezvous.prototype, "verifyNewDeviceOnExistingDevice").mockResolvedValue(undefined); - client.requestLoginToken.mockResolvedValue({ - login_token: "token", - expires_in_ms: 1000 * 1000, - } as LoginTokenPostResponse); // we force the type here so that it works with versions of js-sdk that don't have r1 support yet }); afterEach(() => { @@ -104,279 +92,374 @@ describe("", () => { cleanup(); }); - test("no homeserver support", async () => { - // simulate no support - jest.spyOn(MSC3906Rendezvous.prototype, "generateCode").mockRejectedValue(""); - render(getComponent({ client })); - await waitFor(() => - expect(mockedFlow).toHaveBeenLastCalledWith({ - phase: Phase.Error, - failureReason: LegacyRendezvousFailureReason.HomeserverLacksSupport, - onClick: expect.any(Function), - }), + describe("MSC3906", () => { + const getComponent = (props: { client: MatrixClient; onFinished?: () => void }) => ( + + + ); - const rendezvous = mocked(MSC3906Rendezvous).mock.instances[0]; - expect(rendezvous.generateCode).toHaveBeenCalled(); - }); - - test("failed to connect", async () => { - jest.spyOn(MSC3906Rendezvous.prototype, "startAfterShowingCode").mockRejectedValue(""); - render(getComponent({ client })); - await waitFor(() => - expect(mockedFlow).toHaveBeenLastCalledWith({ - phase: Phase.Error, - failureReason: LegacyRendezvousFailureReason.Unknown, - onClick: expect.any(Function), - }), - ); - const rendezvous = mocked(MSC3906Rendezvous).mock.instances[0]; - expect(rendezvous.generateCode).toHaveBeenCalled(); - expect(rendezvous.startAfterShowingCode).toHaveBeenCalled(); - }); - test("render QR then cancel and try again", async () => { - const onFinished = jest.fn(); - jest.spyOn(MSC3906Rendezvous.prototype, "startAfterShowingCode").mockImplementation(() => unresolvedPromise()); - render(getComponent({ client, onFinished })); - const rendezvous = mocked(MSC3906Rendezvous).mock.instances[0]; + beforeEach(() => { + jest.spyOn(MSC3906Rendezvous.prototype, "generateCode").mockResolvedValue(); + // @ts-ignore + // workaround for https://github.com/facebook/jest/issues/9675 + MSC3906Rendezvous.prototype.code = mockRendezvousCode; + jest.spyOn(MSC3906Rendezvous.prototype, "cancel").mockResolvedValue(); + jest.spyOn(MSC3906Rendezvous.prototype, "startAfterShowingCode").mockResolvedValue(mockConfirmationDigits); + jest.spyOn(MSC3906Rendezvous.prototype, "declineLoginOnExistingDevice").mockResolvedValue(); + jest.spyOn(MSC3906Rendezvous.prototype, "approveLoginOnExistingDevice").mockResolvedValue(newDeviceId); + jest.spyOn(MSC3906Rendezvous.prototype, "verifyNewDeviceOnExistingDevice").mockResolvedValue(undefined); + client.requestLoginToken.mockResolvedValue({ + login_token: "token", + expires_in_ms: 1000 * 1000, + } as LoginTokenPostResponse); // we force the type here so that it works with versions of js-sdk that don't have r1 support yet + }); - await waitFor(() => - expect(mockedFlow).toHaveBeenLastCalledWith( - expect.objectContaining({ - phase: Phase.ShowingQR, + test("no homeserver support", async () => { + // simulate no support + jest.spyOn(MSC3906Rendezvous.prototype, "generateCode").mockRejectedValue(""); + render(getComponent({ client })); + await waitFor(() => + expect(mockedFlow).toHaveBeenLastCalledWith({ + phase: Phase.Error, + failureReason: LegacyRendezvousFailureReason.HomeserverLacksSupport, + onClick: expect.any(Function), }), - ), - ); - // display QR code - expect(mockedFlow).toHaveBeenLastCalledWith({ - phase: Phase.ShowingQR, - code: mockRendezvousCode, - onClick: expect.any(Function), + ); + const rendezvous = mocked(MSC3906Rendezvous).mock.instances[0]; + expect(rendezvous.generateCode).toHaveBeenCalled(); }); - expect(rendezvous.generateCode).toHaveBeenCalled(); - expect(rendezvous.startAfterShowingCode).toHaveBeenCalled(); - - // cancel - const onClick = mockedFlow.mock.calls[0][0].onClick; - await onClick(Click.Cancel); - expect(onFinished).toHaveBeenCalledWith(false); - expect(rendezvous.cancel).toHaveBeenCalledWith(LegacyRendezvousFailureReason.UserCancelled); - - // try again - onClick(Click.TryAgain); - await waitFor(() => - expect(mockedFlow).toHaveBeenLastCalledWith( - expect.objectContaining({ - phase: Phase.ShowingQR, + + test("failed to connect", async () => { + jest.spyOn(MSC3906Rendezvous.prototype, "startAfterShowingCode").mockRejectedValue(""); + render(getComponent({ client })); + await waitFor(() => + expect(mockedFlow).toHaveBeenLastCalledWith({ + phase: Phase.Error, + failureReason: ClientRendezvousFailureReason.Unknown, + onClick: expect.any(Function), }), - ), - ); - // display QR code - expect(mockedFlow).toHaveBeenLastCalledWith({ - phase: Phase.ShowingQR, - code: mockRendezvousCode, - onClick: expect.any(Function), + ); + const rendezvous = mocked(MSC3906Rendezvous).mock.instances[0]; + expect(rendezvous.generateCode).toHaveBeenCalled(); + expect(rendezvous.startAfterShowingCode).toHaveBeenCalled(); }); - }); - test("render QR then back", async () => { - const onFinished = jest.fn(); - jest.spyOn(MSC3906Rendezvous.prototype, "startAfterShowingCode").mockReturnValue(unresolvedPromise()); - render(getComponent({ client, onFinished })); - const rendezvous = mocked(MSC3906Rendezvous).mock.instances[0]; - - await waitFor(() => - expect(mockedFlow).toHaveBeenLastCalledWith( - expect.objectContaining({ - phase: Phase.ShowingQR, - }), - ), - ); - // display QR code - expect(mockedFlow).toHaveBeenLastCalledWith({ - phase: Phase.ShowingQR, - code: mockRendezvousCode, - onClick: expect.any(Function), + test("render QR then back", async () => { + const onFinished = jest.fn(); + jest.spyOn(MSC3906Rendezvous.prototype, "startAfterShowingCode").mockReturnValue(unresolvedPromise()); + render(getComponent({ client, onFinished })); + const rendezvous = mocked(MSC3906Rendezvous).mock.instances[0]; + + await waitFor(() => + expect(mockedFlow).toHaveBeenLastCalledWith( + expect.objectContaining({ + phase: Phase.ShowingQR, + }), + ), + ); + // display QR code + expect(mockedFlow).toHaveBeenLastCalledWith({ + phase: Phase.ShowingQR, + code: mockRendezvousCode, + onClick: expect.any(Function), + }); + expect(rendezvous.generateCode).toHaveBeenCalled(); + expect(rendezvous.startAfterShowingCode).toHaveBeenCalled(); + + // back + const onClick = mockedFlow.mock.calls[0][0].onClick; + await onClick(Click.Back); + expect(onFinished).toHaveBeenCalledWith(false); + expect(rendezvous.cancel).toHaveBeenCalledWith(LegacyRendezvousFailureReason.UserCancelled); }); - expect(rendezvous.generateCode).toHaveBeenCalled(); - expect(rendezvous.startAfterShowingCode).toHaveBeenCalled(); - - // back - const onClick = mockedFlow.mock.calls[0][0].onClick; - await onClick(Click.Back); - expect(onFinished).toHaveBeenCalledWith(false); - expect(rendezvous.cancel).toHaveBeenCalledWith(LegacyRendezvousFailureReason.UserCancelled); - }); - test("render QR then decline", async () => { - const onFinished = jest.fn(); - render(getComponent({ client, onFinished })); - const rendezvous = mocked(MSC3906Rendezvous).mock.instances[0]; + test("render QR then decline", async () => { + const onFinished = jest.fn(); + render(getComponent({ client, onFinished })); + const rendezvous = mocked(MSC3906Rendezvous).mock.instances[0]; + + await waitFor(() => + expect(mockedFlow).toHaveBeenLastCalledWith( + expect.objectContaining({ + phase: Phase.LegacyConnected, + }), + ), + ); + expect(mockedFlow).toHaveBeenLastCalledWith({ + phase: Phase.LegacyConnected, + confirmationDigits: mockConfirmationDigits, + onClick: expect.any(Function), + }); + + // decline + const onClick = mockedFlow.mock.calls[0][0].onClick; + await onClick(Click.Decline); + expect(onFinished).toHaveBeenCalledWith(false); - await waitFor(() => - expect(mockedFlow).toHaveBeenLastCalledWith( - expect.objectContaining({ - phase: Phase.Connected, - }), - ), - ); - expect(mockedFlow).toHaveBeenLastCalledWith({ - phase: Phase.Connected, - confirmationDigits: mockConfirmationDigits, - onClick: expect.any(Function), + expect(rendezvous.generateCode).toHaveBeenCalled(); + expect(rendezvous.startAfterShowingCode).toHaveBeenCalled(); + expect(rendezvous.declineLoginOnExistingDevice).toHaveBeenCalled(); }); - // decline - const onClick = mockedFlow.mock.calls[0][0].onClick; - await onClick(Click.Decline); - expect(onFinished).toHaveBeenCalledWith(false); + test("approve - no crypto", async () => { + (client as any).crypto = undefined; + (client as any).getCrypto = () => undefined; + const onFinished = jest.fn(); + render(getComponent({ client, onFinished })); + const rendezvous = mocked(MSC3906Rendezvous).mock.instances[0]; + + await waitFor(() => + expect(mockedFlow).toHaveBeenLastCalledWith( + expect.objectContaining({ + phase: Phase.LegacyConnected, + }), + ), + ); + expect(mockedFlow).toHaveBeenLastCalledWith({ + phase: Phase.LegacyConnected, + confirmationDigits: mockConfirmationDigits, + onClick: expect.any(Function), + }); + expect(rendezvous.generateCode).toHaveBeenCalled(); + expect(rendezvous.startAfterShowingCode).toHaveBeenCalled(); - expect(rendezvous.generateCode).toHaveBeenCalled(); - expect(rendezvous.startAfterShowingCode).toHaveBeenCalled(); - expect(rendezvous.declineLoginOnExistingDevice).toHaveBeenCalled(); - }); + // approve + const onClick = mockedFlow.mock.calls[0][0].onClick; + await onClick(Click.Approve); - test("approve - no crypto", async () => { - (client as any).crypto = undefined; - (client as any).getCrypto = () => undefined; - const onFinished = jest.fn(); - render(getComponent({ client, onFinished })); - const rendezvous = mocked(MSC3906Rendezvous).mock.instances[0]; - - await waitFor(() => - expect(mockedFlow).toHaveBeenLastCalledWith( - expect.objectContaining({ - phase: Phase.Connected, - }), - ), - ); - expect(mockedFlow).toHaveBeenLastCalledWith({ - phase: Phase.Connected, - confirmationDigits: mockConfirmationDigits, - onClick: expect.any(Function), - }); - expect(rendezvous.generateCode).toHaveBeenCalled(); - expect(rendezvous.startAfterShowingCode).toHaveBeenCalled(); + await waitFor(() => + expect(mockedFlow).toHaveBeenLastCalledWith( + expect.objectContaining({ + phase: Phase.WaitingForDevice, + }), + ), + ); - // approve - const onClick = mockedFlow.mock.calls[0][0].onClick; - await onClick(Click.Approve); + expect(rendezvous.approveLoginOnExistingDevice).toHaveBeenCalledWith("token"); - await waitFor(() => - expect(mockedFlow).toHaveBeenLastCalledWith( - expect.objectContaining({ - phase: Phase.WaitingForDevice, - }), - ), - ); + expect(onFinished).toHaveBeenCalledWith(true); + }); - expect(rendezvous.approveLoginOnExistingDevice).toHaveBeenCalledWith("token"); + test("approve + verifying", async () => { + const onFinished = jest.fn(); + jest.spyOn(MSC3906Rendezvous.prototype, "verifyNewDeviceOnExistingDevice").mockImplementation(() => + unresolvedPromise(), + ); + render(getComponent({ client, onFinished })); + const rendezvous = mocked(MSC3906Rendezvous).mock.instances[0]; + + await waitFor(() => + expect(mockedFlow).toHaveBeenLastCalledWith( + expect.objectContaining({ + phase: Phase.LegacyConnected, + }), + ), + ); + expect(mockedFlow).toHaveBeenLastCalledWith({ + phase: Phase.LegacyConnected, + confirmationDigits: mockConfirmationDigits, + onClick: expect.any(Function), + }); + expect(rendezvous.generateCode).toHaveBeenCalled(); + expect(rendezvous.startAfterShowingCode).toHaveBeenCalled(); + + // approve + const onClick = mockedFlow.mock.calls[0][0].onClick; + onClick(Click.Approve); + + await waitFor(() => + expect(mockedFlow).toHaveBeenLastCalledWith( + expect.objectContaining({ + phase: Phase.Verifying, + }), + ), + ); + + expect(rendezvous.approveLoginOnExistingDevice).toHaveBeenCalledWith("token"); + expect(rendezvous.verifyNewDeviceOnExistingDevice).toHaveBeenCalled(); + // expect(onFinished).toHaveBeenCalledWith(true); + }); + + test("approve + verify", async () => { + const onFinished = jest.fn(); + render(getComponent({ client, onFinished })); + const rendezvous = mocked(MSC3906Rendezvous).mock.instances[0]; + + await waitFor(() => + expect(mockedFlow).toHaveBeenLastCalledWith( + expect.objectContaining({ + phase: Phase.LegacyConnected, + }), + ), + ); + expect(mockedFlow).toHaveBeenLastCalledWith({ + phase: Phase.LegacyConnected, + confirmationDigits: mockConfirmationDigits, + onClick: expect.any(Function), + }); + expect(rendezvous.generateCode).toHaveBeenCalled(); + expect(rendezvous.startAfterShowingCode).toHaveBeenCalled(); + + // approve + const onClick = mockedFlow.mock.calls[0][0].onClick; + await onClick(Click.Approve); + expect(rendezvous.approveLoginOnExistingDevice).toHaveBeenCalledWith("token"); + expect(rendezvous.verifyNewDeviceOnExistingDevice).toHaveBeenCalled(); + expect(rendezvous.close).toHaveBeenCalled(); + expect(onFinished).toHaveBeenCalledWith(true); + }); - expect(onFinished).toHaveBeenCalledWith(true); + test("approve - rate limited", async () => { + mocked(client.requestLoginToken).mockRejectedValue(new HTTPError("rate limit reached", 429)); + const onFinished = jest.fn(); + render(getComponent({ client, onFinished })); + const rendezvous = mocked(MSC3906Rendezvous).mock.instances[0]; + + await waitFor(() => + expect(mockedFlow).toHaveBeenLastCalledWith( + expect.objectContaining({ + phase: Phase.LegacyConnected, + }), + ), + ); + expect(mockedFlow).toHaveBeenLastCalledWith({ + phase: Phase.LegacyConnected, + confirmationDigits: mockConfirmationDigits, + onClick: expect.any(Function), + }); + expect(rendezvous.generateCode).toHaveBeenCalled(); + expect(rendezvous.startAfterShowingCode).toHaveBeenCalled(); + + // approve + const onClick = mockedFlow.mock.calls[0][0].onClick; + await onClick(Click.Approve); + + // the 429 error should be handled and mapped + await waitFor(() => + expect(mockedFlow).toHaveBeenLastCalledWith( + expect.objectContaining({ + phase: Phase.Error, + failureReason: "rate_limited", + }), + ), + ); + }); }); - test("approve + verifying", async () => { - const onFinished = jest.fn(); - jest.spyOn(MSC3906Rendezvous.prototype, "verifyNewDeviceOnExistingDevice").mockImplementation(() => - unresolvedPromise(), + describe("MSC4108", () => { + const getComponent = (props: { client: MatrixClient; onFinished?: () => void }) => ( + + + ); - render(getComponent({ client, onFinished })); - const rendezvous = mocked(MSC3906Rendezvous).mock.instances[0]; - await waitFor(() => - expect(mockedFlow).toHaveBeenLastCalledWith( - expect.objectContaining({ - phase: Phase.Connected, + test("render QR then back", async () => { + const onFinished = jest.fn(); + jest.spyOn(MSC4108SignInWithQR.prototype, "negotiateProtocols").mockReturnValue(unresolvedPromise()); + render(getComponent({ client, onFinished })); + + await waitFor(() => + expect(mockedFlow).toHaveBeenLastCalledWith({ + phase: Phase.ShowingQR, + onClick: expect.any(Function), }), - ), - ); - expect(mockedFlow).toHaveBeenLastCalledWith({ - phase: Phase.Connected, - confirmationDigits: mockConfirmationDigits, - onClick: expect.any(Function), - }); - expect(rendezvous.generateCode).toHaveBeenCalled(); - expect(rendezvous.startAfterShowingCode).toHaveBeenCalled(); + ); - // approve - const onClick = mockedFlow.mock.calls[0][0].onClick; - onClick(Click.Approve); + const rendezvous = mocked(MSC4108SignInWithQR).mock.instances[0]; + expect(rendezvous.generateCode).toHaveBeenCalled(); + expect(rendezvous.negotiateProtocols).toHaveBeenCalled(); - await waitFor(() => - expect(mockedFlow).toHaveBeenLastCalledWith( - expect.objectContaining({ - phase: Phase.Verifying, - }), - ), - ); + // back + const onClick = mockedFlow.mock.calls[0][0].onClick; + await onClick(Click.Back); + expect(onFinished).toHaveBeenCalledWith(false); + expect(rendezvous.cancel).toHaveBeenCalledWith(LegacyRendezvousFailureReason.UserCancelled); + }); - expect(rendezvous.approveLoginOnExistingDevice).toHaveBeenCalledWith("token"); - expect(rendezvous.verifyNewDeviceOnExistingDevice).toHaveBeenCalled(); - // expect(onFinished).toHaveBeenCalledWith(true); - }); + test("failed to connect", async () => { + render(getComponent({ client })); + jest.spyOn(MSC4108SignInWithQR.prototype, "negotiateProtocols").mockResolvedValue({}); + jest.spyOn(MSC4108SignInWithQR.prototype, "deviceAuthorizationGrant").mockRejectedValue( + new HTTPError("Internal Server Error", 500), + ); + const fn = jest.spyOn(MSC4108SignInWithQR.prototype, "cancel"); + await waitFor(() => expect(fn).toHaveBeenLastCalledWith(ClientRendezvousFailureReason.Unknown)); + }); - test("approve + verify", async () => { - const onFinished = jest.fn(); - render(getComponent({ client, onFinished })); - const rendezvous = mocked(MSC3906Rendezvous).mock.instances[0]; + test("reciprocates login", async () => { + jest.spyOn(global.window, "open"); - await waitFor(() => - expect(mockedFlow).toHaveBeenLastCalledWith( - expect.objectContaining({ - phase: Phase.Connected, + render(getComponent({ client })); + jest.spyOn(MSC4108SignInWithQR.prototype, "negotiateProtocols").mockResolvedValue({}); + jest.spyOn(MSC4108SignInWithQR.prototype, "deviceAuthorizationGrant").mockResolvedValue({ + verificationUri: "mock-verification-uri", + }); + + await waitFor(() => + expect(mockedFlow).toHaveBeenLastCalledWith({ + phase: Phase.OutOfBandConfirmation, + onClick: expect.any(Function), }), - ), - ); - expect(mockedFlow).toHaveBeenLastCalledWith({ - phase: Phase.Connected, - confirmationDigits: mockConfirmationDigits, - onClick: expect.any(Function), - }); - expect(rendezvous.generateCode).toHaveBeenCalled(); - expect(rendezvous.startAfterShowingCode).toHaveBeenCalled(); - - // approve - const onClick = mockedFlow.mock.calls[0][0].onClick; - await onClick(Click.Approve); - expect(rendezvous.approveLoginOnExistingDevice).toHaveBeenCalledWith("token"); - expect(rendezvous.verifyNewDeviceOnExistingDevice).toHaveBeenCalled(); - expect(rendezvous.close).toHaveBeenCalled(); - expect(onFinished).toHaveBeenCalledWith(true); - }); + ); - test("approve - rate limited", async () => { - mocked(client.requestLoginToken).mockRejectedValue(new HTTPError("rate limit reached", 429)); - const onFinished = jest.fn(); - render(getComponent({ client, onFinished })); - const rendezvous = mocked(MSC3906Rendezvous).mock.instances[0]; + const onClick = mockedFlow.mock.calls[0][0].onClick; + await onClick(Click.Approve); - await waitFor(() => - expect(mockedFlow).toHaveBeenLastCalledWith( - expect.objectContaining({ - phase: Phase.Connected, + await waitFor(() => + expect(mockedFlow).toHaveBeenLastCalledWith({ + phase: Phase.WaitingForDevice, + onClick: expect.any(Function), }), - ), - ); - expect(mockedFlow).toHaveBeenLastCalledWith({ - phase: Phase.Connected, - confirmationDigits: mockConfirmationDigits, - onClick: expect.any(Function), + ); + expect(global.window.open).toHaveBeenCalledWith("mock-verification-uri", "_blank"); }); - expect(rendezvous.generateCode).toHaveBeenCalled(); - expect(rendezvous.startAfterShowingCode).toHaveBeenCalled(); - // approve - const onClick = mockedFlow.mock.calls[0][0].onClick; - await onClick(Click.Approve); + test("handles errors during reciprocation", async () => { + render(getComponent({ client })); + jest.spyOn(MSC4108SignInWithQR.prototype, "negotiateProtocols").mockResolvedValue({}); + jest.spyOn(MSC4108SignInWithQR.prototype, "deviceAuthorizationGrant").mockResolvedValue({}); + await waitFor(() => + expect(mockedFlow).toHaveBeenLastCalledWith({ + phase: Phase.OutOfBandConfirmation, + onClick: expect.any(Function), + }), + ); + + jest.spyOn(MSC4108SignInWithQR.prototype, "shareSecrets").mockRejectedValue( + new HTTPError("Internal Server Error", 500), + ); + const onClick = mockedFlow.mock.calls[0][0].onClick; + await onClick(Click.Approve); + + await waitFor(() => + expect(mockedFlow).toHaveBeenLastCalledWith( + expect.objectContaining({ + phase: Phase.Error, + failureReason: ClientRendezvousFailureReason.Unknown, + }), + ), + ); + }); - // the 429 error should be handled and mapped - await waitFor(() => - expect(mockedFlow).toHaveBeenLastCalledWith( - expect.objectContaining({ - phase: Phase.Error, - failureReason: "rate_limited", + test("handles user cancelling during reciprocation", async () => { + render(getComponent({ client })); + jest.spyOn(MSC4108SignInWithQR.prototype, "negotiateProtocols").mockResolvedValue({}); + jest.spyOn(MSC4108SignInWithQR.prototype, "deviceAuthorizationGrant").mockResolvedValue({}); + jest.spyOn(MSC4108SignInWithQR.prototype, "deviceAuthorizationGrant").mockResolvedValue({}); + await waitFor(() => + expect(mockedFlow).toHaveBeenLastCalledWith({ + phase: Phase.OutOfBandConfirmation, + onClick: expect.any(Function), }), - ), - ); + ); + + jest.spyOn(MSC4108SignInWithQR.prototype, "cancel").mockResolvedValue(); + const onClick = mockedFlow.mock.calls[0][0].onClick; + await onClick(Click.Cancel); + + const rendezvous = mocked(MSC4108SignInWithQR).mock.instances[0]; + expect(rendezvous.cancel).toHaveBeenCalledWith(MSC4108FailureReason.UserCancelled); + }); }); }); diff --git a/test/components/views/settings/devices/LoginWithQRFlow-test.tsx b/test/components/views/settings/devices/LoginWithQRFlow-test.tsx index 91004abc264..1f896b28b78 100644 --- a/test/components/views/settings/devices/LoginWithQRFlow-test.tsx +++ b/test/components/views/settings/devices/LoginWithQRFlow-test.tsx @@ -16,7 +16,11 @@ limitations under the License. import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react"; import React from "react"; -import { LegacyRendezvousFailureReason } from "matrix-js-sdk/src/rendezvous"; +import { + ClientRendezvousFailureReason, + LegacyRendezvousFailureReason, + MSC4108FailureReason, +} from "matrix-js-sdk/src/rendezvous"; import LoginWithQRFlow from "../../../../../src/components/views/auth/LoginWithQRFlow"; import { LoginWithQRFailureReason, FailureReason } from "../../../../../src/components/views/auth/LoginWithQR"; @@ -54,7 +58,7 @@ describe("", () => { expect(screen.getAllByTestId("cancel-button")).toHaveLength(1); expect(container).toMatchSnapshot(); fireEvent.click(screen.getByTestId("cancel-button")); - expect(onClick).toHaveBeenCalledWith(Click.Cancel); + expect(onClick).toHaveBeenCalledWith(Click.Cancel, undefined); }); it("renders QR code", async () => { @@ -64,24 +68,16 @@ describe("", () => { expect(container).toMatchSnapshot(); }); - it("renders spinner while connecting", async () => { - const { container } = render(getComponent({ phase: Phase.Connecting })); - expect(screen.getAllByTestId("cancel-button")).toHaveLength(1); - expect(container).toMatchSnapshot(); - fireEvent.click(screen.getByTestId("cancel-button")); - expect(onClick).toHaveBeenCalledWith(Click.Cancel); - }); - it("renders code when connected", async () => { - const { container } = render(getComponent({ phase: Phase.Connected, confirmationDigits: "mock-digits" })); + const { container } = render(getComponent({ phase: Phase.LegacyConnected, confirmationDigits: "mock-digits" })); expect(screen.getAllByText("mock-digits")).toHaveLength(1); expect(screen.getAllByTestId("decline-login-button")).toHaveLength(1); expect(screen.getAllByTestId("approve-login-button")).toHaveLength(1); expect(container).toMatchSnapshot(); fireEvent.click(screen.getByTestId("decline-login-button")); - expect(onClick).toHaveBeenCalledWith(Click.Decline); + expect(onClick).toHaveBeenCalledWith(Click.Decline, undefined); fireEvent.click(screen.getByTestId("approve-login-button")); - expect(onClick).toHaveBeenCalledWith(Click.Approve); + expect(onClick).toHaveBeenCalledWith(Click.Approve, undefined); }); it("renders spinner while signing in", async () => { @@ -89,7 +85,7 @@ describe("", () => { expect(screen.getAllByTestId("cancel-button")).toHaveLength(1); expect(container).toMatchSnapshot(); fireEvent.click(screen.getByTestId("cancel-button")); - expect(onClick).toHaveBeenCalledWith(Click.Cancel); + expect(onClick).toHaveBeenCalledWith(Click.Cancel, undefined); }); it("renders spinner while verifying", async () => { @@ -97,10 +93,17 @@ describe("", () => { expect(container).toMatchSnapshot(); }); + it("renders check code confirmation", async () => { + const { container } = render(getComponent({ phase: Phase.OutOfBandConfirmation })); + expect(container).toMatchSnapshot(); + }); + describe("errors", () => { for (const failureReason of [ ...Object.values(LegacyRendezvousFailureReason), + ...Object.values(MSC4108FailureReason), ...Object.values(LoginWithQRFailureReason), + ...Object.values(ClientRendezvousFailureReason), ]) { it(`renders ${failureReason}`, async () => { const { container } = render( @@ -110,10 +113,7 @@ describe("", () => { }), ); expect(screen.getAllByTestId("cancellation-message")).toHaveLength(1); - expect(screen.getAllByTestId("try-again-button")).toHaveLength(1); expect(container).toMatchSnapshot(); - fireEvent.click(screen.getByTestId("try-again-button")); - expect(onClick).toHaveBeenCalledWith(Click.TryAgain); }); } }); diff --git a/test/components/views/settings/devices/LoginWithQRSection-test.tsx b/test/components/views/settings/devices/LoginWithQRSection-test.tsx index 8dc78bfd28d..027aeed45b0 100644 --- a/test/components/views/settings/devices/LoginWithQRSection-test.tsx +++ b/test/components/views/settings/devices/LoginWithQRSection-test.tsx @@ -18,11 +18,17 @@ import { render } from "@testing-library/react"; import { mocked } from "jest-mock"; import { IClientWellKnown, IServerVersions, MatrixClient, GET_LOGIN_TOKEN_CAPABILITY } from "matrix-js-sdk/src/matrix"; import React from "react"; +import fetchMock from "fetch-mock-jest"; import LoginWithQRSection from "../../../../../src/components/views/settings/devices/LoginWithQRSection"; import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg"; function makeClient(wellKnown: IClientWellKnown) { + const crypto = mocked({ + supportsSecretsForQrLogin: jest.fn().mockReturnValue(true), + isCrossSigningReady: jest.fn().mockReturnValue(true), + }); + return mocked({ getUser: jest.fn(), isGuest: jest.fn().mockReturnValue(false), @@ -38,6 +44,7 @@ function makeClient(wellKnown: IClientWellKnown) { on: jest.fn(), }, getClientWellKnown: jest.fn().mockReturnValue(wellKnown), + getCrypto: jest.fn().mockReturnValue(crypto), } as unknown as MatrixClient); } @@ -53,68 +60,105 @@ describe("", () => { jest.spyOn(MatrixClientPeg, "get").mockReturnValue(makeClient({})); }); - const defaultProps = { - onShowQr: () => {}, - versions: makeVersions({}), - wellKnown: {}, - }; + describe("MSC3906", () => { + const defaultProps = { + onShowQr: () => {}, + versions: makeVersions({}), + wellKnown: {}, + }; - const getComponent = (props = {}) => ; + const getComponent = (props = {}) => ; - describe("should not render", () => { - it("no support at all", () => { - const { container } = render(getComponent()); - expect(container).toMatchSnapshot(); - }); + describe("should not render", () => { + it("no support at all", () => { + const { container } = render(getComponent()); + expect(container).toMatchSnapshot(); + }); - it("only get_login_token enabled", async () => { - const { container } = render( - getComponent({ capabilities: { [GET_LOGIN_TOKEN_CAPABILITY.name]: { enabled: true } } }), - ); - expect(container).toMatchSnapshot(); - }); + it("only get_login_token enabled", async () => { + const { container } = render( + getComponent({ capabilities: { [GET_LOGIN_TOKEN_CAPABILITY.name]: { enabled: true } } }), + ); + expect(container).toMatchSnapshot(); + }); - it("MSC3886 + get_login_token disabled", async () => { - const { container } = render( - getComponent({ - versions: makeVersions({ "org.matrix.msc3886": true }), - capabilities: { [GET_LOGIN_TOKEN_CAPABILITY.name]: { enabled: false } }, - }), - ); - expect(container).toMatchSnapshot(); + it("MSC3886 + get_login_token disabled", async () => { + const { container } = render( + getComponent({ + versions: makeVersions({ "org.matrix.msc3886": true }), + capabilities: { [GET_LOGIN_TOKEN_CAPABILITY.name]: { enabled: false } }, + }), + ); + expect(container).toMatchSnapshot(); + }); }); - }); - describe("should render panel", () => { - it("get_login_token + MSC3886", async () => { - const { container } = render( - getComponent({ - versions: makeVersions({ - "org.matrix.msc3886": true, + describe("should render panel", () => { + it("get_login_token + MSC3886", async () => { + const { container } = render( + getComponent({ + versions: makeVersions({ + "org.matrix.msc3886": true, + }), + capabilities: { + [GET_LOGIN_TOKEN_CAPABILITY.name]: { enabled: true }, + }, }), - capabilities: { - [GET_LOGIN_TOKEN_CAPABILITY.name]: { enabled: true }, + ); + expect(container).toMatchSnapshot(); + }); + + it("get_login_token + .well-known", async () => { + const wellKnown = { + "io.element.rendezvous": { + server: "https://rz.local", }, - }), - ); - expect(container).toMatchSnapshot(); + }; + jest.spyOn(MatrixClientPeg, "get").mockReturnValue(makeClient(wellKnown)); + const { container } = render( + getComponent({ + versions: makeVersions({}), + capabilities: { [GET_LOGIN_TOKEN_CAPABILITY.name]: { enabled: true } }, + wellKnown, + }), + ); + expect(container).toMatchSnapshot(); + }); }); + }); - it("get_login_token + .well-known", async () => { - const wellKnown = { - "io.element.rendezvous": { - server: "https://rz.local", - }, + describe("MSC4108", () => { + describe("MSC4108", () => { + const defaultProps = { + onShowQr: () => {}, + versions: makeVersions({ "org.matrix.msc4108": true }), + wellKnown: {}, }; - jest.spyOn(MatrixClientPeg, "get").mockReturnValue(makeClient(wellKnown)); - const { container } = render( - getComponent({ - versions: makeVersions({}), - capabilities: { [GET_LOGIN_TOKEN_CAPABILITY.name]: { enabled: true } }, - wellKnown, - }), - ); - expect(container).toMatchSnapshot(); + + const getComponent = (props = {}) => ; + + let client: MatrixClient; + beforeEach(() => { + client = makeClient({}); + jest.spyOn(MatrixClientPeg, "get").mockReturnValue(client); + }); + + test("no homeserver support", async () => { + const { container } = render(getComponent({ versions: makeVersions({ "org.matrix.msc4108": false }) })); + expect(container.textContent).toBe(""); // show nothing + }); + + test("no support in crypto", async () => { + client.getCrypto()!.exportSecretsBundle = undefined; + const { container } = render(getComponent({ client })); + expect(container.textContent).toBe(""); // show nothing + }); + + test("failed to connect", async () => { + fetchMock.catch(500); + const { container } = render(getComponent({ client })); + expect(container.textContent).toBe(""); // show nothing + }); }); }); }); diff --git a/test/components/views/settings/devices/__snapshots__/LoginWithQRFlow-test.tsx.snap b/test/components/views/settings/devices/__snapshots__/LoginWithQRFlow-test.tsx.snap index 0698d0363f4..56873e510b2 100644 --- a/test/components/views/settings/devices/__snapshots__/LoginWithQRFlow-test.tsx.snap +++ b/test/components/views/settings/devices/__snapshots__/LoginWithQRFlow-test.tsx.snap @@ -1,6 +1,40 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[` errors renders data_mismatch 1`] = ` +exports[` errors renders authorization_expired 1`] = ` +
+
+
+
+
+
+

+ The sign in was not completed in time +

+

+ Sign in expired. Please try again. +

+
+
+
+
+`; + +exports[` errors renders check_code_mismatch 1`] = `
errors renders data_mismatch 1`] = `
+
+
+`; + +exports[` errors renders device_already_exists 1`] = ` +
+
+
- Try again +
+

+ Something went wrong! +

+

+ An unexpected error occurred. The request to connect your other device has been cancelled. +

+
+
+
+
+`; + +exports[` errors renders device_not_found 1`] = ` +
+
+
- Cancel +
+

+ Something went wrong! +

+

+ An unexpected error occurred. The request to connect your other device has been cancelled. +

+
+
+
+`; + +exports[` errors renders etag_missing 1`] = ` +
+
+
+
+
+
+

+ Something went wrong! +

+

+ An unexpected error occurred. This may be due to a browser extension, proxy server, or server misconfiguration. +

+
+
`; @@ -80,24 +199,41 @@ exports[` errors renders expired 1`] = `
+
+
+`; + +exports[` errors renders expired 2`] = ` +
+
+
- Try again +
-
- Cancel -
+ The sign in was not completed in time + +

+ Sign in expired. Please try again. +

+
`; @@ -121,39 +257,56 @@ exports[` errors renders homeserver_lacks_support 1`] = `

- Other device not compatible + Something went wrong!

- This device does not support signing in to the other device with a QR code. + An unexpected error occurred. The request to connect your other device has been cancelled.

+
+
+`; + +exports[` errors renders homeserver_lacks_support 2`] = ` +
+
+
- Try again +
-
- Cancel -
+ Something went wrong! + +

+ An unexpected error occurred. The request to connect your other device has been cancelled. +

+
`; -exports[` errors renders invalid_code 1`] = ` +exports[` errors renders insecure_channel_detected 1`] = `
errors renders invalid_code 1`] = `
+
+
+`; + +exports[` errors renders invalid_code 1`] = ` +
+
+
+
+
+

+ Something went wrong! +

+

+ An unexpected error occurred. The request to connect your other device has been cancelled. +

+
+
+
+
+`; + +exports[` errors renders other_device_already_signed_in 1`] = ` +
+
+
+
- Try again +
+

+ Your other device is already signed in +

+

+ You don’t need to do anything else. +

+
+
+
+
+`; + +exports[` errors renders other_device_not_signed_in 1`] = ` +
+
+
- Cancel +
+

+ Something went wrong! +

+

+ An unexpected error occurred. The request to connect your other device has been cancelled. +

+
+
+
+
+`; + +exports[` errors renders rate_limited 1`] = ` +
+
+
+
+
+
+

+ Something went wrong! +

+

+ Too many attempts in a short time. Wait some time before trying again. +

+
+
+
+
+`; + +exports[` errors renders unexpected_message_received 1`] = ` +
+
+
+
+
+
+

+ Something went wrong! +

+

+ An unexpected error occurred. The request to connect your other device has been cancelled. +

+
+
+
+
+`; + +exports[` errors renders unknown 1`] = ` +
+
+
+
+
+
+

+ Something went wrong! +

+

+ An unexpected error occurred. The request to connect your other device has been cancelled. +

+
`; -exports[` errors renders other_device_already_signed_in 1`] = ` +exports[` errors renders unknown 2`] = `
errors renders other_device_already_signed_in 1`] = class="mx_LoginWithQR_main" >
errors renders other_device_already_signed_in 1`] =

- Your other device is already signed in + Something went wrong!

- You don’t need to do anything else. + An unexpected error occurred. The request to connect your other device has been cancelled.

-
- Try again -
-
- Cancel -
-
+ />
`; -exports[` errors renders other_device_not_signed_in 1`] = ` +exports[` errors renders unsupported_algorithm 1`] = `
errors renders other_device_not_signed_in 1`] = `
-
- Try again -
-
- Cancel -
-
+ />
`; -exports[` errors renders rate_limited 1`] = ` +exports[` errors renders unsupported_protocol 1`] = `
errors renders rate_limited 1`] = `

- Something went wrong! + Other device not compatible

- Too many attempts in a short time. Wait some time before trying again. + This device does not support signing in to the other device with a QR code.

-
- Try again -
-
- Cancel -
-
+ />
`; -exports[` errors renders unknown 1`] = ` +exports[` errors renders unsupported_protocol 2`] = `
errors renders unknown 1`] = `

- Something went wrong! + Other device not compatible

- An unexpected error occurred. The request to connect your other device has been cancelled. + This device does not support signing in to the other device with a QR code.

-
- Try again -
-
- Cancel -
-
+ />
`; -exports[` errors renders unsupported_algorithm 1`] = ` +exports[` errors renders user_cancelled 1`] = `
errors renders unsupported_algorithm 1`] = `

- Other device not compatible + Sign in request cancelled

- This device does not support signing in to the other device with a QR code. + The sign in was cancelled on the other device.

-
- Try again -
-
- Cancel -
-
+ />
`; -exports[` errors renders unsupported_transport 1`] = ` +exports[` errors renders user_cancelled 2`] = `
errors renders unsupported_transport 1`] = `

- Other device not compatible + Sign in request cancelled

- This device does not support signing in to the other device with a QR code. + The sign in was cancelled on the other device.

-
- Try again -
-
- Cancel -
-
+ />
`; -exports[` errors renders user_cancelled 1`] = ` +exports[` errors renders user_declined 1`] = `
errors renders user_cancelled 1`] = `

- Sign in request cancelled + Sign in declined

- The sign in was cancelled on the other device. + You or the account provider declined the sign in request.

-
- Try again -
-
- Cancel -
-
+ />
`; -exports[` errors renders user_declined 1`] = ` +exports[` errors renders user_declined 2`] = `
errors renders user_declined 1`] = `

- You declined the request from your other device to sign in. + You or the account provider declined the sign in request.

-
- Try again -
-
- Cancel -
-
+ />
`; @@ -686,10 +890,10 @@ exports[` renders QR code 1`] = `
  • - Point the camera at the QR code shown here + Scan the QR code shown here
  • - Follow the instructions to link your other device + Follow the remaining instructions
  • @@ -700,7 +904,7 @@ exports[` renders QR code 1`] = `
    `; -exports[` renders code when connected 1`] = ` +exports[` renders check code confirmation 1`] = `
    renders code when connected 1`] = `
    -

    - Check that the code below matches with your other device: +

    + Enter the number shown on your other device +

    +

    + This will verify that the connection to your other device is secure.

    -
    - mock-digits -
    + 2-digit code +
    -
    -
    -
    -
    - By approving access for this device, it will have full access to your account. -
    + class="_container_9zyti_18 mx_LoginWithQR_checkCode_input mx_no_textinput" + > + +