Merge branch 'develop' into t3chguy/wat/230.1

This commit is contained in:
Michael Telatynski 2024-09-24 10:34:36 +01:00 committed by GitHub
commit 3620c5ac62
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 898 additions and 244 deletions

View file

@ -122,8 +122,6 @@ module.exports = {
"!matrix-js-sdk/src/crypto/aes", "!matrix-js-sdk/src/crypto/aes",
"!matrix-js-sdk/src/crypto/keybackup", "!matrix-js-sdk/src/crypto/keybackup",
"!matrix-js-sdk/src/crypto/deviceinfo", "!matrix-js-sdk/src/crypto/deviceinfo",
"!matrix-js-sdk/src/crypto/key_passphrase",
"!matrix-js-sdk/src/crypto/recoverykey",
"!matrix-js-sdk/src/crypto/dehydration", "!matrix-js-sdk/src/crypto/dehydration",
"!matrix-js-sdk/src/oidc", "!matrix-js-sdk/src/oidc",
"!matrix-js-sdk/src/oidc/discovery", "!matrix-js-sdk/src/oidc/discovery",

View file

@ -9,7 +9,7 @@ jobs:
name: "Notify Element Web" name: "Notify Element Web"
runs-on: ubuntu-latest runs-on: ubuntu-latest
# Only respect triggers from our develop branch, ignore that of forks # Only respect triggers from our develop branch, ignore that of forks
if: github.repository == 'matrix-org/matrix-react-sdk' if: github.repository == 'element-hq/matrix-react-sdk'
steps: steps:
- name: Notify element-web repo that a new SDK build is on develop - name: Notify element-web repo that a new SDK build is on develop
uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # v3 uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # v3

View file

@ -20,7 +20,7 @@ jobs:
- name: Create Pull Request - name: Create Pull Request
id: cpr id: cpr
uses: peter-evans/create-pull-request@4320041ed380b20e97d388d56a7fb4f9b8c20e79 # v7 uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7
with: with:
token: ${{ secrets.ELEMENT_BOT_TOKEN }} token: ${{ secrets.ELEMENT_BOT_TOKEN }}
branch: actions/playwright-image-updates branch: actions/playwright-image-updates

View file

@ -19,7 +19,9 @@ concurrency: ${{ github.workflow }}
jobs: jobs:
release: release:
uses: matrix-org/matrix-js-sdk/.github/workflows/release-make.yml@develop uses: matrix-org/matrix-js-sdk/.github/workflows/release-make.yml@develop
secrets: inherit secrets:
ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
with: with:
final: ${{ inputs.mode == 'final' }} final: ${{ inputs.mode == 'final' }}
npm: ${{ inputs.npm }} npm: ${{ inputs.npm }}

View file

@ -39,7 +39,7 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
repository: ${{ inputs.matrix-js-sdk-sha && 'matrix-org/matrix-react-sdk' || github.repository }} repository: ${{ inputs.matrix-js-sdk-sha && 'element-hq/matrix-react-sdk' || github.repository }}
- name: Yarn cache - name: Yarn cache
uses: actions/setup-node@v4 uses: actions/setup-node@v4
@ -96,7 +96,7 @@ jobs:
needs: jest needs: jest
steps: steps:
- name: Skip SonarCloud - name: Skip SonarCloud
uses: Sibz/github-status-action@071b5370da85afbb16637d6eed8524a06bc2053e # v1 uses: Sibz/github-status-action@faaa4d96fecf273bd762985e0e7f9f933c774918 # v1
with: with:
authToken: ${{ secrets.GITHUB_TOKEN }} authToken: ${{ secrets.GITHUB_TOKEN }}
state: success state: success
@ -111,7 +111,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
repository: ${{ inputs.matrix-js-sdk-sha && 'matrix-org/matrix-react-sdk' || github.repository }} repository: ${{ inputs.matrix-js-sdk-sha && 'element-hq/matrix-react-sdk' || github.repository }}
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:

View file

@ -62,7 +62,7 @@
}, },
"resolutions": { "resolutions": {
"@types/react-dom": "17.0.25", "@types/react-dom": "17.0.25",
"@types/react": "17.0.80", "@types/react": "17.0.82",
"@types/seedrandom": "3.0.8", "@types/seedrandom": "3.0.8",
"oidc-client-ts": "3.0.1", "oidc-client-ts": "3.0.1",
"jwt-decode": "4.0.0", "jwt-decode": "4.0.0",
@ -91,7 +91,7 @@
"classnames": "^2.2.6", "classnames": "^2.2.6",
"commonmark": "^0.31.0", "commonmark": "^0.31.0",
"counterpart": "^0.18.6", "counterpart": "^0.18.6",
"css-tree": "^2.3.1", "css-tree": "^3.0.0",
"diff-dom": "^5.0.0", "diff-dom": "^5.0.0",
"diff-match-patch": "^1.0.5", "diff-match-patch": "^1.0.5",
"emojibase-regex": "15.3.2", "emojibase-regex": "15.3.2",
@ -183,7 +183,7 @@
"@types/node-fetch": "^2.6.2", "@types/node-fetch": "^2.6.2",
"@types/pako": "^2.0.0", "@types/pako": "^2.0.0",
"@types/qrcode": "^1.3.5", "@types/qrcode": "^1.3.5",
"@types/react": "17.0.80", "@types/react": "17.0.82",
"@types/react-beautiful-dnd": "^13.0.0", "@types/react-beautiful-dnd": "^13.0.0",
"@types/react-dom": "17.0.25", "@types/react-dom": "17.0.25",
"@types/react-transition-group": "^4.4.0", "@types/react-transition-group": "^4.4.0",
@ -198,7 +198,7 @@
"axe-core": "4.10.0", "axe-core": "4.10.0",
"babel-jest": "^29.0.0", "babel-jest": "^29.0.0",
"blob-polyfill": "^9.0.0", "blob-polyfill": "^9.0.0",
"eslint": "8.57.0", "eslint": "8.57.1",
"eslint-config-google": "^0.14.0", "eslint-config-google": "^0.14.0",
"eslint-config-prettier": "^9.0.0", "eslint-config-prettier": "^9.0.0",
"eslint-plugin-deprecate": "0.8.5", "eslint-plugin-deprecate": "0.8.5",

View file

@ -14,7 +14,7 @@ import type { Logger } from "matrix-js-sdk/src/logger";
import type { SecretStorageKeyDescription } from "matrix-js-sdk/src/secret-storage"; import type { SecretStorageKeyDescription } from "matrix-js-sdk/src/secret-storage";
import type { Credentials, HomeserverInstance } from "../plugins/homeserver"; import type { Credentials, HomeserverInstance } from "../plugins/homeserver";
import type { GeneratedSecretStorageKey } from "matrix-js-sdk/src/crypto-api"; import type { GeneratedSecretStorageKey } from "matrix-js-sdk/src/crypto-api";
import { Client } from "./client"; import { bootstrapCrossSigningForClient, Client } from "./client";
export interface CreateBotOpts { export interface CreateBotOpts {
/** /**
@ -90,9 +90,13 @@ export class Bot extends Client {
} }
protected async getClientHandle(): Promise<JSHandle<ExtendedMatrixClient>> { protected async getClientHandle(): Promise<JSHandle<ExtendedMatrixClient>> {
if (this.handlePromise) return this.handlePromise; if (!this.handlePromise) this.handlePromise = this.buildClient();
return this.handlePromise;
}
this.handlePromise = this.page.evaluateHandle( private async buildClient(): Promise<JSHandle<ExtendedMatrixClient>> {
const credentials = await this.getCredentials();
const clientHandle = await this.page.evaluateHandle(
async ({ homeserver, credentials, opts }) => { async ({ homeserver, credentials, opts }) => {
function getLogger(loggerName: string): Logger { function getLogger(loggerName: string): Logger {
const logger = { const logger = {
@ -172,34 +176,38 @@ export class Bot extends Client {
}); });
} }
if (!opts.startClient) {
return cli; return cli;
},
{
homeserver: this.homeserver.config,
credentials,
opts: this.opts,
},
);
// If we weren't configured to start the client, bail out now.
if (!this.opts.startClient) {
return clientHandle;
} }
await clientHandle.evaluate(async (cli) => {
await cli.initRustCrypto({ useIndexedDB: false }); await cli.initRustCrypto({ useIndexedDB: false });
cli.setGlobalErrorOnUnknownDevices(false); cli.setGlobalErrorOnUnknownDevices(false);
await cli.startClient(); await cli.startClient();
});
if (opts.bootstrapCrossSigning) { if (this.opts.bootstrapCrossSigning) {
// XXX: workaround https://github.com/element-hq/element-web/issues/26755 // XXX: workaround https://github.com/element-hq/element-web/issues/26755
// wait for out device list to be available, as a proxy for the device keys having been uploaded. // wait for out device list to be available, as a proxy for the device keys having been uploaded.
await clientHandle.evaluate(async (cli, credentials) => {
await cli.getCrypto()!.getUserDeviceInfo([credentials.userId]); await cli.getCrypto()!.getUserDeviceInfo([credentials.userId]);
}, credentials);
await cli.getCrypto()!.bootstrapCrossSigning({ await bootstrapCrossSigningForClient(clientHandle, credentials);
authUploadDeviceSigningKeys: async (func) => {
await func({
type: "m.login.password",
identifier: {
type: "m.id.user",
user: credentials.userId,
},
password: credentials.password,
});
},
});
} }
if (opts.bootstrapSecretStorage) { if (this.opts.bootstrapSecretStorage) {
await clientHandle.evaluate(async (cli) => {
const passphrase = "new passphrase"; const passphrase = "new passphrase";
const recoveryKey = await cli.getCrypto().createRecoveryKeyFromPassphrase(passphrase); const recoveryKey = await cli.getCrypto().createRecoveryKeyFromPassphrase(passphrase);
Object.assign(cli, { __playwright_recovery_key: recoveryKey }); Object.assign(cli, { __playwright_recovery_key: recoveryKey });
@ -209,16 +217,9 @@ export class Bot extends Client {
setupNewKeyBackup: true, setupNewKeyBackup: true,
createSecretStorageKey: () => Promise.resolve(recoveryKey), createSecretStorageKey: () => Promise.resolve(recoveryKey),
}); });
});
} }
return cli; return clientHandle;
},
{
homeserver: this.homeserver.config,
credentials: await this.getCredentials(),
opts: this.opts,
},
);
return this.handlePromise;
} }
} }

View file

@ -356,24 +356,11 @@ export class Client {
} }
/** /**
* Boostraps cross-signing. * Bootstraps cross-signing.
*/ */
public async bootstrapCrossSigning(credentials: Credentials): Promise<void> { public async bootstrapCrossSigning(credentials: Credentials): Promise<void> {
const client = await this.prepareClient(); const client = await this.prepareClient();
return client.evaluate(async (client, credentials) => { return bootstrapCrossSigningForClient(client, credentials);
await client.getCrypto().bootstrapCrossSigning({
authUploadDeviceSigningKeys: async (func) => {
await func({
type: "m.login.password",
identifier: {
type: "m.id.user",
user: credentials.userId,
},
password: credentials.password,
});
},
});
}, credentials);
} }
/** /**
@ -439,3 +426,31 @@ export class Client {
); );
} }
} }
/** Call `CryptoApi.bootstrapCrossSigning` on the given Matrix client, using the given credentials to authenticate
* the UIA request.
*/
export function bootstrapCrossSigningForClient(
client: JSHandle<MatrixClient>,
credentials: Credentials,
resetKeys: boolean = false,
) {
return client.evaluate(
async (client, { credentials, resetKeys }) => {
await client.getCrypto().bootstrapCrossSigning({
authUploadDeviceSigningKeys: async (func) => {
await func({
type: "m.login.password",
identifier: {
type: "m.id.user",
user: credentials.userId,
},
password: credentials.password,
});
},
setupNewCrossSigning: resetKeys,
});
},
{ credentials, resetKeys },
);
}

View file

@ -7,8 +7,7 @@ Please see LICENSE files in the repository root for full details.
*/ */
import { ICryptoCallbacks, SecretStorage } from "matrix-js-sdk/src/matrix"; import { ICryptoCallbacks, SecretStorage } from "matrix-js-sdk/src/matrix";
import { deriveKey } from "matrix-js-sdk/src/crypto/key_passphrase"; import { deriveRecoveryKeyFromPassphrase, decodeRecoveryKey } from "matrix-js-sdk/src/crypto-api";
import { decodeRecoveryKey } from "matrix-js-sdk/src/crypto/recoverykey";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import type CreateSecretStorageDialog from "./async-components/views/dialogs/security/CreateSecretStorageDialog"; import type CreateSecretStorageDialog from "./async-components/views/dialogs/security/CreateSecretStorageDialog";
@ -64,7 +63,7 @@ function makeInputToKey(
): (keyParams: KeyParams) => Promise<Uint8Array> { ): (keyParams: KeyParams) => Promise<Uint8Array> {
return async ({ passphrase, recoveryKey }): Promise<Uint8Array> => { return async ({ passphrase, recoveryKey }): Promise<Uint8Array> => {
if (passphrase) { if (passphrase) {
return deriveKey(passphrase, keyInfo.passphrase.salt, keyInfo.passphrase.iterations); return deriveRecoveryKeyFromPassphrase(passphrase, keyInfo.passphrase.salt, keyInfo.passphrase.iterations);
} else if (recoveryKey) { } else if (recoveryKey) {
return decodeRecoveryKey(recoveryKey); return decodeRecoveryKey(recoveryKey);
} }

View file

@ -952,18 +952,20 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
} }
private async startRegistration(params: { [key: string]: string }, isMobileRegistration?: boolean): Promise<void> { private async startRegistration(params: { [key: string]: string }, isMobileRegistration?: boolean): Promise<void> {
if (!SettingsStore.getValue(UIFeature.Registration)) { // If registration is disabled or mobile registration is requested but not enabled in settings redirect to the welcome screen
if (
!SettingsStore.getValue(UIFeature.Registration) ||
(isMobileRegistration && !SettingsStore.getValue("Registration.mobileRegistrationHelper"))
) {
this.showScreen("welcome"); this.showScreen("welcome");
return; return;
} }
const isMobileRegistrationAllowed =
isMobileRegistration && SettingsStore.getValue("Registration.mobileRegistrationHelper");
const newState: Partial<IState> = { const newState: Partial<IState> = {
view: Views.REGISTER, view: Views.REGISTER,
}; };
if (isMobileRegistrationAllowed && params.hs_url) { if (isMobileRegistration && params.hs_url) {
try { try {
const config = await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(params.hs_url); const config = await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(params.hs_url);
newState.serverConfig = config; newState.serverConfig = config;
@ -992,12 +994,12 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
newState.register_id_sid = params.sid; newState.register_id_sid = params.sid;
} }
newState.isMobileRegistration = isMobileRegistrationAllowed; newState.isMobileRegistration = isMobileRegistration;
this.setStateForNewView(newState); this.setStateForNewView(newState);
ThemeController.isLogin = true; ThemeController.isLogin = true;
this.themeWatcher.recheck(); this.themeWatcher.recheck();
this.notifyNewScreen(isMobileRegistrationAllowed ? "mobile_register" : "register"); this.notifyNewScreen(isMobileRegistration ? "mobile_register" : "register");
} }
// switch view to the given room // switch view to the given room

View file

@ -1364,7 +1364,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
if (containsEmoji(ev.getContent(), effect.emojis) || ev.getContent().msgtype === effect.msgType) { if (containsEmoji(ev.getContent(), effect.emojis) || ev.getContent().msgtype === effect.msgType) {
// For initial threads launch, chat effects are disabled see #19731 // For initial threads launch, chat effects are disabled see #19731
if (!ev.isRelation(THREAD_RELATION_TYPE.name)) { if (!ev.isRelation(THREAD_RELATION_TYPE.name)) {
dis.dispatch({ action: `effects.${effect.command}` }); dis.dispatch({ action: `effects.${effect.command}`, event: ev });
} }
} }
}); });

View file

@ -627,6 +627,7 @@ export default class Registration extends React.Component<IProps, IState> {
serverConfig={this.props.serverConfig} serverConfig={this.props.serverConfig}
canSubmit={!this.state.serverErrorIsFatal} canSubmit={!this.state.serverErrorIsFatal}
matrixClient={this.state.matrixClient} matrixClient={this.state.matrixClient}
mobileRegister={this.props.mobileRegister}
/> />
</React.Fragment> </React.Fragment>
); );
@ -779,7 +780,11 @@ export default class Registration extends React.Component<IProps, IState> {
); );
} }
if (this.props.mobileRegister) { if (this.props.mobileRegister) {
return <div className="mx_MobileRegister_body">{body}</div>; return (
<div className="mx_MobileRegister_body" data-testid="mobile-register">
{body}
</div>
);
} }
return ( return (
<AuthPage> <AuthPage>

View file

@ -63,6 +63,19 @@ export default class CaptchaForm extends React.Component<ICaptchaFormProps, ICap
public componentWillUnmount(): void { public componentWillUnmount(): void {
this.resetRecaptcha(); this.resetRecaptcha();
// Resettting the captcha does not clear the challenge overlay from the body in android webviews.
// Search for an iframe with the challenge src and remove it's topmost ancestor from the body.
// TODO: Remove this when the "mobile_register" page is retired.
const iframes = document.querySelectorAll("iframe");
for (const iframe of iframes) {
if (iframe.src.includes("https://www.recaptcha.net/recaptcha/api2/bframe")) {
let parentBeforeBody: HTMLElement | null = iframe;
do {
parentBeforeBody = parentBeforeBody.parentElement;
} while (parentBeforeBody?.parentElement && parentBeforeBody.parentElement != document.body);
parentBeforeBody?.remove();
}
}
} }
// Borrowed directly from: https://github.com/codeep/react-recaptcha-google/commit/e118fa5670fa268426969323b2e7fe77698376ba // Borrowed directly from: https://github.com/codeep/react-recaptcha-google/commit/e118fa5670fa268426969323b2e7fe77698376ba

View file

@ -12,6 +12,7 @@ import Field, { IInputProps } from "../elements/Field";
import { _t, _td, TranslationKey } from "../../../languageHandler"; import { _t, _td, TranslationKey } from "../../../languageHandler";
import withValidation, { IFieldState, IValidationResult } from "../elements/Validation"; import withValidation, { IFieldState, IValidationResult } from "../elements/Validation";
import * as Email from "../../../email"; import * as Email from "../../../email";
import { Alignment } from "../elements/Tooltip";
interface IProps extends Omit<IInputProps, "onValidate" | "element"> { interface IProps extends Omit<IInputProps, "onValidate" | "element"> {
id?: string; id?: string;
@ -22,6 +23,7 @@ interface IProps extends Omit<IInputProps, "onValidate" | "element"> {
label: TranslationKey; label: TranslationKey;
labelRequired: TranslationKey; labelRequired: TranslationKey;
labelInvalid: TranslationKey; labelInvalid: TranslationKey;
tooltipAlignment?: Alignment;
// When present, completely overrides the default validation rules. // When present, completely overrides the default validation rules.
validationRules?: (fieldState: IFieldState) => Promise<IValidationResult>; validationRules?: (fieldState: IFieldState) => Promise<IValidationResult>;
@ -77,6 +79,7 @@ class EmailField extends PureComponent<IProps> {
autoFocus={this.props.autoFocus} autoFocus={this.props.autoFocus}
onChange={this.props.onChange} onChange={this.props.onChange}
onValidate={this.onValidate} onValidate={this.onValidate}
tooltipAlignment={this.props.tooltipAlignment}
/> />
); );
} }

View file

@ -11,6 +11,7 @@ import React, { PureComponent, RefCallback, RefObject } from "react";
import Field, { IInputProps } from "../elements/Field"; import Field, { IInputProps } from "../elements/Field";
import withValidation, { IFieldState, IValidationResult } from "../elements/Validation"; import withValidation, { IFieldState, IValidationResult } from "../elements/Validation";
import { _t, _td, TranslationKey } from "../../../languageHandler"; import { _t, _td, TranslationKey } from "../../../languageHandler";
import { Alignment } from "../elements/Tooltip";
interface IProps extends Omit<IInputProps, "onValidate" | "label" | "element"> { interface IProps extends Omit<IInputProps, "onValidate" | "label" | "element"> {
id?: string; id?: string;
@ -22,7 +23,7 @@ interface IProps extends Omit<IInputProps, "onValidate" | "label" | "element"> {
label: TranslationKey; label: TranslationKey;
labelRequired: TranslationKey; labelRequired: TranslationKey;
labelInvalid: TranslationKey; labelInvalid: TranslationKey;
tooltipAlignment?: Alignment;
onChange(ev: React.FormEvent<HTMLElement>): void; onChange(ev: React.FormEvent<HTMLElement>): void;
onValidate?(result: IValidationResult): void; onValidate?(result: IValidationResult): void;
} }
@ -70,6 +71,7 @@ class PassphraseConfirmField extends PureComponent<IProps> {
onChange={this.props.onChange} onChange={this.props.onChange}
onValidate={this.onValidate} onValidate={this.onValidate}
autoFocus={this.props.autoFocus} autoFocus={this.props.autoFocus}
tooltipAlignment={this.props.tooltipAlignment}
/> />
); );
} }

