Add MatrixClientPeg::safeGet and use it in tests (#10985)

This commit is contained in:
Michael Telatynski 2023-06-05 18:12:23 +01:00 committed by GitHub
parent c47b587225
commit 6b46d6e4f8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
88 changed files with 290 additions and 226 deletions

View file

@ -38,7 +38,7 @@ import { crossSigningCallbacks, tryToUnlockSecretStorageWithDehydrationKey } fro
import SecurityCustomisations from "./customisations/Security";
import { SlidingSyncManager } from "./SlidingSyncManager";
import CryptoStoreTooNewDialog from "./components/views/dialogs/CryptoStoreTooNewDialog";
import { _t } from "./languageHandler";
import { _t, UserFriendlyError } from "./languageHandler";
import { SettingLevel } from "./settings/SettingLevel";
import MatrixClientBackedController from "./settings/controllers/MatrixClientBackedController";
import ErrorDialog from "./components/views/dialogs/ErrorDialog";
@ -49,7 +49,7 @@ export interface IMatrixClientCreds {
identityServerUrl?: string;
userId: string;
deviceId?: string;
accessToken: string;
accessToken?: string;
guest?: boolean;
pickleKey?: string;
freshLogin?: boolean;
@ -71,9 +71,10 @@ export interface IMatrixClientPeg {
*
* @returns {string} The homeserver name, if present.
*/
getHomeserverName(): string;
getHomeserverName(): string | null;
get(): MatrixClient;
safeGet(): MatrixClient;
unset(): void;
assign(): Promise<any>;
start(): Promise<any>;
@ -134,7 +135,7 @@ class MatrixClientPegClass implements IMatrixClientPeg {
initialSyncLimit: 20,
};
private matrixClient: MatrixClient = null;
private matrixClient: MatrixClient | null = null;
private justRegisteredUserId: string | null = null;
// the credentials used to init the current client object.
@ -145,6 +146,13 @@ class MatrixClientPegClass implements IMatrixClientPeg {
return this.matrixClient;
}
public safeGet(): MatrixClient {
if (!this.matrixClient) {
throw new UserFriendlyError("User is not logged in");
}
return this.matrixClient;
}
public unset(): void {
this.matrixClient = null;
@ -215,6 +223,10 @@ class MatrixClientPegClass implements IMatrixClientPeg {
};
public async assign(): Promise<any> {
if (!this.matrixClient) {
throw new Error("createClient must be called first");
}
for (const dbType of ["indexeddb", "memory"]) {
try {
const promise = this.matrixClient.store.startup();
@ -275,6 +287,10 @@ class MatrixClientPegClass implements IMatrixClientPeg {
* Attempt to initialize the crypto layer on a newly-created MatrixClient
*/
private async initClientCrypto(): Promise<void> {
if (!this.matrixClient) {
throw new Error("createClient must be called first");
}
const useRustCrypto = SettingsStore.getValue("feature_rust_crypto");
// we want to make sure that the same crypto implementation is used throughout the lifetime of a device,
@ -317,11 +333,15 @@ class MatrixClientPegClass implements IMatrixClientPeg {
const opts = await this.assign();
logger.log(`MatrixClientPeg: really starting MatrixClient`);
await this.get().startClient(opts);
await this.matrixClient!.startClient(opts);
logger.log(`MatrixClientPeg: MatrixClient started`);
}
public getCredentials(): IMatrixClientCreds {
if (!this.matrixClient) {
throw new Error("createClient must be called first");
}
let copiedCredentials: IMatrixClientCreds | null = this.currentClientCreds;
if (this.currentClientCreds?.userId !== this.matrixClient?.credentials?.userId) {
// cached credentials belong to a different user - don't use them
@ -335,12 +355,14 @@ class MatrixClientPegClass implements IMatrixClientPeg {
identityServerUrl: this.matrixClient.idBaseUrl,
userId: this.matrixClient.getSafeUserId(),
deviceId: this.matrixClient.getDeviceId() ?? undefined,
accessToken: this.matrixClient.getAccessToken(),
accessToken: this.matrixClient.getAccessToken() ?? undefined,
guest: this.matrixClient.isGuest(),
};
}
public getHomeserverName(): string {
public getHomeserverName(): string | null {
if (!this.matrixClient) return null;
const matches = /^@[^:]+:(.+)$/.exec(this.matrixClient.getSafeUserId());
if (matches === null || matches.length < 1) {
throw new Error("Failed to derive homeserver name from user ID!");

View file

@ -91,7 +91,7 @@ const msgTypeHandlers: Record<string, (event: MatrixEvent) => string | null> = {
return null;
}
return TextForEvent.textForEvent(event);
return TextForEvent.textForEvent(event, MatrixClientPeg.get());
},
};
@ -111,7 +111,7 @@ class NotifierClass {
if (msgType && msgTypeHandlers.hasOwnProperty(msgType)) {
return msgTypeHandlers[msgType](ev);
}
return TextForEvent.textForEvent(ev);
return TextForEvent.textForEvent(ev, MatrixClientPeg.get());
}
// XXX: exported for tests

View file

@ -22,6 +22,7 @@ import { GuestAccess, HistoryVisibility, JoinRule } from "matrix-js-sdk/src/@typ
import { EventType, MsgType } from "matrix-js-sdk/src/@types/event";
import { M_POLL_START, M_POLL_END } from "matrix-js-sdk/src/@types/polls";
import { PollStartEvent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent";
import { MatrixClient } from "matrix-js-sdk/src/matrix";
import { _t } from "./languageHandler";
import * as Roles from "./Roles";
@ -31,7 +32,6 @@ import { ALL_RULE_TYPES, ROOM_RULE_TYPES, SERVER_RULE_TYPES, USER_RULE_TYPES } f
import { WIDGET_LAYOUT_EVENT_TYPE } from "./stores/widgets/WidgetLayoutStore";
import { RightPanelPhases } from "./stores/right-panel/RightPanelStorePhases";
import defaultDispatcher from "./dispatcher/dispatcher";
import { MatrixClientPeg } from "./MatrixClientPeg";
import { RoomSettingsTab } from "./components/views/dialogs/RoomSettingsDialog";
import AccessibleButton, { ButtonEvent } from "./components/views/elements/AccessibleButton";
import RightPanelStore from "./stores/right-panel/RightPanelStore";
@ -40,16 +40,15 @@ import { ElementCall } from "./models/Call";
import { textForVoiceBroadcastStoppedEvent, VoiceBroadcastInfoEventType } from "./voice-broadcast";
import { getSenderName } from "./utils/event/getSenderName";
function getRoomMemberDisplayname(event: MatrixEvent, userId = event.getSender()): string {
const client = MatrixClientPeg.get();
function getRoomMemberDisplayname(client: MatrixClient, event: MatrixEvent, userId = event.getSender()): string {
const roomId = event.getRoomId();
const member = client.getRoom(roomId)?.getMember(userId!);
return member?.name || member?.rawDisplayName || userId || _t("Someone");
}
function textForCallEvent(event: MatrixEvent): () => string {
const roomName = MatrixClientPeg.get().getRoom(event.getRoomId()!)?.name;
const isSupported = MatrixClientPeg.get().supportsVoip();
function textForCallEvent(event: MatrixEvent, client: MatrixClient): () => string {
const roomName = client.getRoom(event.getRoomId()!)?.name;
const isSupported = client.supportsVoip();
return isSupported
? () => _t("Video call started in %(roomName)s.", { roomName })
@ -60,11 +59,11 @@ function textForCallEvent(event: MatrixEvent): () => string {
// any text to display at all. For this reason they return deferred values
// to avoid the expense of looking up translations when they're not needed.
function textForCallInviteEvent(event: MatrixEvent): (() => string) | null {
function textForCallInviteEvent(event: MatrixEvent, client: MatrixClient): (() => string) | null {
const senderName = getSenderName(event);
// FIXME: Find a better way to determine this from the event?
const isVoice = !event.getContent().offer?.sdp?.includes("m=video");
const isSupported = MatrixClientPeg.get().supportsVoip();
const isSupported = client.supportsVoip();
// This ladder could be reduced down to a couple string variables, however other languages
// can have a hard time translating those strings. In an effort to make translations easier
@ -103,10 +102,15 @@ function getModification(prev?: string, value?: string): Modification {
return Modification.None;
}
function textForMemberEvent(ev: MatrixEvent, allowJSX: boolean, showHiddenEvents?: boolean): (() => string) | null {
function textForMemberEvent(
ev: MatrixEvent,
client: MatrixClient,
allowJSX: boolean,
showHiddenEvents?: boolean,
): (() => string) | null {
// XXX: SYJS-16 "sender is sometimes null for join messages"
const senderName = ev.sender?.name || getRoomMemberDisplayname(ev);
const targetName = ev.target?.name || getRoomMemberDisplayname(ev, ev.getStateKey());
const senderName = ev.sender?.name || getRoomMemberDisplayname(client, ev);
const targetName = ev.target?.name || getRoomMemberDisplayname(client, ev, ev.getStateKey());
const prevContent = ev.getPrevContent();
const content = ev.getContent();
const reason = content.reason;
@ -269,7 +273,7 @@ const onViewJoinRuleSettingsClick = (): void => {
});
};
function textForJoinRulesEvent(ev: MatrixEvent, allowJSX: boolean): () => Renderable {
function textForJoinRulesEvent(ev: MatrixEvent, client: MatrixClient, allowJSX: boolean): () => Renderable {
const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
switch (ev.getContent().join_rule) {
case JoinRule.Public:
@ -361,7 +365,7 @@ function textForServerACLEvent(ev: MatrixEvent): (() => string) | null {
return getText;
}
function textForMessageEvent(ev: MatrixEvent): (() => string) | null {
function textForMessageEvent(ev: MatrixEvent, client: MatrixClient): (() => string) | null {
if (isLocationEvent(ev)) {
return textForLocationEvent(ev);
}
@ -370,7 +374,7 @@ function textForMessageEvent(ev: MatrixEvent): (() => string) | null {
const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
let message = ev.getContent().body;
if (ev.isRedacted()) {
message = textForRedactedPollAndMessageEvent(ev);
message = textForRedactedPollAndMessageEvent(ev, client);
}
if (ev.getContent().msgtype === MsgType.Emote) {
@ -496,7 +500,7 @@ function textForHistoryVisibilityEvent(event: MatrixEvent): (() => string) | nul
}
// Currently will only display a change if a user's power level is changed
function textForPowerEvent(event: MatrixEvent): (() => string) | null {
function textForPowerEvent(event: MatrixEvent, client: MatrixClient): (() => string) | null {
const senderName = getSenderName(event);
if (!event.getPrevContent()?.users || !event.getContent()?.users) {
return null;
@ -533,7 +537,7 @@ function textForPowerEvent(event: MatrixEvent): (() => string) | null {
return;
}
if (to !== from) {
const name = getRoomMemberDisplayname(event, userId);
const name = getRoomMemberDisplayname(client, event, userId);
diffs.push({ userId, name, from, to });
}
});
@ -561,7 +565,7 @@ const onPinnedMessagesClick = (): void => {
RightPanelStore.instance.setCard({ phase: RightPanelPhases.PinnedMessages }, false);
};
function textForPinnedEvent(event: MatrixEvent, allowJSX: boolean): (() => Renderable) | null {
function textForPinnedEvent(event: MatrixEvent, client: MatrixClient, allowJSX: boolean): (() => Renderable) | null {
if (!SettingsStore.getValue("feature_pinning")) return null;
const senderName = getSenderName(event);
const roomId = event.getRoomId()!;
@ -835,12 +839,12 @@ export function textForLocationEvent(event: MatrixEvent): () => string {
});
}
function textForRedactedPollAndMessageEvent(ev: MatrixEvent): string {
function textForRedactedPollAndMessageEvent(ev: MatrixEvent, client: MatrixClient): string {
let message = _t("Message deleted");
const unsigned = ev.getUnsigned();
const redactedBecauseUserId = unsigned?.redacted_because?.sender;
if (redactedBecauseUserId && redactedBecauseUserId !== ev.getSender()) {
const room = MatrixClientPeg.get().getRoom(ev.getRoomId());
const room = client.getRoom(ev.getRoomId());
const sender = room?.getMember(redactedBecauseUserId);
message = _t("Message deleted by %(name)s", {
name: sender?.name || redactedBecauseUserId,
@ -850,12 +854,12 @@ function textForRedactedPollAndMessageEvent(ev: MatrixEvent): string {
return message;
}
function textForPollStartEvent(event: MatrixEvent): (() => string) | null {
function textForPollStartEvent(event: MatrixEvent, client: MatrixClient): (() => string) | null {
return () => {
let message = "";
if (event.isRedacted()) {
message = textForRedactedPollAndMessageEvent(event);
message = textForRedactedPollAndMessageEvent(event, client);
const senderDisplayName = event.sender?.name ?? event.getSender();
message = senderDisplayName + ": " + message;
} else {
@ -879,7 +883,12 @@ function textForPollEndEvent(event: MatrixEvent): (() => string) | null {
type Renderable = string | React.ReactNode | null;
interface IHandlers {
[type: string]: (ev: MatrixEvent, allowJSX: boolean, showHiddenEvents?: boolean) => (() => Renderable) | null;
[type: string]: (
ev: MatrixEvent,
client: MatrixClient,
allowJSX: boolean,
showHiddenEvents?: boolean,
) => (() => Renderable) | null;
}
const handlers: IHandlers = {
@ -925,25 +934,39 @@ for (const evType of ElementCall.CALL_EVENT_TYPE.names) {
/**
* Determines whether the given event has text to display.
*
* @param client The Matrix Client instance for the logged-in user
* @param ev The event
* @param showHiddenEvents An optional cached setting value for showHiddenEventsInTimeline
* to avoid hitting the settings store
*/
export function hasText(ev: MatrixEvent, showHiddenEvents?: boolean): boolean {
export function hasText(ev: MatrixEvent, client: MatrixClient, showHiddenEvents?: boolean): boolean {
const handler = (ev.isState() ? stateHandlers : handlers)[ev.getType()];
return Boolean(handler?.(ev, false, showHiddenEvents));
return Boolean(handler?.(ev, client, false, showHiddenEvents));
}
/**
* Gets the textual content of the given event.
*
* @param ev The event
* @param client The Matrix Client instance for the logged-in user
* @param allowJSX Whether to output rich JSX content
* @param showHiddenEvents An optional cached setting value for showHiddenEventsInTimeline
* to avoid hitting the settings store
*/
export function textForEvent(ev: MatrixEvent): string;
export function textForEvent(ev: MatrixEvent, allowJSX: true, showHiddenEvents?: boolean): string | React.ReactNode;
export function textForEvent(ev: MatrixEvent, allowJSX = false, showHiddenEvents?: boolean): string | React.ReactNode {
export function textForEvent(ev: MatrixEvent, client: MatrixClient): string;
export function textForEvent(
ev: MatrixEvent,
client: MatrixClient,
allowJSX: true,
showHiddenEvents?: boolean,
): string | React.ReactNode;
export function textForEvent(
ev: MatrixEvent,
client: MatrixClient,
allowJSX = false,
showHiddenEvents?: boolean,
): string | React.ReactNode {
const handler = (ev.isState() ? stateHandlers : handlers)[ev.getType()];
return handler?.(ev, allowJSX, showHiddenEvents)?.() || "";
return handler?.(ev, client, allowJSX, showHiddenEvents)?.() || "";
}

View file

@ -1301,7 +1301,7 @@ class MainGrouper extends BaseGrouper {
public add({ event: ev, shouldShow }: EventAndShouldShow): void {
if (ev.getType() === EventType.RoomMember) {
// We can ignore any events that don't actually have a message to display
if (!hasText(ev, this.panel.showHiddenEvents)) return;
if (!hasText(ev, MatrixClientPeg.get(), this.panel.showHiddenEvents)) return;
}
this.readMarker = this.readMarker || this.panel.readMarkerForEvent(ev.getId()!, ev === this.lastShownEvent);
if (!this.panel.showHiddenEvents && !shouldShow) {

View file

@ -20,7 +20,7 @@ import { M_TEXT } from "matrix-js-sdk/src/@types/extensible_events";
import { logger } from "matrix-js-sdk/src/logger";
import { Icon as PollIcon } from "../../../../res/img/element-icons/room/composer/poll.svg";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import MatrixClientContext, { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
import { _t } from "../../../languageHandler";
import { textForEvent } from "../../../TextForEvent";
import { Caption } from "../typography/Caption";
@ -95,10 +95,11 @@ const usePollStartEvent = (event: MatrixEvent): { pollStartEvent?: MatrixEvent;
};
export const MPollEndBody = React.forwardRef<any, IBodyProps>(({ mxEvent, ...props }, ref) => {
const cli = useMatrixClientContext();
const { pollStartEvent, isLoadingPollStartEvent } = usePollStartEvent(mxEvent);
if (!pollStartEvent) {
const pollEndFallbackMessage = M_TEXT.findIn(mxEvent.getContent()) || textForEvent(mxEvent);
const pollEndFallbackMessage = M_TEXT.findIn(mxEvent.getContent()) || textForEvent(mxEvent, cli);
return (
<>
<PollIcon className="mx_MPollEndBody_icon" />

View file

@ -19,6 +19,7 @@ import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import RoomContext from "../../../contexts/RoomContext";
import * as TextForEvent from "../../../TextForEvent";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
interface IProps {
mxEvent: MatrixEvent;
@ -28,7 +29,12 @@ export default class TextualEvent extends React.Component<IProps> {
public static contextType = RoomContext;
public render(): React.ReactNode {
const text = TextForEvent.textForEvent(this.props.mxEvent, true, this.context?.showHiddenEvents);
const text = TextForEvent.textForEvent(
this.props.mxEvent,
MatrixClientPeg.get(),
true,
this.context?.showHiddenEvents,
);
if (!text) return null;
return <div className="mx_TextualEvent">{text}</div>;
}

View file

@ -255,7 +255,7 @@ export function pickFactory(
return noEventFactoryFactory(); // improper event type to render
}
if (STATE_EVENT_TILE_TYPES.get(evType) === TextualEventFactory && !hasText(mxEvent, showHiddenEvents)) {
if (STATE_EVENT_TILE_TYPES.get(evType) === TextualEventFactory && !hasText(mxEvent, cli, showHiddenEvents)) {
return noEventFactoryFactory();
}
@ -435,7 +435,7 @@ export function haveRendererForEvent(mxEvent: MatrixEvent, showHiddenEvents: boo
const handler = pickFactory(mxEvent, cli, showHiddenEvents);
if (!handler) return false;
if (handler === TextualEventFactory) {
return hasText(mxEvent, showHiddenEvents);
return hasText(mxEvent, cli, showHiddenEvents);
} else if (handler === STATE_EVENT_TILE_TYPES.get(EventType.RoomCreate)) {
const dynamicPredecessorsEnabled = SettingsStore.getValue("feature_dynamic_room_predecessors");
const predecessor = cli.getRoom(mxEvent.getRoomId())?.findPredecessor(dynamicPredecessorsEnabled);

View file

@ -104,6 +104,7 @@
"We couldn't log you in": "We couldn't log you in",
"We asked the browser to remember which homeserver you use to let you sign in, but unfortunately your browser has forgotten it. Go to the sign in page and try again.": "We asked the browser to remember which homeserver you use to let you sign in, but unfortunately your browser has forgotten it. Go to the sign in page and try again.",
"Try again": "Try again",
"User is not logged in": "User is not logged in",
"Database unexpectedly closed": "Database unexpectedly closed",
"This may be caused by having the app open in multiple tabs or due to clearing browser data.": "This may be caused by having the app open in multiple tabs or due to clearing browser data.",
"Reload": "Reload",

View file

@ -402,7 +402,7 @@ export default class HTMLExporter extends Exporter {
// TODO: Handle callEvent errors
logger.error(e);
eventTile = await this.getEventTileMarkup(
this.createModifiedEvent(textForEvent(mxEv), mxEv, false),
this.createModifiedEvent(textForEvent(mxEv, this.room.client), mxEv, false),
joined,
);
}

View file

@ -104,7 +104,7 @@ export default class PlainTextExporter extends Exporter {
} else mediaText = ` (${this.mediaOmitText})`;
}
if (this.isReply(mxEv)) return senderDisplayName + ": " + this.textForReplyEvent(mxEv.getContent()) + mediaText;
else return textForEvent(mxEv) + mediaText;
else return textForEvent(mxEv, this.room.client) + mediaText;
};
protected async createOutput(events: MatrixEvent[]): Promise<string> {

View file

@ -15,7 +15,7 @@ limitations under the License.
*/
import React, { ReactNode } from "react";
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
import { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
import { MatrixClientPeg } from "../../MatrixClientPeg";
import AccessibleButton from "../../components/views/elements/AccessibleButton";
@ -23,7 +23,7 @@ import { highlightEvent } from "../../utils/EventUtils";
import { _t } from "../../languageHandler";
import { getSenderName } from "../../utils/event/getSenderName";
export const textForVoiceBroadcastStoppedEvent = (event: MatrixEvent): (() => ReactNode) => {
export const textForVoiceBroadcastStoppedEvent = (event: MatrixEvent, client: MatrixClient): (() => ReactNode) => {
return (): ReactNode => {
const ownUserId = MatrixClientPeg.get()?.getUserId();
const startEventId = event.getRelation()?.event_id;