diff --git a/app-shell/src/preload.ts b/app-shell/src/preload.ts index cf1f4ef7bef..16ef6b7aa30 100644 --- a/app-shell/src/preload.ts +++ b/app-shell/src/preload.ts @@ -1,7 +1,15 @@ // preload script for renderer process // defines subset of Electron API that renderer process is allowed to access // for security reasons -import { ipcRenderer } from 'electron' +import { ipcRenderer, webUtils } from 'electron' + +// The renderer process is not permitted the file path for any type "file" input +// post Electron v32. The correct way of doing this involves the context bridge, +// see comments in Electron settings. +// See https://www.electronjs.org/docs/latest/breaking-changes#removed-filepath +const getFilePathFrom = (file: File): Promise => { + return Promise.resolve(webUtils.getPathForFile(file)) +} // @ts-expect-error can't get TS to recognize global.d.ts -global.APP_SHELL_REMOTE = { ipcRenderer } +global.APP_SHELL_REMOTE = { ipcRenderer, getFilePathFrom } diff --git a/app/src/organisms/Desktop/Labware/AddCustomLabwareSlideout/__tests__/AddCustomLabwareSlideout.test.tsx b/app/src/organisms/Desktop/Labware/AddCustomLabwareSlideout/__tests__/AddCustomLabwareSlideout.test.tsx index 977f44f2cce..1a4fef0be5c 100644 --- a/app/src/organisms/Desktop/Labware/AddCustomLabwareSlideout/__tests__/AddCustomLabwareSlideout.test.tsx +++ b/app/src/organisms/Desktop/Labware/AddCustomLabwareSlideout/__tests__/AddCustomLabwareSlideout.test.tsx @@ -13,6 +13,11 @@ import { AddCustomLabwareSlideout } from '..' vi.mock('/app/redux/custom-labware') vi.mock('/app/local-resources/labware') vi.mock('/app/redux/analytics') +vi.mock('/app/redux/shell/remote', () => ({ + remote: { + getFilePathFrom: vi.fn(), + }, +})) let mockTrackEvent: any diff --git a/app/src/organisms/Desktop/Labware/AddCustomLabwareSlideout/index.tsx b/app/src/organisms/Desktop/Labware/AddCustomLabwareSlideout/index.tsx index 1ded9827380..448a97c4b79 100644 --- a/app/src/organisms/Desktop/Labware/AddCustomLabwareSlideout/index.tsx +++ b/app/src/organisms/Desktop/Labware/AddCustomLabwareSlideout/index.tsx @@ -19,6 +19,8 @@ import { ANALYTICS_ADD_CUSTOM_LABWARE, } from '/app/redux/analytics' import { UploadInput } from '/app/molecules/UploadInput' +import { remote } from '/app/redux/shell/remote' + import type { Dispatch } from '/app/redux/types' export interface AddCustomLabwareSlideoutProps { @@ -46,7 +48,9 @@ export function AddCustomLabwareSlideout( > { - dispatch(addCustomLabwareFile(file.path)) + void remote.getFilePathFrom(file).then(filePath => { + dispatch(addCustomLabwareFile(filePath)) + }) }} onClick={() => { dispatch(addCustomLabware()) diff --git a/app/src/organisms/Desktop/ProtocolsLanding/ProtocolUploadInput.tsx b/app/src/organisms/Desktop/ProtocolsLanding/ProtocolUploadInput.tsx index 35491c61d2d..17313bd567f 100644 --- a/app/src/organisms/Desktop/ProtocolsLanding/ProtocolUploadInput.tsx +++ b/app/src/organisms/Desktop/ProtocolsLanding/ProtocolUploadInput.tsx @@ -18,6 +18,7 @@ import { } from '/app/redux/analytics' import { useLogger } from '/app/logger' import { useToaster } from '/app/organisms/ToasterOven' +import { remote } from '/app/redux/shell/remote' import type { Dispatch } from '/app/redux/types' @@ -38,21 +39,23 @@ export function ProtocolUploadInput( const trackEvent = useTrackEvent() const { makeToast } = useToaster() - const handleUpload = (file: File): void => { - if (file.path === null) { - logger.warn('Failed to upload file, path not found') - } - if (isValidProtocolFileName(file.name)) { - dispatch(addProtocol(file.path)) - } else { - makeToast(t('incompatible_file_type') as string, ERROR_TOAST, { - closeButton: true, + const handleUpload = (file: File): Promise => { + return remote.getFilePathFrom(file).then(filePath => { + if (filePath == null) { + logger.warn('Failed to upload file, path not found') + } + if (isValidProtocolFileName(file.name)) { + dispatch(addProtocol(filePath)) + } else { + makeToast(t('incompatible_file_type') as string, ERROR_TOAST, { + closeButton: true, + }) + } + props.onUpload?.() + trackEvent({ + name: ANALYTICS_IMPORT_PROTOCOL_TO_APP, + properties: { protocolFileName: file.name }, }) - } - props.onUpload?.() - trackEvent({ - name: ANALYTICS_IMPORT_PROTOCOL_TO_APP, - properties: { protocolFileName: file.name }, }) } @@ -64,7 +67,7 @@ export function ProtocolUploadInput( > { - handleUpload(file) + void handleUpload(file) }} uploadText={t('valid_file_types')} dragAndDropText={ diff --git a/app/src/organisms/Desktop/ProtocolsLanding/__tests__/UploadInput.test.tsx b/app/src/organisms/Desktop/ProtocolsLanding/__tests__/UploadInput.test.tsx index e3b8a8f4cdb..f03aaf861e7 100644 --- a/app/src/organisms/Desktop/ProtocolsLanding/__tests__/UploadInput.test.tsx +++ b/app/src/organisms/Desktop/ProtocolsLanding/__tests__/UploadInput.test.tsx @@ -8,10 +8,16 @@ import { ANALYTICS_IMPORT_PROTOCOL_TO_APP, } from '/app/redux/analytics' import { ProtocolUploadInput } from '../ProtocolUploadInput' +import { remote } from '/app/redux/shell/remote' import type { Mock } from 'vitest' vi.mock('/app/redux/analytics') +vi.mock('/app/redux/shell/remote', () => ({ + remote: { + getFilePathFrom: vi.fn(), + }, +})) describe('ProtocolUploadInput', () => { let onUpload: Mock @@ -31,6 +37,7 @@ describe('ProtocolUploadInput', () => { onUpload = vi.fn() trackEvent = vi.fn() vi.mocked(useTrackEvent).mockReturnValue(trackEvent) + vi.mocked(remote.getFilePathFrom).mockResolvedValue('mockFileName') }) afterEach(() => { vi.resetAllMocks() @@ -56,16 +63,24 @@ describe('ProtocolUploadInput', () => { fireEvent.click(button) expect(input.click).toHaveBeenCalled() }) - it('calls onUpload callback on choose file and trigger analytics event', () => { + it('calls onUpload callback on choose file and trigger analytics event', async () => { render() const input = screen.getByTestId('file_input') + + const mockFile = new File(['mockContent'], 'mockFileName', { + type: 'text/plain', + }) + fireEvent.change(input, { - target: { files: [{ path: 'dummyFile', name: 'dummyName' }] }, + target: { files: [mockFile] }, }) - expect(onUpload).toHaveBeenCalled() - expect(trackEvent).toHaveBeenCalledWith({ - name: ANALYTICS_IMPORT_PROTOCOL_TO_APP, - properties: { protocolFileName: 'dummyName' }, + + await vi.waitFor(() => { + expect(onUpload).toHaveBeenCalled() + expect(trackEvent).toHaveBeenCalledWith({ + name: ANALYTICS_IMPORT_PROTOCOL_TO_APP, + properties: { protocolFileName: 'mockFileName' }, + }) }) }) }) diff --git a/app/src/redux/shell/types.ts b/app/src/redux/shell/types.ts index a29c263a0cb..ca20ddab53e 100644 --- a/app/src/redux/shell/types.ts +++ b/app/src/redux/shell/types.ts @@ -11,6 +11,8 @@ export interface Remote { on: (channel: string, listener: IpcListener) => void off: (channel: string, listener: IpcListener) => void } + /* The renderer process isn't allowed the file path for security reasons. */ + getFilePathFrom: (file: File) => Promise } export type IpcListener = (