View file

@ -15,6 +15,7 @@ import withValidation, { IFieldState, IValidationResult } from "../elements/Vali
import { _t, _td, TranslationKey } from "../../../languageHandler"; import { _t, _td, TranslationKey } from "../../../languageHandler";
import Field, { IInputProps } from "../elements/Field"; import Field, { IInputProps } from "../elements/Field";
import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { Alignment } from "../elements/Tooltip";
interface IProps extends Omit<IInputProps, "onValidate" | "element"> { interface IProps extends Omit<IInputProps, "onValidate" | "element"> {
autoFocus?: boolean; autoFocus?: boolean;
@ -30,6 +31,7 @@ interface IProps extends Omit<IInputProps, "onValidate" | "element"> {
labelEnterPassword: TranslationKey; labelEnterPassword: TranslationKey;
labelStrongPassword: TranslationKey; labelStrongPassword: TranslationKey;
labelAllowedButUnsafe: TranslationKey; labelAllowedButUnsafe: TranslationKey;
tooltipAlignment?: Alignment;
onChange(ev: React.FormEvent<HTMLElement>): void; onChange(ev: React.FormEvent<HTMLElement>): void;
onValidate?(result: IValidationResult): void; onValidate?(result: IValidationResult): void;
@ -111,6 +113,7 @@ class PassphraseField extends PureComponent<IProps> {
value={this.props.value} value={this.props.value}
onChange={this.props.onChange} onChange={this.props.onChange}
onValidate={this.onValidate} onValidate={this.onValidate}
tooltipAlignment={this.props.tooltipAlignment}
/> />
); );
} }

