From b51cf4fc7e64c4b041035119644e35b02aa170ef Mon Sep 17 00:00:00 2001
From: Dariusz Niemczyk
Date: Sun, 8 Aug 2021 20:28:37 +0200
Subject: [PATCH] Typescript strict null checks pt1
---
src/CallHandler.tsx | 100 +++++++++++++++++++---------------
src/languageHandler.tsx | 19 ++++---
src/settings/SettingsStore.ts | 7 ++-
src/widgets/Jitsi.ts | 14 ++---
4 files changed, 77 insertions(+), 63 deletions(-)
diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx
index 41571666c353..a1477aa257f4 100644
--- a/src/CallHandler.tsx
+++ b/src/CallHandler.tsx
@@ -150,14 +150,10 @@ export default class CallHandler extends EventEmitter {
// call with a different party to this one.
private transferees = new Map(); // callId (target) -> call (transferee)
private audioPromises = new Map>();
- private dispatcherRef: string = null;
- private supportsPstnProtocol = null;
- private pstnSupportPrefixed = null; // True if the server only support the prefixed pstn protocol
- private supportsSipNativeVirtual = null; // im.vector.protocol.sip_virtual and im.vector.protocol.sip_native
- private pstnSupportCheckTimer: number;
- // For rooms we've been invited to, true if they're from virtual user, false if we've checked and they aren't.
- private invitedRoomsAreVirtual = new Map();
- private invitedRoomCheckInProgress = false;
+ private dispatcherRef: string | null = null;
+ private supportsPstnProtocol = false;
+ private pstnSupportPrefixed = false; // True if the server only support the prefixed pstn protocol
+ private supportsSipNativeVirtual = false; // im.vector.protocol.sip_virtual and im.vector.protocol.sip_native
// Map of the asserted identity users after we've looked them up using the API.
// We need to be be able to determine the mapped room synchronously, so we
@@ -178,8 +174,8 @@ export default class CallHandler 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 {
- if (!call) return null;
+ public roomIdForCall(call: MatrixCall): string | undefined {
+ if (!call) return undefined;
const voipConfig = SdkConfig.get()['voip'];
@@ -263,8 +259,6 @@ export default class CallHandler extends EventEmitter {
} else if (protocols[PROTOCOL_PSTN_PREFIXED] !== undefined) {
this.supportsPstnProtocol = Boolean(protocols[PROTOCOL_PSTN_PREFIXED]);
if (this.supportsPstnProtocol) this.pstnSupportPrefixed = true;
- } else {
- this.supportsPstnProtocol = null;
}
dis.dispatch({ action: Action.PstnSupportUpdated });
@@ -281,9 +275,6 @@ export default class CallHandler extends EventEmitter {
console.log("Failed to check for protocol support and no retries remain: assuming no support", e);
} else {
console.log("Failed to check for protocol support: will retry", e);
- this.pstnSupportCheckTimer = setTimeout(() => {
- this.checkProtocols(maxTries - 1);
- }, 10000);
}
}
}
@@ -330,15 +321,15 @@ export default class CallHandler extends EventEmitter {
}, true);
};
- public getCallById(callId: string): MatrixCall {
+ public getCallById(callId: string): MatrixCall | null {
for (const call of this.calls.values()) {
if (call.callId === callId) return call;
}
return null;
}
- getCallForRoom(roomId: string): MatrixCall {
- return this.calls.get(roomId) || null;
+ getCallForRoom(roomId?: string): MatrixCall | undefined {
+ return roomId ? this.calls.get(roomId) : undefined;
}
getAnyActiveCall() {
@@ -351,7 +342,7 @@ export default class CallHandler extends EventEmitter {
}
getAllActiveCalls() {
- const activeCalls = [];
+ const activeCalls: MatrixCall[] = [];
for (const call of this.calls.values()) {
if (call.state !== CallState.Ended && call.state !== CallState.Ringing) {
@@ -362,7 +353,7 @@ export default class CallHandler extends EventEmitter {
}
getAllActiveCallsNotInRoom(notInThisRoomId) {
- const callsNotInThatRoom = [];
+ const callsNotInThatRoom: MatrixCall[] = [];
for (const [roomId, call] of this.calls.entries()) {
if (roomId !== notInThisRoomId && call.state !== CallState.Ended) {
@@ -395,7 +386,8 @@ export default class CallHandler extends EventEmitter {
}
};
if (this.audioPromises.has(audioId)) {
- this.audioPromises.set(audioId, this.audioPromises.get(audioId).then(() => {
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ this.audioPromises.set(audioId, this.audioPromises.get(audioId)!.then(() => {
audio.load();
return playAudio();
}));
@@ -411,7 +403,8 @@ export default class CallHandler extends EventEmitter {
const audio = document.getElementById(audioId) as HTMLMediaElement;
if (audio) {
if (this.audioPromises.has(audioId)) {
- this.audioPromises.set(audioId, this.audioPromises.get(audioId).then(() => audio.pause()));
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ this.audioPromises.set(audioId, this.audioPromises.get(audioId)!.then(() => audio.pause()));
} else {
// pause doesn't return a promise, so just do it
audio.pause();
@@ -430,7 +423,10 @@ export default class CallHandler extends EventEmitter {
}
private setCallListeners(call: MatrixCall) {
- let mappedRoomId = this.roomIdForCall(call);
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ let mappedRoomId: string = this.roomIdForCall(call)!;
+ // A lot of things here expect proper room id, so if we don't have it it's better to quit early.
+ if (!mappedRoomId) return;
call.on(CallEvent.Error, (err: CallError) => {
if (!this.matchesCallForThisRoom(call)) return;
@@ -587,7 +583,7 @@ export default class CallHandler extends EventEmitter {
const newMappedRoomId = this.roomIdForCall(call);
console.log(`Old room ID: ${mappedRoomId}, new room ID: ${newMappedRoomId}`);
- if (newMappedRoomId !== mappedRoomId) {
+ if (newMappedRoomId && newMappedRoomId !== mappedRoomId) {
this.removeCallForRoom(mappedRoomId);
mappedRoomId = newMappedRoomId;
console.log("Moving call to room " + mappedRoomId);
@@ -668,10 +664,12 @@ export default class CallHandler extends EventEmitter {
});
}
- private removeCallForRoom(roomId: string) {
+ private removeCallForRoom(roomId?: string) {
console.log("Removing call for room ", roomId);
- this.calls.delete(roomId);
- this.emit(CallHandlerEvent.CallsChanged, this.calls);
+ if (roomId) {
+ this.calls.delete(roomId);
+ this.emit(CallHandlerEvent.CallsChanged, this.calls);
+ }
}
private showICEFallbackPrompt() {
@@ -691,16 +689,16 @@ export default class CallHandler extends EventEmitter {
"turn.matrix.org
, 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, { code },
+ undefined, { code },
) }
,
button: _t('Try using turn.matrix.org'),
cancelButton: _t('OK'),
onFinished: (allow) => {
- SettingsStore.setValue("fallbackICEServerAllowed", null, SettingLevel.DEVICE, allow);
+ SettingsStore.setValue("fallbackICEServerAllowed", undefined, SettingLevel.DEVICE, allow);
cli.setFallbackICEServerAllowed(allow);
},
- }, null, true);
+ }, undefined, true);
}
private showMediaCaptureError(call: MatrixCall) {
@@ -729,18 +727,25 @@ export default class CallHandler extends EventEmitter {
Modal.createTrackedDialog('Media capture failed', '', ErrorDialog, {
title, description,
- }, null, true);
+ }, undefined, true);
}
- private async placeCall(roomId: string, type: PlaceCallType, transferee: MatrixCall) {
+ private async placeCall(roomId: string, type: PlaceCallType, transferee?: MatrixCall) {
Analytics.trackEvent('voip', 'placeCall', 'type', type);
CountlyAnalytics.instance.trackStartCall(roomId, type === PlaceCallType.Video, false);
const mappedRoomId = (await VoipUserMapper.sharedInstance().getOrCreateVirtualRoomForRoom(roomId)) || roomId;
logger.debug("Mapped real room " + roomId + " to room ID " + mappedRoomId);
- const timeUntilTurnCresExpire = MatrixClientPeg.get().getTurnServersExpiry() - Date.now();
- console.log("Current turn creds expire in " + timeUntilTurnCresExpire + " ms");
+ const turnServersExpiry = MatrixClientPeg.get().getTurnServersExpiry();
+ if (turnServersExpiry) {
+ const timeUntilTurnCresExpire = turnServersExpiry - Date.now();
+ console.log("Current turn creds expire in " + timeUntilTurnCresExpire + " ms");
+ } else {
+ if (process.env.NODE_ENV === 'development') {
+ throw new Error('Could not get current turn server expiration date.');
+ }
+ }
const call = MatrixClientPeg.get().createCall(mappedRoomId);
console.log("Adding call for room ", roomId);
@@ -848,6 +853,8 @@ export default class CallHandler extends EventEmitter {
const call = payload.call as MatrixCall;
const mappedRoomId = CallHandler.sharedInstance().roomIdForCall(call);
+ // Leave early if can't figure out room id.
+ if (!mappedRoomId) return;
if (this.getCallForRoom(mappedRoomId)) {
console.log(
"Got incoming call for room " + mappedRoomId +
@@ -870,18 +877,20 @@ export default class CallHandler extends EventEmitter {
}
break;
case 'hangup':
- case 'reject':
- if (!this.calls.get(payload.room_id)) {
+ case 'reject': {
+ const roomId = this.calls.get(payload.room_id);
+ if (!roomId) {
return; // no call to hangup
}
if (payload.action === 'reject') {
- this.calls.get(payload.room_id).reject();
+ roomId.reject();
} else {
- this.calls.get(payload.room_id).hangup(CallErrorCode.UserHangup, false);
+ roomId.hangup(CallErrorCode.UserHangup, false);
}
// don't remove the call yet: let the hangup event handler do it (otherwise it will throw
// the hangup event away)
break;
+ }
case 'hangup_all':
for (const call of this.calls.values()) {
call.hangup(CallErrorCode.UserHangup, false);
@@ -901,6 +910,7 @@ export default class CallHandler extends EventEmitter {
}
const call = this.calls.get(payload.room_id);
+ if (!call) return;
call.answer();
this.setActiveCallRoomId(payload.room_id);
CountlyAnalytics.instance.trackJoinCall(payload.room_id, call.type === CallType.Video, false);
@@ -952,7 +962,7 @@ export default class CallHandler extends EventEmitter {
room_id: roomId,
});
- await this.placeCall(roomId, PlaceCallType.Voice, null);
+ await this.placeCall(roomId, PlaceCallType.Voice, undefined);
}
private async startTransferToPhoneNumber(call: MatrixCall, destination: string, consultFirst: boolean) {
@@ -1102,11 +1112,13 @@ export default class CallHandler extends EventEmitter {
// We'll just obliterate them all. There should only ever be one, but might as well
// be safe.
const roomInfo = WidgetStore.instance.getRoom(roomId);
- const jitsiWidgets = roomInfo.widgets.filter(w => WidgetType.JITSI.matches(w.type));
- jitsiWidgets.forEach(w => {
- // setting invalid content removes it
- WidgetUtils.setRoomWidget(roomId, w.id);
- });
+ if (roomInfo) {
+ const jitsiWidgets = roomInfo.widgets.filter(w => WidgetType.JITSI.matches(w.type));
+ jitsiWidgets.forEach(w => {
+ // setting invalid content removes it
+ WidgetUtils.setRoomWidget(roomId, w.id);
+ });
+ }
},
});
}
diff --git a/src/languageHandler.tsx b/src/languageHandler.tsx
index 8b1d83b337a4..ae319f88fb20 100644
--- a/src/languageHandler.tsx
+++ b/src/languageHandler.tsx
@@ -57,7 +57,7 @@ export function newTranslatableError(message: string) {
}
export function getUserLanguage(): string {
- const language = SettingsStore.getValue("language", null, /*excludeDefault:*/true);
+ const language = SettingsStore.getValue("language", undefined, /*excludeDefault:*/true);
if (language) {
return language;
} else {
@@ -135,7 +135,7 @@ export type TranslatedString = string | React.ReactNode;
// eslint-next-line @typescript-eslint/naming-convention
// eslint-nexline @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, tags?: Tags): React.ReactNode;
export function _t(text: string, variables?: IVariables, tags?: Tags): TranslatedString {
// Don't do substitutions in counterpart. We handle it ourselves so we can replace with React components
// However, still pass the variables to counterpart so that it can choose the correct plural if count is given
@@ -184,7 +184,7 @@ export function sanitizeForTranslation(text: string): string {
* @return a React component if any non-strings were used in substitutions, otherwise a string
*/
export function substitute(text: string, variables?: IVariables): string;
-export function substitute(text: string, variables: IVariables, tags: Tags): string;
+export function substitute(text: string, variables?: IVariables, tags?: Tags): string;
export function substitute(text: string, variables?: IVariables, tags?: Tags): string | React.ReactNode {
let result: React.ReactNode | string = text;
@@ -221,7 +221,7 @@ export function replaceByRegexes(text: string, mapping: Tags): React.ReactNode;
export function replaceByRegexes(text: string, mapping: IVariables | Tags): string | React.ReactNode {
// We initially store our output as an array of strings and objects (e.g. React components).
// This will then be converted to a string or a at the end
- const output = [text];
+ const output: Array = [text];
// If we insert any components we need to wrap the output in a span. React doesn't like just an array of components.
let shouldWrapInSpan = false;
@@ -251,7 +251,7 @@ export function replaceByRegexes(text: string, mapping: IVariables | Tags): stri
// The textual part before the first match
const head = inputText.substr(0, match.index);
- const parts = [];
+ const parts: Array = [];
// keep track of prevMatch
let prevMatch;
while (match) {
@@ -259,7 +259,7 @@ export function replaceByRegexes(text: string, mapping: IVariables | Tags): stri
prevMatch = match;
const capturedGroups = match.slice(2);
- let replaced;
+ let replaced: string | React.ReactNode;
// If substitution is a function, call it
if (mapping[regexpString] instanceof Function) {
replaced = ((mapping as Tags)[regexpString] as Function)(...capturedGroups);
@@ -358,7 +358,7 @@ export function setLanguage(preferredLangs: string | string[]) {
}).then((langData) => {
counterpart.registerTranslations(langToUse, langData);
counterpart.setLocale(langToUse);
- SettingsStore.setValue("language", null, SettingLevel.DEVICE, langToUse);
+ SettingsStore.setValue("language", undefined, SettingLevel.DEVICE, langToUse);
// Adds a lot of noise to test runs, so disable logging there.
if (process.env.NODE_ENV !== "test") {
console.log("set language to " + langToUse);
@@ -375,7 +375,10 @@ export function setLanguage(preferredLangs: string | string[]) {
export function getAllLanguagesFromJson() {
return getLangsJson().then((langsObject) => {
- const langs = [];
+ const langs: Array<{
+ value: string;
+ label: string;
+ }> = [];
for (const langKey in langsObject) {
if (langsObject.hasOwnProperty(langKey)) {
langs.push({
diff --git a/src/settings/SettingsStore.ts b/src/settings/SettingsStore.ts
index c5b83cbcd003..64414677797b 100644
--- a/src/settings/SettingsStore.ts
+++ b/src/settings/SettingsStore.ts
@@ -418,7 +418,7 @@ export default class SettingsStore {
*/
/* eslint-enable valid-jsdoc */
- public static async setValue(settingName: string, roomId: string, level: SettingLevel, value: any): Promise {
+ public static async setValue(settingName: string, roomId?: string, level?: SettingLevel, value: any): Promise {
// Verify that the setting is actually a setting
const setting = SETTINGS[settingName];
if (!setting) {
@@ -438,6 +438,7 @@ export default class SettingsStore {
settingName = setting.invertedSettingName;
value = !value;
}
+ if (!roomId) return;
if (!handler.canSetValue(settingName, roomId)) {
throw new Error("User cannot set " + settingName + " at " + level + " in " + roomId);
@@ -610,9 +611,9 @@ export default class SettingsStore {
console.log(`--- END DEBUG`);
}
- private static getHandler(settingName: string, level: SettingLevel): SettingsHandler {
+ private static getHandler(settingName: string, level?: SettingLevel): SettingsHandler | undefined {
const handlers = SettingsStore.getHandlers(settingName);
- if (!handlers[level]) return null;
+ if (!level || !handlers[level]) return undefined;
return handlers[level];
}
diff --git a/src/widgets/Jitsi.ts b/src/widgets/Jitsi.ts
index e86fd41ed99a..1b22a95125d9 100644
--- a/src/widgets/Jitsi.ts
+++ b/src/widgets/Jitsi.ts
@@ -41,21 +41,21 @@ export class Jitsi {
*
* See https://github.com/matrix-org/prosody-mod-auth-matrix-user-verification
*/
- public async getJitsiAuth(): Promise {
+ public async getJitsiAuth(): Promise {
if (!this.preferredDomain) {
- return null;
+ return undefined;
}
let data;
try {
const response = await fetch(`https://${this.preferredDomain}/.well-known/element/jitsi`);
data = await response.json();
} catch (error) {
- return null;
+ return undefined;
}
if (data.auth) {
return data.auth;
}
- return null;
+ return undefined;
}
public start() {
@@ -83,12 +83,10 @@ export class Jitsi {
/**
* Parses the given URL into the data needed for a Jitsi widget, if the widget
* URL matches the preferredDomain for the app.
- * @param {string} url The URL to parse.
- * @returns {JitsiWidgetData} The widget data if eligible, otherwise null.
*/
- public parsePreferredConferenceUrl(url: string): JitsiWidgetData {
+ public parsePreferredConferenceUrl(url: string): JitsiWidgetData | undefined {
const parsed = new URL(url);
- if (parsed.hostname !== this.preferredDomain) return null; // invalid
+ if (parsed.hostname !== this.preferredDomain) return undefined; // invalid
return {
conferenceId: parsed.pathname,
domain: parsed.hostname,