Conform more code to strict null checking (#10153)

* Conform more code to strict null checking

* Conform more code to strict null checking

* Iterate

* Iterate
This commit is contained in:
Michael Telatynski
2023-02-15 13:36:22 +00:00
committed by GitHub
parent a4ff959aa1
commit 145a5a8a8d
89 changed files with 520 additions and 551 deletions

View File

@@ -92,7 +92,7 @@ export abstract class AsyncStore<T extends Object> extends EventEmitter {
* @param {T|*} newState The new state of the store.
* @param {boolean} quiet If true, the function will not raise an UPDATE_EVENT.
*/
protected async reset(newState: T | Object = null, quiet = false): Promise<void> {
protected async reset(newState: T | Object | null = null, quiet = false): Promise<void> {
await this.lock.acquireAsync();
try {
this.storeState = Object.freeze(<T>(newState || {}));

View File

@@ -19,7 +19,7 @@ import { AsyncStore } from "./AsyncStore";
import { ActionPayload } from "../dispatcher/payloads";
interface IState {
hostSignupActive?: boolean;
hostSignupActive: boolean;
}
export class HostSignupStore extends AsyncStore<IState> {

View File

@@ -22,7 +22,7 @@ import { ActionPayload } from "../dispatcher/payloads";
import { DoAfterSyncPreparedPayload } from "../dispatcher/payloads/DoAfterSyncPreparedPayload";
interface IState {
deferredAction: ActionPayload;
deferredAction: ActionPayload | null;
}
const INITIAL_STATE: IState = {
@@ -83,8 +83,8 @@ class LifecycleStore extends Store<ActionPayload> {
}
}
let singletonLifecycleStore = null;
let singletonLifecycleStore: LifecycleStore | null = null;
if (!singletonLifecycleStore) {
singletonLifecycleStore = new LifecycleStore();
}
export default singletonLifecycleStore;
export default singletonLifecycleStore!;

View File

@@ -110,15 +110,15 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
* ordered by creation time descending
*/
private liveBeaconIds: BeaconIdentifier[] = [];
private locationInterval: number;
private geolocationError: GeolocationError | undefined;
private clearPositionWatch: ClearWatchCallback | undefined;
private locationInterval?: number;
private geolocationError?: GeolocationError;
private clearPositionWatch?: ClearWatchCallback;
/**
* Track when the last position was published
* So we can manually get position on slow interval
* when the target is stationary
*/
private lastPublishedPositionTimestamp: number | undefined;
private lastPublishedPositionTimestamp?: number;
public constructor() {
super(defaultDispatcher);
@@ -231,7 +231,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
*/
private onNewBeacon = (_event: MatrixEvent, beacon: Beacon): void => {
if (!isOwnBeacon(beacon, this.matrixClient.getUserId())) {
if (!isOwnBeacon(beacon, this.matrixClient.getUserId()!)) {
return;
}
this.addBeacon(beacon);
@@ -242,7 +242,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
* This will be called when a beacon is replaced
*/
private onUpdateBeacon = (_event: MatrixEvent, beacon: Beacon): void => {
if (!isOwnBeacon(beacon, this.matrixClient.getUserId())) {
if (!isOwnBeacon(beacon, this.matrixClient.getUserId()!)) {
return;
}
@@ -309,7 +309,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
}
private initialiseBeaconState = (): void => {
const userId = this.matrixClient.getUserId();
const userId = this.matrixClient.getUserId()!;
const visibleRooms = this.matrixClient.getVisibleRooms();
visibleRooms.forEach((room) => {
@@ -329,7 +329,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
this.beaconsByRoomId.set(beacon.roomId, new Set<string>());
}
this.beaconsByRoomId.get(beacon.roomId).add(beacon.identifier);
this.beaconsByRoomId.get(beacon.roomId)!.add(beacon.identifier);
beacon.monitorLiveness();
};
@@ -343,7 +343,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
if (!this.beacons.has(beaconId)) {
return;
}
this.beacons.get(beaconId).destroy();
this.beacons.get(beaconId)!.destroy();
this.beacons.delete(beaconId);
this.checkLiveness();

View File

@@ -43,7 +43,7 @@ export class OwnProfileStore extends AsyncStoreWithClient<IState> {
return instance;
})();
private monitoredUser: User;
private monitoredUser: User | null;
private constructor() {
// seed from localstorage because otherwise we won't get these values until a whole network
@@ -62,7 +62,7 @@ export class OwnProfileStore extends AsyncStoreWithClient<IState> {
/**
* Gets the display name for the user, or null if not present.
*/
public get displayName(): string {
public get displayName(): string | null {
if (!this.matrixClient) return this.state.displayName || null;
if (this.matrixClient.isGuest()) {
@@ -81,7 +81,7 @@ export class OwnProfileStore extends AsyncStoreWithClient<IState> {
/**
* Gets the MXC URI of the user's avatar, or null if not present.
*/
public get avatarMxc(): string {
public get avatarMxc(): string | null {
return this.state.avatarUrl || null;
}
@@ -92,7 +92,7 @@ export class OwnProfileStore extends AsyncStoreWithClient<IState> {
* will be returned as an HTTP URL.
* @returns The HTTP URL of the user's avatar
*/
public getHttpAvatarUrl(size = 0): string {
public getHttpAvatarUrl(size = 0): string | null {
if (!this.avatarMxc) return null;
const media = mediaFromMxc(this.avatarMxc);
if (!size || size <= 0) {
@@ -112,7 +112,7 @@ export class OwnProfileStore extends AsyncStoreWithClient<IState> {
}
protected async onReady(): Promise<void> {
const myUserId = this.matrixClient.getUserId();
const myUserId = this.matrixClient.getUserId()!;
this.monitoredUser = this.matrixClient.getUser(myUserId);
if (this.monitoredUser) {
this.monitoredUser.on(UserEvent.DisplayName, this.onProfileUpdate);
@@ -134,7 +134,7 @@ export class OwnProfileStore extends AsyncStoreWithClient<IState> {
async (): Promise<void> => {
// 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).
const profileInfo = await this.matrixClient.getProfileInfo(this.matrixClient.getUserId());
const profileInfo = await this.matrixClient.getProfileInfo(this.matrixClient.getUserId()!);
if (profileInfo.displayname) {
window.localStorage.setItem(KEY_DISPLAY_NAME, profileInfo.displayname);
} else {

View File

@@ -44,10 +44,12 @@ export enum Phase {
export class SetupEncryptionStore extends EventEmitter {
private started: boolean;
public phase: Phase;
public verificationRequest: VerificationRequest;
public backupInfo: IKeyBackupInfo;
public keyId: string;
public keyInfo: ISecretStorageKeyInfo;
public verificationRequest: VerificationRequest | null = null;
public backupInfo: IKeyBackupInfo | null = null;
// ID of the key that the secrets we want are encrypted with
public keyId: string | null = null;
// Descriptor of the key that the secrets we want are encrypted with
public keyInfo: ISecretStorageKeyInfo | null = null;
public hasDevicesToVerifyAgainst: boolean;
public static sharedInstance(): SetupEncryptionStore {
@@ -61,19 +63,12 @@ export class SetupEncryptionStore extends EventEmitter {
}
this.started = true;
this.phase = Phase.Loading;
this.verificationRequest = null;
this.backupInfo = null;
// ID of the key that the secrets we want are encrypted with
this.keyId = null;
// Descriptor of the key that the secrets we want are encrypted with
this.keyInfo = null;
const cli = MatrixClientPeg.get();
cli.on(CryptoEvent.VerificationRequest, this.onVerificationRequest);
cli.on(CryptoEvent.UserTrustStatusChanged, this.onUserTrustStatusChanged);
const requestsInProgress = cli.getVerificationRequestsToDeviceInProgress(cli.getUserId());
const requestsInProgress = cli.getVerificationRequestsToDeviceInProgress(cli.getUserId()!);
if (requestsInProgress.length) {
// If there are multiple, we take the most recent. Equally if the user sends another request from
// another device after this screen has been shown, we'll switch to the new one, so this
@@ -111,7 +106,7 @@ export class SetupEncryptionStore extends EventEmitter {
// do we have any other verified devices which are E2EE which we can verify against?
const dehydratedDevice = await cli.getDehydratedDevice();
const ownUserId = cli.getUserId();
const ownUserId = cli.getUserId()!;
const crossSigningInfo = cli.getStoredCrossSigningForUser(ownUserId);
this.hasDevicesToVerifyAgainst = cli
.getStoredDevicesForUser(ownUserId)
@@ -119,7 +114,7 @@ export class SetupEncryptionStore extends EventEmitter {
(device) =>
device.getIdentityKey() &&
(!dehydratedDevice || device.deviceId != dehydratedDevice.device_id) &&
crossSigningInfo.checkDeviceTrust(crossSigningInfo, device, false, true).isCrossSigningVerified(),
crossSigningInfo?.checkDeviceTrust(crossSigningInfo, device, false, true).isCrossSigningVerified(),
);
this.phase = Phase.Intro;
@@ -183,11 +178,11 @@ export class SetupEncryptionStore extends EventEmitter {
};
public onVerificationRequestChange = (): void => {
if (this.verificationRequest.cancelled) {
if (this.verificationRequest?.cancelled) {
this.verificationRequest.off(VerificationRequestEvent.Change, this.onVerificationRequestChange);
this.verificationRequest = null;
this.emit("update");
} else if (this.verificationRequest.phase === VERIF_PHASE_DONE) {
} else if (this.verificationRequest?.phase === VERIF_PHASE_DONE) {
this.verificationRequest.off(VerificationRequestEvent.Change, this.onVerificationRequestChange);
this.verificationRequest = null;
// At this point, the verification has finished, we just need to wait for
@@ -259,7 +254,7 @@ export class SetupEncryptionStore extends EventEmitter {
this.phase = Phase.Finished;
this.emit("update");
// async - ask other clients for keys, if necessary
MatrixClientPeg.get().crypto.cancelAndResendAllOutgoingKeyRequests();
MatrixClientPeg.get().crypto?.cancelAndResendAllOutgoingKeyRequests();
}
private async setActiveVerificationRequest(request: VerificationRequest): Promise<void> {

View File

@@ -104,9 +104,9 @@ export default class WidgetStore extends AsyncStoreWithClient<IState> {
private generateApps(room: Room): IApp[] {
return WidgetEchoStore.getEchoedRoomWidgets(room.roomId, WidgetUtils.getRoomWidgets(room)).map((ev) => {
return WidgetUtils.makeAppConfig(
ev.getStateKey(),
ev.getStateKey()!,
ev.getContent(),
ev.getSender(),
ev.getSender()!,
ev.getRoomId(),
ev.getId(),
);
@@ -172,7 +172,7 @@ export default class WidgetStore extends AsyncStoreWithClient<IState> {
private onRoomStateEvents = (ev: MatrixEvent): void => {
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.loadRoomWidgets(this.matrixClient.getRoom(roomId));
this.emit(UPDATE_EVENT, roomId);

View File

@@ -37,7 +37,7 @@ export abstract class EchoContext extends Whenable<ContextTransactionState> impl
return this._state;
}
public get firstFailedTime(): Date {
public get firstFailedTime(): Date | null {
const failedTxn = this.transactions.find((t) => t.didPreviouslyFail || t.status === TransactionStatus.Error);
if (failedTxn) return failedTxn.startTime;
return null;

View File

@@ -28,19 +28,19 @@ export const PROPERTY_UPDATED = "property_updated";
export abstract class GenericEchoChamber<C extends EchoContext, K, V> extends EventEmitter {
private cache = new Map<K, { txn: EchoTransaction; val: V }>();
protected matrixClient: MatrixClient;
protected matrixClient: MatrixClient | null;
protected constructor(public readonly context: C, private lookupFn: (key: K) => V) {
super();
}
public setClient(client: MatrixClient): void {
public setClient(client: MatrixClient | null): void {
const oldClient = this.matrixClient;
this.matrixClient = client;
this.onClientChanged(oldClient, client);
}
protected abstract onClientChanged(oldClient: MatrixClient, newClient: MatrixClient): void;
protected abstract onClientChanged(oldClient: MatrixClient | null, newClient: MatrixClient | null): void;
/**
* Gets a value. If the key is in flight, the cached value will be returned. If
@@ -50,7 +50,7 @@ export abstract class GenericEchoChamber<C extends EchoContext, K, V> extends Ev
* @returns The value for the key.
*/
public getValue(key: K): V {
return this.cache.has(key) ? this.cache.get(key).val : this.lookupFn(key);
return this.cache.has(key) ? this.cache.get(key)!.val : this.lookupFn(key);
}
private cacheVal(key: K, val: V, txn: EchoTransaction): void {
@@ -60,7 +60,7 @@ export abstract class GenericEchoChamber<C extends EchoContext, K, V> extends Ev
private decacheKey(key: K): void {
if (this.cache.has(key)) {
this.context.disownTransaction(this.cache.get(key).txn);
this.context.disownTransaction(this.cache.get(key)!.txn);
this.cache.delete(key);
this.emit(PROPERTY_UPDATED, key);
}
@@ -68,7 +68,7 @@ export abstract class GenericEchoChamber<C extends EchoContext, K, V> extends Ev
protected markEchoReceived(key: K): void {
if (this.cache.has(key)) {
const txn = this.cache.get(key).txn;
const txn = this.cache.get(key)!.txn;
this.context.disownTransaction(txn);
txn.cancel();
}
@@ -78,7 +78,7 @@ export abstract class GenericEchoChamber<C extends EchoContext, K, V> extends Ev
public setValue(auditName: string, key: K, targetVal: V, runFn: RunFn, revertFn: RunFn): void {
// Cancel any pending transactions for the same key
if (this.cache.has(key)) {
this.cache.get(key).txn.cancel();
this.cache.get(key)!.txn.cancel();
}
const ctxn = this.context.beginTransaction(auditName, runFn);

View File

@@ -34,7 +34,7 @@ export class RoomEchoChamber extends GenericEchoChamber<RoomEchoContext, CachedR
super(context, (k) => this.properties.get(k));
}
protected onClientChanged(oldClient: MatrixClient, newClient: MatrixClient): void {
protected onClientChanged(oldClient: MatrixClient | null, newClient: MatrixClient | null): void {
this.properties.clear();
oldClient?.removeListener(ClientEvent.AccountData, this.onAccountData);
if (newClient) {
@@ -57,7 +57,7 @@ export class RoomEchoChamber extends GenericEchoChamber<RoomEchoContext, CachedR
};
private updateNotificationVolume(): void {
const state = getRoomNotifsState(this.matrixClient, this.context.room.roomId);
const state = this.matrixClient ? getRoomNotifsState(this.matrixClient, this.context.room.roomId) : null;
if (state) this.properties.set(CachedRoomKey.NotificationVolume, state);
else this.properties.delete(CachedRoomKey.NotificationVolume);
this.markEchoReceived(CachedRoomKey.NotificationVolume);

View File

@@ -62,7 +62,7 @@ export class RoomNotificationStateStore extends AsyncStoreWithClient<IState> {
*/
public getListState(tagId: TagID): ListNotificationState {
if (this.listMap.has(tagId)) {
return this.listMap.get(tagId);
return this.listMap.get(tagId)!;
}
// TODO: Update if/when invites move out of the room list.
@@ -86,14 +86,14 @@ export class RoomNotificationStateStore extends AsyncStoreWithClient<IState> {
if (!this.roomMap.has(room)) {
this.roomMap.set(room, new RoomNotificationState(room));
}
return this.roomMap.get(room);
return this.roomMap.get(room)!;
}
public static get instance(): RoomNotificationStateStore {
return RoomNotificationStateStore.internalInstance;
}
private onSync = (state: SyncState, prevState?: SyncState, data?: ISyncStateData): void => {
private onSync = (state: SyncState, prevState: SyncState | null, data?: ISyncStateData): void => {
// 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
const globalState = new SummarizedNotificationState();

View File

@@ -58,20 +58,20 @@ export default class RightPanelStore extends ReadyWatchingStore {
* Resets the store. Intended for test usage only.
*/
public reset(): void {
this.global = null;
this.global = undefined;
this.byRoom = {};
this.viewedRoomId = null;
}
protected async onReady(): Promise<any> {
this.viewedRoomId = SdkContextClass.instance.roomViewStore.getRoomId();
this.matrixClient.on(CryptoEvent.VerificationRequest, this.onVerificationRequestUpdate);
this.matrixClient?.on(CryptoEvent.VerificationRequest, this.onVerificationRequestUpdate);
this.loadCacheFromSettings();
this.emitAndUpdateSettings();
}
protected async onNotReady(): Promise<any> {
this.matrixClient.off(CryptoEvent.VerificationRequest, this.onVerificationRequestUpdate);
this.matrixClient?.off(CryptoEvent.VerificationRequest, this.onVerificationRequestUpdate);
}
protected onDispatcherAction(payload: ActionPayload): void {
@@ -376,7 +376,7 @@ export default class RightPanelStore extends ReadyWatchingStore {
// the room member list.
if (SettingsStore.getValue("feature_right_panel_default_open") && !this.byRoom[this.viewedRoomId]?.isOpen) {
const history = [{ phase: RightPanelPhases.RoomMemberList }];
const room = this.viewedRoomId && this.mxClient?.getRoom(this.viewedRoomId);
const room = this.viewedRoomId ? this.mxClient?.getRoom(this.viewedRoomId) : undefined;
if (!room?.isSpaceRoom()) {
history.unshift({ phase: RightPanelPhases.RoomSummary });
}

View File

@@ -123,7 +123,7 @@ export class MessagePreviewStore extends AsyncStoreWithClient<IState> {
* @param inTagId The tag ID in which the room resides
* @returns The preview, or null if none present.
*/
public async getPreviewForRoom(room: Room, inTagId: TagID): Promise<string> {
public async getPreviewForRoom(room: Room, inTagId: TagID): Promise<string | null> {
if (!room) return null; // invalid room, just return nothing
if (!this.previews.has(room.roomId)) await this.generatePreview(room, inTagId);
@@ -132,14 +132,14 @@ export class MessagePreviewStore extends AsyncStoreWithClient<IState> {
if (!previews) return null;
if (!previews.has(inTagId)) {
return previews.get(TAG_ANY);
return previews.get(TAG_ANY)!;
}
return previews.get(inTagId);
return previews.get(inTagId) ?? null;
}
public generatePreviewForEvent(event: MatrixEvent): string {
const previewDef = PREVIEWS[event.getType()];
return previewDef?.previewer.getTextFor(event, null, true) ?? "";
return previewDef?.previewer.getTextFor(event, undefined, true) ?? "";
}
private async generatePreview(room: Room, tagId?: TagID): Promise<void> {
@@ -171,7 +171,7 @@ export class MessagePreviewStore extends AsyncStoreWithClient<IState> {
if (!previewDef) continue;
if (previewDef.isState && isNullOrUndefined(event.getStateKey())) continue;
const anyPreview = previewDef.previewer.getTextFor(event, null);
const anyPreview = previewDef.previewer.getTextFor(event);
if (!anyPreview) continue; // not previewable for some reason
changed = changed || anyPreview !== map.get(TAG_ANY);
@@ -179,7 +179,7 @@ export class MessagePreviewStore extends AsyncStoreWithClient<IState> {
const tagsToGenerate = Array.from(map.keys()).filter((t) => t !== TAG_ANY); // we did the any tag above
for (const genTagId of tagsToGenerate) {
const realTagId: TagID = genTagId === TAG_ANY ? null : genTagId;
const realTagId = genTagId === TAG_ANY ? undefined : genTagId;
const preview = previewDef.previewer.getTextFor(event, realTagId);
if (preview === anyPreview) {
changed = changed || anyPreview !== map.get(genTagId);

View File

@@ -116,7 +116,7 @@ export class Algorithm extends EventEmitter {
* Awaitable version of the sticky room setter.
* @param val The new room to sticky.
*/
public setStickyRoom(val: Room): void {
public setStickyRoom(val: Room | null): void {
try {
this.updateStickyRoom(val);
} catch (e) {

View File

@@ -29,7 +29,7 @@ export class PollStartEventPreview implements IPreview {
public static contextType = MatrixClientContext;
public context!: React.ContextType<typeof MatrixClientContext>;
public getTextFor(event: MatrixEvent, tagId?: TagID, isThread?: boolean): string {
public getTextFor(event: MatrixEvent, tagId?: TagID, isThread?: boolean): string | null {
let eventContent = event.getContent();
if (event.isRelation("m.replace")) {
@@ -51,7 +51,7 @@ export class PollStartEventPreview implements IPreview {
let question = poll.question.text.trim();
question = sanitizeForTranslation(question);
if (isThread || isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId(), tagId)) {
if (isThread || isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId()!, tagId)) {
return question;
} else {
return _t("%(senderName)s: %(message)s", { senderName: getSenderName(event), message: question });

View File

@@ -22,11 +22,11 @@ import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils";
import { _t } from "../../../languageHandler";
export class StickerEventPreview implements IPreview {
public getTextFor(event: MatrixEvent, tagId?: TagID, isThread?: boolean): string {
public getTextFor(event: MatrixEvent, tagId?: TagID, isThread?: boolean): string | null {
const stickerName = event.getContent()["body"];
if (!stickerName) return null;
if (isThread || isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId(), tagId)) {
if (isThread || isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId()!, tagId)) {
return stickerName;
} else {
return _t("%(senderName)s: %(stickerName)s", { senderName: getSenderName(event), stickerName });

View File

@@ -78,7 +78,7 @@ const getSpaceContextKey = (space: SpaceKey): string => `mx_space_context_${spac
const partitionSpacesAndRooms = (arr: Room[]): [Room[], Room[]] => {
// [spaces, rooms]
return arr.reduce(
return arr.reduce<[Room[], Room[]]>(
(result, room: Room) => {
result[room.isSpaceRoom() ? 0 : 1].push(room);
return result;
@@ -165,7 +165,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
return this.rootSpaces;
}
public get activeSpace(): SpaceKey {
public get activeSpace(): SpaceKey | undefined {
return this._activeSpace;
}
@@ -228,7 +228,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
public setActiveSpace(space: SpaceKey, contextSwitch = true): void {
if (!space || !this.matrixClient || space === this.activeSpace) return;
let cliSpace: Room;
let cliSpace: Room | null = null;
if (!isMetaSpace(space)) {
cliSpace = this.matrixClient.getRoom(space);
if (!cliSpace?.isSpaceRoom()) return;
@@ -246,6 +246,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
// else if the last viewed room in this space is joined then view that
// else view space home or home depending on what is being clicked on
if (
roomId &&
cliSpace?.getMyMembership() !== "invite" &&
this.matrixClient.getRoom(roomId)?.getMyMembership() === "join" &&
this.isRoomInSpace(space, roomId)
@@ -348,10 +349,10 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
.filter((ev) => ev.getContent()?.via);
return (
sortBy(childEvents, (ev) => {
return getChildOrder(ev.getContent().order, ev.getTs(), ev.getStateKey());
return getChildOrder(ev.getContent().order, ev.getTs(), ev.getStateKey()!);
})
.map((ev) => {
const history = this.matrixClient.getRoomUpgradeHistory(ev.getStateKey(), true);
const history = this.matrixClient.getRoomUpgradeHistory(ev.getStateKey()!, true);
return history[history.length - 1];
})
.filter((room) => {
@@ -373,7 +374,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
const userId = this.matrixClient?.getUserId();
const room = this.matrixClient?.getRoom(roomId);
return (
room?.currentState
(room?.currentState
.getStateEvents(EventType.SpaceParent)
.map((ev) => {
const content = ev.getContent();
@@ -396,7 +397,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
return parent;
})
.filter(Boolean) || []
.filter(Boolean) as Room[]) || []
);
}
@@ -467,7 +468,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
space: SpaceKey,
includeDescendantSpaces = true,
useCache = true,
): Set<string> => {
): Set<string> | undefined => {
if (space === MetaSpace.Home && this.allRoomsInHome) {
return undefined;
}
@@ -490,7 +491,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
private markTreeChildren = (rootSpace: Room, unseen: Set<Room>): void => {
const stack = [rootSpace];
while (stack.length) {
const space = stack.pop();
const space = stack.pop()!;
unseen.delete(space);
this.getChildSpaces(space.roomId).forEach((space) => {
if (unseen.has(space)) {
@@ -646,7 +647,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
const enabledMetaSpaces = new Set(this.enabledMetaSpaces);
const visibleRooms = this.matrixClient.getVisibleRooms();
let dmBadgeSpace: MetaSpace;
let dmBadgeSpace: MetaSpace | undefined;
// only show badges on dms on the most relevant space if such exists
if (enabledMetaSpaces.has(MetaSpace.People)) {
dmBadgeSpace = MetaSpace.People;
@@ -702,8 +703,8 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
); // put all invites in the Home Space
};
private static isInSpace(member: RoomMember): boolean {
return member.membership === "join" || member.membership === "invite";
private static isInSpace(member?: RoomMember | null): boolean {
return member?.membership === "join" || member?.membership === "invite";
}
// Method for resolving the impact of a single user's membership change in the given Space and its hierarchy
@@ -755,11 +756,14 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
this.rootSpaces.forEach((s) => {
// traverse each space tree in DFS to build up the supersets as you go up,
// reusing results from like subtrees.
const traverseSpace = (spaceId: string, parentPath: Set<string>): [Set<string>, Set<string>] => {
const traverseSpace = (
spaceId: string,
parentPath: Set<string>,
): [Set<string>, Set<string>] | undefined => {
if (parentPath.has(spaceId)) return; // prevent cycles
// reuse existing results if multiple similar branches exist
if (this.roomIdsBySpace.has(spaceId) && this.userIdsBySpace.has(spaceId)) {
return [this.roomIdsBySpace.get(spaceId), this.userIdsBySpace.get(spaceId)];
return [this.roomIdsBySpace.get(spaceId)!, this.userIdsBySpace.get(spaceId)!];
}
const [childSpaces, childRooms] = partitionSpacesAndRooms(this.getChildren(spaceId));
@@ -865,7 +869,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
if (this.suggestedRooms.find((r) => r.room_id === roomId)) return;
// try to find the canonical parent first
let parent: SpaceKey = this.getCanonicalParent(roomId)?.roomId;
let parent: SpaceKey | undefined = this.getCanonicalParent(roomId)?.roomId;
// otherwise, try to find a root space which contains this room
if (!parent) {

View File

@@ -54,7 +54,7 @@ export interface ISuggestedRoom extends IHierarchyRoom {
viaServers: string[];
}
export function isMetaSpace(spaceKey: SpaceKey): boolean {
export function isMetaSpace(spaceKey?: SpaceKey): boolean {
return (
spaceKey === MetaSpace.Home ||
spaceKey === MetaSpace.Favourites ||

View File

@@ -33,6 +33,7 @@ import {
WidgetApiFromWidgetAction,
WidgetKind,
} from "matrix-widget-api";
import { Optional } from "matrix-events-sdk";
import { EventEmitter } from "events";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/models/event";
@@ -156,7 +157,7 @@ export class ElementWidget extends Widget {
export class StopGapWidget extends EventEmitter {
private client: MatrixClient;
private messaging: ClientWidgetApi;
private messaging: ClientWidgetApi | null;
private mockWidget: ElementWidget;
private scalarToken: string;
private roomId?: string;
@@ -172,7 +173,7 @@ export class StopGapWidget extends EventEmitter {
// Backwards compatibility: not all old widgets have a creatorUserId
if (!app.creatorUserId) {
app = objectShallowClone(app); // clone to prevent accidental mutation
app.creatorUserId = this.client.getUserId();
app.creatorUserId = this.client.getUserId()!;
}
this.mockWidget = new ElementWidget(app);
@@ -181,7 +182,7 @@ export class StopGapWidget extends EventEmitter {
this.virtual = app.eventId === undefined;
}
private get eventListenerRoomId(): string {
private get eventListenerRoomId(): Optional<string> {
// When widgets are listening to events, we need to make sure they're only
// receiving events for the right room. In particular, room widgets get locked
// to the room they were added in while account widgets listen to the currently
@@ -192,7 +193,7 @@ export class StopGapWidget extends EventEmitter {
return SdkContextClass.instance.roomViewStore.getRoomId();
}
public get widgetApi(): ClientWidgetApi {
public get widgetApi(): ClientWidgetApi | null {
return this.messaging;
}
@@ -214,7 +215,7 @@ export class StopGapWidget extends EventEmitter {
const fromCustomisation = WidgetVariableCustomisations?.provideVariables?.() ?? {};
const defaults: ITemplateParams = {
widgetRoomId: this.roomId,
currentUserId: this.client.getUserId(),
currentUserId: this.client.getUserId()!,
userDisplayName: OwnProfileStore.instance.displayName,
userHttpAvatarUrl: OwnProfileStore.instance.getHttpAvatarUrl(),
clientId: ELEMENT_CLIENT_ID,
@@ -256,9 +257,9 @@ export class StopGapWidget extends EventEmitter {
ev.preventDefault();
if (ModalWidgetStore.instance.canOpenModalWidget()) {
ModalWidgetStore.instance.openModalWidget(ev.detail.data, this.mockWidget, this.roomId);
this.messaging.transport.reply(ev.detail, {}); // ack
this.messaging?.transport.reply(ev.detail, {}); // ack
} else {
this.messaging.transport.reply(ev.detail, {
this.messaging?.transport.reply(ev.detail, {
error: {
message: "Unable to open modal at this time",
},
@@ -301,14 +302,14 @@ export class StopGapWidget extends EventEmitter {
// Check up front if this is even a valid request
const targetRoomId = (ev.detail.data || {}).room_id;
if (!targetRoomId) {
return this.messaging.transport.reply(ev.detail, <IWidgetApiErrorResponseData>{
return this.messaging?.transport.reply(ev.detail, <IWidgetApiErrorResponseData>{
error: { message: "Room ID not supplied." },
});
}
// Check the widget's permission
if (!this.messaging.hasCapability(ElementWidgetCapabilities.CanChangeViewedRoom)) {
return this.messaging.transport.reply(ev.detail, <IWidgetApiErrorResponseData>{
if (!this.messaging?.hasCapability(ElementWidgetCapabilities.CanChangeViewedRoom)) {
return this.messaging?.transport.reply(ev.detail, <IWidgetApiErrorResponseData>{
error: { message: "This widget does not have permission for this action (denied)." },
});
}
@@ -332,7 +333,7 @@ export class StopGapWidget extends EventEmitter {
const events = room.getLiveTimeline()?.getEvents() || [];
const roomEvent = events[events.length - 1];
if (!roomEvent) continue; // force later code to think the room is fresh
this.readUpToMap[room.roomId] = roomEvent.getId();
this.readUpToMap[room.roomId] = roomEvent.getId()!;
}
// Attach listeners for feeding events - the underlying widget classes handle permissions for us
@@ -343,7 +344,7 @@ export class StopGapWidget extends EventEmitter {
this.messaging.on(
`action:${WidgetApiFromWidgetAction.UpdateAlwaysOnScreen}`,
(ev: CustomEvent<IStickyActionRequest>) => {
if (this.messaging.hasCapability(MatrixCapabilities.AlwaysOnScreen)) {
if (this.messaging?.hasCapability(MatrixCapabilities.AlwaysOnScreen)) {
ActiveWidgetStore.instance.setWidgetPersistence(
this.mockWidget.id,
this.roomId,
@@ -360,7 +361,7 @@ export class StopGapWidget extends EventEmitter {
this.messaging.on(
`action:${WidgetApiFromWidgetAction.SendSticker}`,
(ev: CustomEvent<IStickerActionRequest>) => {
if (this.messaging.hasCapability(MatrixCapabilities.StickerSending)) {
if (this.messaging?.hasCapability(MatrixCapabilities.StickerSending)) {
// Acknowledge first
ev.preventDefault();
this.messaging.transport.reply(ev.detail, <IWidgetApiRequestEmptyData>{});
@@ -381,7 +382,7 @@ export class StopGapWidget extends EventEmitter {
(ev: CustomEvent<IWidgetApiRequest>) => {
// Acknowledge first
ev.preventDefault();
this.messaging.transport.reply(ev.detail, <IWidgetApiRequestEmptyData>{});
this.messaging?.transport.reply(ev.detail, <IWidgetApiRequestEmptyData>{});
// First close the stickerpicker
defaultDispatcher.dispatch({ action: "stickerpicker_close" });
@@ -415,7 +416,7 @@ export class StopGapWidget extends EventEmitter {
}),
});
}
this.messaging.transport.reply(ev.detail, <IWidgetApiRequestEmptyData>{});
this.messaging?.transport.reply(ev.detail, <IWidgetApiRequestEmptyData>{});
});
}
}
@@ -478,7 +479,7 @@ export class StopGapWidget extends EventEmitter {
private onToDeviceEvent = async (ev: MatrixEvent): Promise<void> => {
await this.client.decryptEventIfNeeded(ev);
if (ev.isDecryptionFailure()) return;
await this.messaging.feedToDevice(ev.getEffectiveEvent() as IRoomEvent, ev.isEncrypted());
await this.messaging?.feedToDevice(ev.getEffectiveEvent() as IRoomEvent, ev.isEncrypted());
};
private feedEvent(ev: MatrixEvent): void {
@@ -490,7 +491,7 @@ export class StopGapWidget extends EventEmitter {
//
// This approach of "read up to" prevents widgets receiving decryption spam from startup or
// receiving out-of-order events from backfill and such.
const upToEventId = this.readUpToMap[ev.getRoomId()];
const upToEventId = this.readUpToMap[ev.getRoomId()!];
if (upToEventId) {
// Small optimization for exact match (prevent search)
if (upToEventId === ev.getId()) {
@@ -501,7 +502,7 @@ export class StopGapWidget extends EventEmitter {
// Timelines are most recent last, so reverse the order and limit ourselves to 100 events
// to avoid overusing the CPU.
const timeline = this.client.getRoom(ev.getRoomId()).getLiveTimeline();
const timeline = this.client.getRoom(ev.getRoomId()!).getLiveTimeline();
const events = arrayFastClone(timeline.getEvents()).reverse().slice(0, 100);
for (const timelineEvent of events) {

View File

@@ -131,7 +131,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
protected async onReady(): Promise<void> {
this.updateAllRooms();
this.matrixClient.on(RoomStateEvent.Events, this.updateRoomFromState);
this.matrixClient?.on(RoomStateEvent.Events, this.updateRoomFromState);
this.pinnedRef = SettingsStore.watchSetting("Widgets.pinned", null, this.updateFromSettings);
this.layoutRef = SettingsStore.watchSetting("Widgets.layout", null, this.updateFromSettings);
WidgetStore.instance.on(UPDATE_EVENT, this.updateFromWidgetStore);
@@ -155,7 +155,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
private updateFromWidgetStore = (roomId?: string): void => {
if (roomId) {
const room = this.matrixClient.getRoom(roomId);
const room = this.matrixClient?.getRoom(roomId);
if (room) this.recalculateRoom(room);
} else {
this.updateAllRooms();
@@ -164,13 +164,13 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
private updateRoomFromState = (ev: MatrixEvent): void => {
if (ev.getType() !== WIDGET_LAYOUT_EVENT_TYPE) return;
const room = this.matrixClient.getRoom(ev.getRoomId());
const room = this.matrixClient?.getRoom(ev.getRoomId());
if (room) this.recalculateRoom(room);
};
private updateFromSettings = (settingName: string, roomId: string /* and other stuff */): void => {
if (roomId) {
const room = this.matrixClient.getRoom(roomId);
const room = this.matrixClient?.getRoom(roomId);
if (room) this.recalculateRoom(room);
} else {
this.updateAllRooms();
@@ -189,7 +189,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
const layoutEv = room.currentState.getStateEvents(WIDGET_LAYOUT_EVENT_TYPE, "");
const legacyPinned = SettingsStore.getValue("Widgets.pinned", room.roomId);
let userLayout = SettingsStore.getValue<ILayoutSettings>("Widgets.layout", room.roomId);
let userLayout = SettingsStore.getValue<ILayoutSettings | null>("Widgets.layout", room.roomId);
if (layoutEv && userLayout && userLayout.overrides !== layoutEv.getId()) {
// For some other layout that we don't really care about. The user can reset this
@@ -197,7 +197,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
userLayout = null;
}
const roomLayout: ILayoutStateEvent = layoutEv ? layoutEv.getContent() : null;
const roomLayout = layoutEv?.getContent<ILayoutStateEvent>() ?? null;
// We filter for the center container first.
// (An error is raised, if there are multiple widgets marked for the center container)
// For the right and top container multiple widgets are allowed.
@@ -218,9 +218,9 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
// The widget won't need to be put in any other container.
continue;
}
let targetContainer = defaultContainer;
let targetContainer: Container = defaultContainer;
if (!!manualContainer || !!stateContainer) {
targetContainer = manualContainer ? manualContainer : stateContainer;
targetContainer = manualContainer ?? stateContainer!;
} else if (isLegacyPinned && !stateContainer) {
// Special legacy case
targetContainer = Container.Top;
@@ -259,7 +259,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
// Determine width distribution and height of the top container now (the only relevant one)
const widths: number[] = [];
let maxHeight = null; // null == default
let maxHeight: number | null = null; // null == default
let doAutobalance = true;
for (let i = 0; i < topWidgets.length; i++) {
const widget = topWidgets[i];
@@ -487,7 +487,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
public canCopyLayoutToRoom(room: Room): boolean {
if (!this.matrixClient) return false; // not ready yet
return room.currentState.maySendStateEvent(WIDGET_LAYOUT_EVENT_TYPE, this.matrixClient.getUserId());
return room.currentState.maySendStateEvent(WIDGET_LAYOUT_EVENT_TYPE, this.matrixClient.getUserId()!);
}
public copyLayoutToRoom(room: Room): void {
@@ -508,7 +508,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
};
}
}
this.matrixClient.sendStateEvent(room.roomId, WIDGET_LAYOUT_EVENT_TYPE, evContent, "");
this.matrixClient?.sendStateEvent(room.roomId, WIDGET_LAYOUT_EVENT_TYPE, evContent, "");
}
private getAllWidgets(room: Room): [IApp, Container][] {
@@ -516,7 +516,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
if (!containers) return [];
const ret: [IApp, Container][] = [];
for (const container of Object.keys(containers)) {
for (const container in containers) {
const widgets = containers[container as Container].ordered;
for (const widget of widgets) {
ret.push([widget, container as Container]);

View File

@@ -32,7 +32,7 @@ export class WidgetPermissionStore {
// TODO (all functions here): Merge widgetKind with the widget definition
private packSettingKey(widget: Widget, kind: WidgetKind, roomId?: string): string {
let location = roomId;
let location: string | null | undefined = roomId;
if (kind !== WidgetKind.Room) {
location = this.context.client?.getUserId();
}