mirror of
https://github.com/element-hq/element-web
synced 2024-11-23 01:35:49 +03:00
Merge branch 'master' into develop
This commit is contained in:
commit
0475e7107f
15 changed files with 186 additions and 125 deletions
|
@ -1,3 +1,10 @@
|
|||
Changes in [3.69.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.69.0) (2023-03-28)
|
||||
=====================================================================================================
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
* Changes for matrix-js-sdk v24.0.0
|
||||
* Changes for matrix-react-sdk v3.69.0
|
||||
|
||||
Changes in [3.68.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.68.0) (2023-03-15)
|
||||
=====================================================================================================
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "matrix-react-sdk",
|
||||
"version": "3.68.0",
|
||||
"version": "3.69.0",
|
||||
"description": "SDK for matrix.org using React",
|
||||
"author": "matrix.org",
|
||||
"repository": {
|
||||
|
|
|
@ -233,7 +233,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
|||
// This is recomputed on each render. It's only stored on the component
|
||||
// for ease of passing the data around since it's computed in one pass
|
||||
// over all events.
|
||||
private readReceiptsByEvent: Record<string, IReadReceiptProps[]> = {};
|
||||
private readReceiptsByEvent: Map<string, IReadReceiptProps[]> = new Map();
|
||||
|
||||
// Track read receipts by user ID. For each user ID we've ever shown a
|
||||
// a read receipt for, we store an object:
|
||||
|
@ -252,7 +252,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
|||
// This is recomputed on each render, using the data from the previous
|
||||
// render as our fallback for any user IDs we can't match a receipt to a
|
||||
// displayed event in the current render cycle.
|
||||
private readReceiptsByUserId: Record<string, IReadReceiptForUser> = {};
|
||||
private readReceiptsByUserId: Map<string, IReadReceiptForUser> = new Map();
|
||||
|
||||
private readonly _showHiddenEvents: boolean;
|
||||
private isMounted = false;
|
||||
|
@ -637,7 +637,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
|||
// Note: the EventTile might still render a "sent/sending receipt" independent of
|
||||
// this information. When not providing read receipt information, the tile is likely
|
||||
// to assume that sent receipts are to be shown more often.
|
||||
this.readReceiptsByEvent = {};
|
||||
this.readReceiptsByEvent = new Map();
|
||||
if (this.props.showReadReceipts) {
|
||||
this.readReceiptsByEvent = this.getReadReceiptsByShownEvent(events);
|
||||
}
|
||||
|
@ -748,7 +748,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
|||
const eventId = mxEv.getId();
|
||||
const highlight = eventId === this.props.highlightedEventId;
|
||||
|
||||
const readReceipts = this.readReceiptsByEvent[eventId];
|
||||
const readReceipts = this.readReceiptsByEvent.get(eventId);
|
||||
|
||||
let isLastSuccessful = false;
|
||||
const isSentState = (s: EventStatus | null): boolean => !s || s === EventStatus.SENT;
|
||||
|
@ -865,28 +865,22 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
|||
// Get an object that maps from event ID to a list of read receipts that
|
||||
// should be shown next to that event. If a hidden event has read receipts,
|
||||
// they are folded into the receipts of the last shown event.
|
||||
private getReadReceiptsByShownEvent(events: EventAndShouldShow[]): Record<string, IReadReceiptProps[]> {
|
||||
const receiptsByEvent: Record<string, IReadReceiptProps[]> = {};
|
||||
const receiptsByUserId: Record<
|
||||
string,
|
||||
{
|
||||
lastShownEventId: string;
|
||||
receipt: IReadReceiptProps;
|
||||
}
|
||||
> = {};
|
||||
private getReadReceiptsByShownEvent(events: EventAndShouldShow[]): Map<string, IReadReceiptProps[]> {
|
||||
const receiptsByEvent: Map<string, IReadReceiptProps[]> = new Map();
|
||||
const receiptsByUserId: Map<string, IReadReceiptForUser> = new Map();
|
||||
|
||||
let lastShownEventId;
|
||||
for (const { event, shouldShow } of events) {
|
||||
if (shouldShow) {
|
||||
let lastShownEventId: string;
|
||||
for (const event of this.props.events) {
|
||||
if (this.shouldShowEvent(event)) {
|
||||
lastShownEventId = event.getId();
|
||||
}
|
||||
if (!lastShownEventId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const existingReceipts = receiptsByEvent[lastShownEventId] || [];
|
||||
const existingReceipts = receiptsByEvent.get(lastShownEventId) || [];
|
||||
const newReceipts = this.getReadReceiptsForEvent(event);
|
||||
receiptsByEvent[lastShownEventId] = existingReceipts.concat(newReceipts);
|
||||
receiptsByEvent.set(lastShownEventId, existingReceipts.concat(newReceipts));
|
||||
|
||||
// Record these receipts along with their last shown event ID for
|
||||
// each associated user ID.
|
||||
|
@ -904,21 +898,21 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
|||
// someone which had one in the last. By looking through our previous
|
||||
// mapping of receipts by user ID, we can cover recover any receipts
|
||||
// that would have been lost by using the same event ID from last time.
|
||||
for (const userId in this.readReceiptsByUserId) {
|
||||
if (receiptsByUserId[userId]) {
|
||||
for (const userId of this.readReceiptsByUserId.keys()) {
|
||||
if (receiptsByUserId.get(userId)) {
|
||||
continue;
|
||||
}
|
||||
const { lastShownEventId, receipt } = this.readReceiptsByUserId[userId];
|
||||
const existingReceipts = receiptsByEvent[lastShownEventId] || [];
|
||||
receiptsByEvent[lastShownEventId] = existingReceipts.concat(receipt);
|
||||
receiptsByUserId[userId] = { lastShownEventId, receipt };
|
||||
const { lastShownEventId, receipt } = this.readReceiptsByUserId.get(userId);
|
||||
const existingReceipts = receiptsByEvent.get(lastShownEventId) || [];
|
||||
receiptsByEvent.set(lastShownEventId, existingReceipts.concat(receipt));
|
||||
receiptsByUserId.set(userId, { lastShownEventId, receipt });
|
||||
}
|
||||
this.readReceiptsByUserId = receiptsByUserId;
|
||||
|
||||
// After grouping receipts by shown events, do another pass to sort each
|
||||
// receipt list.
|
||||
for (const eventId in receiptsByEvent) {
|
||||
receiptsByEvent[eventId].sort((r1, r2) => {
|
||||
for (const receipts of receiptsByEvent.values()) {
|
||||
receipts.sort((r1, r2) => {
|
||||
return r2.ts - r1.ts;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ export const RoomAccountDataEventEditor: React.FC<IEditorProps> = ({ mxEvent, on
|
|||
};
|
||||
|
||||
interface IProps extends IDevtoolsProps {
|
||||
events: Record<string, MatrixEvent>;
|
||||
events: Map<string, MatrixEvent>;
|
||||
Editor: React.FC<IEditorProps>;
|
||||
actionLabel: string;
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ const BaseAccountDataExplorer: React.FC<IProps> = ({ events, Editor, actionLabel
|
|||
return (
|
||||
<BaseTool onBack={onBack} actionLabel={actionLabel} onAction={onAction}>
|
||||
<FilteredList query={query} onChange={setQuery}>
|
||||
{Object.entries(events).map(([eventType, ev]) => {
|
||||
{Array.from(events.entries()).map(([eventType, ev]) => {
|
||||
const onClick = (): void => {
|
||||
setEvent(ev);
|
||||
};
|
||||
|
|
|
@ -21,6 +21,7 @@ import counterpart from "counterpart";
|
|||
import React from "react";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { Optional } from "matrix-events-sdk";
|
||||
import { MapWithDefault, safeSet } from "matrix-js-sdk/src/utils";
|
||||
|
||||
import SettingsStore from "./settings/SettingsStore";
|
||||
import PlatformPeg from "./PlatformPeg";
|
||||
|
@ -629,21 +630,16 @@ export class CustomTranslationOptions {
|
|||
function doRegisterTranslations(customTranslations: ICustomTranslations): void {
|
||||
// We convert the operator-friendly version into something counterpart can
|
||||
// consume.
|
||||
const langs: {
|
||||
// same structure, just flipped key order
|
||||
[lang: string]: {
|
||||
[str: string]: string;
|
||||
};
|
||||
} = {};
|
||||
// Map: lang → Record: string → translation
|
||||
const langs: MapWithDefault<string, Record<string, string>> = new MapWithDefault(() => ({}));
|
||||
for (const [str, translations] of Object.entries(customTranslations)) {
|
||||
for (const [lang, newStr] of Object.entries(translations)) {
|
||||
if (!langs[lang]) langs[lang] = {};
|
||||
langs[lang][str] = newStr;
|
||||
safeSet(langs.getOrCreate(lang), str, newStr);
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, tell counterpart about our translations
|
||||
for (const [lang, translations] of Object.entries(langs)) {
|
||||
for (const [lang, translations] of langs) {
|
||||
counterpart.registerTranslations(lang, translations);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,11 +14,13 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { safeSet } from "matrix-js-sdk/src/utils";
|
||||
import { TranslationStringsObject } from "@matrix-org/react-sdk-module-api/lib/types/translations";
|
||||
import { AnyLifecycle } from "@matrix-org/react-sdk-module-api/lib/lifecycles/types";
|
||||
|
||||
import { AppModule } from "./AppModule";
|
||||
import { ModuleFactory } from "./ModuleFactory";
|
||||
|
||||
import "./ModuleComponents";
|
||||
|
||||
/**
|
||||
|
@ -53,9 +55,10 @@ export class ModuleRunner {
|
|||
if (!i18n) continue;
|
||||
|
||||
for (const [lang, strings] of Object.entries(i18n)) {
|
||||
if (!merged[lang]) merged[lang] = {};
|
||||
safeSet(merged, lang, merged[lang] || {});
|
||||
|
||||
for (const [str, val] of Object.entries(strings)) {
|
||||
merged[lang][str] = val;
|
||||
safeSet(merged[lang], str, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,8 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { safeSet } from "matrix-js-sdk/src/utils";
|
||||
|
||||
import { SettingLevel } from "../SettingLevel";
|
||||
import { WatchManager } from "../WatchManager";
|
||||
import AbstractLocalStorageSettingsHandler from "./AbstractLocalStorageSettingsHandler";
|
||||
|
@ -48,7 +50,7 @@ export default class RoomDeviceSettingsHandler extends AbstractLocalStorageSetti
|
|||
let value = this.read("mx_local_settings");
|
||||
if (!value) value = {};
|
||||
if (!value["blacklistUnverifiedDevicesPerRoom"]) value["blacklistUnverifiedDevicesPerRoom"] = {};
|
||||
value["blacklistUnverifiedDevicesPerRoom"][roomId] = newValue;
|
||||
safeSet(value["blacklistUnverifiedDevicesPerRoom"], roomId, newValue);
|
||||
this.setObject("mx_local_settings", value);
|
||||
this.watchers.notifyUpdate(settingName, roomId, SettingLevel.ROOM_DEVICE, newValue);
|
||||
return Promise.resolve();
|
||||
|
|
|
@ -135,9 +135,10 @@ export default class AutoRageshakeStore extends AsyncStoreWithClient<IState> {
|
|||
...eventInfo,
|
||||
recipient_rageshake: rageshakeURL,
|
||||
};
|
||||
this.matrixClient.sendToDevice(AUTO_RS_REQUEST, {
|
||||
[messageContent.user_id]: { [messageContent.device_id]: messageContent },
|
||||
});
|
||||
this.matrixClient.sendToDevice(
|
||||
AUTO_RS_REQUEST,
|
||||
new Map([["messageContent.user_id", new Map([[messageContent.device_id, messageContent]])]]),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -277,7 +277,7 @@ export class StopGapWidgetDriver extends WidgetDriver {
|
|||
if (deviceId === "*") {
|
||||
// Send the message to all devices we have keys for
|
||||
await client.encryptAndSendToDevices(
|
||||
Object.values(deviceInfoMap[userId]).map((deviceInfo) => ({
|
||||
Array.from(deviceInfoMap.get(userId).values()).map((deviceInfo) => ({
|
||||
userId,
|
||||
deviceInfo,
|
||||
})),
|
||||
|
@ -286,7 +286,7 @@ export class StopGapWidgetDriver extends WidgetDriver {
|
|||
} else {
|
||||
// Send the message to a specific device
|
||||
await client.encryptAndSendToDevices(
|
||||
[{ userId, deviceInfo: deviceInfoMap[userId][deviceId] }],
|
||||
[{ userId, deviceInfo: deviceInfoMap.get(userId).get(deviceId) }],
|
||||
content,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -17,7 +17,8 @@
|
|||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
|
||||
import { compare } from "matrix-js-sdk/src/utils";
|
||||
import { Optional } from "matrix-events-sdk";
|
||||
import { compare, MapWithDefault, recursiveMapToObject } from "matrix-js-sdk/src/utils";
|
||||
|
||||
import SettingsStore from "../../settings/SettingsStore";
|
||||
import WidgetStore, { IApp } from "../WidgetStore";
|
||||
|
@ -91,19 +92,17 @@ export const MAX_PINNED = 3;
|
|||
const MIN_WIDGET_WIDTH_PCT = 10; // 10%
|
||||
const MIN_WIDGET_HEIGHT_PCT = 2; // 2%
|
||||
|
||||
interface ContainerValue {
|
||||
ordered: IApp[];
|
||||
height?: number;
|
||||
distributions?: number[];
|
||||
}
|
||||
|
||||
export class WidgetLayoutStore extends ReadyWatchingStore {
|
||||
private static internalInstance: WidgetLayoutStore;
|
||||
|
||||
private byRoom: {
|
||||
[roomId: string]: Partial<{
|
||||
[container in Container]: {
|
||||
ordered: IApp[];
|
||||
height?: number | null;
|
||||
distributions?: number[];
|
||||
};
|
||||
}>;
|
||||
} = {};
|
||||
|
||||
// Map: room Id → container → ContainerValue
|
||||
private byRoom: MapWithDefault<string, Map<Container, ContainerValue>> = new MapWithDefault(() => new Map());
|
||||
private pinnedRef: string | undefined;
|
||||
private layoutRef: string | undefined;
|
||||
private dynamicRef: string | undefined;
|
||||
|
@ -143,7 +142,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
|
|||
}
|
||||
|
||||
protected async onNotReady(): Promise<void> {
|
||||
this.byRoom = {};
|
||||
this.byRoom = new MapWithDefault(() => new Map());
|
||||
|
||||
this.matrixClient?.off(RoomStateEvent.Events, this.updateRoomFromState);
|
||||
if (this.pinnedRef) SettingsStore.unwatchSetting(this.pinnedRef);
|
||||
|
@ -155,7 +154,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
|
|||
private updateAllRooms = (): void => {
|
||||
const msc3946ProcessDynamicPredecessor = SettingsStore.getValue("feature_dynamic_room_predecessors");
|
||||
if (!this.matrixClient) return;
|
||||
this.byRoom = {};
|
||||
this.byRoom = new MapWithDefault(() => new Map());
|
||||
for (const room of this.matrixClient.getVisibleRooms(msc3946ProcessDynamicPredecessor)) {
|
||||
this.recalculateRoom(room);
|
||||
}
|
||||
|
@ -194,12 +193,13 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
|
|||
public recalculateRoom(room: Room): void {
|
||||
const widgets = WidgetStore.instance.getApps(room.roomId);
|
||||
if (!widgets?.length) {
|
||||
this.byRoom[room.roomId] = {};
|
||||
this.byRoom.set(room.roomId, new Map());
|
||||
this.emitFor(room);
|
||||
return;
|
||||
}
|
||||
|
||||
const beforeChanges = JSON.stringify(this.byRoom[room.roomId]);
|
||||
const roomContainers = this.byRoom.getOrCreate(room.roomId);
|
||||
const beforeChanges = JSON.stringify(recursiveMapToObject(roomContainers));
|
||||
|
||||
const layoutEv = room.currentState.getStateEvents(WIDGET_LAYOUT_EVENT_TYPE, "");
|
||||
const legacyPinned = SettingsStore.getValue("Widgets.pinned", room.roomId);
|
||||
|
@ -335,33 +335,35 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
|
|||
}
|
||||
|
||||
// Finally, fill in our cache and update
|
||||
this.byRoom[room.roomId] = {};
|
||||
const newRoomContainers = new Map();
|
||||
this.byRoom.set(room.roomId, newRoomContainers);
|
||||
if (topWidgets.length) {
|
||||
this.byRoom[room.roomId][Container.Top] = {
|
||||
newRoomContainers.set(Container.Top, {
|
||||
ordered: topWidgets,
|
||||
distributions: widths,
|
||||
height: maxHeight,
|
||||
};
|
||||
});
|
||||
}
|
||||
if (rightWidgets.length) {
|
||||
this.byRoom[room.roomId][Container.Right] = {
|
||||
newRoomContainers.set(Container.Right, {
|
||||
ordered: rightWidgets,
|
||||
};
|
||||
});
|
||||
}
|
||||
if (centerWidgets.length) {
|
||||
this.byRoom[room.roomId][Container.Center] = {
|
||||
newRoomContainers.set(Container.Center, {
|
||||
ordered: centerWidgets,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const afterChanges = JSON.stringify(this.byRoom[room.roomId]);
|
||||
const afterChanges = JSON.stringify(recursiveMapToObject(newRoomContainers));
|
||||
|
||||
if (afterChanges !== beforeChanges) {
|
||||
this.emitFor(room);
|
||||
}
|
||||
}
|
||||
|
||||
public getContainerWidgets(room: Room, container: Container): IApp[] {
|
||||
return this.byRoom[room.roomId]?.[container]?.ordered || [];
|
||||
public getContainerWidgets(room: Optional<Room>, container: Container): IApp[] {
|
||||
return this.byRoom.get(room?.roomId)?.get(container)?.ordered || [];
|
||||
}
|
||||
|
||||
public isInContainer(room: Room, widget: IApp, container: Container): boolean {
|
||||
|
@ -381,7 +383,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
|
|||
|
||||
public getResizerDistributions(room: Room, container: Container): string[] {
|
||||
// yes, string.
|
||||
let distributions = this.byRoom[room.roomId]?.[container]?.distributions;
|
||||
let distributions = this.byRoom.get(room.roomId)?.get(container)?.distributions;
|
||||
if (!distributions || distributions.length < 2) return [];
|
||||
|
||||
// The distributor actually expects to be fed N-1 sizes and expands the middle section
|
||||
|
@ -410,19 +412,19 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
|
|||
container: container,
|
||||
width: numbers[i],
|
||||
index: i,
|
||||
height: this.byRoom[room.roomId]?.[container]?.height || MIN_WIDGET_HEIGHT_PCT,
|
||||
height: this.byRoom.get(room.roomId)?.get(container)?.height || MIN_WIDGET_HEIGHT_PCT,
|
||||
};
|
||||
});
|
||||
this.updateUserLayout(room, localLayout);
|
||||
}
|
||||
|
||||
public getContainerHeight(room: Room, container: Container): number | null {
|
||||
return this.byRoom[room.roomId]?.[container]?.height ?? null; // let the default get returned if needed
|
||||
return this.byRoom.get(room.roomId)?.get(container)?.height ?? null; // let the default get returned if needed
|
||||
}
|
||||
|
||||
public setContainerHeight(room: Room, container: Container, height?: number | null): void {
|
||||
const widgets = this.getContainerWidgets(room, container);
|
||||
const widths = this.byRoom[room.roomId]?.[container]?.distributions;
|
||||
const widths = this.byRoom.get(room.roomId)?.get(container)?.distributions;
|
||||
const localLayout: Record<string, IStoredLayout> = {};
|
||||
widgets.forEach((w, i) => {
|
||||
localLayout[w.id] = {
|
||||
|
@ -444,8 +446,8 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
|
|||
const newIdx = clamp(currentIdx + delta, 0, widgets.length);
|
||||
widgets.splice(newIdx, 0, widget);
|
||||
|
||||
const widths = this.byRoom[room.roomId]?.[container]?.distributions;
|
||||
const height = this.byRoom[room.roomId]?.[container]?.height;
|
||||
const widths = this.byRoom.get(room.roomId)?.get(container)?.distributions;
|
||||
const height = this.byRoom.get(room.roomId)?.get(container)?.height;
|
||||
const localLayout: Record<string, IStoredLayout> = {};
|
||||
widgets.forEach((w, i) => {
|
||||
localLayout[w.id] = {
|
||||
|
@ -512,8 +514,8 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
|
|||
if (container === Container.Top) {
|
||||
const containerWidgets = this.getContainerWidgets(room, container);
|
||||
const idx = containerWidgets.findIndex((w) => w.id === widget.id);
|
||||
const widths = this.byRoom[room.roomId]?.[container]?.distributions;
|
||||
const height = this.byRoom[room.roomId]?.[container]?.height;
|
||||
const widths = this.byRoom.get(room.roomId)?.get(container)?.distributions;
|
||||
const height = this.byRoom.get(room.roomId)?.get(container)?.height;
|
||||
evContent.widgets[widget.id] = {
|
||||
...evContent.widgets[widget.id],
|
||||
height: height ? Math.round(height) : undefined,
|
||||
|
@ -526,12 +528,12 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
|
|||
}
|
||||
|
||||
private getAllWidgets(room: Room): [IApp, Container][] {
|
||||
const containers = this.byRoom[room.roomId];
|
||||
const containers = this.byRoom.get(room.roomId);
|
||||
if (!containers) return [];
|
||||
|
||||
const ret: [IApp, Container][] = [];
|
||||
for (const container in containers) {
|
||||
const widgets = containers[container as Container]!.ordered;
|
||||
for (const [container, containerValue] of containers) {
|
||||
const widgets = containerValue.ordered;
|
||||
for (const widget of widgets) {
|
||||
ret.push([widget, container as Container]);
|
||||
}
|
||||
|
@ -545,12 +547,12 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
|
|||
for (const [widget, container] of allWidgets) {
|
||||
const containerWidgets = this.getContainerWidgets(room, container);
|
||||
const idx = containerWidgets.findIndex((w) => w.id === widget.id);
|
||||
const widths = this.byRoom[room.roomId]?.[container]?.distributions;
|
||||
const widths = this.byRoom.get(room.roomId)?.get(container)?.distributions;
|
||||
if (!newLayout[widget.id]) {
|
||||
newLayout[widget.id] = {
|
||||
container: container,
|
||||
index: idx,
|
||||
height: this.byRoom[room.roomId]?.[container]?.height,
|
||||
height: this.byRoom.get(room.roomId)?.get(container)?.height,
|
||||
width: widths?.[idx],
|
||||
};
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ export const recordClientInformation = async (
|
|||
* client information for devices NOT in this list will be removed
|
||||
*/
|
||||
export const pruneClientInformation = (validDeviceIds: string[], matrixClient: MatrixClient): void => {
|
||||
Object.values(matrixClient.store.accountData).forEach((event) => {
|
||||
Array.from(matrixClient.store.accountData.values()).forEach((event) => {
|
||||
if (!event.getType().startsWith(clientInformationEventPrefix)) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -76,7 +76,10 @@ describe("<SessionManagerTab />", () => {
|
|||
const mockCrossSigningInfo = {
|
||||
checkDeviceTrust: jest.fn(),
|
||||
};
|
||||
const mockVerificationRequest = { cancel: jest.fn(), on: jest.fn() } as unknown as VerificationRequest;
|
||||
const mockVerificationRequest = {
|
||||
cancel: jest.fn(),
|
||||
on: jest.fn(),
|
||||
} as unknown as VerificationRequest;
|
||||
const mockClient = getMockClientWithEventEmitter({
|
||||
...mockClientMethodsUser(aliceId),
|
||||
getStoredCrossSigningForUser: jest.fn().mockReturnValue(mockCrossSigningInfo),
|
||||
|
@ -185,7 +188,7 @@ describe("<SessionManagerTab />", () => {
|
|||
});
|
||||
|
||||
// @ts-ignore mock
|
||||
mockClient.store = { accountData: {} };
|
||||
mockClient.store = { accountData: new Map() };
|
||||
|
||||
mockClient.getAccountData.mockReset().mockImplementation((eventType) => {
|
||||
if (eventType.startsWith(LOCAL_NOTIFICATION_SETTINGS_PREFIX.name)) {
|
||||
|
@ -222,7 +225,9 @@ describe("<SessionManagerTab />", () => {
|
|||
|
||||
it("does not fail when checking device verification fails", async () => {
|
||||
const logSpy = jest.spyOn(logger, "error").mockImplementation(() => {});
|
||||
mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice, alicesMobileDevice] });
|
||||
mockClient.getDevices.mockResolvedValue({
|
||||
devices: [alicesDevice, alicesMobileDevice],
|
||||
});
|
||||
const noCryptoError = new Error("End-to-end encryption disabled");
|
||||
mockClient.getStoredDevice.mockImplementation(() => {
|
||||
throw noCryptoError;
|
||||
|
@ -277,7 +282,9 @@ describe("<SessionManagerTab />", () => {
|
|||
});
|
||||
|
||||
it("extends device with client information when available", async () => {
|
||||
mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice, alicesMobileDevice] });
|
||||
mockClient.getDevices.mockResolvedValue({
|
||||
devices: [alicesDevice, alicesMobileDevice],
|
||||
});
|
||||
mockClient.getAccountData.mockImplementation((eventType: string) => {
|
||||
const content = {
|
||||
name: "Element Web",
|
||||
|
@ -305,7 +312,9 @@ describe("<SessionManagerTab />", () => {
|
|||
});
|
||||
|
||||
it("renders devices without available client information without error", async () => {
|
||||
mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice, alicesMobileDevice] });
|
||||
mockClient.getDevices.mockResolvedValue({
|
||||
devices: [alicesDevice, alicesMobileDevice],
|
||||
});
|
||||
|
||||
const { getByTestId, queryByTestId } = render(getComponent());
|
||||
|
||||
|
@ -343,7 +352,9 @@ describe("<SessionManagerTab />", () => {
|
|||
});
|
||||
|
||||
it("goes to filtered list from security recommendations", async () => {
|
||||
mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice, alicesMobileDevice] });
|
||||
mockClient.getDevices.mockResolvedValue({
|
||||
devices: [alicesDevice, alicesMobileDevice],
|
||||
});
|
||||
const { getByTestId, container } = render(getComponent());
|
||||
|
||||
await act(async () => {
|
||||
|
@ -376,7 +387,9 @@ describe("<SessionManagerTab />", () => {
|
|||
});
|
||||
|
||||
it("renders current session section with an unverified session", async () => {
|
||||
mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice, alicesMobileDevice] });
|
||||
mockClient.getDevices.mockResolvedValue({
|
||||
devices: [alicesDevice, alicesMobileDevice],
|
||||
});
|
||||
const { getByTestId } = render(getComponent());
|
||||
|
||||
await act(async () => {
|
||||
|
@ -387,7 +400,9 @@ describe("<SessionManagerTab />", () => {
|
|||
});
|
||||
|
||||
it("opens encryption setup dialog when verifiying current session", async () => {
|
||||
mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice, alicesMobileDevice] });
|
||||
mockClient.getDevices.mockResolvedValue({
|
||||
devices: [alicesDevice, alicesMobileDevice],
|
||||
});
|
||||
const { getByTestId } = render(getComponent());
|
||||
const modalSpy = jest.spyOn(Modal, "createDialog");
|
||||
|
||||
|
@ -402,7 +417,9 @@ describe("<SessionManagerTab />", () => {
|
|||
});
|
||||
|
||||
it("renders current session section with a verified session", async () => {
|
||||
mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice, alicesMobileDevice] });
|
||||
mockClient.getDevices.mockResolvedValue({
|
||||
devices: [alicesDevice, alicesMobileDevice],
|
||||
});
|
||||
mockClient.getStoredDevice.mockImplementation(() => new DeviceInfo(alicesDevice.device_id));
|
||||
mockCrossSigningInfo.checkDeviceTrust.mockReturnValue(new DeviceTrustLevel(true, true, false, false));
|
||||
|
||||
|
@ -416,7 +433,9 @@ describe("<SessionManagerTab />", () => {
|
|||
});
|
||||
|
||||
it("expands current session details", async () => {
|
||||
mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice, alicesMobileDevice] });
|
||||
mockClient.getDevices.mockResolvedValue({
|
||||
devices: [alicesDevice, alicesMobileDevice],
|
||||
});
|
||||
const { getByTestId } = render(getComponent());
|
||||
|
||||
await act(async () => {
|
||||
|
@ -500,7 +519,9 @@ describe("<SessionManagerTab />", () => {
|
|||
const modalSpy = jest.spyOn(Modal, "createDialog");
|
||||
|
||||
// make the current device verified
|
||||
mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice, alicesMobileDevice] });
|
||||
mockClient.getDevices.mockResolvedValue({
|
||||
devices: [alicesDevice, alicesMobileDevice],
|
||||
});
|
||||
mockClient.getStoredDevice.mockImplementation((_userId, deviceId) => new DeviceInfo(deviceId));
|
||||
mockCrossSigningInfo.checkDeviceTrust.mockImplementation((_userId, { deviceId }) => {
|
||||
if (deviceId === alicesDevice.device_id) {
|
||||
|
@ -525,7 +546,9 @@ describe("<SessionManagerTab />", () => {
|
|||
});
|
||||
|
||||
it("does not allow device verification on session that do not support encryption", async () => {
|
||||
mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice, alicesMobileDevice] });
|
||||
mockClient.getDevices.mockResolvedValue({
|
||||
devices: [alicesDevice, alicesMobileDevice],
|
||||
});
|
||||
mockClient.getStoredDevice.mockImplementation((_userId, deviceId) => new DeviceInfo(deviceId));
|
||||
mockCrossSigningInfo.checkDeviceTrust.mockImplementation((_userId, { deviceId }) => {
|
||||
// current session verified = able to verify other sessions
|
||||
|
@ -557,7 +580,9 @@ describe("<SessionManagerTab />", () => {
|
|||
const modalSpy = jest.spyOn(Modal, "createDialog");
|
||||
|
||||
// make the current device verified
|
||||
mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice, alicesMobileDevice] });
|
||||
mockClient.getDevices.mockResolvedValue({
|
||||
devices: [alicesDevice, alicesMobileDevice],
|
||||
});
|
||||
mockClient.getStoredDevice.mockImplementation((_userId, deviceId) => new DeviceInfo(deviceId));
|
||||
mockCrossSigningInfo.checkDeviceTrust.mockImplementation((_userId, { deviceId }) => {
|
||||
if (deviceId === alicesDevice.device_id) {
|
||||
|
@ -595,7 +620,9 @@ describe("<SessionManagerTab />", () => {
|
|||
it("Signs out of current device", async () => {
|
||||
const modalSpy = jest.spyOn(Modal, "createDialog");
|
||||
|
||||
mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice] });
|
||||
mockClient.getDevices.mockResolvedValue({
|
||||
devices: [alicesDevice],
|
||||
});
|
||||
const { getByTestId } = render(getComponent());
|
||||
|
||||
await act(async () => {
|
||||
|
@ -614,7 +641,9 @@ describe("<SessionManagerTab />", () => {
|
|||
|
||||
it("Signs out of current device from kebab menu", async () => {
|
||||
const modalSpy = jest.spyOn(Modal, "createDialog");
|
||||
mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice] });
|
||||
mockClient.getDevices.mockResolvedValue({
|
||||
devices: [alicesDevice],
|
||||
});
|
||||
const { getByTestId, getByLabelText } = render(getComponent());
|
||||
|
||||
await act(async () => {
|
||||
|
@ -629,7 +658,9 @@ describe("<SessionManagerTab />", () => {
|
|||
});
|
||||
|
||||
it("does not render sign out other devices option when only one device", async () => {
|
||||
mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice] });
|
||||
mockClient.getDevices.mockResolvedValue({
|
||||
devices: [alicesDevice],
|
||||
});
|
||||
const { getByTestId, queryByLabelText } = render(getComponent());
|
||||
|
||||
await act(async () => {
|
||||
|
@ -671,9 +702,7 @@ describe("<SessionManagerTab />", () => {
|
|||
// @ts-ignore setup mock
|
||||
mockClient.store = {
|
||||
// @ts-ignore setup mock
|
||||
accountData: {
|
||||
[mobileDeviceClientInfo.getType()]: mobileDeviceClientInfo,
|
||||
},
|
||||
accountData: new Map([[mobileDeviceClientInfo.getType(), mobileDeviceClientInfo]]),
|
||||
};
|
||||
|
||||
mockClient.getDevices
|
||||
|
@ -703,7 +732,10 @@ describe("<SessionManagerTab />", () => {
|
|||
});
|
||||
|
||||
describe("other devices", () => {
|
||||
const interactiveAuthError = { httpStatus: 401, data: { flows: [{ stages: ["m.login.password"] }] } };
|
||||
const interactiveAuthError = {
|
||||
httpStatus: 401,
|
||||
data: { flows: [{ stages: ["m.login.password"] }] },
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockClient.deleteMultipleDevices.mockReset();
|
||||
|
@ -712,9 +744,13 @@ describe("<SessionManagerTab />", () => {
|
|||
it("deletes a device when interactive auth is not required", async () => {
|
||||
mockClient.deleteMultipleDevices.mockResolvedValue({});
|
||||
mockClient.getDevices
|
||||
.mockResolvedValueOnce({ devices: [alicesDevice, alicesMobileDevice, alicesOlderMobileDevice] })
|
||||
.mockResolvedValueOnce({
|
||||
devices: [alicesDevice, alicesMobileDevice, alicesOlderMobileDevice],
|
||||
})
|
||||
// pretend it was really deleted on refresh
|
||||
.mockResolvedValueOnce({ devices: [alicesDevice, alicesOlderMobileDevice] });
|
||||
.mockResolvedValueOnce({
|
||||
devices: [alicesDevice, alicesOlderMobileDevice],
|
||||
});
|
||||
|
||||
const { getByTestId } = render(getComponent());
|
||||
|
||||
|
@ -785,9 +821,13 @@ describe("<SessionManagerTab />", () => {
|
|||
.mockResolvedValueOnce({});
|
||||
|
||||
mockClient.getDevices
|
||||
.mockResolvedValueOnce({ devices: [alicesDevice, alicesMobileDevice, alicesOlderMobileDevice] })
|
||||
.mockResolvedValueOnce({
|
||||
devices: [alicesDevice, alicesMobileDevice, alicesOlderMobileDevice],
|
||||
})
|
||||
// pretend it was really deleted on refresh
|
||||
.mockResolvedValueOnce({ devices: [alicesDevice, alicesOlderMobileDevice] });
|
||||
.mockResolvedValueOnce({
|
||||
devices: [alicesDevice, alicesOlderMobileDevice],
|
||||
});
|
||||
|
||||
const { getByTestId, getByLabelText } = render(getComponent());
|
||||
|
||||
|
@ -821,7 +861,9 @@ describe("<SessionManagerTab />", () => {
|
|||
|
||||
// fill password and submit for interactive auth
|
||||
act(() => {
|
||||
fireEvent.change(getByLabelText("Password"), { target: { value: "topsecret" } });
|
||||
fireEvent.change(getByLabelText("Password"), {
|
||||
target: { value: "topsecret" },
|
||||
});
|
||||
fireEvent.submit(getByLabelText("Password"));
|
||||
});
|
||||
|
||||
|
@ -1062,7 +1104,9 @@ describe("<SessionManagerTab />", () => {
|
|||
|
||||
await updateDeviceName(getByTestId, alicesDevice, "");
|
||||
|
||||
expect(mockClient.setDeviceDetails).toHaveBeenCalledWith(alicesDevice.device_id, { display_name: "" });
|
||||
expect(mockClient.setDeviceDetails).toHaveBeenCalledWith(alicesDevice.device_id, {
|
||||
display_name: "",
|
||||
});
|
||||
});
|
||||
|
||||
it("displays an error when session display name fails to save", async () => {
|
||||
|
|
|
@ -16,7 +16,7 @@ limitations under the License.
|
|||
|
||||
import { mocked, Mocked } from "jest-mock";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { IDevice } from "matrix-js-sdk/src/crypto/deviceinfo";
|
||||
import { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo";
|
||||
import { RoomType } from "matrix-js-sdk/src/@types/event";
|
||||
|
||||
import { stubClient, setupAsyncStoreWithClient, mockPlatformPeg } from "./test-utils";
|
||||
|
@ -147,12 +147,16 @@ describe("createRoom", () => {
|
|||
});
|
||||
|
||||
describe("canEncryptToAllUsers", () => {
|
||||
const trueUser = {
|
||||
"@goodUser:localhost": {
|
||||
DEV1: {} as unknown as IDevice,
|
||||
DEV2: {} as unknown as IDevice,
|
||||
},
|
||||
};
|
||||
const trueUser = new Map([
|
||||
[
|
||||
"@goodUser:localhost",
|
||||
new Map([
|
||||
["DEV1", {} as unknown as DeviceInfo],
|
||||
["DEV2", {} as unknown as DeviceInfo],
|
||||
]),
|
||||
],
|
||||
]);
|
||||
|
||||
const falseUser = {
|
||||
"@badUser:localhost": {},
|
||||
};
|
||||
|
|
|
@ -79,9 +79,9 @@ describe("AutoRageshakeStore", () => {
|
|||
[
|
||||
[
|
||||
"im.vector.auto_rs_request",
|
||||
{
|
||||
"@userId:matrix.org": {
|
||||
"undefined": {
|
||||
Map {
|
||||
"messageContent.user_id" => Map {
|
||||
undefined => {
|
||||
"device_id": undefined,
|
||||
"event_id": "utd_event_id",
|
||||
"recipient_rageshake": undefined,
|
||||
|
|
|
@ -185,10 +185,18 @@ describe("StopGapWidgetDriver", () => {
|
|||
const aliceMobile = new DeviceInfo("aliceMobile");
|
||||
const bobDesktop = new DeviceInfo("bobDesktop");
|
||||
|
||||
mocked(client.crypto!.deviceList).downloadKeys.mockResolvedValue({
|
||||
"@alice:example.org": { aliceWeb, aliceMobile },
|
||||
"@bob:example.org": { bobDesktop },
|
||||
});
|
||||
mocked(client.crypto.deviceList).downloadKeys.mockResolvedValue(
|
||||
new Map([
|
||||
[
|
||||
"@alice:example.org",
|
||||
new Map([
|
||||
["aliceWeb", aliceWeb],
|
||||
["aliceMobile", aliceMobile],
|
||||
]),
|
||||
],
|
||||
["@bob:example.org", new Map([["bobDesktop", bobDesktop]])],
|
||||
]),
|
||||
);
|
||||
|
||||
await driver.sendToDevice("org.example.foo", true, contentMap);
|
||||
expect(client.encryptAndSendToDevices.mock.calls).toMatchSnapshot();
|
||||
|
|
Loading…
Reference in a new issue