From 2e8e6e92cca556997e9bd97066f85c20d234cad9 Mon Sep 17 00:00:00 2001 From: ElementRobot Date: Tue, 16 Sep 2025 09:41:11 +0100 Subject: [PATCH 1/5] Add mechanism for Electron to render toasts (#30765) (#30767) Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> --- src/@types/global.d.ts | 3 ++- src/vector/platform/ElectronPlatform.tsx | 21 +++++++++++++++++++ .../vector/platform/ElectronPlatform-test.ts | 14 +++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index a8f132c84b..289bda3f49 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -68,7 +68,8 @@ type ElectronChannel = | "openDesktopCapturerSourcePicker" | "userAccessToken" | "homeserverUrl" - | "serverSupportedVersions"; + | "serverSupportedVersions" + | "showToast"; declare global { // use `number` as the return type in all cases for globalThis.set{Interval,Timeout}, diff --git a/src/vector/platform/ElectronPlatform.tsx b/src/vector/platform/ElectronPlatform.tsx index bef544d15d..579096d5c0 100644 --- a/src/vector/platform/ElectronPlatform.tsx +++ b/src/vector/platform/ElectronPlatform.tsx @@ -18,6 +18,7 @@ import { } from "matrix-js-sdk/src/matrix"; import React from "react"; import { logger } from "matrix-js-sdk/src/logger"; +import { uniqueId } from "lodash"; import BasePlatform, { UpdateCheckStatus, type UpdateStatus } from "../../BasePlatform"; import type BaseEventIndexManager from "../../indexing/BaseEventIndexManager"; @@ -43,6 +44,7 @@ import { SeshatIndexManager } from "./SeshatIndexManager"; import { IPCManager } from "./IPCManager"; import { _t } from "../../languageHandler"; import { BadgeOverlayRenderer } from "../../favicon"; +import GenericToast from "../../components/views/toasts/GenericToast.tsx"; interface SquirrelUpdate { releaseNotes: string; @@ -182,6 +184,25 @@ export default class ElectronPlatform extends BasePlatform { await this.ipc.call("callDisplayMediaCallback", source ?? { id: "", name: "", thumbnailURL: "" }); }); + this.electron.on("showToast", (ev, { title, description, priority = 40 }) => { + const key = uniqueId("electron_showToast_"); + const onPrimaryClick = (): void => { + ToastStore.sharedInstance().dismissToast(key); + }; + + ToastStore.sharedInstance().addOrReplaceToast({ + key, + title, + props: { + description, + primaryLabel: _t("action|dismiss"), + onPrimaryClick, + }, + component: GenericToast, + priority, + }); + }); + BreadcrumbsStore.instance.on(UPDATE_EVENT, this.onBreadcrumbsUpdate); this.initialised = this.initialise(); diff --git a/test/unit-tests/vector/platform/ElectronPlatform-test.ts b/test/unit-tests/vector/platform/ElectronPlatform-test.ts index 7b1669a04e..ec1c18bb46 100644 --- a/test/unit-tests/vector/platform/ElectronPlatform-test.ts +++ b/test/unit-tests/vector/platform/ElectronPlatform-test.ts @@ -21,6 +21,7 @@ import DesktopCapturerSourcePicker from "../../../../src/components/views/elemen import ElectronPlatform from "../../../../src/vector/platform/ElectronPlatform"; import { setupLanguageMock } from "../../../setup/setupLanguage"; import { stubClient } from "../../../test-utils"; +import ToastStore from "../../../../src/stores/ToastStore.ts"; jest.mock("../../../../src/rageshake/rageshake", () => ({ flush: jest.fn(), @@ -127,6 +128,19 @@ describe("ElectronPlatform", () => { expect(plat.ipc.call).toHaveBeenCalledWith("callDisplayMediaCallback", "source"); }); + it("should show a toast when showToast is fired", async () => { + new ElectronPlatform(); + const spy = jest.spyOn(ToastStore.sharedInstance(), "addOrReplaceToast"); + + const [event, handler] = getElectronEventHandlerCall("showToast")!; + handler({} as any, { title: "title", description: "description" }); + + expect(event).toBeTruthy(); + expect(spy).toHaveBeenCalledWith( + expect.objectContaining({ title: "title", props: expect.objectContaining({ description: "description" }) }), + ); + }); + describe("updates", () => { it("dispatches on check updates action", () => { new ElectronPlatform(); From 8d07e797c5a7d833248e162c8874fede04293f2b Mon Sep 17 00:00:00 2001 From: ElementRobot Date: Tue, 16 Sep 2025 12:05:08 +0100 Subject: [PATCH 2/5] Hold Electron toasts until after the client starts (#30768) (#30769) Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> --- src/vector/platform/ElectronPlatform.tsx | 9 ++++++++- .../vector/platform/ElectronPlatform-test.ts | 15 +++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/vector/platform/ElectronPlatform.tsx b/src/vector/platform/ElectronPlatform.tsx index 579096d5c0..4139f73d53 100644 --- a/src/vector/platform/ElectronPlatform.tsx +++ b/src/vector/platform/ElectronPlatform.tsx @@ -97,6 +97,7 @@ export default class ElectronPlatform extends BasePlatform { private badgeOverlayRenderer?: BadgeOverlayRenderer; private config!: IConfigOptions; private supportedSettings?: Record; + private clientStartedPromiseWithResolvers = Promise.withResolvers(); public constructor() { super(); @@ -184,7 +185,9 @@ export default class ElectronPlatform extends BasePlatform { await this.ipc.call("callDisplayMediaCallback", source ?? { id: "", name: "", thumbnailURL: "" }); }); - this.electron.on("showToast", (ev, { title, description, priority = 40 }) => { + this.electron.on("showToast", async (ev, { title, description, priority = 40 }) => { + await this.clientStartedPromiseWithResolvers.promise; + const key = uniqueId("electron_showToast_"); const onPrimaryClick = (): void => { ToastStore.sharedInstance().dismissToast(key); @@ -214,6 +217,10 @@ export default class ElectronPlatform extends BasePlatform { if (["call_state"].includes(payload.action)) { this.electron.send("app_onAction", payload); } + + if (payload.action === "client_started") { + this.clientStartedPromiseWithResolvers.resolve(); + } } private async initialise(): Promise { diff --git a/test/unit-tests/vector/platform/ElectronPlatform-test.ts b/test/unit-tests/vector/platform/ElectronPlatform-test.ts index ec1c18bb46..90b69f7fcf 100644 --- a/test/unit-tests/vector/platform/ElectronPlatform-test.ts +++ b/test/unit-tests/vector/platform/ElectronPlatform-test.ts @@ -130,14 +130,25 @@ describe("ElectronPlatform", () => { it("should show a toast when showToast is fired", async () => { new ElectronPlatform(); + dispatcher.dispatch( + { + action: "client_started", + }, + true, + ); const spy = jest.spyOn(ToastStore.sharedInstance(), "addOrReplaceToast"); const [event, handler] = getElectronEventHandlerCall("showToast")!; handler({} as any, { title: "title", description: "description" }); expect(event).toBeTruthy(); - expect(spy).toHaveBeenCalledWith( - expect.objectContaining({ title: "title", props: expect.objectContaining({ description: "description" }) }), + await waitFor(() => + expect(spy).toHaveBeenCalledWith( + expect.objectContaining({ + title: "title", + props: expect.objectContaining({ description: "description" }), + }), + ), ); }); From 9a11a8048375be20d9eab54579a1a4f797a6f03c Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 16 Sep 2025 11:41:51 +0000 Subject: [PATCH 3/5] Upgrade dependency to matrix-js-sdk@38.2.0 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 80df9047a1..2e58c7194e 100644 --- a/package.json +++ b/package.json @@ -134,7 +134,7 @@ "maplibre-gl": "^5.0.0", "matrix-encrypt-attachment": "^1.0.3", "matrix-events-sdk": "0.0.1", - "matrix-js-sdk": "38.1.0", + "matrix-js-sdk": "38.2.0", "matrix-widget-api": "^1.10.0", "memoize-one": "^6.0.0", "mime": "^4.0.4", diff --git a/yarn.lock b/yarn.lock index 99bf84ac80..c512f4e40f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11008,10 +11008,10 @@ matrix-events-sdk@0.0.1: resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz#c8c38911e2cb29023b0bbac8d6f32e0de2c957dd" integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA== -matrix-js-sdk@38.1.0: - version "38.1.0" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-38.1.0.tgz#76e569e0daa3d78e545d77d85de73f2f8abae4c9" - integrity sha512-yej0LgzPwBkQGI5XcBX8IXyXj/CHEgp7xBIj099wQWDLhK8iN2vDRs/yxZ+E4t38FDhavxqugU0TYDNoCSb1+w== +matrix-js-sdk@38.2.0: + version "38.2.0" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-38.2.0.tgz#bcda87ef767897e52afa31465a84b5bdacc80163" + integrity sha512-R3jzK8rDGi/3OXOax8jFFyxblCG9KTT5yuXAbvnZCGcpTm8lZ4mHQAn5UydVD8qiyUMNMpaaMd6/k7N+5I/yaQ== dependencies: "@babel/runtime" "^7.12.5" "@matrix-org/matrix-sdk-crypto-wasm" "^15.1.0" From 8e9a43d70c90e6a3b110cd0a377296079e4c81f5 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 16 Sep 2025 12:43:49 +0100 Subject: [PATCH 4/5] Merge commit from fork * Validate room upgrade relationships properly Ensures only correctly related rooms are left when leaving the latest version of a room. Ensures the room list does not wrongly hide rooms which have not yet been upgraded. Ensures the breadcrumbs store finds the correct latest version of a room for a given stored room. Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Tests Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/stores/BreadcrumbsStore.ts | 2 +- src/stores/room-list-v3/RoomListStoreV3.ts | 17 ++++---- src/stores/room-list/RoomListStore.ts | 34 ++++++++-------- src/utils/leave-behaviour.ts | 2 +- .../stores/BreadcrumbsStore-test.ts | 4 +- .../room-list-v3/RoomListStoreV3-test.ts | 39 +++++++++++++++++++ .../stores/room-list/RoomListStore-test.ts | 4 ++ test/unit-tests/utils/leave-behaviour-test.ts | 4 +- 8 files changed, 75 insertions(+), 31 deletions(-) diff --git a/src/stores/BreadcrumbsStore.ts b/src/stores/BreadcrumbsStore.ts index e3b01cae0b..041430340e 100644 --- a/src/stores/BreadcrumbsStore.ts +++ b/src/stores/BreadcrumbsStore.ts @@ -143,7 +143,7 @@ export class BreadcrumbsStore extends AsyncStoreWithClient { // If the room is upgraded, use that room instead. We'll also splice out // any children of the room. - const history = this.matrixClient?.getRoomUpgradeHistory(room.roomId, false, msc3946ProcessDynamicPredecessor); + const history = this.matrixClient?.getRoomUpgradeHistory(room.roomId, true, msc3946ProcessDynamicPredecessor); if (history && history.length > 1) { room = history[history.length - 1]; // Last room is most recent in history diff --git a/src/stores/room-list-v3/RoomListStoreV3.ts b/src/stores/room-list-v3/RoomListStoreV3.ts index 7933b1fc85..6ccca9b9a3 100644 --- a/src/stores/room-list-v3/RoomListStoreV3.ts +++ b/src/stores/room-list-v3/RoomListStoreV3.ts @@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details. import { logger } from "matrix-js-sdk/src/logger"; import { EventType, KnownMembership } from "matrix-js-sdk/src/matrix"; -import type { EmptyObject, Room, RoomState } from "matrix-js-sdk/src/matrix"; +import type { EmptyObject, Room } from "matrix-js-sdk/src/matrix"; import type { MatrixDispatcher } from "../../dispatcher/dispatcher"; import type { ActionPayload } from "../../dispatcher/payloads"; import type { FilterKey } from "./skip-list/filters"; @@ -250,12 +250,15 @@ export class RoomListStoreV3Class extends AsyncStoreWithClient { // If we're joining an upgraded room, we'll want to make sure we don't proliferate // the dead room in the list. if (oldMembership !== EffectiveMembership.Join && newMembership === EffectiveMembership.Join) { - const roomState: RoomState = payload.room.currentState; - const predecessor = roomState.findPredecessor(this.msc3946ProcessDynamicPredecessor); - if (predecessor) { - const prevRoom = this.matrixClient?.getRoom(predecessor.roomId); - if (prevRoom) this.roomSkipList.removeRoom(prevRoom); - else logger.warn(`Unable to find predecessor room with id ${predecessor.roomId}`); + const room: Room = payload.room; + const roomUpgradeHistory = room.client.getRoomUpgradeHistory( + room.roomId, + true, + this.msc3946ProcessDynamicPredecessor, + ); + const predecessors = roomUpgradeHistory.slice(0, roomUpgradeHistory.indexOf(room)); + for (const predecessor of predecessors) { + this.roomSkipList.removeRoom(predecessor); } } diff --git a/src/stores/room-list/RoomListStore.ts b/src/stores/room-list/RoomListStore.ts index 2fc396b8b3..c6675d7aa5 100644 --- a/src/stores/room-list/RoomListStore.ts +++ b/src/stores/room-list/RoomListStore.ts @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import { type MatrixClient, type Room, type RoomState, EventType, type EmptyObject } from "matrix-js-sdk/src/matrix"; +import { type MatrixClient, type Room, EventType, type EmptyObject } from "matrix-js-sdk/src/matrix"; import { logger } from "matrix-js-sdk/src/logger"; import SettingsStore from "../../settings/SettingsStore"; @@ -308,24 +308,22 @@ export class RoomListStoreClass extends AsyncStoreWithClient implem const oldMembership = getEffectiveMembership(membershipPayload.oldMembership); const newMembership = getEffectiveMembershipTag(membershipPayload.room, membershipPayload.membership); if (oldMembership !== EffectiveMembership.Join && newMembership === EffectiveMembership.Join) { - // If we're joining an upgraded room, we'll want to make sure we don't proliferate - // the dead room in the list. - const roomState: RoomState = membershipPayload.room.currentState; - const predecessor = roomState.findPredecessor(this.msc3946ProcessDynamicPredecessor); - if (predecessor) { - const prevRoom = this.matrixClient?.getRoom(predecessor.roomId); - if (prevRoom) { - const isSticky = this.algorithm.stickyRoom === prevRoom; - if (isSticky) { - this.algorithm.setStickyRoom(null); - } - - // Note: we hit the algorithm instead of our handleRoomUpdate() function to - // avoid redundant updates. - this.algorithm.handleRoomUpdate(prevRoom, RoomUpdateCause.RoomRemoved); - } else { - logger.warn(`Unable to find predecessor room with id ${predecessor.roomId}`); + // If we're joining an upgraded room, we'll want to make sure we don't proliferate the dead room in the list. + const room: Room = membershipPayload.room; + const roomUpgradeHistory = room.client.getRoomUpgradeHistory( + room.roomId, + true, + this.msc3946ProcessDynamicPredecessor, + ); + const predecessors = roomUpgradeHistory.slice(0, roomUpgradeHistory.indexOf(room)); + for (const predecessor of predecessors) { + const isSticky = this.algorithm.stickyRoom === predecessor; + if (isSticky) { + this.algorithm.setStickyRoom(null); } + // Note: we hit the algorithm instead of our handleRoomUpdate() function to + // avoid redundant updates. + this.algorithm.handleRoomUpdate(predecessor, RoomUpdateCause.RoomRemoved); } await this.handleRoomUpdate(membershipPayload.room, RoomUpdateCause.NewRoom); diff --git a/src/utils/leave-behaviour.ts b/src/utils/leave-behaviour.ts index 2ad6e7d4fe..8b7ee02f36 100644 --- a/src/utils/leave-behaviour.ts +++ b/src/utils/leave-behaviour.ts @@ -40,7 +40,7 @@ export async function leaveRoomBehaviour( let leavingAllVersions = true; const history = matrixClient.getRoomUpgradeHistory( roomId, - false, + true, SettingsStore.getValue("feature_dynamic_room_predecessors"), ); if (history && history.length > 0) { diff --git a/test/unit-tests/stores/BreadcrumbsStore-test.ts b/test/unit-tests/stores/BreadcrumbsStore-test.ts index 66cd88f41e..71ae11831d 100644 --- a/test/unit-tests/stores/BreadcrumbsStore-test.ts +++ b/test/unit-tests/stores/BreadcrumbsStore-test.ts @@ -112,7 +112,7 @@ describe("BreadcrumbsStore", () => { await dispatchJoinRoom(room.roomId); // We pass the value of the dynamic predecessor setting through - expect(client.getRoomUpgradeHistory).toHaveBeenCalledWith(room.roomId, false, false); + expect(client.getRoomUpgradeHistory).toHaveBeenCalledWith(room.roomId, true, false); }); }); @@ -134,7 +134,7 @@ describe("BreadcrumbsStore", () => { await dispatchJoinRoom(room.roomId); // We pass the value of the dynamic predecessor setting through - expect(client.getRoomUpgradeHistory).toHaveBeenCalledWith(room.roomId, false, true); + expect(client.getRoomUpgradeHistory).toHaveBeenCalledWith(room.roomId, true, true); }); }); diff --git a/test/unit-tests/stores/room-list-v3/RoomListStoreV3-test.ts b/test/unit-tests/stores/room-list-v3/RoomListStoreV3-test.ts index a5bf757d0f..03249dc1fc 100644 --- a/test/unit-tests/stores/room-list-v3/RoomListStoreV3-test.ts +++ b/test/unit-tests/stores/room-list-v3/RoomListStoreV3-test.ts @@ -28,6 +28,7 @@ import SettingsStore from "../../../../src/settings/SettingsStore"; import * as utils from "../../../../src/utils/notifications"; import * as roomMute from "../../../../src/stores/room-list/utils/roomMute"; import { Action } from "../../../../src/dispatcher/actions"; +import { mocked } from "jest-mock"; describe("RoomListStoreV3", () => { async function getRoomListStore() { @@ -197,6 +198,9 @@ describe("RoomListStoreV3", () => { const oldRoom = rooms[32]; // Create a new room with a predecessor event that points to oldRoom const newRoom = new Room("!foonew:matrix.org", client, client.getSafeUserId(), {}); + mocked(client.getRoomUpgradeHistory).mockImplementation((roomId) => + roomId === newRoom.roomId ? [oldRoom, newRoom] : [], + ); const createWithPredecessor = new MatrixEvent({ type: EventType.RoomCreate, sender: "@foo:foo.org", @@ -227,6 +231,41 @@ describe("RoomListStoreV3", () => { expect(roomIds).toContain(newRoom.roomId); }); + it("should not remove predecessor room based on non-reciprocated relationship", async () => { + const { store, rooms, client, dispatcher } = await getRoomListStore(); + const oldRoom = rooms[32]; + // Create a new room with a predecessor event that points to oldRoom, but oldRoom does not point back + const newRoom = new Room("!nefarious:matrix.org", client, client.getSafeUserId(), {}); + const createWithPredecessor = new MatrixEvent({ + type: EventType.RoomCreate, + sender: "@foo:foo.org", + room_id: newRoom.roomId, + content: { + predecessor: { room_id: oldRoom.roomId, event_id: "tombstone_event_id" }, + }, + event_id: "$create", + state_key: "", + }); + upsertRoomStateEvents(newRoom, [createWithPredecessor]); + + const fn = jest.fn(); + store.on(LISTS_UPDATE_EVENT, fn); + dispatcher.dispatch( + { + action: "MatrixActions.Room.myMembership", + oldMembership: KnownMembership.Invite, + membership: KnownMembership.Join, + room: newRoom, + }, + true, + ); + + expect(fn).toHaveBeenCalled(); + const roomIds = store.getSortedRooms().map((r) => r.roomId); + expect(roomIds).toContain(oldRoom.roomId); + expect(roomIds).toContain(newRoom.roomId); + }); + it("Rooms are re-inserted on m.direct event", async () => { const { store, dispatcher, client } = await getRoomListStore(); diff --git a/test/unit-tests/stores/room-list/RoomListStore-test.ts b/test/unit-tests/stores/room-list/RoomListStore-test.ts index d1ab2e7183..f6e1700ae3 100644 --- a/test/unit-tests/stores/room-list/RoomListStore-test.ts +++ b/test/unit-tests/stores/room-list/RoomListStore-test.ts @@ -115,6 +115,10 @@ describe("RoomListStore", () => { // Given a store we can spy on const { store, handleRoomUpdate } = createStore(); + mocked(client.getRoomUpgradeHistory).mockImplementation((roomId) => + roomId === roomWithCreatePredecessor.roomId ? [oldRoom, roomWithCreatePredecessor] : [], + ); + // When we tell it we joined a new room that has an old room as // predecessor in the create event const payload = { diff --git a/test/unit-tests/utils/leave-behaviour-test.ts b/test/unit-tests/utils/leave-behaviour-test.ts index 23be7fef98..9da796a3a5 100644 --- a/test/unit-tests/utils/leave-behaviour-test.ts +++ b/test/unit-tests/utils/leave-behaviour-test.ts @@ -129,7 +129,7 @@ describe("leaveRoomBehaviour", () => { it("Passes through the dynamic predecessor setting", async () => { await leaveRoomBehaviour(client, room.roomId); - expect(client.getRoomUpgradeHistory).toHaveBeenCalledWith(room.roomId, false, false); + expect(client.getRoomUpgradeHistory).toHaveBeenCalledWith(room.roomId, true, false); }); }); @@ -143,7 +143,7 @@ describe("leaveRoomBehaviour", () => { it("Passes through the dynamic predecessor setting", async () => { await leaveRoomBehaviour(client, room.roomId); - expect(client.getRoomUpgradeHistory).toHaveBeenCalledWith(room.roomId, false, true); + expect(client.getRoomUpgradeHistory).toHaveBeenCalledWith(room.roomId, true, true); }); }); }); From 87d40ab0e0d38a064912ee73ffaa8684cb1e79b1 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 16 Sep 2025 11:52:32 +0000 Subject: [PATCH 5/5] v1.11.112 --- CHANGELOG.md | 5 +++++ package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4effd50a59..6aae07ecda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +Changes in [1.11.112](https://github.com/element-hq/element-web/releases/tag/v1.11.112) (2025-09-16) +==================================================================================================== +Fix [CVE-2025-59161](https://www.cve.org/CVERecord?id=CVE-2025-59161) / [GHSA-m6c8-98f4-75rr](https://github.com/element-hq/element-web/security/advisories/GHSA-m6c8-98f4-75rr) + + Changes in [1.11.111](https://github.com/element-hq/element-web/releases/tag/v1.11.111) (2025-09-10) ==================================================================================================== ## ✨ Features diff --git a/package.json b/package.json index 2e58c7194e..4c6e29b591 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "element-web", - "version": "1.11.111", + "version": "1.11.112", "description": "Element: the future of secure communication", "author": "New Vector Ltd.", "repository": {