diff --git a/web/packages/teleterm/src/main.ts b/web/packages/teleterm/src/main.ts index 7f6d87399a81f..2f2022411187f 100644 --- a/web/packages/teleterm/src/main.ts +++ b/web/packages/teleterm/src/main.ts @@ -42,7 +42,7 @@ if (app.requestSingleInstanceLock()) { app.exit(1); } -async function initializeApp(): Promise { +function initializeApp(): void { updateSessionDataPath(); let devRelaunchScheduled = false; const settings = getRuntimeSettings(); @@ -52,7 +52,7 @@ async function initializeApp(): Promise { appStateFileStorage, configFileStorage, configJsonSchemaFileStorage, - } = await createFileStorages(settings.userDataDir); + } = createFileStorages(settings.userDataDir); runConfigFileMigration(configFileStorage); const configService = createConfigService({ @@ -238,23 +238,19 @@ function initMainLogger(settings: types.RuntimeSettings) { } function createFileStorages(userDataDir: string) { - return Promise.all([ - createFileStorage({ + return { + appStateFileStorage: createFileStorage({ filePath: path.join(userDataDir, 'app_state.json'), debounceWrites: true, }), - createFileStorage({ + configFileStorage: createFileStorage({ filePath: path.join(userDataDir, 'app_config.json'), debounceWrites: false, discardUpdatesOnLoadError: true, }), - createFileStorage({ + configJsonSchemaFileStorage: createFileStorage({ filePath: path.join(userDataDir, 'schema_app_config.json'), debounceWrites: false, }), - ]).then(storages => ({ - appStateFileStorage: storages[0], - configFileStorage: storages[1], - configJsonSchemaFileStorage: storages[2], - })); + }; } diff --git a/web/packages/teleterm/src/services/fileStorage/fileStorage.ts b/web/packages/teleterm/src/services/fileStorage/fileStorage.ts index 03da0187b424c..e22fc8c17b66b 100644 --- a/web/packages/teleterm/src/services/fileStorage/fileStorage.ts +++ b/web/packages/teleterm/src/services/fileStorage/fileStorage.ts @@ -14,7 +14,9 @@ * limitations under the License. */ -import fs from 'fs/promises'; +// Both versions are imported because some operations need to be sync. +import fsAsync from 'node:fs/promises'; +import fs from 'node:fs'; import { debounce } from 'shared/utils/highbar'; @@ -44,12 +46,25 @@ export interface FileStorage { getFileLoadingError(): Error | undefined; } -export async function createFileStorage(opts: { +/** + * createFileStorage reads and parses existing JSON structure from filePath or creates a new file + * under filePath with an empty object if the file is missing. + * + * createFileStorage itself uses blocking filesystem APIs but the functions of the returned + * FileStorage interface, such as write and replace, are async. + */ +// createFileStorage needs to be kept sync so that initialization of the app in main.ts can be sync. +// createFileStorage is called only during initialization, so blocking the main process during that +// time is acceptable. +// +// However, functions such as write or replace returned by createFileStorage need to be async as +// those are called after initialization. +export function createFileStorage(opts: { filePath: string; debounceWrites: boolean; /** Prevents state updates when the file has not been loaded correctly, so its content will not be overwritten. */ discardUpdatesOnLoadError?: boolean; -}): Promise { +}): FileStorage { if (!opts || !opts.filePath) { throw Error('missing filePath'); } @@ -58,7 +73,7 @@ export async function createFileStorage(opts: { let state: any, error: Error | undefined; try { - state = await loadState(filePath); + state = loadStateSync(filePath); } catch (e) { state = {}; error = e; @@ -121,18 +136,19 @@ export async function createFileStorage(opts: { }; } -async function loadState(filePath: string): Promise { - const file = await readOrCreateFile(filePath); +function loadStateSync(filePath: string): any { + const file = readOrCreateFileSync(filePath); return JSON.parse(file); } -async function readOrCreateFile(filePath: string): Promise { +const defaultValue = '{}' as const; + +function readOrCreateFileSync(filePath: string): string { try { - return await fs.readFile(filePath, { encoding: 'utf-8' }); + return fs.readFileSync(filePath, { encoding: 'utf-8' }); } catch (error) { - const defaultValue = '{}'; if (error?.code === 'ENOENT') { - await fs.writeFile(filePath, defaultValue); + fs.writeFileSync(filePath, defaultValue); return defaultValue; } throw error; @@ -149,6 +165,6 @@ const writeFileDebounced = debounce( ); const writeFile = (filePath: string, text: string) => - fs.writeFile(filePath, text).catch(error => { + fsAsync.writeFile(filePath, text).catch(error => { logger.error(`Cannot update ${filePath} file`, error); });