Conform more code to strict null checking (#10167)

* Conform more code to strict null checking

* Delint

* Iterate PR based on feedback
pull/28788/head^2
Michael Telatynski 2023-02-16 17:21:44 +00:00 committed by GitHub
parent f7bea2cae5
commit 4574c665ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
103 changed files with 517 additions and 495 deletions

View File

@ -89,7 +89,7 @@ async function loadImageElement(imageFile: File): Promise<{
// check for hi-dpi PNGs and fudge display resolution as needed.
// this is mainly needed for macOS screencaps
let parsePromise: Promise<boolean>;
let parsePromise = Promise.resolve(false);
if (imageFile.type === "image/png") {
// in practice macOS happens to order the chunks so they fall in
// the first 0x1000 bytes (thanks to a massive ICC header).
@ -101,7 +101,7 @@ async function loadImageElement(imageFile: File): Promise<{
const chunks = extractPngChunks(buffer);
for (const chunk of chunks) {
if (chunk.name === "pHYs") {
if (chunk.data.byteLength !== PHYS_HIDPI.length) return;
if (chunk.data.byteLength !== PHYS_HIDPI.length) return false;
return chunk.data.every((val, i) => val === PHYS_HIDPI[i]);
}
}
@ -199,10 +199,10 @@ function loadVideoElement(videoFile: File): Promise<HTMLVideoElement> {
reject(e);
};
let dataUrl = ev.target.result as string;
let dataUrl = ev.target?.result as string;
// Chrome chokes on quicktime but likes mp4, and `file.type` is
// read only, so do this horrible hack to unbreak quicktime
if (dataUrl.startsWith("data:video/quicktime;")) {
if (dataUrl?.startsWith("data:video/quicktime;")) {
dataUrl = dataUrl.replace("data:video/quicktime;", "data:video/mp4;");
}
@ -258,7 +258,7 @@ function readFileAsArrayBuffer(file: File | Blob): Promise<ArrayBuffer> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = function (e): void {
resolve(e.target.result as ArrayBuffer);
resolve(e.target?.result as ArrayBuffer);
};
reader.onerror = function (e): void {
reject(e);
@ -329,7 +329,7 @@ export async function uploadFile(
export default class ContentMessages {
private inprogress: RoomUpload[] = [];
private mediaConfig: IMediaConfig = null;
private mediaConfig: IMediaConfig | null = null;
public sendStickerContentToRoom(
url: string,
@ -377,8 +377,8 @@ export default class ContentMessages {
modal.close();
}
const tooBigFiles = [];
const okFiles = [];
const tooBigFiles: File[] = [];
const okFiles: File[] = [];
for (const file of files) {
if (this.isFileSizeAcceptable(file)) {
@ -420,7 +420,14 @@ export default class ContentMessages {
}
promBefore = doMaybeLocalRoomAction(roomId, (actualRoomId) =>
this.sendContentToRoom(file, actualRoomId, relation, matrixClient, replyToEvent, loopPromiseBefore),
this.sendContentToRoom(
file,
actualRoomId,
relation,
matrixClient,
replyToEvent ?? undefined,
loopPromiseBefore,
),
);
}
@ -584,7 +591,7 @@ export default class ContentMessages {
}
private ensureMediaConfigFetched(matrixClient: MatrixClient): Promise<void> {
if (this.mediaConfig !== null) return;
if (this.mediaConfig !== null) return Promise.resolve();
logger.log("[Media Config] Fetching");
return matrixClient

View File

@ -138,7 +138,7 @@ export class DecryptionFailureTracker {
return;
}
if (err) {
this.addDecryptionFailure(new DecryptionFailure(e.getId(), err.code));
this.addDecryptionFailure(new DecryptionFailure(e.getId()!, err.code));
} else {
// Could be an event in the failures, remove it
this.removeDecryptionFailuresForEvent(e);
@ -146,7 +146,7 @@ export class DecryptionFailureTracker {
}
public addVisibleEvent(e: MatrixEvent): void {
const eventId = e.getId();
const eventId = e.getId()!;
if (this.trackedEvents.has(eventId)) {
return;
@ -154,7 +154,7 @@ export class DecryptionFailureTracker {
this.visibleEvents.add(eventId);
if (this.failures.has(eventId) && !this.visibleFailures.has(eventId)) {
this.visibleFailures.set(eventId, this.failures.get(eventId));
this.visibleFailures.set(eventId, this.failures.get(eventId)!);
}
}
@ -172,7 +172,7 @@ export class DecryptionFailureTracker {
}
public removeDecryptionFailuresForEvent(e: MatrixEvent): void {
const eventId = e.getId();
const eventId = e.getId()!;
this.failures.delete(eventId);
this.visibleFailures.delete(eventId);
}
@ -193,8 +193,8 @@ export class DecryptionFailureTracker {
* Clear state and stop checking for and tracking failures.
*/
public stop(): void {
clearInterval(this.checkInterval);
clearInterval(this.trackInterval);
if (this.checkInterval) clearInterval(this.checkInterval);
if (this.trackInterval) clearInterval(this.trackInterval);
this.failures = new Map();
this.visibleEvents = new Set();

View File

@ -51,7 +51,7 @@ import { isBulkUnverifiedDeviceReminderSnoozed } from "./utils/device/snoozeBulk
const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000;
export default class DeviceListener {
private dispatcherRef: string;
private dispatcherRef: string | null;
// device IDs for which the user has dismissed the verify toast ('Later')
private dismissed = new Set<string>();
// has the user dismissed any of the various nag toasts to setup encryption on this device?
@ -152,7 +152,7 @@ export default class DeviceListener {
private ensureDeviceIdsAtStartPopulated(): void {
if (this.ourDeviceIdsAtStart === null) {
const cli = MatrixClientPeg.get();
this.ourDeviceIdsAtStart = new Set(cli.getStoredDevicesForUser(cli.getUserId()).map((d) => d.deviceId));
this.ourDeviceIdsAtStart = new Set(cli.getStoredDevicesForUser(cli.getUserId()!).map((d) => d.deviceId));
}
}
@ -162,7 +162,7 @@ export default class DeviceListener {
// devicesAtStart list to the devices that we see after the fetch.
if (initialFetch) return;
const myUserId = MatrixClientPeg.get().getUserId();
const myUserId = MatrixClientPeg.get().getUserId()!;
if (users.includes(myUserId)) this.ensureDeviceIdsAtStartPopulated();
// No need to do a recheck here: we just need to get a snapshot of our devices
@ -170,7 +170,7 @@ export default class DeviceListener {
};
private onDevicesUpdated = (users: string[]): void => {
if (!users.includes(MatrixClientPeg.get().getUserId())) return;
if (!users.includes(MatrixClientPeg.get().getUserId()!)) return;
this.recheck();
};
@ -225,7 +225,7 @@ export default class DeviceListener {
// The server doesn't tell us when key backup is set up, so we poll
// & cache the result
private async getKeyBackupInfo(): Promise<IKeyBackupInfo> {
private async getKeyBackupInfo(): Promise<IKeyBackupInfo | null> {
const now = new Date().getTime();
if (!this.keyBackupInfo || this.keyBackupFetchedAt < now - KEY_BACKUP_POLL_INTERVAL) {
this.keyBackupInfo = await MatrixClientPeg.get().getKeyBackupVersion();
@ -265,10 +265,10 @@ export default class DeviceListener {
this.checkKeyBackupStatus();
} else if (this.shouldShowSetupEncryptionToast()) {
// make sure our keys are finished downloading
await cli.downloadKeys([cli.getUserId()]);
await cli.downloadKeys([cli.getUserId()!]);
// cross signing isn't enabled - nag to enable it
// There are 3 different toasts for:
if (!cli.getCrossSigningId() && cli.getStoredCrossSigningForUser(cli.getUserId())) {
if (!cli.getCrossSigningId() && cli.getStoredCrossSigningForUser(cli.getUserId()!)) {
// Cross-signing on account but this device doesn't trust the master key (verify this session)
showSetupEncryptionToast(SetupKind.VERIFY_THIS_SESSION);
this.checkKeyBackupStatus();
@ -310,13 +310,13 @@ export default class DeviceListener {
// as long as cross-signing isn't ready,
// you can't see or dismiss any device toasts
if (crossSigningReady) {
const devices = cli.getStoredDevicesForUser(cli.getUserId());
const devices = cli.getStoredDevicesForUser(cli.getUserId()!);
for (const device of devices) {
if (device.deviceId === cli.deviceId) continue;
const deviceTrust = await cli.checkDeviceTrust(cli.getUserId()!, device.deviceId!);
if (!deviceTrust.isCrossSigningVerified() && !this.dismissed.has(device.deviceId)) {
if (this.ourDeviceIdsAtStart.has(device.deviceId)) {
if (this.ourDeviceIdsAtStart?.has(device.deviceId)) {
oldUnverifiedDeviceIds.add(device.deviceId);
} else {
newUnverifiedDeviceIds.add(device.deviceId);

View File

@ -67,7 +67,7 @@ export default class IdentityAuthClient {
window.localStorage.setItem("mx_is_access_token", this.accessToken);
}
private readToken(): string {
private readToken(): string | null {
if (this.tempClient) return null; // temporary client: ignore
return window.localStorage.getItem("mx_is_access_token");
}
@ -77,13 +77,13 @@ export default class IdentityAuthClient {
}
// Returns a promise that resolves to the access_token string from the IS
public async getAccessToken({ check = true } = {}): Promise<string> {
public async getAccessToken({ check = true } = {}): Promise<string | null> {
if (!this.authEnabled) {
// The current IS doesn't support authentication
return null;
}
let token = this.accessToken;
let token: string | null = this.accessToken;
if (!token) {
token = this.readToken();
}

View File

@ -181,7 +181,7 @@ export default class LegacyCallHandler extends EventEmitter {
* Gets the user-facing room associated with a call (call.roomId may be the call "virtual room"
* if a voip_mxid_translate_pattern is set in the config)
*/
public roomIdForCall(call: MatrixCall): string {
public roomIdForCall(call?: MatrixCall): string | null {
if (!call) return null;
// check asserted identity: if we're not obeying asserted identity,
@ -194,7 +194,7 @@ export default class LegacyCallHandler extends EventEmitter {
}
}
return VoipUserMapper.sharedInstance().nativeRoomForVirtualRoom(call.roomId) || call.roomId;
return VoipUserMapper.sharedInstance().nativeRoomForVirtualRoom(call.roomId) ?? call.roomId ?? null;
}
public start(): void {
@ -282,7 +282,7 @@ export default class LegacyCallHandler extends EventEmitter {
}
public unSilenceCall(callId: string): void {
if (this.isForcedSilent) return;
if (this.isForcedSilent()) return;
this.silencedCalls.delete(callId);
this.emit(LegacyCallHandlerEvent.SilencedCallsChanged, this.silencedCalls);
this.play(AudioID.Ring);
@ -341,14 +341,14 @@ export default class LegacyCallHandler extends EventEmitter {
}
private shouldObeyAssertedfIdentity(): boolean {
return SdkConfig.getObject("voip")?.get("obey_asserted_identity");
return !!SdkConfig.getObject("voip")?.get("obey_asserted_identity");
}
public getSupportsPstnProtocol(): boolean {
public getSupportsPstnProtocol(): boolean | null {
return this.supportsPstnProtocol;
}
public getSupportsVirtualRooms(): boolean {
public getSupportsVirtualRooms(): boolean | null {
return this.supportsSipNativeVirtual;
}
@ -414,7 +414,7 @@ export default class LegacyCallHandler extends EventEmitter {
cli.prepareToEncrypt(cli.getRoom(call.roomId));
};
public getCallById(callId: string): MatrixCall {
public getCallById(callId: string): MatrixCall | null {
for (const call of this.calls.values()) {
if (call.callId === callId) return call;
}
@ -435,7 +435,7 @@ export default class LegacyCallHandler extends EventEmitter {
}
public getAllActiveCalls(): MatrixCall[] {
const activeCalls = [];
const activeCalls: MatrixCall[] = [];
for (const call of this.calls.values()) {
if (call.state !== CallState.Ended && call.state !== CallState.Ringing) {
@ -446,7 +446,7 @@ export default class LegacyCallHandler extends EventEmitter {
}
public getAllActiveCallsNotInRoom(notInThisRoomId: string): MatrixCall[] {
const callsNotInThatRoom = [];
const callsNotInThatRoom: MatrixCall[] = [];
for (const [roomId, call] of this.calls.entries()) {
if (roomId !== notInThisRoomId && call.state !== CallState.Ended) {
@ -547,7 +547,7 @@ export default class LegacyCallHandler extends EventEmitter {
const mappedRoomId = this.roomIdForCall(call);
const callForThisRoom = this.getCallForRoom(mappedRoomId);
return callForThisRoom && call.callId === callForThisRoom.callId;
return !!callForThisRoom && call.callId === callForThisRoom.callId;
}
private setCallListeners(call: MatrixCall): void {
@ -610,7 +610,7 @@ export default class LegacyCallHandler extends EventEmitter {
return;
}
const newAssertedIdentity = call.getRemoteAssertedIdentity().id;
const newAssertedIdentity = call.getRemoteAssertedIdentity()?.id;
let newNativeAssertedIdentity = newAssertedIdentity;
if (newAssertedIdentity) {
const response = await this.sipNativeLookup(newAssertedIdentity);
@ -642,7 +642,7 @@ export default class LegacyCallHandler extends EventEmitter {
});
}
private onCallStateChanged = (newState: CallState, oldState: CallState, call: MatrixCall): void => {
private onCallStateChanged = (newState: CallState, oldState: CallState | null, call: MatrixCall): void => {
if (!this.matchesCallForThisRoom(call)) return;
const mappedRoomId = this.roomIdForCall(call);
@ -830,7 +830,7 @@ export default class LegacyCallHandler extends EventEmitter {
"<code>turn.matrix.org</code>, but this will not be as reliable, and " +
"it will share your IP address with that server. You can also manage " +
"this in Settings.",
null,
undefined,
{ code },
)}
</p>
@ -843,7 +843,7 @@ export default class LegacyCallHandler extends EventEmitter {
cli.setFallbackICEServerAllowed(allow);
},
},
null,
undefined,
true,
);
}
@ -882,7 +882,7 @@ export default class LegacyCallHandler extends EventEmitter {
title,
description,
},
null,
undefined,
true,
);
}

View File

@ -29,20 +29,17 @@ interface ILoginOptions {
}
export default class Login {
private hsUrl: string;
private isUrl: string;
private fallbackHsUrl: string;
private flows: Array<LoginFlow>;
private defaultDeviceDisplayName: string;
private tempClient: MatrixClient;
private flows: Array<LoginFlow> = [];
private readonly defaultDeviceDisplayName?: string;
private tempClient: MatrixClient | null = null; // memoize
public constructor(hsUrl: string, isUrl: string, fallbackHsUrl?: string, opts?: ILoginOptions) {
this.hsUrl = hsUrl;
this.isUrl = isUrl;
this.fallbackHsUrl = fallbackHsUrl;
this.flows = [];
public constructor(
private hsUrl: string,
private isUrl: string,
private fallbackHsUrl: string | null,
opts: ILoginOptions,
) {
this.defaultDeviceDisplayName = opts.defaultDeviceDisplayName;
this.tempClient = null; // memoize
}
public getHomeserverUrl(): string {
@ -96,7 +93,7 @@ export default class Login {
phoneNumber: string | undefined,
password: string,
): Promise<IMatrixClientCreds> {
const isEmail = username?.indexOf("@") > 0;
const isEmail = !!username && username.indexOf("@") > 0;
let identifier;
if (phoneCountry && phoneNumber) {
@ -127,7 +124,7 @@ export default class Login {
};
const tryFallbackHs = (originalError: Error): Promise<IMatrixClientCreds> => {
return sendLoginRequest(this.fallbackHsUrl, this.isUrl, "m.login.password", loginParams).catch(
return sendLoginRequest(this.fallbackHsUrl!, this.isUrl, "m.login.password", loginParams).catch(
(fallbackError) => {
logger.log("fallback HS login failed", fallbackError);
// throw the original error
@ -136,13 +133,13 @@ export default class Login {
);
};
let originalLoginError = null;
let originalLoginError: Error | null = null;
return sendLoginRequest(this.hsUrl, this.isUrl, "m.login.password", loginParams)
.catch((error) => {
originalLoginError = error;
if (error.httpStatus === 403) {
if (this.fallbackHsUrl) {
return tryFallbackHs(originalLoginError);
return tryFallbackHs(originalLoginError!);
}
}
throw originalLoginError;

View File

@ -256,7 +256,7 @@ export default class ScalarAuthClient {
}
}
public getScalarInterfaceUrlForRoom(room: Room, screen: string, id: string): string {
public getScalarInterfaceUrlForRoom(room: Room, screen?: string, id?: string): string {
const roomId = room.roomId;
const roomName = room.name;
let url = this.uiUrl;

View File

@ -102,7 +102,7 @@ async function getSecretStorageKey({
}): Promise<[string, Uint8Array]> {
const cli = MatrixClientPeg.get();
let keyId = await cli.getDefaultSecretStorageKeyId();
let keyInfo: ISecretStorageKeyInfo;
let keyInfo!: ISecretStorageKeyInfo;
if (keyId) {
// use the default SSSS key if set
keyInfo = keyInfos[keyId];

View File

@ -82,6 +82,7 @@ const singleMxcUpload = async (): Promise<string | null> => {
fileSelector.setAttribute("type", "file");
fileSelector.onchange = (ev: HTMLInputEvent) => {
const file = ev.target.files?.[0];
if (!file) return;
Modal.createDialog(UploadConfirmDialog, {
file,
@ -304,7 +305,7 @@ export const Commands = [
if (args) {
const cli = MatrixClientPeg.get();
const room = cli.getRoom(roomId);
if (!room.currentState.mayClientSendStateEvent("m.room.tombstone", cli)) {
if (!room?.currentState.mayClientSendStateEvent("m.room.tombstone", cli)) {
return reject(
newTranslatableError("You do not have the required permissions to use this command."),
);
@ -313,7 +314,7 @@ export const Commands = [
const { finished } = Modal.createDialog(
RoomUpgradeWarningDialog,
{ roomId: roomId, targetVersion: args },
/*className=*/ null,
/*className=*/ undefined,
/*isPriority=*/ false,
/*isStatic=*/ true,
);
@ -1199,7 +1200,7 @@ export const Commands = [
description: _td("Switches to this room's virtual room, if it has one"),
category: CommandCategories.advanced,
isEnabled(): boolean {
return LegacyCallHandler.instance.getSupportsVirtualRooms() && !isCurrentLocalRoom();
return !!LegacyCallHandler.instance.getSupportsVirtualRooms() && !isCurrentLocalRoom();
},
runFn: (roomId) => {
return success(
@ -1389,7 +1390,7 @@ export function parseCommandString(input: string): { cmd?: string; args?: string
const bits = input.match(/^(\S+?)(?:[ \n]+((.|\n)*))?$/);
let cmd: string;
let args: string;
let args: string | undefined;
if (bits) {
cmd = bits[1].substring(1).toLowerCase();
args = bits[2];
@ -1414,7 +1415,7 @@ interface ICmd {
export function getCommand(input: string): ICmd {
const { cmd, args } = parseCommandString(input);
if (CommandMap.has(cmd) && CommandMap.get(cmd)!.isEnabled()) {
if (cmd && CommandMap.has(cmd) && CommandMap.get(cmd)!.isEnabled()) {
return {
cmd: CommandMap.get(cmd),
args,

View File

@ -120,20 +120,20 @@ function textForMemberEvent(ev: MatrixEvent, allowJSX: boolean, showHiddenEvents
// We're taking the display namke directly from the event content here so we need
// to strip direction override chars which the js-sdk would normally do when
// calculating the display name
oldDisplayName: removeDirectionOverrideChars(prevContent.displayname),
displayName: removeDirectionOverrideChars(content.displayname),
oldDisplayName: removeDirectionOverrideChars(prevContent.displayname!),
displayName: removeDirectionOverrideChars(content.displayname!),
});
} else if (!prevContent.displayname && content.displayname) {
return () =>
_t("%(senderName)s set their display name to %(displayName)s", {
senderName: ev.getSender(),
displayName: removeDirectionOverrideChars(content.displayname),
displayName: removeDirectionOverrideChars(content.displayname!),
});
} else if (prevContent.displayname && !content.displayname) {
return () =>
_t("%(senderName)s removed their display name (%(oldDisplayName)s)", {
senderName,
oldDisplayName: removeDirectionOverrideChars(prevContent.displayname),
oldDisplayName: removeDirectionOverrideChars(prevContent.displayname!),
});
} else if (prevContent.avatar_url && !content.avatar_url) {
return () => _t("%(senderName)s removed their profile picture", { senderName });
@ -545,7 +545,7 @@ function textForPinnedEvent(event: MatrixEvent, allowJSX: boolean): (() => Rende
if (newlyPinned.length === 1 && newlyUnpinned.length === 0) {
// A single message was pinned, include a link to that message.
if (allowJSX) {
const messageId = newlyPinned.pop();
const messageId = newlyPinned.pop()!;
return () => (
<span>
@ -578,7 +578,7 @@ function textForPinnedEvent(event: MatrixEvent, allowJSX: boolean): (() => Rende
if (newlyUnpinned.length === 1 && newlyPinned.length === 0) {
// A single message was unpinned, include a link to that message.
if (allowJSX) {
const messageId = newlyUnpinned.pop();
const messageId = newlyUnpinned.pop()!;
return () => (
<span>

View File

@ -105,7 +105,7 @@ export interface IProps extends MenuProps {
}
interface IState {
contextMenuElem: HTMLDivElement;
contextMenuElem?: HTMLDivElement;
}
// Generic ContextMenu Portal wrapper
@ -122,9 +122,7 @@ export default class ContextMenu extends React.PureComponent<IProps, IState> {
public constructor(props: IProps) {
super(props);
this.state = {
contextMenuElem: null,
};
this.state = {};
// persist what had focus when we got initialized so we can return it after
this.initialFocus = document.activeElement as HTMLElement;
@ -181,7 +179,7 @@ export default class ContextMenu extends React.PureComponent<IProps, IState> {
button: 0, // Left
relatedTarget: null,
});
document.elementFromPoint(x, y).dispatchEvent(clickEvent);
document.elementFromPoint(x, y)?.dispatchEvent(clickEvent);
});
}
};
@ -239,7 +237,7 @@ export default class ContextMenu extends React.PureComponent<IProps, IState> {
// MessageActionBar), we should close any ContextMenu that is open.
KeyBindingAction.ArrowLeft,
KeyBindingAction.ArrowRight,
].includes(action)
].includes(action!)
) {
this.props.onFinished();
}
@ -312,12 +310,12 @@ export default class ContextMenu extends React.PureComponent<IProps, IState> {
position.top = Math.min(position.top, maxTop);
// Adjust the chevron if necessary
if (chevronOffset.top !== undefined) {
chevronOffset.top = propsChevronOffset + top - position.top;
chevronOffset.top = propsChevronOffset! + top! - position.top;
}
} else if (position.bottom !== undefined) {
position.bottom = Math.min(position.bottom, windowHeight - contextMenuRect.height - WINDOW_PADDING);
if (chevronOffset.top !== undefined) {
chevronOffset.top = propsChevronOffset + position.bottom - bottom;
chevronOffset.top = propsChevronOffset! + position.bottom - bottom!;
}
}
if (position.left !== undefined) {
@ -327,12 +325,12 @@ export default class ContextMenu extends React.PureComponent<IProps, IState> {
}
position.left = Math.min(position.left, maxLeft);
if (chevronOffset.left !== undefined) {
chevronOffset.left = propsChevronOffset + left - position.left;
chevronOffset.left = propsChevronOffset! + left! - position.left;
}
} else if (position.right !== undefined) {
position.right = Math.min(position.right, windowWidth - contextMenuRect.width - WINDOW_PADDING);
if (chevronOffset.left !== undefined) {
chevronOffset.left = propsChevronOffset + position.right - right;
chevronOffset.left = propsChevronOffset! + position.right - right!;
}
}
}
@ -389,7 +387,7 @@ export default class ContextMenu extends React.PureComponent<IProps, IState> {
const wrapperStyle: CSSProperties = {};
if (!isNaN(Number(zIndex))) {
menuStyle["zIndex"] = zIndex + 1;
menuStyle["zIndex"] = zIndex! + 1;
wrapperStyle["zIndex"] = zIndex;
}

View File

@ -97,7 +97,7 @@ class DMUserTile extends React.PureComponent<IDMUserTileProps> {
e.preventDefault();
e.stopPropagation();
this.props.onRemove(this.props.member);
this.props.onRemove!(this.props.member);
};
public render(): React.ReactNode {
@ -132,7 +132,7 @@ class DMUserTile extends React.PureComponent<IDMUserTileProps> {
interface IDMRoomTileProps {
member: Member;
lastActiveTs: number;
lastActiveTs?: number;
onToggle(member: Member): void;
highlightWord: string;
isSelected: boolean;
@ -188,7 +188,7 @@ class DMRoomTile extends React.PureComponent<IDMRoomTileProps> {
}
public render(): React.ReactNode {
let timestamp = null;
let timestamp: JSX.Element | undefined;
if (this.props.lastActiveTs) {
const humanTs = humanizeTime(this.props.lastActiveTs);
timestamp = <span className="mx_InviteDialog_tile--room_time">{humanTs}</span>;
@ -201,7 +201,7 @@ class DMRoomTile extends React.PureComponent<IDMRoomTileProps> {
<BaseAvatar
url={
this.props.member.getMxcAvatarUrl()
? mediaFromMxc(this.props.member.getMxcAvatarUrl()).getSquareThumbnailHttp(avatarSize)
? mediaFromMxc(this.props.member.getMxcAvatarUrl()!).getSquareThumbnailHttp(avatarSize)
: null
}
name={this.props.member.name}
@ -211,7 +211,7 @@ class DMRoomTile extends React.PureComponent<IDMRoomTileProps> {
/>
);
let checkmark = null;
let checkmark: JSX.Element | undefined;
if (this.props.isSelected) {
// To reduce flickering we put the 'selected' room tile above the real avatar
checkmark = <div className="mx_InviteDialog_tile--room_selected" />;
@ -301,7 +301,7 @@ interface IInviteDialogState {
// These two flags are used for the 'Go' button to communicate what is going on.
busy: boolean;
errorText: string;
errorText?: string;
}
export default class InviteDialog extends React.PureComponent<Props, IInviteDialogState> {
@ -324,7 +324,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
throw new Error("When using KIND_CALL_TRANSFER a call is required for an InviteDialog");
}
const alreadyInvited = new Set([MatrixClientPeg.get().getUserId(), SdkConfig.get("welcome_user_id")]);
const alreadyInvited = new Set([MatrixClientPeg.get().getUserId()!, SdkConfig.get("welcome_user_id")]);
if (isRoomInvite(props)) {
const room = MatrixClientPeg.get().getRoom(props.roomId);
if (!room) throw new Error("Room ID given to InviteDialog does not look like a room");
@ -351,7 +351,6 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
// These two flags are used for the 'Go' button to communicate what is going on.
busy: false,
errorText: null,
};
}
@ -386,7 +385,11 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
}
}
const recents = [];
const recents: {
userId: string;
user: RoomMember;
lastActive: number;
}[] = [];
for (const userId in rooms) {
// Filter out user IDs that are already in the room / should be excluded
if (excludedTargetIds.has(userId)) {
@ -455,14 +458,16 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
// Check to see if there's anything to convert first
if (!this.state.filterText || !this.state.filterText.includes("@")) return this.state.targets || [];
let newMember: Member;
let newMember: Member | undefined;
if (this.state.filterText.startsWith("@")) {
// Assume mxid
newMember = new DirectoryMember({ user_id: this.state.filterText, display_name: null, avatar_url: null });
newMember = new DirectoryMember({ user_id: this.state.filterText });
} else if (SettingsStore.getValue(UIFeature.IdentityServer)) {
// Assume email
newMember = new ThreepidMember(this.state.filterText);
}
if (!newMember) return this.state.targets;
const newTargets = [...(this.state.targets || []), newMember];
this.setState({ targets: newTargets, filterText: "" });
return newTargets;
@ -778,8 +783,8 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
...this.state.serverResultsMixin,
...this.state.threepidResultsMixin,
];
const toAdd = [];
const failed = [];
const toAdd: Member[] = [];
const failed: string[] = [];
const potentialAddresses = text
.split(/[\s,]+/)
.map((p) => p.trim())
@ -803,13 +808,11 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
try {
const profile = await MatrixClientPeg.get().getProfileInfo(address);
const displayName = profile ? profile.displayname : null;
const avatarUrl = profile ? profile.avatar_url : null;
toAdd.push(
new DirectoryMember({
user_id: address,
display_name: displayName,
avatar_url: avatarUrl,
display_name: profile?.displayname,
avatar_url: profile?.avatar_url,
}),
);
} catch (e) {
@ -859,11 +862,11 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
this.props.onFinished(false);
};
private renderSection(kind: "recents" | "suggestions"): JSX.Element {
private renderSection(kind: "recents" | "suggestions"): ReactNode {
let sourceMembers = kind === "recents" ? this.state.recents : this.state.suggestions;
let showNum = kind === "recents" ? this.state.numRecentsShown : this.state.numSuggestionsShown;
const showMoreFn = kind === "recents" ? this.showMoreRecents.bind(this) : this.showMoreSuggestions.bind(this);
const lastActive = (m: Result): number | null => (kind === "recents" ? m.lastActive : null);
const lastActive = (m: Result): number | undefined => (kind === "recents" ? m.lastActive : undefined);
let sectionName = kind === "recents" ? _t("Recent Conversations") : _t("Suggestions");
if (this.props.kind === KIND_INVITE) {
@ -924,7 +927,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
const toRender = sourceMembers.slice(0, showNum);
const hasMore = toRender.length < sourceMembers.length;
let showMore = null;
let showMore: JSX.Element | undefined;
if (hasMore) {
showMore = (
<div className="mx_InviteDialog_section_showMore">
@ -960,7 +963,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
this.state.targets.length === 0 &&
this.state.filterText.length === 0;
const targets = this.state.targets.map((t) => (
<DMUserTile member={t} onRemove={!this.state.busy && this.removeMember} key={t.userId} />
<DMUserTile member={t} onRemove={this.state.busy ? undefined : this.removeMember} key={t.userId} />
));
const input = (
<input
@ -973,7 +976,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
autoFocus={true}
disabled={this.state.busy || (this.props.kind == KIND_CALL_TRANSFER && this.state.targets.length > 0)}
autoComplete="off"
placeholder={hasPlaceholder ? _t("Search") : null}
placeholder={hasPlaceholder ? _t("Search") : undefined}
data-testid="invite-dialog-input"
/>
);
@ -985,7 +988,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
);
}
private renderIdentityServerWarning(): JSX.Element {
private renderIdentityServerWarning(): ReactNode {
if (
!this.state.tryingIdentityServer ||
this.state.canUseIdentityServer ||
@ -1080,7 +1083,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
selectText(e.currentTarget);
}
private get screenName(): ScreenName {
private get screenName(): ScreenName | undefined {
switch (this.props.kind) {
case KIND_DM:
return "StartChat";
@ -1088,7 +1091,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
}
public render(): React.ReactNode {
let spinner = null;
let spinner: JSX.Element | undefined;
if (this.state.busy) {
spinner = <Spinner w={20} h={20} />;
}
@ -1108,7 +1111,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
this.state.targets.length > 0 || (this.state.filterText && this.state.filterText.includes("@"));
const cli = MatrixClientPeg.get();
const userId = cli.getUserId();
const userId = cli.getUserId()!;
if (this.props.kind === KIND_DM) {
title = _t("Direct Messages");
@ -1150,11 +1153,11 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
<p>{_t("If you can't see who you're looking for, send them your invite link below.")}</p>
</div>
);
const link = makeUserPermalink(MatrixClientPeg.get().getUserId());
const link = makeUserPermalink(MatrixClientPeg.get().getUserId()!);
footer = (
<div className="mx_InviteDialog_footer">
<h3>{_t("Or send invite link")}</h3>
<CopyableText getTextToCopy={() => makeUserPermalink(MatrixClientPeg.get().getUserId())}>
<CopyableText getTextToCopy={() => makeUserPermalink(MatrixClientPeg.get().getUserId()!)}>
<a href={link} onClick={this.onLinkClick}>
{link}
</a>
@ -1296,7 +1299,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
let dialogContent;
if (this.props.kind === KIND_CALL_TRANSFER) {
const tabs = [];
const tabs: Tab[] = [];
tabs.push(
new Tab(TabId.UserDirectory, _td("User Directory"), "mx_InviteDialog_userDirectoryIcon", usersSection),
);

View File

@ -179,7 +179,7 @@ const toPublicRoomResult = (publicRoom: IPublicRoomsChunkRoom): IPublicRoomResul
publicRoom.name?.toLowerCase(),
sanitizeHtml(publicRoom.topic?.toLowerCase() ?? "", { allowedTags: [] }),
...(publicRoom.aliases?.map((it) => it.toLowerCase()) || []),
].filter(Boolean),
].filter(Boolean) as string[],
});
const toRoomResult = (room: Room): IRoomResult => {
@ -310,7 +310,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
}, [cli]);
const msc3946ProcessDynamicPredecessor = useFeatureEnabled("feature_dynamic_room_predecessors");
const ownInviteLink = makeUserPermalink(cli.getUserId());
const ownInviteLink = makeUserPermalink(cli.getUserId()!);
const [inviteLinkCopied, setInviteLinkCopied] = useState<boolean>(false);
const trimmedQuery = useMemo(() => query.trim(), [query]);
@ -465,7 +465,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
useWebSearchMetrics(numResults, query.length, true);
const activeSpace = SpaceStore.instance.activeSpaceRoom;
const [spaceResults, spaceResultsLoading] = useSpaceResults(activeSpace, query);
const [spaceResults, spaceResultsLoading] = useSpaceResults(activeSpace ?? undefined, query);
const setQuery = (e: ChangeEvent<HTMLInputElement>): void => {
const newQuery = e.currentTarget.value;
@ -473,7 +473,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
};
useEffect(() => {
setImmediate(() => {
let ref: Ref;
let ref: Ref | undefined;
if (rovingContext.state.refs) {
ref = rovingContext.state.refs[0];
}
@ -521,7 +521,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
onFinished();
};
let otherSearchesSection: JSX.Element;
let otherSearchesSection: JSX.Element | undefined;
if (trimmedQuery || filter !== Filter.PublicRooms) {
otherSearchesSection = (
<div
@ -693,7 +693,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
);
};
let peopleSection: JSX.Element;
let peopleSection: JSX.Element | undefined;
if (results[Section.People].length) {
peopleSection = (
<div
@ -707,7 +707,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
);
}
let suggestionsSection: JSX.Element;
let suggestionsSection: JSX.Element | undefined;
if (results[Section.Suggestions].length && filter === Filter.People) {
suggestionsSection = (
<div
@ -721,7 +721,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
);
}
let roomsSection: JSX.Element;
let roomsSection: JSX.Element | undefined;
if (results[Section.Rooms].length) {
roomsSection = (
<div
@ -735,7 +735,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
);
}
let spacesSection: JSX.Element;
let spacesSection: JSX.Element | undefined;
if (results[Section.Spaces].length) {
spacesSection = (
<div
@ -749,7 +749,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
);
}
let publicRoomsSection: JSX.Element;
let publicRoomsSection: JSX.Element | undefined;
if (filter === Filter.PublicRooms) {
publicRoomsSection = (
<div
@ -791,7 +791,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
);
}
let spaceRoomsSection: JSX.Element;
let spaceRoomsSection: JSX.Element | undefined;
if (spaceResults.length && activeSpace && filter === null) {
spaceRoomsSection = (
<div
@ -836,7 +836,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
);
}
let joinRoomSection: JSX.Element;
let joinRoomSection: JSX.Element | undefined;
if (
trimmedQuery.startsWith("#") &&
trimmedQuery.includes(":") &&
@ -868,7 +868,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
);
}
let hiddenResultsSection: JSX.Element;
let hiddenResultsSection: JSX.Element | undefined;
if (filter === Filter.People) {
hiddenResultsSection = (
<div className="mx_SpotlightDialog_section mx_SpotlightDialog_hiddenResults" role="group">
@ -921,7 +921,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
);
}
let groupChatSection: JSX.Element;
let groupChatSection: JSX.Element | undefined;
if (filter === Filter.People) {
groupChatSection = (
<div
@ -941,7 +941,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
);
}
let messageSearchSection: JSX.Element;
let messageSearchSection: JSX.Element | undefined;
if (filter === null) {
messageSearchSection = (
<div
@ -977,7 +977,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
</>
);
} else {
let recentSearchesSection: JSX.Element;
let recentSearchesSection: JSX.Element | undefined;
if (recentSearches.length) {
recentSearchesSection = (
<div
@ -1075,7 +1075,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
break;
}
let ref: RefObject<HTMLElement>;
let ref: RefObject<HTMLElement> | undefined;
const accessibilityAction = getKeyBindingsManager().getAccessibilityAction(ev);
switch (accessibilityAction) {
case KeyBindingAction.Escape:

View File

@ -24,11 +24,11 @@ import { chromeFileInputFix } from "../../../utils/BrowserWorkarounds";
interface IProps {
avatarUrl?: string;
avatarDisabled?: boolean;
name?: string;
name: string;
nameDisabled?: boolean;
topic?: string;
topicDisabled?: boolean;
setAvatar(avatar: File): void;
setAvatar(avatar?: File): void;
setName(name: string): void;
setTopic(topic: string): void;
}
@ -102,7 +102,7 @@ export const SpaceAvatar: React.FC<Pick<IProps, "avatarUrl" | "avatarDisabled" |
setAvatar(file);
const reader = new FileReader();
reader.onload = (ev) => {
setAvatarDataUrl(ev.target.result as string);
setAvatarDataUrl(ev.target?.result as string);
};
reader.readAsDataURL(file);
}}

View File

@ -53,7 +53,7 @@ const SpecificChildrenPicker: React.FC<ISpecificChildrenPickerProps> = ({
const matcher = new QueryMatcher<Room>(rooms, {
keys: ["name"],
funcs: [(r) => [r.getCanonicalAlias(), ...r.getAltAliases()].filter(Boolean)],
funcs: [(r) => [r.getCanonicalAlias(), ...r.getAltAliases()].filter(Boolean) as string[]],
shouldMatchWordsOnly: false,
});

View File

@ -245,14 +245,14 @@ export const SpaceCreateForm: React.FC<ISpaceCreateFormProps> = ({
const SpaceCreateMenu: React.FC<{
onFinished(): void;
}> = ({ onFinished }) => {
const [visibility, setVisibility] = useState<Visibility>(null);
const [visibility, setVisibility] = useState<Visibility | null>(null);
const [busy, setBusy] = useState<boolean>(false);
const [name, setName] = useState("");
const spaceNameField = useRef<Field>();
const [alias, setAlias] = useState("");
const spaceAliasField = useRef<RoomAliasField>();
const [avatar, setAvatar] = useState<File>(null);
const [avatar, setAvatar] = useState<File | undefined>(undefined);
const [topic, setTopic] = useState<string>("");
const onSpaceCreateClick = async (e: ButtonEvent): Promise<void> => {

View File

@ -215,7 +215,7 @@ const CreateSpaceButton: React.FC<Pick<IInnerSpacePanelProps, "isPanelCollapsed"
}
}, [isPanelCollapsed]); // eslint-disable-line react-hooks/exhaustive-deps
let contextMenu = null;
let contextMenu: JSX.Element | undefined;
if (menuDisplayed) {
contextMenu = <SpaceCreateMenu onFinished={closeMenu} />;
}

View File

@ -121,11 +121,11 @@ export const SpaceButton = forwardRef<HTMLElement, IButtonProps>(
);
}
let contextMenu: JSX.Element;
if (menuDisplayed && ContextMenuComponent) {
let contextMenu: JSX.Element | undefined;
if (menuDisplayed && handle.current && ContextMenuComponent) {
contextMenu = (
<ContextMenuComponent
{...toRightOf(handle.current?.getBoundingClientRect(), 0)}
{...toRightOf(handle.current.getBoundingClientRect(), 0)}
space={space}
onFinished={closeMenu}
/>
@ -242,7 +242,7 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
}
private get isCollapsed(): boolean {
return this.state.collapsed || this.props.isPanelCollapsed;
return this.state.collapsed || !!this.props.isPanelCollapsed;
}
private toggleCollapse = (evt: ButtonEvent): void => {

View File

@ -30,7 +30,7 @@ const onClickSendDm = (ev: ButtonEvent): void => {
};
interface Props {
useCase: UseCase;
useCase: UseCase | null;
}
export function UserOnboardingHeader({ useCase }: Props): JSX.Element {

View File

@ -38,7 +38,7 @@ interface Props {
// We decided to only show the new user onboarding page to new users
// For now, that means we set the cutoff at 2022-07-01 00:00 UTC
const USER_ONBOARDING_CUTOFF_DATE = new Date(1_656_633_600);
export function showUserOnboardingPage(useCase: UseCase): boolean {
export function showUserOnboardingPage(useCase: UseCase | null): boolean {
return useCase !== null || MatrixClientPeg.userRegisteredAfter(USER_ONBOARDING_CUTOFF_DATE);
}
@ -55,13 +55,11 @@ export function UserOnboardingPage({ justRegistered = false }: Props): JSX.Eleme
const [showList, setShowList] = useState<boolean>(false);
useEffect(() => {
if (initialSyncComplete) {
let handler: number | null = window.setTimeout(() => {
handler = null;
const handler = window.setTimeout(() => {
setShowList(true);
}, ANIMATION_DURATION);
return () => {
clearTimeout(handler);
handler = null;
};
} else {
setShowList(false);

View File

@ -18,6 +18,7 @@ import * as React from "react";
import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
import { _t } from "../../../languageHandler";
import { XOR } from "../../../@types/common";
const BUTTONS = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "*", "0", "#"];
const BUTTON_LETTERS = ["", "ABC", "DEF", "GHI", "JKL", "MNO", "PQRS", "TUV", "WXYZ", "", "+", ""];
@ -31,7 +32,7 @@ interface IButtonProps {
kind: DialPadButtonKind;
digit?: string;
digitSubtext?: string;
onButtonPress: (digit: string, ev: ButtonEvent) => void;
onButtonPress: (digit: string | undefined, ev: ButtonEvent) => void;
}
class DialPadButton extends React.PureComponent<IButtonProps> {
@ -60,16 +61,24 @@ class DialPadButton extends React.PureComponent<IButtonProps> {
}
}
interface IProps {
interface IBaseProps {
onDigitPress: (digit: string, ev: ButtonEvent) => void;
hasDial: boolean;
onDeletePress?: (ev: ButtonEvent) => void;
onDialPress?: () => void;
hasDial: boolean;
}
export default class Dialpad extends React.PureComponent<IProps> {
interface IProps extends IBaseProps {
hasDial: false;
}
interface IDialProps extends IBaseProps {
hasDial: true;
onDialPress: () => void;
}
export default class Dialpad extends React.PureComponent<XOR<IProps, IDialProps>> {
public render(): React.ReactNode {
const buttonNodes = [];
const buttonNodes: JSX.Element[] = [];
for (let i = 0; i < BUTTONS.length; i++) {
const button = BUTTONS[i];

View File

@ -76,7 +76,7 @@ interface IState {
sidebarShown: boolean;
}
function getFullScreenElement(): Element | undefined {
function getFullScreenElement(): Element | null {
return (
document.fullscreenElement ||
// moz omitted because firefox supports this unprefixed now (webkit here for safari)
@ -180,7 +180,7 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
}
};
private updateCallListeners(oldCall: MatrixCall, newCall: MatrixCall | null): void {
private updateCallListeners(oldCall: MatrixCall | null, newCall: MatrixCall | null): void {
if (oldCall === newCall) return;
if (oldCall) {
@ -245,7 +245,7 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
};
}
let primary: CallFeed;
let primary: CallFeed | undefined;
// Try to use a screensharing as primary, a remote one if possible
const screensharingFeeds = feeds.filter((feed) => feed.purpose === SDPStreamMetadataPurpose.Screenshare);
@ -289,7 +289,7 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
if (this.state.screensharing) {
isScreensharing = await this.props.call.setScreensharingEnabled(false);
} else {
if (PlatformPeg.get().supportsDesktopCapturer()) {
if (PlatformPeg.get()?.supportsDesktopCapturer()) {
const { finished } = Modal.createDialog<[string]>(DesktopCapturerSourcePicker);
const [source] = await finished;
if (!source) return;
@ -403,7 +403,7 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
);
}
private renderToast(): JSX.Element {
private renderToast(): JSX.Element | null {
const { call } = this.props;
const someoneIsScreensharing = call.getFeeds().some((feed) => {
return feed.purpose === SDPStreamMetadataPurpose.Screenshare;
@ -413,8 +413,8 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
const isScreensharing = call.isScreensharing();
const { primaryFeed, sidebarShown } = this.state;
const sharerName = primaryFeed?.getMember().name;
if (!sharerName) return;
const sharerName = primaryFeed?.getMember()?.name;
if (!sharerName) return null;
let text = isScreensharing ? _t("You are presenting") : _t("%(sharerName)s is presenting", { sharerName });
if (!sidebarShown) {
@ -495,7 +495,7 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
);
} else if (isLocalOnHold) {
onHoldText = _t("%(peerName)s held the call", {
peerName: call.getOpponentMember().name,
peerName: call.getOpponentMember()?.name,
});
}
@ -556,8 +556,8 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
const client = MatrixClientPeg.get();
const callRoomId = LegacyCallHandler.instance.roomIdForCall(call);
const secondaryCallRoomId = LegacyCallHandler.instance.roomIdForCall(secondaryCall);
const callRoom = client.getRoom(callRoomId);
const secCallRoom = secondaryCall ? client.getRoom(secondaryCallRoomId) : null;
const callRoom = callRoomId ? client.getRoom(callRoomId) : null;
const secCallRoom = secondaryCallRoomId ? client.getRoom(secondaryCallRoomId) : null;
const callViewClasses = classNames({
mx_LegacyCallView: true,

View File

@ -104,9 +104,9 @@ const LegacyCallViewDropdownButton: React.FC<IDropdownButtonProps> = ({ state, d
onHover={(hovering) => setHoveringDropdown(hovering)}
state={state}
/>
{menuDisplayed && (
{menuDisplayed && buttonRef.current && (
<DeviceContextMenu
{...alwaysAboveRightOf(buttonRef.current?.getBoundingClientRect())}
{...alwaysAboveRightOf(buttonRef.current.getBoundingClientRect())}
onFinished={closeMenu}
deviceKinds={deviceKinds}
/>
@ -117,7 +117,7 @@ const LegacyCallViewDropdownButton: React.FC<IDropdownButtonProps> = ({ state, d
interface IProps {
call: MatrixCall;
pipMode: boolean;
pipMode?: boolean;
handlers: {
onHangupClick: () => void;
onScreenshareClick: () => void;
@ -150,7 +150,7 @@ interface IState {
export default class LegacyCallViewButtons extends React.Component<IProps, IState> {
private dialpadButton = createRef<HTMLDivElement>();
private contextMenuButton = createRef<HTMLDivElement>();
private controlsHideTimer: number = null;
private controlsHideTimer: number | null = null;
public constructor(props: IProps) {
super(props);
@ -223,7 +223,7 @@ export default class LegacyCallViewButtons extends React.Component<IProps, IStat
});
let dialPad;
if (this.state.showDialpad) {
if (this.state.showDialpad && this.dialpadButton.current) {
dialPad = (
<DialpadContextMenu
{...alwaysMenuProps(
@ -231,7 +231,7 @@ export default class LegacyCallViewButtons extends React.Component<IProps, IStat
ChevronFace.None,
CONTEXT_MENU_VPADDING,
)}
// We mount the context menus as a as a child typically in order to include the
// We mount the context menus as a child typically in order to include the
// context menus when fullscreening the call content.
// However, this does not work as well when the call is embedded in a
// picture-in-picture frame. Thus, only mount as child when we are *not* in PiP.
@ -243,7 +243,7 @@ export default class LegacyCallViewButtons extends React.Component<IProps, IStat
}
let contextMenu;
if (this.state.showMoreMenu) {
if (this.state.showMoreMenu && this.contextMenuButton.current) {
contextMenu = (
<LegacyCallContextMenu
{...alwaysMenuProps(

View File

@ -71,9 +71,9 @@ const SecondaryCallInfo: React.FC<ISecondaryCallInfoProps> = ({ callRoom }) => {
};
interface LegacyCallViewHeaderProps {
pipMode: boolean;
callRooms?: Room[];
onPipMouseDown: (event: React.MouseEvent<Element, MouseEvent>) => void;
pipMode?: boolean;
callRooms: [Room, Room | null];
onPipMouseDown?: (event: React.MouseEvent<Element, MouseEvent>) => void;
onExpand?: () => void;
onPin?: () => void;
onMaximize?: () => void;
@ -81,7 +81,7 @@ interface LegacyCallViewHeaderProps {
const LegacyCallViewHeader: React.FC<LegacyCallViewHeaderProps> = ({
pipMode = false,
callRooms = [],
callRooms,
onPipMouseDown,
onExpand,
onPin,

View File

@ -95,7 +95,7 @@ export default class VideoFeed extends React.PureComponent<IProps, IState> {
element.addEventListener("resize", this.onResize);
};
private updateFeed(oldFeed: CallFeed, newFeed: CallFeed): void {
private updateFeed(oldFeed: CallFeed | null, newFeed: CallFeed | null): void {
if (oldFeed === newFeed) return;
if (oldFeed) {

View File

@ -84,7 +84,7 @@ export class Media {
* The HTTP URL for the thumbnail media (without any specified width, height, etc). Null/undefined
* if no thumbnail media recorded.
*/
public get thumbnailHttp(): string | undefined | null {
public get thumbnailHttp(): string | null {
if (!this.hasThumbnail) return null;
// eslint-disable-next-line no-restricted-properties
return this.client.mxcUrlToHttp(this.thumbnailMxc!);

View File

@ -45,7 +45,7 @@ export function isSlashCommand(model: EditorModel): boolean {
return false;
}
export function getSlashCommand(model: EditorModel): [Command, string, string] {
export function getSlashCommand(model: EditorModel): [Command | undefined, string | undefined, string] {
const commandText = model.parts.reduce((text, part) => {
// use mxid to textify user pills in a command and room alias/id for room pills
if (part.type === Type.UserPill || part.type === Type.RoomPill) {
@ -69,7 +69,7 @@ export async function runSlashCommand(
if (result.promise) {
try {
if (cmd.category === CommandCategories.messages || cmd.category === CommandCategories.effects) {
messageContent = await result.promise;
messageContent = (await result.promise) ?? null;
} else {
await result.promise;
}

View File

@ -32,7 +32,7 @@ export function walkDOMDepthFirst(rootNode: Node, enterNodeCallback: Predicate,
} else if (node.nextSibling) {
node = node.nextSibling;
} else {
while (!node.nextSibling && node !== rootNode) {
while (node && !node.nextSibling && node !== rootNode) {
node = node.parentElement;
if (node !== rootNode) {
leaveNodeCallback(node);

View File

@ -127,7 +127,7 @@ export default class EditorModel {
return this._parts;
}
public get autoComplete(): AutocompleteWrapperModel {
public get autoComplete(): AutocompleteWrapperModel | null {
if (this.activePartIdx === this.autoCompletePartIdx) {
return this._autoComplete;
}
@ -212,12 +212,12 @@ export default class EditorModel {
const transformAddedLen = this.getTransformAddedLen(newPosition, inputType, diff);
newPosition = this.positionForOffset(caretOffset + transformAddedLen, true);
}
this.updateCallback(newPosition, inputType, diff);
this.updateCallback?.(newPosition, inputType, diff);
return acPromise;
}
private getTransformAddedLen(newPosition: DocumentPosition, inputType: string, diff: IDiff): number {
const result = this.transformCallback(newPosition, inputType, diff);
const result = this.transformCallback?.(newPosition, inputType, diff);
return Number.isFinite(result) ? (result as number) : 0;
}
@ -268,13 +268,13 @@ export default class EditorModel {
// rerender even if editor contents didn't change
// to make sure the MessageEditor checks
// model.autoComplete being empty and closes it
this.updateCallback(pos);
this.updateCallback?.(pos);
};
private mergeAdjacentParts(): void {
let prevPart: Part | undefined;
for (let i = 0; i < this._parts.length; ++i) {
let part = this._parts[i];
let part: Part | undefined = this._parts[i];
const isEmpty = !part.text.length;
const isMerged = !isEmpty && prevPart && prevPart.merge?.(part);
if (isEmpty || isMerged) {
@ -452,13 +452,13 @@ export default class EditorModel {
*/
public transform(callback: ManualTransformCallback): Promise<void> {
const pos = callback();
let acPromise: Promise<void> = null;
let acPromise: Promise<void> | null = null;
if (!(pos instanceof Range)) {
acPromise = this.setActivePart(pos, true);
} else {
acPromise = Promise.resolve();
}
this.updateCallback(pos);
this.updateCallback?.(pos);
return acPromise;
}
}

View File

@ -422,7 +422,7 @@ class RoomPillPart extends PillPart {
protected setAvatar(node: HTMLElement): void {
let initialLetter = "";
let avatarUrl = Avatar.avatarUrlForRoom(this.room, 16, 16, "crop");
let avatarUrl = Avatar.avatarUrlForRoom(this.room ?? null, 16, 16, "crop");
if (!avatarUrl) {
initialLetter = Avatar.getInitialLetter(this.room?.name || this.resourceId) ?? "";
avatarUrl = Avatar.defaultAvatarUrlForString(this.room?.roomId ?? this.resourceId);
@ -541,7 +541,7 @@ export class PartCreator {
public constructor(
private readonly room: Room,
private readonly client: MatrixClient,
autoCompleteCreator: AutoCompleteCreator = null,
autoCompleteCreator: AutoCompleteCreator | null = null,
) {
// pre-create the creator as an object even without callback so it can already be passed
// to PillCandidatePart (e.g. while deserializing) and set later on
@ -574,7 +574,7 @@ export class PartCreator {
return this.plain(text);
}
public deserializePart(part: SerializedPart): Part {
public deserializePart(part: SerializedPart): Part | undefined {
switch (part.type) {
case Type.Plain:
return this.plain(part.text);
@ -612,7 +612,7 @@ export class PartCreator {
public roomPill(alias: string, roomId?: string): RoomPillPart {
let room: Room | undefined;
if (roomId || alias[0] !== "#") {
room = this.client.getRoom(roomId || alias);
room = this.client.getRoom(roomId || alias) ?? undefined;
} else {
room = this.client.getRooms().find((r) => {
return r.getCanonicalAlias() === alias || r.getAltAliases().includes(alias);
@ -691,7 +691,7 @@ export class CommandPartCreator extends PartCreator {
return new CommandPart(text, this.autoCompleteCreator);
}
public deserializePart(part: SerializedPart): Part {
public deserializePart(part: SerializedPart): Part | undefined {
if (part.type === Type.Command) {
return this.command(part.text);
} else {

View File

@ -93,8 +93,8 @@ function reconcileLine(lineContainer: ChildNode, parts: Part[]): void {
if (needsCaretNodeBefore(part, prevPart)) {
if (isCaretNode(currentNode as Element)) {
updateCaretNode(currentNode);
currentNode = currentNode.nextSibling;
updateCaretNode(currentNode!);
currentNode = currentNode!.nextSibling;
} else {
lineContainer.insertBefore(createCaretNode(), currentNode);
}

View File

@ -63,7 +63,7 @@ interface ISerializeOpts {
export function htmlSerializeIfNeeded(
model: EditorModel,
{ forceHTML = false, useMarkdown = true }: ISerializeOpts = {},
): string {
): string | undefined {
if (!useMarkdown) {
return escapeHtml(textSerialize(model)).replace(/\n/g, "<br/>");
}
@ -72,7 +72,7 @@ export function htmlSerializeIfNeeded(
return htmlSerializeFromMdIfNeeded(md, { forceHTML });
}
export function htmlSerializeFromMdIfNeeded(md: string, { forceHTML = false } = {}): string {
export function htmlSerializeFromMdIfNeeded(md: string, { forceHTML = false } = {}): string | undefined {
// copy of raw input to remove unwanted math later
const orig = md;

View File

@ -139,7 +139,7 @@ export const usePublicRoomDirectory = (): {
SdkConfig.getObject("room_directory")?.get("servers")?.includes(lsRoomServer) ||
SettingsStore.getValue("room_directory_servers")?.includes(lsRoomServer)
) {
roomServer = lsRoomServer;
roomServer = lsRoomServer!;
}
let instanceId: string | undefined = undefined;

View File

@ -19,7 +19,7 @@ import { useEffect, useState } from "react";
import SettingsStore from "../settings/SettingsStore";
// Hook to fetch the value of a setting and dynamically update when it changes
export const useSettingValue = <T>(settingName: string, roomId: string = null, excludeDefault = false): T => {
export const useSettingValue = <T>(settingName: string, roomId: string | null = null, excludeDefault = false): T => {
const [value, setValue] = useState(SettingsStore.getValue<T>(settingName, roomId, excludeDefault));
useEffect(() => {
@ -36,7 +36,7 @@ export const useSettingValue = <T>(settingName: string, roomId: string = null, e
};
// Hook to fetch whether a feature is enabled and dynamically update when that changes
export const useFeatureEnabled = (featureName: string, roomId: string = null): boolean => {
export const useFeatureEnabled = (featureName: string, roomId: string | null = null): boolean => {
const [enabled, setEnabled] = useState(SettingsStore.getValue<boolean>(featureName, roomId));
useEffect(() => {

View File

@ -23,7 +23,7 @@ import { SlidingSyncManager } from "../SlidingSyncManager";
export interface SlidingSyncRoomSearchOpts {
limit: number;
query?: string;
query: string;
}
export const useSlidingSyncRoomSearch = (): {
@ -55,7 +55,7 @@ export const useSlidingSyncRoomSearch = (): {
room_name_like: term,
},
});
const rooms = [];
const rooms: Room[] = [];
const { roomIndexToRoomId } = SlidingSyncManager.instance.slidingSync.getListData(
SlidingSyncManager.ListSearch,
)!;

View File

@ -31,7 +31,7 @@ export const useTimeout = (handler: Handler, timeoutMs: number): void => {
// Set up timer
useEffect(() => {
const timeoutID = window.setTimeout(() => {
savedHandler.current();
savedHandler.current?.();
}, timeoutMs);
return () => clearTimeout(timeoutID);
}, [timeoutMs]);
@ -50,7 +50,7 @@ export const useInterval = (handler: Handler, intervalMs: number): void => {
// Set up timer
useEffect(() => {
const intervalID = window.setInterval(() => {
savedHandler.current();
savedHandler.current?.();
}, intervalMs);
return () => clearInterval(intervalID);
}, [intervalMs]);

View File

@ -22,7 +22,7 @@ import { useLatestResult } from "./useLatestResult";
export interface IUserDirectoryOpts {
limit: number;
query?: string;
query: string;
}
export const useUserDirectory = (): {

View File

@ -27,6 +27,7 @@ import { IEventWithRoomId, IMatrixProfile, IResultRoomEvents } from "matrix-js-s
import { logger } from "matrix-js-sdk/src/logger";
import { EventType } from "matrix-js-sdk/src/@types/event";
import { ClientEvent, MatrixClient } from "matrix-js-sdk/src/client";
import { ISyncStateData, SyncState } from "matrix-js-sdk/src/sync";
import PlatformPeg from "../PlatformPeg";
import { MatrixClientPeg } from "../MatrixClientPeg";
@ -50,11 +51,11 @@ interface ICrawler {
*/
export default class EventIndex extends EventEmitter {
private crawlerCheckpoints: ICrawlerCheckpoint[] = [];
private crawler: ICrawler = null;
private currentCheckpoint: ICrawlerCheckpoint = null;
private crawler: ICrawler | null = null;
private currentCheckpoint: ICrawlerCheckpoint | null = null;
public async init(): Promise<void> {
const indexManager = PlatformPeg.get().getEventIndexingManager();
const indexManager = PlatformPeg.get()?.getEventIndexingManager();
this.crawlerCheckpoints = await indexManager.loadCheckpoints();
logger.log("EventIndex: Loaded checkpoints", this.crawlerCheckpoints);
@ -91,7 +92,7 @@ export default class EventIndex extends EventEmitter {
* Get crawler checkpoints for the encrypted rooms and store them in the index.
*/
public async addInitialCheckpoints(): Promise<void> {
const indexManager = PlatformPeg.get().getEventIndexingManager();
const indexManager = PlatformPeg.get()?.getEventIndexingManager();
const client = MatrixClientPeg.get();
const rooms = client.getRooms();
@ -157,8 +158,8 @@ export default class EventIndex extends EventEmitter {
* - Every other sync, tell the event index to commit all the queued up
* live events
*/
private onSync = async (state: string, prevState: string, data: object): Promise<void> => {
const indexManager = PlatformPeg.get().getEventIndexingManager();
private onSync = async (state: SyncState, prevState: SyncState | null, data?: ISyncStateData): Promise<void> => {
const indexManager = PlatformPeg.get()?.getEventIndexingManager();
if (prevState === "PREPARED" && state === "SYNCING") {
// If our indexer is empty we're most likely running Element the
@ -188,7 +189,7 @@ export default class EventIndex extends EventEmitter {
*/
private onRoomTimeline = async (
ev: MatrixEvent,
room: Room | null,
room: Room | undefined,
toStartOfTimeline: boolean,
removed: boolean,
data: IRoomTimelineData,
@ -198,7 +199,7 @@ export default class EventIndex extends EventEmitter {
const client = MatrixClientPeg.get();
// We only index encrypted rooms locally.
if (!client.isRoomEncrypted(ev.getRoomId())) return;
if (!client.isRoomEncrypted(ev.getRoomId()!)) return;
if (ev.isRedaction()) {
return this.redactEvent(ev);
@ -228,7 +229,7 @@ export default class EventIndex extends EventEmitter {
* We cannot rely on Room.redaction as this only fires if the redaction applied to an event the js-sdk has loaded.
*/
private redactEvent = async (ev: MatrixEvent): Promise<void> => {
const indexManager = PlatformPeg.get().getEventIndexingManager();
const indexManager = PlatformPeg.get()?.getEventIndexingManager();
try {
await indexManager.deleteEvent(ev.getAssociatedId());
@ -321,15 +322,15 @@ export default class EventIndex extends EventEmitter {
* @param {MatrixEvent} ev The event that should be added to the index.
*/
private async addLiveEventToIndex(ev: MatrixEvent): Promise<void> {
const indexManager = PlatformPeg.get().getEventIndexingManager();
const indexManager = PlatformPeg.get()?.getEventIndexingManager();
if (!this.isValidEvent(ev)) return;
if (!indexManager || !this.isValidEvent(ev)) return;
const e = this.eventToJson(ev);
const profile = {
displayname: ev.sender.rawDisplayName,
avatar_url: ev.sender.getMxcAvatarUrl(),
displayname: ev.sender?.rawDisplayName,
avatar_url: ev.sender?.getMxcAvatarUrl(),
};
await indexManager.addEventToIndex(e, profile);
@ -353,7 +354,7 @@ export default class EventIndex extends EventEmitter {
}
private async addRoomCheckpoint(roomId: string, fullCrawl = false): Promise<void> {
const indexManager = PlatformPeg.get().getEventIndexingManager();
const indexManager = PlatformPeg.get()?.getEventIndexingManager();
const client = MatrixClientPeg.get();
const room = client.getRoom(roomId);
@ -401,7 +402,7 @@ export default class EventIndex extends EventEmitter {
let cancelled = false;
const client = MatrixClientPeg.get();
const indexManager = PlatformPeg.get().getEventIndexingManager();
const indexManager = PlatformPeg.get()?.getEventIndexingManager();
this.crawler = {
cancel: () => {
@ -649,7 +650,7 @@ export default class EventIndex extends EventEmitter {
* task, and closes the index.
*/
public async close(): Promise<void> {
const indexManager = PlatformPeg.get().getEventIndexingManager();
const indexManager = PlatformPeg.get()?.getEventIndexingManager();
this.removeListeners();
this.stopCrawler();
await indexManager.closeEventIndex();
@ -665,7 +666,7 @@ export default class EventIndex extends EventEmitter {
* of search results once the search is done.
*/
public async search(searchArgs: ISearchArgs): Promise<IResultRoomEvents> {
const indexManager = PlatformPeg.get().getEventIndexingManager();
const indexManager = PlatformPeg.get()?.getEventIndexingManager();
return indexManager.searchEventIndex(searchArgs);
}
@ -693,11 +694,11 @@ export default class EventIndex extends EventEmitter {
public async loadFileEvents(
room: Room,
limit = 10,
fromEvent: string = null,
fromEvent?: string,
direction: string = EventTimeline.BACKWARDS,
): Promise<MatrixEvent[]> {
const client = MatrixClientPeg.get();
const indexManager = PlatformPeg.get().getEventIndexingManager();
const indexManager = PlatformPeg.get()?.getEventIndexingManager();
const loadArgs: ILoadArgs = {
roomId: room.roomId,
@ -790,7 +791,7 @@ export default class EventIndex extends EventEmitter {
timeline: EventTimeline,
room: Room,
limit = 10,
fromEvent: string = null,
fromEvent?: string,
direction: string = EventTimeline.BACKWARDS,
): Promise<boolean> {
const matrixEvents = await this.loadFileEvents(room, limit, fromEvent, direction);
@ -807,7 +808,7 @@ export default class EventIndex extends EventEmitter {
// Add the events to the timeline of the file panel.
matrixEvents.forEach((e) => {
if (!timelineSet.eventIdToTimeline(e.getId())) {
if (!timelineSet.eventIdToTimeline(e.getId()!)) {
timelineSet.addEventToTimeline(e, timeline, direction == EventTimeline.BACKWARDS);
}
});
@ -817,7 +818,7 @@ export default class EventIndex extends EventEmitter {
// Set the pagination token to the oldest event that we retrieved.
if (matrixEvents.length > 0) {
paginationToken = matrixEvents[matrixEvents.length - 1].getId();
paginationToken = matrixEvents[matrixEvents.length - 1].getId()!;
ret = true;
}
@ -878,11 +879,11 @@ export default class EventIndex extends EventEmitter {
): Promise<boolean> => {
const timeline = timelineIndex.timeline;
const timelineSet = timeline.getTimelineSet();
const token = timeline.getPaginationToken(direction);
const token = timeline.getPaginationToken(direction) ?? undefined;
const ret = await this.populateFileTimeline(timelineSet, timeline, room, limit, token, direction);
timelineIndex.pendingPaginate = null;
timelineIndex.pendingPaginate = undefined;
timelineWindow.extend(direction, limit);
return ret;
@ -900,9 +901,9 @@ export default class EventIndex extends EventEmitter {
* @return {Promise<IIndexStats>} A promise that will resolve to the index
* statistics.
*/
public async getStats(): Promise<IIndexStats> {
const indexManager = PlatformPeg.get().getEventIndexingManager();
return indexManager.getStats();
public async getStats(): Promise<IIndexStats | undefined> {
const indexManager = PlatformPeg.get()?.getEventIndexingManager();
return indexManager?.getStats();
}
/**
@ -914,9 +915,9 @@ export default class EventIndex extends EventEmitter {
* @return {Promise<boolean>} Returns true if the index contains events for
* the given room, false otherwise.
*/
public async isRoomIndexed(roomId: string): Promise<boolean> {
const indexManager = PlatformPeg.get().getEventIndexingManager();
return indexManager.isRoomIndexed(roomId);
public async isRoomIndexed(roomId: string): Promise<boolean | undefined> {
const indexManager = PlatformPeg.get()?.getEventIndexingManager();
return indexManager?.isRoomIndexed(roomId);
}
/**

View File

@ -36,8 +36,8 @@ const INDEX_VERSION = 1;
* you'll find a `EventIndex` hanging on the `EventIndexPeg`.
*/
export class EventIndexPeg {
public index: EventIndex = null;
public error: Error = null;
public index: EventIndex | null = null;
public error: Error | null = null;
private _supportIsInstalled = false;
@ -49,7 +49,7 @@ export class EventIndexPeg {
* EventIndex was successfully initialized, false otherwise.
*/
public async init(): Promise<boolean> {
const indexManager = PlatformPeg.get().getEventIndexingManager();
const indexManager = PlatformPeg.get()?.getEventIndexingManager();
if (!indexManager) {
logger.log("EventIndex: Platform doesn't support event indexing, not initializing.");
return false;
@ -78,11 +78,14 @@ export class EventIndexPeg {
*/
public async initEventIndex(): Promise<boolean> {
const index = new EventIndex();
const indexManager = PlatformPeg.get().getEventIndexingManager();
const indexManager = PlatformPeg.get()?.getEventIndexingManager();
const client = MatrixClientPeg.get();
if (!indexManager || !client) {
throw new Error("Unable to init event index");
}
const userId = client.getUserId();
const deviceId = client.getDeviceId();
const userId = client.getUserId()!;
const deviceId = client.getDeviceId()!;
try {
await indexManager.initEventIndex(userId, deviceId);
@ -120,7 +123,7 @@ export class EventIndexPeg {
* does not mean that support is installed.
*/
public platformHasSupport(): boolean {
return PlatformPeg.get().getEventIndexingManager() !== null;
return PlatformPeg.get()?.getEventIndexingManager() != null;
}
/**
@ -141,7 +144,7 @@ export class EventIndexPeg {
*
* @return {EventIndex} The current event index.
*/
public get(): EventIndex {
public get(): EventIndex | null {
return this.index;
}
@ -178,9 +181,9 @@ export class EventIndexPeg {
* deleted.
*/
public async deleteEventIndex(): Promise<void> {
const indexManager = PlatformPeg.get().getEventIndexingManager();
const indexManager = PlatformPeg.get()?.getEventIndexingManager();
if (indexManager !== null) {
if (indexManager) {
await this.unset();
logger.log("EventIndex: Deleting event index.");
await indexManager.deleteEventIndex();

View File

@ -33,18 +33,13 @@ export enum Kind {
}
export class IntegrationManagerInstance {
public readonly apiUrl: string;
public readonly uiUrl: string;
public readonly kind: string;
public readonly id: string; // only applicable in some cases
// Per the spec: UI URL is optional.
public constructor(kind: string, apiUrl: string, uiUrl: string = apiUrl, id?: string) {
this.kind = kind;
this.apiUrl = apiUrl;
this.uiUrl = uiUrl;
this.id = id;
}
public constructor(
public readonly kind: string,
public readonly apiUrl: string,
public readonly uiUrl: string = apiUrl,
public readonly id?: string, // only applicable in some cases
) {}
public get name(): string {
const parsed = url.parse(this.uiUrl);
@ -62,7 +57,7 @@ export class IntegrationManagerInstance {
return new ScalarAuthClient(this.apiUrl, this.uiUrl);
}
public async open(room: Room = null, screen: string = null, integrationId: string = null): Promise<void> {
public async open(room: Room, screen?: string, integrationId?: string): Promise<void> {
if (!SettingsStore.getValue("integrationProvisioning")) {
return IntegrationManagers.sharedInstance().showDisabledDialog();
}

View File

@ -40,7 +40,7 @@ export class IntegrationManagers {
private managers: IntegrationManagerInstance[] = [];
private client: MatrixClient;
private primaryManager: IntegrationManagerInstance;
private primaryManager: IntegrationManagerInstance | null;
public static sharedInstance(): IntegrationManagers {
if (!IntegrationManagers.instance) {
@ -146,7 +146,7 @@ export class IntegrationManagers {
}
public getOrderedManagers(): IntegrationManagerInstance[] {
const ordered = [];
const ordered: IntegrationManagerInstance[] = [];
for (const kind of KIND_PREFERENCE) {
const managers = this.managers.filter((m) => m.kind === kind);
if (!managers || !managers.length) continue;
@ -161,7 +161,7 @@ export class IntegrationManagers {
return ordered;
}
public getPrimaryManager(): IntegrationManagerInstance {
public getPrimaryManager(): IntegrationManagerInstance | null {
if (this.hasManager()) {
if (this.primaryManager) return this.primaryManager;
@ -195,7 +195,7 @@ export class IntegrationManagers {
* @returns {Promise<IntegrationManagerInstance>} Resolves to an integration manager instance,
* or null if none was found.
*/
public async tryDiscoverManager(domainName: string): Promise<IntegrationManagerInstance> {
public async tryDiscoverManager(domainName: string): Promise<IntegrationManagerInstance | null> {
logger.log("Looking up integration manager via .well-known");
if (domainName.startsWith("http:") || domainName.startsWith("https:")) {
// trim off the scheme and just use the domain

View File

@ -194,7 +194,7 @@ const annotateStrings = (result: TranslatedString, translationKey: string): Tran
*/
// eslint-next-line @typescript-eslint/naming-convention
export function _t(text: string, variables?: IVariables): string;
export function _t(text: string, variables: IVariables, tags: Tags): React.ReactNode;
export function _t(text: string, variables: IVariables | undefined, tags: Tags): React.ReactNode;
export function _t(text: string, variables?: IVariables, tags?: Tags): TranslatedString {
// The translation returns text so there's no XSS vector here (no unsafe HTML, no code execution)
const { translated } = safeCounterpartTranslate(text, variables);

View File

@ -19,7 +19,7 @@ import * as linkifyjs from "linkifyjs";
import { Opts, registerCustomProtocol, registerPlugin } from "linkifyjs";
import linkifyElement from "linkify-element";
import linkifyString from "linkify-string";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { User } from "matrix-js-sdk/src/matrix";
import {
parsePermalink,
@ -105,13 +105,9 @@ function matrixOpaqueIdLinkifyParser({
function onUserClick(event: MouseEvent, userId: string): void {
event.preventDefault();
const member = new RoomMember(null, userId);
if (!member) {
return;
}
dis.dispatch<ViewUserPayload>({
action: Action.ViewUser,
member: member,
member: new User(userId),
});
}

View File

@ -101,10 +101,10 @@ export class ProxiedModuleApi implements ModuleApi {
password: string,
displayName?: string,
): Promise<AccountAuthInfo> {
const hsUrl = SdkConfig.get("validated_server_config").hsUrl;
const hsUrl = SdkConfig.get("validated_server_config")?.hsUrl;
const client = Matrix.createClient({ baseUrl: hsUrl });
const deviceName =
SdkConfig.get("default_device_display_name") || PlatformPeg.get().getDefaultDeviceDisplayName();
SdkConfig.get("default_device_display_name") || PlatformPeg.get()?.getDefaultDeviceDisplayName();
const req: IRegisterRequestParams = {
username,
password,
@ -134,9 +134,9 @@ export class ProxiedModuleApi implements ModuleApi {
return {
homeserverUrl: hsUrl,
userId: creds.user_id,
deviceId: creds.device_id,
accessToken: creds.access_token,
userId: creds.user_id!,
deviceId: creds.device_id!,
accessToken: creds.access_token!,
};
}
@ -163,8 +163,8 @@ export class ProxiedModuleApi implements ModuleApi {
navigateToPermalink(uri);
const parts = parsePermalink(uri);
if (parts.roomIdOrAlias && andJoin) {
let roomId = parts.roomIdOrAlias;
if (parts?.roomIdOrAlias && andJoin) {
let roomId: string | undefined = parts.roomIdOrAlias;
let servers = parts.viaServers;
if (roomId.startsWith("#")) {
roomId = getCachedRoomIDForAlias(parts.roomIdOrAlias);

View File

@ -53,6 +53,7 @@ export class PushRuleVectorState {
} else if (pushRuleVectorState === VectorState.Loud) {
return StandardActions.ACTION_HIGHLIGHT_DEFAULT_SOUND;
}
return [];
}
/**

View File

@ -76,7 +76,7 @@ export class ConsoleLogger {
}
public bypassRageshake(fnName: LogFunctionName, ...args: (Error | DOMException | object | string)[]): void {
this.originalFunctions[fnName](...args);
this.originalFunctions[fnName]?.(...args);
}
public log(level: string, ...args: (Error | DOMException | object | string)[]): void {
@ -152,7 +152,7 @@ export class IndexedDBLogStore {
};
req.onerror = () => {
const err = "Failed to open log database: " + req.error.name;
const err = "Failed to open log database: " + req.error?.name;
logger.error(err);
reject(new Error(err));
};
@ -234,7 +234,7 @@ export class IndexedDBLogStore {
};
txn.onerror = () => {
logger.error("Failed to flush logs : ", txn.error);
reject(new Error("Failed to write logs: " + txn.error.message));
reject(new Error("Failed to write logs: " + txn.error?.message));
};
objStore.add(this.generateLogEntry(lines));
const lastModStore = txn.objectStore("logslastmod");
@ -267,7 +267,7 @@ export class IndexedDBLogStore {
const query = objectStore.index("id").openCursor(IDBKeyRange.only(id), "prev");
let lines = "";
query.onerror = () => {
reject(new Error("Query failed: " + query.error.message));
reject(new Error("Query failed: " + query.error?.message));
};
query.onsuccess = () => {
const cursor = query.result;
@ -322,7 +322,7 @@ export class IndexedDBLogStore {
resolve();
};
txn.onerror = () => {
reject(new Error("Failed to delete logs for " + `'${id}' : ${query.error.message}`));
reject(new Error("Failed to delete logs for " + `'${id}' : ${query.error?.message}`));
};
// delete last modified entries
const lastModStore = txn.objectStore("logslastmod");
@ -401,14 +401,14 @@ export class IndexedDBLogStore {
*/
function selectQuery<T>(
store: IDBIndex | IDBObjectStore,
keyRange: IDBKeyRange,
keyRange: IDBKeyRange | undefined,
resultMapper: (cursor: IDBCursorWithValue) => T,
): Promise<T[]> {
const query = store.openCursor(keyRange);
return new Promise((resolve, reject) => {
const results: T[] = [];
query.onerror = () => {
reject(new Error("Query failed: " + query.error.message));
reject(new Error("Query failed: " + query.error?.message));
};
// collect results
query.onsuccess = () => {

View File

@ -437,7 +437,7 @@ export default class SettingsStore {
level: SettingLevel,
roomId: string | null,
calculatedValue: any,
calculatedAtLevel: SettingLevel,
calculatedAtLevel: SettingLevel | null,
): any {
let resultingValue = calculatedValue;

View File

@ -36,7 +36,7 @@ export default class IncompatibleController extends SettingController {
level: SettingLevel,
roomId: string,
calculatedValue: any,
calculatedAtLevel: SettingLevel,
calculatedAtLevel: SettingLevel | null,
): any {
if (this.incompatibleSetting) {
return this.forcedValue;

View File

@ -53,7 +53,7 @@ export class NotificationsEnabledController extends SettingController {
level: SettingLevel,
roomId: string,
calculatedValue: any,
calculatedAtLevel: SettingLevel,
calculatedAtLevel: SettingLevel | null,
): any {
if (!getNotifier().isPossible()) return false;

View File

@ -35,7 +35,7 @@ export class OrderedMultiController extends SettingController {
level: SettingLevel,
roomId: string,
calculatedValue: any,
calculatedAtLevel: SettingLevel,
calculatedAtLevel: SettingLevel | null,
): any {
for (const controller of this.controllers) {
const override = controller.getValueOverride(level, roomId, calculatedValue, calculatedAtLevel);

View File

@ -27,7 +27,7 @@ export default class ReducedMotionController extends SettingController {
level: SettingLevel,
roomId: string,
calculatedValue: any,
calculatedAtLevel: SettingLevel,
calculatedAtLevel: SettingLevel | null,
): any {
if (this.prefersReducedMotion()) {
return false;

View File

@ -41,7 +41,7 @@ export default abstract class SettingController {
level: SettingLevel,
roomId: string | null,
calculatedValue: any,
calculatedAtLevel: SettingLevel,
calculatedAtLevel: SettingLevel | null,
): any {
return null; // no override
}

View File

@ -26,7 +26,7 @@ export default class ThemeController extends SettingController {
level: SettingLevel,
roomId: string,
calculatedValue: any,
calculatedAtLevel: SettingLevel,
calculatedAtLevel: SettingLevel | null,
): any {
if (!calculatedValue) return null; // Don't override null themes

View File

@ -34,7 +34,7 @@ export default class UIFeatureController extends SettingController {
level: SettingLevel,
roomId: string,
calculatedValue: any,
calculatedAtLevel: SettingLevel,
calculatedAtLevel: SettingLevel | null,
): any {
if (this.settingDisabled) {
// per the docs: we force a disabled state when the feature isn't active

View File

@ -22,7 +22,7 @@ const SIZE_LARGE = { w: 800, h: 600 };
const SIZE_NORMAL_LANDSCAPE = { w: 324, h: 324 }; // for w > h
const SIZE_NORMAL_PORTRAIT = { w: Math.ceil(324 * (9 / 16)), h: 324 }; // for h > w
type Dimensions = { w: number; h: number };
type Dimensions = { w?: number; h?: number };
export enum ImageSize {
Normal = "normal",
@ -36,7 +36,7 @@ export enum ImageSize {
* @returns {Dimensions} The suggested maximum dimensions for the image
*/
export function suggestedSize(size: ImageSize, contentSize: Dimensions, maxHeight?: number): Dimensions {
const aspectRatio = contentSize.w / contentSize.h;
const aspectRatio = contentSize.w! / contentSize.h!;
const portrait = aspectRatio < 1;
const maxSize = size === ImageSize.Large ? SIZE_LARGE : portrait ? SIZE_NORMAL_PORTRAIT : SIZE_NORMAL_LANDSCAPE;

View File

@ -38,7 +38,7 @@ export class RoomScrollStateStore {
// from the focussedEvent.
private scrollStateMap = new Map<string, ScrollState>();
public getScrollState(roomId: string): ScrollState {
public getScrollState(roomId: string): ScrollState | undefined {
return this.scrollStateMap.get(roomId);
}

View File

@ -712,7 +712,7 @@ export class RoomViewStore extends EventEmitter {
}
// The mxEvent if one is currently being replied to/quoted
public getQuotingEvent(): Optional<MatrixEvent> {
public getQuotingEvent(): MatrixEvent | null {
return this.state.replyingToEvent;
}

View File

@ -17,6 +17,7 @@ limitations under the License.
import { Room } from "matrix-js-sdk/src/models/room";
import { logger } from "matrix-js-sdk/src/logger";
import { MSC3575Filter, SlidingSyncEvent } from "matrix-js-sdk/src/sliding-sync";
import { Optional } from "matrix-events-sdk";
import { RoomUpdateCause, TagID, OrderedDefaultTagIDs, DefaultTagID } from "./models";
import { ITagMap, ListAlgorithm, SortAlgorithm } from "./algorithms/models";
@ -79,12 +80,11 @@ export class SlidingRoomListStoreClass extends AsyncStoreWithClient<IState> impl
private tagIdToSortAlgo: Record<TagID, SortAlgorithm> = {};
private tagMap: ITagMap = {};
private counts: Record<TagID, number> = {};
private stickyRoomId: string | null;
private stickyRoomId: Optional<string>;
public constructor(dis: MatrixDispatcher, private readonly context: SdkContextClass) {
super(dis);
this.setMaxListeners(20); // RoomList + LeftPanel + 8xRoomSubList + spares
this.stickyRoomId = null;
}
public async setTagSorting(tagId: TagID, sort: SortAlgorithm): Promise<void> {

View File

@ -24,14 +24,17 @@ import SettingsStore from "../../../settings/SettingsStore";
import DMRoomMap from "../../../utils/DMRoomMap";
export class ReactionEventPreview implements IPreview {
public getTextFor(event: MatrixEvent, tagId?: TagID, isThread?: boolean): string {
public getTextFor(event: MatrixEvent, tagId?: TagID, isThread?: boolean): string | null {
const showDms = SettingsStore.getValue("feature_roomlist_preview_reactions_dms");
const showAll = SettingsStore.getValue("feature_roomlist_preview_reactions_all");
const roomId = event.getRoomId();
if (!roomId) return null; // not a room event
// If we're not showing all reactions, see if we're showing DMs instead
if (!showAll) {
// If we're not showing reactions on DMs, or we are and the room isn't a DM, skip
if (!(showDms && DMRoomMap.shared().getUserIdForRoomId(event.getRoomId()))) {
if (!(showDms && DMRoomMap.shared().getUserIdForRoomId(roomId))) {
return null;
}
}
@ -42,7 +45,7 @@ export class ReactionEventPreview implements IPreview {
const reaction = relation.key;
if (!reaction) return null; // invalid reaction (unknown format)
if (isThread || isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId(), tagId)) {
if (isThread || isSelf(event) || !shouldPrefixMessagesIn(roomId, tagId)) {
return reaction;
} else {
return _t("%(senderName)s: %(reaction)s", { senderName: getSenderName(event), reaction });

View File

@ -27,7 +27,7 @@ export function isSelf(event: MatrixEvent): boolean {
return event.getSender() === selfUserId;
}
export function shouldPrefixMessagesIn(roomId: string, tagId: TagID): boolean {
export function shouldPrefixMessagesIn(roomId: string, tagId?: TagID): boolean {
if (tagId !== DefaultTagID.DM) return true;
// We don't prefix anything in 1:1s

View File

@ -137,7 +137,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
userIdsBySpace: new Map<Room["roomId"], Set<string>>(),
};
// The space currently selected in the Space Panel
private _activeSpace?: SpaceKey = MetaSpace.Home; // set properly by onReady
private _activeSpace: SpaceKey = MetaSpace.Home; // set properly by onReady
private _suggestedRooms: ISuggestedRoom[] = [];
private _invitedSpaces = new Set<Room>();
private spaceOrderLocalEchoMap = new Map<string, string>();
@ -812,13 +812,13 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
const spaceDiff = mapDiff(prevChildSpacesBySpace, this.childSpacesBySpace);
// filter out keys which changed by reference only by checking whether the sets differ
const roomsChanged = roomDiff.changed.filter((k) => {
return setHasDiff(prevRoomsBySpace.get(k), this.roomIdsBySpace.get(k));
return setHasDiff(prevRoomsBySpace.get(k)!, this.roomIdsBySpace.get(k)!);
});
const usersChanged = userDiff.changed.filter((k) => {
return setHasDiff(prevUsersBySpace.get(k), this.userIdsBySpace.get(k));
return setHasDiff(prevUsersBySpace.get(k)!, this.userIdsBySpace.get(k)!);
});
const spacesChanged = spaceDiff.changed.filter((k) => {
return setHasDiff(prevChildSpacesBySpace.get(k), this.childSpacesBySpace.get(k));
return setHasDiff(prevChildSpacesBySpace.get(k)!, this.childSpacesBySpace.get(k)!);
});
const changeSet = new Set([
@ -1142,9 +1142,8 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
// restore selected state from last session if any and still valid
const lastSpaceId = window.localStorage.getItem(ACTIVE_SPACE_LS_KEY);
const valid =
lastSpaceId && !isMetaSpace(lastSpaceId)
? this.matrixClient.getRoom(lastSpaceId)
: enabledMetaSpaces[lastSpaceId];
lastSpaceId &&
(!isMetaSpace(lastSpaceId) ? this.matrixClient.getRoom(lastSpaceId) : enabledMetaSpaces[lastSpaceId]);
if (valid) {
// don't context switch here as it may break permalinks
this.setActiveSpace(lastSpaceId, false);
@ -1285,7 +1284,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
public getNotificationState(key: SpaceKey): SpaceNotificationState {
if (this.notificationStateMap.has(key)) {
return this.notificationStateMap.get(key);
return this.notificationStateMap.get(key)!;
}
const state = new SpaceNotificationState(getRoomFn);

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
const getSpaceCollapsedKey = (roomId: string, parents: Set<string>): string => {
const getSpaceCollapsedKey = (roomId: string, parents?: Set<string>): string => {
const separator = "/";
let path = "";
if (parents) {
@ -35,12 +35,12 @@ export default class SpaceTreeLevelLayoutStore {
return SpaceTreeLevelLayoutStore.internalInstance;
}
public setSpaceCollapsedState(roomId: string, parents: Set<string>, collapsed: boolean): void {
public setSpaceCollapsedState(roomId: string, parents: Set<string> | undefined, collapsed: boolean): void {
// XXX: localStorage doesn't allow booleans
localStorage.setItem(getSpaceCollapsedKey(roomId, parents), collapsed.toString());
}
public getSpaceCollapsedState(roomId: string, parents: Set<string>, fallback: boolean): boolean {
public getSpaceCollapsedState(roomId: string, parents: Set<string> | undefined, fallback: boolean): boolean {
const collapsedLocalStorage = localStorage.getItem(getSpaceCollapsedKey(roomId, parents));
// XXX: localStorage doesn't allow booleans
return collapsedLocalStorage ? collapsedLocalStorage === "true" : fallback;

View File

@ -216,8 +216,8 @@ export class StopGapWidget extends EventEmitter {
const defaults: ITemplateParams = {
widgetRoomId: this.roomId,
currentUserId: this.client.getUserId()!,
userDisplayName: OwnProfileStore.instance.displayName,
userHttpAvatarUrl: OwnProfileStore.instance.getHttpAvatarUrl(),
userDisplayName: OwnProfileStore.instance.displayName ?? undefined,
userHttpAvatarUrl: OwnProfileStore.instance.getHttpAvatarUrl() ?? undefined,
clientId: ELEMENT_CLIENT_ID,
clientTheme: SettingsStore.getValue("theme"),
clientLanguage: getUserLanguage(),

View File

@ -477,7 +477,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
this.updateUserLayout(room, newLayout);
}
public hasMaximisedWidget(room: Room): boolean {
public hasMaximisedWidget(room?: Room): boolean {
return this.getContainerWidgets(room, Container.Center).length > 0;
}

View File

@ -85,11 +85,11 @@ export const showToast = (kind: Kind): void => {
const onAccept = async (): Promise<void> => {
if (kind === Kind.VERIFY_THIS_SESSION) {
Modal.createDialog(SetupEncryptionDialog, {}, null, /* priority = */ false, /* static = */ true);
Modal.createDialog(SetupEncryptionDialog, {}, undefined, /* priority = */ false, /* static = */ true);
} else {
const modal = Modal.createDialog(
Spinner,
null,
undefined,
"mx_Dialog_spinner",
/* priority */ false,
/* static */ true,

View File

@ -19,6 +19,7 @@ import { logger } from "matrix-js-sdk/src/logger";
import SdkConfig from "../SdkConfig";
import { MatrixClientPeg } from "../MatrixClientPeg";
import { Policies } from "../Terms";
export function getDefaultIdentityServerUrl(): string {
return SdkConfig.get("validated_server_config").isUrl;
@ -33,7 +34,7 @@ export function setToDefaultIdentityServer(): void {
}
export async function doesIdentityServerHaveTerms(fullUrl: string): Promise<boolean> {
let terms;
let terms: { policies?: Policies } | null;
try {
terms = await MatrixClientPeg.get().getTerms(SERVICE_TYPES.IS, fullUrl);
} catch (e) {
@ -45,7 +46,7 @@ export async function doesIdentityServerHaveTerms(fullUrl: string): Promise<bool
}
}
return terms && terms["policies"] && Object.keys(terms["policies"]).length > 0;
return !!terms?.["policies"] && Object.keys(terms["policies"]).length > 0;
}
export function doesAccountDataHaveIdentityServer(): boolean {

View File

@ -28,12 +28,12 @@ import { IDestroyable } from "./IDestroyable";
export class MediaEventHelper implements IDestroyable {
// Either an HTTP or Object URL (when encrypted) to the media.
public readonly sourceUrl: LazyValue<string>;
public readonly thumbnailUrl: LazyValue<string>;
public readonly sourceUrl: LazyValue<string | null>;
public readonly thumbnailUrl: LazyValue<string | null>;
// Either the raw or decrypted (when encrypted) contents of the file.
public readonly sourceBlob: LazyValue<Blob>;
public readonly thumbnailBlob: LazyValue<Blob>;
public readonly thumbnailBlob: LazyValue<Blob | null>;
public readonly media: Media;
@ -56,12 +56,12 @@ export class MediaEventHelper implements IDestroyable {
public destroy(): void {
if (this.media.isEncrypted) {
if (this.sourceUrl.present) URL.revokeObjectURL(this.sourceUrl.cachedValue);
if (this.thumbnailUrl.present) URL.revokeObjectURL(this.thumbnailUrl.cachedValue);
if (this.sourceUrl.cachedValue) URL.revokeObjectURL(this.sourceUrl.cachedValue);
if (this.thumbnailUrl.cachedValue) URL.revokeObjectURL(this.thumbnailUrl.cachedValue);
}
}
private prepareSourceUrl = async (): Promise<string> => {
private prepareSourceUrl = async (): Promise<string | null> => {
if (this.media.isEncrypted) {
const blob = await this.sourceBlob.value;
return URL.createObjectURL(blob);
@ -70,7 +70,7 @@ export class MediaEventHelper implements IDestroyable {
}
};
private prepareThumbnailUrl = async (): Promise<string> => {
private prepareThumbnailUrl = async (): Promise<string | null> => {
if (this.media.isEncrypted) {
const blob = await this.thumbnailBlob.value;
if (blob === null) return null;
@ -83,12 +83,12 @@ export class MediaEventHelper implements IDestroyable {
private fetchSource = (): Promise<Blob> => {
if (this.media.isEncrypted) {
const content = this.event.getContent<IMediaEventContent>();
return decryptFile(content.file, content.info);
return decryptFile(content.file!, content.info);
}
return this.media.downloadSource().then((r) => r.blob());
};
private fetchThumbnail = (): Promise<Blob> => {
private fetchThumbnail = (): Promise<Blob | null> => {
if (!this.media.hasThumbnail) return Promise.resolve(null);
if (this.media.isEncrypted) {
@ -113,7 +113,7 @@ export class MediaEventHelper implements IDestroyable {
const content = event.getContent();
const mediaMsgTypes: string[] = [MsgType.Video, MsgType.Audio, MsgType.Image, MsgType.File];
if (mediaMsgTypes.includes(content.msgtype)) return true;
if (mediaMsgTypes.includes(content.msgtype!)) return true;
if (typeof content.url === "string") return true;
// Finally, it's probably not media

View File

@ -96,7 +96,7 @@ export default class WidgetUtils {
* @param {[type]} testUrlString URL to check
* @return {Boolean} True if specified URL is a scalar URL
*/
public static isScalarUrl(testUrlString: string): boolean {
public static isScalarUrl(testUrlString?: string): boolean {
if (!testUrlString) {
logger.error("Scalar URL check failed. No URL specified");
return false;
@ -554,7 +554,7 @@ export default class WidgetUtils {
// noinspection JSIgnoredPromiseFromCall
IntegrationManagers.sharedInstance()
.getPrimaryManager()
.open(room, "type_" + app.type, app.id);
?.open(room, "type_" + app.type, app.id);
}
public static isManagedByManager(app: IApp): boolean {
@ -563,7 +563,7 @@ export default class WidgetUtils {
if (managers.hasManager()) {
// TODO: Pick the right manager for the widget
const defaultManager = managers.getPrimaryManager();
return WidgetUtils.isScalarUrl(defaultManager.apiUrl);
return WidgetUtils.isScalarUrl(defaultManager?.apiUrl);
}
}
return false;

View File

@ -29,7 +29,7 @@ export const useBeacon = (beaconInfoEvent: MatrixEvent): Beacon | undefined => {
const beaconIdentifier = getBeaconInfoIdentifier(beaconInfoEvent);
const room = matrixClient.getRoom(roomId);
const beaconInstance = room.currentState.beacons.get(beaconIdentifier);
const beaconInstance = room?.currentState.beacons.get(beaconIdentifier);
// TODO could this be less stupid?

View File

@ -173,7 +173,7 @@ export class ThreepidMember extends Member {
export interface IDMUserTileProps {
member: Member;
onRemove(member: Member): void;
onRemove?(member: Member): void;
}
/**

View File

@ -92,7 +92,7 @@ export async function createDmLocalRoom(client: MatrixClient, targets: Member[])
type: EventType.RoomMember,
content: {
displayname: target.name,
avatar_url: target.getMxcAvatarUrl(),
avatar_url: target.getMxcAvatarUrl() ?? undefined,
membership: "invite",
isDirect: true,
},
@ -107,7 +107,7 @@ export async function createDmLocalRoom(client: MatrixClient, targets: Member[])
type: EventType.RoomMember,
content: {
displayname: target.name,
avatar_url: target.getMxcAvatarUrl(),
avatar_url: target.getMxcAvatarUrl() ?? undefined,
membership: "join",
},
state_key: target.userId,

View File

@ -37,7 +37,7 @@ import { bulkSpaceBehaviour } from "./space";
import { SdkContextClass } from "../contexts/SDKContext";
export async function leaveRoomBehaviour(roomId: string, retry = true, spinner = true): Promise<void> {
let spinnerModal: IHandle<any>;
let spinnerModal: IHandle<any> | undefined;
if (spinner) {
spinnerModal = Modal.createDialog(Spinner, undefined, "mx_Dialog_spinner");
}
@ -60,7 +60,7 @@ export async function leaveRoomBehaviour(roomId: string, retry = true, spinner =
room
.getPendingEvents()
.filter((ev) => {
return [EventStatus.QUEUED, EventStatus.ENCRYPTING, EventStatus.SENDING].includes(ev.status);
return [EventStatus.QUEUED, EventStatus.ENCRYPTING, EventStatus.SENDING].includes(ev.status!);
})
.map(
(ev) =>

View File

@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
export const parseGeoUri = (uri: string): GeolocationCoordinates => {
function parse(s: string): number {
export const parseGeoUri = (uri: string): GeolocationCoordinates | undefined => {
function parse(s: string): number | undefined {
const ret = parseFloat(s);
if (Number.isNaN(ret)) {
return undefined;
@ -28,7 +28,7 @@ export const parseGeoUri = (uri: string): GeolocationCoordinates => {
if (!m) return;
const parts = m[1].split(";");
const coords = parts[0].split(",");
let uncertainty: number;
let uncertainty: number | undefined;
for (const param of parts.slice(1)) {
const m = param.match(/u=(.*)/);
if (m) uncertainty = parse(m[1]);

View File

@ -23,5 +23,5 @@ export function isLoggedIn(): boolean {
// store to hold this state.
// See also https://github.com/vector-im/element-web/issues/15034.
const app = window.matrixChat;
return app && (app as MatrixChat).state.view === Views.LOGGED_IN;
return (app as MatrixChat)?.state.view === Views.LOGGED_IN;
}

View File

@ -38,7 +38,7 @@ import { parsePermalink } from "./permalinks/Permalinks";
* The initial caller should pass in an empty array to seed the accumulator.
*/
export function pillifyLinks(nodes: ArrayLike<Element>, mxEvent: MatrixEvent, pills: Element[]): void {
const room = MatrixClientPeg.get().getRoom(mxEvent.getRoomId());
const room = MatrixClientPeg.get().getRoom(mxEvent.getRoomId()) ?? undefined;
const shouldShowPillAvatar = SettingsStore.getValue("Pill.shouldShowPillAvatar");
let node = nodes[0];
while (node) {
@ -49,7 +49,7 @@ export function pillifyLinks(nodes: ArrayLike<Element>, mxEvent: MatrixEvent, pi
node = node.nextSibling as Element;
continue;
} else if (node.tagName === "A" && node.getAttribute("href")) {
const href = node.getAttribute("href");
const href = node.getAttribute("href")!;
const parts = parsePermalink(href);
// If the link is a (localised) matrix.to link, replace it with a pill
// We don't want to pill event permalinks, so those are ignored.

View File

@ -71,17 +71,17 @@ export class VoiceBroadcastPlaybacksStore
}
public getByInfoEvent(infoEvent: MatrixEvent, client: MatrixClient): VoiceBroadcastPlayback {
const infoEventId = infoEvent.getId();
const infoEventId = infoEvent.getId()!;
if (!this.playbacks.has(infoEventId)) {
this.addPlayback(new VoiceBroadcastPlayback(infoEvent, client, this.recordings));
}
return this.playbacks.get(infoEventId);
return this.playbacks.get(infoEventId)!;
}
private addPlayback(playback: VoiceBroadcastPlayback): void {
const infoEventId = playback.infoEvent.getId();
const infoEventId = playback.infoEvent.getId()!;
if (this.playbacks.has(infoEventId)) return;

View File

@ -57,7 +57,7 @@ describe("ContentMessages", () => {
uploadContent: jest.fn().mockResolvedValue({ content_uri: "mxc://server/file" }),
} as unknown as MatrixClient;
contentMessages = new ContentMessages();
prom = Promise.resolve(null);
prom = Promise.resolve<ISendEventResponse>({ event_id: "$event_id" });
});
describe("sendStickerContentToRoom", () => {
@ -98,7 +98,7 @@ describe("ContentMessages", () => {
mocked(doMaybeLocalRoomAction).mockImplementation(
<T>(roomId: string, fn: (actualRoomId: string) => Promise<T>) => fn(roomId),
);
mocked(BlurhashEncoder.instance.getBlurhash).mockResolvedValue(undefined);
mocked(BlurhashEncoder.instance.getBlurhash).mockResolvedValue("blurhashstring");
});
it("should use m.image for image files", async () => {
@ -134,7 +134,7 @@ describe("ContentMessages", () => {
const element = createElement(tagName);
if (tagName === "video") {
(<HTMLVideoElement>element).load = jest.fn();
(<HTMLVideoElement>element).play = () => element.onloadeddata(new Event("loadeddata"));
(<HTMLVideoElement>element).play = () => element.onloadeddata!(new Event("loadeddata"));
(<HTMLVideoElement>element).pause = jest.fn();
Object.defineProperty(element, "videoHeight", {
get() {
@ -200,8 +200,8 @@ describe("ContentMessages", () => {
expect(upload.loaded).toBe(0);
expect(upload.total).toBe(file.size);
const { progressHandler } = mocked(client.uploadContent).mock.calls[0][1];
progressHandler({ loaded: 123, total: 1234 });
const { progressHandler } = mocked(client.uploadContent).mock.calls[0][1]!;
progressHandler!({ loaded: 123, total: 1234 });
expect(upload.loaded).toBe(123);
expect(upload.total).toBe(1234);
await prom;
@ -256,11 +256,11 @@ describe("ContentMessages", () => {
mocked(client.uploadContent).mockReturnValue(deferred.promise);
const file1 = new File([], "file1");
const prom = contentMessages.sendContentToRoom(file1, roomId, undefined, client, undefined);
const { abortController } = mocked(client.uploadContent).mock.calls[0][1];
expect(abortController.signal.aborted).toBeFalsy();
const { abortController } = mocked(client.uploadContent).mock.calls[0][1]!;
expect(abortController!.signal.aborted).toBeFalsy();
const [upload] = contentMessages.getCurrentUploads();
contentMessages.cancelUpload(upload);
expect(abortController.signal.aborted).toBeTruthy();
expect(abortController!.signal.aborted).toBeTruthy();
deferred.resolve({} as UploadResponse);
await prom;
});
@ -325,7 +325,7 @@ describe("uploadFile", () => {
const file = new Blob([]);
const prom = uploadFile(client, "!roomId:server", file);
mocked(client.uploadContent).mock.calls[0][1].abortController.abort();
mocked(client.uploadContent).mock.calls[0][1]!.abortController!.abort();
deferred.resolve({ content_uri: "mxc://foo/bar" });
await expect(prom).rejects.toThrowError(UploadCanceledError);
});

View File

@ -134,12 +134,14 @@ But this is not
expect(getNestedReplyText(event, mockPermalinkGenerator)).toMatchSnapshot();
});
[
["m.room.message", MsgType.Location, LocationAssetType.Pin],
["m.room.message", MsgType.Location, LocationAssetType.Self],
[M_BEACON_INFO.name, undefined, LocationAssetType.Pin],
[M_BEACON_INFO.name, undefined, LocationAssetType.Self],
].forEach(([type, msgType, assetType]) => {
(
[
["m.room.message", MsgType.Location, LocationAssetType.Pin],
["m.room.message", MsgType.Location, LocationAssetType.Self],
[M_BEACON_INFO.name, undefined, LocationAssetType.Pin],
[M_BEACON_INFO.name, undefined, LocationAssetType.Self],
] as const
).forEach(([type, msgType, assetType]) => {
it(`should create the expected fallback text for ${assetType} ${type}/${msgType}`, () => {
const event = makeTestEvent(type, {
body: "body",

View File

@ -49,7 +49,7 @@ describe("Terms", function () {
beforeEach(function () {
jest.clearAllMocks();
mockClient.getAccountData.mockReturnValue(null);
mockClient.getAccountData.mockReturnValue(undefined);
mockClient.getTerms.mockResolvedValue(null);
mockClient.setAccountData.mockResolvedValue({});
});
@ -59,7 +59,7 @@ describe("Terms", function () {
});
it("should prompt for all terms & services if no account data", async function () {
mockClient.getAccountData.mockReturnValue(null);
mockClient.getAccountData.mockReturnValue(undefined);
mockClient.getTerms.mockResolvedValue({
policies: {
policy_the_first: POLICY_ONE,

View File

@ -34,7 +34,7 @@ describe("RoomStatusBar", () => {
stubClient();
client = MatrixClientPeg.get();
room = new Room(ROOM_ID, client, client.getUserId(), {
room = new Room(ROOM_ID, client, client.getUserId()!, {
pendingEventOrdering: PendingEventOrdering.Detached,
});
event = mkEvent({
@ -72,7 +72,7 @@ describe("RoomStatusBar", () => {
length: 2,
});
rootEvent.status = EventStatus.NOT_SENT;
room.addPendingEvent(rootEvent, rootEvent.getId());
room.addPendingEvent(rootEvent, rootEvent.getId()!);
for (const event of events) {
event.status = EventStatus.NOT_SENT;
room.addPendingEvent(event, Date.now() + Math.random() + "");

View File

@ -99,10 +99,10 @@ describe("ThreadView", () => {
"is_falling_back": true,
"m.in_reply_to": {
event_id: rootEvent
.getThread()
.getThread()!
.lastReply((ev: MatrixEvent) => {
return ev.isRelation(THREAD_RELATION_TYPE.name);
})
})!
.getId(),
},
"rel_type": RelationType.Thread,
@ -126,8 +126,8 @@ describe("ThreadView", () => {
const res = mkThread({
room,
client: mockClient,
authorId: mockClient.getUserId(),
participantUserIds: [mockClient.getUserId()],
authorId: mockClient.getUserId()!,
participantUserIds: [mockClient.getUserId()!],
});
rootEvent = res.rootEvent;
@ -154,8 +154,8 @@ describe("ThreadView", () => {
const { rootEvent: rootEvent2 } = mkThread({
room,
client: mockClient,
authorId: mockClient.getUserId(),
participantUserIds: [mockClient.getUserId()],
authorId: mockClient.getUserId()!,
participantUserIds: [mockClient.getUserId()!],
});
act(() => {

View File

@ -54,8 +54,8 @@ describe("ThreadListContextMenu", () => {
const res = mkThread({
room,
client: mockClient,
authorId: mockClient.getUserId(),
participantUserIds: [mockClient.getUserId()],
authorId: mockClient.getUserId()!,
participantUserIds: [mockClient.getUserId()!],
});
event = res.rootEvent;

View File

@ -109,7 +109,7 @@ describe("<ExportDialog />", () => {
plainTextExporterInstance.export.mockClear();
// default setting value
ChatExportMock.getForceChatExportParameters.mockClear().mockReturnValue({});
mocked(ChatExportMock.getForceChatExportParameters!).mockClear().mockReturnValue({});
});
it("renders export dialog", () => {
@ -145,7 +145,7 @@ describe("<ExportDialog />", () => {
});
it("exports room using values set from ForceRoomExportParameters", async () => {
ChatExportMock.getForceChatExportParameters.mockReturnValue({
mocked(ChatExportMock.getForceChatExportParameters!).mockReturnValue({
format: ExportFormat.PlainText,
range: ExportType.Beginning,
sizeMb: 7000,
@ -198,7 +198,7 @@ describe("<ExportDialog />", () => {
});
it("does not render export format when set in ForceRoomExportParameters", () => {
ChatExportMock.getForceChatExportParameters.mockReturnValue({
mocked(ChatExportMock.getForceChatExportParameters!).mockReturnValue({
format: ExportFormat.PlainText,
});
const component = getComponent();
@ -219,7 +219,7 @@ describe("<ExportDialog />", () => {
});
it("does not render export type when set in ForceRoomExportParameters", () => {
ChatExportMock.getForceChatExportParameters.mockReturnValue({
mocked(ChatExportMock.getForceChatExportParameters!).mockReturnValue({
range: ExportType.Beginning,
});
const component = getComponent();
@ -310,7 +310,7 @@ describe("<ExportDialog />", () => {
});
it("does not render size limit input when set in ForceRoomExportParameters", () => {
ChatExportMock.getForceChatExportParameters.mockReturnValue({
mocked(ChatExportMock.getForceChatExportParameters!).mockReturnValue({
sizeMb: 10000,
});
const component = getComponent();
@ -321,7 +321,7 @@ describe("<ExportDialog />", () => {
* 2000mb size limit does not apply when higher limit is configured in config
*/
it("exports when size limit set in ForceRoomExportParameters is larger than 2000", async () => {
ChatExportMock.getForceChatExportParameters.mockReturnValue({
mocked(ChatExportMock.getForceChatExportParameters!).mockReturnValue({
sizeMb: 10000,
});
const component = getComponent();
@ -344,7 +344,7 @@ describe("<ExportDialog />", () => {
});
it("does not render input when set in ForceRoomExportParameters", () => {
ChatExportMock.getForceChatExportParameters.mockReturnValue({
mocked(ChatExportMock.getForceChatExportParameters!).mockReturnValue({
includeAttachments: false,
});
const component = getComponent();

View File

@ -74,7 +74,7 @@ describe("<DevicesPanel />", () => {
const toggleDeviceSelection = (container: HTMLElement, deviceId: string) =>
act(() => {
const checkbox = container.querySelector(`#device-tile-checkbox-${deviceId}`);
const checkbox = container.querySelector(`#device-tile-checkbox-${deviceId}`)!;
fireEvent.click(checkbox);
});
@ -204,7 +204,7 @@ describe("<DevicesPanel />", () => {
// close the modal without submission
act(() => {
const modalCloseButton = document.querySelector('[aria-label="Close dialog"]');
const modalCloseButton = document.querySelector('[aria-label="Close dialog"]')!;
fireEvent.click(modalCloseButton);
});

View File

@ -49,6 +49,7 @@ describe("PreferencesUserSettingsTab", () => {
const client = MatrixClientPeg.get();
jest.spyOn(client, "isVersionSupported").mockImplementation(async (version: string) => {
if (version === "v1.4") return val;
return false;
});
};
@ -61,8 +62,12 @@ describe("PreferencesUserSettingsTab", () => {
};
};
const expectSetValueToHaveBeenCalled = (name: string, roomId: string, level: SettingLevel, value: boolean) =>
expect(SettingsStore.setValue).toHaveBeenCalledWith(name, roomId, level, value);
const expectSetValueToHaveBeenCalled = (
name: string,
roomId: string | undefined,
level: SettingLevel,
value: boolean,
) => expect(SettingsStore.setValue).toHaveBeenCalledWith(name, roomId, level, value);
describe("with server support", () => {
beforeEach(() => {

View File

@ -923,7 +923,7 @@ describe("ElementCall", () => {
jest.spyOn(Modal, "createDialog").mockReturnValue({
finished: new Promise((r) => r([sourceId])),
} as IHandle<any[]>);
jest.spyOn(PlatformPeg.get(), "supportsDesktopCapturer").mockReturnValue(true);
jest.spyOn(PlatformPeg.get()!, "supportsDesktopCapturer").mockReturnValue(true);
await call.connect();
@ -951,7 +951,7 @@ describe("ElementCall", () => {
jest.spyOn(Modal, "createDialog").mockReturnValue({
finished: new Promise((r) => r([null])),
} as IHandle<any[]>);
jest.spyOn(PlatformPeg.get(), "supportsDesktopCapturer").mockReturnValue(true);
jest.spyOn(PlatformPeg.get()!, "supportsDesktopCapturer").mockReturnValue(true);
await call.connect();
@ -976,7 +976,7 @@ describe("ElementCall", () => {
});
it("replies with pending: false if we don't support desktop capturer", async () => {
jest.spyOn(PlatformPeg.get(), "supportsDesktopCapturer").mockReturnValue(false);
jest.spyOn(PlatformPeg.get()!, "supportsDesktopCapturer").mockReturnValue(false);
await call.connect();

View File

@ -31,7 +31,7 @@ describe("ImageSize", () => {
expect(size).toStrictEqual({ w: 800, h: 400 });
});
it("returns max values if content size is not specified", () => {
const size = suggestedSize(ImageSize.Normal, { w: null, h: null });
const size = suggestedSize(ImageSize.Normal, {});
expect(size).toStrictEqual({ w: 324, h: 324 });
});
it("returns integer values", () => {

View File

@ -102,9 +102,9 @@ describe("OwnBeaconStore", () => {
};
const expireBeaconAndEmit = (store: OwnBeaconStore, beaconInfoEvent: MatrixEvent): void => {
const beacon = store.getBeaconById(getBeaconInfoIdentifier(beaconInfoEvent));
const beacon = store.getBeaconById(getBeaconInfoIdentifier(beaconInfoEvent))!;
// time travel until beacon is expired
advanceDateAndTime(beacon.beaconInfo.timeout + 100);
advanceDateAndTime(beacon.beaconInfo!.timeout + 100);
// force an update on the beacon
// @ts-ignore
@ -118,13 +118,13 @@ describe("OwnBeaconStore", () => {
beaconInfoEvent: MatrixEvent,
isLive: boolean,
): void => {
const beacon = store.getBeaconById(getBeaconInfoIdentifier(beaconInfoEvent));
const beacon = store.getBeaconById(getBeaconInfoIdentifier(beaconInfoEvent))!;
// matches original state of event content
// except for live property
const updateEvent = makeBeaconInfoEvent(
beaconInfoEvent.getSender(),
beaconInfoEvent.getRoomId(),
{ isLive, timeout: beacon.beaconInfo.timeout },
beaconInfoEvent.getSender()!,
beaconInfoEvent.getRoomId()!,
{ isLive, timeout: beacon.beaconInfo!.timeout },
"update-event-id",
);
beacon.update(updateEvent);
@ -236,12 +236,12 @@ describe("OwnBeaconStore", () => {
expect(mockClient.sendEvent).toHaveBeenCalledWith(
room1Id,
M_BEACON.name,
makeBeaconContent(defaultLocationUri, now, alicesRoom1BeaconInfo.getId()),
makeBeaconContent(defaultLocationUri, now, alicesRoom1BeaconInfo.getId()!),
);
expect(mockClient.sendEvent).toHaveBeenCalledWith(
room2Id,
M_BEACON.name,
makeBeaconContent(defaultLocationUri, now, alicesRoom2BeaconInfo.getId()),
makeBeaconContent(defaultLocationUri, now, alicesRoom2BeaconInfo.getId()!),
);
});
});
@ -263,7 +263,7 @@ describe("OwnBeaconStore", () => {
it("destroys beacons", async () => {
const [room1] = makeRoomsWithStateEvents([alicesRoom1BeaconInfo]);
const store = await makeOwnBeaconStore();
const beacon = room1.currentState.beacons.get(getBeaconInfoIdentifier(alicesRoom1BeaconInfo));
const beacon = room1.currentState.beacons.get(getBeaconInfoIdentifier(alicesRoom1BeaconInfo))!;
const destroySpy = jest.spyOn(beacon, "destroy");
// @ts-ignore
store.onNotReady();
@ -559,7 +559,7 @@ describe("OwnBeaconStore", () => {
const [room1] = makeRoomsWithStateEvents([alicesRoom1BeaconInfo, alicesRoom2BeaconInfo]);
const store = await makeOwnBeaconStore();
const room1BeaconInstance = store.beacons.get(getBeaconInfoIdentifier(alicesRoom1BeaconInfo));
const room1BeaconInstance = store.beacons.get(getBeaconInfoIdentifier(alicesRoom1BeaconInfo))!;
const beaconDestroySpy = jest.spyOn(room1BeaconInstance, "destroy");
const emitSpy = jest.spyOn(store, "emit");
@ -610,7 +610,7 @@ describe("OwnBeaconStore", () => {
expect(store.hasLiveBeacons()).toBe(true);
const emitSpy = jest.spyOn(store, "emit");
const beacon = store.getBeaconById(getBeaconInfoIdentifier(alicesRoom1BeaconInfo));
const beacon = store.getBeaconById(getBeaconInfoIdentifier(alicesRoom1BeaconInfo))!;
beacon.destroy();
mockClient.emit(BeaconEvent.Destroy, beacon.identifier);

View File

@ -103,10 +103,11 @@ describe("SpaceStore", () => {
const viewRoom = (roomId: string) => defaultDispatcher.dispatch({ action: Action.ViewRoom, room_id: roomId }, true);
const run = async () => {
mocked(client).getRoom.mockImplementation((roomId) => rooms.find((room) => room.roomId === roomId));
mocked(client).getRoomUpgradeHistory.mockImplementation((roomId) => [
rooms.find((room) => room.roomId === roomId),
]);
mocked(client).getRoom.mockImplementation((roomId) => rooms.find((room) => room.roomId === roomId) || null);
mocked(client).getRoomUpgradeHistory.mockImplementation((roomId) => {
const room = rooms.find((room) => room.roomId === roomId);
return room ? [room] : [];
});
await testUtils.setupAsyncStoreWithClient(store, client);
jest.runOnlyPendingTimers();
};
@ -312,10 +313,12 @@ describe("SpaceStore", () => {
mkSpace(space3, [invite2]);
mkSpace(space4, [room4, fav2, space2, space3]);
mocked(client).getRoom.mockImplementation((roomId) => rooms.find((room) => room.roomId === roomId));
mocked(client).getRoom.mockImplementation(
(roomId) => rooms.find((room) => room.roomId === roomId) || null,
);
[fav1, fav2, fav3].forEach((roomId) => {
client.getRoom(roomId).tags = {
client.getRoom(roomId)!.tags = {
"m.favourite": {
order: 0.5,
},
@ -323,20 +326,20 @@ describe("SpaceStore", () => {
});
[invite1, invite2].forEach((roomId) => {
mocked(client.getRoom(roomId)).getMyMembership.mockReturnValue("invite");
mocked(client.getRoom(roomId)!).getMyMembership.mockReturnValue("invite");
});
// have dmPartner1 be in space1 with you
const mySpace1Member = new RoomMember(space1, testUserId);
mySpace1Member.membership = "join";
(rooms.find((r) => r.roomId === space1).getMembers as jest.Mock).mockReturnValue([
(rooms.find((r) => r.roomId === space1)!.getMembers as jest.Mock).mockReturnValue([
mySpace1Member,
dm1Partner,
]);
// have dmPartner2 be in space2 with you
const mySpace2Member = new RoomMember(space2, testUserId);
mySpace2Member.membership = "join";
(rooms.find((r) => r.roomId === space2).getMembers as jest.Mock).mockReturnValue([
(rooms.find((r) => r.roomId === space2)!.getMembers as jest.Mock).mockReturnValue([
mySpace2Member,
dm2Partner,
]);
@ -349,15 +352,15 @@ describe("SpaceStore", () => {
event: true,
type: EventType.SpaceParent,
room: room2,
user: client.getUserId(),
user: client.getUserId()!,
skey: space2,
content: { via: [], canonical: true },
ts: Date.now(),
}) as MatrixEvent,
]);
mocked(cliRoom2.currentState).getStateEvents.mockImplementation(room2MockStateEvents);
mocked(cliRoom2!.currentState).getStateEvents.mockImplementation(room2MockStateEvents);
const cliSpace2 = client.getRoom(space2);
mocked(cliSpace2.currentState).maySendStateEvent.mockImplementation(
mocked(cliSpace2!.currentState).maySendStateEvent.mockImplementation(
(evType: string, userId: string) => {
if (evType === EventType.SpaceChild) {
return userId === client.getUserId();
@ -368,13 +371,13 @@ describe("SpaceStore", () => {
// room 3 claims to be a child of space3 but is not due to invalid m.space.parent (permissions)
const cliRoom3 = client.getRoom(room3);
mocked(cliRoom3.currentState).getStateEvents.mockImplementation(
mocked(cliRoom3!.currentState).getStateEvents.mockImplementation(
testUtils.mockStateEventImplementation([
mkEvent({
event: true,
type: EventType.SpaceParent,
room: room3,
user: client.getUserId(),
user: client.getUserId()!,
skey: space3,
content: { via: [], canonical: true },
ts: Date.now(),
@ -382,7 +385,7 @@ describe("SpaceStore", () => {
]),
);
const cliSpace3 = client.getRoom(space3);
mocked(cliSpace3.currentState).maySendStateEvent.mockImplementation(
mocked(cliSpace3!.currentState).maySendStateEvent.mockImplementation(
(evType: string, userId: string) => {
if (evType === EventType.SpaceChild) {
return false;
@ -813,7 +816,7 @@ describe("SpaceStore", () => {
content: { membership: "join" },
ts: Date.now(),
});
const spaceRoom = client.getRoom(spaceId);
const spaceRoom = client.getRoom(spaceId)!;
mocked(spaceRoom.currentState).getStateEvents.mockImplementation(
testUtils.mockStateEventImplementation([memberEvent]),
);
@ -929,7 +932,7 @@ describe("SpaceStore", () => {
it("switch to unknown space is a nop", async () => {
expect(store.activeSpace).toBe(MetaSpace.Home);
const space = client.getRoom(room1); // not a space
const space = client.getRoom(room1)!; // not a space
store.setActiveSpace(space.roomId);
expect(fn).not.toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space.roomId);
expect(store.activeSpace).toBe(MetaSpace.Home);
@ -962,6 +965,7 @@ describe("SpaceStore", () => {
member.membership = "join";
return member;
}
return null;
});
client.emit(RoomStateEvent.Members, event, space.currentState, dm1Partner);
@ -1088,7 +1092,7 @@ describe("SpaceStore", () => {
mkSpace(space1, [room1, room2, room3]);
mkSpace(space2, [room1, room2]);
const cliRoom2 = client.getRoom(room2);
const cliRoom2 = client.getRoom(room2)!;
mocked(cliRoom2.currentState).getStateEvents.mockImplementation(
testUtils.mockStateEventImplementation([
mkEvent({
@ -1267,8 +1271,9 @@ describe("SpaceStore", () => {
case dm1Partner.userId:
return rootSpaceFriend;
}
return null;
});
expect(SpaceStore.instance.getSpaceFilteredUserIds(space1).has(dm1Partner.userId)).toBeFalsy();
expect(SpaceStore.instance.getSpaceFilteredUserIds(space1)!.has(dm1Partner.userId)).toBeFalsy();
const memberEvent = mkEvent({
event: true,
type: EventType.RoomMember,
@ -1281,7 +1286,7 @@ describe("SpaceStore", () => {
});
client.emit(RoomStateEvent.Members, memberEvent, rootSpace.currentState, dm1Partner);
jest.runOnlyPendingTimers();
expect(SpaceStore.instance.getSpaceFilteredUserIds(space1).has(dm1Partner.userId)).toBeTruthy();
expect(SpaceStore.instance.getSpaceFilteredUserIds(space1)!.has(dm1Partner.userId)).toBeTruthy();
const dm1Room = mkRoom(dm1);
dm1Room.getMyMembership.mockReturnValue("join");
client.emit(ClientEvent.Room, dm1Room);

View File

@ -198,10 +198,10 @@ describe("SlidingRoomListStore", () => {
return keyToListData[key] || null;
});
expect(store.getTagsForRoom(new Room(roomA, context.client!, context.client!.getUserId()))).toEqual([
expect(store.getTagsForRoom(new Room(roomA, context.client!, context.client!.getUserId()!))).toEqual([
DefaultTagID.Untagged,
]);
expect(store.getTagsForRoom(new Room(roomB, context.client!, context.client!.getUserId()))).toEqual([
expect(store.getTagsForRoom(new Room(roomB, context.client!, context.client!.getUserId()!))).toEqual([
DefaultTagID.Favourite,
DefaultTagID.Untagged,
]);
@ -221,9 +221,9 @@ describe("SlidingRoomListStore", () => {
0: roomA,
};
const rooms = [
new Room(roomA, context.client!, context.client!.getUserId()),
new Room(roomB, context.client!, context.client!.getUserId()),
new Room(roomC, context.client!, context.client!.getUserId()),
new Room(roomA, context.client!, context.client!.getUserId()!),
new Room(roomB, context.client!, context.client!.getUserId()!),
new Room(roomC, context.client!, context.client!.getUserId()!),
];
mocked(context.client!.getRoom).mockImplementation((roomId: string) => {
switch (roomId) {
@ -257,9 +257,9 @@ describe("SlidingRoomListStore", () => {
2: roomIdC,
0: roomIdA,
};
const roomA = new Room(roomIdA, context.client!, context.client!.getUserId());
const roomB = new Room(roomIdB, context.client!, context.client!.getUserId());
const roomC = new Room(roomIdC, context.client!, context.client!.getUserId());
const roomA = new Room(roomIdA, context.client!, context.client!.getUserId()!);
const roomB = new Room(roomIdB, context.client!, context.client!.getUserId()!);
const roomC = new Room(roomIdC, context.client!, context.client!.getUserId()!);
mocked(context.client!.getRoom).mockImplementation((roomId: string) => {
switch (roomId) {
case roomIdA:
@ -321,8 +321,8 @@ describe("SlidingRoomListStore", () => {
const tagId = DefaultTagID.Favourite;
const joinCount = 10;
// seed the store with 2 rooms
const roomA = new Room(roomIdA, context.client!, context.client!.getUserId());
const roomC = new Room(roomIdC, context.client!, context.client!.getUserId());
const roomA = new Room(roomIdA, context.client!, context.client!.getUserId()!);
const roomC = new Room(roomIdC, context.client!, context.client!.getUserId()!);
mocked(context.client!.getRoom).mockImplementation((roomId: string) => {
switch (roomId) {
case roomIdA:

View File

@ -29,11 +29,13 @@ import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
import { SpaceFilterCondition } from "../../../src/stores/room-list/filters/SpaceFilterCondition";
import DMRoomMap from "../../../src/utils/DMRoomMap";
let filter: SpaceFilterCondition = null;
let filter: SpaceFilterCondition | null = null;
const mockRoomListStore = {
addFilter: (f: SpaceFilterCondition) => (filter = f),
removeFilter: (): void => (filter = null),
removeFilter: (): void => {
filter = null;
},
} as unknown as RoomListStoreClass;
const getUserIdForRoomId = jest.fn();
@ -74,7 +76,7 @@ describe("SpaceWatcher", () => {
[MetaSpace.Orphans]: true,
});
client.getRoom.mockImplementation((roomId) => rooms.find((room) => room.roomId === roomId));
client.getRoom.mockImplementation((roomId) => rooms.find((room) => room.roomId === roomId) || null);
await setupAsyncStoreWithClient(store, client);
});
@ -99,7 +101,7 @@ describe("SpaceWatcher", () => {
await setShowAllRooms(false);
expect(filter).toBeInstanceOf(SpaceFilterCondition);
expect(filter["space"]).toBe(MetaSpace.Home);
expect(filter!["space"]).toBe(MetaSpace.Home);
});
it("sets filter correctly for all -> space transition", async () => {
@ -109,7 +111,7 @@ describe("SpaceWatcher", () => {
SpaceStore.instance.setActiveSpace(space1);
expect(filter).toBeInstanceOf(SpaceFilterCondition);
expect(filter["space"]).toBe(space1);
expect(filter!["space"]).toBe(space1);
});
it("removes filter for home -> all transition", async () => {
@ -128,7 +130,7 @@ describe("SpaceWatcher", () => {
SpaceStore.instance.setActiveSpace(space1);
expect(filter).toBeInstanceOf(SpaceFilterCondition);
expect(filter["space"]).toBe(space1);
expect(filter!["space"]).toBe(space1);
});
it("removes filter for space -> all transition", async () => {
@ -137,7 +139,7 @@ describe("SpaceWatcher", () => {
SpaceStore.instance.setActiveSpace(space1);
expect(filter).toBeInstanceOf(SpaceFilterCondition);
expect(filter["space"]).toBe(space1);
expect(filter!["space"]).toBe(space1);
SpaceStore.instance.setActiveSpace(MetaSpace.Home);
expect(filter).toBeNull();
@ -149,7 +151,7 @@ describe("SpaceWatcher", () => {
SpaceStore.instance.setActiveSpace(MetaSpace.Favourites);
expect(filter).toBeInstanceOf(SpaceFilterCondition);
expect(filter["space"]).toBe(MetaSpace.Favourites);
expect(filter!["space"]).toBe(MetaSpace.Favourites);
SpaceStore.instance.setActiveSpace(MetaSpace.Home);
expect(filter).toBeNull();
@ -161,7 +163,7 @@ describe("SpaceWatcher", () => {
SpaceStore.instance.setActiveSpace(MetaSpace.People);
expect(filter).toBeInstanceOf(SpaceFilterCondition);
expect(filter["space"]).toBe(MetaSpace.People);
expect(filter!["space"]).toBe(MetaSpace.People);
SpaceStore.instance.setActiveSpace(MetaSpace.Home);
expect(filter).toBeNull();
@ -173,7 +175,7 @@ describe("SpaceWatcher", () => {
SpaceStore.instance.setActiveSpace(MetaSpace.Orphans);
expect(filter).toBeInstanceOf(SpaceFilterCondition);
expect(filter["space"]).toBe(MetaSpace.Orphans);
expect(filter!["space"]).toBe(MetaSpace.Orphans);
SpaceStore.instance.setActiveSpace(MetaSpace.Home);
expect(filter).toBeNull();
@ -185,11 +187,11 @@ describe("SpaceWatcher", () => {
new SpaceWatcher(mockRoomListStore);
expect(filter).toBeInstanceOf(SpaceFilterCondition);
expect(filter["space"]).toBe(space1);
expect(filter!["space"]).toBe(space1);
SpaceStore.instance.setActiveSpace(MetaSpace.Home);
expect(filter).toBeInstanceOf(SpaceFilterCondition);
expect(filter["space"]).toBe(MetaSpace.Home);
expect(filter!["space"]).toBe(MetaSpace.Home);
});
it("updates filter correctly for space -> orphans transition", async () => {
@ -198,11 +200,11 @@ describe("SpaceWatcher", () => {
new SpaceWatcher(mockRoomListStore);
expect(filter).toBeInstanceOf(SpaceFilterCondition);
expect(filter["space"]).toBe(space1);
expect(filter!["space"]).toBe(space1);
SpaceStore.instance.setActiveSpace(MetaSpace.Orphans);
expect(filter).toBeInstanceOf(SpaceFilterCondition);
expect(filter["space"]).toBe(MetaSpace.Orphans);
expect(filter!["space"]).toBe(MetaSpace.Orphans);
});
it("updates filter correctly for orphans -> people transition", async () => {
@ -211,11 +213,11 @@ describe("SpaceWatcher", () => {
new SpaceWatcher(mockRoomListStore);
expect(filter).toBeInstanceOf(SpaceFilterCondition);
expect(filter["space"]).toBe(MetaSpace.Orphans);
expect(filter!["space"]).toBe(MetaSpace.Orphans);
SpaceStore.instance.setActiveSpace(MetaSpace.People);
expect(filter).toBeInstanceOf(SpaceFilterCondition);
expect(filter["space"]).toBe(MetaSpace.People);
expect(filter!["space"]).toBe(MetaSpace.People);
});
it("updates filter correctly for space -> space transition", async () => {
@ -224,11 +226,11 @@ describe("SpaceWatcher", () => {
new SpaceWatcher(mockRoomListStore);
expect(filter).toBeInstanceOf(SpaceFilterCondition);
expect(filter["space"]).toBe(space1);
expect(filter!["space"]).toBe(space1);
SpaceStore.instance.setActiveSpace(space2);
expect(filter).toBeInstanceOf(SpaceFilterCondition);
expect(filter["space"]).toBe(space2);
expect(filter!["space"]).toBe(space2);
});
it("doesn't change filter when changing showAllRooms mode to true", async () => {
@ -237,11 +239,11 @@ describe("SpaceWatcher", () => {
new SpaceWatcher(mockRoomListStore);
expect(filter).toBeInstanceOf(SpaceFilterCondition);
expect(filter["space"]).toBe(space1);
expect(filter!["space"]).toBe(space1);
await setShowAllRooms(true);
expect(filter).toBeInstanceOf(SpaceFilterCondition);
expect(filter["space"]).toBe(space1);
expect(filter!["space"]).toBe(space1);
});
it("doesn't change filter when changing showAllRooms mode to false", async () => {
@ -250,10 +252,10 @@ describe("SpaceWatcher", () => {
new SpaceWatcher(mockRoomListStore);
expect(filter).toBeInstanceOf(SpaceFilterCondition);
expect(filter["space"]).toBe(space1);
expect(filter!["space"]).toBe(space1);
await setShowAllRooms(false);
expect(filter).toBeInstanceOf(SpaceFilterCondition);
expect(filter["space"]).toBe(space1);
expect(filter!["space"]).toBe(space1);
});
});

View File

@ -109,14 +109,14 @@ describe("VisibilityProvider", () => {
});
it("should return false if visibility customisation returns false", () => {
mocked(RoomListCustomisations.isRoomVisible).mockReturnValue(false);
mocked(RoomListCustomisations.isRoomVisible!).mockReturnValue(false);
const room = createRoom();
expect(VisibilityProvider.instance.isRoomVisible(room)).toBe(false);
expect(RoomListCustomisations.isRoomVisible).toHaveBeenCalledWith(room);
});
it("should return true if visibility customisation returns true", () => {
mocked(RoomListCustomisations.isRoomVisible).mockReturnValue(true);
mocked(RoomListCustomisations.isRoomVisible!).mockReturnValue(true);
const room = createRoom();
expect(VisibilityProvider.instance.isRoomVisible(room)).toBe(true);
expect(RoomListCustomisations.isRoomVisible).toHaveBeenCalledWith(room);

View File

@ -80,8 +80,8 @@ describe("StopGapWidget", () => {
beforeEach(() => {
voiceBroadcastInfoEvent = mkEvent({
event: true,
room: client.getRoom("x").roomId,
user: client.getUserId(),
room: client.getRoom("x")?.roomId,
user: client.getUserId()!,
type: VoiceBroadcastInfoEventType,
content: {},
});

View File

@ -59,22 +59,22 @@ describe("WidgetPermissionStore", () => {
});
it("should persist OIDCState.Allowed for a widget", () => {
widgetPermissionStore.setOIDCState(w, WidgetKind.Account, null, OIDCState.Allowed);
widgetPermissionStore.setOIDCState(w, WidgetKind.Account, roomId, OIDCState.Allowed);
// check it remembered the value
expect(widgetPermissionStore.getOIDCState(w, WidgetKind.Account, null)).toEqual(OIDCState.Allowed);
expect(widgetPermissionStore.getOIDCState(w, WidgetKind.Account, roomId)).toEqual(OIDCState.Allowed);
});
it("should persist OIDCState.Denied for a widget", () => {
widgetPermissionStore.setOIDCState(w, WidgetKind.Account, null, OIDCState.Denied);
widgetPermissionStore.setOIDCState(w, WidgetKind.Account, roomId, OIDCState.Denied);
// check it remembered the value
expect(widgetPermissionStore.getOIDCState(w, WidgetKind.Account, null)).toEqual(OIDCState.Denied);
expect(widgetPermissionStore.getOIDCState(w, WidgetKind.Account, roomId)).toEqual(OIDCState.Denied);
});
it("should update OIDCState for a widget", () => {
widgetPermissionStore.setOIDCState(w, WidgetKind.Account, null, OIDCState.Allowed);
widgetPermissionStore.setOIDCState(w, WidgetKind.Account, null, OIDCState.Denied);
widgetPermissionStore.setOIDCState(w, WidgetKind.Account, roomId, OIDCState.Allowed);
widgetPermissionStore.setOIDCState(w, WidgetKind.Account, roomId, OIDCState.Denied);
// check it remembered the latest value
expect(widgetPermissionStore.getOIDCState(w, WidgetKind.Account, null)).toEqual(OIDCState.Denied);
expect(widgetPermissionStore.getOIDCState(w, WidgetKind.Account, roomId)).toEqual(OIDCState.Denied);
});
it("should scope the location for a widget when setting OIDC state", () => {

View File

@ -197,7 +197,7 @@ export const makeRoomWithBeacons = (
locationEvents?: MatrixEvent[],
): Beacon[] => {
const room = makeRoomWithStateEvents(beaconInfoEvents, { roomId, mockClient });
const beacons = beaconInfoEvents.map((event) => room.currentState.beacons.get(getBeaconInfoIdentifier(event)));
const beacons = beaconInfoEvents.map((event) => room.currentState.beacons.get(getBeaconInfoIdentifier(event))!);
if (locationEvents) {
beacons.forEach((beacon) => {
// this filtering happens in roomState, which is bypassed here

View File

@ -31,7 +31,7 @@ export const addTextToComposer = (container: HTMLElement, text: string) =>
getData: (type: string) => (type === "text/plain" ? text : undefined),
} as unknown as DataTransfer,
};
fireEvent.paste(container.querySelector('[role="textbox"]'), pasteEvent);
fireEvent.paste(container.querySelector('[role="textbox"]')!, pasteEvent);
});
export const addTextToComposerEnzyme = (wrapper: ReactWrapper, text: string) =>

View File

@ -37,7 +37,7 @@ export function untilDispatch(
dispatcher = defaultDispatcher,
timeout = 1000,
): Promise<ActionPayload> {
const callerLine = new Error().stack.toString().split("\n")[2];
const callerLine = new Error().stack!.toString().split("\n")[2];
if (typeof waitForAction === "string") {
const action = waitForAction;
waitForAction = (payload) => {
@ -89,10 +89,10 @@ export function untilDispatch(
export function untilEmission(
emitter: EventEmitter,
eventName: string,
check: (...args: any[]) => boolean = undefined,
check?: (...args: any[]) => boolean,
timeout = 1000,
): Promise<void> {
const callerLine = new Error().stack.toString().split("\n")[2];
const callerLine = new Error().stack!.toString().split("\n")[2];
return new Promise((resolve, reject) => {
let fulfilled = false;
let timeoutId: number;

View File

@ -58,7 +58,7 @@ describe("theme", () => {
// When
await new Promise((resolve) => {
setTheme("light").then(resolve);
lightTheme.onload(void 0);
lightTheme.onload!({} as Event);
});
// Then
@ -72,7 +72,7 @@ describe("theme", () => {
return expect(
new Promise((resolve) => {
setTheme("light").catch((e) => resolve(e));
lightTheme.onerror("call onerror");
lightTheme.onerror!("call onerror");
}),
).resolves.toBe("call onerror");
});

View File

@ -42,7 +42,7 @@ describe("useTopic", () => {
function RoomTopic() {
const topic = useTopic(room);
return <p>{topic.text}</p>;
return <p>{topic!.text}</p>;
}
render(<RoomTopic />);

View File

@ -85,10 +85,6 @@ describe("MegolmExportEncryption", function () {
MegolmExportEncryption = require("../../src/utils/MegolmExportEncryption");
});
afterAll(() => {
window.crypto = undefined;
});
describe("decrypt", function () {
it("should handle missing header", function () {
const input = stringToArray(`-----`);

Some files were not shown because too many files have changed in this diff Show More