From 14b12f59d1b0891be14b462642934c36f813bd6c Mon Sep 17 00:00:00 2001 From: Seth Foster Date: Fri, 9 Aug 2024 16:13:54 -0400 Subject: [PATCH 1/9] fix(app-shell): forward usb request errors and log We were swallowing all errors from usb requests, which doesn't seem like a great idea - it definitely led to lots of errors in browser logs. While we're at it, let's get some more in-detph logging of usb requests. --- app-shell/src/usb.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/app-shell/src/usb.ts b/app-shell/src/usb.ts index 276c537f062..8f329c7fa14 100644 --- a/app-shell/src/usb.ts +++ b/app-shell/src/usb.ts @@ -18,7 +18,7 @@ import { } from './constants' import type { IpcMainInvokeEvent } from 'electron' -import type { AxiosRequestConfig } from 'axios' +import type { AxiosRequestConfig, isAxiosError } from 'axios' import type { IPCSafeFormData } from '@opentrons/app/src/redux/shell/types' import type { UsbDevice } from '@opentrons/app/src/redux/system-info/types' import type { PortInfo } from '@opentrons/usb-bridge/node-client' @@ -114,12 +114,14 @@ async function usbListener( const usbHttpAgent = getSerialPortHttpAgent() try { + usbLog.silly(`${config.method} ${config.url} timeout=${config.timeout}`) const response = await axios.request({ httpAgent: usbHttpAgent, ...config, data, headers: { ...config.headers, ...formHeaders }, }) + usbLog.silly(`${config.method} ${config.url} resolved ok`) return { error: false, data: response.data, @@ -127,9 +129,12 @@ async function usbListener( statusText: response.statusText, } } catch (e) { - if (e instanceof Error) { - console.log(`axios request error ${e?.message ?? 'unknown'}`) - } + usbLog.info( + `${config.method} ${config.url} failed: ${ + isAxiosError(e) ? e.toJSON() : JSON.stringify(e) + }` + ) + throw e } } From d43913932d88ee792011e5185251fb3d22c326cf Mon Sep 17 00:00:00 2001 From: Seth Foster Date: Fri, 9 Aug 2024 16:24:52 -0400 Subject: [PATCH 2/9] that's not a type --- app-shell/src/usb.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app-shell/src/usb.ts b/app-shell/src/usb.ts index 8f329c7fa14..97510ca3e94 100644 --- a/app-shell/src/usb.ts +++ b/app-shell/src/usb.ts @@ -18,7 +18,7 @@ import { } from './constants' import type { IpcMainInvokeEvent } from 'electron' -import type { AxiosRequestConfig, isAxiosError } from 'axios' +import { AxiosRequestConfig, isAxiosError } from 'axios' import type { IPCSafeFormData } from '@opentrons/app/src/redux/shell/types' import type { UsbDevice } from '@opentrons/app/src/redux/system-info/types' import type { PortInfo } from '@opentrons/usb-bridge/node-client' From 0a33902053e2a9711fd7e4998c1101691da0c0a0 Mon Sep 17 00:00:00 2001 From: Seth Foster Date: Fri, 9 Aug 2024 16:48:25 -0400 Subject: [PATCH 3/9] bad --- app-shell/src/usb.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app-shell/src/usb.ts b/app-shell/src/usb.ts index 97510ca3e94..0d2df7499b7 100644 --- a/app-shell/src/usb.ts +++ b/app-shell/src/usb.ts @@ -18,7 +18,7 @@ import { } from './constants' import type { IpcMainInvokeEvent } from 'electron' -import { AxiosRequestConfig, isAxiosError } from 'axios' +import type { AxiosRequestConfig } from 'axios' import type { IPCSafeFormData } from '@opentrons/app/src/redux/shell/types' import type { UsbDevice } from '@opentrons/app/src/redux/system-info/types' import type { PortInfo } from '@opentrons/usb-bridge/node-client' @@ -131,7 +131,7 @@ async function usbListener( } catch (e) { usbLog.info( `${config.method} ${config.url} failed: ${ - isAxiosError(e) ? e.toJSON() : JSON.stringify(e) + axios.isAxiosError(e) ? e.toJSON() : JSON.stringify(e) }` ) throw e From a20de840a7d230c0c1d4614d2e5c48b7503d6a8f Mon Sep 17 00:00:00 2001 From: Seth Foster Date: Fri, 9 Aug 2024 16:56:40 -0400 Subject: [PATCH 4/9] i think lint is saying this --- app-shell/src/usb.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app-shell/src/usb.ts b/app-shell/src/usb.ts index 0d2df7499b7..0fd0f5c072e 100644 --- a/app-shell/src/usb.ts +++ b/app-shell/src/usb.ts @@ -130,9 +130,9 @@ async function usbListener( } } catch (e) { usbLog.info( - `${config.method} ${config.url} failed: ${ - axios.isAxiosError(e) ? e.toJSON() : JSON.stringify(e) - }` + `${config.method} ${config.url} failed: ${JSON.stringify( + axios.isAxiosError(e) ? e.toJSON() : e + )}` ) throw e } From bbf5df35a2e0b9b10ba9f8b85159efe8cf41fee3 Mon Sep 17 00:00:00 2001 From: Seth Foster Date: Tue, 20 Aug 2024 10:15:52 -0400 Subject: [PATCH 5/9] two error analyzers --- app-shell/src/system-info/usb-devices.ts | 13 +++++++++---- app-shell/src/usb.ts | 1 + 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/app-shell/src/system-info/usb-devices.ts b/app-shell/src/system-info/usb-devices.ts index 660f606fd03..de36ba6a877 100644 --- a/app-shell/src/system-info/usb-devices.ts +++ b/app-shell/src/system-info/usb-devices.ts @@ -133,10 +133,15 @@ function upstreamDeviceFromUsbDeviceWinAPI( const parsePoshJsonOutputToWmiObjectArray = ( dump: string ): WmiObject[] => { - if (dump[0] === '[') { - return JSON.parse(dump) as WmiObject[] - } else { - return [JSON.parse(dump) as WmiObject] + try { + if (dump[0] === '[') { + return JSON.parse(dump) as WmiObject[] + } else { + return [JSON.parse(dump) as WmiObject] + } + } catch (e: any) { + log.error(`Failed to parse posh json output: ${dump}`) + throw e } } if (dump.stderr !== '') { diff --git a/app-shell/src/usb.ts b/app-shell/src/usb.ts index 0fd0f5c072e..fe25fdf6cf5 100644 --- a/app-shell/src/usb.ts +++ b/app-shell/src/usb.ts @@ -122,6 +122,7 @@ async function usbListener( headers: { ...config.headers, ...formHeaders }, }) usbLog.silly(`${config.method} ${config.url} resolved ok`) + usbLog.info(`response is ${JSON.stringify(response.data)}`) return { error: false, data: response.data, From f6f21fd83c3a2e9a9fdfe4008417cd3bd15ef5f7 Mon Sep 17 00:00:00 2001 From: Seth Foster Date: Tue, 20 Aug 2024 13:33:22 -0400 Subject: [PATCH 6/9] dont stringify, dont throw across --- app-shell/src/usb.ts | 316 +++++++++++++++++----------------- app/src/redux/shell/remote.ts | 6 +- 2 files changed, 166 insertions(+), 156 deletions(-) diff --git a/app-shell/src/usb.ts b/app-shell/src/usb.ts index fe25fdf6cf5..b22b96b4367 100644 --- a/app-shell/src/usb.ts +++ b/app-shell/src/usb.ts @@ -3,18 +3,18 @@ import axios from 'axios' import FormData from 'form-data' import { - fetchSerialPortList, - SerialPortHttpAgent, - DEFAULT_PRODUCT_ID, - DEFAULT_VENDOR_ID, + fetchSerialPortList, + SerialPortHttpAgent, + DEFAULT_PRODUCT_ID, + DEFAULT_VENDOR_ID, } from '@opentrons/usb-bridge/node-client' import { createLogger } from './log' import { usbRequestsStart, usbRequestsStop } from './config/actions' import { - SYSTEM_INFO_INITIALIZED, - USB_DEVICE_ADDED, - USB_DEVICE_REMOVED, + SYSTEM_INFO_INITIALIZED, + USB_DEVICE_ADDED, + USB_DEVICE_REMOVED, } from './constants' import type { IpcMainInvokeEvent } from 'electron' @@ -29,184 +29,190 @@ const usbLog = createLogger('usb') let usbFetchInterval: NodeJS.Timeout export function getSerialPortHttpAgent(): SerialPortHttpAgent | undefined { - return usbHttpAgent + return usbHttpAgent } export function createSerialPortHttpAgent( - path: string, - onComplete: (err: Error | null, agent?: SerialPortHttpAgent) => void + path: string, + onComplete: (err: Error | null, agent?: SerialPortHttpAgent) => void ): void { - if (usbHttpAgent != null) { - onComplete( - new Error('Tried to make a USB http agent when one already existed') - ) - } else { - usbHttpAgent = new SerialPortHttpAgent( - { - maxFreeSockets: 1, - maxSockets: 1, - maxTotalSockets: 1, - keepAlive: true, - keepAliveMsecs: Infinity, - path, - logger: usbLog, - timeout: 100000, - }, - (err, agent?) => { - if (err != null) { - usbHttpAgent = undefined - } - onComplete(err, agent) - } - ) - } + if (usbHttpAgent != null) { + onComplete( + new Error('Tried to make a USB http agent when one already existed') + ) + } else { + usbHttpAgent = new SerialPortHttpAgent( + { + maxFreeSockets: 1, + maxSockets: 1, + maxTotalSockets: 1, + keepAlive: true, + keepAliveMsecs: Infinity, + path, + logger: usbLog, + timeout: 100000, + }, + (err, agent?) => { + if (err != null) { + usbHttpAgent = undefined + } + onComplete(err, agent) + } + ) + } } export function destroyAndStopUsbHttpRequests(dispatch: Dispatch): void { - if (usbHttpAgent != null) { - usbHttpAgent.destroy() - } - usbHttpAgent = undefined - ipcMain.removeHandler('usb:request') - dispatch(usbRequestsStop()) - // handle any additional invocations of usb:request - ipcMain.handle('usb:request', () => - Promise.resolve({ - status: 400, - statusText: 'USB robot disconnected', - }) - ) + if (usbHttpAgent != null) { + usbHttpAgent.destroy() + } + usbHttpAgent = undefined + ipcMain.removeHandler('usb:request') + dispatch(usbRequestsStop()) + // handle any additional invocations of usb:request + ipcMain.handle('usb:request', () => + Promise.resolve({ + status: 400, + statusText: 'USB robot disconnected', + }) + ) } function isUsbDeviceOt3(device: UsbDevice): boolean { - return ( - device.productId === parseInt(DEFAULT_PRODUCT_ID, 16) && - device.vendorId === parseInt(DEFAULT_VENDOR_ID, 16) - ) + return ( + device.productId === parseInt(DEFAULT_PRODUCT_ID, 16) && + device.vendorId === parseInt(DEFAULT_VENDOR_ID, 16) + ) } function reconstructFormData(ipcSafeFormData: IPCSafeFormData): FormData { - const result = new FormData() - ipcSafeFormData.forEach(entry => { - entry.type === 'file' - ? result.append(entry.name, Buffer.from(entry.value), entry.filename) - : result.append(entry.name, entry.value) - }) - return result + const result = new FormData() + ipcSafeFormData.forEach(entry => { + entry.type === 'file' + ? result.append( + entry.name, + Buffer.from(entry.value), + entry.filename + ) + : result.append(entry.name, entry.value) + }) + return result } async function usbListener( - _event: IpcMainInvokeEvent, - config: AxiosRequestConfig + _event: IpcMainInvokeEvent, + config: AxiosRequestConfig ): Promise { - // TODO(bh, 2023-05-03): remove mutation - let { data } = config - let formHeaders = {} + // TODO(bh, 2023-05-03): remove mutation + let { data } = config + let formHeaders = {} - // check for formDataProxy - if (data?.proxiedFormData != null) { - // reconstruct FormData - const formData = reconstructFormData( - data.proxiedFormData as IPCSafeFormData - ) - formHeaders = formData.getHeaders() - data = formData - } + // check for formDataProxy + if (data?.proxiedFormData != null) { + // reconstruct FormData + const formData = reconstructFormData( + data.proxiedFormData as IPCSafeFormData + ) + formHeaders = formData.getHeaders() + data = formData + } - const usbHttpAgent = getSerialPortHttpAgent() - try { - usbLog.silly(`${config.method} ${config.url} timeout=${config.timeout}`) - const response = await axios.request({ - httpAgent: usbHttpAgent, - ...config, - data, - headers: { ...config.headers, ...formHeaders }, - }) - usbLog.silly(`${config.method} ${config.url} resolved ok`) - usbLog.info(`response is ${JSON.stringify(response.data)}`) - return { - error: false, - data: response.data, - status: response.status, - statusText: response.statusText, + const usbHttpAgent = getSerialPortHttpAgent() + try { + usbLog.silly(`${config.method} ${config.url} timeout=${config.timeout}`) + const response = await axios.request({ + httpAgent: usbHttpAgent, + ...config, + data, + headers: { ...config.headers, ...formHeaders }, + }) + usbLog.silly(`${config.method} ${config.url} resolved ok`) + return { + error: null, + data: response.data, + status: response.status, + statusText: response.statusText, + } + } catch (e) { + usbLog.info(`${config.method} ${config.url} failed: ${e}`) + return { + error: axios.isAxiosError(e) ? e.toJSON() : e, + } } - } catch (e) { - usbLog.info( - `${config.method} ${config.url} failed: ${JSON.stringify( - axios.isAxiosError(e) ? e.toJSON() : e - )}` - ) - throw e - } } function pollSerialPortAndCreateAgent(dispatch: Dispatch): void { - // usb poll already initialized - if (usbFetchInterval != null) { - return - } - usbFetchInterval = setInterval(() => { - // already connected to an Opentrons robot via USB - tryCreateAndStartUsbHttpRequests(dispatch) - }, 10000) + // usb poll already initialized + if (usbFetchInterval != null) { + return + } + usbFetchInterval = setInterval(() => { + // already connected to an Opentrons robot via USB + tryCreateAndStartUsbHttpRequests(dispatch) + }, 10000) } function tryCreateAndStartUsbHttpRequests(dispatch: Dispatch): void { - fetchSerialPortList() - .then((list: PortInfo[]) => { - const ot3UsbSerialPort = list.find( - port => - port.productId?.localeCompare(DEFAULT_PRODUCT_ID, 'en-US', { - sensitivity: 'base', - }) === 0 && - port.vendorId?.localeCompare(DEFAULT_VENDOR_ID, 'en-US', { - sensitivity: 'base', - }) === 0 - ) + fetchSerialPortList() + .then((list: PortInfo[]) => { + const ot3UsbSerialPort = list.find( + port => + port.productId?.localeCompare(DEFAULT_PRODUCT_ID, 'en-US', { + sensitivity: 'base', + }) === 0 && + port.vendorId?.localeCompare(DEFAULT_VENDOR_ID, 'en-US', { + sensitivity: 'base', + }) === 0 + ) - // retry if no Flex serial port found - usb-detection and serialport packages have race condition - if (ot3UsbSerialPort == null) { - usbLog.debug('No Flex serial port found.') - return - } - if (usbHttpAgent == null) { - createSerialPortHttpAgent(ot3UsbSerialPort.path, (err, agent?) => { - if (err != null) { - const message = err?.message ?? err - usbLog.error(`Failed to create serial port: ${message}`) - } - if (agent != null) { - ipcMain.removeHandler('usb:request') - ipcMain.handle('usb:request', usbListener) - dispatch(usbRequestsStart()) - } + // retry if no Flex serial port found - usb-detection and serialport packages have race condition + if (ot3UsbSerialPort == null) { + usbLog.debug('No Flex serial port found.') + return + } + if (usbHttpAgent == null) { + createSerialPortHttpAgent( + ot3UsbSerialPort.path, + (err, agent?) => { + if (err != null) { + const message = err?.message ?? err + usbLog.error( + `Failed to create serial port: ${message}` + ) + } + if (agent != null) { + ipcMain.removeHandler('usb:request') + ipcMain.handle('usb:request', usbListener) + dispatch(usbRequestsStart()) + } + } + ) + } }) - } - }) - .catch(e => - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - usbLog.debug(`fetchSerialPortList error ${e?.message ?? 'unknown'}`) - ) + .catch(e => + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + usbLog.debug(`fetchSerialPortList error ${e?.message ?? 'unknown'}`) + ) } export function registerUsb(dispatch: Dispatch): (action: Action) => unknown { - return function handleIncomingAction(action: Action): void { - switch (action.type) { - case SYSTEM_INFO_INITIALIZED: - if (action.payload.usbDevices.find(isUsbDeviceOt3) != null) { - tryCreateAndStartUsbHttpRequests(dispatch) - } - pollSerialPortAndCreateAgent(dispatch) - break - case USB_DEVICE_ADDED: - if (isUsbDeviceOt3(action.payload.usbDevice)) { - tryCreateAndStartUsbHttpRequests(dispatch) - } - break - case USB_DEVICE_REMOVED: - if (isUsbDeviceOt3(action.payload.usbDevice)) { - destroyAndStopUsbHttpRequests(dispatch) + return function handleIncomingAction(action: Action): void { + switch (action.type) { + case SYSTEM_INFO_INITIALIZED: + if (action.payload.usbDevices.find(isUsbDeviceOt3) != null) { + tryCreateAndStartUsbHttpRequests(dispatch) + } + pollSerialPortAndCreateAgent(dispatch) + break + case USB_DEVICE_ADDED: + if (isUsbDeviceOt3(action.payload.usbDevice)) { + tryCreateAndStartUsbHttpRequests(dispatch) + } + break + case USB_DEVICE_REMOVED: + if (isUsbDeviceOt3(action.payload.usbDevice)) { + destroyAndStopUsbHttpRequests(dispatch) + } + break } - break } - } } diff --git a/app/src/redux/shell/remote.ts b/app/src/redux/shell/remote.ts index 6692c6dca04..4af9cc5a3ce 100644 --- a/app/src/redux/shell/remote.ts +++ b/app/src/redux/shell/remote.ts @@ -57,7 +57,11 @@ export async function appShellRequestor( : data const configProxy = { ...config, data: formDataProxy } - return await remote.ipcRenderer.invoke('usb:request', configProxy) + const result = await remote.ipcRenderer.invoke('usb:request', configProxy) + if (result?.error != null) { + throw result.error + } + return result } interface CallbackStore { From 989aba964b2158bd0b083e0bbdac34ab9c82ef6d Mon Sep 17 00:00:00 2001 From: Seth Foster Date: Tue, 20 Aug 2024 14:27:14 -0400 Subject: [PATCH 7/9] this sucks --- app-shell/src/usb.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/app-shell/src/usb.ts b/app-shell/src/usb.ts index b22b96b4367..d0f79b9637e 100644 --- a/app-shell/src/usb.ts +++ b/app-shell/src/usb.ts @@ -98,6 +98,18 @@ function reconstructFormData(ipcSafeFormData: IPCSafeFormData): FormData { return result } +const cloneError = (e: object): object => + Object.entries(axios.isAxiosError(e) ? e.toJSON() : e).reduce( + (acc, [k, v]) => { + try { + return (acc[k] = structuredClone(v)) + } catch (e) { + return acc + } + }, + {} + ) + async function usbListener( _event: IpcMainInvokeEvent, config: AxiosRequestConfig @@ -135,7 +147,7 @@ async function usbListener( } catch (e) { usbLog.info(`${config.method} ${config.url} failed: ${e}`) return { - error: axios.isAxiosError(e) ? e.toJSON() : e, + error: cloneError(e), } } } From 5044a93aa9ddf465569f2d871bfc9c972232cc13 Mon Sep 17 00:00:00 2001 From: Seth Foster Date: Tue, 20 Aug 2024 15:52:11 -0400 Subject: [PATCH 8/9] formats and such --- app-shell/src/usb.ts | 340 +++++++++++++++++++++---------------------- 1 file changed, 166 insertions(+), 174 deletions(-) diff --git a/app-shell/src/usb.ts b/app-shell/src/usb.ts index d0f79b9637e..4f8121e5160 100644 --- a/app-shell/src/usb.ts +++ b/app-shell/src/usb.ts @@ -3,18 +3,18 @@ import axios from 'axios' import FormData from 'form-data' import { - fetchSerialPortList, - SerialPortHttpAgent, - DEFAULT_PRODUCT_ID, - DEFAULT_VENDOR_ID, + fetchSerialPortList, + SerialPortHttpAgent, + DEFAULT_PRODUCT_ID, + DEFAULT_VENDOR_ID, } from '@opentrons/usb-bridge/node-client' import { createLogger } from './log' import { usbRequestsStart, usbRequestsStop } from './config/actions' import { - SYSTEM_INFO_INITIALIZED, - USB_DEVICE_ADDED, - USB_DEVICE_REMOVED, + SYSTEM_INFO_INITIALIZED, + USB_DEVICE_ADDED, + USB_DEVICE_REMOVED, } from './constants' import type { IpcMainInvokeEvent } from 'electron' @@ -29,202 +29,194 @@ const usbLog = createLogger('usb') let usbFetchInterval: NodeJS.Timeout export function getSerialPortHttpAgent(): SerialPortHttpAgent | undefined { - return usbHttpAgent + return usbHttpAgent } export function createSerialPortHttpAgent( - path: string, - onComplete: (err: Error | null, agent?: SerialPortHttpAgent) => void + path: string, + onComplete: (err: Error | null, agent?: SerialPortHttpAgent) => void ): void { - if (usbHttpAgent != null) { - onComplete( - new Error('Tried to make a USB http agent when one already existed') - ) - } else { - usbHttpAgent = new SerialPortHttpAgent( - { - maxFreeSockets: 1, - maxSockets: 1, - maxTotalSockets: 1, - keepAlive: true, - keepAliveMsecs: Infinity, - path, - logger: usbLog, - timeout: 100000, - }, - (err, agent?) => { - if (err != null) { - usbHttpAgent = undefined - } - onComplete(err, agent) - } - ) - } + if (usbHttpAgent != null) { + onComplete( + new Error('Tried to make a USB http agent when one already existed') + ) + } else { + usbHttpAgent = new SerialPortHttpAgent( + { + maxFreeSockets: 1, + maxSockets: 1, + maxTotalSockets: 1, + keepAlive: true, + keepAliveMsecs: Infinity, + path, + logger: usbLog, + timeout: 100000, + }, + (err, agent?) => { + if (err != null) { + usbHttpAgent = undefined + } + onComplete(err, agent) + } + ) + } } export function destroyAndStopUsbHttpRequests(dispatch: Dispatch): void { - if (usbHttpAgent != null) { - usbHttpAgent.destroy() - } - usbHttpAgent = undefined - ipcMain.removeHandler('usb:request') - dispatch(usbRequestsStop()) - // handle any additional invocations of usb:request - ipcMain.handle('usb:request', () => - Promise.resolve({ - status: 400, - statusText: 'USB robot disconnected', - }) - ) + if (usbHttpAgent != null) { + usbHttpAgent.destroy() + } + usbHttpAgent = undefined + ipcMain.removeHandler('usb:request') + dispatch(usbRequestsStop()) + // handle any additional invocations of usb:request + ipcMain.handle('usb:request', () => + Promise.resolve({ + status: 400, + statusText: 'USB robot disconnected', + }) + ) } function isUsbDeviceOt3(device: UsbDevice): boolean { - return ( - device.productId === parseInt(DEFAULT_PRODUCT_ID, 16) && - device.vendorId === parseInt(DEFAULT_VENDOR_ID, 16) - ) + return ( + device.productId === parseInt(DEFAULT_PRODUCT_ID, 16) && + device.vendorId === parseInt(DEFAULT_VENDOR_ID, 16) + ) } function reconstructFormData(ipcSafeFormData: IPCSafeFormData): FormData { - const result = new FormData() - ipcSafeFormData.forEach(entry => { - entry.type === 'file' - ? result.append( - entry.name, - Buffer.from(entry.value), - entry.filename - ) - : result.append(entry.name, entry.value) - }) - return result + const result = new FormData() + ipcSafeFormData.forEach(entry => { + entry.type === 'file' + ? result.append(entry.name, Buffer.from(entry.value), entry.filename) + : result.append(entry.name, entry.value) + }) + return result } -const cloneError = (e: object): object => - Object.entries(axios.isAxiosError(e) ? e.toJSON() : e).reduce( - (acc, [k, v]) => { - try { - return (acc[k] = structuredClone(v)) - } catch (e) { - return acc - } - }, - {} - ) +const cloneError = (e: any): Record => + Object.entries(axios.isAxiosError(e) ? e.toJSON() : e).reduce( + (acc, [k, v]) => { + try { + acc[k] = structuredClone(v) + return acc + } catch (e) { + return acc + } + }, + {} as Record + ) async function usbListener( - _event: IpcMainInvokeEvent, - config: AxiosRequestConfig + _event: IpcMainInvokeEvent, + config: AxiosRequestConfig ): Promise { - // TODO(bh, 2023-05-03): remove mutation - let { data } = config - let formHeaders = {} - - // check for formDataProxy - if (data?.proxiedFormData != null) { - // reconstruct FormData - const formData = reconstructFormData( - data.proxiedFormData as IPCSafeFormData - ) - formHeaders = formData.getHeaders() - data = formData + // TODO(bh, 2023-05-03): remove mutation + let { data } = config + let formHeaders = {} + + // check for formDataProxy + if (data?.proxiedFormData != null) { + // reconstruct FormData + const formData = reconstructFormData( + data.proxiedFormData as IPCSafeFormData + ) + formHeaders = formData.getHeaders() + data = formData + } + + const usbHttpAgent = getSerialPortHttpAgent() + try { + usbLog.silly(`${config.method} ${config.url} timeout=${config.timeout}`) + const response = await axios.request({ + httpAgent: usbHttpAgent, + ...config, + data, + headers: { ...config.headers, ...formHeaders }, + }) + usbLog.silly(`${config.method} ${config.url} resolved ok`) + return { + error: null, + data: response.data, + status: response.status, + statusText: response.statusText, } - - const usbHttpAgent = getSerialPortHttpAgent() - try { - usbLog.silly(`${config.method} ${config.url} timeout=${config.timeout}`) - const response = await axios.request({ - httpAgent: usbHttpAgent, - ...config, - data, - headers: { ...config.headers, ...formHeaders }, - }) - usbLog.silly(`${config.method} ${config.url} resolved ok`) - return { - error: null, - data: response.data, - status: response.status, - statusText: response.statusText, - } - } catch (e) { - usbLog.info(`${config.method} ${config.url} failed: ${e}`) - return { - error: cloneError(e), - } + } catch (e: any) { + usbLog.info(`${config.method} ${config.url} failed: ${e}`) + return { + error: cloneError(e), } + } } function pollSerialPortAndCreateAgent(dispatch: Dispatch): void { - // usb poll already initialized - if (usbFetchInterval != null) { - return - } - usbFetchInterval = setInterval(() => { - // already connected to an Opentrons robot via USB - tryCreateAndStartUsbHttpRequests(dispatch) - }, 10000) + // usb poll already initialized + if (usbFetchInterval != null) { + return + } + usbFetchInterval = setInterval(() => { + // already connected to an Opentrons robot via USB + tryCreateAndStartUsbHttpRequests(dispatch) + }, 10000) } function tryCreateAndStartUsbHttpRequests(dispatch: Dispatch): void { - fetchSerialPortList() - .then((list: PortInfo[]) => { - const ot3UsbSerialPort = list.find( - port => - port.productId?.localeCompare(DEFAULT_PRODUCT_ID, 'en-US', { - sensitivity: 'base', - }) === 0 && - port.vendorId?.localeCompare(DEFAULT_VENDOR_ID, 'en-US', { - sensitivity: 'base', - }) === 0 - ) - - // retry if no Flex serial port found - usb-detection and serialport packages have race condition - if (ot3UsbSerialPort == null) { - usbLog.debug('No Flex serial port found.') - return - } - if (usbHttpAgent == null) { - createSerialPortHttpAgent( - ot3UsbSerialPort.path, - (err, agent?) => { - if (err != null) { - const message = err?.message ?? err - usbLog.error( - `Failed to create serial port: ${message}` - ) - } - if (agent != null) { - ipcMain.removeHandler('usb:request') - ipcMain.handle('usb:request', usbListener) - dispatch(usbRequestsStart()) - } - } - ) - } + fetchSerialPortList() + .then((list: PortInfo[]) => { + const ot3UsbSerialPort = list.find( + port => + port.productId?.localeCompare(DEFAULT_PRODUCT_ID, 'en-US', { + sensitivity: 'base', + }) === 0 && + port.vendorId?.localeCompare(DEFAULT_VENDOR_ID, 'en-US', { + sensitivity: 'base', + }) === 0 + ) + + // retry if no Flex serial port found - usb-detection and serialport packages have race condition + if (ot3UsbSerialPort == null) { + usbLog.debug('No Flex serial port found.') + return + } + if (usbHttpAgent == null) { + createSerialPortHttpAgent(ot3UsbSerialPort.path, (err, agent?) => { + if (err != null) { + const message = err?.message ?? err + usbLog.error(`Failed to create serial port: ${message}`) + } + if (agent != null) { + ipcMain.removeHandler('usb:request') + ipcMain.handle('usb:request', usbListener) + dispatch(usbRequestsStart()) + } }) - .catch(e => - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - usbLog.debug(`fetchSerialPortList error ${e?.message ?? 'unknown'}`) - ) + } + }) + .catch(e => + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + usbLog.debug(`fetchSerialPortList error ${e?.message ?? 'unknown'}`) + ) } export function registerUsb(dispatch: Dispatch): (action: Action) => unknown { - return function handleIncomingAction(action: Action): void { - switch (action.type) { - case SYSTEM_INFO_INITIALIZED: - if (action.payload.usbDevices.find(isUsbDeviceOt3) != null) { - tryCreateAndStartUsbHttpRequests(dispatch) - } - pollSerialPortAndCreateAgent(dispatch) - break - case USB_DEVICE_ADDED: - if (isUsbDeviceOt3(action.payload.usbDevice)) { - tryCreateAndStartUsbHttpRequests(dispatch) - } - break - case USB_DEVICE_REMOVED: - if (isUsbDeviceOt3(action.payload.usbDevice)) { - destroyAndStopUsbHttpRequests(dispatch) - } - break + return function handleIncomingAction(action: Action): void { + switch (action.type) { + case SYSTEM_INFO_INITIALIZED: + if (action.payload.usbDevices.find(isUsbDeviceOt3) != null) { + tryCreateAndStartUsbHttpRequests(dispatch) + } + pollSerialPortAndCreateAgent(dispatch) + break + case USB_DEVICE_ADDED: + if (isUsbDeviceOt3(action.payload.usbDevice)) { + tryCreateAndStartUsbHttpRequests(dispatch) + } + break + case USB_DEVICE_REMOVED: + if (isUsbDeviceOt3(action.payload.usbDevice)) { + destroyAndStopUsbHttpRequests(dispatch) } + break } + } } From 810a7047500dd497fc35f0b8884f9164fa531a2c Mon Sep 17 00:00:00 2001 From: Seth Foster Date: Tue, 20 Aug 2024 15:59:59 -0400 Subject: [PATCH 9/9] delint --- app-shell/src/usb.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/app-shell/src/usb.ts b/app-shell/src/usb.ts index 4f8121e5160..cf551653674 100644 --- a/app-shell/src/usb.ts +++ b/app-shell/src/usb.ts @@ -95,17 +95,17 @@ function reconstructFormData(ipcSafeFormData: IPCSafeFormData): FormData { } const cloneError = (e: any): Record => - Object.entries(axios.isAxiosError(e) ? e.toJSON() : e).reduce( - (acc, [k, v]) => { - try { - acc[k] = structuredClone(v) - return acc - } catch (e) { - return acc - } - }, - {} as Record - ) + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + Object.entries(axios.isAxiosError(e) ? e.toJSON() : e).reduce< + Record + >((acc, [k, v]) => { + try { + acc[k] = structuredClone(v) + return acc + } catch (e) { + return acc + } + }, {}) async function usbListener( _event: IpcMainInvokeEvent,