Merge branch 'develop' into florianduros/rip-out-legacy-crypto/migrate-roomview-isencrypted
This commit is contained in:
commit
223dd9698c
26 changed files with 188 additions and 63 deletions
33
.github/actions/download-verify-element-tarball/action.yml
vendored
Normal file
33
.github/actions/download-verify-element-tarball/action.yml
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
name: Upload release assets
|
||||
description: Uploads assets to an existing release and optionally signs them
|
||||
inputs:
|
||||
tag:
|
||||
description: GitHub release tag to fetch assets from.
|
||||
required: true
|
||||
out-file-path:
|
||||
description: Path to where the webapp should be extracted to.
|
||||
required: true
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Download current version for its old bundles
|
||||
id: current_download
|
||||
uses: robinraju/release-downloader@a96f54c1b5f5e09e47d9504526e96febd949d4c2 # v1
|
||||
with:
|
||||
tag: steps.current_version.outputs.version
|
||||
fileName: element-*.tar.gz*
|
||||
out-file-path: ${{ runner.temp }}/download-verify-element-tarball
|
||||
|
||||
- name: Verify tarball
|
||||
run: gpg --verify element-*.tar.gz.asc element-*.tar.gz
|
||||
working-directory: ${{ runner.temp }}/download-verify-element-tarball
|
||||
|
||||
- name: Extract tarball
|
||||
run: tar xvzf element-*.tar.gz -C webapp --strip-components=1
|
||||
working-directory: ${{ runner.temp }}/download-verify-element-tarball
|
||||
|
||||
- name: Move webapp to out-file-path
|
||||
run: mv ${{ runner.temp }}/download-verify-element-tarball/webapp ${{ inputs.out-file-path }}
|
||||
|
||||
- name: Clean up temp directory
|
||||
run: rm -R ${{ runner.temp }}/download-verify-element-tarball
|
1
.github/workflows/build_develop.yml
vendored
1
.github/workflows/build_develop.yml
vendored
|
@ -20,6 +20,7 @@ jobs:
|
|||
permissions:
|
||||
checks: read
|
||||
pages: write
|
||||
deployments: write
|
||||
env:
|
||||
R2_BUCKET: "element-web-develop"
|
||||
R2_URL: ${{ vars.CF_R2_S3_API }}
|
||||
|
|
88
.github/workflows/deploy.yml
vendored
Normal file
88
.github/workflows/deploy.yml
vendored
Normal file
|
@ -0,0 +1,88 @@
|
|||
# Manual deploy workflow for deploying to app.element.io & staging.element.io
|
||||
# Runs automatically for staging.element.io when an RC or Release is published
|
||||
# Note: Does *NOT* run automatically for app.element.io so that it gets tested on staging.element.io beforehand
|
||||
name: Build and Deploy ${{ inputs.site || 'staging.element.io' }}
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
site:
|
||||
description: Which site to deploy to
|
||||
required: true
|
||||
default: staging.element.io
|
||||
type: choice
|
||||
options:
|
||||
- staging.element.io
|
||||
- app.element.io
|
||||
concurrency: ${{ inputs.site || 'staging.element.io' }}
|
||||
permissions: {}
|
||||
jobs:
|
||||
deploy:
|
||||
name: "Deploy to Cloudflare Pages"
|
||||
runs-on: ubuntu-24.04
|
||||
environment: ${{ inputs.site || 'staging.element.io' }}
|
||||
permissions:
|
||||
checks: read
|
||||
deployments: write
|
||||
env:
|
||||
SITE: ${{ inputs.site || 'staging.element.io' }}
|
||||
steps:
|
||||
- name: Load GPG key
|
||||
run: |
|
||||
curl https://packages.element.io/element-release-key.gpg | gpg --import
|
||||
gpg -k "$GPG_FINGERPRINT"
|
||||
env:
|
||||
GPG_FINGERPRINT: ${{ secrets.GPG_FINGERPRINT }}
|
||||
|
||||
- name: Check current version on deployment
|
||||
id: current_version
|
||||
run: |
|
||||
echo "version=$(curl -s https://$SITE/version)" >> $GITHUB_OUTPUT
|
||||
|
||||
# The current version bundle melding dance is skipped if the version we're deploying is the same
|
||||
# as then we're just doing a re-deploy of the same version with potentially different configs.
|
||||
- name: Download current version for its old bundles
|
||||
id: current_download
|
||||
if: steps.current_version.outputs.version != github.ref_name
|
||||
uses: element-hq/element-web/.github/actions/download-verify-element-tarball@${{ github.ref_name }}
|
||||
with:
|
||||
tag: steps.current_version.outputs.version
|
||||
out-file-path: current_version
|
||||
|
||||
- name: Download target version
|
||||
uses: element-hq/element-web/.github/actions/download-verify-element-tarball@${{ github.ref_name }}
|
||||
with:
|
||||
tag: ${{ github.ref_name }}
|
||||
out-file-path: _deploy
|
||||
|
||||
- name: Merge current bundles into target
|
||||
if: steps.current_download.outcome == 'success'
|
||||
run: cp -vnpr current_version/bundles/* _deploy/bundles/
|
||||
|
||||
- name: Copy config
|
||||
run: cp element.io/app/config.json _deploy/config.json
|
||||
|
||||
- name: Populate 404.html
|
||||
run: echo "404 Not Found" > _deploy/404.html
|
||||
|
||||
- name: Populate _headers
|
||||
run: cp .github/cfp_headers _deploy/_headers
|
||||
|
||||
- name: Wait for other steps to succeed
|
||||
uses: t3chguy/wait-on-check-action@18541021811b56544d90e0f073401c2b99e249d6 # fork
|
||||
with:
|
||||
ref: ${{ github.sha }}
|
||||
running-workflow-name: "Build and Deploy ${{ env.SITE }}"
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
wait-interval: 10
|
||||
check-regexp: ^((?!SonarCloud|SonarQube|issue|board|label|Release|prepare|GitHub Pages).)*$
|
||||
|
||||
- name: Deploy to Cloudflare Pages
|
||||
uses: cloudflare/pages-action@f0a1cd58cd66095dee69bfa18fa5efd1dde93bca # v1
|
||||
with:
|
||||
apiToken: ${{ secrets.CF_PAGES_TOKEN }}
|
||||
accountId: ${{ secrets.CF_PAGES_ACCOUNT_ID }}
|
||||
projectName: ${{ env.SITE == 'staging.element.io' && 'element-web-staging' || 'element-web' }}
|
||||
directory: _deploy
|
||||
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
|
|
@ -20,7 +20,7 @@ import { randB64Bytes } from "../../utils/rand";
|
|||
// Docker tag to use for synapse docker 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:127c68d4468019ce363c8b2fd7a42a3ef50710eb3aaf288a2295dd4623ce9f54";
|
||||
const DOCKER_TAG = "develop@sha256:34da08a44994e0ad2def7ed5f28c3cc7a2f7ead9722f4ae87b23030f59384ea5";
|
||||
|
||||
async function cfgDirFromTemplate(opts: StartHomeserverOpts): Promise<Omit<HomeserverConfig, "dockerUrl">> {
|
||||
const templateDir = path.join(__dirname, "templates", opts.template);
|
||||
|
|
7
src/@types/matrix-js-sdk.d.ts
vendored
7
src/@types/matrix-js-sdk.d.ts
vendored
|
@ -22,6 +22,13 @@ declare module "matrix-js-sdk/src/types" {
|
|||
[BLURHASH_FIELD]?: string;
|
||||
}
|
||||
|
||||
export interface ImageInfo {
|
||||
/**
|
||||
* @see https://github.com/matrix-org/matrix-spec-proposals/pull/4230
|
||||
*/
|
||||
"org.matrix.msc4230.is_animated"?: boolean;
|
||||
}
|
||||
|
||||
export interface StateEvents {
|
||||
// Jitsi-backed video room state events
|
||||
[JitsiCallMemberEventType]: JitsiCallMemberContent;
|
||||
|
|
|
@ -56,6 +56,7 @@ import { createThumbnail } from "./utils/image-media";
|
|||
import { attachMentions, attachRelation } from "./components/views/rooms/SendMessageComposer";
|
||||
import { doMaybeLocalRoomAction } from "./utils/local-room";
|
||||
import { SdkContextClass } from "./contexts/SDKContext";
|
||||
import { blobIsAnimated } from "./utils/Image.ts";
|
||||
|
||||
// scraped out of a macOS hidpi (5660ppm) screenshot png
|
||||
// 5669 px (x-axis) , 5669 px (y-axis) , per metre
|
||||
|
@ -150,15 +151,20 @@ async function infoForImageFile(matrixClient: MatrixClient, roomId: string, imag
|
|||
thumbnailType = "image/jpeg";
|
||||
}
|
||||
|
||||
// We don't await this immediately so it can happen in the background
|
||||
const isAnimatedPromise = blobIsAnimated(imageFile.type, imageFile);
|
||||
|
||||
const imageElement = await loadImageElement(imageFile);
|
||||
|
||||
const result = await createThumbnail(imageElement.img, imageElement.width, imageElement.height, thumbnailType);
|
||||
const imageInfo = result.info;
|
||||
|
||||
imageInfo["org.matrix.msc4230.is_animated"] = await isAnimatedPromise;
|
||||
|
||||
// For lesser supported image types, always include the thumbnail even if it is larger
|
||||
if (!ALWAYS_INCLUDE_THUMBNAIL.includes(imageFile.type)) {
|
||||
// we do all sizing checks here because we still rely on thumbnail generation for making a blurhash from.
|
||||
const sizeDifference = imageFile.size - imageInfo.thumbnail_info!.size;
|
||||
const sizeDifference = imageFile.size - imageInfo.thumbnail_info!.size!;
|
||||
if (
|
||||
// image is small enough already
|
||||
imageFile.size <= IMAGE_SIZE_THRESHOLD_THUMBNAIL ||
|
||||
|
|
|
@ -230,12 +230,15 @@ export default class DeviceListener {
|
|||
private async getKeyBackupInfo(): Promise<KeyBackupInfo | null> {
|
||||
if (!this.client) return null;
|
||||
const now = new Date().getTime();
|
||||
const crypto = this.client.getCrypto();
|
||||
if (!crypto) return null;
|
||||
|
||||
if (
|
||||
!this.keyBackupInfo ||
|
||||
!this.keyBackupFetchedAt ||
|
||||
this.keyBackupFetchedAt < now - KEY_BACKUP_POLL_INTERVAL
|
||||
) {
|
||||
this.keyBackupInfo = await this.client.getKeyBackupVersion();
|
||||
this.keyBackupInfo = await crypto.getKeyBackupInfo();
|
||||
this.keyBackupFetchedAt = now;
|
||||
}
|
||||
return this.keyBackupInfo;
|
||||
|
|
|
@ -279,7 +279,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
|||
if (!forceReset) {
|
||||
try {
|
||||
this.setState({ phase: Phase.Loading });
|
||||
backupInfo = await cli.getKeyBackupVersion();
|
||||
backupInfo = await crypto.getKeyBackupInfo();
|
||||
} catch (e) {
|
||||
logger.error("Error fetching backup data from server", e);
|
||||
this.setState({ phase: Phase.LoadError });
|
||||
|
|
|
@ -1638,7 +1638,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
} else {
|
||||
// otherwise check the server to see if there's a new one
|
||||
try {
|
||||
newVersionInfo = await cli.getKeyBackupVersion();
|
||||
newVersionInfo = (await cli.getCrypto()?.getKeyBackupInfo()) ?? null;
|
||||
if (newVersionInfo !== null) haveNewVersion = true;
|
||||
} catch (e) {
|
||||
logger.error("Saw key backup error but failed to check backup version!", e);
|
||||
|
|
|
@ -109,7 +109,7 @@ export default class LogoutDialog extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
// backup is not active. see if there is a backup version on the server we ought to back up to.
|
||||
const backupInfo = await client.getKeyBackupVersion();
|
||||
const backupInfo = await crypto.getKeyBackupInfo();
|
||||
this.setState({ backupStatus: backupInfo ? BackupStatus.SERVER_BACKUP_BUT_DISABLED : BackupStatus.NO_BACKUP });
|
||||
}
|
||||
|
||||
|
|
|
@ -258,7 +258,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent<IProps,
|
|||
});
|
||||
try {
|
||||
const cli = MatrixClientPeg.safeGet();
|
||||
const backupInfo = await cli.getKeyBackupVersion();
|
||||
const backupInfo = (await cli.getCrypto()?.getKeyBackupInfo()) ?? null;
|
||||
const has4S = await cli.secretStorage.hasKey();
|
||||
const backupKeyStored = has4S ? await cli.isKeyBackupKeyStored() : null;
|
||||
this.setState({
|
||||
|
|
|
@ -275,7 +275,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
|||
}
|
||||
|
||||
const content = this.props.mxEvent.getContent<ImageContent>();
|
||||
let isAnimated = mayBeAnimated(content.info?.mimetype);
|
||||
let isAnimated = content.info?.["org.matrix.msc4230.is_animated"] ?? mayBeAnimated(content.info?.mimetype);
|
||||
|
||||
// If there is no included non-animated thumbnail then we will generate our own, we can't depend on the server
|
||||
// because 1. encryption and 2. we can't ask the server specifically for a non-animated thumbnail.
|
||||
|
@ -298,8 +298,15 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
|||
}
|
||||
|
||||
try {
|
||||
const blob = await this.props.mediaEventHelper!.sourceBlob.value;
|
||||
if (!(await blobIsAnimated(content.info?.mimetype, blob))) {
|
||||
// If we didn't receive the MSC4230 is_animated flag
|
||||
// then we need to check if the image is animated by downloading it.
|
||||
if (
|
||||
content.info?.["org.matrix.msc4230.is_animated"] === false ||
|
||||
!(await blobIsAnimated(
|
||||
content.info?.mimetype,
|
||||
await this.props.mediaEventHelper!.sourceBlob.value,
|
||||
))
|
||||
) {
|
||||
isAnimated = false;
|
||||
}
|
||||
|
||||
|
|
|
@ -118,7 +118,7 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
|
|||
this.getUpdatedDiagnostics();
|
||||
try {
|
||||
const cli = MatrixClientPeg.safeGet();
|
||||
const backupInfo = await cli.getKeyBackupVersion();
|
||||
const backupInfo = (await cli.getCrypto()?.getKeyBackupInfo()) ?? null;
|
||||
const backupTrustInfo = backupInfo ? await cli.getCrypto()?.isKeyBackupTrusted(backupInfo) : undefined;
|
||||
|
||||
const activeBackupVersion = (await cli.getCrypto()?.getActiveSessionBackupVersion()) ?? null;
|
||||
|
@ -192,12 +192,9 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
|
|||
if (!proceed) return;
|
||||
this.setState({ loading: true });
|
||||
const versionToDelete = this.state.backupInfo!.version!;
|
||||
MatrixClientPeg.safeGet()
|
||||
.getCrypto()
|
||||
?.deleteKeyBackupVersion(versionToDelete)
|
||||
.then(() => {
|
||||
this.loadBackupStatus();
|
||||
});
|
||||
// deleteKeyBackupVersion fires a key backup status event
|
||||
// which will update the UI
|
||||
MatrixClientPeg.safeGet().getCrypto()?.deleteKeyBackupVersion(versionToDelete);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
|
@ -125,7 +125,7 @@ export class SetupEncryptionStore extends EventEmitter {
|
|||
this.emit("update");
|
||||
try {
|
||||
const cli = MatrixClientPeg.safeGet();
|
||||
const backupInfo = await cli.getKeyBackupVersion();
|
||||
const backupInfo = (await cli.getCrypto()?.getKeyBackupInfo()) ?? null;
|
||||
this.backupInfo = backupInfo;
|
||||
this.emit("update");
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
|||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { EncryptedFile } from "matrix-js-sdk/src/types";
|
||||
import { ImageInfo } from "matrix-js-sdk/src/types";
|
||||
|
||||
import { BlurhashEncoder } from "../BlurhashEncoder";
|
||||
|
||||
|
@ -15,19 +15,7 @@ type ThumbnailableElement = HTMLImageElement | HTMLVideoElement;
|
|||
export const BLURHASH_FIELD = "xyz.amorgan.blurhash"; // MSC2448
|
||||
|
||||
interface IThumbnail {
|
||||
info: {
|
||||
thumbnail_info?: {
|
||||
w: number;
|
||||
h: number;
|
||||
mimetype: string;
|
||||
size: number;
|
||||
};
|
||||
w: number;
|
||||
h: number;
|
||||
[BLURHASH_FIELD]?: string;
|
||||
thumbnail_url?: string;
|
||||
thumbnail_file?: EncryptedFile;
|
||||
};
|
||||
info: ImageInfo;
|
||||
thumbnail: Blob;
|
||||
}
|
||||
|
||||
|
|
|
@ -474,10 +474,8 @@ export default class ElectronPlatform extends BasePlatform {
|
|||
const url = super.getOidcCallbackUrl();
|
||||
url.protocol = "io.element.desktop";
|
||||
// Trim the double slash into a single slash to comply with https://datatracker.ietf.org/doc/html/rfc8252#section-7.1
|
||||
// Chrome seems to have a strange issue where non-standard protocols prevent URL object mutations on pathname
|
||||
// field, so we cannot mutate `pathname` reliably and instead have to rewrite the href manually.
|
||||
if (url.pathname.startsWith("//")) {
|
||||
url.href = url.href.replace(url.pathname, url.pathname.slice(1));
|
||||
if (url.href.startsWith(`${url.protocol}://`)) {
|
||||
url.href = url.href.replace("://", ":/");
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
|
|
@ -143,7 +143,6 @@ export const mockClientMethodsCrypto = (): Partial<
|
|||
> => ({
|
||||
isKeyBackupKeyStored: jest.fn(),
|
||||
getCrossSigningCacheCallbacks: jest.fn().mockReturnValue({ getCrossSigningKeyCache: jest.fn() }),
|
||||
getKeyBackupVersion: jest.fn().mockResolvedValue(null),
|
||||
secretStorage: { hasKey: jest.fn() },
|
||||
getCrypto: jest.fn().mockReturnValue({
|
||||
getUserDeviceInfo: jest.fn(),
|
||||
|
@ -163,6 +162,7 @@ export const mockClientMethodsCrypto = (): Partial<
|
|||
getOwnDeviceKeys: jest.fn().mockReturnValue(new Promise(() => {})),
|
||||
getCrossSigningKeyId: jest.fn(),
|
||||
isEncryptionEnabledInRoom: jest.fn().mockResolvedValue(false),
|
||||
getKeyBackupInfo: jest.fn().mockResolvedValue(null),
|
||||
}),
|
||||
});
|
||||
|
||||
|
|
|
@ -99,7 +99,6 @@ export function createTestClient(): MatrixClient {
|
|||
getDevices: jest.fn().mockResolvedValue({ devices: [{ device_id: "ABCDEFGHI" }] }),
|
||||
getSessionId: jest.fn().mockReturnValue("iaszphgvfku"),
|
||||
credentials: { userId: "@userId:matrix.org" },
|
||||
getKeyBackupVersion: jest.fn(),
|
||||
|
||||
secretStorage: {
|
||||
get: jest.fn(),
|
||||
|
@ -135,6 +134,7 @@ export function createTestClient(): MatrixClient {
|
|||
restoreKeyBackupWithPassphrase: jest.fn(),
|
||||
loadSessionBackupPrivateKeyFromSecretStorage: jest.fn(),
|
||||
storeSessionBackupPrivateKey: jest.fn(),
|
||||
getKeyBackupInfo: jest.fn().mockResolvedValue(null),
|
||||
}),
|
||||
|
||||
getPushActionsForEvent: jest.fn(),
|
||||
|
|
|
@ -96,12 +96,12 @@ describe("DeviceListener", () => {
|
|||
}),
|
||||
getSessionBackupPrivateKey: jest.fn(),
|
||||
isEncryptionEnabledInRoom: jest.fn(),
|
||||
getKeyBackupInfo: jest.fn().mockResolvedValue(null),
|
||||
} as unknown as Mocked<CryptoApi>;
|
||||
mockClient = getMockClientWithEventEmitter({
|
||||
isGuest: jest.fn(),
|
||||
getUserId: jest.fn().mockReturnValue(userId),
|
||||
getSafeUserId: jest.fn().mockReturnValue(userId),
|
||||
getKeyBackupVersion: jest.fn().mockResolvedValue(undefined),
|
||||
getRooms: jest.fn().mockReturnValue([]),
|
||||
isVersionSupported: jest.fn().mockResolvedValue(true),
|
||||
isInitialSyncComplete: jest.fn().mockReturnValue(true),
|
||||
|
@ -354,7 +354,7 @@ describe("DeviceListener", () => {
|
|||
|
||||
it("shows set up encryption toast when user has a key backup available", async () => {
|
||||
// non falsy response
|
||||
mockClient!.getKeyBackupVersion.mockResolvedValue({} as unknown as KeyBackupInfo);
|
||||
mockCrypto.getKeyBackupInfo.mockResolvedValue({} as unknown as KeyBackupInfo);
|
||||
await createAndStart();
|
||||
|
||||
expect(SetupEncryptionToast.showToast).toHaveBeenCalledWith(
|
||||
|
@ -673,7 +673,7 @@ describe("DeviceListener", () => {
|
|||
describe("When Room Key Backup is not enabled", () => {
|
||||
beforeEach(() => {
|
||||
// no backup
|
||||
mockClient.getKeyBackupVersion.mockResolvedValue(null);
|
||||
mockCrypto.getKeyBackupInfo.mockResolvedValue(null);
|
||||
});
|
||||
|
||||
it("Should report recovery state as Enabled", async () => {
|
||||
|
@ -722,7 +722,7 @@ describe("DeviceListener", () => {
|
|||
});
|
||||
|
||||
// no backup
|
||||
mockClient.getKeyBackupVersion.mockResolvedValue(null);
|
||||
mockCrypto.getKeyBackupInfo.mockResolvedValue(null);
|
||||
|
||||
await createAndStart();
|
||||
|
||||
|
@ -872,7 +872,7 @@ describe("DeviceListener", () => {
|
|||
describe("When Room Key Backup is enabled", () => {
|
||||
beforeEach(() => {
|
||||
// backup enabled - just need a mock object
|
||||
mockClient.getKeyBackupVersion.mockResolvedValue({} as KeyBackupInfo);
|
||||
mockCrypto.getKeyBackupInfo.mockResolvedValue({} as KeyBackupInfo);
|
||||
});
|
||||
|
||||
const testCases = [
|
||||
|
|
|
@ -139,6 +139,7 @@ describe("<MatrixChat />", () => {
|
|||
globalBlacklistUnverifiedDevices: false,
|
||||
// This needs to not finish immediately because we need to test the screen appears
|
||||
bootstrapCrossSigning: jest.fn().mockImplementation(() => bootstrapDeferred.promise),
|
||||
getKeyBackupInfo: jest.fn().mockResolvedValue(null),
|
||||
}),
|
||||
secretStorage: {
|
||||
isStored: jest.fn().mockReturnValue(null),
|
||||
|
@ -148,7 +149,6 @@ describe("<MatrixChat />", () => {
|
|||
whoami: jest.fn(),
|
||||
logout: jest.fn(),
|
||||
getDeviceId: jest.fn(),
|
||||
getKeyBackupVersion: jest.fn().mockResolvedValue(null),
|
||||
});
|
||||
let mockClient: Mocked<MatrixClient>;
|
||||
const serverConfig = {
|
||||
|
|
|
@ -22,7 +22,6 @@ describe("LogoutDialog", () => {
|
|||
beforeEach(() => {
|
||||
mockClient = getMockClientWithEventEmitter({
|
||||
...mockClientMethodsCrypto(),
|
||||
getKeyBackupVersion: jest.fn(),
|
||||
});
|
||||
|
||||
mockCrypto = mocked(mockClient.getCrypto()!);
|
||||
|
@ -50,14 +49,14 @@ describe("LogoutDialog", () => {
|
|||
});
|
||||
|
||||
it("Prompts user to connect backup if there is a backup on the server", async () => {
|
||||
mockClient.getKeyBackupVersion.mockResolvedValue({} as KeyBackupInfo);
|
||||
mockCrypto.getKeyBackupInfo.mockResolvedValue({} as KeyBackupInfo);
|
||||
const rendered = renderComponent();
|
||||
await rendered.findByText("Connect this session to Key Backup");
|
||||
expect(rendered.container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("Prompts user to set up backup if there is no backup on the server", async () => {
|
||||
mockClient.getKeyBackupVersion.mockResolvedValue(null);
|
||||
mockCrypto.getKeyBackupInfo.mockResolvedValue(null);
|
||||
const rendered = renderComponent();
|
||||
await rendered.findByText("Start using Key Backup");
|
||||
expect(rendered.container).toMatchSnapshot();
|
||||
|
@ -69,7 +68,7 @@ describe("LogoutDialog", () => {
|
|||
describe("when there is an error fetching backups", () => {
|
||||
filterConsole("Unable to fetch key backup status");
|
||||
it("prompts user to set up backup", async () => {
|
||||
mockClient.getKeyBackupVersion.mockImplementation(async () => {
|
||||
mockCrypto.getKeyBackupInfo.mockImplementation(async () => {
|
||||
throw new Error("beep");
|
||||
});
|
||||
const rendered = renderComponent();
|
||||
|
|
|
@ -77,7 +77,7 @@ describe("CreateSecretStorageDialog", () => {
|
|||
filterConsole("Error fetching backup data from server");
|
||||
|
||||
it("shows an error", async () => {
|
||||
mockClient.getKeyBackupVersion.mockImplementation(async () => {
|
||||
jest.spyOn(mockClient.getCrypto()!, "getKeyBackupInfo").mockImplementation(async () => {
|
||||
throw new Error("bleh bleh");
|
||||
});
|
||||
|
||||
|
@ -92,7 +92,7 @@ describe("CreateSecretStorageDialog", () => {
|
|||
expect(result.container).toMatchSnapshot();
|
||||
|
||||
// Now we can get the backup and we retry
|
||||
mockClient.getKeyBackupVersion.mockRestore();
|
||||
jest.spyOn(mockClient.getCrypto()!, "getKeyBackupInfo").mockRestore();
|
||||
await userEvent.click(screen.getByRole("button", { name: "Retry" }));
|
||||
await screen.findByText("Your keys are now being backed up from this device.");
|
||||
});
|
||||
|
|
|
@ -28,7 +28,7 @@ describe("<RestoreKeyBackupDialog />", () => {
|
|||
beforeEach(() => {
|
||||
matrixClient = stubClient();
|
||||
jest.spyOn(recoveryKeyModule, "decodeRecoveryKey").mockReturnValue(new Uint8Array(32));
|
||||
jest.spyOn(matrixClient, "getKeyBackupVersion").mockResolvedValue({ version: "1" } as KeyBackupInfo);
|
||||
jest.spyOn(matrixClient.getCrypto()!, "getKeyBackupInfo").mockResolvedValue({ version: "1" } as KeyBackupInfo);
|
||||
});
|
||||
|
||||
it("should render", async () => {
|
||||
|
@ -99,7 +99,7 @@ describe("<RestoreKeyBackupDialog />", () => {
|
|||
|
||||
test("should restore key backup when passphrase is filled", async () => {
|
||||
// Determine that the passphrase is required
|
||||
jest.spyOn(matrixClient, "getKeyBackupVersion").mockResolvedValue({
|
||||
jest.spyOn(matrixClient.getCrypto()!, "getKeyBackupInfo").mockResolvedValue({
|
||||
version: "1",
|
||||
auth_data: {
|
||||
private_key_salt: "salt",
|
||||
|
|
|
@ -28,14 +28,13 @@ describe("<SecureBackupPanel />", () => {
|
|||
const client = getMockClientWithEventEmitter({
|
||||
...mockClientMethodsUser(userId),
|
||||
...mockClientMethodsCrypto(),
|
||||
getKeyBackupVersion: jest.fn().mockReturnValue("1"),
|
||||
getClientWellKnown: jest.fn(),
|
||||
});
|
||||
|
||||
const getComponent = () => render(<SecureBackupPanel />);
|
||||
|
||||
beforeEach(() => {
|
||||
client.getKeyBackupVersion.mockResolvedValue({
|
||||
jest.spyOn(client.getCrypto()!, "getKeyBackupInfo").mockResolvedValue({
|
||||
version: "1",
|
||||
algorithm: "test",
|
||||
auth_data: {
|
||||
|
@ -52,7 +51,6 @@ describe("<SecureBackupPanel />", () => {
|
|||
});
|
||||
|
||||
mocked(client.secretStorage.hasKey).mockClear().mockResolvedValue(false);
|
||||
client.getKeyBackupVersion.mockClear();
|
||||
|
||||
mocked(accessSecretStorage).mockClear().mockResolvedValue();
|
||||
});
|
||||
|
@ -65,8 +63,8 @@ describe("<SecureBackupPanel />", () => {
|
|||
});
|
||||
|
||||
it("handles error fetching backup", async () => {
|
||||
// getKeyBackupVersion can fail for various reasons
|
||||
client.getKeyBackupVersion.mockImplementation(async () => {
|
||||
// getKeyBackupInfo can fail for various reasons
|
||||
jest.spyOn(client.getCrypto()!, "getKeyBackupInfo").mockImplementation(async () => {
|
||||
throw new Error("beep beep");
|
||||
});
|
||||
const renderResult = getComponent();
|
||||
|
@ -75,9 +73,9 @@ describe("<SecureBackupPanel />", () => {
|
|||
});
|
||||
|
||||
it("handles absence of backup", async () => {
|
||||
client.getKeyBackupVersion.mockResolvedValue(null);
|
||||
jest.spyOn(client.getCrypto()!, "getKeyBackupInfo").mockResolvedValue(null);
|
||||
getComponent();
|
||||
// flush getKeyBackupVersion promise
|
||||
// flush getKeyBackupInfo promise
|
||||
await flushPromises();
|
||||
expect(screen.getByText("Back up your keys before signing out to avoid losing them.")).toBeInTheDocument();
|
||||
});
|
||||
|
@ -120,7 +118,7 @@ describe("<SecureBackupPanel />", () => {
|
|||
});
|
||||
|
||||
it("deletes backup after confirmation", async () => {
|
||||
client.getKeyBackupVersion
|
||||
jest.spyOn(client.getCrypto()!, "getKeyBackupInfo")
|
||||
.mockResolvedValueOnce({
|
||||
version: "1",
|
||||
algorithm: "test",
|
||||
|
@ -157,7 +155,7 @@ describe("<SecureBackupPanel />", () => {
|
|||
// flush checkKeyBackup promise
|
||||
await flushPromises();
|
||||
|
||||
client.getKeyBackupVersion.mockClear();
|
||||
jest.spyOn(client.getCrypto()!, "getKeyBackupInfo").mockClear();
|
||||
mocked(client.getCrypto()!).isKeyBackupTrusted.mockClear();
|
||||
|
||||
fireEvent.click(screen.getByText("Reset"));
|
||||
|
@ -167,7 +165,7 @@ describe("<SecureBackupPanel />", () => {
|
|||
await flushPromises();
|
||||
|
||||
// backup status refreshed
|
||||
expect(client.getKeyBackupVersion).toHaveBeenCalled();
|
||||
expect(client.getCrypto()!.getKeyBackupInfo).toHaveBeenCalled();
|
||||
expect(client.getCrypto()!.isKeyBackupTrusted).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -34,7 +34,6 @@ describe("<SecurityUserSettingsTab />", () => {
|
|||
...mockClientMethodsCrypto(),
|
||||
getRooms: jest.fn().mockReturnValue([]),
|
||||
getIgnoredUsers: jest.fn(),
|
||||
getKeyBackupVersion: jest.fn(),
|
||||
});
|
||||
|
||||
const sdkContext = new SdkContextClass();
|
||||
|
|
|
@ -37,6 +37,7 @@ describe("SetupEncryptionStore", () => {
|
|||
getDeviceVerificationStatus: jest.fn(),
|
||||
isDehydrationSupported: jest.fn().mockResolvedValue(false),
|
||||
startDehydration: jest.fn(),
|
||||
getKeyBackupInfo: jest.fn().mockResolvedValue(null),
|
||||
} as unknown as Mocked<CryptoApi>;
|
||||
client.getCrypto.mockReturnValue(mockCrypto);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue