Conform more of the codebase to strictNullChecks (#10518

* Conform more of the codebase to `strictNullChecks`

* Iterate

* Fix tests
This commit is contained in:
Michael Telatynski 2023-04-06 11:10:14 +01:00 committed by GitHub
parent e9cc88b872
commit 55d3548330
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 107 additions and 99 deletions

View file

@ -130,7 +130,7 @@ export async function loadSession(opts: ILoadSessionOpts = {}): Promise<boolean>
enableGuest = false; enableGuest = false;
} }
if (enableGuest && fragmentQueryParams.guest_user_id && fragmentQueryParams.guest_access_token) { if (enableGuest && guestHsUrl && fragmentQueryParams.guest_user_id && fragmentQueryParams.guest_access_token) {
logger.log("Using guest access credentials"); logger.log("Using guest access credentials");
return doSetLoggedIn( return doSetLoggedIn(
{ {
@ -150,7 +150,7 @@ export async function loadSession(opts: ILoadSessionOpts = {}): Promise<boolean>
return true; return true;
} }
if (enableGuest) { if (enableGuest && guestHsUrl) {
return registerAsGuest(guestHsUrl, guestIsUrl, defaultDeviceDisplayName); return registerAsGuest(guestHsUrl, guestIsUrl, defaultDeviceDisplayName);
} }
@ -174,7 +174,7 @@ export async function loadSession(opts: ILoadSessionOpts = {}): Promise<boolean>
* session is for a guest user, if an owner exists. If there is no stored session, * session is for a guest user, if an owner exists. If there is no stored session,
* return [null, null]. * return [null, null].
*/ */
export async function getStoredSessionOwner(): Promise<[string, boolean]> { export async function getStoredSessionOwner(): Promise<[string, boolean] | [null, null]> {
const { hsUrl, userId, hasAccessToken, isGuest } = await getStoredSessionVars(); const { hsUrl, userId, hasAccessToken, isGuest } = await getStoredSessionVars();
return hsUrl && userId && hasAccessToken ? [userId, isGuest] : [null, null]; return hsUrl && userId && hasAccessToken ? [userId, isGuest] : [null, null];
} }
@ -259,7 +259,7 @@ export function attemptTokenLogin(
}); });
} }
export function handleInvalidStoreError(e: InvalidStoreError): Promise<void> { export function handleInvalidStoreError(e: InvalidStoreError): Promise<void> | void {
if (e.reason === InvalidStoreError.TOGGLED_LAZY_LOADING) { if (e.reason === InvalidStoreError.TOGGLED_LAZY_LOADING) {
return Promise.resolve() return Promise.resolve()
.then(() => { .then(() => {
@ -292,7 +292,7 @@ export function handleInvalidStoreError(e: InvalidStoreError): Promise<void> {
} }
} }
function registerAsGuest(hsUrl: string, isUrl: string, defaultDeviceDisplayName: string): Promise<boolean> { function registerAsGuest(hsUrl: string, isUrl?: string, defaultDeviceDisplayName?: string): Promise<boolean> {
logger.log(`Doing guest login on ${hsUrl}`); logger.log(`Doing guest login on ${hsUrl}`);
// create a temporary MatrixClient to do the login // create a temporary MatrixClient to do the login
@ -346,14 +346,14 @@ export interface IStoredSession {
export async function getStoredSessionVars(): Promise<IStoredSession> { export async function getStoredSessionVars(): Promise<IStoredSession> {
const hsUrl = localStorage.getItem(HOMESERVER_URL_KEY); const hsUrl = localStorage.getItem(HOMESERVER_URL_KEY);
const isUrl = localStorage.getItem(ID_SERVER_URL_KEY); const isUrl = localStorage.getItem(ID_SERVER_URL_KEY);
let accessToken; let accessToken: string | undefined;
try { try {
accessToken = await StorageManager.idbLoad("account", "mx_access_token"); accessToken = await StorageManager.idbLoad("account", "mx_access_token");
} catch (e) { } catch (e) {
logger.error("StorageManager.idbLoad failed for account:mx_access_token", e); logger.error("StorageManager.idbLoad failed for account:mx_access_token", e);
} }
if (!accessToken) { if (!accessToken) {
accessToken = localStorage.getItem("mx_access_token"); accessToken = localStorage.getItem("mx_access_token") ?? undefined;
if (accessToken) { if (accessToken) {
try { try {
// try to migrate access token to IndexedDB if we can // try to migrate access token to IndexedDB if we can
@ -370,7 +370,7 @@ export async function getStoredSessionVars(): Promise<IStoredSession> {
const userId = localStorage.getItem("mx_user_id"); const userId = localStorage.getItem("mx_user_id");
const deviceId = localStorage.getItem("mx_device_id"); const deviceId = localStorage.getItem("mx_device_id");
let isGuest; let isGuest: boolean;
if (localStorage.getItem("mx_is_guest") !== null) { if (localStorage.getItem("mx_is_guest") !== null) {
isGuest = localStorage.getItem("mx_is_guest") === "true"; isGuest = localStorage.getItem("mx_is_guest") === "true";
} else { } else {
@ -447,7 +447,7 @@ export async function restoreFromLocalStorage(opts?: { ignoreGuest?: boolean }):
} }
let decryptedAccessToken = accessToken; let decryptedAccessToken = accessToken;
const pickleKey = await PlatformPeg.get().getPickleKey(userId, deviceId); const pickleKey = await PlatformPeg.get()?.getPickleKey(userId, deviceId);
if (pickleKey) { if (pickleKey) {
logger.log("Got pickle key"); logger.log("Got pickle key");
if (typeof accessToken !== "string") { if (typeof accessToken !== "string") {
@ -471,7 +471,7 @@ export async function restoreFromLocalStorage(opts?: { ignoreGuest?: boolean }):
homeserverUrl: hsUrl, homeserverUrl: hsUrl,
identityServerUrl: isUrl, identityServerUrl: isUrl,
guest: isGuest, guest: isGuest,
pickleKey: pickleKey, pickleKey: pickleKey ?? undefined,
freshLogin: freshLogin, freshLogin: freshLogin,
}, },
false, false,
@ -561,7 +561,8 @@ export async function hydrateSession(credentials: IMatrixClientCreds): Promise<M
if (!credentials.pickleKey) { if (!credentials.pickleKey) {
logger.info("Lifecycle#hydrateSession: Pickle key not provided - trying to get one"); logger.info("Lifecycle#hydrateSession: Pickle key not provided - trying to get one");
credentials.pickleKey = await PlatformPeg.get().getPickleKey(credentials.userId, credentials.deviceId); credentials.pickleKey =
(await PlatformPeg.get()?.getPickleKey(credentials.userId, credentials.deviceId)) ?? undefined;
} }
return doSetLoggedIn(credentials, overwrite); return doSetLoggedIn(credentials, overwrite);
@ -646,12 +647,10 @@ async function doSetLoggedIn(credentials: IMatrixClientCreds, clearStorageEnable
return client; return client;
} }
function showStorageEvictedDialog(): Promise<boolean> { async function showStorageEvictedDialog(): Promise<boolean> {
return new Promise((resolve) => { const { finished } = Modal.createDialog(StorageEvictedDialog);
Modal.createDialog(StorageEvictedDialog, { const [ok] = await finished;
onFinished: resolve, return !!ok;
});
});
} }
// Note: Babel 6 requires the `transform-builtin-extend` plugin for this to satisfy // Note: Babel 6 requires the `transform-builtin-extend` plugin for this to satisfy
@ -675,7 +674,7 @@ async function persistCredentials(credentials: IMatrixClientCreds): Promise<void
} }
if (credentials.pickleKey) { if (credentials.pickleKey) {
let encryptedAccessToken: IEncryptedPayload; let encryptedAccessToken: IEncryptedPayload | undefined;
try { try {
// try to encrypt the access token using the pickle key // try to encrypt the access token using the pickle key
const encrKey = await pickleKeyToAesKey(credentials.pickleKey); const encrKey = await pickleKeyToAesKey(credentials.pickleKey);
@ -741,7 +740,7 @@ export function logout(): void {
_isLoggingOut = true; _isLoggingOut = true;
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
PlatformPeg.get().destroyPickleKey(client.getUserId(), client.getDeviceId()); PlatformPeg.get()?.destroyPickleKey(client.getSafeUserId(), client.getDeviceId());
client.logout(true).then(onLoggedOut, (err) => { client.logout(true).then(onLoggedOut, (err) => {
// Just throwing an error here is going to be very unhelpful // Just throwing an error here is going to be very unhelpful
// if you're trying to log out because your server's down and // if you're trying to log out because your server's down and
@ -870,7 +869,7 @@ export async function onLoggedOut(): Promise<void> {
logger.log("Redirecting to external provider to finish logout"); logger.log("Redirecting to external provider to finish logout");
// XXX: Defer this so that it doesn't race with MatrixChat unmounting the world by going to /#/login // XXX: Defer this so that it doesn't race with MatrixChat unmounting the world by going to /#/login
window.setTimeout(() => { window.setTimeout(() => {
window.location.href = SdkConfig.get().logout_redirect_url; window.location.href = SdkConfig.get().logout_redirect_url!;
}, 100); }, 100);
} }
// Do this last to prevent racing `stopMatrixClient` and `on_logged_out` with MatrixChat handling Session.logged_out // Do this last to prevent racing `stopMatrixClient` and `on_logged_out` with MatrixChat handling Session.logged_out

View file

@ -160,7 +160,7 @@ class MatrixClientPegClass implements IMatrixClientPeg {
} }
public currentUserIsJustRegistered(): boolean { public currentUserIsJustRegistered(): boolean {
return this.matrixClient && this.matrixClient.credentials.userId === this.justRegisteredUserId; return !!this.matrixClient && this.matrixClient.credentials.userId === this.justRegisteredUserId;
} }
public userRegisteredWithinLastHours(hours: number): boolean { public userRegisteredWithinLastHours(hours: number): boolean {

View file

@ -79,7 +79,7 @@ export default class EditHistoryMessage extends React.PureComponent<IProps, ISta
ConfirmAndWaitRedactDialog, ConfirmAndWaitRedactDialog,
{ {
redact: async () => { redact: async () => {
await cli.redactEvent(event.getRoomId()!, event.getId()); await cli.redactEvent(event.getRoomId()!, event.getId()!);
}, },
}, },
"mx_Dialog_confirmredact", "mx_Dialog_confirmredact",

View file

@ -56,7 +56,7 @@ export default class ReactionsRowButton extends React.PureComponent<IProps, ISta
public onClick = (): void => { public onClick = (): void => {
const { mxEvent, myReactionEvent, content } = this.props; const { mxEvent, myReactionEvent, content } = this.props;
if (myReactionEvent) { if (myReactionEvent) {
this.context.redactEvent(mxEvent.getRoomId()!, myReactionEvent.getId()); this.context.redactEvent(mxEvent.getRoomId()!, myReactionEvent.getId()!);
} else { } else {
this.context.sendEvent(mxEvent.getRoomId()!, "m.reaction", { this.context.sendEvent(mxEvent.getRoomId()!, "m.reaction", {
"m.relates_to": { "m.relates_to": {

View file

@ -206,7 +206,7 @@ export default class PhoneNumbers extends React.Component<IProps, IState> {
const address = this.state.verifyMsisdn; const address = this.state.verifyMsisdn;
this.state.addTask this.state.addTask
?.haveMsisdnToken(token) ?.haveMsisdnToken(token)
.then(([finished]) => { .then(([finished] = []) => {
let newPhoneNumber = this.state.newPhoneNumber; let newPhoneNumber = this.state.newPhoneNumber;
if (finished) { if (finished) {
const msisdns = [...this.props.msisdns, { address, medium: ThreepidMedium.Phone }]; const msisdns = [...this.props.msisdns, { address, medium: ThreepidMedium.Phone }];

View file

@ -320,7 +320,7 @@ export default async function createRoom(opts: IOpts): Promise<string | null> {
return SpaceStore.instance.addRoomToSpace( return SpaceStore.instance.addRoomToSpace(
opts.parentSpace, opts.parentSpace,
roomId, roomId,
[client.getDomain()], [client.getDomain()!],
opts.suggested, opts.suggested,
); );
} }

View file

@ -30,7 +30,7 @@ export abstract class AsyncStoreWithClient<T extends Object> extends AsyncStore<
// Create an anonymous class to avoid code duplication // Create an anonymous class to avoid code duplication
const asyncStore = this; // eslint-disable-line @typescript-eslint/no-this-alias const asyncStore = this; // eslint-disable-line @typescript-eslint/no-this-alias
this.readyStore = new (class extends ReadyWatchingStore { this.readyStore = new (class extends ReadyWatchingStore {
public get mxClient(): MatrixClient { public get mxClient(): MatrixClient | null {
return this.matrixClient; return this.matrixClient;
} }
@ -48,7 +48,7 @@ export abstract class AsyncStoreWithClient<T extends Object> extends AsyncStore<
await this.readyStore.start(); await this.readyStore.start();
} }
public get matrixClient(): MatrixClient { public get matrixClient(): MatrixClient | null {
return this.readyStore.mxClient; return this.readyStore.mxClient;
} }

View file

@ -135,7 +135,7 @@ export default class AutoRageshakeStore extends AsyncStoreWithClient<IState> {
...eventInfo, ...eventInfo,
recipient_rageshake: rageshakeURL, recipient_rageshake: rageshakeURL,
}; };
this.matrixClient.sendToDevice( this.matrixClient?.sendToDevice(
AUTO_RS_REQUEST, AUTO_RS_REQUEST,
new Map([["messageContent.user_id", new Map([[messageContent.device_id, messageContent]])]]), new Map([["messageContent.user_id", new Map([[messageContent.device_id, messageContent]])]]),
); );

View file

@ -21,7 +21,7 @@ import { ClientEvent } from "matrix-js-sdk/src/client";
import SettingsStore from "../settings/SettingsStore"; import SettingsStore from "../settings/SettingsStore";
import { AsyncStoreWithClient } from "./AsyncStoreWithClient"; import { AsyncStoreWithClient } from "./AsyncStoreWithClient";
import defaultDispatcher from "../dispatcher/dispatcher"; import defaultDispatcher from "../dispatcher/dispatcher";
import { arrayHasDiff } from "../utils/arrays"; import { arrayHasDiff, filterBoolean } from "../utils/arrays";
import { SettingLevel } from "../settings/SettingLevel"; import { SettingLevel } from "../settings/SettingLevel";
import { Action } from "../dispatcher/actions"; import { Action } from "../dispatcher/actions";
import { SettingUpdatedPayload } from "../dispatcher/payloads/SettingUpdatedPayload"; import { SettingUpdatedPayload } from "../dispatcher/payloads/SettingUpdatedPayload";
@ -75,7 +75,7 @@ export class BreadcrumbsStore extends AsyncStoreWithClient<IState> {
public get meetsRoomRequirement(): boolean { public get meetsRoomRequirement(): boolean {
if (SettingsStore.getValue("feature_breadcrumbs_v2")) return true; if (SettingsStore.getValue("feature_breadcrumbs_v2")) return true;
const msc3946ProcessDynamicPredecessor = SettingsStore.getValue("feature_dynamic_room_predecessors"); const msc3946ProcessDynamicPredecessor = SettingsStore.getValue("feature_dynamic_room_predecessors");
return this.matrixClient?.getVisibleRooms(msc3946ProcessDynamicPredecessor).length >= 20; return !!this.matrixClient && this.matrixClient.getVisibleRooms(msc3946ProcessDynamicPredecessor).length >= 20;
} }
protected async onAction(payload: SettingUpdatedPayload | ViewRoomPayload | JoinRoomPayload): Promise<void> { protected async onAction(payload: SettingUpdatedPayload | ViewRoomPayload | JoinRoomPayload): Promise<void> {
@ -107,13 +107,17 @@ export class BreadcrumbsStore extends AsyncStoreWithClient<IState> {
await this.updateRooms(); await this.updateRooms();
await this.updateState({ enabled: SettingsStore.getValue("breadcrumbs", null) }); await this.updateState({ enabled: SettingsStore.getValue("breadcrumbs", null) });
this.matrixClient.on(RoomEvent.MyMembership, this.onMyMembership); if (this.matrixClient) {
this.matrixClient.on(ClientEvent.Room, this.onRoom); this.matrixClient.on(RoomEvent.MyMembership, this.onMyMembership);
this.matrixClient.on(ClientEvent.Room, this.onRoom);
}
} }
protected async onNotReady(): Promise<void> { protected async onNotReady(): Promise<void> {
this.matrixClient.removeListener(RoomEvent.MyMembership, this.onMyMembership); if (this.matrixClient) {
this.matrixClient.removeListener(ClientEvent.Room, this.onRoom); this.matrixClient.removeListener(RoomEvent.MyMembership, this.onMyMembership);
this.matrixClient.removeListener(ClientEvent.Room, this.onRoom);
}
} }
private onMyMembership = async (room: Room): Promise<void> => { private onMyMembership = async (room: Room): Promise<void> => {
@ -137,7 +141,7 @@ export class BreadcrumbsStore extends AsyncStoreWithClient<IState> {
let roomIds = SettingsStore.getValue<string[]>("breadcrumb_rooms"); let roomIds = SettingsStore.getValue<string[]>("breadcrumb_rooms");
if (!roomIds || roomIds.length === 0) roomIds = []; if (!roomIds || roomIds.length === 0) roomIds = [];
const rooms = roomIds.map((r) => this.matrixClient.getRoom(r)).filter((r) => !!r); const rooms = filterBoolean(roomIds.map((r) => this.matrixClient?.getRoom(r)));
const currentRooms = this.state.rooms || []; const currentRooms = this.state.rooms || [];
if (!arrayHasDiff(rooms, currentRooms)) return; // no change (probably echo) if (!arrayHasDiff(rooms, currentRooms)) return; // no change (probably echo)
await this.updateState({ rooms }); await this.updateState({ rooms });
@ -150,8 +154,8 @@ export class BreadcrumbsStore extends AsyncStoreWithClient<IState> {
// If the room is upgraded, use that room instead. We'll also splice out // If the room is upgraded, use that room instead. We'll also splice out
// any children of the room. // any children of the room.
const history = this.matrixClient.getRoomUpgradeHistory(room.roomId, false, msc3946ProcessDynamicPredecessor); const history = this.matrixClient?.getRoomUpgradeHistory(room.roomId, false, msc3946ProcessDynamicPredecessor);
if (history.length > 1) { if (history && history.length > 1) {
room = history[history.length - 1]; // Last room is most recent in history room = history[history.length - 1]; // Last room is most recent in history
// Take out any room that isn't the most recent room // Take out any room that isn't the most recent room

View file

@ -53,6 +53,7 @@ export class CallStore extends AsyncStoreWithClient<{}> {
} }
protected async onReady(): Promise<any> { protected async onReady(): Promise<any> {
if (!this.matrixClient) return;
// We assume that the calls present in a room are a function of room // We assume that the calls present in a room are a function of room
// widgets and group calls, so we initialize the room map here and then // widgets and group calls, so we initialize the room map here and then
// update it whenever those change // update it whenever those change
@ -90,9 +91,11 @@ export class CallStore extends AsyncStoreWithClient<{}> {
this.calls.clear(); this.calls.clear();
this._activeCalls.clear(); this._activeCalls.clear();
this.matrixClient.off(GroupCallEventHandlerEvent.Incoming, this.onGroupCall); if (this.matrixClient) {
this.matrixClient.off(GroupCallEventHandlerEvent.Outgoing, this.onGroupCall); this.matrixClient.off(GroupCallEventHandlerEvent.Incoming, this.onGroupCall);
this.matrixClient.off(GroupCallEventHandlerEvent.Ended, this.onGroupCall); this.matrixClient.off(GroupCallEventHandlerEvent.Outgoing, this.onGroupCall);
this.matrixClient.off(GroupCallEventHandlerEvent.Ended, this.onGroupCall);
}
WidgetStore.instance.off(UPDATE_EVENT, this.onWidgets); WidgetStore.instance.off(UPDATE_EVENT, this.onWidgets);
} }
@ -174,6 +177,7 @@ export class CallStore extends AsyncStoreWithClient<{}> {
} }
private onWidgets = (roomId: string | null): void => { private onWidgets = (roomId: string | null): void => {
if (!this.matrixClient) return;
if (roomId === null) { if (roomId === null) {
// This store happened to start before the widget store was done // This store happened to start before the widget store was done
// loading all rooms, so we need to initialize each room again // loading all rooms, so we need to initialize each room again

View file

@ -142,11 +142,13 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
} }
protected async onNotReady(): Promise<void> { protected async onNotReady(): Promise<void> {
this.matrixClient.removeListener(BeaconEvent.LivenessChange, this.onBeaconLiveness); if (this.matrixClient) {
this.matrixClient.removeListener(BeaconEvent.New, this.onNewBeacon); this.matrixClient.removeListener(BeaconEvent.LivenessChange, this.onBeaconLiveness);
this.matrixClient.removeListener(BeaconEvent.Update, this.onUpdateBeacon); this.matrixClient.removeListener(BeaconEvent.New, this.onNewBeacon);
this.matrixClient.removeListener(BeaconEvent.Destroy, this.onDestroyBeacon); this.matrixClient.removeListener(BeaconEvent.Update, this.onUpdateBeacon);
this.matrixClient.removeListener(RoomStateEvent.Members, this.onRoomStateMembers); this.matrixClient.removeListener(BeaconEvent.Destroy, this.onDestroyBeacon);
this.matrixClient.removeListener(RoomStateEvent.Members, this.onRoomStateMembers);
}
SettingsStore.unwatchSetting(this.dynamicWatcherRef ?? ""); SettingsStore.unwatchSetting(this.dynamicWatcherRef ?? "");
this.clearBeacons(); this.clearBeacons();
@ -164,11 +166,13 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
} }
protected async onReady(): Promise<void> { protected async onReady(): Promise<void> {
this.matrixClient.on(BeaconEvent.LivenessChange, this.onBeaconLiveness); if (this.matrixClient) {
this.matrixClient.on(BeaconEvent.New, this.onNewBeacon); this.matrixClient.on(BeaconEvent.LivenessChange, this.onBeaconLiveness);
this.matrixClient.on(BeaconEvent.Update, this.onUpdateBeacon); this.matrixClient.on(BeaconEvent.New, this.onNewBeacon);
this.matrixClient.on(BeaconEvent.Destroy, this.onDestroyBeacon); this.matrixClient.on(BeaconEvent.Update, this.onUpdateBeacon);
this.matrixClient.on(RoomStateEvent.Members, this.onRoomStateMembers); this.matrixClient.on(BeaconEvent.Destroy, this.onDestroyBeacon);
this.matrixClient.on(RoomStateEvent.Members, this.onRoomStateMembers);
}
this.dynamicWatcherRef = SettingsStore.watchSetting( this.dynamicWatcherRef = SettingsStore.watchSetting(
"feature_dynamic_room_predecessors", "feature_dynamic_room_predecessors",
null, null,
@ -200,7 +204,8 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
* Then consider it to have an error * Then consider it to have an error
*/ */
public beaconHasLocationPublishError = (beaconId: string): boolean => { public beaconHasLocationPublishError = (beaconId: string): boolean => {
return this.beaconLocationPublishErrorCounts.get(beaconId) >= BAIL_AFTER_CONSECUTIVE_ERROR_COUNT; const counts = this.beaconLocationPublishErrorCounts.get(beaconId);
return counts !== undefined && counts >= BAIL_AFTER_CONSECUTIVE_ERROR_COUNT;
}; };
public resetLocationPublishError = (beaconId: string): void => { public resetLocationPublishError = (beaconId: string): void => {
@ -246,7 +251,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
*/ */
private onNewBeacon = (_event: MatrixEvent, beacon: Beacon): void => { private onNewBeacon = (_event: MatrixEvent, beacon: Beacon): void => {
if (!isOwnBeacon(beacon, this.matrixClient.getUserId()!)) { if (!this.matrixClient || !isOwnBeacon(beacon, this.matrixClient.getUserId()!)) {
return; return;
} }
this.addBeacon(beacon); this.addBeacon(beacon);
@ -257,7 +262,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
* This will be called when a beacon is replaced * This will be called when a beacon is replaced
*/ */
private onUpdateBeacon = (_event: MatrixEvent, beacon: Beacon): void => { private onUpdateBeacon = (_event: MatrixEvent, beacon: Beacon): void => {
if (!isOwnBeacon(beacon, this.matrixClient.getUserId()!)) { if (!this.matrixClient || !isOwnBeacon(beacon, this.matrixClient.getUserId()!)) {
return; return;
} }
@ -296,7 +301,11 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
*/ */
private onRoomStateMembers = (_event: MatrixEvent, roomState: RoomState, member: RoomMember): void => { private onRoomStateMembers = (_event: MatrixEvent, roomState: RoomState, member: RoomMember): void => {
// no beacons for this room, ignore // no beacons for this room, ignore
if (!this.beaconsByRoomId.has(roomState.roomId) || member.userId !== this.matrixClient.getUserId()) { if (
!this.matrixClient ||
!this.beaconsByRoomId.has(roomState.roomId) ||
member.userId !== this.matrixClient.getUserId()
) {
return; return;
} }
@ -332,7 +341,8 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
}; };
private initialiseBeaconState = (): void => { private initialiseBeaconState = (): void => {
const userId = this.matrixClient.getUserId()!; if (!this.matrixClient) return;
const userId = this.matrixClient.getSafeUserId();
const visibleRooms = this.matrixClient.getVisibleRooms( const visibleRooms = this.matrixClient.getVisibleRooms(
SettingsStore.getValue("feature_dynamic_room_predecessors"), SettingsStore.getValue("feature_dynamic_room_predecessors"),
); );

View file

@ -112,7 +112,8 @@ export class OwnProfileStore extends AsyncStoreWithClient<IState> {
} }
protected async onReady(): Promise<void> { protected async onReady(): Promise<void> {
const myUserId = this.matrixClient.getUserId()!; if (!this.matrixClient) return;
const myUserId = this.matrixClient.getSafeUserId();
this.monitoredUser = this.matrixClient.getUser(myUserId); this.monitoredUser = this.matrixClient.getUser(myUserId);
if (this.monitoredUser) { if (this.monitoredUser) {
this.monitoredUser.on(UserEvent.DisplayName, this.onProfileUpdate); this.monitoredUser.on(UserEvent.DisplayName, this.onProfileUpdate);
@ -132,9 +133,10 @@ export class OwnProfileStore extends AsyncStoreWithClient<IState> {
private onProfileUpdate = throttle( private onProfileUpdate = throttle(
async (): Promise<void> => { async (): Promise<void> => {
if (!this.matrixClient) return;
// We specifically do not use the User object we stored for profile info as it // We specifically do not use the User object we stored for profile info as it
// could easily be wrong (such as per-room instead of global profile). // could easily be wrong (such as per-room instead of global profile).
const profileInfo = await this.matrixClient.getProfileInfo(this.matrixClient.getUserId()!); const profileInfo = await this.matrixClient.getProfileInfo(this.matrixClient.getSafeUserId());
if (profileInfo.displayname) { if (profileInfo.displayname) {
window.localStorage.setItem(KEY_DISPLAY_NAME, profileInfo.displayname); window.localStorage.setItem(KEY_DISPLAY_NAME, profileInfo.displayname);
} else { } else {

View file

@ -27,7 +27,6 @@ import defaultDispatcher from "../dispatcher/dispatcher";
import WidgetEchoStore from "../stores/WidgetEchoStore"; import WidgetEchoStore from "../stores/WidgetEchoStore";
import ActiveWidgetStore from "../stores/ActiveWidgetStore"; import ActiveWidgetStore from "../stores/ActiveWidgetStore";
import WidgetUtils from "../utils/WidgetUtils"; import WidgetUtils from "../utils/WidgetUtils";
import { WidgetType } from "../widgets/WidgetType";
import { UPDATE_EVENT } from "./AsyncStore"; import { UPDATE_EVENT } from "./AsyncStore";
interface IState {} interface IState {}
@ -74,6 +73,7 @@ export default class WidgetStore extends AsyncStoreWithClient<IState> {
} }
protected async onReady(): Promise<any> { protected async onReady(): Promise<any> {
if (!this.matrixClient) return;
this.matrixClient.on(ClientEvent.Room, this.onRoom); this.matrixClient.on(ClientEvent.Room, this.onRoom);
this.matrixClient.on(RoomStateEvent.Events, this.onRoomStateEvents); this.matrixClient.on(RoomStateEvent.Events, this.onRoomStateEvents);
this.matrixClient.getRooms().forEach((room: Room) => { this.matrixClient.getRooms().forEach((room: Room) => {
@ -83,8 +83,10 @@ export default class WidgetStore extends AsyncStoreWithClient<IState> {
} }
protected async onNotReady(): Promise<any> { protected async onNotReady(): Promise<any> {
this.matrixClient.off(ClientEvent.Room, this.onRoom); if (this.matrixClient) {
this.matrixClient.off(RoomStateEvent.Events, this.onRoomStateEvents); this.matrixClient.off(ClientEvent.Room, this.onRoom);
this.matrixClient.off(RoomStateEvent.Events, this.onRoomStateEvents);
}
this.widgetMap = new Map(); this.widgetMap = new Map();
this.roomMap = new Map(); this.roomMap = new Map();
await this.reset({}); await this.reset({});
@ -95,9 +97,9 @@ export default class WidgetStore extends AsyncStoreWithClient<IState> {
return; return;
} }
private onWidgetEchoStoreUpdate = (roomId: string, widgetId: string): void => { private onWidgetEchoStoreUpdate = (roomId: string): void => {
this.initRoom(roomId); this.initRoom(roomId);
this.loadRoomWidgets(this.matrixClient.getRoom(roomId)); this.loadRoomWidgets(this.matrixClient?.getRoom(roomId) ?? null);
this.emit(UPDATE_EVENT, roomId); this.emit(UPDATE_EVENT, roomId);
}; };
@ -174,7 +176,7 @@ export default class WidgetStore extends AsyncStoreWithClient<IState> {
if (ev.getType() !== "im.vector.modular.widgets") return; // TODO: Support m.widget too if (ev.getType() !== "im.vector.modular.widgets") return; // TODO: Support m.widget too
const roomId = ev.getRoomId()!; const roomId = ev.getRoomId()!;
this.initRoom(roomId); this.initRoom(roomId);
this.loadRoomWidgets(this.matrixClient.getRoom(roomId)); this.loadRoomWidgets(this.matrixClient?.getRoom(roomId) ?? null);
this.emit(UPDATE_EVENT, roomId); this.emit(UPDATE_EVENT, roomId);
}; };
@ -207,24 +209,6 @@ export default class WidgetStore extends AsyncStoreWithClient<IState> {
roomApps.widgets = roomApps.widgets.filter((app) => !(app.id === widgetId && app.roomId === roomId)); roomApps.widgets = roomApps.widgets.filter((app) => !(app.id === widgetId && app.roomId === roomId));
} }
} }
public doesRoomHaveConference(room: Room): boolean {
const roomInfo = this.getRoom(room.roomId);
if (!roomInfo) return false;
const currentWidgets = roomInfo.widgets.filter((w) => WidgetType.JITSI.matches(w.type));
const hasPendingWidgets = WidgetEchoStore.roomHasPendingWidgetsOfType(room.roomId, [], WidgetType.JITSI);
return currentWidgets.length > 0 || hasPendingWidgets;
}
public isJoinedToConferenceIn(room: Room): boolean {
const roomInfo = this.getRoom(room.roomId);
if (!roomInfo) return false;
// A persistent conference widget indicates that we're participating
const widgets = roomInfo.widgets.filter((w) => WidgetType.JITSI.matches(w.type));
return widgets.some((w) => ActiveWidgetStore.instance.getWidgetPersistence(w.id, room.roomId));
}
} }
window.mxWidgetStore = WidgetStore.instance; window.mxWidgetStore = WidgetStore.instance;

View file

@ -47,6 +47,7 @@ export class RoomEchoChamber extends GenericEchoChamber<RoomEchoContext, CachedR
} }
private onAccountData = (event: MatrixEvent): void => { private onAccountData = (event: MatrixEvent): void => {
if (!this.matrixClient) return;
if (event.getType() === EventType.PushRules) { if (event.getType() === EventType.PushRules) {
const currentVolume = this.properties.get(CachedRoomKey.NotificationVolume); const currentVolume = this.properties.get(CachedRoomKey.NotificationVolume);
const newVolume = getRoomNotifsState(this.matrixClient, this.context.room.roomId); const newVolume = getRoomNotifsState(this.matrixClient, this.context.room.roomId);

View file

@ -119,6 +119,7 @@ export class RoomNotificationStateStore extends AsyncStoreWithClient<IState> {
* @internal public for test * @internal public for test
*/ */
public emitUpdateIfStateChanged = (state: SyncState, forceEmit: boolean): void => { public emitUpdateIfStateChanged = (state: SyncState, forceEmit: boolean): void => {
if (!this.matrixClient) return;
// Only count visible rooms to not torment the user with notification counts in rooms they can't see. // Only count visible rooms to not torment the user with notification counts in rooms they can't see.
// This will include highlights from the previous version of the room internally // This will include highlights from the previous version of the room internally
const msc3946ProcessDynamicPredecessor = SettingsStore.getValue("feature_dynamic_room_predecessors"); const msc3946ProcessDynamicPredecessor = SettingsStore.getValue("feature_dynamic_room_predecessors");
@ -149,7 +150,7 @@ export class RoomNotificationStateStore extends AsyncStoreWithClient<IState> {
}; };
protected async onReady(): Promise<void> { protected async onReady(): Promise<void> {
this.matrixClient.on(ClientEvent.Sync, this.onSync); this.matrixClient?.on(ClientEvent.Sync, this.onSync);
} }
protected async onNotReady(): Promise<any> { protected async onNotReady(): Promise<any> {

View file

@ -77,7 +77,8 @@ export class SpaceNotificationState extends NotificationState {
this._count = 0; this._count = 0;
this._color = NotificationColor.None; this._color = NotificationColor.None;
for (const [roomId, state] of Object.entries(this.states)) { for (const [roomId, state] of Object.entries(this.states)) {
const roomTags = RoomListStore.instance.getTagsForRoom(this.rooms.find((r) => r.roomId === roomId)); const room = this.rooms.find((r) => r.roomId === roomId);
const roomTags = room ? RoomListStore.instance.getTagsForRoom(room) : [];
// We ignore unreads in LowPriority rooms, see https://github.com/vector-im/element-web/issues/16836 // We ignore unreads in LowPriority rooms, see https://github.com/vector-im/element-web/issues/16836
if (roomTags.includes(DefaultTagID.LowPriority) && state.color === NotificationColor.Bold) continue; if (roomTags.includes(DefaultTagID.LowPriority) && state.color === NotificationColor.Bold) continue;

View file

@ -165,7 +165,7 @@ export class MessagePreviewStore extends AsyncStoreWithClient<IState> {
const event = events[i]; const event = events[i];
await this.matrixClient.decryptEventIfNeeded(event); await this.matrixClient?.decryptEventIfNeeded(event);
const previewDef = PREVIEWS[event.getType()]; const previewDef = PREVIEWS[event.getType()];
if (!previewDef) continue; if (!previewDef) continue;

View file

@ -189,8 +189,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> implements
protected async onDispatchAsync(payload: ActionPayload): Promise<void> { protected async onDispatchAsync(payload: ActionPayload): Promise<void> {
// Everything here requires a MatrixClient or some sort of logical readiness. // Everything here requires a MatrixClient or some sort of logical readiness.
const logicallyReady = this.matrixClient && this.initialListsGenerated; if (!this.matrixClient || !this.initialListsGenerated) return;
if (!logicallyReady) return;
if (!this.algorithm) { if (!this.algorithm) {
// This shouldn't happen because `initialListsGenerated` implies we have an algorithm. // This shouldn't happen because `initialListsGenerated` implies we have an algorithm.
@ -229,7 +228,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> implements
eventPayload.event.getType() === EventType.RoomTombstone && eventPayload.event.getType() === EventType.RoomTombstone &&
eventPayload.event.getStateKey() === "" eventPayload.event.getStateKey() === ""
) { ) {
const newRoom = this.matrixClient.getRoom(eventPayload.event.getContent()["replacement_room"]); const newRoom = this.matrixClient?.getRoom(eventPayload.event.getContent()["replacement_room"]);
if (newRoom) { if (newRoom) {
// If we have the new room, then the new room check will have seen the predecessor // If we have the new room, then the new room check will have seen the predecessor
// and did the required updates, so do nothing here. // and did the required updates, so do nothing here.
@ -243,7 +242,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> implements
logger.warn(`Live timeline event ${eventPayload.event.getId()} received without associated room`); logger.warn(`Live timeline event ${eventPayload.event.getId()} received without associated room`);
logger.warn(`Queuing failed room update for retry as a result.`); logger.warn(`Queuing failed room update for retry as a result.`);
window.setTimeout(async (): Promise<void> => { window.setTimeout(async (): Promise<void> => {
const updatedRoom = this.matrixClient.getRoom(roomId); const updatedRoom = this.matrixClient?.getRoom(roomId);
if (updatedRoom) { if (updatedRoom) {
await tryUpdate(updatedRoom); await tryUpdate(updatedRoom);
@ -307,7 +306,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> implements
const roomState: RoomState = membershipPayload.room.currentState; const roomState: RoomState = membershipPayload.room.currentState;
const predecessor = roomState.findPredecessor(this.msc3946ProcessDynamicPredecessor); const predecessor = roomState.findPredecessor(this.msc3946ProcessDynamicPredecessor);
if (predecessor) { if (predecessor) {
const prevRoom = this.matrixClient.getRoom(predecessor.roomId); const prevRoom = this.matrixClient?.getRoom(predecessor.roomId);
if (prevRoom) { if (prevRoom) {
const isSticky = this.algorithm.stickyRoom === prevRoom; const isSticky = this.algorithm.stickyRoom === prevRoom;
if (isSticky) { if (isSticky) {

View file

@ -252,7 +252,7 @@ export class SlidingRoomListStoreClass extends AsyncStoreWithClient<IState> impl
// now set the rooms // now set the rooms
const rooms: Room[] = []; const rooms: Room[] = [];
orderedRoomIds.forEach((roomId) => { orderedRoomIds.forEach((roomId) => {
const room = this.matrixClient.getRoom(roomId); const room = this.matrixClient?.getRoom(roomId);
if (!room) { if (!room) {
return; return;
} }

View file

@ -178,7 +178,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
public get activeSpaceRoom(): Room | null { public get activeSpaceRoom(): Room | null {
if (isMetaSpace(this._activeSpace)) return null; if (isMetaSpace(this._activeSpace)) return null;
return this.matrixClient?.getRoom(this._activeSpace); return this.matrixClient?.getRoom(this._activeSpace) ?? null;
} }
public get suggestedRooms(): ISuggestedRoom[] { public get suggestedRooms(): ISuggestedRoom[] {
@ -290,7 +290,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
SpaceStore.instance.traverseSpace( SpaceStore.instance.traverseSpace(
space, space,
(roomId) => { (roomId) => {
this.matrixClient.getRoom(roomId)?.loadMembersIfNeeded(); this.matrixClient?.getRoom(roomId)?.loadMembersIfNeeded();
}, },
false, false,
); );
@ -324,7 +324,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
.filter((roomInfo) => { .filter((roomInfo) => {
return ( return (
roomInfo.room_type !== RoomType.Space && roomInfo.room_type !== RoomType.Space &&
this.matrixClient.getRoom(roomInfo.room_id)?.getMyMembership() !== "join" this.matrixClient?.getRoom(roomInfo.room_id)?.getMyMembership() !== "join"
); );
}) })
.map((roomInfo) => ({ .map((roomInfo) => ({
@ -396,7 +396,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
// only respect the relationship if the sender has sufficient permissions in the parent to set // only respect the relationship if the sender has sufficient permissions in the parent to set
// child relations, as per MSC1772. // child relations, as per MSC1772.
// https://github.com/matrix-org/matrix-doc/blob/main/proposals/1772-groups-as-rooms.md#relationship-between-rooms-and-spaces // https://github.com/matrix-org/matrix-doc/blob/main/proposals/1772-groups-as-rooms.md#relationship-between-rooms-and-spaces
const parent = this.matrixClient.getRoom(ev.getStateKey()); const parent = this.matrixClient?.getRoom(ev.getStateKey());
const relation = parent?.currentState.getStateEvents(EventType.SpaceChild, roomId); const relation = parent?.currentState.getStateEvents(EventType.SpaceChild, roomId);
if ( if (
!parent?.currentState.maySendStateEvent(EventType.SpaceChild, userId) || !parent?.currentState.maySendStateEvent(EventType.SpaceChild, userId) ||
@ -877,7 +877,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
private switchSpaceIfNeeded = (roomId = SdkContextClass.instance.roomViewStore.getRoomId()): void => { private switchSpaceIfNeeded = (roomId = SdkContextClass.instance.roomViewStore.getRoomId()): void => {
if (!roomId) return; if (!roomId) return;
if (!this.isRoomInSpace(this.activeSpace, roomId) && !this.matrixClient.getRoom(roomId)?.isSpaceRoom()) { if (!this.isRoomInSpace(this.activeSpace, roomId) && !this.matrixClient?.getRoom(roomId)?.isSpaceRoom()) {
this.switchToRelatedSpace(roomId); this.switchToRelatedSpace(roomId);
} }
}; };
@ -972,7 +972,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
} }
private onRoomState = (ev: MatrixEvent): void => { private onRoomState = (ev: MatrixEvent): void => {
const room = this.matrixClient.getRoom(ev.getRoomId()); const room = this.matrixClient?.getRoom(ev.getRoomId());
if (!room) return; if (!room) return;
@ -1022,7 +1022,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
// listening for m.room.member events in onRoomState above doesn't work as the Member object isn't updated by then // listening for m.room.member events in onRoomState above doesn't work as the Member object isn't updated by then
private onRoomStateMembers = (ev: MatrixEvent): void => { private onRoomStateMembers = (ev: MatrixEvent): void => {
const room = this.matrixClient.getRoom(ev.getRoomId()); const room = this.matrixClient?.getRoom(ev.getRoomId());
const userId = ev.getStateKey()!; const userId = ev.getStateKey()!;
if ( if (
@ -1135,6 +1135,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
} }
protected async onReady(): Promise<void> { protected async onReady(): Promise<void> {
if (!this.matrixClient) return;
this.matrixClient.on(ClientEvent.Room, this.onRoom); this.matrixClient.on(ClientEvent.Room, this.onRoom);
this.matrixClient.on(RoomEvent.MyMembership, this.onRoom); this.matrixClient.on(RoomEvent.MyMembership, this.onRoom);
this.matrixClient.on(RoomEvent.AccountData, this.onRoomAccountData); this.matrixClient.on(RoomEvent.AccountData, this.onRoomAccountData);
@ -1350,7 +1351,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
private async setRootSpaceOrder(space: Room, order: string): Promise<void> { private async setRootSpaceOrder(space: Room, order: string): Promise<void> {
this.spaceOrderLocalEchoMap.set(space.roomId, order); this.spaceOrderLocalEchoMap.set(space.roomId, order);
try { try {
await this.matrixClient.setRoomAccountData(space.roomId, EventType.SpaceOrder, { order }); await this.matrixClient?.setRoomAccountData(space.roomId, EventType.SpaceOrder, { order });
} catch (e) { } catch (e) {
logger.warn("Failed to set root space order", e); logger.warn("Failed to set root space order", e);
if (this.spaceOrderLocalEchoMap.get(space.roomId) === order) { if (this.spaceOrderLocalEchoMap.get(space.roomId) === order) {

View file

@ -42,6 +42,7 @@ describe("<RoomLiveShareWarning />", () => {
const mockClient = getMockClientWithEventEmitter({ const mockClient = getMockClientWithEventEmitter({
getVisibleRooms: jest.fn().mockReturnValue([]), getVisibleRooms: jest.fn().mockReturnValue([]),
getUserId: jest.fn().mockReturnValue(aliceId), getUserId: jest.fn().mockReturnValue(aliceId),
getSafeUserId: jest.fn().mockReturnValue(aliceId),
unstable_setLiveBeacon: jest.fn().mockResolvedValue({ event_id: "1" }), unstable_setLiveBeacon: jest.fn().mockResolvedValue({ event_id: "1" }),
sendEvent: jest.fn(), sendEvent: jest.fn(),
isGuest: jest.fn().mockReturnValue(false), isGuest: jest.fn().mockReturnValue(false),

View file

@ -42,7 +42,7 @@ describe("NotificatinSettingsTab", () => {
const room = mkStubRoom(roomId, "test room", cli); const room = mkStubRoom(roomId, "test room", cli);
roomProps = EchoChamber.forRoom(room); roomProps = EchoChamber.forRoom(room);
NotificationSettingsTab.contextType = React.createContext<MatrixClient | undefined>(cli); NotificationSettingsTab.contextType = React.createContext<MatrixClient>(cli);
}); });
it("should prevent »Settings« link click from bubbling up to radio buttons", async () => { it("should prevent »Settings« link click from bubbling up to radio buttons", async () => {

View file

@ -59,6 +59,7 @@ describe("OwnBeaconStore", () => {
const bobId = "@bob:server.org"; const bobId = "@bob:server.org";
const mockClient = getMockClientWithEventEmitter({ const mockClient = getMockClientWithEventEmitter({
getUserId: jest.fn().mockReturnValue(aliceId), getUserId: jest.fn().mockReturnValue(aliceId),
getSafeUserId: jest.fn().mockReturnValue(aliceId),
getVisibleRooms: jest.fn().mockReturnValue([]), getVisibleRooms: jest.fn().mockReturnValue([]),
unstable_setLiveBeacon: jest.fn().mockResolvedValue({ event_id: "1" }), unstable_setLiveBeacon: jest.fn().mockResolvedValue({ event_id: "1" }),
sendEvent: jest.fn().mockResolvedValue({ event_id: "1" }), sendEvent: jest.fn().mockResolvedValue({ event_id: "1" }),