View file

@ -26,6 +26,7 @@ import RegistrationEmailPromptDialog from "../dialogs/RegistrationEmailPromptDia
import CountryDropdown from "./CountryDropdown"; import CountryDropdown from "./CountryDropdown";
import PassphraseConfirmField from "./PassphraseConfirmField"; import PassphraseConfirmField from "./PassphraseConfirmField";
import { PosthogAnalytics } from "../../../PosthogAnalytics"; import { PosthogAnalytics } from "../../../PosthogAnalytics";
import { Alignment } from "../elements/Tooltip";
enum RegistrationField { enum RegistrationField {
Email = "field_email", Email = "field_email",
@ -58,6 +59,7 @@ interface IProps {
serverConfig: ValidatedServerConfig; serverConfig: ValidatedServerConfig;
canSubmit?: boolean; canSubmit?: boolean;
matrixClient: MatrixClient; matrixClient: MatrixClient;
mobileRegister?: boolean;
onRegisterClick(params: { onRegisterClick(params: {
username: string; username: string;
@ -439,6 +441,13 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
return true; return true;
} }
private tooltipAlignment(): Alignment | undefined {
if (this.props.mobileRegister) {
return Alignment.Bottom;
}
return undefined;
}
private renderEmail(): ReactNode { private renderEmail(): ReactNode {
if (!this.showEmail()) { if (!this.showEmail()) {
return null; return null;
@ -454,6 +463,7 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
validationRules={this.validateEmailRules.bind(this)} validationRules={this.validateEmailRules.bind(this)}
onChange={this.onEmailChange} onChange={this.onEmailChange}
onValidate={this.onEmailValidate} onValidate={this.onEmailValidate}
tooltipAlignment={this.tooltipAlignment()}
/> />
); );
} }
@ -468,6 +478,7 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
onChange={this.onPasswordChange} onChange={this.onPasswordChange}
onValidate={this.onPasswordValidate} onValidate={this.onPasswordValidate}
userInputs={[this.state.username]} userInputs={[this.state.username]}
tooltipAlignment={this.tooltipAlignment()}
/> />
); );
} }
@ -482,6 +493,7 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
password={this.state.password} password={this.state.password}
onChange={this.onPasswordConfirmChange} onChange={this.onPasswordConfirmChange}
onValidate={this.onPasswordConfirmValidate} onValidate={this.onPasswordConfirmValidate}
tooltipAlignment={this.tooltipAlignment()}
/> />
); );
} }
@ -526,6 +538,9 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
value={this.state.username} value={this.state.username}
onChange={this.onUsernameChange} onChange={this.onUsernameChange}
onValidate={this.onUsernameValidate} onValidate={this.onUsernameValidate}
tooltipAlignment={this.tooltipAlignment()}
autoCorrect="off"
autoCapitalize="none"
/> />
); );
} }
@ -557,14 +572,28 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
} }
} }
return ( let passwordFields: JSX.Element | undefined;
<div> if (this.props.mobileRegister) {
<form onSubmit={this.onSubmit}> passwordFields = (
<div className="mx_AuthBody_fieldRow">{this.renderUsername()}</div> <>
<div className="mx_AuthBody_fieldRow">{this.renderPassword()}</div>
<div className="mx_AuthBody_fieldRow">{this.renderPasswordConfirm()}</div>
</>
);
} else {
passwordFields = (
<div className="mx_AuthBody_fieldRow"> <div className="mx_AuthBody_fieldRow">
{this.renderPassword()} {this.renderPassword()}
{this.renderPasswordConfirm()} {this.renderPasswordConfirm()}
</div> </div>
);
}
return (
<div>
<form onSubmit={this.onSubmit}>
<div className="mx_AuthBody_fieldRow">{this.renderUsername()}</div>
{passwordFields}
<div className="mx_AuthBody_fieldRow"> <div className="mx_AuthBody_fieldRow">
{this.renderEmail()} {this.renderEmail()}
{this.renderPhoneNumber()} {this.renderPhoneNumber()}

View file

@ -10,6 +10,7 @@ import { debounce } from "lodash";
import classNames from "classnames"; import classNames from "classnames";
import React, { ChangeEvent, FormEvent } from "react"; import React, { ChangeEvent, FormEvent } from "react";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { decodeRecoveryKey } from "matrix-js-sdk/src/crypto-api";
import { SecretStorage } from "matrix-js-sdk/src/matrix"; import { SecretStorage } from "matrix-js-sdk/src/matrix";
import { MatrixClientPeg } from "../../../../MatrixClientPeg"; import { MatrixClientPeg } from "../../../../MatrixClientPeg";
@ -100,7 +101,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
try { try {
const cli = MatrixClientPeg.safeGet(); const cli = MatrixClientPeg.safeGet();
const decodedKey = cli.keyBackupKeyFromRecoveryKey(this.state.recoveryKey); const decodedKey = decodeRecoveryKey(this.state.recoveryKey);
const correct = await cli.checkSecretStorageKey(decodedKey, this.props.keyInfo); const correct = await cli.checkSecretStorageKey(decodedKey, this.props.keyInfo);
this.setState({ this.setState({
recoveryKeyValid: true, recoveryKeyValid: true,

View file

@ -9,9 +9,9 @@ Please see LICENSE files in the repository root for full details.
import React, { ChangeEvent } from "react"; import React, { ChangeEvent } from "react";
import { MatrixClient, MatrixError, SecretStorage } from "matrix-js-sdk/src/matrix"; import { MatrixClient, MatrixError, SecretStorage } from "matrix-js-sdk/src/matrix";
import { decodeRecoveryKey, KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
import { IKeyBackupRestoreResult } from "matrix-js-sdk/src/crypto/keybackup"; import { IKeyBackupRestoreResult } from "matrix-js-sdk/src/crypto/keybackup";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
import { MatrixClientPeg } from "../../../../MatrixClientPeg"; import { MatrixClientPeg } from "../../../../MatrixClientPeg";
import { _t } from "../../../../languageHandler"; import { _t } from "../../../../languageHandler";
@ -118,10 +118,24 @@ export default class RestoreKeyBackupDialog extends React.PureComponent<IProps,
accessSecretStorage(async (): Promise<void> => {}, /* forceReset = */ true); accessSecretStorage(async (): Promise<void> => {}, /* forceReset = */ true);
}; };
/**
* Check if the recovery key is valid
* @param recoveryKey
* @private
*/
private isValidRecoveryKey(recoveryKey: string): boolean {
try {
decodeRecoveryKey(recoveryKey);
return true;
} catch (e) {
return false;
}
}
private onRecoveryKeyChange = (e: ChangeEvent<HTMLInputElement>): void => { private onRecoveryKeyChange = (e: ChangeEvent<HTMLInputElement>): void => {
this.setState({ this.setState({
recoveryKey: e.target.value, recoveryKey: e.target.value,
recoveryKeyValid: MatrixClientPeg.safeGet().isValidRecoveryKey(e.target.value), recoveryKeyValid: this.isValidRecoveryKey(e.target.value),
}); });
}; };
@ -184,7 +198,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent<IProps,
{ progressCallback: this.progressCallback }, { progressCallback: this.progressCallback },
); );
if (this.props.keyCallback) { if (this.props.keyCallback) {
const key = MatrixClientPeg.safeGet().keyBackupKeyFromRecoveryKey(this.state.recoveryKey); const key = decodeRecoveryKey(this.state.recoveryKey);
this.props.keyCallback(key); this.props.keyCallback(key);
} }
if (!this.props.showSummary) { if (!this.props.showSummary) {

View file

@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details.
*/ */
import React, { FunctionComponent, useEffect, useRef } from "react"; import React, { FunctionComponent, useEffect, useRef } from "react";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
import dis from "../../../dispatcher/dispatcher"; import dis from "../../../dispatcher/dispatcher";
import ICanvasEffect from "../../../effects/ICanvasEffect"; import ICanvasEffect from "../../../effects/ICanvasEffect";
@ -44,9 +45,10 @@ const EffectsOverlay: FunctionComponent<IProps> = ({ roomWidth }) => {
canvasRef.current.height = UIStore.instance.windowHeight; canvasRef.current.height = UIStore.instance.windowHeight;
} }
}; };
const onAction = (payload: { action: string }): void => { const onAction = (payload: { action: string; event?: MatrixEvent }): void => {
const actionPrefix = "effects."; const actionPrefix = "effects.";
if (canvasRef.current && payload.action.startsWith(actionPrefix)) { const isOutdated = isEventOutdated(payload.event);
if (canvasRef.current && payload.action.startsWith(actionPrefix) && !isOutdated) {
const effect = payload.action.slice(actionPrefix.length); const effect = payload.action.slice(actionPrefix.length);
lazyLoadEffectModule(effect).then((module) => module?.start(canvasRef.current!)); lazyLoadEffectModule(effect).then((module) => module?.start(canvasRef.current!));
} }
@ -88,3 +90,19 @@ const EffectsOverlay: FunctionComponent<IProps> = ({ roomWidth }) => {
}; };
export default EffectsOverlay; export default EffectsOverlay;
// 48 hours
// 48h * 60m * 60s * 1000ms
const OUTDATED_EVENT_THRESHOLD = 48 * 60 * 60 * 1000;
/**
* Return true if the event is older than 48h.
* @param event
*/
function isEventOutdated(event?: MatrixEvent): boolean {
if (!event) return false;
const nowTs = Date.now();
const eventTs = event.getTs();
return nowTs - eventTs > OUTDATED_EVENT_THRESHOLD;
}

View file

@ -17,7 +17,7 @@ import classNames from "classnames";
import { debounce } from "lodash"; import { debounce } from "lodash";
import { IFieldState, IValidationResult } from "./Validation"; import { IFieldState, IValidationResult } from "./Validation";
import Tooltip from "./Tooltip"; import Tooltip, { Alignment } from "./Tooltip";
import { Key } from "../../../Keyboard"; import { Key } from "../../../Keyboard";
// Invoke validation from user input (when typing, etc.) at most once every N ms. // Invoke validation from user input (when typing, etc.) at most once every N ms.
@ -60,6 +60,8 @@ interface IProps {
tooltipContent?: React.ReactNode; tooltipContent?: React.ReactNode;
// If specified the tooltip will be shown regardless of feedback // If specified the tooltip will be shown regardless of feedback
forceTooltipVisible?: boolean; forceTooltipVisible?: boolean;
// If specified, the tooltip with be aligned accorindly with the field, defaults to Right.
tooltipAlignment?: Alignment;
// If specified alongside tooltipContent, the class name to apply to the // If specified alongside tooltipContent, the class name to apply to the
// tooltip itself. // tooltip itself.
tooltipClassName?: string; tooltipClassName?: string;
@ -261,6 +263,7 @@ export default class Field extends React.PureComponent<PropShapes, IState> {
validateOnFocus, validateOnFocus,
usePlaceholderAsHint, usePlaceholderAsHint,
forceTooltipVisible, forceTooltipVisible,
tooltipAlignment,
...inputProps ...inputProps
} = this.props; } = this.props;
@ -286,7 +289,7 @@ export default class Field extends React.PureComponent<PropShapes, IState> {
tooltipClassName={classNames("mx_Field_tooltip", "mx_Tooltip_noMargin", tooltipClassName)} tooltipClassName={classNames("mx_Field_tooltip", "mx_Tooltip_noMargin", tooltipClassName)}
visible={visible} visible={visible}
label={tooltipContent || this.state.feedback} label={tooltipContent || this.state.feedback}
alignment={Tooltip.Alignment.Right} alignment={tooltipAlignment || Alignment.Right}
role={role} role={role}
/> />
); );

View file

@ -175,7 +175,7 @@ export class StopGapWidgetDriver extends WidgetDriver {
WidgetEventCapability.forStateEvent(EventDirection.Receive, EventType.RoomCreate).raw, WidgetEventCapability.forStateEvent(EventDirection.Receive, EventType.RoomCreate).raw,
); );
const sendRecvRoomEvents = ["io.element.call.encryption_keys"]; const sendRecvRoomEvents = ["io.element.call.encryption_keys", EventType.Reaction, EventType.RoomRedaction];
for (const eventType of sendRecvRoomEvents) { for (const eventType of sendRecvRoomEvents) {
this.allowedCapabilities.add(WidgetEventCapability.forRoomEvent(EventDirection.Send, eventType).raw); this.allowedCapabilities.add(WidgetEventCapability.forRoomEvent(EventDirection.Send, eventType).raw);
this.allowedCapabilities.add(WidgetEventCapability.forRoomEvent(EventDirection.Receive, eventType).raw); this.allowedCapabilities.add(WidgetEventCapability.forRoomEvent(EventDirection.Receive, eventType).raw);

View file

@ -55,6 +55,7 @@ import { MatrixClientPeg as peg } from "../../../src/MatrixClientPeg";
import DMRoomMap from "../../../src/utils/DMRoomMap"; import DMRoomMap from "../../../src/utils/DMRoomMap";
import { ReleaseAnnouncementStore } from "../../../src/stores/ReleaseAnnouncementStore"; import { ReleaseAnnouncementStore } from "../../../src/stores/ReleaseAnnouncementStore";
import { DRAFT_LAST_CLEANUP_KEY } from "../../../src/DraftCleaner"; import { DRAFT_LAST_CLEANUP_KEY } from "../../../src/DraftCleaner";
import { UIFeature } from "../../../src/settings/UIFeature";
jest.mock("matrix-js-sdk/src/oidc/authorize", () => ({ jest.mock("matrix-js-sdk/src/oidc/authorize", () => ({
completeAuthorizationCodeGrant: jest.fn(), completeAuthorizationCodeGrant: jest.fn(),
@ -1462,4 +1463,42 @@ describe("<MatrixChat />", () => {
}); });
}); });
}); });
describe("mobile registration", () => {
const getComponentAndWaitForReady = async (): Promise<RenderResult> => {
const renderResult = getComponent();
// wait for welcome page chrome render
await screen.findByText("powered by Matrix");
// go to mobile_register page
defaultDispatcher.dispatch({
action: "start_mobile_registration",
});
await flushPromises();
return renderResult;
};
const enabledMobileRegistration = (): void => {
jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName: string) => {
if (settingName === "Registration.mobileRegistrationHelper") return true;
if (settingName === UIFeature.Registration) return true;
});
};
it("should render welcome screen if mobile registration is not enabled in settings", async () => {
await getComponentAndWaitForReady();
await screen.findByText("powered by Matrix");
});
it("should render mobile registration", async () => {
enabledMobileRegistration();
await getComponentAndWaitForReady();
expect(screen.getByTestId("mobile-register")).toBeInTheDocument();
});
});
}); });

View file

@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
*/ */
import React from "react"; import React from "react";
import { fireEvent, render, screen, waitForElementToBeRemoved } from "@testing-library/react"; import { fireEvent, render, screen, waitFor, waitForElementToBeRemoved } from "@testing-library/react";
import { createClient, MatrixClient, MatrixError, OidcClientConfig } from "matrix-js-sdk/src/matrix"; import { createClient, MatrixClient, MatrixError, OidcClientConfig } from "matrix-js-sdk/src/matrix";
import { mocked, MockedObject } from "jest-mock"; import { mocked, MockedObject } from "jest-mock";
import fetchMock from "fetch-mock-jest"; import fetchMock from "fetch-mock-jest";
@ -87,12 +87,23 @@ describe("Registration", function () {
const defaultHsUrl = "https://matrix.org"; const defaultHsUrl = "https://matrix.org";
const defaultIsUrl = "https://vector.im"; const defaultIsUrl = "https://vector.im";
function getRawComponent(hsUrl = defaultHsUrl, isUrl = defaultIsUrl, authConfig?: OidcClientConfig) { function getRawComponent(
return <Registration {...defaultProps} serverConfig={mkServerConfig(hsUrl, isUrl, authConfig)} />; hsUrl = defaultHsUrl,
isUrl = defaultIsUrl,
authConfig?: OidcClientConfig,
mobileRegister?: boolean,
) {
return (
<Registration
{...defaultProps}
serverConfig={mkServerConfig(hsUrl, isUrl, authConfig)}
mobileRegister={mobileRegister}
/>
);
} }
function getComponent(hsUrl?: string, isUrl?: string, authConfig?: OidcClientConfig) { function getComponent(hsUrl?: string, isUrl?: string, authConfig?: OidcClientConfig, mobileRegister?: boolean) {
return render(getRawComponent(hsUrl, isUrl, authConfig)); return render(getRawComponent(hsUrl, isUrl, authConfig, mobileRegister));
} }
it("should show server picker", async function () { it("should show server picker", async function () {
@ -208,5 +219,31 @@ describe("Registration", function () {
); );
}); });
}); });
describe("when is mobile registeration", () => {
it("should not show server picker", async function () {
const { container } = getComponent(defaultHsUrl, defaultIsUrl, undefined, true);
expect(container.querySelector(".mx_ServerPicker")).toBeFalsy();
});
it("should show username field with autocaps disabled", async function () {
const { container } = getComponent(defaultHsUrl, defaultIsUrl, undefined, true);
await waitFor(() =>
expect(container.querySelector("#mx_RegistrationForm_username")).toHaveAttribute(
"autocapitalize",
"none",
),
);
});
it("should show password and confirm password fields in separate rows", async function () {
const { container } = getComponent(defaultHsUrl, defaultIsUrl, undefined, true);
await waitFor(() => expect(container.querySelector("#mx_RegistrationForm_username")).toBeTruthy());
// when password and confirm password fields are in separate rows there should be 4 rather than 3
expect(container.querySelectorAll(".mx_AuthBody_fieldRow")).toHaveLength(4);
});
});
}); });
}); });

View file

