Merge branch 'element-hq:develop' into poll-votes

This commit is contained in:
Tim Vahlbrock 2024-11-01 01:28:33 +01:00 committed by GitHub
commit 6b92c92531
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
49 changed files with 604 additions and 1278 deletions

View file

@ -266,6 +266,9 @@ module.exports = {
parserOptions: { parserOptions: {
project: ["./playwright/tsconfig.json"], project: ["./playwright/tsconfig.json"],
}, },
rules: {
"react-hooks/rules-of-hooks": ["off"],
},
}, },
], ],
settings: { settings: {

View file

@ -20,6 +20,9 @@ on:
jobs: jobs:
prepare: prepare:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
env:
# The order is specified bottom-up to avoid any races for allchange
REPOS: matrix-js-sdk element-web element-desktop
steps: steps:
- name: Checkout Element Desktop - name: Checkout Element Desktop
uses: actions/checkout@v4 uses: actions/checkout@v4

View file

@ -1 +1 @@
20 22

View file

@ -1,5 +1,5 @@
# Builder # Builder
FROM --platform=$BUILDPLATFORM node:20-bullseye as builder FROM --platform=$BUILDPLATFORM node:22-bullseye as builder
# Support custom branch of the js-sdk. This also helps us build images of element-web develop. # Support custom branch of the js-sdk. This also helps us build images of element-web develop.
ARG USE_CUSTOM_SDKS=false ARG USE_CUSTOM_SDKS=false

View file

@ -29,7 +29,7 @@ default theme, you would use `default_theme: "custom-Electric Blue"`.
e.g. in config.json: e.g. in config.json:
``` ```json5
"setting_defaults": { "setting_defaults": {
"custom_themes": [ "custom_themes": [
{ {
@ -59,6 +59,10 @@ e.g. in config.json:
"timeline-text-color": "#2e2f32", "timeline-text-color": "#2e2f32",
"timeline-text-secondary-color": "#61708b", "timeline-text-secondary-color": "#61708b",
"timeline-highlights-color": "#f3f8fd", "timeline-highlights-color": "#f3f8fd",
// These should both be 8 values long
"username-colors": ["#ff0000", /*...*/],
"avatar-background-colors": ["#cc0000", /*...*/]
}, },
"compound": { "compound": {
"--cpd-color-icon-accent-tertiary": "var(--cpd-color-blue-800)", "--cpd-color-icon-accent-tertiary": "var(--cpd-color-blue-800)",

View file

@ -46,5 +46,13 @@
"map_style_url": "https://api.maptiler.com/maps/streets/style.json?key=fU3vlMsMn4Jb6dnEIFsx", "map_style_url": "https://api.maptiler.com/maps/streets/style.json?key=fU3vlMsMn4Jb6dnEIFsx",
"setting_defaults": { "setting_defaults": {
"RustCrypto.staged_rollout_percent": 60 "RustCrypto.staged_rollout_percent": 60
},
"features": {
"feature_video_rooms": true,
"feature_group_calls": true,
"feature_element_call_video_rooms": true
},
"element_call": {
"url": "https://call.element.io"
} }
} }

View file

@ -84,7 +84,7 @@
"dependencies": { "dependencies": {
"@babel/runtime": "^7.12.5", "@babel/runtime": "^7.12.5",
"@formatjs/intl-segmenter": "^11.5.7", "@formatjs/intl-segmenter": "^11.5.7",
"@matrix-org/analytics-events": "^0.26.0", "@matrix-org/analytics-events": "^0.28.0",
"@matrix-org/emojibase-bindings": "^1.3.3", "@matrix-org/emojibase-bindings": "^1.3.3",
"@vector-im/matrix-wysiwyg": "2.37.13", "@vector-im/matrix-wysiwyg": "2.37.13",
"@matrix-org/react-sdk-module-api": "^2.4.0", "@matrix-org/react-sdk-module-api": "^2.4.0",
@ -148,7 +148,7 @@
"tar-js": "^0.3.0", "tar-js": "^0.3.0",
"temporal-polyfill": "^0.2.5", "temporal-polyfill": "^0.2.5",
"ua-parser-js": "^1.0.2", "ua-parser-js": "^1.0.2",
"uuid": "^10.0.0", "uuid": "^11.0.0",
"what-input": "^5.2.10" "what-input": "^5.2.10"
}, },
"devDependencies": { "devDependencies": {
@ -208,7 +208,7 @@
"@types/qrcode": "^1.3.5", "@types/qrcode": "^1.3.5",
"@types/react": "18.3.3", "@types/react": "18.3.3",
"@types/react-beautiful-dnd": "^13.0.0", "@types/react-beautiful-dnd": "^13.0.0",
"@types/react-dom": "18.3.0", "@types/react-dom": "18.3.1",
"@types/react-transition-group": "^4.4.0", "@types/react-transition-group": "^4.4.0",
"@types/sanitize-html": "2.13.0", "@types/sanitize-html": "2.13.0",
"@types/sdp-transform": "^2.4.6", "@types/sdp-transform": "^2.4.6",
@ -219,7 +219,7 @@
"@types/uuid": "^10.0.0", "@types/uuid": "^10.0.0",
"@typescript-eslint/eslint-plugin": "^8.0.0", "@typescript-eslint/eslint-plugin": "^8.0.0",
"@typescript-eslint/parser": "^8.0.0", "@typescript-eslint/parser": "^8.0.0",
"axe-core": "4.10.0", "axe-core": "4.10.2",
"babel-jest": "^29.0.0", "babel-jest": "^29.0.0",
"babel-loader": "^9.0.0", "babel-loader": "^9.0.0",
"babel-plugin-jsx-remove-data-test-id": "^3.0.0", "babel-plugin-jsx-remove-data-test-id": "^3.0.0",
@ -242,7 +242,7 @@
"eslint-plugin-jsx-a11y": "^6.5.1", "eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-matrix-org": "^2.0.2", "eslint-plugin-matrix-org": "^2.0.2",
"eslint-plugin-react": "^7.28.0", "eslint-plugin-react": "^7.28.0",
"eslint-plugin-react-hooks": "^4.3.0", "eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-unicorn": "^56.0.0", "eslint-plugin-unicorn": "^56.0.0",
"express": "^4.18.2", "express": "^4.18.2",
"fake-indexeddb": "^6.0.0", "fake-indexeddb": "^6.0.0",

View file

@ -1,4 +1,4 @@
FROM mcr.microsoft.com/playwright:v1.48.0-jammy FROM mcr.microsoft.com/playwright:v1.48.2-jammy
WORKDIR /work WORKDIR /work

View file

@ -196,14 +196,7 @@ export class Helpers {
*/ */
async assertEmptyPinnedMessagesList() { async assertEmptyPinnedMessagesList() {
const rightPanel = this.getRightPanel(); const rightPanel = this.getRightPanel();
await expect(rightPanel).toMatchScreenshot(`pinned-messages-list-empty.png`, { await expect(rightPanel).toMatchScreenshot(`pinned-messages-list-empty.png`);
// hide the tooltip "Room information" to avoid flakiness
css: `
[data-floating-ui-portal] {
display: none !important;
}
`,
});
} }
/** /**

View file

@ -42,7 +42,7 @@ export class Helpers {
*/ */
async assertReleaseAnnouncementIsVisible(name: string) { async assertReleaseAnnouncementIsVisible(name: string) {
await expect(this.getReleaseAnnouncement(name)).toBeVisible(); await expect(this.getReleaseAnnouncement(name)).toBeVisible();
await expect(this.page).toMatchScreenshot(`release-announcement-${name}.png`); await expect(this.page).toMatchScreenshot(`release-announcement-${name}.png`, { showTooltips: true });
} }
/** /**

View file

@ -345,6 +345,7 @@ export const expect = baseExpect.extend({
if (!options?.showTooltips) { if (!options?.showTooltips) {
css += ` css += `
[data-floating-ui-portal],
[role="tooltip"] { [role="tooltip"] {
visibility: hidden !important; visibility: hidden !important;
} }

View file

@ -20,7 +20,7 @@ import { randB64Bytes } from "../../utils/rand";
// Docker tag to use for synapse docker image. // 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. // 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. // This digest is updated by the playwright-image-updates.yaml workflow periodically.
const DOCKER_TAG = "develop@sha256:85dc2cf25f45ee91fd87efa0ddf2220a5933d212ed656886d5f3832ae3a9ddaf"; const DOCKER_TAG = "develop@sha256:47ed20bdf698523c1876aded101ffa5cd5c5b879c80762b7b18b87347349123b";
async function cfgDirFromTemplate(opts: StartHomeserverOpts): Promise<Omit<HomeserverConfig, "dockerUrl">> { async function cfgDirFromTemplate(opts: StartHomeserverOpts): Promise<Omit<HomeserverConfig, "dockerUrl">> {
const templateDir = path.join(__dirname, "templates", opts.template); const templateDir = path.join(__dirname, "templates", opts.template);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 KiB

After

Width:  |  Height:  |  Size: 198 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Before After
Before After

View file

@ -287,7 +287,6 @@
@import "./views/rooms/_HistoryTile.pcss"; @import "./views/rooms/_HistoryTile.pcss";
@import "./views/rooms/_IRCLayout.pcss"; @import "./views/rooms/_IRCLayout.pcss";
@import "./views/rooms/_JumpToBottomButton.pcss"; @import "./views/rooms/_JumpToBottomButton.pcss";
@import "./views/rooms/_LegacyRoomHeader.pcss";
@import "./views/rooms/_LinkPreviewGroup.pcss"; @import "./views/rooms/_LinkPreviewGroup.pcss";
@import "./views/rooms/_LinkPreviewWidget.pcss"; @import "./views/rooms/_LinkPreviewWidget.pcss";
@import "./views/rooms/_LiveContentSummary.pcss"; @import "./views/rooms/_LiveContentSummary.pcss";

View file

@ -181,11 +181,6 @@ Please see LICENSE files in the repository root for full details.
} }
} }
/* Rooms with immersive content */
.mx_RoomView_immersive .mx_LegacyRoomHeader_wrapper {
border: unset;
}
.mx_RoomView_inCall { .mx_RoomView_inCall {
.mx_RoomView_statusAreaBox_line { .mx_RoomView_statusAreaBox_line {
margin-top: 2px; margin-top: 2px;

View file

@ -18,10 +18,9 @@ Please see LICENSE files in the repository root for full details.
.mx_EditableItem_delete { .mx_EditableItem_delete {
@mixin customisedCancelButton; @mixin customisedCancelButton;
order: 3; order: 3;
margin-right: 5px;
vertical-align: middle; vertical-align: middle;
width: 14px; width: 28px;
height: 14px; height: 28px;
background-color: $alert; background-color: $alert;
mask-size: 100%; mask-size: 100%;
} }
@ -42,7 +41,7 @@ Please see LICENSE files in the repository root for full details.
.mx_EditableItem_item { .mx_EditableItem_item {
flex: auto 1 0; flex: auto 1 0;
order: 1; order: 1;
width: calc(100% - 14px); /* leave space for the remove button */ width: calc(100% - 28px); /* leave space for the remove button */
overflow-x: hidden; overflow-x: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }

View file

@ -1,281 +0,0 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2023 The Matrix.org Foundation C.I.C.
Copyright 2015, 2016 OpenMarket Ltd
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
:root {
--RoomHeader-indicator-dot-size: 8px;
--RoomHeader-indicator-dot-offset: -3px;
--RoomHeader-indicator-pulseColor: $alert;
}
.mx_LegacyRoomHeader {
flex: 0 0 50px;
border-bottom: 1px solid $primary-hairline-color;
background-color: $background;
.mx_LegacyRoomHeader_icon {
height: 12px;
width: 12px;
&.mx_LegacyRoomHeader_icon_video {
height: 14px;
width: 14px;
background-color: $secondary-content;
mask-image: url("$(res)/img/element-icons/call/video-call.svg");
mask-size: 100%;
}
&.mx_E2EIcon {
margin: 0;
height: 100%; /* To give the tooltip room to breathe */
}
}
.mx_CallDuration {
margin-top: calc(($font-15px - $font-13px) / 2); /* To align with the name */
font-size: $font-13px;
}
}
.mx_LegacyRoomHeader_wrapper {
height: 44px;
display: flex;
align-items: center;
min-width: 0;
padding: 10px 20px 9px 16px;
border-bottom: 1px solid $separator;
.mx_InviteOnlyIcon_large {
margin: 0;
}
.mx_BetaCard_betaPill {
margin-right: $spacing-8;
}
/* The container of E2EIcon in the legacy header needs to have its height set */
& > span {
height: 100%;
}
}
.mx_LegacyRoomHeader_name {
flex: 0 1 auto;
overflow: hidden;
color: $primary-content;
font: var(--cpd-font-heading-sm-semibold);
font-weight: var(--cpd-font-weight-semibold);
min-height: 24px;
align-items: center;
border-radius: 6px;
margin: 0 3px;
padding: 1px 4px;
display: flex;
user-select: none;
cursor: pointer;
&:hover {
background-color: $quinary-content;
}
.mx_LegacyRoomHeader_nametext {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.mx_LegacyRoomHeader_chevron {
align-self: center;
width: 20px;
height: 20px;
mask-position: center;
mask-size: 20px;
mask-repeat: no-repeat;
mask-image: url("$(res)/img/feather-customised/chevron-down.svg");
background-color: $tertiary-content;
}
&.mx_LegacyRoomHeader_name--textonly {
cursor: unset;
&:hover {
background-color: unset;
}
}
&[aria-expanded="true"] {
background-color: $separator;
.mx_LegacyRoomHeader_chevron {
transform: rotate(180deg);
}
}
}
.mx_LegacyRoomHeader_settingsHint {
color: $settings-grey-fg-color !important;
}
.mx_LegacyRoomHeader_searchStatus {
font-weight: normal;
opacity: 0.6;
}
.mx_RoomTopic {
position: relative;
cursor: pointer;
}
.mx_LegacyRoomHeader_topic {
$lines: 2;
flex: 1;
color: $secondary-content;
font: var(--cpd-font-body-sm-regular);
line-height: 1rem;
max-height: calc(1rem * $lines);
overflow: hidden;
-webkit-line-clamp: $lines; /* See: https://drafts.csswg.org/css-overflow-3/#webkit-line-clamp */
-webkit-box-orient: vertical;
display: -webkit-box;
}
.mx_LegacyRoomHeader_topic .mx_Emoji {
/* Undo font size increase to prevent vertical cropping and ensure the same size */
/* as in plain text emojis */
font-size: inherit;
}
.mx_LegacyRoomHeader_avatar {
flex: 0;
margin: 0 7px;
position: relative;
cursor: pointer;
}
.mx_LegacyRoomHeader_button_unreadIndicator_bg {
position: absolute;
right: var(--RoomHeader-indicator-dot-offset);
top: var(--RoomHeader-indicator-dot-offset);
margin: 4px;
width: var(--RoomHeader-indicator-dot-size);
height: var(--RoomHeader-indicator-dot-size);
border-radius: 50%;
transform: scale(1.6);
transform-origin: center center;
background: $background;
}
.mx_LegacyRoomHeader_button_unreadIndicator {
position: absolute;
right: var(--RoomHeader-indicator-dot-offset);
top: var(--RoomHeader-indicator-dot-offset);
margin: 4px;
&.mx_Indicator_highlight {
background: var(--cpd-color-icon-critical-primary);
box-shadow: var(--cpd-color-icon-critical-primary);
}
&.mx_Indicator_notification {
background: var(--cpd-color-icon-success-primary);
box-shadow: var(--cpd-color-icon-success-primary);
}
&.mx_Indicator_activity {
background: var(--cpd-color-icon-primary);
box-shadow: var(--cpd-color-icon-primary);
}
}
.mx_LegacyRoomHeader_forgetButton::before {
mask-image: url("$(res)/img/element-icons/leave.svg");
width: 26px;
}
.mx_LegacyRoomHeader_appsButton::before {
mask-image: url("$(res)/img/element-icons/room/apps.svg");
}
.mx_LegacyRoomHeader_appsButton_highlight::before {
background-color: $accent;
}
.mx_LegacyRoomHeader_searchButton::before {
mask-image: url("$(res)/img/element-icons/room/search-inset.svg");
}
.mx_LegacyRoomHeader_inviteButton::before {
mask-image: url("$(res)/img/element-icons/room/invite.svg");
}
.mx_LegacyRoomHeader_voiceCallButton::before {
mask-image: url("$(res)/img/element-icons/call/voice-call.svg");
/* The call button SVG is padded slightly differently, so match it up to the size */
/* of the other icons */
mask-size: 20px;
mask-position: center;
}
.mx_LegacyRoomHeader_videoCallButton::before {
mask-image: url("$(res)/img/element-icons/call/video-call.svg");
}
.mx_LegacyRoomHeader_layoutButton--freedom::before,
.mx_LegacyRoomHeader_freedomIcon::before {
mask-image: url("$(res)/img/element-icons/call/freedom.svg");
}
.mx_LegacyRoomHeader_layoutButton--spotlight::before,
.mx_LegacyRoomHeader_spotlightIcon::before {
mask-image: url("$(res)/img/element-icons/call/spotlight.svg");
}
.mx_LegacyRoomHeader_closeButton {
&::before {
mask-image: url("@vector-im/compound-design-tokens/icons/close.svg");
mask-size: 20px;
mask-position: center;
}
&:hover {
background: unset; /* remove background color on hover */
&::before {
background-color: $icon-button-color; /* set the default background color */
}
}
}
.mx_LegacyRoomHeader_minimiseButton::before {
mask-image: url("$(res)/img/element-icons/reduce.svg");
}
.mx_LegacyRoomHeader_layoutMenu .mx_IconizedContextMenu_icon::before {
content: "";
width: 16px;
height: 16px;
display: block;
mask-position: center;
mask-size: 20px;
mask-repeat: no-repeat;
background: $primary-content;
}
@media only screen and (max-width: 480px) {
.mx_LegacyRoomHeader_wrapper {
padding: 0;
margin: 0;
}
.mx_LegacyRoomHeader {
overflow: hidden;
}
}

View file

@ -88,3 +88,8 @@ Please see LICENSE files in the repository root for full details.
.mx_RoomHeader .mx_BaseAvatar { .mx_RoomHeader .mx_BaseAvatar {
flex-shrink: 0; flex-shrink: 0;
} }
.mx_RoomHeader_videoCallOption {
/* Workaround for https://github.com/element-hq/compound/issues/331 */
min-width: 240px;
}

View file

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="17px" height="15px" viewBox="-1 -1 16 14" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
<!-- Generator: Sketch 3.4.4 (17249) - http://www.bohemiancoding.com/sketch -->
<title>icon_camera</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="06a-Room-settings" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
<g id="06_4-Room-settings-admin" sketch:type="MSArtboardGroup" transform="translate(-248.000000, -71.000000)" fill="#454545">
<path d="M255.5,76.25 C256.119795,76.25 256.649737,76.4700499 257.089844,76.9101562 C257.52995,77.3502626 257.75,77.8802052 257.75,78.5 C257.75,79.1197948 257.52995,79.6497374 257.089844,80.0898438 C256.649737,80.5299501 256.119795,80.75 255.5,80.75 C254.880205,80.75 254.350263,80.5299501 253.910156,80.0898438 C253.47005,79.6497374 253.25,79.1197948 253.25,78.5 C253.25,77.8802052 253.47005,77.3502626 253.910156,76.9101562 C254.350263,76.4700499 254.880205,76.25 255.5,76.25 L255.5,76.25 Z M261,73 C261.552086,73 262.023436,73.1953105 262.414062,73.5859375 C262.804689,73.9765645 263,74.4479139 263,75 L263,82 C263,82.5520861 262.804689,83.0234355 262.414062,83.4140625 C262.023436,83.8046895 261.552086,84 261,84 L250,84 C249.447914,84 248.976564,83.8046895 248.585938,83.4140625 C248.195311,83.0234355 248,82.5520861 248,82 L248,75 C248,74.4479139 248.195311,73.9765645 248.585938,73.5859375 C248.976564,73.1953105 249.447914,73 250,73 L251.75,73 L252.148438,71.9375 C252.247396,71.6822904 252.428384,71.4622405 252.691406,71.2773438 C252.954428,71.092447 253.223957,71 253.5,71 L257.5,71 C257.776043,71 258.045572,71.092447 258.308594,71.2773438 C258.571616,71.4622405 258.752604,71.6822904 258.851562,71.9375 L259.25,73 L261,73 Z M255.5,82 C256.463546,82 257.287757,81.6575555 257.972656,80.9726562 C258.657556,80.287757 259,79.4635465 259,78.5 C259,77.5364535 258.657556,76.712243 257.972656,76.0273438 C257.287757,75.3424445 256.463546,75 255.5,75 C254.536454,75 253.712243,75.3424445 253.027344,76.0273438 C252.342444,76.712243 252,77.5364535 252,78.5 C252,79.4635465 252.342444,80.287757 253.027344,80.9726562 C253.712243,81.6575555 254.536454,82 255.5,82 L255.5,82 Z" id="icon_camera" sketch:type="MSShapeGroup"></path>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -1,3 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.28571 3.66667C6.28571 2.74619 7.03191 2 7.95238 2H11.7619C12.6824 2 13.4286 2.74619 13.4286 3.66667V7.47619C13.4286 8.39666 12.6824 9.14286 11.7619 9.14286H7.95238C7.03191 9.14286 6.28571 8.39667 6.28571 7.47619V3.66667ZM16.5238 2C15.6033 2 14.8571 2.74619 14.8571 3.66667V7.47619C14.8571 8.39667 15.6033 9.14286 16.5238 9.14286H20.3333C21.2538 9.14286 22 8.39666 22 7.47619V3.66667C22 2.74619 21.2538 2 20.3333 2H16.5238ZM16.5238 10.5714C15.6033 10.5714 14.8571 11.3176 14.8571 12.2381V16.0476C14.8571 16.9681 15.6033 17.7143 16.5238 17.7143H20.3333C21.2538 17.7143 22 16.9681 22 16.0476V12.2381C22 11.3176 21.2538 10.5714 20.3333 10.5714H16.5238ZM3.63265 10.5714C2.73096 10.5714 2 11.3024 2 12.2041V20.3673C2 21.269 2.73097 22 3.63266 22H11.7959C12.6976 22 13.4286 21.269 13.4286 20.3673V12.2041C13.4286 11.3024 12.6976 10.5714 11.7959 10.5714H3.63265Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 1 KiB

View file

@ -1,3 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 17.8689V2.51551C4 2.06669 4.53728 1.83452 4.86986 2.13591C8.18767 5.14263 10.9111 7.48209 13.102 9.36399L13.102 9.36403C18.3295 13.8544 20.5243 15.7398 20.5243 17.8689C20.5243 19.4181 19.6538 20.0153 18.1044 20.79C16.5549 21.5648 14.4534 22 12.2621 22C10.0709 22 7.96938 21.5648 6.41992 20.79C4.87047 20.0153 4 18.9646 4 17.8689ZM12.2621 20.9673C16.2548 20.9673 19.4915 19.5801 19.4915 17.869C19.4915 16.1578 16.2548 14.7707 12.2621 14.7707C8.26947 14.7707 5.03277 16.1578 5.03277 17.869C5.03277 19.5801 8.26947 20.9673 12.2621 20.9673ZM16.2618 8.67876C16.1718 8.64549 16.1718 8.51831 16.2618 8.48504L17.84 7.90103C17.8683 7.89057 17.8906 7.86828 17.901 7.84001L18.4851 6.26174C18.5183 6.17182 18.6455 6.17182 18.6788 6.26174L19.2628 7.84001C19.2733 7.86828 19.2955 7.89057 19.3238 7.90103L20.9021 8.48504C20.992 8.51831 20.992 8.64549 20.9021 8.67876L19.3238 9.26277C19.2955 9.27323 19.2733 9.29552 19.2628 9.32379L18.6788 10.9021C18.6455 10.992 18.5183 10.992 18.4851 10.9021L17.901 9.32379C17.8906 9.29552 17.8683 9.27323 17.84 9.26277L16.2618 8.67876ZM13.2618 5.45232C13.1718 5.48559 13.1718 5.61276 13.2618 5.64604L14.0862 5.95111C14.1145 5.96157 14.1368 5.98386 14.1472 6.01213L14.4523 6.83657C14.4856 6.92649 14.6127 6.92649 14.646 6.83657L14.9511 6.01213C14.9615 5.98386 14.9838 5.96157 15.0121 5.95111L15.8365 5.64603C15.9265 5.61276 15.9265 5.48559 15.8365 5.45232L15.0121 5.14725C14.9838 5.13679 14.9615 5.1145 14.9511 5.08623L14.646 4.26178C14.6127 4.17187 14.4856 4.17187 14.4523 4.26178L14.1472 5.08623C14.1368 5.1145 14.1145 5.13679 14.0862 5.14725L13.2618 5.45232Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -1,4 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.0759 13.6172C13.0273 13.7343 13.0004 13.8625 13 13.997C13 13.998 13 13.999 13 14L13 14.0007L13 20C13 20.5523 13.4477 21 14 21C14.5523 21 15 20.5523 15 20L15 16.4142L18.7929 20.2071C19.1834 20.5976 19.8166 20.5976 20.2071 20.2071C20.5976 19.8166 20.5976 19.1834 20.2071 18.7929L16.4142 15L20 15C20.5523 15 21 14.5523 21 14C21 13.4477 20.5523 13 20 13L14.0007 13L14 13C13.999 13 13.998 13 13.997 13C13.743 13.0008 13.4892 13.0977 13.295 13.2908C13.2943 13.2915 13.2936 13.2922 13.2929 13.2929C13.2922 13.2936 13.2915 13.2943 13.2908 13.295C13.196 13.3904 13.1243 13.5001 13.0759 13.6172ZM13.0759 13.6172C13.1262 13.4959 13.1996 13.3867 13.2908 13.295L13.0759 13.6172Z" fill="#737D8C"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.9241 10.3828C10.9727 10.2657 10.9996 10.1375 11 10.003C11 10.002 11 10.001 11 10V9.9993L11 4C11 3.44772 10.5523 3 10 3C9.44772 3 9 3.44772 9 4L9 7.58579L5.20711 3.79289C4.81658 3.40237 4.18342 3.40237 3.79289 3.79289C3.40237 4.18342 3.40237 4.81658 3.79289 5.20711L7.58579 9L4 9C3.44771 9 3 9.44771 3 10C3 10.5523 3.44771 11 4 11L9.9993 11H10C10.001 11 10.002 11 10.003 11C10.257 10.9992 10.5108 10.9023 10.705 10.7092C10.7057 10.7085 10.7064 10.7078 10.7071 10.7071C10.7078 10.7064 10.7085 10.7057 10.7092 10.705C10.804 10.6096 10.8757 10.4999 10.9241 10.3828ZM10.9241 10.3828C10.8738 10.5041 10.8004 10.6133 10.7092 10.705L10.9241 10.3828Z" fill="#737D8C"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -1,4 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M14 2L18 6L7 17H3V13L14 2V2Z" stroke="#2E2F32" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3 22H21" stroke="#2E2F32" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 333 B

View file

@ -1,3 +0,0 @@
<svg width="9" height="4" viewBox="0 0 9 4" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.5 4L0.602887 0.25L8.39711 0.250001L4.5 4Z" fill="#0DBD8B"/>
</svg>

Before

Width:  |  Height:  |  Size: 171 B

View file

@ -1,3 +0,0 @@
<svg height="45" viewBox="0 0 69 45" width="69" xmlns="http://www.w3.org/2000/svg">
<path d="m6 0h57c3.3137085 0 6 2.6862915 6 6v33c0 3.3137085-2.6862915 6-6 6h-57c-3.3137085 0-6-2.6862915-6-6v-33c0-3.3137085 2.6862915-6 6-6zm23.3703704 11c-.6518551 0-1.1841955.3860101-1.5970371 1.158042l-7.7244444 14.7006993-7.7896296-14.7006993c-.4128416-.7720319-.9560461-1.158042-1.6296297-1.158042-.4780271 0-.86913425.1554763-1.1733333.4664336-.30419906.3109572-.4562963.7130511-.4562963 1.2062937v19.7510489c0 .4717973.13580111.8524461.40740741 1.1419581.27160629.2895119.63555329.4342657 1.09185189.4342657.478027 0 .8474061-.1393925 1.1081481-.4181818.2607421-.2787893.3911111-.6647994.3911111-1.158042v-14.8293706l6.4207408 11.8699301c.4128415.7505865.9451819 1.1258741 1.597037 1.1258741s1.1841955-.3752876 1.597037-1.1258741l6.3881482-11.9986014v14.9580419c0 .4932426.130369.8792527.3911111 1.158042.260742.2787893.619257.4181818 1.0755555.4181818.4780271 0 .8528382-.1393925 1.1244445-.4181818s.4074074-.6647994.4074074-1.158042v-19.7510489c0-.4932426-.1520972-.8953365-.4562963-1.2062937-.304199-.3109573-.6953062-.4664336-1.1733333-.4664336zm18.1296296 17.8786797-7.3180195-7.3180195c-.5857864-.5857865-1.5355339-.5857865-2.1213203 0-.5857865.5857864-.5857865 1.5355339 0 2.1213203l9.8994949 9.899495c.3056756.3056756.7104567.4518432 1.1109127.438503.400456.0133402.8052371-.1328274 1.1109127-.438503l9.899495-9.899495c.5857864-.5857864.5857864-1.5355339 0-2.1213203-.5857865-.5857865-1.535534-.5857865-2.1213204 0l-7.4601551 7.4601551v-16.5208153c0-.8284271-.6715729-1.5-1.5-1.5s-1.5.6715729-1.5 1.5z" fill="#d8d8d8" fill-rule="evenodd"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -1,5 +0,0 @@
<svg width="11" height="12" viewBox="0 0 11 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect y="0.5" width="2.2" height="11" fill="#0DBD8B"/>
<rect x="4.40015" y="2.70001" width="2.2" height="8.8" fill="#0DBD8B"/>
<rect x="8.79993" y="7.10004" width="2.2" height="4.4" fill="#0DBD8B"/>
</svg>

Before

Width:  |  Height:  |  Size: 302 B

File diff suppressed because one or more lines are too long

View file

@ -82,6 +82,10 @@ export class DecryptionFailureTracker {
return "HistoricalMessage"; return "HistoricalMessage";
case DecryptionFailureCode.HISTORICAL_MESSAGE_USER_NOT_JOINED: case DecryptionFailureCode.HISTORICAL_MESSAGE_USER_NOT_JOINED:
return "ExpectedDueToMembership"; return "ExpectedDueToMembership";
case DecryptionFailureCode.SENDER_IDENTITY_PREVIOUSLY_VERIFIED:
return "ExpectedVerificationViolation";
case DecryptionFailureCode.UNSIGNED_SENDER_DEVICE:
return "ExpectedSentByInsecureDevice";
default: default:
return "UnknownError"; return "UnknownError";
} }

View file

@ -292,16 +292,11 @@ export default class DeviceListener {
await crypto.getUserDeviceInfo([cli.getSafeUserId()]); await crypto.getUserDeviceInfo([cli.getSafeUserId()]);
// cross signing isn't enabled - nag to enable it // cross signing isn't enabled - nag to enable it
// There are 3 different toasts for: // There are 2 different toasts for:
if (!(await crypto.getCrossSigningKeyId()) && (await crypto.userHasCrossSigningKeys())) { if (!(await crypto.getCrossSigningKeyId()) && (await crypto.userHasCrossSigningKeys())) {
// Cross-signing on account but this device doesn't trust the master key (verify this session) // Cross-signing on account but this device doesn't trust the master key (verify this session)
showSetupEncryptionToast(SetupKind.VERIFY_THIS_SESSION); showSetupEncryptionToast(SetupKind.VERIFY_THIS_SESSION);
this.checkKeyBackupStatus(); this.checkKeyBackupStatus();
} else {
const backupInfo = await this.getKeyBackupInfo();
if (backupInfo) {
// No cross-signing on account but key backup available (upgrade encryption)
showSetupEncryptionToast(SetupKind.UPGRADE_ENCRYPTION);
} else { } else {
// No cross-signing or key backup on account (set up encryption) // No cross-signing or key backup on account (set up encryption)
await cli.waitForClientWellKnown(); await cli.waitForClientWellKnown();
@ -315,7 +310,6 @@ export default class DeviceListener {
} }
} }
} }
}
// This needs to be done after awaiting on getUserDeviceInfo() above, so // This needs to be done after awaiting on getUserDeviceInfo() above, so
// we make sure we get the devices after the fetch is done. // we make sure we get the devices after the fetch is done.

View file

@ -11,7 +11,7 @@ import React, { createRef } from "react";
import FileSaver from "file-saver"; import FileSaver from "file-saver";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { AuthDict, CrossSigningKeys, MatrixError, UIAFlow, UIAResponse } from "matrix-js-sdk/src/matrix"; import { AuthDict, CrossSigningKeys, MatrixError, UIAFlow, UIAResponse } from "matrix-js-sdk/src/matrix";
import { CryptoEvent, BackupTrustInfo, GeneratedSecretStorageKey, KeyBackupInfo } from "matrix-js-sdk/src/crypto-api"; import { GeneratedSecretStorageKey } from "matrix-js-sdk/src/crypto-api";
import classNames from "classnames"; import classNames from "classnames";
import CheckmarkIcon from "@vector-im/compound-design-tokens/assets/web/icons/check"; import CheckmarkIcon from "@vector-im/compound-design-tokens/assets/web/icons/check";
@ -25,7 +25,6 @@ import StyledRadioButton from "../../../../components/views/elements/StyledRadio
import AccessibleButton from "../../../../components/views/elements/AccessibleButton"; import AccessibleButton from "../../../../components/views/elements/AccessibleButton";
import DialogButtons from "../../../../components/views/elements/DialogButtons"; import DialogButtons from "../../../../components/views/elements/DialogButtons";
import InlineSpinner from "../../../../components/views/elements/InlineSpinner"; import InlineSpinner from "../../../../components/views/elements/InlineSpinner";
import RestoreKeyBackupDialog from "../../../../components/views/dialogs/security/RestoreKeyBackupDialog";
import { import {
getSecureBackupSetupMethods, getSecureBackupSetupMethods,
isSecureBackupRequired, isSecureBackupRequired,
@ -45,7 +44,6 @@ enum Phase {
Loading = "loading", Loading = "loading",
LoadError = "load_error", LoadError = "load_error",
ChooseKeyPassphrase = "choose_key_passphrase", ChooseKeyPassphrase = "choose_key_passphrase",
Migrate = "migrate",
Passphrase = "passphrase", Passphrase = "passphrase",
PassphraseConfirm = "passphrase_confirm", PassphraseConfirm = "passphrase_confirm",
ShowKey = "show_key", ShowKey = "show_key",
@ -72,24 +70,6 @@ interface IState {
downloaded: boolean; downloaded: boolean;
setPassphrase: boolean; setPassphrase: boolean;
/** Information on the current key backup version, as returned by the server.
*
* `null` could mean any of:
* * we haven't yet requested the data from the server.
* * we were unable to reach the server.
* * the server returned key backup version data we didn't understand or was malformed.
* * there is actually no backup on the server.
*/
backupInfo: KeyBackupInfo | null;
/**
* Information on whether the backup in `backupInfo` is correctly signed, and whether we have the right key to
* decrypt it.
*
* `undefined` if `backupInfo` is null, or if crypto is not enabled in the client.
*/
backupTrustInfo: BackupTrustInfo | undefined;
// does the server offer a UI auth flow with just m.login.password // does the server offer a UI auth flow with just m.login.password
// for /keys/device_signing/upload? // for /keys/device_signing/upload?
canUploadKeysWithPasswordOnly: boolean | null; canUploadKeysWithPasswordOnly: boolean | null;
@ -141,16 +121,17 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
this.queryKeyUploadAuth(); this.queryKeyUploadAuth();
} }
const keyFromCustomisations = ModuleRunner.instance.extensions.cryptoSetup.createSecretStorageKey();
const phase = keyFromCustomisations ? Phase.Loading : Phase.ChooseKeyPassphrase;
this.state = { this.state = {
phase: Phase.Loading, phase,
passPhrase: "", passPhrase: "",
passPhraseValid: false, passPhraseValid: false,
passPhraseConfirm: "", passPhraseConfirm: "",
copied: false, copied: false,
downloaded: false, downloaded: false,
setPassphrase: false, setPassphrase: false,
backupInfo: null,
backupTrustInfo: undefined,
// does the server offer a UI auth flow with just m.login.password // does the server offer a UI auth flow with just m.login.password
// for /keys/device_signing/upload? // for /keys/device_signing/upload?
accountPasswordCorrect: null, accountPasswordCorrect: null,
@ -160,60 +141,15 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
accountPassword, accountPassword,
}; };
cli.on(CryptoEvent.KeyBackupStatus, this.onKeyBackupStatusChange); if (keyFromCustomisations) this.initExtension(keyFromCustomisations);
this.getInitialPhase();
} }
public componentWillUnmount(): void { private initExtension(keyFromCustomisations: Uint8Array): void {
MatrixClientPeg.get()?.removeListener(CryptoEvent.KeyBackupStatus, this.onKeyBackupStatusChange);
}
private getInitialPhase(): void {
const keyFromCustomisations = ModuleRunner.instance.extensions.cryptoSetup.createSecretStorageKey();
if (keyFromCustomisations) {
logger.log("CryptoSetupExtension: Created key via extension, jumping to bootstrap step"); logger.log("CryptoSetupExtension: Created key via extension, jumping to bootstrap step");
this.recoveryKey = { this.recoveryKey = {
privateKey: keyFromCustomisations, privateKey: keyFromCustomisations,
}; };
this.bootstrapSecretStorage(); this.bootstrapSecretStorage();
return;
}
this.fetchBackupInfo();
}
/**
* Attempt to get information on the current backup from the server, and update the state.
*
* Updates {@link IState.backupInfo} and {@link IState.backupTrustInfo}, and picks an appropriate phase for
* {@link IState.phase}.
*
* @returns If the backup data was retrieved successfully, the trust info for the backup. Otherwise, undefined.
*/
private async fetchBackupInfo(): Promise<BackupTrustInfo | undefined> {
try {
const cli = MatrixClientPeg.safeGet();
const backupInfo = await cli.getKeyBackupVersion();
const backupTrustInfo =
// we may not have started crypto yet, in which case we definitely don't trust the backup
backupInfo ? await cli.getCrypto()?.isKeyBackupTrusted(backupInfo) : undefined;
const { forceReset } = this.props;
const phase = backupInfo && !forceReset ? Phase.Migrate : Phase.ChooseKeyPassphrase;
this.setState({
phase,
backupInfo,
backupTrustInfo,
});
return backupTrustInfo;
} catch (e) {
console.error("Error fetching backup data from server", e);
this.setState({ phase: Phase.LoadError });
return undefined;
}
} }
private async queryKeyUploadAuth(): Promise<void> { private async queryKeyUploadAuth(): Promise<void> {
@ -237,10 +173,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
} }
} }
private onKeyBackupStatusChange = (): void => {
if (this.state.phase === Phase.Migrate) this.fetchBackupInfo();
};
private onKeyPassphraseChange = (e: React.ChangeEvent<HTMLInputElement>): void => { private onKeyPassphraseChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
this.setState({ this.setState({
passPhraseKeySelected: e.target.value, passPhraseKeySelected: e.target.value,
@ -265,15 +197,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
} }
}; };
private onMigrateFormSubmit = (e: React.FormEvent): void => {
e.preventDefault();
if (this.state.backupTrustInfo?.trusted) {
this.bootstrapSecretStorage();
} else {
this.restoreBackup();
}
};
private onCopyClick = (): void => { private onCopyClick = (): void => {
const successful = copyNode(this.recoveryKeyNode.current); const successful = copyNode(this.recoveryKeyNode.current);
if (successful) { if (successful) {
@ -340,16 +263,28 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
}; };
private bootstrapSecretStorage = async (): Promise<void> => { private bootstrapSecretStorage = async (): Promise<void> => {
const cli = MatrixClientPeg.safeGet();
const crypto = cli.getCrypto()!;
const { forceReset } = this.props;
let backupInfo;
// First, unless we know we want to do a reset, we see if there is an existing key backup
if (!forceReset) {
try {
this.setState({ phase: Phase.Loading });
backupInfo = await cli.getKeyBackupVersion();
} catch (e) {
logger.error("Error fetching backup data from server", e);
this.setState({ phase: Phase.LoadError });
return;
}
}
this.setState({ this.setState({
phase: Phase.Storing, phase: Phase.Storing,
error: undefined, error: undefined,
}); });
const cli = MatrixClientPeg.safeGet();
const crypto = cli.getCrypto()!;
const { forceReset } = this.props;
try { try {
if (forceReset) { if (forceReset) {
logger.log("Forcing secret storage reset"); logger.log("Forcing secret storage reset");
@ -371,8 +306,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
}); });
await crypto.bootstrapSecretStorage({ await crypto.bootstrapSecretStorage({
createSecretStorageKey: async () => this.recoveryKey!, createSecretStorageKey: async () => this.recoveryKey!,
keyBackupInfo: this.state.backupInfo!, setupNewKeyBackup: !backupInfo,
setupNewKeyBackup: !this.state.backupInfo,
}); });
} }
await initialiseDehydration(true); await initialiseDehydration(true);
@ -381,20 +315,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
phase: Phase.Stored, phase: Phase.Stored,
}); });
} catch (e) { } catch (e) {
if (
this.state.canUploadKeysWithPasswordOnly &&
e instanceof MatrixError &&
e.httpStatus === 401 &&
e.data.flows
) {
this.setState({
accountPassword: "",
accountPasswordCorrect: false,
phase: Phase.Migrate,
});
} else {
this.setState({ error: true }); this.setState({ error: true });
}
logger.error("Error bootstrapping secret storage", e); logger.error("Error bootstrapping secret storage", e);
} }
}; };
@ -403,27 +324,8 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
this.props.onFinished(false); this.props.onFinished(false);
}; };
private restoreBackup = async (): Promise<void> => {
const { finished } = Modal.createDialog(
RestoreKeyBackupDialog,
{
showSummary: false,
},
undefined,
/* priority = */ false,
/* static = */ false,
);
await finished;
const backupTrustInfo = await this.fetchBackupInfo();
if (backupTrustInfo?.trusted && this.state.canUploadKeysWithPasswordOnly && this.state.accountPassword) {
this.bootstrapSecretStorage();
}
};
private onLoadRetryClick = (): void => { private onLoadRetryClick = (): void => {
this.setState({ phase: Phase.Loading }); this.bootstrapSecretStorage();
this.fetchBackupInfo();
}; };
private onShowKeyContinueClick = (): void => { private onShowKeyContinueClick = (): void => {
@ -495,12 +397,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
}); });
}; };
private onAccountPasswordChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
this.setState({
accountPassword: e.target.value,
});
};
private renderOptionKey(): JSX.Element { private renderOptionKey(): JSX.Element {
return ( return (
<StyledRadioButton <StyledRadioButton
@ -565,55 +461,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
); );
} }
private renderPhaseMigrate(): JSX.Element {
let authPrompt;
let nextCaption = _t("action|next");
if (this.state.canUploadKeysWithPasswordOnly) {
authPrompt = (
<div>
<div>{_t("settings|key_backup|setup_secure_backup|requires_password_confirmation")}</div>
<div>
<Field
id="mx_CreateSecretStorageDialog_password"
type="password"
label={_t("common|password")}
value={this.state.accountPassword}
onChange={this.onAccountPasswordChange}
forceValidity={this.state.accountPasswordCorrect === false ? false : undefined}
autoFocus={true}
/>
</div>
</div>
);
} else if (!this.state.backupTrustInfo?.trusted) {
authPrompt = (
<div>
<div>{_t("settings|key_backup|setup_secure_backup|requires_key_restore")}</div>
</div>
);
nextCaption = _t("action|restore");
} else {
authPrompt = <p>{_t("settings|key_backup|setup_secure_backup|requires_server_authentication")}</p>;
}
return (
<form onSubmit={this.onMigrateFormSubmit}>
<p>{_t("settings|key_backup|setup_secure_backup|session_upgrade_description")}</p>
<div>{authPrompt}</div>
<DialogButtons
primaryButton={nextCaption}
onPrimaryButtonClick={this.onMigrateFormSubmit}
hasCancel={false}
primaryDisabled={!!this.state.canUploadKeysWithPasswordOnly && !this.state.accountPassword}
>
<button type="button" className="danger" onClick={this.onCancelClick}>
{_t("action|skip")}
</button>
</DialogButtons>
</form>
);
}
private renderPhasePassPhrase(): JSX.Element { private renderPhasePassPhrase(): JSX.Element {
return ( return (
<form onSubmit={this.onPassPhraseNextClick}> <form onSubmit={this.onPassPhraseNextClick}>
@ -829,8 +676,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
switch (phase) { switch (phase) {
case Phase.ChooseKeyPassphrase: case Phase.ChooseKeyPassphrase:
return _t("encryption|set_up_toast_title"); return _t("encryption|set_up_toast_title");
case Phase.Migrate:
return _t("settings|key_backup|setup_secure_backup|title_upgrade_encryption");
case Phase.Passphrase: case Phase.Passphrase:
return _t("settings|key_backup|setup_secure_backup|title_set_phrase"); return _t("settings|key_backup|setup_secure_backup|title_set_phrase");
case Phase.PassphraseConfirm: case Phase.PassphraseConfirm:
@ -889,9 +734,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
case Phase.ChooseKeyPassphrase: case Phase.ChooseKeyPassphrase:
content = this.renderPhaseChooseKeyPassphrase(); content = this.renderPhaseChooseKeyPassphrase();
break; break;
case Phase.Migrate:
content = this.renderPhaseMigrate();
break;
case Phase.Passphrase: case Phase.Passphrase:
content = this.renderPhasePassPhrase(); content = this.renderPhasePassPhrase();
break; break;

View file

@ -61,6 +61,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
private unmounted = true; private unmounted = true;
private image = createRef<HTMLImageElement>(); private image = createRef<HTMLImageElement>();
private placeholder = createRef<HTMLDivElement>();
private timeout?: number; private timeout?: number;
private sizeWatcher?: string; private sizeWatcher?: string;
@ -453,7 +454,11 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
"mx_MImageBody_placeholder--blurhash": this.props.mxEvent.getContent().info?.[BLURHASH_FIELD], "mx_MImageBody_placeholder--blurhash": this.props.mxEvent.getContent().info?.[BLURHASH_FIELD],
}); });
placeholder = <div className={classes}>{this.getPlaceholder(maxWidth, maxHeight)}</div>; placeholder = (
<div className={classes} ref={this.placeholder}>
{this.getPlaceholder(maxWidth, maxHeight)}
</div>
);
} }
let showPlaceholder = Boolean(placeholder); let showPlaceholder = Boolean(placeholder);
@ -499,8 +504,19 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
if (!this.props.forExport) { if (!this.props.forExport) {
placeholder = ( placeholder = (
<SwitchTransition mode="out-in"> <SwitchTransition mode="out-in">
<CSSTransition classNames="mx_rtg--fade" key={`img-${showPlaceholder}`} timeout={300}> <CSSTransition
{showPlaceholder ? placeholder : <></> /* Transition always expects a child */} classNames="mx_rtg--fade"
key={`img-${showPlaceholder}`}
timeout={300}
nodeRef={this.placeholder}
>
{
showPlaceholder ? (
placeholder
) : (
<div ref={this.placeholder} />
) /* Transition always expects a child */
}
</CSSTransition> </CSSTransition>
</SwitchTransition> </SwitchTransition>
); );

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 React from "react"; import React, { createRef } from "react";
import { Room } from "matrix-js-sdk/src/matrix"; import { Room } from "matrix-js-sdk/src/matrix";
import { CSSTransition } from "react-transition-group"; import { CSSTransition } from "react-transition-group";
@ -61,6 +61,7 @@ const RoomBreadcrumbTile: React.FC<{ room: Room; onClick: (ev: ButtonEvent) => v
export default class RoomBreadcrumbs extends React.PureComponent<IProps, IState> { export default class RoomBreadcrumbs extends React.PureComponent<IProps, IState> {
private isMounted = true; private isMounted = true;
private toolbar = createRef<HTMLDivElement>();
public constructor(props: IProps) { public constructor(props: IProps) {
super(props); super(props);
@ -113,8 +114,18 @@ export default class RoomBreadcrumbs extends React.PureComponent<IProps, IState>
if (tiles.length > 0) { if (tiles.length > 0) {
// NOTE: The CSSTransition timeout MUST match the timeout in our CSS! // NOTE: The CSSTransition timeout MUST match the timeout in our CSS!
return ( return (
<CSSTransition appear={true} in={this.state.doAnimation} timeout={640} classNames="mx_RoomBreadcrumbs"> <CSSTransition
<Toolbar className="mx_RoomBreadcrumbs" aria-label={_t("room_list|breadcrumbs_label")}> appear={true}
in={this.state.doAnimation}
timeout={640}
classNames="mx_RoomBreadcrumbs"
nodeRef={this.toolbar}
>
<Toolbar
className="mx_RoomBreadcrumbs"
aria-label={_t("room_list|breadcrumbs_label")}
ref={this.toolbar}
>
{tiles.slice(this.state.skipFirst ? 1 : 0)} {tiles.slice(this.state.skipFirst ? 1 : 0)}
</Toolbar> </Toolbar>
</CSSTransition> </CSSTransition>

View file

@ -27,7 +27,7 @@ import { useRoomMemberCount, useRoomMembers } from "../../../hooks/useRoomMember
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import { Flex } from "../../utils/Flex"; import { Flex } from "../../utils/Flex";
import { Box } from "../../utils/Box"; import { Box } from "../../utils/Box";
import { getPlatformCallTypeLabel, useRoomCall } from "../../../hooks/room/useRoomCall"; import { getPlatformCallTypeChildren, getPlatformCallTypeLabel, useRoomCall } from "../../../hooks/room/useRoomCall";
import { useRoomThreadNotifications } from "../../../hooks/room/useRoomThreadNotifications"; import { useRoomThreadNotifications } from "../../../hooks/room/useRoomThreadNotifications";
import { useGlobalNotificationState } from "../../../hooks/useGlobalNotificationState"; import { useGlobalNotificationState } from "../../../hooks/useGlobalNotificationState";
import SdkConfig from "../../../SdkConfig"; import SdkConfig from "../../../SdkConfig";
@ -172,6 +172,8 @@ export default function RoomHeader({
key={option} key={option}
label={getPlatformCallTypeLabel(option)} label={getPlatformCallTypeLabel(option)}
aria-label={getPlatformCallTypeLabel(option)} aria-label={getPlatformCallTypeLabel(option)}
children={getPlatformCallTypeChildren(option)}
className="mx_RoomHeader_videoCallOption"
onClick={(ev) => videoCallClick(ev, option)} onClick={(ev) => videoCallClick(ev, option)}
Icon={VideoCallIcon} Icon={VideoCallIcon}
onSelect={() => {} /* Dummy handler since we want the click event.*/} onSelect={() => {} /* Dummy handler since we want the click event.*/}

View file

@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
*/ */
import { Room } from "matrix-js-sdk/src/matrix"; import { Room } from "matrix-js-sdk/src/matrix";
import React, { useCallback, useEffect, useMemo, useState } from "react"; import React, { ReactNode, useCallback, useEffect, useMemo, useState } from "react";
import { CallType } from "matrix-js-sdk/src/webrtc/call"; import { CallType } from "matrix-js-sdk/src/webrtc/call";
import { useFeatureEnabled } from "../useSettings"; import { useFeatureEnabled } from "../useSettings";
@ -35,6 +35,7 @@ import { isVideoRoom } from "../../utils/video-rooms";
import { useGuestAccessInformation } from "./useGuestAccessInformation"; import { useGuestAccessInformation } from "./useGuestAccessInformation";
import SettingsStore from "../../settings/SettingsStore"; import SettingsStore from "../../settings/SettingsStore";
import { UIFeature } from "../../settings/UIFeature"; import { UIFeature } from "../../settings/UIFeature";
import { BetaPill } from "../../components/views/beta/BetaCard";
export enum PlatformCallType { export enum PlatformCallType {
ElementCall, ElementCall,
@ -51,6 +52,14 @@ export const getPlatformCallTypeLabel = (platformCallType: PlatformCallType): st
return _t("voip|legacy_call"); return _t("voip|legacy_call");
} }
}; };
export const getPlatformCallTypeChildren = (platformCallType: PlatformCallType): ReactNode => {
switch (platformCallType) {
case PlatformCallType.ElementCall:
return <BetaPill />;
default:
return null;
}
};
const enum State { const enum State {
NoCall, NoCall,
NoOneHere, NoOneHere,

View file

@ -103,7 +103,6 @@
"report_content": "Report Content", "report_content": "Report Content",
"resend": "Resend", "resend": "Resend",
"reset": "Reset", "reset": "Reset",
"restore": "Restore",
"resume": "Resume", "resume": "Resume",
"retry": "Retry", "retry": "Retry",
"review": "Review", "review": "Review",
@ -930,7 +929,6 @@
}, },
"unable_to_setup_keys_error": "Unable to set up keys", "unable_to_setup_keys_error": "Unable to set up keys",
"unsupported": "This client does not support end-to-end encryption.", "unsupported": "This client does not support end-to-end encryption.",
"upgrade_toast_title": "Encryption upgrade available",
"verification": { "verification": {
"accepting": "Accepting…", "accepting": "Accepting…",
"after_new_login": { "after_new_login": {
@ -2594,18 +2592,13 @@
"pass_phrase_match_failed": "That doesn't match.", "pass_phrase_match_failed": "That doesn't match.",
"pass_phrase_match_success": "That matches!", "pass_phrase_match_success": "That matches!",
"phrase_strong_enough": "Great! This Security Phrase looks strong enough.", "phrase_strong_enough": "Great! This Security Phrase looks strong enough.",
"requires_key_restore": "Restore your key backup to upgrade your encryption",
"requires_password_confirmation": "Enter your account password to confirm the upgrade:",
"requires_server_authentication": "You'll need to authenticate with the server to confirm the upgrade.",
"secret_storage_query_failure": "Unable to query secret storage status", "secret_storage_query_failure": "Unable to query secret storage status",
"security_key_safety_reminder": "Store your Security Key somewhere safe, like a password manager or a safe, as it's used to safeguard your encrypted data.", "security_key_safety_reminder": "Store your Security Key somewhere safe, like a password manager or a safe, as it's used to safeguard your encrypted data.",
"session_upgrade_description": "Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.",
"set_phrase_again": "Go back to set it again.", "set_phrase_again": "Go back to set it again.",
"settings_reminder": "You can also set up Secure Backup & manage your keys in Settings.", "settings_reminder": "You can also set up Secure Backup & manage your keys in Settings.",
"title_confirm_phrase": "Confirm Security Phrase", "title_confirm_phrase": "Confirm Security Phrase",
"title_save_key": "Save your Security Key", "title_save_key": "Save your Security Key",
"title_set_phrase": "Set a Security Phrase", "title_set_phrase": "Set a Security Phrase",
"title_upgrade_encryption": "Upgrade your encryption",
"unable_to_setup": "Unable to set up secret storage", "unable_to_setup": "Unable to set up secret storage",
"use_different_passphrase": "Use a different passphrase?", "use_different_passphrase": "Use a different passphrase?",
"use_phrase_only_you_know": "Use a secret phrase only you know, and optionally save a Security Key to use for backup." "use_phrase_only_you_know": "Use a secret phrase only you know, and optionally save a Security Key to use for backup."

View file

@ -416,16 +416,43 @@ export class StopGapWidgetDriver extends WidgetDriver {
/** /**
* Implements {@link WidgetDriver#sendToDevice} * Implements {@link WidgetDriver#sendToDevice}
* Encrypted to-device events are not supported.
*/ */
public async sendToDevice( public async sendToDevice(
eventType: string, eventType: string,
encrypted: boolean, encrypted: boolean,
contentMap: { [userId: string]: { [deviceId: string]: object } }, contentMap: { [userId: string]: { [deviceId: string]: object } },
): Promise<void> { ): Promise<void> {
if (encrypted) throw new Error("Encrypted to-device events are not supported");
const client = MatrixClientPeg.safeGet(); const client = MatrixClientPeg.safeGet();
if (encrypted) {
const crypto = client.getCrypto();
if (!crypto) throw new Error("E2EE not enabled");
// attempt to re-batch these up into a single request
const invertedContentMap: { [content: string]: { userId: string; deviceId: string }[] } = {};
for (const userId of Object.keys(contentMap)) {
const userContentMap = contentMap[userId];
for (const deviceId of Object.keys(userContentMap)) {
const content = userContentMap[deviceId];
const stringifiedContent = JSON.stringify(content);
invertedContentMap[stringifiedContent] = invertedContentMap[stringifiedContent] || [];
invertedContentMap[stringifiedContent].push({ userId, deviceId });
}
}
await Promise.all(
Object.entries(invertedContentMap).map(async ([stringifiedContent, recipients]) => {
const batch = await crypto.encryptToDeviceMessages(
eventType,
recipients,
JSON.parse(stringifiedContent),
);
await client.queueToDevice(batch);
}),
);
} else {
await client.queueToDevice({ await client.queueToDevice({
eventType, eventType,
batch: Object.entries(contentMap).flatMap(([userId, userContentMap]) => batch: Object.entries(contentMap).flatMap(([userId, userContentMap]) =>
@ -437,6 +464,7 @@ export class StopGapWidgetDriver extends WidgetDriver {
), ),
}); });
} }
}
private pickRooms(roomIds?: (string | Symbols.AnyRoom)[]): Room[] { private pickRooms(roomIds?: (string | Symbols.AnyRoom)[]): Room[] {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();

View file

@ -23,8 +23,6 @@ const getTitle = (kind: Kind): string => {
switch (kind) { switch (kind) {
case Kind.SET_UP_ENCRYPTION: case Kind.SET_UP_ENCRYPTION:
return _t("encryption|set_up_toast_title"); return _t("encryption|set_up_toast_title");
case Kind.UPGRADE_ENCRYPTION:
return _t("encryption|upgrade_toast_title");
case Kind.VERIFY_THIS_SESSION: case Kind.VERIFY_THIS_SESSION:
return _t("encryption|verify_toast_title"); return _t("encryption|verify_toast_title");
} }
@ -33,7 +31,6 @@ const getTitle = (kind: Kind): string => {
const getIcon = (kind: Kind): string => { const getIcon = (kind: Kind): string => {
switch (kind) { switch (kind) {
case Kind.SET_UP_ENCRYPTION: case Kind.SET_UP_ENCRYPTION:
case Kind.UPGRADE_ENCRYPTION:
return "secure_backup"; return "secure_backup";
case Kind.VERIFY_THIS_SESSION: case Kind.VERIFY_THIS_SESSION:
return "verification_warning"; return "verification_warning";
@ -44,8 +41,6 @@ const getSetupCaption = (kind: Kind): string => {
switch (kind) { switch (kind) {
case Kind.SET_UP_ENCRYPTION: case Kind.SET_UP_ENCRYPTION:
return _t("action|continue"); return _t("action|continue");
case Kind.UPGRADE_ENCRYPTION:
return _t("action|upgrade");
case Kind.VERIFY_THIS_SESSION: case Kind.VERIFY_THIS_SESSION:
return _t("action|verify"); return _t("action|verify");
} }
@ -54,7 +49,6 @@ const getSetupCaption = (kind: Kind): string => {
const getDescription = (kind: Kind): string => { const getDescription = (kind: Kind): string => {
switch (kind) { switch (kind) {
case Kind.SET_UP_ENCRYPTION: case Kind.SET_UP_ENCRYPTION:
case Kind.UPGRADE_ENCRYPTION:
return _t("encryption|set_up_toast_description"); return _t("encryption|set_up_toast_description");
case Kind.VERIFY_THIS_SESSION: case Kind.VERIFY_THIS_SESSION:
return _t("encryption|verify_toast_description"); return _t("encryption|verify_toast_description");
@ -63,7 +57,6 @@ const getDescription = (kind: Kind): string => {
export enum Kind { export enum Kind {
SET_UP_ENCRYPTION = "set_up_encryption", SET_UP_ENCRYPTION = "set_up_encryption",
UPGRADE_ENCRYPTION = "upgrade_encryption",
VERIFY_THIS_SESSION = "verify_this_session", VERIFY_THIS_SESSION = "verify_this_session",
} }

View file

@ -161,24 +161,19 @@ export default class HTMLExporter extends Exporter {
<div class="mx_MatrixChat_wrapper" aria-hidden="false"> <div class="mx_MatrixChat_wrapper" aria-hidden="false">
<div class="mx_MatrixChat"> <div class="mx_MatrixChat">
<main class="mx_RoomView"> <main class="mx_RoomView">
<div class="mx_LegacyRoomHeader light-panel"> <div class="mx_Flex mx_RoomHeader light-panel">
<div class="mx_LegacyRoomHeader_wrapper" aria-owns="mx_RightPanel">
<div class="mx_LegacyRoomHeader_avatar">
<div class="mx_DecoratedRoomAvatar">
${roomAvatar} ${roomAvatar}
</div> <div class="mx_RoomHeader_infoWrapper">
</div>
<div class="mx_LegacyRoomHeader_name">
<div <div
dir="auto" dir="auto"
class="mx_LegacyRoomHeader_nametext" class="mx_RoomHeader_info"
title="${safeRoomName}" title="${safeRoomName}"
> >
<span class="mx_RoomHeader_truncated mx_lineClamp">
${safeRoomName} ${safeRoomName}
</span>
</div> </div>
</div> </div>
<div class="mx_LegacyRoomHeader_topic" dir="auto"> ${safeTopic} </div>
</div>
</div> </div>
${previousMessagesLink} ${previousMessagesLink}
<div class="mx_MainSplit"> <div class="mx_MainSplit">

View file

@ -130,6 +130,14 @@ a.mx_reply_anchor:hover {
} }
} }
.mx_RoomHeader {
--mx-flex-display: flex;
--mx-flex-direction: row;
--mx-flex-align: center;
--mx-flex-justify: start;
--mx-flex-gap: var(--cpd-space-3x);
}
.mx_ReplyChain_Export { .mx_ReplyChain_Export {
margin-top: 0; margin-top: 0;
margin-bottom: 5px; margin-bottom: 5px;

View file

@ -127,6 +127,10 @@ export function createTestClient(): MatrixClient {
prepareToEncrypt: jest.fn(), prepareToEncrypt: jest.fn(),
bootstrapCrossSigning: jest.fn(), bootstrapCrossSigning: jest.fn(),
getActiveSessionBackupVersion: jest.fn().mockResolvedValue(null), getActiveSessionBackupVersion: jest.fn().mockResolvedValue(null),
isKeyBackupTrusted: jest.fn().mockResolvedValue({}),
createRecoveryKeyFromPassphrase: jest.fn().mockResolvedValue({}),
bootstrapSecretStorage: jest.fn(),
isDehydrationSupported: jest.fn().mockResolvedValue(false),
}), }),
getPushActionsForEvent: jest.fn(), getPushActionsForEvent: jest.fn(),
@ -270,6 +274,7 @@ export function createTestClient(): MatrixClient {
getOrCreateFilter: jest.fn(), getOrCreateFilter: jest.fn(),
sendStickerMessage: jest.fn(), sendStickerMessage: jest.fn(),
getLocalAliases: jest.fn().mockReturnValue([]), getLocalAliases: jest.fn().mockReturnValue([]),
uploadDeviceSigningKeys: jest.fn(),
} as unknown as MatrixClient; } as unknown as MatrixClient;
client.reEmitter = new ReEmitter(client); client.reEmitter = new ReEmitter(client);

View file

@ -496,6 +496,8 @@ describe("DecryptionFailureTracker", function () {
await createAndTrackEventWithError(DecryptionFailureCode.HISTORICAL_MESSAGE_USER_NOT_JOINED); await createAndTrackEventWithError(DecryptionFailureCode.HISTORICAL_MESSAGE_USER_NOT_JOINED);
await createAndTrackEventWithError(DecryptionFailureCode.MEGOLM_KEY_WITHHELD); await createAndTrackEventWithError(DecryptionFailureCode.MEGOLM_KEY_WITHHELD);
await createAndTrackEventWithError(DecryptionFailureCode.MEGOLM_KEY_WITHHELD_FOR_UNVERIFIED_DEVICE); await createAndTrackEventWithError(DecryptionFailureCode.MEGOLM_KEY_WITHHELD_FOR_UNVERIFIED_DEVICE);
await createAndTrackEventWithError(DecryptionFailureCode.SENDER_IDENTITY_PREVIOUSLY_VERIFIED);
await createAndTrackEventWithError(DecryptionFailureCode.UNSIGNED_SENDER_DEVICE);
await createAndTrackEventWithError(DecryptionFailureCode.UNKNOWN_ERROR); await createAndTrackEventWithError(DecryptionFailureCode.UNKNOWN_ERROR);
// Pretend "now" is Infinity // Pretend "now" is Infinity
@ -510,6 +512,8 @@ describe("DecryptionFailureTracker", function () {
"ExpectedDueToMembership", "ExpectedDueToMembership",
"OlmKeysNotSentError", "OlmKeysNotSentError",
"RoomKeysWithheldForUnverifiedDevice", "RoomKeysWithheldForUnverifiedDevice",
"ExpectedVerificationViolation",
"ExpectedSentByInsecureDevice",
"UnknownError", "UnknownError",
]); ]);
}); });

View file

@ -351,13 +351,13 @@ describe("DeviceListener", () => {
mockCrypto!.getCrossSigningKeyId.mockResolvedValue("abc"); mockCrypto!.getCrossSigningKeyId.mockResolvedValue("abc");
}); });
it("shows upgrade encryption toast when user has a key backup available", async () => { it("shows set up encryption toast when user has a key backup available", async () => {
// non falsy response // non falsy response
mockClient!.getKeyBackupVersion.mockResolvedValue({} as unknown as KeyBackupInfo); mockClient!.getKeyBackupVersion.mockResolvedValue({} as unknown as KeyBackupInfo);
await createAndStart(); await createAndStart();
expect(SetupEncryptionToast.showToast).toHaveBeenCalledWith( expect(SetupEncryptionToast.showToast).toHaveBeenCalledWith(
SetupEncryptionToast.Kind.UPGRADE_ENCRYPTION, SetupEncryptionToast.Kind.SET_UP_ENCRYPTION,
); );
}); });
}); });

View file

@ -10,42 +10,23 @@ import { render, RenderResult, screen } from "jest-matrix-react";
import userEvent from "@testing-library/user-event"; import userEvent from "@testing-library/user-event";
import React from "react"; import React from "react";
import { mocked, MockedObject } from "jest-mock"; import { mocked, MockedObject } from "jest-mock";
import { Crypto, MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix"; import { MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix";
import { defer, IDeferred, sleep } from "matrix-js-sdk/src/utils"; import { sleep } from "matrix-js-sdk/src/utils";
import { BackupTrustInfo, KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
import { import { filterConsole, stubClient } from "../../../../../test-utils";
filterConsole,
flushPromises,
getMockClientWithEventEmitter,
mockClientMethodsCrypto,
mockClientMethodsServer,
} from "../../../../../test-utils";
import CreateSecretStorageDialog from "../../../../../../src/async-components/views/dialogs/security/CreateSecretStorageDialog"; import CreateSecretStorageDialog from "../../../../../../src/async-components/views/dialogs/security/CreateSecretStorageDialog";
import Modal from "../../../../../../src/Modal";
import RestoreKeyBackupDialog from "../../../../../../src/components/views/dialogs/security/RestoreKeyBackupDialog";
describe("CreateSecretStorageDialog", () => { describe("CreateSecretStorageDialog", () => {
let mockClient: MockedObject<MatrixClient>; let mockClient: MockedObject<MatrixClient>;
let mockCrypto: MockedObject<Crypto.CryptoApi>;
beforeEach(() => { beforeEach(() => {
mockClient = getMockClientWithEventEmitter({ mockClient = mocked(stubClient());
...mockClientMethodsServer(), mockClient.uploadDeviceSigningKeys.mockImplementation(async () => {
...mockClientMethodsCrypto(),
uploadDeviceSigningKeys: jest.fn().mockImplementation(async () => {
await sleep(0); // CreateSecretStorageDialog doesn't expect this to resolve immediately await sleep(0); // CreateSecretStorageDialog doesn't expect this to resolve immediately
throw new MatrixError({ flows: [] }); throw new MatrixError({ flows: [] });
}),
});
mockCrypto = mocked(mockClient.getCrypto()!);
Object.assign(mockCrypto, {
isKeyBackupTrusted: jest.fn(),
isDehydrationSupported: jest.fn(() => false),
bootstrapCrossSigning: jest.fn(),
bootstrapSecretStorage: jest.fn(),
}); });
// Mock the clipboard API
document.execCommand = jest.fn().mockReturnValue(true);
}); });
afterEach(() => { afterEach(() => {
@ -59,11 +40,37 @@ describe("CreateSecretStorageDialog", () => {
return render(<CreateSecretStorageDialog onFinished={onFinished} {...props} />); return render(<CreateSecretStorageDialog onFinished={onFinished} {...props} />);
} }
it("shows a loading spinner initially", async () => { it("handles the happy path", async () => {
const { container } = renderComponent(); const result = renderComponent();
expect(screen.getByTestId("spinner")).toBeDefined(); await result.findByText(
expect(container).toMatchSnapshot(); "Safeguard against losing access to encrypted messages & data by backing up encryption keys on your server.",
await flushPromises(); );
expect(result.container).toMatchSnapshot();
await userEvent.click(result.getByRole("button", { name: "Continue" }));
await screen.findByText("Save your Security Key");
expect(result.container).toMatchSnapshot();
// Copy the key to enable the continue button
await userEvent.click(screen.getByRole("button", { name: "Copy" }));
expect(result.queryByText("Copied!")).not.toBeNull();
await userEvent.click(screen.getByRole("button", { name: "Continue" }));
await screen.findByText("Your keys are now being backed up from this device.");
});
it("when there is an error when bootstraping the secret storage, it shows an error", async () => {
jest.spyOn(mockClient.getCrypto()!, "bootstrapSecretStorage").mockRejectedValue(new Error("error"));
renderComponent();
await screen.findByText(
"Safeguard against losing access to encrypted messages & data by backing up encryption keys on your server.",
);
await userEvent.click(screen.getByRole("button", { name: "Continue" }));
await screen.findByText("Save your Security Key");
await userEvent.click(screen.getByRole("button", { name: "Copy" }));
await userEvent.click(screen.getByRole("button", { name: "Continue" }));
await screen.findByText("Unable to set up secret storage");
}); });
describe("when there is an error fetching the backup version", () => { describe("when there is an error fetching the backup version", () => {
@ -75,139 +82,19 @@ describe("CreateSecretStorageDialog", () => {
}); });
const result = renderComponent(); const result = renderComponent();
// We go though the dialog until we have to get the key backup
await userEvent.click(result.getByRole("button", { name: "Continue" }));
await userEvent.click(screen.getByRole("button", { name: "Copy" }));
await userEvent.click(screen.getByRole("button", { name: "Continue" }));
// XXX the error message is... misleading. // XXX the error message is... misleading.
await result.findByText("Unable to query secret storage status"); await screen.findByText("Unable to query secret storage status");
expect(result.container).toMatchSnapshot();
});
});
it("shows 'Generate a Security Key' text if no key backup is present", async () => {
const result = renderComponent();
await flushPromises();
expect(result.container).toMatchSnapshot();
result.getByText("Generate a Security Key");
});
describe("when canUploadKeysWithPasswordOnly", () => {
// spy on Modal.createDialog
let modalSpy: jest.SpyInstance;
// deferred which should be resolved to indicate that the created dialog has completed
let restoreDialogFinishedDefer: IDeferred<[done?: boolean]>;
beforeEach(() => {
mockClient.getKeyBackupVersion.mockResolvedValue({} as KeyBackupInfo);
mockClient.uploadDeviceSigningKeys.mockImplementation(async () => {
await sleep(0);
throw new MatrixError({
flows: [{ stages: ["m.login.password"] }],
});
});
restoreDialogFinishedDefer = defer<[done?: boolean]>();
modalSpy = jest.spyOn(Modal, "createDialog").mockReturnValue({
finished: restoreDialogFinishedDefer.promise,
close: jest.fn(),
});
});
it("prompts for a password and then shows RestoreKeyBackupDialog", async () => {
const result = renderComponent();
await result.findByText(/Enter your account password to confirm the upgrade/);
expect(result.container).toMatchSnapshot(); expect(result.container).toMatchSnapshot();
await userEvent.type(result.getByPlaceholderText("Password"), "my pass"); // Now we can get the backup and we retry
result.getByRole("button", { name: "Next" }).click(); mockClient.getKeyBackupVersion.mockRestore();
await userEvent.click(screen.getByRole("button", { name: "Retry" }));
expect(modalSpy).toHaveBeenCalledWith( await screen.findByText("Your keys are now being backed up from this device.");
RestoreKeyBackupDialog,
{
showSummary: false,
},
undefined,
false,
false,
);
restoreDialogFinishedDefer.resolve([]);
});
it("calls bootstrapSecretStorage once keys are restored if the backup is now trusted", async () => {
const result = renderComponent();
await result.findByText(/Enter your account password to confirm the upgrade/);
expect(result.container).toMatchSnapshot();
await userEvent.type(result.getByPlaceholderText("Password"), "my pass");
result.getByRole("button", { name: "Next" }).click();
expect(modalSpy).toHaveBeenCalled();
// While we restore the key backup, its signature becomes accepted
mockCrypto.isKeyBackupTrusted.mockResolvedValue({ trusted: true } as BackupTrustInfo);
restoreDialogFinishedDefer.resolve([]);
await flushPromises();
// XXX no idea why this is a sensible thing to do. I just work here.
expect(mockCrypto.bootstrapCrossSigning).toHaveBeenCalled();
expect(mockCrypto.bootstrapSecretStorage).toHaveBeenCalled();
await result.findByText("Your keys are now being backed up from this device.");
});
describe("when there is an error fetching the backup version after RestoreKeyBackupDialog", () => {
filterConsole("Error fetching backup data from server");
it("handles the error sensibly", async () => {
const result = renderComponent();
await result.findByText(/Enter your account password to confirm the upgrade/);
expect(result.container).toMatchSnapshot();
await userEvent.type(result.getByPlaceholderText("Password"), "my pass");
result.getByRole("button", { name: "Next" }).click();
expect(modalSpy).toHaveBeenCalled();
mockClient.getKeyBackupVersion.mockImplementation(async () => {
throw new Error("bleh bleh");
});
restoreDialogFinishedDefer.resolve([]);
await result.findByText("Unable to query secret storage status");
});
});
});
describe("when backup is present but not trusted", () => {
beforeEach(() => {
mockClient.getKeyBackupVersion.mockResolvedValue({} as KeyBackupInfo);
});
it("shows migrate text, then 'RestoreKeyBackupDialog' if 'Restore' is clicked", async () => {
const result = renderComponent();
await result.findByText("Restore your key backup to upgrade your encryption");
expect(result.container).toMatchSnapshot();
// before we click "Restore", set up a spy on createDialog
const restoreDialogFinishedDefer = defer<[done?: boolean]>();
const modalSpy = jest.spyOn(Modal, "createDialog").mockReturnValue({
finished: restoreDialogFinishedDefer.promise,
close: jest.fn(),
});
result.getByRole("button", { name: "Restore" }).click();
expect(modalSpy).toHaveBeenCalledWith(
RestoreKeyBackupDialog,
{
showSummary: false,
},
undefined,
false,
false,
);
// simulate RestoreKeyBackupDialog completing, to run that code path
restoreDialogFinishedDefer.resolve([]);
}); });
}); });
}); });

View file

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CreateSecretStorageDialog shows 'Generate a Security Key' text if no key backup is present 1`] = ` exports[`CreateSecretStorageDialog handles the happy path 1`] = `
<div> <div>
<div <div
data-focus-guard="true" data-focus-guard="true"
@ -128,47 +128,7 @@ exports[`CreateSecretStorageDialog shows 'Generate a Security Key' text if no ke
</div> </div>
`; `;
exports[`CreateSecretStorageDialog shows a loading spinner initially 1`] = ` exports[`CreateSecretStorageDialog handles the happy path 2`] = `
<div>
<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_CreateSecretStorageDialog"
data-focus-lock-disabled="false"
role="dialog"
>
<div
class="mx_Dialog_header"
/>
<div>
<div>
<div
class="mx_Spinner"
>
<div
aria-label="Loading…"
class="mx_Spinner_icon"
data-testid="spinner"
role="progressbar"
style="width: 32px; height: 32px;"
/>
</div>
</div>
</div>
</div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
</div>
`;
exports[`CreateSecretStorageDialog when backup is present but not trusted shows migrate text, then 'RestoreKeyBackupDialog' if 'Restore' is clicked 1`] = `
<div> <div>
<div <div
data-focus-guard="true" data-focus-guard="true"
@ -185,105 +145,47 @@ exports[`CreateSecretStorageDialog when backup is present but not trusted shows
class="mx_Dialog_header" class="mx_Dialog_header"
> >
<h1 <h1
class="mx_Heading_h3 mx_Dialog_title" class="mx_Heading_h3 mx_Dialog_title mx_CreateSecretStorageDialog_titleWithIcon mx_CreateSecretStorageDialog_secureBackupTitle"
id="mx_BaseDialog_title" id="mx_BaseDialog_title"
> >
Upgrade your encryption Save your Security Key
</h1> </h1>
</div> </div>
<div> <div>
<form> <div>
<p> <p>
Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users. Store your Security Key somewhere safe, like a password manager or a safe, as it's used to safeguard your encrypted data.
</p> </p>
<div> <div
<div> class="mx_CreateSecretStorageDialog_primaryContainer mx_CreateSecretStorageDialog_recoveryKeyPrimarycontainer"
<div> >
Restore your key backup to upgrade your encryption <div
</div> class="mx_CreateSecretStorageDialog_recoveryKeyContainer"
</div> >
<div
class="mx_CreateSecretStorageDialog_recoveryKey"
>
<code />
</div> </div>
<div <div
class="mx_Dialog_buttons" class="mx_CreateSecretStorageDialog_recoveryKeyButtons"
> >
<span <div
class="mx_Dialog_buttons_row" class="mx_AccessibleButton mx_Dialog_primary mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
role="button"
tabindex="0"
> >
<button Download
class="danger" </div>
type="button" <span>
> or
Skip
</button>
<button
class="mx_Dialog_primary"
data-testid="dialog-primary-button"
type="button"
>
Restore
</button>
</span> </span>
</div>
</form>
</div>
</div>
<div <div
data-focus-guard="true" class="mx_AccessibleButton mx_Dialog_primary mx_CreateSecretStorageDialog_recoveryKeyButtons_copyBtn mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;" role="button"
tabindex="0" tabindex="0"
/>
</div>
`;
exports[`CreateSecretStorageDialog when canUploadKeysWithPasswordOnly calls bootstrapSecretStorage once keys are restored if the backup is now trusted 1`] = `
<div>
<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_CreateSecretStorageDialog"
data-focus-lock-disabled="false"
role="dialog"
> >
<div Copy
class="mx_Dialog_header"
>
<h1
class="mx_Heading_h3 mx_Dialog_title"
id="mx_BaseDialog_title"
>
Upgrade your encryption
</h1>
</div>
<div>
<form>
<p>
Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.
</p>
<div>
<div>
<div>
Enter your account password to confirm the upgrade:
</div>
<div>
<div
class="mx_Field mx_Field_input"
>
<input
id="mx_CreateSecretStorageDialog_password"
label="Password"
placeholder="Password"
type="password"
value=""
/>
<label
for="mx_CreateSecretStorageDialog_password"
>
Password
</label>
</div> </div>
</div> </div>
</div> </div>
@ -294,196 +196,18 @@ exports[`CreateSecretStorageDialog when canUploadKeysWithPasswordOnly calls boot
<span <span
class="mx_Dialog_buttons_row" class="mx_Dialog_buttons_row"
> >
<button
class="danger"
type="button"
>
Skip
</button>
<button <button
class="mx_Dialog_primary" class="mx_Dialog_primary"
data-testid="dialog-primary-button" data-testid="dialog-primary-button"
disabled="" disabled=""
type="button" type="button"
> >
Next Continue
</button> </button>
</span> </span>
</div> </div>
</form>
</div> </div>
</div> </div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
</div>
`;
exports[`CreateSecretStorageDialog when canUploadKeysWithPasswordOnly prompts for a password and then shows RestoreKeyBackupDialog 1`] = `
<div>
<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_CreateSecretStorageDialog"
data-focus-lock-disabled="false"
role="dialog"
>
<div
class="mx_Dialog_header"
>
<h1
class="mx_Heading_h3 mx_Dialog_title"
id="mx_BaseDialog_title"
>
Upgrade your encryption
</h1>
</div>
<div>
<form>
<p>
Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.
</p>
<div>
<div>
<div>
Enter your account password to confirm the upgrade:
</div>
<div>
<div
class="mx_Field mx_Field_input"
>
<input
id="mx_CreateSecretStorageDialog_password"
label="Password"
placeholder="Password"
type="password"
value=""
/>
<label
for="mx_CreateSecretStorageDialog_password"
>
Password
</label>
</div>
</div>
</div>
</div>
<div
class="mx_Dialog_buttons"
>
<span
class="mx_Dialog_buttons_row"
>
<button
class="danger"
type="button"
>
Skip
</button>
<button
class="mx_Dialog_primary"
data-testid="dialog-primary-button"
disabled=""
type="button"
>
Next
</button>
</span>
</div>
</form>
</div>
</div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
</div>
`;
exports[`CreateSecretStorageDialog when canUploadKeysWithPasswordOnly when there is an error fetching the backup version after RestoreKeyBackupDialog handles the error sensibly 1`] = `
<div>
<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_CreateSecretStorageDialog"
data-focus-lock-disabled="false"
role="dialog"
>
<div
class="mx_Dialog_header"
>
<h1
class="mx_Heading_h3 mx_Dialog_title"
id="mx_BaseDialog_title"
>
Upgrade your encryption
</h1>
</div>
<div>
<form>
<p>
Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.
</p>
<div>
<div>
<div>
Enter your account password to confirm the upgrade:
</div>
<div>
<div
class="mx_Field mx_Field_input"
>
<input
id="mx_CreateSecretStorageDialog_password"
label="Password"
placeholder="Password"
type="password"
value=""
/>
<label
for="mx_CreateSecretStorageDialog_password"
>
Password
</label>
</div>
</div>
</div>
</div>
<div
class="mx_Dialog_buttons"
>
<span
class="mx_Dialog_buttons_row"
>
<button
class="danger"
type="button"
>
Skip
</button>
<button
class="mx_Dialog_primary"
data-testid="dialog-primary-button"
disabled=""
type="button"
>
Next
</button>
</span>
</div>
</form>
</div>
</div> </div>
<div <div
data-focus-guard="true" data-focus-guard="true"

View file

@ -201,10 +201,72 @@ describe("StopGapWidgetDriver", () => {
}); });
}); });
it("raises an error if encrypted", async () => { it("sends encrypted messages", async () => {
await expect(driver.sendToDevice("org.example.foo", true, contentMap)).rejects.toThrow( const encryptToDeviceMessages = jest
"Encrypted to-device events are not supported", .fn()
.mockImplementation(
(eventType, recipients: { userId: string; deviceId: string }[], content: object) => ({
eventType: "m.room.encrypted",
batch: recipients.map(({ userId, deviceId }) => ({
userId,
deviceId,
payload: {
eventType,
content,
},
})),
}),
); );
MatrixClientPeg.safeGet().getCrypto()!.encryptToDeviceMessages = encryptToDeviceMessages;
await driver.sendToDevice("org.example.foo", true, {
"@alice:example.org": {
aliceMobile: {
hello: "alice",
},
},
"@bob:example.org": {
bobDesktop: {
hello: "bob",
},
},
});
expect(encryptToDeviceMessages).toHaveBeenCalledWith(
"org.example.foo",
[{ deviceId: "aliceMobile", userId: "@alice:example.org" }],
{
hello: "alice",
},
);
expect(encryptToDeviceMessages).toHaveBeenCalledWith(
"org.example.foo",
[{ deviceId: "bobDesktop", userId: "@bob:example.org" }],
{
hello: "bob",
},
);
expect(client.queueToDevice).toHaveBeenCalledWith({
eventType: "m.room.encrypted",
batch: expect.arrayContaining([
{
deviceId: "aliceMobile",
payload: { content: { hello: "alice" }, eventType: "org.example.foo" },
userId: "@alice:example.org",
},
]),
});
expect(client.queueToDevice).toHaveBeenCalledWith({
eventType: "m.room.encrypted",
batch: expect.arrayContaining([
{
deviceId: "bobDesktop",
payload: { content: { hello: "bob" }, eventType: "org.example.foo" },
userId: "@bob:example.org",
},
]),
});
}); });
}); });

View file

@ -17,24 +17,19 @@ exports[`HTMLExport should export 1`] = `
<div class="mx_MatrixChat_wrapper" aria-hidden="false"> <div class="mx_MatrixChat_wrapper" aria-hidden="false">
<div class="mx_MatrixChat"> <div class="mx_MatrixChat">
<main class="mx_RoomView"> <main class="mx_RoomView">
<div class="mx_LegacyRoomHeader light-panel"> <div class="mx_Flex mx_RoomHeader light-panel">
<div class="mx_LegacyRoomHeader_wrapper" aria-owns="mx_RightPanel">
<div class="mx_LegacyRoomHeader_avatar">
<div class="mx_DecoratedRoomAvatar">
<span role="presentation" title="!myroom:example.org" data-testid="avatar-img" data-type="round" data-color="1" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size:32px">!</span> <span role="presentation" title="!myroom:example.org" data-testid="avatar-img" data-type="round" data-color="1" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size:32px">!</span>
</div> <div class="mx_RoomHeader_infoWrapper">
</div>
<div class="mx_LegacyRoomHeader_name">
<div <div
dir="auto" dir="auto"
class="mx_LegacyRoomHeader_nametext" class="mx_RoomHeader_info"
title="!myroom:example.org" title="!myroom:example.org"
> >
<span class="mx_RoomHeader_truncated mx_lineClamp">
!myroom:example.org !myroom:example.org
</span>
</div> </div>
</div> </div>
<div class="mx_LegacyRoomHeader_topic" dir="auto"> </div>
</div>
</div> </div>
<div class="mx_MainSplit"> <div class="mx_MainSplit">

368
yarn.lock
View file

@ -1421,14 +1421,26 @@
resolved "https://registry.yarnpkg.com/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz#519c1549b0e147759e7825701ecffd25e5819f7b" resolved "https://registry.yarnpkg.com/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz#519c1549b0e147759e7825701ecffd25e5819f7b"
integrity sha512-+nxncfwHM5SgAtrVzgpzJOI1ol0PkumhVo469KCf9lUi21IGcY90G98VuHm9VRrUypmAzawAHO9bs6hqeADaVg== integrity sha512-+nxncfwHM5SgAtrVzgpzJOI1ol0PkumhVo469KCf9lUi21IGcY90G98VuHm9VRrUypmAzawAHO9bs6hqeADaVg==
"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": "@eslint-community/eslint-utils@^4.2.0":
version "4.4.0" version "4.4.0"
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59"
integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==
dependencies: dependencies:
eslint-visitor-keys "^3.3.0" eslint-visitor-keys "^3.3.0"
"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.6.1": "@eslint-community/eslint-utils@^4.4.0":
version "4.4.1"
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz#d1145bf2c20132d6400495d6df4bf59362fd9d56"
integrity sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==
dependencies:
eslint-visitor-keys "^3.4.3"
"@eslint-community/regexpp@^4.10.0":
version "4.12.1"
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0"
integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==
"@eslint-community/regexpp@^4.6.1":
version "4.11.1" version "4.11.1"
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.11.1.tgz#a547badfc719eb3e5f4b556325e542fbe9d7a18f" resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.11.1.tgz#a547badfc719eb3e5f4b556325e542fbe9d7a18f"
integrity sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q== integrity sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==
@ -1504,37 +1516,37 @@
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.8.tgz#21a907684723bbbaa5f0974cf7730bd797eb8e62" resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.8.tgz#21a907684723bbbaa5f0974cf7730bd797eb8e62"
integrity sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig== integrity sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==
"@formatjs/ecma402-abstract@2.2.0": "@formatjs/ecma402-abstract@2.2.1":
version "2.2.0"
resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-2.2.0.tgz#36f5bc0dac4ca77ca429fe44bd95b32d5ccd98dd"
integrity sha512-IpM+ev1E4QLtstniOE29W1rqH9eTdx5hQdNL8pzrflMj/gogfaoONZqL83LUeQScHAvyMbpqP5C9MzNf+fFwhQ==
dependencies:
"@formatjs/fast-memoize" "2.2.1"
"@formatjs/intl-localematcher" "0.5.5"
tslib "^2.7.0"
"@formatjs/fast-memoize@2.2.1":
version "2.2.1" version "2.2.1"
resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-2.2.1.tgz#74575f18c6a789472517995ca9686e7a3f7c0b60" resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-2.2.1.tgz#2e62bc5c22b0e6a5e13bfec6aac15d3d403e1065"
integrity sha512-XS2RcOSyWxmUB7BUjj3mlPH0exsUzlf6QfhhijgI941WaJhVxXQ6mEWkdUFIdnKi3TuTYxRdelsgv3mjieIGIA== integrity sha512-O4ywpkdJybrjFc9zyL8qK5aklleIAi5O4nYhBVJaOFtCkNrnU+lKFeJOFC48zpsZQmR8Aok2V79hGpHnzbmFpg==
dependencies: dependencies:
tslib "^2.7.0" "@formatjs/fast-memoize" "2.2.2"
"@formatjs/intl-localematcher" "0.5.6"
tslib "2"
"@formatjs/intl-localematcher@0.5.5": "@formatjs/fast-memoize@2.2.2":
version "0.5.5" version "2.2.2"
resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.5.5.tgz#b24f100f30658104d5f6db35b0b8d97235298681" resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-2.2.2.tgz#2409ec10f5f7d6c65f4c04e6c2d6cc56fa1e4cef"
integrity sha512-t5tOGMgZ/i5+ALl2/offNqAQq/lfUnKLEw0mXQI4N4bqpedhrSE+fyKLpwnd22sK0dif6AV+ufQcTsKShB9J1g== integrity sha512-mzxZcS0g1pOzwZTslJOBTmLzDXseMLLvnh25ymRilCm8QLMObsQ7x/rj9GNrH0iUhZMlFisVOD6J1n6WQqpKPQ==
dependencies: dependencies:
tslib "^2.7.0" tslib "2"
"@formatjs/intl-localematcher@0.5.6":
version "0.5.6"
resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.5.6.tgz#cd0cd99483673d3196a15b4e2c924cfda7f002f8"
integrity sha512-roz1+Ba5e23AHX6KUAWmLEyTRZegM5YDuxuvkHCyK3RJddf/UXB2f+s7pOMm9ktfPGla0g+mQXOn5vsuYirnaA==
dependencies:
tslib "2"
"@formatjs/intl-segmenter@^11.5.7": "@formatjs/intl-segmenter@^11.5.7":
version "11.5.9" version "11.7.1"
resolved "https://registry.yarnpkg.com/@formatjs/intl-segmenter/-/intl-segmenter-11.5.9.tgz#197aae6991d456cc28827928ed10db0f284c5e2a" resolved "https://registry.yarnpkg.com/@formatjs/intl-segmenter/-/intl-segmenter-11.7.1.tgz#61f0654adb7eb48c4893ac3f882ed93a34580491"
integrity sha512-/vRTJsue3GWk+jvf0VJao9xL+/sIykoMwTBzbq1UWGiyIcUdI4P+02KRbXCURgHovy2Ew7zkV7p8MYjTw8lrMw== integrity sha512-rlJ0C2wq+NSQEFW6Lp3jf0qIhGX6smfz14hMRR/DYCJwQHOR2idW4zuwqcn/CrEQxXTOxAV0f2+N9AdCPeH5QA==
dependencies: dependencies:
"@formatjs/ecma402-abstract" "2.2.0" "@formatjs/ecma402-abstract" "2.2.1"
"@formatjs/intl-localematcher" "0.5.5" "@formatjs/intl-localematcher" "0.5.6"
tslib "^2.7.0" tslib "2"
"@humanwhocodes/config-array@^0.13.0": "@humanwhocodes/config-array@^0.13.0":
version "0.13.0" version "0.13.0"
@ -1919,10 +1931,10 @@
resolved "https://registry.yarnpkg.com/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz#497c67a1cef50d1a2459ba60f315e448d2ad87fe" resolved "https://registry.yarnpkg.com/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz#497c67a1cef50d1a2459ba60f315e448d2ad87fe"
integrity sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q== integrity sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==
"@matrix-org/analytics-events@^0.26.0": "@matrix-org/analytics-events@^0.28.0":
version "0.26.0" version "0.28.0"
resolved "https://registry.yarnpkg.com/@matrix-org/analytics-events/-/analytics-events-0.26.0.tgz#7c8f8f924d8313c87951a0e941640ef8ff78f3d6" resolved "https://registry.yarnpkg.com/@matrix-org/analytics-events/-/analytics-events-0.28.0.tgz#91f501bb25435b9418f785ca850ca858aa6efc76"
integrity sha512-cjKZBejajUG8wPhVygMkBTwTLdEn74luUP6g6RjCUqPR3RYIl3NVi58Zil8CWfRTILb4wVLCPpAvehgXJn1HnQ== integrity sha512-RvvGBYzgJrk2wTRVGk2fWhGM1f69f6nBraRqTiuqlqE2eQd2hZ2onHyRhvhxJeKVC/oNBsvrupObqrrWowXsnQ==
"@matrix-org/emojibase-bindings@^1.3.3": "@matrix-org/emojibase-bindings@^1.3.3":
version "1.3.3" version "1.3.3"
@ -2015,11 +2027,11 @@
webcrypto-core "^1.8.0" webcrypto-core "^1.8.0"
"@playwright/test@^1.40.1": "@playwright/test@^1.40.1":
version "1.48.1" version "1.48.2"
resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.48.1.tgz#343e710fcf2e559529e3ec8d7782e09f325b9396" resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.48.2.tgz#87dd40633f980872283404c8142a65744d3f13d6"
integrity sha512-s9RtWoxkOLmRJdw3oFvhFbs9OJS0BzrLUc8Hf6l2UdCNd1rqeEyD4BhCJkvzeEoD1FsK4mirsWwGerhVmYKtZg== integrity sha512-54w1xCWfXuax7dz4W2M9uw0gDyh+ti/0K/MxcCUxChFh37kkdxPdfZDw5QBbuPUJHr1CiHJ1hXgSs+GgeQc5Zw==
dependencies: dependencies:
playwright "1.48.1" playwright "1.48.2"
"@polka/url@^1.0.0-next.24": "@polka/url@^1.0.0-next.24":
version "1.0.0-next.28" version "1.0.0-next.28"
@ -2317,43 +2329,43 @@
resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8" resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8"
integrity sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g== integrity sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==
"@sentry-internal/browser-utils@8.34.0": "@sentry-internal/browser-utils@8.35.0":
version "8.34.0" version "8.35.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-8.34.0.tgz#36a50d503ad4ad51fce22e80670f8fd6fd195a27" resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-8.35.0.tgz#92602f8dd2bb777af2994eb446cb3cf71bf0cfad"
integrity sha512-4AcYOzPzD1tL5eSRQ/GpKv5enquZf4dMVUez99/Bh3va8qiJrNP55AcM7UzZ7WZLTqKygIYruJTU5Zu2SpEAPQ== integrity sha512-uj9nwERm7HIS13f/Q52hF/NUS5Al8Ma6jkgpfYGeppYvU0uSjPkwMogtqoJQNbOoZg973tV8qUScbcWY616wNA==
dependencies: dependencies:
"@sentry/core" "8.34.0" "@sentry/core" "8.35.0"
"@sentry/types" "8.34.0" "@sentry/types" "8.35.0"
"@sentry/utils" "8.34.0" "@sentry/utils" "8.35.0"
"@sentry-internal/feedback@8.34.0": "@sentry-internal/feedback@8.35.0":
version "8.34.0" version "8.35.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-8.34.0.tgz#ff0db65c36f13665db99e3e22f2032bfdda98731" resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-8.35.0.tgz#b31fb7fbec8ecd9cc683948a0d1af2b87731b0a1"
integrity sha512-aYSM2KPUs0FLPxxbJCFSwCYG70VMzlT04xepD1Y/tTlPPOja/02tSv2tyOdZbv8Uw7xslZs3/8Lhj74oYcTBxw== integrity sha512-7bjSaUhL0bDArozre6EiIhhdWdT/1AWNWBC1Wc5w1IxEi5xF7nvF/FfvjQYrONQzZAI3HRxc45J2qhLUzHBmoQ==
dependencies: dependencies:
"@sentry/core" "8.34.0" "@sentry/core" "8.35.0"
"@sentry/types" "8.34.0" "@sentry/types" "8.35.0"
"@sentry/utils" "8.34.0" "@sentry/utils" "8.35.0"
"@sentry-internal/replay-canvas@8.34.0": "@sentry-internal/replay-canvas@8.35.0":
version "8.34.0" version "8.35.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-8.34.0.tgz#10acadaef74e982dee2b9842a3eb6fec73f032ed" resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-8.35.0.tgz#de7849e0d4212ee37a9225b1fc346188d9b05072"
integrity sha512-x8KhZcCDpbKHqFOykYXiamX6x0LRxv6N1OJHoH+XCrMtiDBZr4Yo30d/MaS6rjmKGMtSRij30v+Uq+YWIgxUrg== integrity sha512-TUrH6Piv19kvHIiRyIuapLdnuwxk/Un/l1WDCQfq7mK9p1Pac0FkQ7Uufjp6zY3lyhDDZQ8qvCS4ioCMibCwQg==
dependencies: dependencies:
"@sentry-internal/replay" "8.34.0" "@sentry-internal/replay" "8.35.0"
"@sentry/core" "8.34.0" "@sentry/core" "8.35.0"
"@sentry/types" "8.34.0" "@sentry/types" "8.35.0"
"@sentry/utils" "8.34.0" "@sentry/utils" "8.35.0"
"@sentry-internal/replay@8.34.0": "@sentry-internal/replay@8.35.0":
version "8.34.0" version "8.35.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-8.34.0.tgz#b730919a174cc5ae8a77f79fb24a5ffb18e44db5" resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-8.35.0.tgz#f71abae95cb492a54b43885386adbc5c639486c7"
integrity sha512-EoMh9NYljNewZK1quY23YILgtNdGgrkzJ9TPsj6jXUG0LZ0Q7N7eFWd0xOEDBvFxrmI3cSXF1i4d1sBb+eyKRw== integrity sha512-3wkW03vXYMyWtTLxl9yrtkV+qxbnKFgfASdoGWhXzfLjycgT6o4/04eb3Gn71q9aXqRwH17ISVQbVswnRqMcmA==
dependencies: dependencies:
"@sentry-internal/browser-utils" "8.34.0" "@sentry-internal/browser-utils" "8.35.0"
"@sentry/core" "8.34.0" "@sentry/core" "8.35.0"
"@sentry/types" "8.34.0" "@sentry/types" "8.35.0"
"@sentry/utils" "8.34.0" "@sentry/utils" "8.35.0"
"@sentry/babel-plugin-component-annotate@2.22.5": "@sentry/babel-plugin-component-annotate@2.22.5":
version "2.22.5" version "2.22.5"
@ -2361,17 +2373,17 @@
integrity sha512-+93qwB9vTX1nj4hD8AMWowXZsZVkvmP9OwTqSh5d4kOeiJ+dZftUk4+FKeKkAX9lvY2reyHV8Gms5mo67c27RQ== integrity sha512-+93qwB9vTX1nj4hD8AMWowXZsZVkvmP9OwTqSh5d4kOeiJ+dZftUk4+FKeKkAX9lvY2reyHV8Gms5mo67c27RQ==
"@sentry/browser@^8.0.0": "@sentry/browser@^8.0.0":
version "8.34.0" version "8.35.0"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-8.34.0.tgz#d2dfc2dbbfa9132d5c3e951f0a4b467805bc4c75" resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-8.35.0.tgz#67820951fd092ef72ee1a4897464bc7c8d317d77"
integrity sha512-3HHG2NXxzHq1lVmDy2uRjYjGNf9NsJsTPlOC70vbQdOb+S49EdH/XMPy+J3ruIoyv6Cu0LwvA6bMOM6rHZOgNQ== integrity sha512-WHfI+NoZzpCsmIvtr6ChOe7yWPLQyMchPnVhY3Z4UeC70bkYNdKcoj/4XZbX3m0D8+71JAsm0mJ9s9OC3Ue6MQ==
dependencies: dependencies:
"@sentry-internal/browser-utils" "8.34.0" "@sentry-internal/browser-utils" "8.35.0"
"@sentry-internal/feedback" "8.34.0" "@sentry-internal/feedback" "8.35.0"
"@sentry-internal/replay" "8.34.0" "@sentry-internal/replay" "8.35.0"
"@sentry-internal/replay-canvas" "8.34.0" "@sentry-internal/replay-canvas" "8.35.0"
"@sentry/core" "8.34.0" "@sentry/core" "8.35.0"
"@sentry/types" "8.34.0" "@sentry/types" "8.35.0"
"@sentry/utils" "8.34.0" "@sentry/utils" "8.35.0"
"@sentry/bundler-plugin-core@2.22.5": "@sentry/bundler-plugin-core@2.22.5":
version "2.22.5" version "2.22.5"
@ -2441,25 +2453,25 @@
"@sentry/cli-win32-i686" "2.37.0" "@sentry/cli-win32-i686" "2.37.0"
"@sentry/cli-win32-x64" "2.37.0" "@sentry/cli-win32-x64" "2.37.0"
"@sentry/core@8.34.0": "@sentry/core@8.35.0":
version "8.34.0" version "8.35.0"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-8.34.0.tgz#92efe1cc8ced843beee636c344e66086d8915563" resolved "https://registry.yarnpkg.com/@sentry/core/-/core-8.35.0.tgz#17090f4d2d3bb983d9d99ecd2d27f4e9e107e0b0"
integrity sha512-adrXCTK/zsg5pJ67lgtZqdqHvyx6etMjQW3P82NgWdj83c8fb+zH+K79Z47pD4zQjX0ou2Ws5nwwi4wJbz4bfA== integrity sha512-Ci0Nmtw5ETWLqQJGY4dyF+iWh7PWKy6k303fCEoEmqj2czDrKJCp7yHBNV0XYbo00prj2ZTbCr6I7albYiyONA==
dependencies: dependencies:
"@sentry/types" "8.34.0" "@sentry/types" "8.35.0"
"@sentry/utils" "8.34.0" "@sentry/utils" "8.35.0"
"@sentry/types@8.34.0": "@sentry/types@8.35.0":
version "8.34.0" version "8.35.0"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-8.34.0.tgz#b02da72d1be67df5246aa9a97ca661ee71569372" resolved "https://registry.yarnpkg.com/@sentry/types/-/types-8.35.0.tgz#535c807800f7e378f61416f30177c0ef81b95012"
integrity sha512-zLRc60CzohGCo6zNsNeQ9JF3SiEeRE4aDCP9fDDdIVCOKovS+mn1rtSip0qd0Vp2fidOu0+2yY0ALCz1A3PJSQ== integrity sha512-AVEZjb16MlYPifiDDvJ19dPQyDn0jlrtC1PHs6ZKO+Rzyz+2EX2BRdszvanqArldexPoU1p5Bn2w81XZNXThBA==
"@sentry/utils@8.34.0": "@sentry/utils@8.35.0":
version "8.34.0" version "8.35.0"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-8.34.0.tgz#5ba543381a9de0ada1196df1fc5cde3b891de41e" resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-8.35.0.tgz#1e099fcbc60040091c79f028a83226c145d588ee"
integrity sha512-W1KoRlFUjprlh3t86DZPFxLfM6mzjRzshVfMY7vRlJFymBelJsnJ3A1lPeBZM9nCraOSiw6GtOWu6k5BAkiGIg== integrity sha512-MdMb6+uXjqND7qIPWhulubpSeHzia6HtxeJa8jYI09OCvIcmNGPydv/Gx/LZBwosfMHrLdTWcFH7Y7aCxrq7cg==
dependencies: dependencies:
"@sentry/types" "8.34.0" "@sentry/types" "8.35.0"
"@sentry/webpack-plugin@^2.7.1": "@sentry/webpack-plugin@^2.7.1":
version "2.22.5" version "2.22.5"
@ -2631,9 +2643,9 @@
pretty-format "^27.0.2" pretty-format "^27.0.2"
"@testing-library/jest-dom@^6.4.8": "@testing-library/jest-dom@^6.4.8":
version "6.6.1" version "6.6.2"
resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.6.1.tgz#835612c9d8c529c835b15bbc1d1a924310c6c73c" resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.6.2.tgz#8186aa9a07263adef9cc5a59a4772db8c31f4a5b"
integrity sha512-mNYIiAuP4yJwV2zBRQCV7PHoQwbb6/8TfMpPcwSUzcSVDJHWOXt6hjNtIN1v5knDmimYnjJxKhsoVd4LVGIO+w== integrity sha512-P6GJD4yqc9jZLbe98j/EkyQDTPgqftohZF5FBkHY5BUERZmcf4HeO2k0XaefEg329ux2p21i1A1DmyQ1kKw2Jw==
dependencies: dependencies:
"@adobe/css-tools" "^4.4.0" "@adobe/css-tools" "^4.4.0"
aria-query "^5.0.0" aria-query "^5.0.0"
@ -3078,10 +3090,10 @@
dependencies: dependencies:
"@types/react" "*" "@types/react" "*"
"@types/react-dom@18.3.0": "@types/react-dom@18.3.1":
version "18.3.0" version "18.3.1"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.3.0.tgz#0cbc818755d87066ab6ca74fbedb2547d74a82b0" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.3.1.tgz#1e4654c08a9cdcfb6594c780ac59b55aad42fe07"
integrity sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg== integrity sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==
dependencies: dependencies:
"@types/react" "*" "@types/react" "*"
@ -3218,29 +3230,29 @@
"@types/yargs-parser" "*" "@types/yargs-parser" "*"
"@typescript-eslint/eslint-plugin@^8.0.0": "@typescript-eslint/eslint-plugin@^8.0.0":
version "8.10.0" version "8.12.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.10.0.tgz#9c8218ed62f9a322df10ded7c34990f014df44f2" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.12.2.tgz#c2ef660bb83fd1432368319312a2581fc92ccac1"
integrity sha512-phuB3hoP7FFKbRXxjl+DRlQDuJqhpOnm5MmtROXyWi3uS/Xg2ZXqiQfcG2BJHiN4QKyzdOJi3NEn/qTnjUlkmQ== integrity sha512-gQxbxM8mcxBwaEmWdtLCIGLfixBMHhQjBqR8sVWNTPpcj45WlYL2IObS/DNMLH1DBP0n8qz+aiiLTGfopPEebw==
dependencies: dependencies:
"@eslint-community/regexpp" "^4.10.0" "@eslint-community/regexpp" "^4.10.0"
"@typescript-eslint/scope-manager" "8.10.0" "@typescript-eslint/scope-manager" "8.12.2"
"@typescript-eslint/type-utils" "8.10.0" "@typescript-eslint/type-utils" "8.12.2"
"@typescript-eslint/utils" "8.10.0" "@typescript-eslint/utils" "8.12.2"
"@typescript-eslint/visitor-keys" "8.10.0" "@typescript-eslint/visitor-keys" "8.12.2"
graphemer "^1.4.0" graphemer "^1.4.0"
ignore "^5.3.1" ignore "^5.3.1"
natural-compare "^1.4.0" natural-compare "^1.4.0"
ts-api-utils "^1.3.0" ts-api-utils "^1.3.0"
"@typescript-eslint/parser@^8.0.0": "@typescript-eslint/parser@^8.0.0":
version "8.10.0" version "8.12.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.10.0.tgz#3cbe7206f5e42835878a74a76da533549f977662" resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.12.2.tgz#2e8173b34e1685e918b2d571c16c906d3747bad2"
integrity sha512-E24l90SxuJhytWJ0pTQydFT46Nk0Z+bsLKo/L8rtQSL93rQ6byd1V/QbDpHUTdLPOMsBCcYXZweADNCfOCmOAg== integrity sha512-MrvlXNfGPLH3Z+r7Tk+Z5moZAc0dzdVjTgUgwsdGweH7lydysQsnSww3nAmsq8blFuRD5VRlAr9YdEFw3e6PBw==
dependencies: dependencies:
"@typescript-eslint/scope-manager" "8.10.0" "@typescript-eslint/scope-manager" "8.12.2"
"@typescript-eslint/types" "8.10.0" "@typescript-eslint/types" "8.12.2"
"@typescript-eslint/typescript-estree" "8.10.0" "@typescript-eslint/typescript-estree" "8.12.2"
"@typescript-eslint/visitor-keys" "8.10.0" "@typescript-eslint/visitor-keys" "8.12.2"
debug "^4.3.4" debug "^4.3.4"
"@typescript-eslint/scope-manager@8.10.0": "@typescript-eslint/scope-manager@8.10.0":
@ -3251,6 +3263,14 @@
"@typescript-eslint/types" "8.10.0" "@typescript-eslint/types" "8.10.0"
"@typescript-eslint/visitor-keys" "8.10.0" "@typescript-eslint/visitor-keys" "8.10.0"
"@typescript-eslint/scope-manager@8.12.2":
version "8.12.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.12.2.tgz#6db0213745e6392c8e90fe9af5915e6da32eb94a"
integrity sha512-gPLpLtrj9aMHOvxJkSbDBmbRuYdtiEbnvO25bCMza3DhMjTQw0u7Y1M+YR5JPbMsXXnSPuCf5hfq0nEkQDL/JQ==
dependencies:
"@typescript-eslint/types" "8.12.2"
"@typescript-eslint/visitor-keys" "8.12.2"
"@typescript-eslint/scope-manager@8.9.0": "@typescript-eslint/scope-manager@8.9.0":
version "8.9.0" version "8.9.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.9.0.tgz#c98fef0c4a82a484e6a1eb610a55b154d14d46f3" resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.9.0.tgz#c98fef0c4a82a484e6a1eb610a55b154d14d46f3"
@ -3259,13 +3279,13 @@
"@typescript-eslint/types" "8.9.0" "@typescript-eslint/types" "8.9.0"
"@typescript-eslint/visitor-keys" "8.9.0" "@typescript-eslint/visitor-keys" "8.9.0"
"@typescript-eslint/type-utils@8.10.0": "@typescript-eslint/type-utils@8.12.2":
version "8.10.0" version "8.12.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.10.0.tgz#99f1d2e21f8c74703e7d9c4a67a87271eaf57597" resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.12.2.tgz#132b0c52d45f6814e6f2e32416c7951ed480b016"
integrity sha512-PCpUOpyQSpxBn230yIcK+LeCQaXuxrgCm2Zk1S+PTIRJsEfU6nJ0TtwyH8pIwPK/vJoA+7TZtzyAJSGBz+s/dg== integrity sha512-bwuU4TAogPI+1q/IJSKuD4shBLc/d2vGcRT588q+jzayQyjVK2X6v/fbR4InY2U2sgf8MEvVCqEWUzYzgBNcGQ==
dependencies: dependencies:
"@typescript-eslint/typescript-estree" "8.10.0" "@typescript-eslint/typescript-estree" "8.12.2"
"@typescript-eslint/utils" "8.10.0" "@typescript-eslint/utils" "8.12.2"
debug "^4.3.4" debug "^4.3.4"
ts-api-utils "^1.3.0" ts-api-utils "^1.3.0"
@ -3274,6 +3294,11 @@
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.10.0.tgz#eb29c4bc2ed23489348c297469c76d28c38fb618" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.10.0.tgz#eb29c4bc2ed23489348c297469c76d28c38fb618"
integrity sha512-k/E48uzsfJCRRbGLapdZgrX52csmWJ2rcowwPvOZ8lwPUv3xW6CcFeJAXgx4uJm+Ge4+a4tFOkdYvSpxhRhg1w== integrity sha512-k/E48uzsfJCRRbGLapdZgrX52csmWJ2rcowwPvOZ8lwPUv3xW6CcFeJAXgx4uJm+Ge4+a4tFOkdYvSpxhRhg1w==
"@typescript-eslint/types@8.12.2":
version "8.12.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.12.2.tgz#8d70098c0e90442495b53d0296acdca6d0f3f73c"
integrity sha512-VwDwMF1SZ7wPBUZwmMdnDJ6sIFk4K4s+ALKLP6aIQsISkPv8jhiw65sAK6SuWODN/ix+m+HgbYDkH+zLjrzvOA==
"@typescript-eslint/types@8.9.0": "@typescript-eslint/types@8.9.0":
version "8.9.0" version "8.9.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.9.0.tgz#b733af07fb340b32e962c6c63b1062aec2dc0fe6" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.9.0.tgz#b733af07fb340b32e962c6c63b1062aec2dc0fe6"
@ -3293,6 +3318,20 @@
semver "^7.6.0" semver "^7.6.0"
ts-api-utils "^1.3.0" ts-api-utils "^1.3.0"
"@typescript-eslint/typescript-estree@8.12.2":
version "8.12.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.12.2.tgz#206df9b1cbff212aaa9401985ef99f04daa84da5"
integrity sha512-mME5MDwGe30Pq9zKPvyduyU86PH7aixwqYR2grTglAdB+AN8xXQ1vFGpYaUSJ5o5P/5znsSBeNcs5g5/2aQwow==
dependencies:
"@typescript-eslint/types" "8.12.2"
"@typescript-eslint/visitor-keys" "8.12.2"
debug "^4.3.4"
fast-glob "^3.3.2"
is-glob "^4.0.3"
minimatch "^9.0.4"
semver "^7.6.0"
ts-api-utils "^1.3.0"
"@typescript-eslint/typescript-estree@8.9.0": "@typescript-eslint/typescript-estree@8.9.0":
version "8.9.0" version "8.9.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.9.0.tgz#1714f167e9063062dc0df49c1d25afcbc7a96199" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.9.0.tgz#1714f167e9063062dc0df49c1d25afcbc7a96199"
@ -3307,15 +3346,15 @@
semver "^7.6.0" semver "^7.6.0"
ts-api-utils "^1.3.0" ts-api-utils "^1.3.0"
"@typescript-eslint/utils@8.10.0", "@typescript-eslint/utils@^8.8.0": "@typescript-eslint/utils@8.12.2":
version "8.10.0" version "8.12.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.10.0.tgz#d78d1ce3ea3d2a88a2593ebfb1c98490131d00bf" resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.12.2.tgz#726cc9f49f5866605bd15bbc1768ffc15637930e"
integrity sha512-Oq4uZ7JFr9d1ZunE/QKy5egcDRXT/FrS2z/nlxzPua2VHFtmMvFNDvpq1m/hq0ra+T52aUezfcjGRIB7vNJF9w== integrity sha512-UTTuDIX3fkfAz6iSVa5rTuSfWIYZ6ATtEocQ/umkRSyC9O919lbZ8dcH7mysshrCdrAM03skJOEYaBugxN+M6A==
dependencies: dependencies:
"@eslint-community/eslint-utils" "^4.4.0" "@eslint-community/eslint-utils" "^4.4.0"
"@typescript-eslint/scope-manager" "8.10.0" "@typescript-eslint/scope-manager" "8.12.2"
"@typescript-eslint/types" "8.10.0" "@typescript-eslint/types" "8.12.2"
"@typescript-eslint/typescript-estree" "8.10.0" "@typescript-eslint/typescript-estree" "8.12.2"
"@typescript-eslint/utils@^6.0.0 || ^7.0.0 || ^8.0.0": "@typescript-eslint/utils@^6.0.0 || ^7.0.0 || ^8.0.0":
version "8.9.0" version "8.9.0"
@ -3327,6 +3366,16 @@
"@typescript-eslint/types" "8.9.0" "@typescript-eslint/types" "8.9.0"
"@typescript-eslint/typescript-estree" "8.9.0" "@typescript-eslint/typescript-estree" "8.9.0"
"@typescript-eslint/utils@^8.8.0":
version "8.10.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.10.0.tgz#d78d1ce3ea3d2a88a2593ebfb1c98490131d00bf"
integrity sha512-Oq4uZ7JFr9d1ZunE/QKy5egcDRXT/FrS2z/nlxzPua2VHFtmMvFNDvpq1m/hq0ra+T52aUezfcjGRIB7vNJF9w==
dependencies:
"@eslint-community/eslint-utils" "^4.4.0"
"@typescript-eslint/scope-manager" "8.10.0"
"@typescript-eslint/types" "8.10.0"
"@typescript-eslint/typescript-estree" "8.10.0"
"@typescript-eslint/visitor-keys@8.10.0": "@typescript-eslint/visitor-keys@8.10.0":
version "8.10.0" version "8.10.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.10.0.tgz#7ce4c0c3b82140415c9cd9babe09e0000b4e9979" resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.10.0.tgz#7ce4c0c3b82140415c9cd9babe09e0000b4e9979"
@ -3335,6 +3384,14 @@
"@typescript-eslint/types" "8.10.0" "@typescript-eslint/types" "8.10.0"
eslint-visitor-keys "^3.4.3" eslint-visitor-keys "^3.4.3"
"@typescript-eslint/visitor-keys@8.12.2":
version "8.12.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.12.2.tgz#94d7410f78eb6d134b9fcabaf1eeedb910ba8c38"
integrity sha512-PChz8UaKQAVNHghsHcPyx1OMHoFRUEA7rJSK/mDhdq85bk+PLsUHUBqTQTFt18VJZbmxBovM65fezlheQRsSDA==
dependencies:
"@typescript-eslint/types" "8.12.2"
eslint-visitor-keys "^3.4.3"
"@typescript-eslint/visitor-keys@8.9.0": "@typescript-eslint/visitor-keys@8.9.0":
version "8.9.0" version "8.9.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.9.0.tgz#5f11f4d9db913f37da42776893ffe0dd1ae78f78" resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.9.0.tgz#5f11f4d9db913f37da42776893ffe0dd1ae78f78"
@ -3349,9 +3406,9 @@
integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==
"@vector-im/compound-design-tokens@^1.8.0": "@vector-im/compound-design-tokens@^1.8.0":
version "1.8.0" version "1.9.0"
resolved "https://registry.yarnpkg.com/@vector-im/compound-design-tokens/-/compound-design-tokens-1.8.0.tgz#bc844cb6b9842c1eb8e5c42f5cedcaf51a49b86f" resolved "https://registry.yarnpkg.com/@vector-im/compound-design-tokens/-/compound-design-tokens-1.9.0.tgz#a3788845110fdcafb1720f633cb060b86f9a1592"
integrity sha512-PtQMG7kDzwtjw/fLKD63uWP5rJ8cgWc/aXarfEzxYUf9ceWxBajnYOBI2jDqtE3WIUe9uTVBzNEvmOBG/VIgTA== integrity sha512-09eIRJSiWtAqK605eIu+PfT1ugu7u13gkvfxvfN7kjJMHQOzHSvDxmwADmfIzlV7oBQ8M+5D4KSKHNskvMxWsA==
"@vector-im/compound-web@^7.1.0": "@vector-im/compound-web@^7.1.0":
version "7.1.0" version "7.1.0"
@ -3884,10 +3941,10 @@ await-lock@^2.1.0:
resolved "https://registry.yarnpkg.com/await-lock/-/await-lock-2.2.2.tgz#a95a9b269bfd2f69d22b17a321686f551152bcef" resolved "https://registry.yarnpkg.com/await-lock/-/await-lock-2.2.2.tgz#a95a9b269bfd2f69d22b17a321686f551152bcef"
integrity sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw== integrity sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw==
axe-core@4.10.0: axe-core@4.10.2:
version "4.10.0" version "4.10.2"
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.10.0.tgz#d9e56ab0147278272739a000880196cdfe113b59" resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.10.2.tgz#85228e3e1d8b8532a27659b332e39b7fa0e022df"
integrity sha512-Mr2ZakwQ7XUAjp7pAwQWRhhK8mQQ6JAaNWSjmjxil0R8BPioMtQsTLOolGYkji1rcL++3dCqZA3zWqpT+9Ew6g== integrity sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==
axe-core@^4.10.0, axe-core@~4.10.0: axe-core@^4.10.0, axe-core@~4.10.0:
version "4.10.1" version "4.10.1"
@ -5626,10 +5683,10 @@ eslint-plugin-matrix-org@^2.0.2:
resolved "https://registry.yarnpkg.com/eslint-plugin-matrix-org/-/eslint-plugin-matrix-org-2.0.2.tgz#95b86b0f16704ab19740f7c3c62eae69e20365e6" resolved "https://registry.yarnpkg.com/eslint-plugin-matrix-org/-/eslint-plugin-matrix-org-2.0.2.tgz#95b86b0f16704ab19740f7c3c62eae69e20365e6"
integrity sha512-cQy5Rjeq6uyu1mLXlPZwEJdyM0NmclrnEz68y792FSuuxzMyJNNYLGDQ5CkYW8H+PrD825HUFZ34pNXnjMOzOw== integrity sha512-cQy5Rjeq6uyu1mLXlPZwEJdyM0NmclrnEz68y792FSuuxzMyJNNYLGDQ5CkYW8H+PrD825HUFZ34pNXnjMOzOw==
eslint-plugin-react-hooks@^4.3.0: eslint-plugin-react-hooks@^5.0.0:
version "4.6.2" version "5.0.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz#c829eb06c0e6f484b3fbb85a97e57784f328c596" resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.0.0.tgz#72e2eefbac4b694f5324154619fee44f5f60f101"
integrity sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ== integrity sha512-hIOwI+5hYGpJEc4uPRmz2ulCjAGD/N13Lukkh8cLV0i2IRk/bdZDYjgLVHj+U9Z704kLIdIO6iueGvxNur0sgw==
eslint-plugin-react@^7.28.0: eslint-plugin-react@^7.28.0:
version "7.37.1" version "7.37.1"
@ -8320,10 +8377,10 @@ mdn-data@2.10.0:
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.10.0.tgz#701da407f8fbc7a42aa0ba0c149ec897daef8986" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.10.0.tgz#701da407f8fbc7a42aa0ba0c149ec897daef8986"
integrity sha512-qq7C3EtK3yJXMwz1zAab65pjl+UhohqMOctTgcqjLOWABqmwj+me02LSsCuEUxnst9X1lCBpoE0WArGKgdGDzw== integrity sha512-qq7C3EtK3yJXMwz1zAab65pjl+UhohqMOctTgcqjLOWABqmwj+me02LSsCuEUxnst9X1lCBpoE0WArGKgdGDzw==
mdn-data@^2.0.30: mdn-data@^2.11.1:
version "2.11.1" version "2.12.1"
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.11.1.tgz#bb973c4272a446005444259fd8227d7f727dc047" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.12.1.tgz#10cb462215c13d95c92ff60d0fb3becac1bbb924"
integrity sha512-Hdx3wmyqPFrhd6YHVuSkUK2eIGAcxR0xlndcgZqjA68yMJTbfXrjJwbgsBOsNjI7LnBIVUQnmyMVSdi/ob0GpQ== integrity sha512-rsfnCbOHjqrhWxwt5/wtSLzpoKTzW7OXdT5lLOIH1OTYhWu9rRJveGq0sKvDZODABH7RX+uoR+DYcpFnq4Tf6Q==
mdurl@^1.0.1, mdurl@~1.0.1: mdurl@^1.0.1, mdurl@~1.0.1:
version "1.0.1" version "1.0.1"
@ -9088,17 +9145,17 @@ pkg-dir@^7.0.0:
dependencies: dependencies:
find-up "^6.3.0" find-up "^6.3.0"
playwright-core@1.48.1, playwright-core@^1.45.1: playwright-core@1.48.2, playwright-core@^1.45.1:
version "1.48.1" version "1.48.2"
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.48.1.tgz#5fe28fb9a9326dae88d4608c35e819163cceeb23" resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.48.2.tgz#cd76ed8af61690edef5c05c64721c26a8db2f3d7"
integrity sha512-Yw/t4VAFX/bBr1OzwCuOMZkY1Cnb4z/doAFSwf4huqAGWmf9eMNjmK7NiOljCdLmxeRYcGPPmcDgU0zOlzP0YA== integrity sha512-sjjw+qrLFlriJo64du+EK0kJgZzoQPsabGF4lBvsid+3CNIZIYLgnMj9V6JY5VhM2Peh20DJWIVpVljLLnlawA==
playwright@1.48.1: playwright@1.48.2:
version "1.48.1" version "1.48.2"
resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.48.1.tgz#2a920cfbec4572c84789e757d8b044baaed49435" resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.48.2.tgz#fca45ae8abdc34835c715718072aaff7e305167e"
integrity sha512-j8CiHW/V6HxmbntOfyB4+T/uk08tBy6ph0MpBXwuoofkSnLmlfdYNNkFTYD6ofzzlSqLA1fwH4vwvVFvJgLN0w== integrity sha512-NjYvYgp4BPmiwfe31j4gHLa3J7bD2WiBz8Lk2RoSsmX38SVIARZ18VYjxLjAcDsAhA+F4iSEXTSGgjua0rrlgQ==
dependencies: dependencies:
playwright-core "1.48.1" playwright-core "1.48.2"
optionalDependencies: optionalDependencies:
fsevents "2.3.2" fsevents "2.3.2"
@ -11019,14 +11076,14 @@ 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.8.0" version "6.8.1"
resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-6.8.0.tgz#bf3992bdb708b78b115a039f5be97dd7482133fa" resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-6.8.1.tgz#b6554d93f2ea0bf37ffdcae571bbfaa35d79ba8a"
integrity sha512-6gjsCZ30UUF6ivjZB2Z+1lb6k0+JFa1uR2MgGbYu76xRjEfvNTpSS1nQim1Gom1ijFF9GzauOiq1Kr7zKptQOw== integrity sha512-al+5eRb72bKrFyVAY+CLWKUMX+k+wsDCgyooSfhISJA2exqnJq1PX1iIIpdrvhu3GtJgNJZl9/BIW6EVSMCxdg==
dependencies: dependencies:
css-tree "^3.0.0" css-tree "^3.0.0"
is-plain-object "^5.0.0" is-plain-object "^5.0.0"
known-css-properties "^0.34.0" known-css-properties "^0.34.0"
mdn-data "^2.0.30" mdn-data "^2.11.1"
postcss-media-query-parser "^0.2.3" postcss-media-query-parser "^0.2.3"
postcss-resolve-nested-selector "^0.1.6" postcss-resolve-nested-selector "^0.1.6"
postcss-selector-parser "^6.1.2" postcss-selector-parser "^6.1.2"
@ -11388,7 +11445,7 @@ tsconfig-paths@^3.15.0:
minimist "^1.2.6" minimist "^1.2.6"
strip-bom "^3.0.0" strip-bom "^3.0.0"
tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.4.0, tslib@^2.6.1, tslib@^2.6.2, tslib@^2.7.0: tslib@2, tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.4.0, tslib@^2.6.1, tslib@^2.6.2, tslib@^2.7.0:
version "2.8.0" version "2.8.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.0.tgz#d124c86c3c05a40a91e6fdea4021bd31d377971b" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.0.tgz#d124c86c3c05a40a91e6fdea4021bd31d377971b"
integrity sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA== integrity sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==
@ -11644,7 +11701,7 @@ utils-merge@1.0.1:
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
uuid@10, uuid@^10.0.0: uuid@10:
version "10.0.0" version "10.0.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-10.0.0.tgz#5a95aa454e6e002725c79055fd42aaba30ca6294" resolved "https://registry.yarnpkg.com/uuid/-/uuid-10.0.0.tgz#5a95aa454e6e002725c79055fd42aaba30ca6294"
integrity sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ== integrity sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==
@ -11654,6 +11711,11 @@ uuid@8.3.2, uuid@^8.3.2:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
uuid@^11.0.0:
version "11.0.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-11.0.2.tgz#a8d68ba7347d051e7ea716cc8dcbbab634d66875"
integrity sha512-14FfcOJmqdjbBPdDjFQyk/SdT4NySW4eM0zcG+HqbHP5jzuH56xO3J1DGhgs/cEMCfwYi3HQI1gnTO62iaG+tQ==
uuid@^9.0.0: uuid@^9.0.0:
version "9.0.1" version "9.0.1"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30"