Skip to content
This repository has been archived by the owner on Oct 16, 2024. It is now read-only.

Commit

Permalink
Merge branch 'develop' into dbkr/refactor_createcrosssigningdialog
Browse files Browse the repository at this point in the history
  • Loading branch information
dbkr authored Oct 14, 2024
2 parents 62da8ab + 771d4a8 commit 49b6b65
Show file tree
Hide file tree
Showing 42 changed files with 190 additions and 98 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
"dependencies": {
"@babel/runtime": "^7.12.5",
"@matrix-org/analytics-events": "^0.26.0",
"@matrix-org/emojibase-bindings": "^1.1.2",
"@matrix-org/emojibase-bindings": "^1.3.3",
"@vector-im/matrix-wysiwyg": "2.37.13",
"@matrix-org/react-sdk-module-api": "^2.4.0",
"@matrix-org/spec": "^1.7.0",
Expand Down
7 changes: 6 additions & 1 deletion playwright/e2e/crypto/crypto.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const testMessages = async (page: Page, bob: Bot, bobRoomId: string) => {
};

const bobJoin = async (page: Page, bob: Bot) => {
// Wait for Bob to get the invite
await bob.evaluate(async (cli) => {
const bobRooms = cli.getRooms();
if (!bobRooms.length) {
Expand All @@ -55,9 +56,13 @@ const bobJoin = async (page: Page, bob: Bot) => {
});
}
});
const roomId = await bob.joinRoomByName("Alice");

const roomId = await bob.joinRoomByName("Alice");
await expect(page.getByText("Bob joined the room")).toBeVisible();

// Even though Alice has seen Bob's join event, Bob may not have done so yet. Wait for the sync to arrive.
await bob.awaitRoomMembership(roomId);

return roomId;
};

