Skip to content

Commit 1519447

Browse files
committed
Merge remote-tracking branch 'upstream/master' into synodim
2 parents 8a8e985 + 324dd5a commit 1519447

File tree

272 files changed

+5871
-2612
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

272 files changed

+5871
-2612
lines changed

.github/workflows/triage-stale.yml

Lines changed: 0 additions & 27 deletions
This file was deleted.

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,28 @@
1+
Changes in [1.11.95](https://github.com/element-hq/element-web/releases/tag/v1.11.95) (2025-03-11)
2+
==================================================================================================
3+
## ✨ Features
4+
5+
* Room List Store: Filter rooms by active space ([#29399](https://github.com/element-hq/element-web/pull/29399)). Contributed by @MidhunSureshR.
6+
* Room List - Update the room list store on actions from the dispatcher ([#29397](https://github.com/element-hq/element-web/pull/29397)). Contributed by @MidhunSureshR.
7+
* Room List - Implement a minimal view model ([#29357](https://github.com/element-hq/element-web/pull/29357)). Contributed by @MidhunSureshR.
8+
* New room list: add space menu in room header ([#29352](https://github.com/element-hq/element-web/pull/29352)). Contributed by @florianduros.
9+
* Room List - Store sorted rooms in skip list ([#29345](https://github.com/element-hq/element-web/pull/29345)). Contributed by @MidhunSureshR.
10+
* New room list: add dial to search section ([#29359](https://github.com/element-hq/element-web/pull/29359)). Contributed by @florianduros.
11+
* New room list: add compose menu for spaces in header ([#29347](https://github.com/element-hq/element-web/pull/29347)). Contributed by @florianduros.
12+
* Use EditInPlace control for Identity Server picker to improve a11y ([#29280](https://github.com/element-hq/element-web/pull/29280)). Contributed by @Half-Shot.
13+
* First step to add header to new room list ([#29320](https://github.com/element-hq/element-web/pull/29320)). Contributed by @florianduros.
14+
* Add Windows 64-bit arm link and remove 32-bit link on compatibility page ([#29312](https://github.com/element-hq/element-web/pull/29312)). Contributed by @t3chguy.
15+
* Honour the backup disable flag from Element X ([#29290](https://github.com/element-hq/element-web/pull/29290)). Contributed by @dbkr.
16+
17+
## 🐛 Bug Fixes
18+
19+
* Fix edited code block width ([#29394](https://github.com/element-hq/element-web/pull/29394)). Contributed by @florianduros.
20+
* new room list: keep space name in one line in header ([#29369](https://github.com/element-hq/element-web/pull/29369)). Contributed by @florianduros.
21+
* Dismiss "Key storage out of sync" toast when secrets received ([#29348](https://github.com/element-hq/element-web/pull/29348)). Contributed by @richvdh.
22+
* Minor CSS fixes for the new room list ([#29334](https://github.com/element-hq/element-web/pull/29334)). Contributed by @florianduros.
23+
* Add padding to room header icon ([#29271](https://github.com/element-hq/element-web/pull/29271)). Contributed by @langleyd.
24+
25+
126
Changes in [1.11.94](https://github.com/element-hq/element-web/releases/tag/v1.11.94) (2025-02-27)
227
==================================================================================================
328
## 🐛 Bug Fixes

Dockerfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,5 @@ USER nginx
4646

4747
# HTTP listen port
4848
ENV ELEMENT_WEB_PORT=80
49+
50+
HEALTHCHECK --start-period=5s CMD wget --retry-connrefused --tries=5 -q --wait=3 --spider http://localhost:$ELEMENT_WEB_PORT/config.json

babel.config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,7 @@ module.exports = {
3131

3232
"@babel/plugin-syntax-dynamic-import",
3333
"@babel/plugin-transform-runtime",
34+
["@babel/plugin-proposal-decorators", { version: "2023-11" }], // only needed by the js-sdk
35+
"@babel/plugin-transform-class-static-block", // only needed by the js-sdk for decorators
3436
],
3537
};

docs/MVVM.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# MVVM
2+
3+
General description of the pattern can be found [here](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel). But the gist of it is that you divide your code into three sections:
4+
5+
1. Model: This is where the business logic and data resides.
6+
2. View Model: This code exists to provide the logic necessary for the UI. It directly uses the Model code.
7+
3. View: This is the UI code itself and depends on the view model.
8+
9+
If you do MVVM right, your view should be dumb i.e it gets data from the view model and merely displays it.
10+
11+
### Practical guidelines for MVVM in element-web
12+
13+
#### Model
14+
15+
This is anywhere your data or business logic comes from. If your view model is accessing something simple exposed from `matrix-js-sdk`, then the sdk is your model. If you're using something more high level in element-web to get your data/logic (eg: `MemberListStore`), then that becomes your model.
16+
17+
#### View Model
18+
19+
1. View model is always a custom react hook named like `useFooViewModel()`.
20+
2. The return type of your view model (known as view state) must be defined as a typescript interface:
21+
```ts
22+
inteface FooViewState {
23+
somethingUseful: string;
24+
somethingElse: BarType;
25+
update: () => Promise<void>
26+
...
27+
}
28+
```
29+
3. Any react state that your UI needs must be in the view model.
30+
31+
#### View
32+
33+
1. Views are simple react components (eg: `FooView`).
34+
2. Views usually start by calling the view model hook, eg:
35+
```tsx
36+
const FooView: React.FC<IProps> = (props: IProps) => {
37+
const vm = useFooViewModel();
38+
....
39+
return(
40+
<div>
41+
{vm.somethingUseful}
42+
</div>
43+
);
44+
}
45+
```
46+
3. Views are also allowed to accept the view model as a prop, eg:
47+
```tsx
48+
const FooView: React.FC<IProps> = ({ vm }: IProps) => {
49+
....
50+
return(
51+
<div>
52+
{vm.somethingUseful}
53+
</div>
54+
);
55+
}
56+
```
57+
4. Multiple views can share the same view model if necessary.
58+
59+
### Benefits
60+
61+
1. MVVM forces a separation of concern i.e we will no longer have large react components that have a lot of state and rendering code mixed together. This improves code readability and makes it easier to introduce changes.
62+
2. Introduces the possibility of code reuse. You can reuse an old view model with a new view or vice versa.
63+
3. Adding to the point above, in future you could import element-web view models to your project and supply your own views thus creating something similar to the [hydrogen sdk](https://github.com/element-hq/hydrogen-web/blob/master/doc/SDK.md).
64+
65+
### Example
66+
67+
We started experimenting with MVVM in the redesigned memberlist, you can see the code [here](https://github.com/vector-im/element-web/blob/develop/src/components/views/rooms/MemberList/MemberListView.tsx).

docs/config.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -163,14 +163,14 @@ These two options describe the various availability for the application. When th
163163
such as trying to get the user to use an Android app or the desktop app for encrypted search, the config options will be looked
164164
at to see if the link should be to somewhere else.
165165

166-
Starting with `desktop_builds`, the following subproperties are available:
166+
Starting with `desktop_builds`, the following sub-properties are available:
167167

168168
1. `available`: Required. When `true`, the desktop app can be downloaded from somewhere.
169169
2. `logo`: Required. A URL to a logo (SVG), intended to be shown at 24x24 pixels.
170170
3. `url`: Required. The download URL for the app. This is used as a hyperlink.
171171
4. `url_macos`: Optional. Direct link to download macOS desktop app.
172-
5. `url_win32`: Optional. Direct link to download Windows 32-bit desktop app.
173-
6. `url_win64`: Optional. Direct link to download Windows 64-bit desktop app.
172+
5. `url_win64`: Optional. Direct link to download Windows x86 64-bit desktop app.
173+
6. `url_win64arm`: Optional. Direct link to download Windows ARM 64-bit desktop app.
174174
7. `url_linux`: Optional. Direct link to download Linux desktop app.
175175

176176
When `desktop_builds` is not specified at all, the app will assume desktop downloads are available from https://element.io

package.json

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "element-web",
3-
"version": "1.11.94",
3+
"version": "1.11.95",
44
"description": "Element: the future of secure communication",
55
"author": "New Vector Ltd.",
66
"repository": {
@@ -74,7 +74,7 @@
7474
"@types/react-dom": "18.3.5",
7575
"oidc-client-ts": "3.1.0",
7676
"jwt-decode": "4.0.0",
77-
"caniuse-lite": "1.0.30001697",
77+
"caniuse-lite": "1.0.30001699",
7878
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0",
7979
"wrap-ansi": "npm:wrap-ansi@^7.0.0"
8080
},
@@ -88,11 +88,11 @@
8888
"@matrix-org/emojibase-bindings": "^1.3.4",
8989
"@matrix-org/react-sdk-module-api": "^2.4.0",
9090
"@matrix-org/spec": "^1.7.0",
91-
"@sentry/browser": "^8.0.0",
91+
"@sentry/browser": "^9.0.0",
9292
"@types/png-chunks-extract": "^1.0.2",
9393
"@types/react-virtualized": "^9.21.30",
94-
"@vector-im/compound-design-tokens": "^3.0.0",
95-
"@vector-im/compound-web": "^7.6.1",
94+
"@vector-im/compound-design-tokens": "^4.0.0",
95+
"@vector-im/compound-web": "^7.6.4",
9696
"@vector-im/matrix-wysiwyg": "2.38.0",
9797
"@zxcvbn-ts/core": "^3.0.4",
9898
"@zxcvbn-ts/language-common": "^3.0.4",
@@ -128,7 +128,7 @@
128128
"maplibre-gl": "^5.0.0",
129129
"matrix-encrypt-attachment": "^1.0.3",
130130
"matrix-events-sdk": "0.0.1",
131-
"matrix-js-sdk": "37.0.0",
131+
"matrix-js-sdk": "37.1.0",
132132
"matrix-widget-api": "^1.10.0",
133133
"memoize-one": "^6.0.0",
134134
"mime": "^4.0.4",
@@ -162,9 +162,11 @@
162162
"@babel/core": "^7.12.10",
163163
"@babel/eslint-parser": "^7.12.10",
164164
"@babel/eslint-plugin": "^7.12.10",
165+
"@babel/plugin-proposal-decorators": "^7.25.9",
165166
"@babel/plugin-proposal-export-default-from": "^7.12.1",
166167
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
167168
"@babel/plugin-transform-class-properties": "^7.12.1",
169+
"@babel/plugin-transform-class-static-block": "^7.26.0",
168170
"@babel/plugin-transform-logical-assignment-operators": "^7.20.7",
169171
"@babel/plugin-transform-nullish-coalescing-operator": "^7.12.1",
170172
"@babel/plugin-transform-numeric-separator": "^7.12.7",
@@ -274,7 +276,7 @@
274276
"postcss-preset-env": "^10.0.0",
275277
"postcss-scss": "^4.0.4",
276278
"postcss-simple-vars": "^7.0.1",
277-
"prettier": "3.4.2",
279+
"prettier": "3.5.1",
278280
"process": "^0.11.10",
279281
"raw-loader": "^4.0.2",
280282
"rimraf": "^6.0.0",

playwright/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM mcr.microsoft.com/playwright:v1.49.1-noble
1+
FROM mcr.microsoft.com/playwright:v1.50.1-noble
22

33
WORKDIR /work
44

playwright/e2e/crypto/crypto.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const checkDMRoom = async (page: Page) => {
2828
};
2929

3030
const startDMWithBob = async (page: Page, bob: Bot) => {
31-
await page.locator(".mx_RoomList").getByRole("button", { name: "Start chat" }).click();
31+
await page.locator(".mx_LegacyRoomList").getByRole("button", { name: "Start chat" }).click();
3232
await page.getByTestId("invite-dialog-input").fill(bob.credentials.userId);
3333
await page.locator(".mx_InviteDialog_tile_nameStack_name").getByText("Bob").click();
3434
await expect(

playwright/e2e/crypto/dehydration.spec.ts

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,6 @@ test.use({
2222
msc3814_enabled: true,
2323
},
2424
},
25-
config: async ({ config, context }, use) => {
26-
const wellKnown = {
27-
...config.default_server_config,
28-
"org.matrix.msc3814": true,
29-
};
30-
31-
await context.route("https://localhost/.well-known/matrix/client", async (route) => {
32-
await route.fulfill({ json: wellKnown });
33-
});
34-
35-
await use(config);
36-
},
3725
});
3826

3927
test.describe("Dehydration", () => {
@@ -116,6 +104,40 @@ test.describe("Dehydration", () => {
116104
expect(dehydratedDeviceIds.length).toBe(1);
117105
expect(dehydratedDeviceIds[0]).not.toEqual(initialDehydratedDeviceIds[0]);
118106
});
107+
108+
test("'Reset cryptographic identity' removes dehydrated device", async ({ page, homeserver, app, credentials }) => {
109+
await logIntoElement(page, credentials);
110+
111+
// Create a dehydrated device by setting up recovery (see "'Set up
112+
// recovery' creates dehydrated device" test above)
113+
const settingsDialogLocator = await app.settings.openUserSettings("Encryption");
114+
await settingsDialogLocator.getByRole("button", { name: "Set up recovery" }).click();
115+
116+
// First it displays an informative panel about the recovery key
117+
await expect(settingsDialogLocator.getByRole("heading", { name: "Set up recovery" })).toBeVisible();
118+
await settingsDialogLocator.getByRole("button", { name: "Continue" }).click();
119+
120+
// Next, it displays the new recovery key. We click on the copy button.
121+
await expect(settingsDialogLocator.getByText("Save your recovery key somewhere safe")).toBeVisible();
122+
await settingsDialogLocator.getByRole("button", { name: "Copy" }).click();
123+
const recoveryKey = await app.getClipboard();
124+
await settingsDialogLocator.getByRole("button", { name: "Continue" }).click();
125+
126+
await expect(
127+
settingsDialogLocator.getByText("Enter your recovery key to confirm", { exact: true }),
128+
).toBeVisible();
129+
await settingsDialogLocator.getByRole("textbox").fill(recoveryKey);
130+
await settingsDialogLocator.getByRole("button", { name: "Finish set up" }).click();
131+
132+
await expectDehydratedDeviceEnabled(app);
133+
134+
// After recovery is set up, we reset our cryptographic identity, which
135+
// should drop the dehydrated device.
136+
await settingsDialogLocator.getByRole("button", { name: "Reset cryptographic identity" }).click();
137+
await settingsDialogLocator.getByRole("button", { name: "Continue" }).click();
138+
139+
await expectDehydratedDeviceDisabled(app);
140+
});
119141
});
120142

121143
async function getDehydratedDeviceIds(client: Client): Promise<string[]> {
@@ -144,3 +166,16 @@ async function expectDehydratedDeviceEnabled(app: ElementAppPage): Promise<void>
144166
})
145167
.toEqual(1);
146168
}
169+
170+
/** Wait for our user to not have a dehydrated device */
171+
async function expectDehydratedDeviceDisabled(app: ElementAppPage): Promise<void> {
172+
// It might be nice to do this via the UI, but currently this info is not exposed via the UI.
173+
//
174+
// Note we might have to wait for the device list to be refreshed, so we wrap in `expect.poll`.
175+
await expect
176+
.poll(async () => {
177+
const dehydratedDeviceIds = await getDehydratedDeviceIds(app.client);
178+
return dehydratedDeviceIds.length;
179+
})
180+
.toEqual(0);
181+
}

playwright/e2e/crypto/device-verification.spec.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
waitForVerificationRequest,
2222
} from "./utils";
2323
import { type Bot } from "../../pages/bot";
24+
import { Toasts } from "../../pages/toasts.ts";
2425

2526
test.describe("Device verification", { tag: "@no-webkit" }, () => {
2627
let aliceBotClient: Bot;
@@ -72,6 +73,51 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => {
7273
await checkDeviceIsConnectedKeyBackup(app, expectedBackupVersion, false);
7374
});
7475

76+
// Regression test for https://github.com/element-hq/element-web/issues/29110
77+
test("No toast after verification, even if the secrets take a while to arrive", async ({ page, credentials }) => {
78+
// Before we log in, the bot creates an encrypted room, so that we can test the toast behaviour that only happens
79+
// when we are in an encrypted room.
80+
await aliceBotClient.createRoom({
81+
initial_state: [
82+
{
83+
type: "m.room.encryption",
84+
state_key: "",
85+
content: { algorithm: "m.megolm.v1.aes-sha2" },
86+
},
87+
],
88+
});
89+
90+
// In order to simulate a real environment more accurately, we need to slow down the arrival of the
91+
// `m.secret.send` to-device messages. That's slightly tricky to do directly, so instead we delay the *outgoing*
92+
// `m.secret.request` messages.
93+
await page.route("**/_matrix/client/v3/sendToDevice/m.secret.request/**", async (route) => {
94+
await route.fulfill({ json: {} });
95+
await new Promise((f) => setTimeout(f, 1000));
96+
await route.fetch();
97+
});
98+
99+
await logIntoElement(page, credentials);
100+
101+
// Launch the verification request between alice and the bot
102+
const verificationRequest = await initiateAliceVerificationRequest(page);
103+
104+
// Handle emoji SAS verification
105+
const infoDialog = page.locator(".mx_InfoDialog");
106+
// the bot chooses to do an emoji verification
107+
const verifier = await verificationRequest.evaluateHandle((request) => request.startVerification("m.sas.v1"));
108+
109+
// Handle emoji request and check that emojis are matching
110+
await doTwoWaySasVerification(page, verifier);
111+
112+
await infoDialog.getByRole("button", { name: "They match" }).click();
113+
await infoDialog.getByRole("button", { name: "Got it" }).click();
114+
115+
// There should be no toast (other than the notifications one)
116+
const toasts = new Toasts(page);
117+
await toasts.rejectToast("Notifications");
118+
await toasts.assertNoToasts();
119+
});
120+
75121
test("Verify device with QR code during login", async ({ page, app, credentials, homeserver }) => {
76122
// A mode 0x02 verification: "self-verifying in which the current device does not yet trust the master key"
77123
await logIntoElement(page, credentials);

playwright/e2e/forgot-password/forgot-password.spec.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,21 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
66
Please see LICENSE files in the repository root for full details.
77
*/
88

9-
import { expect, test as base } from "../../element-web-test";
9+
import { type CredentialsWithDisplayName, expect, test as base } from "../../element-web-test";
1010
import { selectHomeserver } from "../utils";
1111
import { emailHomeserver } from "../../plugins/homeserver/synapse/emailHomeserver.ts";
1212
import { isDendrite } from "../../plugins/homeserver/dendrite";
13-
import { type Credentials } from "../../plugins/homeserver";
1413

1514
const email = "[email protected]";
1615

17-
const test = base.extend<{ credentials: Pick<Credentials, "username" | "password"> }>({
16+
const test = base.extend({
1817
// eslint-disable-next-line no-empty-pattern
1918
credentials: async ({}, use, testInfo) => {
2019
await use({
2120
username: `user_${testInfo.testId}`,
2221
// this has to be password-like enough to please zxcvbn. Needless to say it's just from pwgen.
2322
password: "oETo7MPf0o",
24-
});
23+
} as CredentialsWithDisplayName);
2524
},
2625
});
2726

0 commit comments

Comments
 (0)