Merge branch 'master' into develop

This commit is contained in:
RiotRobot 2023-03-28 14:30:52 +01:00
commit 0475e7107f
15 changed files with 186 additions and 125 deletions

View file

@ -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)
=====================================================================================================

View file

@ -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": {

View file

@ -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;
});
}

View file

@ -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);
};

View file

@ -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);
}
}

View file

@ -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);
}
}
}

View file

@ -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();

View file

@ -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]])]]),
);
}
}

View file

@ -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,
);
}

View file

@ -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],
};
}

View file

@ -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;
}

View file

@ -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 () => {

View file

@ -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": {},
};

View file

@ -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,

View file

@ -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();