Expand Down
9 changes: 6 additions & 3 deletions playwright/e2e/crypto/event-shields.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ test.describe("Cryptography", function () {
await app.client.bootstrapCrossSigning(aliceCredentials);
await autoJoin(bob);

// create an encrypted room
// create an encrypted room, and wait for Bob to join it.
testRoomId = await createSharedRoomWithUser(app, bob.credentials.userId, {
name: "TestRoom",
initial_state: [
Expand All @@ -46,6 +46,9 @@ test.describe("Cryptography", function () {
},
],
});

// Even though Alice has seen Bob's join event, Bob may not have done so yet. Wait for the sync to arrive.
await bob.awaitRoomMembership(testRoomId);
});

test("should show the correct shield on e2e events", async ({
Expand Down Expand Up @@ -287,9 +290,9 @@ test.describe("Cryptography", function () {
// Let our app start syncing again
await app.client.network.goOnline();

// Wait for the messages to arrive
// Wait for the messages to arrive. It can take quite a while for the sync to wake up.
const last = page.locator(".mx_EventTile_last");
await expect(last).toContainText("test encrypted from unverified");
await expect(last).toContainText("test encrypted from unverified", { timeout: 20000 });
const lastE2eIcon = last.locator(".mx_EventTile_e2eIcon");
await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/);
await lastE2eIcon.focus();
Expand Down
8 changes: 8 additions & 0 deletions playwright/e2e/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ import { Client } from "../pages/client";
* @param client Client instance that can be user or bot
* @param roomId room id to find room and check
* @param predicate defines condition that is used to check the room state
*
* FIXME this does not do what it is supposed to do, and I think it is unfixable.
* `page.exposeFunction` adds a function which returns a Promise. `window[predicateId](room)` therefore
* always returns a truthy value (a Promise). But even if you fix that: as far as I can tell, the Room is
* just passed to the callback function as a JSON blob: you cannot actually call any methods on it, so the
* callback is useless.
*
* @deprecated This function is broken.
*/
export async function waitForRoom(
page: Page,
Expand Down
48 changes: 48 additions & 0 deletions playwright/pages/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,54 @@ export class Client {
await client.evaluate((client, { roomId, userId }) => client.unban(roomId, userId), { roomId, userId });
}

/**
* Wait for the client to have specific membership of a given room
*
* This is often useful after joining a room, when we need to wait for the sync loop to catch up.
*
* Times out with an error after 1 second.
*
* @param roomId - ID of the room to check
* @param membership - required membership.
*/
public async awaitRoomMembership(roomId: string, membership: string = "join") {
await this.evaluate(
(cli: MatrixClient, { roomId, membership }) => {
const isReady = () => {
// Fetch the room on each check, because we get a different instance before and after the join arrives.
const room = cli.getRoom(roomId);
const myMembership = room?.getMyMembership();
// @ts-ignore access to private field "logger"
cli.logger.info(`waiting for room ${roomId}: membership now ${myMembership}`);
return myMembership === membership;
};
if (isReady()) return;

const timeoutPromise = new Promise((resolve) => setTimeout(resolve, 1000)).then(() => {
const room = cli.getRoom(roomId);
const myMembership = room?.getMyMembership();
throw new Error(
`Timeout waiting for room ${roomId} membership (now '${myMembership}', wanted '${membership}')`,
);
});

const readyPromise = new Promise<void>((resolve) => {
async function onEvent() {
if (isReady()) {
cli.removeListener(window.matrixcs.ClientEvent.Event, onEvent);
resolve();
}
}

cli.on(window.matrixcs.ClientEvent.Event, onEvent);
});

return Promise.race([timeoutPromise, readyPromise]);
},
{ roomId, membership },
);
}

/**
* @param {MatrixEvent} event
* @param {ReceiptType} receiptType
Expand Down
3 changes: 3 additions & 0 deletions res/css/structures/auth/_MobileRegistration.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,7 @@ Please see LICENSE files in the repository root for full details.

.mx_MobileRegister_body {
padding: 32px;
height: 100vh;
overflow-y: auto;
box-sizing: border-box;
}
2 changes: 0 additions & 2 deletions src/MatrixClientPeg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ import { formatList } from "./utils/FormattingUtils";
import SdkConfig from "./SdkConfig";
import { Features } from "./settings/Settings";
import { setDeviceIsolationMode } from "./settings/controllers/DeviceIsolationModeController.ts";
import { ReadyWatchingStore } from "./stores/ReadyWatchingStore.ts";

export interface IMatrixClientCreds {
homeserverUrl: string;
Expand Down Expand Up @@ -310,7 +309,6 @@ class MatrixClientPegClass implements IMatrixClientPeg {
MatrixActionCreators.start(this.matrixClient);
MatrixClientBackedSettingsHandler.matrixClient = this.matrixClient;
MatrixClientBackedController.matrixClient = this.matrixClient;
ReadyWatchingStore.matrixClient = this.matrixClient;

return opts;
}
Expand Down
6 changes: 3 additions & 3 deletions src/components/views/rooms/BasicMessageComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -201,9 +201,9 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
// so xd will not match if the string was "mixd 123456"
// and we are lookinh at xd 123456 part of the string
if (emoticonMatch && (n >= 0 || emoticonMatch.index !== 0)) {
const query = emoticonMatch[1].replace("-", "");
// try both exact match and lower-case, this means that xd won't match xD but :P will match :p
const data = EMOTICON_TO_EMOJI.get(query) || EMOTICON_TO_EMOJI.get(query.toLowerCase());
const query = emoticonMatch[1];
// variations of plaintext emoitcons(E.g. :P vs :p vs :-P) are handled upstream by the emojibase-bindings library
const data = EMOTICON_TO_EMOJI.get(query);

if (data) {
const { partCreator } = model;
Expand Down
2 changes: 1 addition & 1 deletion src/components/views/rooms/EventTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1519,7 +1519,7 @@ class E2ePadlock extends React.Component<IE2ePadlockProps> {
// https://github.com/element-hq/compound/issues/294
return (
<Tooltip label={this.props.title} isTriggerInteractive={true}>
<div className={classes} tabIndex={0} />
<div className={classes} tabIndex={0} aria-label={_t("timeline|e2e_state")} />
</Tooltip>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,9 @@ function shouldIncrementEndIndex(text: string, index: number): boolean {
*/
export function getMappedSuggestion(text: string, isAutoReplaceEmojiEnabled?: boolean): MappedSuggestion | null {
if (isAutoReplaceEmojiEnabled) {
const emoji = EMOTICON_TO_EMOJI.get(text.toLocaleLowerCase());
// variations of plaintext emoitcons(E.g. :P vs :p vs :-P) are handled upstream by the emojibase-bindings/emojibase libraries.
// See rules for variations here https://github.com/milesj/emojibase/blob/master/packages/core/src/generateEmoticonPermutations.ts#L3-L32
const emoji = EMOTICON_TO_EMOJI.get(text);
if (emoji?.unicode) {
return { keyChar: "", text: emoji.unicode, type: "custom" };
}
Expand Down
1 change: 1 addition & 0 deletions src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -3272,6 +3272,7 @@
"download_action_downloading": "Downloading",
"download_failed": "Download failed",
"download_failed_description": "An error occurred while downloading this file",
"e2e_state": "State of the end-to-end encryption",
"edits": {
"tooltip_label": "Edited at %(date)s. Click to view edits.",
"tooltip_sub": "Click to view edits",
Expand Down
9 changes: 2 additions & 7 deletions src/stores/AsyncStoreWithClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,8 @@ export abstract class AsyncStoreWithClient<T extends Object> extends AsyncStore<
})(dispatcher);
}

protected async start(matrixClient: MatrixClient | null): Promise<void> {
await this.readyStore.start(matrixClient);
}

// XXX: This method is intended only for use in tests.
public async useUnitTestClient(cli: MatrixClient): Promise<void> {
await this.readyStore.useUnitTestClient(cli);
public async start(): Promise<void> {
await this.readyStore.start();
}

public get matrixClient(): MatrixClient | null {
Expand Down
4 changes: 3 additions & 1 deletion src/stores/AutoRageshakeStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ interface IState {
*/
export default class AutoRageshakeStore extends AsyncStoreWithClient<IState> {
private static readonly internalInstance = (() => {
return new AutoRageshakeStore();
const instance = new AutoRageshakeStore();
instance.start();
return instance;
})();

private constructor() {
Expand Down
4 changes: 3 additions & 1 deletion src/stores/BreadcrumbsStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ interface IState {

export class BreadcrumbsStore extends AsyncStoreWithClient<IState> {
private static readonly internalInstance = (() => {
return new BreadcrumbsStore();
const instance = new BreadcrumbsStore();
instance.start();
return instance;
})();

private waitingRooms: { roomId: string; addedTs: number }[] = [];
Expand Down
1 change: 1 addition & 0 deletions src/stores/CallStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export class CallStore extends AsyncStoreWithClient<{}> {
public static get instance(): CallStore {
if (!this._instance) {
this._instance = new CallStore();
this._instance.start();
}
return this._instance;
}
Expand Down
1 change: 1 addition & 0 deletions src/stores/ModalWidgetStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ interface IState {
export class ModalWidgetStore extends AsyncStoreWithClient<IState> {
private static readonly internalInstance = (() => {
const instance = new ModalWidgetStore();
instance.start();
return instance;
})();
private modalInstance: IHandle<typeof ModalWidgetDialog> | null = null;
Expand Down
1 change: 1 addition & 0 deletions src/stores/OwnBeaconStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ const getLocallyCreatedBeaconEventIds = (): string[] => {
export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
private static readonly internalInstance = (() => {
const instance = new OwnBeaconStore();
instance.start();
return instance;
})();
// users beacons, keyed by event type
Expand Down
1 change: 1 addition & 0 deletions src/stores/OwnProfileStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const KEY_AVATAR_URL = "mx_profile_avatar_url";
export class OwnProfileStore extends AsyncStoreWithClient<IState> {
private static readonly internalInstance = (() => {
const instance = new OwnProfileStore();
instance.start();
return instance;
})();

Expand Down
37 changes: 11 additions & 26 deletions src/stores/ReadyWatchingStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,40 +9,27 @@
import { MatrixClient, SyncState } from "matrix-js-sdk/src/matrix";
import { EventEmitter } from "events";

import { MatrixClientPeg } from "../MatrixClientPeg";
import { ActionPayload } from "../dispatcher/payloads";
import { IDestroyable } from "../utils/IDestroyable";
import { Action } from "../dispatcher/actions";
import { MatrixDispatcher } from "../dispatcher/dispatcher";

export abstract class ReadyWatchingStore extends EventEmitter implements IDestroyable {
private static instances: ReadyWatchingStore[] = [];
protected _matrixClient: MatrixClient | null = null;
protected matrixClient: MatrixClient | null = null;
private dispatcherRef: string | null = null;

public static set matrixClient(client: MatrixClient) {
for (const instance of ReadyWatchingStore.instances) {
instance.start(client);
}
}

public constructor(protected readonly dispatcher: MatrixDispatcher) {
super();

this.dispatcherRef = this.dispatcher.register(this.onAction);
}

public get matrixClient(): MatrixClient | null {
return this._matrixClient;
}

public async start(matrixClient: MatrixClient | null): Promise<void> {
const oldClient = this._matrixClient;
this._matrixClient = matrixClient;
public async start(): Promise<void> {
this.dispatcherRef = this.dispatcher.register(this.onAction);

if (oldClient !== matrixClient) {
await this.onNotReady();
}
// MatrixClientPeg can be undefined in tests because of circular dependencies with other stores
const matrixClient = MatrixClientPeg?.get();
if (matrixClient) {
this.matrixClient = matrixClient;
await this.onReady();
}
}
Expand All @@ -51,10 +38,8 @@ export abstract class ReadyWatchingStore extends EventEmitter implements IDestro
return this.matrixClient; // for external readonly access
}

// XXX: This method is intended only for use in tests.
public async useUnitTestClient(cli: MatrixClient): Promise<void> {
this._matrixClient = cli;
await this.onReady();
public useUnitTestClient(cli: MatrixClient): void {
this.matrixClient = cli;
}

public destroy(): void {
Expand Down Expand Up @@ -89,13 +74,13 @@ export abstract class ReadyWatchingStore extends EventEmitter implements IDestro
if (this.matrixClient) {
await this.onNotReady();
}
this._matrixClient = payload.matrixClient;
this.matrixClient = payload.matrixClient;
await this.onReady();
}
} else if (payload.action === "on_client_not_viable" || payload.action === Action.OnLoggedOut) {
if (this.matrixClient) {
await this.onNotReady();
this._matrixClient = null;
this.matrixClient = null;
}
}
};
Expand Down
1 change: 1 addition & 0 deletions src/stores/VoiceRecordingStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export class VoiceRecordingStore extends AsyncStoreWithClient<IState> {
public static get instance(): VoiceRecordingStore {
if (!this.internalInstance) {
this.internalInstance = new VoiceRecordingStore();
this.internalInstance.start();
}
return this.internalInstance;
}
Expand Down
1 change: 1 addition & 0 deletions src/stores/WidgetStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ interface IRoomWidgets {
export default class WidgetStore extends AsyncStoreWithClient<IState> {
private static readonly internalInstance = (() => {
const instance = new WidgetStore();
instance.start();
return instance;
})();

Expand Down
1 change: 1 addition & 0 deletions src/stores/local-echo/EchoStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export class EchoStore extends AsyncStoreWithClient<IState> {
public static get instance(): EchoStore {
if (!this._instance) {
this._instance = new EchoStore();
this._instance.start();
}
return this._instance;
}
Expand Down
1 change: 1 addition & 0 deletions src/stores/notifications/RoomNotificationStateStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export const UPDATE_STATUS_INDICATOR = Symbol("update-status-indicator");
export class RoomNotificationStateStore extends AsyncStoreWithClient<IState> {
private static readonly internalInstance = (() => {
const instance = new RoomNotificationStateStore();
instance.start();
return instance;
})();
private roomMap = new Map<Room, RoomNotificationState>();
Expand Down
1 change: 1 addition & 0 deletions src/stores/right-panel/RightPanelStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,7 @@ export default class RightPanelStore extends ReadyWatchingStore {
public static get instance(): RightPanelStore {
if (!this.internalInstance) {
this.internalInstance = new RightPanelStore();
this.internalInstance.start();
}
return this.internalInstance;
}
Expand Down
6 changes: 5 additions & 1 deletion src/stores/room-list/MessagePreviewStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,11 @@ const mkMessagePreview = (text: string, event: MatrixEvent): MessagePreview => {
};

export class MessagePreviewStore extends AsyncStoreWithClient<IState> {
private static readonly internalInstance = (() => new MessagePreviewStore())();
private static readonly internalInstance = (() => {
const instance = new MessagePreviewStore();
instance.start();
return instance;
})();

/**
* @internal Public for test only
Expand Down
Loading

0 comments on commit 49b6b65

Please sign in to comment.