Improve typing (#364)

* Improve typing

* Make sonar happier
This commit is contained in:
Michael Telatynski 2022-05-23 15:44:29 +01:00 committed by GitHub
parent d1f488a094
commit e50e04c507
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 248 additions and 36 deletions

54
src/@types/keytar.d.ts vendored Normal file
View file

@ -0,0 +1,54 @@
// Based on https://github.com/atom/node-keytar/blob/master/keytar.d.ts because keytar is a hak-dependency and not a normal one
// Definitions by: Milan Burda <https://github.com/miniak>, Brendan Forster <https://github.com/shiftkey>, Hari Juturu <https://github.com/juturu>
// Adapted from DefinitelyTyped: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/keytar/index.d.ts
declare module "keytar" {
/**
* Get the stored password for the service and account.
*
* @param service The string service name.
* @param account The string account name.
*
* @returns A promise for the password string.
*/
export function getPassword(service: string, account: string): Promise<string | null>;
/**
* Add the password for the service and account to the keychain.
*
* @param service The string service name.
* @param account The string account name.
* @param password The string password.
*
* @returns A promise for the set password completion.
*/
export function setPassword(service: string, account: string, password: string): Promise<void>;
/**
* Delete the stored password for the service and account.
*
* @param service The string service name.
* @param account The string account name.
*
* @returns A promise for the deletion status. True on success.
*/
export function deletePassword(service: string, account: string): Promise<boolean>;
/**
* Find a password for the service in the keychain.
*
* @param service The string service name.
*
* @returns A promise for the password string.
*/
export function findPassword(service: string): Promise<string | null>;
/**
* Find all accounts and passwords for `service` in the keychain.
*
* @param service The string service name.
*
* @returns A promise for the array of found credentials.
*/
export function findCredentials(service: string): Promise<Array<{ account: string, password: string}>>;
}

145
src/@types/matrix-seshat.d.ts vendored Normal file
View file

@ -0,0 +1,145 @@
/*
Copyright 2022 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
declare module "matrix-seshat" {
interface IConfig {
language?: string;
passphrase?: string;
}
/* eslint-disable camelcase */
interface IMatrixEvent {
event_id: string;
sender: string;
room_id: string;
origin_server_ts: number;
content: Record<string, any>;
}
interface IMatrixProfile {
displayname?: string;
avatar_url?: string;
}
interface ISearchArgs {
searchTerm: number;
limit: number;
before_limit: number;
after_limit: number;
order_by_recency: boolean;
next_batch?: string;
}
interface ISearchContext {
events_before: IMatrixEvent[];
events_after: IMatrixEvent[];
profile_info: { [userId: string]: IMatrixProfile };
}
interface ISearchResult {
next_batch: string;
count: number;
results: Array<{
rank: number;
result: IMatrixEvent;
context: ISearchContext;
}>;
}
/* eslint-enable camelcase */
interface ICheckpoint {
roomId: string;
token: string;
fullCrawl: boolean;
direction: "b" | "f";
}
interface IDatabaseStats {
size: number;
eventCount: number;
roomCount: number;
}
interface ILoadArgs {
roomId: string;
limit: number;
fromEvent: string;
direction: "b" | "f";
}
interface ILoadResult {
event: IMatrixEvent;
matrixProfile: IMatrixProfile;
}
export class Seshat {
constructor(path: string, config?: IConfig);
public addEvent(matrixEvent: IMatrixEvent, profile?: IMatrixProfile): void;
public deleteEvent(eventId: string): Promise<boolean>;
public commit(force?: boolean): Promise<number>;
public commitSync(wait?: boolean, force?: boolean): number;
public reload(): void;
public search(args: ISearchArgs): Promise<ISearchResult>;
public searchSync(
term: string,
limit?: number,
beforeLimit?: number,
afterLimit?: number,
orderByRecency?: boolean,
): ISearchResult;
public addHistoricEventsSync(
events: IMatrixEvent[],
newCheckpoint?: ICheckpoint,
oldCheckpoint?: ICheckpoint,
): boolean;
public addHistoricEvents(
events: IMatrixEvent[],
newCheckpoint?: ICheckpoint,
oldCheckpoint?: ICheckpoint,
): Promise<boolean>;
public addCrawlerCheckpoint(checkpoint: ICheckpoint): Promise<void>;
public removeCrawlerCheckpoint(checkpoint: ICheckpoint): Promise<void>;
public loadCheckpoints(): Promise<ICheckpoint[]>;
public getSize(): Promise<number>;
public getStats(): Promise<IDatabaseStats>;
public delete(): Promise<void>;
public shutdown(): Promise<void>;
public changePassphrase(newPassphrase: string): Promise<void>;
public isEmpty(): Promise<boolean>;
public isRoomIndexed(roomId: string): Promise<boolean>;
public getUserVersion(): Promise<number>;
public setUserVersion(version: number): Promise<void>;
public loadFileEvents(args: ILoadArgs): Promise<ILoadResult[]>;
}
interface IRecoveryInfo {
totalEvents: number;
reindexedEvents: number;
done: number;
}
export class SeshatRecovery {
constructor(path: string, config?: IConfig);
public info(): IRecoveryInfo;
public getUserVersion(): Promise<number>;
public shutdown(): Promise<void>;
public reindex(): Promise<void>;
}
export class ReindexError extends Error {
constructor(message?: string);
}
}

View file

@ -39,18 +39,26 @@ import crypto from "crypto";
import { URL } from "url";
import minimist from "minimist";
import type * as Keytar from "keytar"; // Hak dependency type
import type {
Seshat as SeshatType,
SeshatRecovery as SeshatRecoveryType,
ReindexError as ReindexErrorType,
} from "matrix-seshat"; // Hak dependency type
import * as tray from "./tray";
import { buildMenuTemplate } from './vectormenu';
import webContentsHandler from './webcontents-handler';
import * as updater from './updater';
import { getProfileFromDeeplink, protocolInit, recordSSOSession } from './protocol';
import { _t, AppLocalization } from './language-helper';
import Input = Electron.Input;
import IpcMainEvent = Electron.IpcMainEvent;
const argv = minimist(process.argv, {
alias: { help: "h" },
});
let keytar;
let keytar: typeof Keytar;
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
keytar = require('keytar');
@ -63,9 +71,9 @@ try {
}
let seshatSupported = false;
let Seshat;
let SeshatRecovery;
let ReindexError;
let Seshat: typeof SeshatType;
let SeshatRecovery: typeof SeshatRecoveryType;
let ReindexError: typeof ReindexErrorType;
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
@ -84,13 +92,18 @@ try {
// Things we need throughout the file but need to be created
// async to are initialised in setupGlobals()
let asarPath;
let resPath;
let vectorConfig;
let iconPath;
let trayConfig;
let launcher;
let appLocalization;
let asarPath: string;
let resPath: string;
let iconPath: string;
let vectorConfig: Record<string, any>;
let trayConfig: {
// eslint-disable-next-line camelcase
icon_path: string;
brand: string;
};
let launcher: AutoLaunch;
let appLocalization: AppLocalization;
if (argv["help"]) {
console.log("Options:");
@ -108,12 +121,12 @@ if (argv["help"]) {
// Electron creates the user data directory (with just an empty 'Dictionaries' directory...)
// as soon as the app path is set, so pick a random path in it that must exist if it's a
// real user data directory.
function isRealUserDataDir(d) {
function isRealUserDataDir(d: string): boolean {
return fs.existsSync(path.join(d, 'IndexedDB'));
}
// check if we are passed a profile in the SSO callback url
let userDataPath;
let userDataPath: string;
const userDataPathInProtocol = getProfileFromDeeplink(argv["_"]);
if (userDataPathInProtocol) {
@ -143,7 +156,7 @@ if (userDataPathInProtocol) {
}
app.setPath('userData', userDataPath);
async function tryPaths(name, root, rawPaths) {
async function tryPaths(name: string, root: string, rawPaths: string[]): Promise<string> {
// Make everything relative to root
const paths = rawPaths.map(p => path.join(root, p));
@ -162,7 +175,7 @@ async function tryPaths(name, root, rawPaths) {
}
// Find the webapp resources and set up things that require them
async function setupGlobals() {
async function setupGlobals(): Promise<void> {
// find the webapp asar.
asarPath = await tryPaths("webapp", __dirname, [
// If run from the source checkout, this will be in the directory above
@ -245,9 +258,9 @@ async function setupGlobals() {
});
}
async function moveAutoLauncher() {
async function moveAutoLauncher(): Promise<void> {
// Look for an auto-launcher under 'Riot' and if we find one, port it's
// enabled/disbaledp-ness over to the new 'Element' launcher
// enabled/disabled-ness over to the new 'Element' launcher
if (!vectorConfig.brand || vectorConfig.brand === 'Element') {
const oldLauncher = new AutoLaunch({
name: 'Riot',
@ -274,18 +287,18 @@ const store = new Store<{
disableHardwareAcceleration?: boolean;
}>({ name: "electron-config" });
let eventIndex = null;
let eventIndex: SeshatType = null;
let mainWindow = null;
let mainWindow: BrowserWindow = null;
global.appQuitting = false;
const exitShortcuts = [
const exitShortcuts: Array<(input: Input, platform: string) => boolean> = [
(input, platform) => platform !== 'darwin' && input.alt && input.key.toUpperCase() === 'F4',
(input, platform) => platform !== 'darwin' && input.control && input.key.toUpperCase() === 'Q',
(input, platform) => platform === 'darwin' && input.meta && input.key.toUpperCase() === 'Q',
];
const warnBeforeExit = (event, input) => {
const warnBeforeExit = (event: Event, input: Input): void => {
const shouldWarnBeforeExit = store.get('warnBeforeExit', true);
const exitShortcutPressed =
input.type === 'keyDown' && exitShortcuts.some(shortcutFn => shortcutFn(input, process.platform));
@ -305,14 +318,14 @@ const warnBeforeExit = (event, input) => {
}
};
const deleteContents = async (p) => {
const deleteContents = async (p: string): Promise<void> => {
for (const entry of await afs.readdir(p)) {
const curPath = path.join(p, entry);
await afs.unlink(curPath);
}
};
async function randomArray(size) {
async function randomArray(size: number): Promise<string> {
return new Promise((resolve, reject) => {
crypto.randomBytes(size, (err, buf) => {
if (err) {
@ -330,12 +343,12 @@ async function randomArray(size) {
// no other way to catch this error).
// Assuming we generally run from the console when developing,
// this is far preferable.
process.on('uncaughtException', function(error) {
process.on('uncaughtException', function(error: Error): void {
console.log('Unhandled exception', error);
});
let focusHandlerAttached = false;
ipcMain.on('setBadgeCount', function(ev, count) {
ipcMain.on('setBadgeCount', function(_ev: IpcMainEvent, count: number): void {
if (process.platform !== 'win32') {
// only set badgeCount on Mac/Linux, the docs say that only those platforms support it but turns out Electron
// has some Windows support too, and in some Windows environments this leads to two badges rendering atop
@ -347,7 +360,7 @@ ipcMain.on('setBadgeCount', function(ev, count) {
}
});
ipcMain.on('loudNotification', function() {
ipcMain.on('loudNotification', function(): void {
if (process.platform === 'win32' && mainWindow && !mainWindow.isFocused() && !focusHandlerAttached) {
mainWindow.flashFrame(true);
mainWindow.once('focus', () => {
@ -358,8 +371,8 @@ ipcMain.on('loudNotification', function() {
}
});
let powerSaveBlockerId = null;
ipcMain.on('app_onAction', function(ev, payload) {
let powerSaveBlockerId: number = null;
ipcMain.on('app_onAction', function(_ev: IpcMainEvent, payload) {
switch (payload.action) {
case 'call_state':
if (powerSaveBlockerId !== null && powerSaveBlocker.isStarted(powerSaveBlockerId)) {
@ -376,11 +389,11 @@ ipcMain.on('app_onAction', function(ev, payload) {
}
});
ipcMain.on('ipcCall', async function(ev, payload) {
ipcMain.on('ipcCall', async function(_ev: IpcMainEvent, payload) {
if (!mainWindow) return;
const args = payload.args || [];
let ret;
let ret: any;
switch (payload.name) {
case 'getUpdateFeedUrl':
@ -542,7 +555,7 @@ ipcMain.on('ipcCall', async function(ev, payload) {
});
const seshatDefaultPassphrase = "DEFAULT_PASSPHRASE";
async function getOrCreatePassphrase(key) {
async function getOrCreatePassphrase(key: string): Promise<string> {
if (keytar) {
try {
const storedPassphrase = await keytar.getPassword("element.io", key);
@ -561,7 +574,7 @@ async function getOrCreatePassphrase(key) {
}
}
ipcMain.on('seshat', async function(ev, payload) {
ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise<void> {
if (!mainWindow) return;
const sendError = (id, e) => {
@ -576,7 +589,7 @@ ipcMain.on('seshat', async function(ev, payload) {
};
const args = payload.args || [];
let ret;
let ret: any;
switch (payload.name) {
case 'supportsEventIndexing':
@ -913,7 +926,7 @@ app.on('ready', async () => {
target[target.length - 1] = 'index.html';
}
let baseDir;
let baseDir: string;
if (target[1] === 'webapp') {
baseDir = asarPath;
} else {
@ -1048,7 +1061,7 @@ app.on('activate', () => {
mainWindow.show();
});
function beforeQuit() {
function beforeQuit(): void {
global.appQuitting = true;
if (mainWindow) {
mainWindow.webContents.send('before-quit');

View file

@ -80,7 +80,7 @@ export function recordSSOSession(sessionID: string): void {
writeStore(store);
}
export function getProfileFromDeeplink(args): string | undefined {
export function getProfileFromDeeplink(args: string[]): string | undefined {
// check if we are passed a profile in the SSO callback url
const deeplinkUrl = args.find(arg => arg.startsWith(PROTOCOL + '//'));
if (deeplinkUrl && deeplinkUrl.includes(SEARCH_PARAM)) {