@ -58,15 +58,12 @@ describe("AccessSecretStorageDialog", () => {
beforeEach(() => { beforeEach(() => {
mockClient = getMockClientWithEventEmitter({ mockClient = getMockClientWithEventEmitter({
keyBackupKeyFromRecoveryKey: jest.fn(),
checkSecretStorageKey: jest.fn(), checkSecretStorageKey: jest.fn(),
isValidRecoveryKey: jest.fn(),
}); });
}); });
it("Closes the dialog when the form is submitted with a valid key", async () => { it("Closes the dialog when the form is submitted with a valid key", async () => {
mockClient.checkSecretStorageKey.mockResolvedValue(true); mockClient.checkSecretStorageKey.mockResolvedValue(true);
mockClient.isValidRecoveryKey.mockReturnValue(true);
const onFinished = jest.fn(); const onFinished = jest.fn();
const checkPrivateKey = jest.fn().mockResolvedValue(true); const checkPrivateKey = jest.fn().mockResolvedValue(true);
@ -88,8 +85,8 @@ describe("AccessSecretStorageDialog", () => {
const checkPrivateKey = jest.fn().mockResolvedValue(true); const checkPrivateKey = jest.fn().mockResolvedValue(true);
renderComponent({ onFinished, checkPrivateKey }); renderComponent({ onFinished, checkPrivateKey });
mockClient.keyBackupKeyFromRecoveryKey.mockImplementation(() => { mockClient.checkSecretStorageKey.mockImplementation(() => {
throw new Error("that's no key"); throw new Error("invalid key");
}); });
await enterSecurityKey(); await enterSecurityKey();
@ -115,7 +112,6 @@ describe("AccessSecretStorageDialog", () => {
}; };
const checkPrivateKey = jest.fn().mockResolvedValue(false); const checkPrivateKey = jest.fn().mockResolvedValue(false);
renderComponent({ checkPrivateKey, keyInfo }); renderComponent({ checkPrivateKey, keyInfo });
mockClient.isValidRecoveryKey.mockReturnValue(false);
await enterSecurityKey("Security Phrase"); await enterSecurityKey("Security Phrase");
expect(screen.getByPlaceholderText("Security Phrase")).toHaveValue(securityKey); expect(screen.getByPlaceholderText("Security Phrase")).toHaveValue(securityKey);

View file

@ -0,0 +1,51 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
* Please see LICENSE files in the repository root for full details.
*
*/
import React from "react";
import { screen, render, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
// Needed to be able to mock decodeRecoveryKey
// eslint-disable-next-line no-restricted-imports
import * as recoveryKeyModule from "matrix-js-sdk/src/crypto-api/recovery-key";
import RestoreKeyBackupDialog from "../../../../../src/components/views/dialogs/security/RestoreKeyBackupDialog.tsx";
import { stubClient } from "../../../../test-utils";
describe("<RestoreKeyBackupDialog />", () => {
beforeEach(() => {
stubClient();
jest.spyOn(recoveryKeyModule, "decodeRecoveryKey").mockReturnValue(new Uint8Array(32));
});
it("should render", async () => {
const { asFragment } = render(<RestoreKeyBackupDialog onFinished={jest.fn()} />);
await waitFor(() => expect(screen.getByText("Enter Security Key")).toBeInTheDocument());
expect(asFragment()).toMatchSnapshot();
});
it("should display an error when recovery key is invalid", async () => {
jest.spyOn(recoveryKeyModule, "decodeRecoveryKey").mockImplementation(() => {
throw new Error("Invalid recovery key");
});
const { asFragment } = render(<RestoreKeyBackupDialog onFinished={jest.fn()} />);
await waitFor(() => expect(screen.getByText("Enter Security Key")).toBeInTheDocument());
await userEvent.type(screen.getByRole("textbox"), "invalid key");
await waitFor(() => expect(screen.getByText("👎 Not a valid Security Key")).toBeInTheDocument());
expect(asFragment()).toMatchSnapshot();
});
it("should not raise an error when recovery is valid", async () => {
const { asFragment } = render(<RestoreKeyBackupDialog onFinished={jest.fn()} />);
await waitFor(() => expect(screen.getByText("Enter Security Key")).toBeInTheDocument());
await userEvent.type(screen.getByRole("textbox"), "valid key");
await waitFor(() => expect(screen.getByText("👍 This looks like a valid Security Key!")).toBeInTheDocument());
expect(asFragment()).toMatchSnapshot();
});
});

View file

@ -0,0 +1,298 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<RestoreKeyBackupDialog /> should display an error when recovery key is invalid 1`] = `
<DocumentFragment>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
<div
aria-labelledby="mx_BaseDialog_title"
class="mx_RestoreKeyBackupDialog mx_Dialog_fixedWidth"
data-focus-lock-disabled="false"
role="dialog"
>
<div
class="mx_Dialog_header"
>
<h1
class="mx_Heading_h3 mx_Dialog_title"
id="mx_BaseDialog_title"
>
Enter Security Key
</h1>
</div>
<div
class="mx_RestoreKeyBackupDialog_content"
>
<div>
<p>
<span>
<b>
Warning
</b>
: you should only set up key backup from a trusted computer.
</span>
</p>
<p>
Access your secure message history and set up secure messaging by entering your Security Key.
</p>
<div
class="mx_RestoreKeyBackupDialog_primaryContainer"
>
<input
class="mx_RestoreKeyBackupDialog_recoveryKeyInput"
value="invalid key"
/>
<div
class="mx_RestoreKeyBackupDialog_keyStatus"
>
👎 Not a valid Security Key
</div>
<div
class="mx_Dialog_buttons"
>
<span
class="mx_Dialog_buttons_row"
>
<button
data-testid="dialog-cancel-button"
type="button"
>
Cancel
</button>
<button
class="mx_Dialog_primary"
data-testid="dialog-primary-button"
disabled=""
type="button"
>
Next
</button>
</span>
</div>
</div>
<span>
If you've forgotten your Security Key you can
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
role="button"
tabindex="0"
>
set up new recovery options
</div>
</span>
</div>
</div>
<div
aria-label="Close dialog"
class="mx_AccessibleButton mx_Dialog_cancelButton"
role="button"
tabindex="0"
/>
</div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
</DocumentFragment>
`;
exports[`<RestoreKeyBackupDialog /> should not raise an error when recovery is valid 1`] = `
<DocumentFragment>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
<div
aria-labelledby="mx_BaseDialog_title"
class="mx_RestoreKeyBackupDialog mx_Dialog_fixedWidth"
data-focus-lock-disabled="false"
role="dialog"
>
<div
class="mx_Dialog_header"
>
<h1
class="mx_Heading_h3 mx_Dialog_title"
id="mx_BaseDialog_title"
>
Enter Security Key
</h1>
</div>
<div
class="mx_RestoreKeyBackupDialog_content"
>
<div>
<p>
<span>
<b>
Warning
</b>
: you should only set up key backup from a trusted computer.
</span>
</p>
<p>
Access your secure message history and set up secure messaging by entering your Security Key.
</p>
<div
class="mx_RestoreKeyBackupDialog_primaryContainer"
>
<input
class="mx_RestoreKeyBackupDialog_recoveryKeyInput"
value="valid key"
/>
<div
class="mx_RestoreKeyBackupDialog_keyStatus"
>
👍 This looks like a valid Security Key!
</div>
<div
class="mx_Dialog_buttons"
>
<span
class="mx_Dialog_buttons_row"
>
<button
data-testid="dialog-cancel-button"
type="button"
>
Cancel
</button>
<button
class="mx_Dialog_primary"
data-testid="dialog-primary-button"
type="button"
>
Next
</button>
</span>
</div>
</div>
<span>
If you've forgotten your Security Key you can
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
role="button"
tabindex="0"
>
set up new recovery options
</div>
</span>
</div>
</div>
<div
aria-label="Close dialog"
class="mx_AccessibleButton mx_Dialog_cancelButton"
role="button"
tabindex="0"
/>
</div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
</DocumentFragment>
`;
exports[`<RestoreKeyBackupDialog /> should render 1`] = `
<DocumentFragment>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
<div
aria-labelledby="mx_BaseDialog_title"
class="mx_RestoreKeyBackupDialog mx_Dialog_fixedWidth"
data-focus-lock-disabled="false"
role="dialog"
>
<div
class="mx_Dialog_header"
>
<h1
class="mx_Heading_h3 mx_Dialog_title"
id="mx_BaseDialog_title"
>
Enter Security Key
</h1>
</div>
<div
class="mx_RestoreKeyBackupDialog_content"
>
<div>
<p>
<span>
<b>
Warning
</b>
: you should only set up key backup from a trusted computer.
</span>
</p>
<p>
Access your secure message history and set up secure messaging by entering your Security Key.
</p>
<div
class="mx_RestoreKeyBackupDialog_primaryContainer"
>
<input
class="mx_RestoreKeyBackupDialog_recoveryKeyInput"
value=""
/>
<div
class="mx_RestoreKeyBackupDialog_keyStatus"
/>
<div
class="mx_Dialog_buttons"
>
<span
class="mx_Dialog_buttons_row"
>
<button
data-testid="dialog-cancel-button"
type="button"
>
Cancel
</button>
<button
class="mx_Dialog_primary"
data-testid="dialog-primary-button"
disabled=""
type="button"
>
Next
</button>
</span>
</div>
</div>
<span>
If you've forgotten your Security Key you can
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
role="button"
tabindex="0"
>
set up new recovery options
</div>
</span>
</div>
</div>
<div
aria-label="Close dialog"
class="mx_AccessibleButton mx_Dialog_cancelButton"
role="button"
tabindex="0"
/>
</div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
</DocumentFragment>
`;

View file

@ -0,0 +1,51 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
* Please see LICENSE files in the repository root for full details.
*
*/
import React from "react";
import { render, waitFor } from "@testing-library/react";
import dis from "../../../../src/dispatcher/dispatcher";
import EffectsOverlay from "../../../../src/components/views/elements/EffectsOverlay.tsx";
describe("<EffectsOverlay/>", () => {
let isStarted: boolean;
beforeEach(() => {
isStarted = false;
jest.mock("../../../../src/effects/confetti/index.ts", () => {
return class Confetti {
start = () => {
isStarted = true;
};
stop = jest.fn();
};
});
});
afterEach(() => jest.useRealTimers());
it("should render", () => {
const { asFragment } = render(<EffectsOverlay roomWidth={100} />);
expect(asFragment()).toMatchSnapshot();
});
it("should start the confetti effect", async () => {
render(<EffectsOverlay roomWidth={100} />);
dis.dispatch({ action: "effects.confetti" });
await waitFor(() => expect(isStarted).toBe(true));
});
it("should start the confetti effect when the event is not outdated", async () => {
const eventDate = new Date("2024-09-01");
const date = new Date("2024-09-02");
jest.useFakeTimers().setSystemTime(date);
render(<EffectsOverlay roomWidth={100} />);
dis.dispatch({ action: "effects.confetti", event: { getTs: () => eventDate.getTime() } });
await waitFor(() => expect(isStarted).toBe(true));
});
});

View file

@ -0,0 +1,12 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<EffectsOverlay/> should render 1`] = `
<DocumentFragment>
<canvas
aria-hidden="true"
height="768"
style="display: block; z-index: 999999; pointer-events: none; position: fixed; top: 0px; right: 0px;"
width="100"
/>
</DocumentFragment>
`;

View file

@ -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. Please see LICENSE files in the repository root for full details.
*/ */
import { render, screen } from "@testing-library/react"; import { render, screen, waitFor } from "@testing-library/react";
import { MatrixClient, ThreepidMedium } from "matrix-js-sdk/src/matrix"; import { MatrixClient, ThreepidMedium } from "matrix-js-sdk/src/matrix";
import React from "react"; import React from "react";
import userEvent from "@testing-library/user-event"; import userEvent from "@testing-library/user-event";
@ -218,7 +218,17 @@ describe("AddRemoveThreepids", () => {
await userEvent.type(input, PHONE1_LOCALNUM); await userEvent.type(input, PHONE1_LOCALNUM);
const addButton = screen.getByRole("button", { name: "Add" }); const addButton = screen.getByRole("button", { name: "Add" });
await userEvent.click(addButton); userEvent.click(addButton);
const continueButton = await screen.findByRole("button", { name: "Continue" });
await expect(continueButton).toHaveAttribute("aria-disabled", "true");
await expect(
await screen.findByText(
`A text message has been sent to +${PHONE1.address}. Please enter the verification code it contains.`,
),
).toBeInTheDocument();
expect(client.requestAdd3pidMsisdnToken).toHaveBeenCalledWith( expect(client.requestAdd3pidMsisdnToken).toHaveBeenCalledWith(
"GB", "GB",
@ -226,15 +236,14 @@ describe("AddRemoveThreepids", () => {
client.generateClientSecret(), client.generateClientSecret(),
1, 1,
); );
const continueButton = screen.getByRole("button", { name: "Continue" });
expect(continueButton).toHaveAttribute("aria-disabled", "true");
const verificationInput = screen.getByRole("textbox", { name: "Verification code" }); const verificationInput = screen.getByRole("textbox", { name: "Verification code" });
await userEvent.type(verificationInput, "123456"); await userEvent.type(verificationInput, "123456");
expect(continueButton).not.toHaveAttribute("aria-disabled", "true"); expect(continueButton).not.toHaveAttribute("aria-disabled", "true");
await userEvent.click(continueButton); userEvent.click(continueButton);
await waitFor(() => expect(continueButton).toHaveAttribute("aria-disabled", "true"));
expect(client.addThreePidOnly).toHaveBeenCalledWith({ expect(client.addThreePidOnly).toHaveBeenCalledWith({
client_secret: client.generateClientSecret(), client_secret: client.generateClientSecret(),

View file

@ -94,6 +94,10 @@ describe("StopGapWidgetDriver", () => {
"org.matrix.msc2762.timeline:!1:example.org", "org.matrix.msc2762.timeline:!1:example.org",
"org.matrix.msc2762.send.event:org.matrix.rageshake_request", "org.matrix.msc2762.send.event:org.matrix.rageshake_request",
"org.matrix.msc2762.receive.event:org.matrix.rageshake_request", "org.matrix.msc2762.receive.event:org.matrix.rageshake_request",
"org.matrix.msc2762.send.event:m.reaction",
"org.matrix.msc2762.receive.event:m.reaction",
"org.matrix.msc2762.send.event:m.room.redaction",
"org.matrix.msc2762.receive.event:m.room.redaction",
"org.matrix.msc2762.receive.state_event:m.room.create", "org.matrix.msc2762.receive.state_event:m.room.create",
"org.matrix.msc2762.receive.state_event:m.room.member", "org.matrix.msc2762.receive.state_event:m.room.member",
"org.matrix.msc2762.receive.state_event:org.matrix.msc3401.call", "org.matrix.msc2762.receive.state_event:org.matrix.msc3401.call",

311
yarn.lock
View file

@ -1515,10 +1515,10 @@
minimatch "^3.1.2" minimatch "^3.1.2"
strip-json-comments "^3.1.1" strip-json-comments "^3.1.1"
"@eslint/js@8.57.0": "@eslint/js@8.57.1":
version "8.57.0" version "8.57.1"
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f" resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.1.tgz#de633db3ec2ef6a3c89e2f19038063e8a122e2c2"
integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==
"@floating-ui/core@^1.6.0": "@floating-ui/core@^1.6.0":
version "1.6.5" version "1.6.5"
@ -1556,12 +1556,12 @@
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.5.tgz#105c37d9d9620ce69b7f692a20c821bf1ad2cbf9" resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.5.tgz#105c37d9d9620ce69b7f692a20c821bf1ad2cbf9"
integrity sha512-sTcG+QZ6fdEUObICavU+aB3Mp8HY4n14wYHdxK4fXjPmv3PXZZeY5RaguJmGyeH/CJQhX3fqKUtS4qc1LoHwhQ== integrity sha512-sTcG+QZ6fdEUObICavU+aB3Mp8HY4n14wYHdxK4fXjPmv3PXZZeY5RaguJmGyeH/CJQhX3fqKUtS4qc1LoHwhQ==
"@humanwhocodes/config-array@^0.11.14": "@humanwhocodes/config-array@^0.13.0":
version "0.11.14" version "0.13.0"
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.13.0.tgz#fb907624df3256d04b9aa2df50d7aa97ec648748"
integrity sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg== integrity sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==
dependencies: dependencies:
"@humanwhocodes/object-schema" "^2.0.2" "@humanwhocodes/object-schema" "^2.0.3"
debug "^4.3.1" debug "^4.3.1"
minimatch "^3.0.5" minimatch "^3.0.5"
@ -1570,10 +1570,10 @@
resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c"
integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==
"@humanwhocodes/object-schema@^2.0.2": "@humanwhocodes/object-schema@^2.0.3":
version "2.0.2" version "2.0.3"
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz#d9fae00a2d5cb40f92cfe64b47ad749fbc38f917" resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3"
integrity sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw== integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==
"@isaacs/cliui@^8.0.2": "@isaacs/cliui@^8.0.2":
version "8.0.2" version "8.0.2"
@ -2001,11 +2001,11 @@
integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
"@playwright/test@^1.40.1": "@playwright/test@^1.40.1":
version "1.46.1" version "1.47.1"
resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.46.1.tgz#a8dfdcd623c4c23bb1b7ea588058aad41055c188" resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.47.1.tgz#568a46229a5aef54b74977297a7946bb5ac4b67b"
integrity sha512-Fq6SwLujA/DOIvNC2EL/SojJnkKf/rAwJ//APpJJHRyMi1PdKrY3Az+4XNQ51N4RTbItbIByQ0jgd1tayq1aeA== integrity sha512-dbWpcNQZ5nj16m+A5UNScYx7HX5trIy7g4phrcitn+Nk83S32EBX/CLU4hiF4RGKX/yRc93AAqtfaXB7JWBd4Q==
dependencies: dependencies:
playwright "1.46.1" playwright "1.47.1"
"@radix-ui/primitive@1.0.1": "@radix-ui/primitive@1.0.1":
version "1.0.1" version "1.0.1"
@ -2347,76 +2347,76 @@
resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-1.1.0.tgz#f817d1d3265ac5415dadc67edab30ae196696438" resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-1.1.0.tgz#f817d1d3265ac5415dadc67edab30ae196696438"
integrity sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg== integrity sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==
"@sentry-internal/browser-utils@8.28.0": "@sentry-internal/browser-utils@8.30.0":
version "8.28.0" version "8.30.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-8.28.0.tgz#bddc58c154e898195d45e971e058e237085bbcc2" resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-8.30.0.tgz#eb68c79556ffb864eb5924a53affde52f2b77362"
integrity sha512-tE9++KEy8SlqibTmYymuxFVAnutsXBqrwQ936WJbjaMfkqXiro7C1El0ybkprskd0rKS7kln20Q6nQlNlMEoTA== integrity sha512-pwX+awNWaxSOAsBLVLqc1+Hw+Fm1Nci9mbKFA6Ed5YzCG049PnBVQwugpmx2dcyyCqJpORhcIqb9jHdCkYmCiA==
dependencies: dependencies:
"@sentry/core" "8.28.0" "@sentry/core" "8.30.0"
"@sentry/types" "8.28.0" "@sentry/types" "8.30.0"
"@sentry/utils" "8.28.0" "@sentry/utils" "8.30.0"
"@sentry-internal/feedback@8.28.0": "@sentry-internal/feedback@8.30.0":
version "8.28.0" version "8.30.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-8.28.0.tgz#f278548ead037ad38e54d24b1afcdc1f711c5715" resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-8.30.0.tgz#6f78a245298502e4cc5ce77313dde6965abfecfe"
integrity sha512-5vYunPCDBLCJ8QNnhepacdYheiN+UtYxpGAIaC/zjBC1nDuBgWs+TfKPo1UlO/1sesfgs9ibpxtShOweucL61g== integrity sha512-ParFRxQY6helxkwUDmro77Wc5uSIC6rZos88jYMrYwFmoTJaNWf4lDzPyECfdSiSYyzSMZk4dorSUN85Ul7DCg==
dependencies: dependencies:
"@sentry/core" "8.28.0" "@sentry/core" "8.30.0"
"@sentry/types" "8.28.0" "@sentry/types" "8.30.0"
"@sentry/utils" "8.28.0" "@sentry/utils" "8.30.0"
"@sentry-internal/replay-canvas@8.28.0": "@sentry-internal/replay-canvas@8.30.0":
version "8.28.0" version "8.30.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-8.28.0.tgz#6a08541f9fecd912b7334c693a403469c9e34a89" resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-8.30.0.tgz#3630eec14d23b1fd368d8c331ee695aa5bb41425"
integrity sha512-RfpYHDHMUKGeEdx41QtHITjEn6P3tGaDPHvatqdrD3yv4j+wbJ6laX1PrIxCpGFUtjdzkqi/KUcvUd2kzbH/FA== integrity sha512-y/QqcvchhtMlVA6eOZicIfTxtZarazQZJuFW0018ynPxBTiuuWSxMCLqduulXUYsFejfD8/eKHb3BpCIFdDYjg==
dependencies: dependencies:
"@sentry-internal/replay" "8.28.0" "@sentry-internal/replay" "8.30.0"
"@sentry/core" "8.28.0" "@sentry/core" "8.30.0"
"@sentry/types" "8.28.0" "@sentry/types" "8.30.0"
"@sentry/utils" "8.28.0" "@sentry/utils" "8.30.0"
"@sentry-internal/replay@8.28.0": "@sentry-internal/replay@8.30.0":
version "8.28.0" version "8.30.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-8.28.0.tgz#a84523066ab363239ef6b4180726908cab510e5f" resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-8.30.0.tgz#6a4a8bd551a16ea5f77f913acbccd88061868c84"
integrity sha512-70jvzzOL5O74gahgXKyRkZgiYN93yly5gq+bbj4/6NRQ+EtPd285+ccy0laExdfyK0ugvvwD4v+1MQit52OAsg== integrity sha512-/KFre+BrovPCiovgAu5N1ErJtkDVzkJA5hV3Jw011AlxRWxrmPwu6+9sV9/rn3tqYAGyq6IggYqeIOHhLh1Ihg==
dependencies: dependencies:
"@sentry-internal/browser-utils" "8.28.0" "@sentry-internal/browser-utils" "8.30.0"
"@sentry/core" "8.28.0" "@sentry/core" "8.30.0"
"@sentry/types" "8.28.0" "@sentry/types" "8.30.0"
"@sentry/utils" "8.28.0" "@sentry/utils" "8.30.0"
"@sentry/browser@^8.0.0": "@sentry/browser@^8.0.0":
version "8.28.0" version "8.30.0"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-8.28.0.tgz#e3d28e7a917c212418c887b4fabacc6ed88baea8" resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-8.30.0.tgz#3c6d5ef62d7daca2873b47f59b136c33941b56de"
integrity sha512-i/gjMYzIGQiPFH1pCbdnTwH9xs9mTAqzN+goP3GWX5a58frc7h8vxyA/5z0yMd0aCW6U8mVxnoAT72vGbKbx0g== integrity sha512-M+tKqawH9S3CqlAIcqdZcHbcsNQkEa9MrPqPCYvXco3C4LRpNizJP2XwBiGQY2yK+fOSvbaWpPtlI938/wuRZQ==
dependencies: dependencies:
"@sentry-internal/browser-utils" "8.28.0" "@sentry-internal/browser-utils" "8.30.0"
"@sentry-internal/feedback" "8.28.0" "@sentry-internal/feedback" "8.30.0"
"@sentry-internal/replay" "8.28.0" "@sentry-internal/replay" "8.30.0"
"@sentry-internal/replay-canvas" "8.28.0" "@sentry-internal/replay-canvas" "8.30.0"
"@sentry/core" "8.28.0" "@sentry/core" "8.30.0"
"@sentry/types" "8.28.0" "@sentry/types" "8.30.0"
"@sentry/utils" "8.28.0" "@sentry/utils" "8.30.0"
"@sentry/core@8.28.0": "@sentry/core@8.30.0":
version "8.28.0" version "8.30.0"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-8.28.0.tgz#dd28fa913c296b443d4070f147c63e81edf429c8" resolved "https://registry.yarnpkg.com/@sentry/core/-/core-8.30.0.tgz#f929e42e9a537bfa3eb6024082714e9ab98d822b"
integrity sha512-+If9uubvpZpvaQQw4HLiKPhrSS9/KcoA/AcdQkNm+5CVwAoOmDPtyYfkPBgfo2hLZnZQqR1bwkz/PrNoOm+gqA== integrity sha512-CJ/FuWLw0QEKGKXGL/nm9eaOdajEcmPekLuHAuOCxID7N07R9l9laz3vFbAkUZ97GGDv3sYrJZgywfY3Moropg==
dependencies: dependencies:
"@sentry/types" "8.28.0" "@sentry/types" "8.30.0"
"@sentry/utils" "8.28.0" "@sentry/utils" "8.30.0"
"@sentry/types@8.28.0": "@sentry/types@8.30.0":
version "8.28.0" version "8.30.0"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-8.28.0.tgz#a1cfc004d5714679cb3fed06c27298b0275d13b5" resolved "https://registry.yarnpkg.com/@sentry/types/-/types-8.30.0.tgz#5f5011f5b16bafd30a039ca5e8c337e948c703fb"
integrity sha512-hOfqfd92/AzBrEdMgmmV1VfOXJbIfleFTnerRl0mg/+CcNgP/6+Fdonp354TD56ouWNF2WkOM6sEKSXMWp6SEQ== integrity sha512-kgWW2BCjBmVlSQRG32GonHEVyeDbys74xf9mLPvynwHTgw3+NUlNAlEdu05xnb2ow4bCTHfbkS5G1zRgyv5k4Q==
"@sentry/utils@8.28.0": "@sentry/utils@8.30.0":
version "8.28.0" version "8.30.0"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-8.28.0.tgz#0feb46015033879b2a3cee4c0661386610025f47" resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-8.30.0.tgz#2343dd8593ea83890b3e0d792ed3fa257955a26b"
integrity sha512-smhk7PJpvDMQ2DB5p2qn9UeoUHdU41IgjMmS2xklZpa8tjzBTxDeWpGvrX2fuH67D9bAJuLC/XyZjJCHLoEW5g== integrity sha512-wZxU2HWlzsnu8214Xy7S7cRIuD6h8Z5DnnkojJfX0i0NLooepZQk2824el1Q13AakLb7/S8CHSHXOMnCtoSduw==
dependencies: dependencies:
"@sentry/types" "8.28.0" "@sentry/types" "8.30.0"
"@sinclair/typebox@^0.27.8": "@sinclair/typebox@^0.27.8":
version "0.27.8" version "0.27.8"
@ -2784,9 +2784,9 @@
undici-types "~5.26.4" undici-types "~5.26.4"
"@types/node@18": "@types/node@18":
version "18.19.48" version "18.19.50"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.48.tgz#3a1696f4a7298d8831ed9ce47db62bf4c62c8880" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.50.tgz#8652b34ee7c0e7e2004b3f08192281808d41bf5a"
integrity sha512-7WevbG4ekUcRQSZzOwxWgi5dZmTak7FaxXDoW7xVxPBmKx1rTzfmRLkeCgJzcbBnOV2dkhAPc8cCeT6agocpjg== integrity sha512-xonK+NRrMBRtkL1hVCc3G+uXtjh1Al4opBLjqVmipe5ZAaBYWW6cNAiBVZ1BvmkBhep698rP3UM3aRAdSALuhg==
dependencies: dependencies:
undici-types "~5.26.4" undici-types "~5.26.4"
@ -2858,10 +2858,10 @@
dependencies: dependencies:
"@types/react" "*" "@types/react" "*"
"@types/react@*", "@types/react@17.0.80", "@types/react@^17": "@types/react@*", "@types/react@17.0.82", "@types/react@^17":
version "17.0.80" version "17.0.82"
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.80.tgz#a5dfc351d6a41257eb592d73d3a85d3b7dbcbb41" resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.82.tgz#eb84c38ee1023cd61be1b909fde083ac83fc163f"
integrity sha512-LrgHIu2lEtIo8M7d1FcI3BdwXWoRQwMoXOZ7+dPTW0lYREjmlHl3P0U1VD0i/9tppOuv8/sam7sOjx34TxSFbA== integrity sha512-wTW8Lu/PARGPFE8tOZqCvprOKg5sen/2uS03yKn2xbCDFP9oLncm7vMDQ2+dEQXHVIXrOpW6u72xUXEXO0ypSw==
dependencies: dependencies:
"@types/prop-types" "*" "@types/prop-types" "*"
"@types/scheduler" "^0.16" "@types/scheduler" "^0.16"
@ -3601,10 +3601,10 @@ blurhash@^2.0.3:
resolved "https://registry.yarnpkg.com/blurhash/-/blurhash-2.0.5.tgz#efde729fc14a2f03571a6aa91b49cba80d1abe4b" resolved "https://registry.yarnpkg.com/blurhash/-/blurhash-2.0.5.tgz#efde729fc14a2f03571a6aa91b49cba80d1abe4b"
integrity sha512-cRygWd7kGBQO3VEhPiTgq4Wc43ctsM+o46urrmPOiuAe+07fzlSB9OJVdpgDL0jPqXUVQ9ht7aq7kxOeJHRK+w== integrity sha512-cRygWd7kGBQO3VEhPiTgq4Wc43ctsM+o46urrmPOiuAe+07fzlSB9OJVdpgDL0jPqXUVQ9ht7aq7kxOeJHRK+w==
body-parser@1.20.2: body-parser@1.20.3:
version "1.20.2" version "1.20.3"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6"
integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==
dependencies: dependencies:
bytes "3.1.2" bytes "3.1.2"
content-type "~1.0.5" content-type "~1.0.5"
@ -3614,7 +3614,7 @@ body-parser@1.20.2:
http-errors "2.0.0" http-errors "2.0.0"
iconv-lite "0.4.24" iconv-lite "0.4.24"
on-finished "2.4.1" on-finished "2.4.1"
qs "6.11.0" qs "6.13.0"
raw-body "2.5.2" raw-body "2.5.2"
type-is "~1.6.18" type-is "~1.6.18"
unpipe "1.0.0" unpipe "1.0.0"
@ -4059,6 +4059,14 @@ css-tree@2.3.1, css-tree@^2.3.1:
mdn-data "2.0.30" mdn-data "2.0.30"
source-map-js "^1.0.1" source-map-js "^1.0.1"
css-tree@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-3.0.0.tgz#079c7b87e465a28cedbc826502f9a227213db0f3"
integrity sha512-o88DVQ6GzsABn1+6+zo2ct801dBO5OASVyxbbvA2W20ue2puSh/VOuqUj90eUeMSX/xqGqBmOKiRQN7tJOuBXw==
dependencies:
mdn-data "2.10.0"
source-map-js "^1.0.1"
css.escape@^1.5.1: css.escape@^1.5.1:
version "1.5.1" version "1.5.1"
resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb"
@ -4443,6 +4451,11 @@ encodeurl@~1.0.2:
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
encodeurl@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58"
integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==
entities@^4.2.0: entities@^4.2.0:
version "4.4.0" version "4.4.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-4.4.0.tgz#97bdaba170339446495e653cfd2db78962900174" resolved "https://registry.yarnpkg.com/entities/-/entities-4.4.0.tgz#97bdaba170339446495e653cfd2db78962900174"
@ -4844,16 +4857,16 @@ eslint-visitor-keys@^4.0.0:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz#e3adc021aa038a2a8e0b2f8b0ce8f66b9483b1fb" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz#e3adc021aa038a2a8e0b2f8b0ce8f66b9483b1fb"
integrity sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw== integrity sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==
eslint@8.57.0: eslint@8.57.1:
version "8.57.0" version "8.57.1"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.0.tgz#c786a6fd0e0b68941aaf624596fb987089195668" resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.1.tgz#7df109654aba7e3bbe5c8eae533c5e461d3c6ca9"
integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ== integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==
dependencies: dependencies:
"@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/eslint-utils" "^4.2.0"
"@eslint-community/regexpp" "^4.6.1" "@eslint-community/regexpp" "^4.6.1"
"@eslint/eslintrc" "^2.1.4" "@eslint/eslintrc" "^2.1.4"
"@eslint/js" "8.57.0" "@eslint/js" "8.57.1"
"@humanwhocodes/config-array" "^0.11.14" "@humanwhocodes/config-array" "^0.13.0"
"@humanwhocodes/module-importer" "^1.0.1" "@humanwhocodes/module-importer" "^1.0.1"
"@nodelib/fs.walk" "^1.2.8" "@nodelib/fs.walk" "^1.2.8"
"@ungap/structured-clone" "^1.2.0" "@ungap/structured-clone" "^1.2.0"
@ -5009,36 +5022,36 @@ expect@^29.0.0, expect@^29.7.0:
jest-util "^29.7.0" jest-util "^29.7.0"
express@^4.18.2: express@^4.18.2:
version "4.19.2" version "4.20.0"
resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465" resolved "https://registry.yarnpkg.com/express/-/express-4.20.0.tgz#f1d08e591fcec770c07be4767af8eb9bcfd67c48"
integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q== integrity sha512-pLdae7I6QqShF5PnNTCVn4hI91Dx0Grkn2+IAsMTgMIKuQVte2dN9PeGSSAME2FR8anOhVA62QDIUaWVfEXVLw==
dependencies: dependencies:
accepts "~1.3.8" accepts "~1.3.8"
array-flatten "1.1.1" array-flatten "1.1.1"
body-parser "1.20.2" body-parser "1.20.3"
content-disposition "0.5.4" content-disposition "0.5.4"
content-type "~1.0.4" content-type "~1.0.4"
cookie "0.6.0" cookie "0.6.0"
cookie-signature "1.0.6" cookie-signature "1.0.6"
debug "2.6.9" debug "2.6.9"
depd "2.0.0" depd "2.0.0"
encodeurl "~1.0.2" encodeurl "~2.0.0"
escape-html "~1.0.3" escape-html "~1.0.3"
etag "~1.8.1" etag "~1.8.1"
finalhandler "1.2.0" finalhandler "1.2.0"
fresh "0.5.2" fresh "0.5.2"
http-errors "2.0.0" http-errors "2.0.0"
merge-descriptors "1.0.1" merge-descriptors "1.0.3"
methods "~1.1.2" methods "~1.1.2"
on-finished "2.4.1" on-finished "2.4.1"
parseurl "~1.3.3" parseurl "~1.3.3"
path-to-regexp "0.1.7" path-to-regexp "0.1.10"
proxy-addr "~2.0.7" proxy-addr "~2.0.7"
qs "6.11.0" qs "6.11.0"
range-parser "~1.2.1" range-parser "~1.2.1"
safe-buffer "5.2.1" safe-buffer "5.2.1"
send "0.18.0" send "0.19.0"
serve-static "1.15.0" serve-static "1.16.0"
setprototypeof "1.2.0" setprototypeof "1.2.0"
statuses "2.0.1" statuses "2.0.1"
type-is "~1.6.18" type-is "~1.6.18"
@ -7065,6 +7078,11 @@ mdn-data@2.0.30:
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.30.tgz#ce4df6f80af6cfbe218ecd5c552ba13c4dfa08cc" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.30.tgz#ce4df6f80af6cfbe218ecd5c552ba13c4dfa08cc"
integrity sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA== integrity sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==
mdn-data@2.10.0:
version "2.10.0"
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.10.0.tgz#701da407f8fbc7a42aa0ba0c149ec897daef8986"
integrity sha512-qq7C3EtK3yJXMwz1zAab65pjl+UhohqMOctTgcqjLOWABqmwj+me02LSsCuEUxnst9X1lCBpoE0WArGKgdGDzw==
mdurl@~1.0.1: mdurl@~1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
@ -7090,10 +7108,10 @@ meow@^13.2.0:
resolved "https://registry.yarnpkg.com/meow/-/meow-13.2.0.tgz#6b7d63f913f984063b3cc261b6e8800c4cd3474f" resolved "https://registry.yarnpkg.com/meow/-/meow-13.2.0.tgz#6b7d63f913f984063b3cc261b6e8800c4cd3474f"
integrity sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA== integrity sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==
merge-descriptors@1.0.1: merge-descriptors@1.0.3:
version "1.0.1" version "1.0.3"
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5"
integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==
merge-stream@^2.0.0: merge-stream@^2.0.0:
version "2.0.0" version "2.0.0"
@ -7570,10 +7588,10 @@ path-scurry@^2.0.0:
lru-cache "^11.0.0" lru-cache "^11.0.0"
minipass "^7.1.2" minipass "^7.1.2"
path-to-regexp@0.1.7: path-to-regexp@0.1.10:
version "0.1.7" version "0.1.10"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.10.tgz#67e9108c5c0551b9e5326064387de4763c4d5f8b"
integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== integrity sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==
path-to-regexp@^2.2.1: path-to-regexp@^2.2.1:
version "2.4.0" version "2.4.0"
@ -7632,17 +7650,17 @@ pkg-dir@^4.2.0:
dependencies: dependencies:
find-up "^4.0.0" find-up "^4.0.0"
playwright-core@1.46.1, playwright-core@^1.45.1: playwright-core@1.47.1, playwright-core@^1.45.1:
version "1.46.1" version "1.47.1"
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.46.1.tgz#28f3ab35312135dda75b0c92a3e5c0e7edb9cc8b" resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.47.1.tgz#bb45bdfb0d48412c535501aa3805867282857df8"
integrity sha512-h9LqIQaAv+CYvWzsZ+h3RsrqCStkBHlgo6/TJlFst3cOTlLghBQlJwPOZKQJTKNaD3QIB7aAVQ+gfWbN3NXB7A== integrity sha512-i1iyJdLftqtt51mEk6AhYFaAJCDx0xQ/O5NU8EKaWFgMjItPVma542Nh/Aq8aLCjIJSzjaiEQGW/nyqLkGF1OQ==
playwright@1.46.1: playwright@1.47.1:
version "1.46.1" version "1.47.1"
resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.46.1.tgz#ea562bc48373648e10420a10c16842f0b227c218" resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.47.1.tgz#cdc1116f5265b8d2ff7be0d8942d49900634dc6c"
integrity sha512-oPcr1yqoXLCkgKtD5eNUPLiN40rYEM39odNpIb6VE6S7/15gJmA1NzVv6zJYusV0e7tzvkU/utBFNa/Kpxmwng== integrity sha512-SUEKi6947IqYbKxRiqnbUobVZY4bF1uu+ZnZNJX9DfU1tlf2UhWfvVjLf01pQx9URsOr18bFVUKXmanYWhbfkw==
dependencies: dependencies:
playwright-core "1.46.1" playwright-core "1.47.1"
optionalDependencies: optionalDependencies:
fsevents "2.3.2" fsevents "2.3.2"
@ -7678,7 +7696,7 @@ postcss-media-query-parser@^0.2.3:
resolved "https://registry.yarnpkg.com/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz#27b39c6f4d94f81b1a73b8f76351c609e5cef244" resolved "https://registry.yarnpkg.com/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz#27b39c6f4d94f81b1a73b8f76351c609e5cef244"
integrity sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig== integrity sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==
postcss-resolve-nested-selector@^0.1.4, postcss-resolve-nested-selector@^0.1.6: postcss-resolve-nested-selector@^0.1.6:
version "0.1.6" version "0.1.6"
resolved "https://registry.yarnpkg.com/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.6.tgz#3d84dec809f34de020372c41b039956966896686" resolved "https://registry.yarnpkg.com/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.6.tgz#3d84dec809f34de020372c41b039956966896686"
integrity sha512-0sglIs9Wmkzbr8lQwEyIzlDOOC9bGmfVKcJTaxv3vMmd3uo4o4DerC3En0bnmgceeql9BfC8hRkp7cg0fjdVqw== integrity sha512-0sglIs9Wmkzbr8lQwEyIzlDOOC9bGmfVKcJTaxv3vMmd3uo4o4DerC3En0bnmgceeql9BfC8hRkp7cg0fjdVqw==
@ -7693,7 +7711,7 @@ postcss-scss@^4.0.4:
resolved "https://registry.yarnpkg.com/postcss-scss/-/postcss-scss-4.0.9.tgz#a03c773cd4c9623cb04ce142a52afcec74806685" resolved "https://registry.yarnpkg.com/postcss-scss/-/postcss-scss-4.0.9.tgz#a03c773cd4c9623cb04ce142a52afcec74806685"
integrity sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A== integrity sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==
postcss-selector-parser@^6.1.1, postcss-selector-parser@^6.1.2: postcss-selector-parser@^6.1.2:
version "6.1.2" version "6.1.2"
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz#27ecb41fb0e3b6ba7a1ec84fff347f734c7929de" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz#27ecb41fb0e3b6ba7a1ec84fff347f734c7929de"
integrity sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg== integrity sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==
@ -7854,6 +7872,13 @@ qs@6.11.0:
dependencies: dependencies:
side-channel "^1.0.4" side-channel "^1.0.4"
qs@6.13.0:
version "6.13.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906"
integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==
dependencies:
side-channel "^1.0.6"
querystring@^0.2.0: querystring@^0.2.0:
version "0.2.1" version "0.2.1"
resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.1.tgz#40d77615bb09d16902a85c3e38aa8b5ed761c2dd" resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.1.tgz#40d77615bb09d16902a85c3e38aa8b5ed761c2dd"
@ -8445,10 +8470,29 @@ send@0.18.0:
range-parser "~1.2.1" range-parser "~1.2.1"
statuses "2.0.1" statuses "2.0.1"
serve-static@1.15.0: send@0.19.0:
version "1.15.0" version "0.19.0"
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8"
integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==
dependencies:
debug "2.6.9"
depd "2.0.0"
destroy "1.2.0"
encodeurl "~1.0.2"
escape-html "~1.0.3"
etag "~1.8.1"
fresh "0.5.2"
http-errors "2.0.0"
mime "1.6.0"
ms "2.1.3"
on-finished "2.4.1"
range-parser "~1.2.1"
statuses "2.0.1"
serve-static@1.16.0:
version "1.16.0"
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.0.tgz#2bf4ed49f8af311b519c46f272bf6ac3baf38a92"
integrity sha512-pDLK8zwl2eKaYrs8mrPZBJua4hMplRWJ1tIFksVC3FtBEBnl8dxgeHtsaMS8DhS9i4fLObaon6ABoc4/hQGdPA==
dependencies: dependencies:
encodeurl "~1.0.2" encodeurl "~1.0.2"
escape-html "~1.0.3" escape-html "~1.0.3"
@ -8571,16 +8615,21 @@ slice-ansi@^7.1.0:
ansi-styles "^6.2.1" ansi-styles "^6.2.1"
is-fullwidth-code-point "^5.0.0" is-fullwidth-code-point "^5.0.0"
source-map-js@^1.0.1, source-map-js@^1.2.0: source-map-js@^1.0.1:
version "1.2.0" version "1.2.1"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
source-map-js@^1.0.2: source-map-js@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
source-map-js@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af"
integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==
source-map-support@0.5.13: source-map-support@0.5.13:
version "0.5.13" version "0.5.13"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932"
@ -8841,16 +8890,16 @@ stylelint-config-standard@^36.0.0:
stylelint-config-recommended "^14.0.1" stylelint-config-recommended "^14.0.1"
stylelint-scss@^6.0.0: stylelint-scss@^6.0.0:
version "6.5.1" version "6.7.0"
resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-6.5.1.tgz#bcb6a4ada71a0adbf181e155548e5f25ee4aeece" resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-6.7.0.tgz#df9c2bcd20c555c5670914f23bb983c94bfbb0e3"
integrity sha512-ZLqdqihm6uDYkrsOeD6YWb+stZI8Wn92kUNDhE4M+g9g1aCnRv0JlOrttFiAJJwaNzpdQgX3YJb5vDQXVuO9Ww== integrity sha512-RFIa2A+pVWS5wjNT+whtK7wsbZEWazyqesCuSaPbPlZ8lh2TujwVJSnCYJijg6ChZzwI8pZPRZS1L6A9aCbXDg==
dependencies: dependencies:
css-tree "2.3.1" css-tree "2.3.1"
is-plain-object "5.0.0" is-plain-object "5.0.0"
known-css-properties "^0.34.0" known-css-properties "^0.34.0"
postcss-media-query-parser "^0.2.3" postcss-media-query-parser "^0.2.3"
postcss-resolve-nested-selector "^0.1.4" postcss-resolve-nested-selector "^0.1.6"
postcss-selector-parser "^6.1.1" postcss-selector-parser "^6.1.2"
postcss-value-parser "^4.2.0" postcss-value-parser "^4.2.0"
stylelint@^16.1.0: stylelint@^16.1.0: