diff --git a/eslint.config.mjs b/eslint.config.mjs index 0d34b6f9448..3817a761a52 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -11,7 +11,6 @@ const globalIgnores = [ ]; export const typescriptRules = { - '@typescript-eslint/no-misused-promises': 'off', '@typescript-eslint/no-redundant-type-constituents': 'off', '@typescript-eslint/no-unnecessary-type-assertion': 'off', '@typescript-eslint/no-unsafe-argument': 'off', diff --git a/packages/kboot/src/usb-peripheral.ts b/packages/kboot/src/usb-peripheral.ts index ad289853a25..a2f33f2f0cd 100644 --- a/packages/kboot/src/usb-peripheral.ts +++ b/packages/kboot/src/usb-peripheral.ts @@ -55,145 +55,136 @@ export class UsbPeripheral implements Peripheral { } async sendCommand(options: CommandOption): Promise { - return new Promise(async (resolve, reject) => { - try { - await this.open(); - validateCommandParams(options.params); - const data = encodeCommandOption(options); - logger('send data %o', `<${convertToHexString(data)}>`); - await this._device.write(data); - const receivedData = await this._device.read(options.timeout || 2000); - logger('received data %o', `<${convertToHexString(receivedData)}>`); - const commandResponse = decodeCommandResponse(receivedData); - logger('command response: %o', commandResponse); - resolve(commandResponse); - } catch (err) { - logger('USB send command communication error %O', err); + try { + await this.open(); + validateCommandParams(options.params); + const data = encodeCommandOption(options); + logger('send data %o', `<${convertToHexString(data)}>`); + await this._device.write(data); + const receivedData = await this._device.read(options.timeout || 2000); + logger('received data %o', `<${convertToHexString(receivedData)}>`); + const commandResponse = decodeCommandResponse(receivedData); + logger('command response: %o', commandResponse); + return commandResponse; + } catch (err) { + logger('USB send command communication error %O', err); + + throw err; + } + } - return reject(err); + async writeMemory(option: DataOption): Promise { + try { + const command: CommandOption = { + command: Commands.WriteMemory, + hasDataPhase: true, + params: [ + ...pack(option.startAddress, { bits: 32 }), + ...pack(option.data.length, { bits: 32 }) + ] + }; + + const firsCommandResponse = await this.sendCommand(command); + if (firsCommandResponse.tag !== ResponseTags.Generic) { + logger('Invalid write memory response! %o', firsCommandResponse); + throw new Error('Invalid write memory response!'); + } + + if (firsCommandResponse.code !== 0) { + logger('Non zero write memory response! %o', firsCommandResponse); + throw new Error(`Non zero write memory response! Response code: ${firsCommandResponse.code}`); } - }); - } - writeMemory(option: DataOption): Promise { - return new Promise(async (resolve, reject) => { - try { - const command: CommandOption = { - command: Commands.WriteMemory, - hasDataPhase: true, - params: [ - ...pack(option.startAddress, { bits: 32 }), - ...pack(option.data.length, { bits: 32 }) - ] - }; - - const firsCommandResponse = await this.sendCommand(command); - if (firsCommandResponse.tag !== ResponseTags.Generic) { - logger('Invalid write memory response! %o', firsCommandResponse); - return reject(new Error('Invalid write memory response!')); - } - - if (firsCommandResponse.code !== 0) { - logger('Non zero write memory response! %o', firsCommandResponse); - return reject(new Error(`Non zero write memory response! Response code: ${firsCommandResponse.code}`)); - } - - for (let i = 0; i < option.data.length; i = i + WRITE_DATA_STREAM_PACKAGE_LENGTH) { - const slice = option.data.slice(i, i + WRITE_DATA_STREAM_PACKAGE_LENGTH); - - const writeData = [ - 2, // USB channel - 0, - slice.length, - 0, // TODO: What is it? - ...slice - ]; - - logger('send data %o', convertToHexString(writeData)); - await this._device.write(writeData); - // workaround to prevent main thread blocking - await snooze(1); - } - - const receivedData = await this._device.read(option.timeout || 2000); - logger('write memory received data %o', `<${convertToHexString(receivedData)}>`); - const secondCommandResponse = decodeCommandResponse(receivedData); - logger('write memory response: %o', secondCommandResponse); - - if (secondCommandResponse.tag !== ResponseTags.Generic) { - logger('Invalid write memory final response %o', secondCommandResponse); - return reject(new Error('Invalid write memory final response!')); - } - - if (secondCommandResponse.code !== 0) { - logger('Non zero write memory final response %o', secondCommandResponse); - const msg = `Non zero write memory final response! Response code: ${secondCommandResponse.code}`; - return reject(new Error(msg)); - } - - resolve(); - } catch (err) { - logger('Can not write memory data %O', err); - reject(err); + for (let i = 0; i < option.data.length; i = i + WRITE_DATA_STREAM_PACKAGE_LENGTH) { + const slice = option.data.slice(i, i + WRITE_DATA_STREAM_PACKAGE_LENGTH); + + const writeData = [ + 2, // USB channel + 0, + slice.length, + 0, // TODO: What is it? + ...slice + ]; + + logger('send data %o', convertToHexString(writeData)); + await this._device.write(writeData); + // workaround to prevent main thread blocking + await snooze(1); } - }); + const receivedData = await this._device.read(option.timeout || 2000); + logger('write memory received data %o', `<${convertToHexString(receivedData)}>`); + const secondCommandResponse = decodeCommandResponse(receivedData); + logger('write memory response: %o', secondCommandResponse); + + if (secondCommandResponse.tag !== ResponseTags.Generic) { + logger('Invalid write memory final response %o', secondCommandResponse); + throw new Error('Invalid write memory final response!'); + } + + if (secondCommandResponse.code !== 0) { + logger('Non zero write memory final response %o', secondCommandResponse); + const msg = `Non zero write memory final response! Response code: ${secondCommandResponse.code}`; + throw new Error(msg); + } + } catch (err) { + logger('Can not write memory data %O', err); + throw err; + } } - readMemory(startAddress: number, count: number): Promise { - return new Promise(async (resolve, reject) => { - try { - const command: CommandOption = { - command: Commands.ReadMemory, - params: [ - ...pack(startAddress, { bits: 32 }), - ...pack(count, { bits: 32 }) - ] - }; - - const firsCommandResponse = await this.sendCommand(command); - if (firsCommandResponse.tag !== ResponseTags.ReadMemory) { - logger('Invalid read memory response %o', firsCommandResponse); - return reject(new Error('Invalid read memory response!')); - } - - if (firsCommandResponse.code !== 0) { - logger('Non zero read memory response %o', firsCommandResponse); - return reject(new Error(`Non zero read memory response! Response code: ${firsCommandResponse.code}`)); - } - - const byte4Number = firsCommandResponse.raw.slice(12, 15); - const arrivingDataSize = convertLittleEndianNumber(byte4Number); - const memoryData: Array = []; - while (memoryData.length < arrivingDataSize) { - const receivedData = await this._device.read(2000); - logger('received data %o', `<${convertToHexString(receivedData)}>`); - memoryData.push(...receivedData); - // workaround to prevent main thread blocking - await snooze(1); - } - - const responseData = await this._device.read(2000); - logger('received data %o', `<${convertToHexString(responseData)}>`); - - const secondCommandResponse = decodeCommandResponse(responseData); - if (secondCommandResponse.tag !== ResponseTags.Generic) { - logger('Invalid read memory final response %o', secondCommandResponse); - return reject(new Error('Invalid read memory final response!')); - } - - if (secondCommandResponse.code !== 0) { - logger('Non zero read memory final response %o', secondCommandResponse); - const msg = `Non zero read memory final response! Response code: ${secondCommandResponse.code}`; - return reject(new Error(msg)); - } - - resolve(Buffer.from(memoryData)); - } catch (error) { - logger('Read memory error %O', error); - - reject(error); + async readMemory(startAddress: number, count: number): Promise { + try { + const command: CommandOption = { + command: Commands.ReadMemory, + params: [ + ...pack(startAddress, { bits: 32 }), + ...pack(count, { bits: 32 }) + ] + }; + + const firsCommandResponse = await this.sendCommand(command); + if (firsCommandResponse.tag !== ResponseTags.ReadMemory) { + logger('Invalid read memory response %o', firsCommandResponse); + throw new Error('Invalid read memory response!'); + } + + if (firsCommandResponse.code !== 0) { + logger('Non zero read memory response %o', firsCommandResponse); + throw new Error(`Non zero read memory response! Response code: ${firsCommandResponse.code}`); + } + + const byte4Number = firsCommandResponse.raw.slice(12, 15); + const arrivingDataSize = convertLittleEndianNumber(byte4Number); + const memoryData: Array = []; + while (memoryData.length < arrivingDataSize) { + const receivedData = await this._device.read(2000); + logger('received data %o', `<${convertToHexString(receivedData)}>`); + memoryData.push(...receivedData); + // workaround to prevent main thread blocking + await snooze(1); + } + + const responseData = await this._device.read(2000); + logger('received data %o', `<${convertToHexString(responseData)}>`); + + const secondCommandResponse = decodeCommandResponse(responseData); + if (secondCommandResponse.tag !== ResponseTags.Generic) { + logger('Invalid read memory final response %o', secondCommandResponse); + throw new Error('Invalid read memory final response!'); + } + + if (secondCommandResponse.code !== 0) { + logger('Non zero read memory final response %o', secondCommandResponse); + const msg = `Non zero read memory final response! Response code: ${secondCommandResponse.code}`; + throw new Error(msg); } - }); + + return Buffer.from(memoryData); + } catch (error) { + logger('Read memory error %O', error); + + throw error; + } } } diff --git a/packages/uhk-agent/src/electron-main.ts b/packages/uhk-agent/src/electron-main.ts index e18837f5369..21715136e32 100644 --- a/packages/uhk-agent/src/electron-main.ts +++ b/packages/uhk-agent/src/electron-main.ts @@ -135,26 +135,11 @@ async function createWindow() { }); // Emitted when the window is closed. - win.on('closed', async () => { - // Dereference the window object, usually you would store windows - // in an array if your app supports multi windows, this is the time - // when you should delete the corresponding element. - logger.misc('[Electron Main] win closed'); - win = null; - try { - await deviceService.close(); - } catch (error) { - // TODO: Investigate it deeper. It happens on MacOs 15+ sometimes - logger.error('[Electron Main] Error while closing DeviceService when electron has been closed', error); - } - deviceService = null; - appUpdateService = null; - appService = null; - await uhkHidDeviceService.close(); - uhkHidDeviceService = null; - sudoService = null; - await smartMacroDocService.stop(); - smartMacroDocService = null; + win.on('closed', () => { + windowClosed() + .catch((error) => { + logger.error('[Electron Main] Error while closing window', error); + }) }); win.once('ready-to-show', () => { @@ -200,6 +185,28 @@ async function createWindow() { win.on('close', () => saveWindowState(win, logger)); } +async function windowClosed() { + // Dereference the window object, usually you would store windows + // in an array if your app supports multi windows, this is the time + // when you should delete the corresponding element. + logger.misc('[Electron Main] win closed'); + win = null; + try { + await deviceService.close(); + } catch (error) { + // TODO: Investigate it deeper. It happens on MacOs 15+ sometimes + logger.error('[Electron Main] Error while closing DeviceService when electron has been closed', error); + } + deviceService = null; + appUpdateService = null; + appService = null; + await uhkHidDeviceService.close(); + uhkHidDeviceService = null; + sudoService = null; + await smartMacroDocService.stop(); + smartMacroDocService = null; +} + if (isSecondInstance) { app.quit(); } else if (options['capture-oled']) { @@ -254,7 +261,12 @@ if (isSecondInstance) { // This method will be called when Electron has finished // initialization and is ready to create browser windows. // Some APIs can only be used after this event occurs. - app.on('ready', createWindow); + app.on('ready', () => { + createWindow() + .catch((error) => { + logger.error('[Electron Main] when creating the window: ', error); + }); + }); // Quit when all windows are closed. app.on('window-all-closed', () => { @@ -264,11 +276,14 @@ if (isSecondInstance) { app.on('will-quit', () => { }); - app.on('activate', async () => { + app.on('activate', () => { // On macOS it's common to re-create a window in the app when the // dock icon is clicked and there are no other windows open. if (win === null) { - await createWindow(); + createWindow() + .catch((error) => { + logger.error('[Electron Main] when activating the app: ', error); + }); } }); diff --git a/packages/uhk-agent/src/services/app-update.service.ts b/packages/uhk-agent/src/services/app-update.service.ts index 5b925b43838..18f7514d72a 100644 --- a/packages/uhk-agent/src/services/app-update.service.ts +++ b/packages/uhk-agent/src/services/app-update.service.ts @@ -45,10 +45,15 @@ export class AppUpdateService extends MainServiceBase { this.sendIpcToWindow(IpcEvents.autoUpdater.checkingForUpdate); }); - autoUpdater.on('update-available', async (info: UpdateInfo) => { + autoUpdater.on('update-available', (info: UpdateInfo) => { this.logService.misc('[AppUpdateService] update available. Downloading started'); - await autoUpdater.downloadUpdate(); - this.sendIpcToWindow(IpcEvents.autoUpdater.updateAvailable, info); + autoUpdater.downloadUpdate() + .then(() => { + this.sendIpcToWindow(IpcEvents.autoUpdater.updateAvailable, info); + }) + .catch((error) => { + this.logService.error('[AppUpdateService] Error when reporting update available: ', error); + }); }); autoUpdater.on('update-not-available', (info: UpdateInfo) => { @@ -88,12 +93,16 @@ export class AppUpdateService extends MainServiceBase { return autoUpdater.quitAndInstall(true, true); }); - ipcMain.on(IpcEvents.app.appStarted, async () => { - if (await this.checkForUpdateAtStartup()) { - this.sendAutoUpdateNotification = false; - this.logService.misc('[AppUpdateService] app started. Automatically check for update.'); - this.checkForUpdate(); - } + ipcMain.on(IpcEvents.app.appStarted, () => { + this.logService.misc('[AppUpdateService] app started'); + this.checkForUpdateAtStartup() + .then((checkForUpdate) => { + if (checkForUpdate) { + this.sendAutoUpdateNotification = false; + this.logService.misc('[AppUpdateService] app started. Automatically check for update.'); + this.checkForUpdate(); + } + }) }); ipcMain.on(IpcEvents.autoUpdater.checkForUpdate, (event: Electron.Event, args) => { diff --git a/packages/uhk-agent/src/services/sudo.service.ts b/packages/uhk-agent/src/services/sudo.service.ts index 60e2f570d92..568245315fc 100644 --- a/packages/uhk-agent/src/services/sudo.service.ts +++ b/packages/uhk-agent/src/services/sudo.service.ts @@ -61,7 +61,7 @@ export class SudoService { }; const command = `sh ${scriptPath}`; this.logService.misc('[SudoService] Set privilege command: ', command); - sudo.exec(command, options, async (error: Error) => { + sudo.exec(command, options, (error: Error) => { const response = new IpcResponse(); if (error) { @@ -72,9 +72,14 @@ export class SudoService { response.success = true; } - await rm(tmpDirectory, { recursive: true, force: true }); - this.deviceService.startPollUhkDevice(); - event.sender.send(IpcEvents.device.setPrivilegeOnLinuxReply, response); + rm(tmpDirectory, { recursive: true, force: true }) + .then(() => { + this.deviceService.startPollUhkDevice(); + event.sender.send(IpcEvents.device.setPrivilegeOnLinuxReply, response); + }) + .catch((error) => { + this.logService.error('[SudoService] Error when removing tmp directory: ', error); + }); }); } } diff --git a/packages/uhk-web/src/app/store/effects/device.ts b/packages/uhk-web/src/app/store/effects/device.ts index 03d25fd5ee7..855be30700f 100644 --- a/packages/uhk-web/src/app/store/effects/device.ts +++ b/packages/uhk-web/src/app/store/effects/device.ts @@ -189,19 +189,23 @@ export class DeviceEffects { } if (state.multiDevice) { - return this.router.navigate(['/multi-device']); + this.router.navigate(['/multi-device']); + return; } if (!state.hasPermission || state.udevRulesInfo === UdevRulesInfo.Different) { - return this.router.navigate(['/privilege']); + this.router.navigate(['/privilege']); + return; } if (state.bootloaderActive || state.leftHalfBootloaderActive || state.dongle.bootloaderActive) { - return this.router.navigate(['/recovery-device']); + this.router.navigate(['/recovery-device']); + return; } if (shouldUpgradeFirmware(state.hardwareModules?.rightModuleInfo?.userConfigVersion)) { - return this.router.navigate(['/update-firmware']); + this.router.navigate(['/update-firmware']); + return; } if (state.connectedDevice && state.communicationInterfaceAvailable) { @@ -212,13 +216,14 @@ export class DeviceEffects { ].some(start => route.state.url.startsWith(start)); if (allowDefaultNavigation) { - return this.router.navigate(['/']); + this.router.navigate(['/']); + return; } return; } - return this.router.navigate(['/detection']); + this.router.navigate(['/detection']); }), distinctUntilChanged(( [prevAction, prevRoute, prevConnected], @@ -335,11 +340,14 @@ export class DeviceEffects { ofType(ActionTypes.SaveConfiguration), withLatestFrom(this.store, this.store.select(getShowFirmwareUpgradePanel)), tap(([action, state, shouldUpgradeFirmware]) => { - if (shouldUpgradeFirmware) - return this.router.navigate(['/update-firmware']); + if (shouldUpgradeFirmware) { + this.router.navigate(['/update-firmware']); + return; + } if (state.userConfiguration.userConfiguration.hostConnections.length > HOST_CONNECTION_COUNT_MAX) { - return this.router.navigate(['/host-connections']); + this.router.navigate(['/host-connections']); + return; } setTimeout(() => this.sendUserConfigToKeyboard( diff --git a/packages/uhk-web/src/app/store/effects/smart-macro-doc.effect.ts b/packages/uhk-web/src/app/store/effects/smart-macro-doc.effect.ts index 03a479b1078..c749541a20f 100644 --- a/packages/uhk-web/src/app/store/effects/smart-macro-doc.effect.ts +++ b/packages/uhk-web/src/app/store/effects/smart-macro-doc.effect.ts @@ -2,7 +2,7 @@ import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { distinctUntilChanged, map, startWith, tap, withLatestFrom } from 'rxjs/operators'; -import { FirmwareRepoInfo } from 'uhk-common'; +import { FirmwareRepoInfo, LogService } from 'uhk-common'; import { MonacoEditorCompletionItemProvider } from '../../services/monaco-editor-completion-item-provider'; import { SmartMacroDocRendererService } from '../../services/smart-macro-doc-renderer.service'; @@ -19,13 +19,21 @@ export class SmartMacroDocEffect { ofType(AppActions.ActionTypes.AppBootstrapped), startWith(new AppActions.AppStartedAction()), withLatestFrom(this.store.select(runningInElectron)), - tap(async ([, electron]) => { + tap(([, electron]) => { if (!electron) { - const response = await fetch('https://raw.githubusercontent.com/UltimateHackingKeyboard/firmware/master/doc-dev/reference-manual.md'); - if (response.ok) { - const text = await response.text(); - this.completionItemProvider.setReferenceManual(text); - } + fetch('https://raw.githubusercontent.com/UltimateHackingKeyboard/firmware/master/doc-dev/reference-manual.md') + .then(async (response) => { + if (response.ok) { + const text = await response.text(); + this.completionItemProvider.setReferenceManual(text); + } + else { + this.logService.error('[SmartMacroDocEffect] failed to fetch reference manual', response.statusText); + } + }) + .catch((error) => { + this.logService.error('[SmartMacroDocEffect] failed to fetch reference manual', error); + }); } }) ), @@ -59,6 +67,7 @@ export class SmartMacroDocEffect { constructor(private actions$: Actions, private completionItemProvider: MonacoEditorCompletionItemProvider, + private logService: LogService, private smartMacroDocRendererService: SmartMacroDocRendererService, private smartMacroDocService: SmartMacroDocService, private store: Store) { diff --git a/packages/uhk-web/src/app/store/effects/user-config.ts b/packages/uhk-web/src/app/store/effects/user-config.ts index a41b0376706..f5ccf860dc0 100644 --- a/packages/uhk-web/src/app/store/effects/user-config.ts +++ b/packages/uhk-web/src/app/store/effects/user-config.ts @@ -167,7 +167,7 @@ export class UserConfigEffects { map(action => action.payload), switchMap((payload: UndoUserConfigData) => this.dataStorageRepository.saveConfig(payload.config, payload.uhkDeviceProduct) .pipe( - tap(() => this.router.navigate([payload.path])), + tap(() => { this.router.navigate([payload.path]); }), map(() => new LoadUserConfigSuccessAction(payload.config)) ) ) @@ -413,15 +413,17 @@ export class UserConfigEffects { resetKeymapQueryParams$ = createEffect(() => this.actions$ .pipe( ofType(Keymaps.ActionTypes.SaveKey, Keymaps.ActionTypes.ClosePopover), - tap(() => this.router.navigate([], { - queryParams: { - module: null, - key: null, - remapOnAllKeymap: null, - remapOnAllLayer: null, - }, - queryParamsHandling: 'merge' - })) + tap(() => { + this.router.navigate([], { + queryParams: { + module: null, + key: null, + remapOnAllKeymap: null, + remapOnAllLayer: null, + }, + queryParamsHandling: 'merge' + }) + }) ), { dispatch: false } );