diff --git a/desktop/app/src/reducers/connections.tsx b/desktop/app/src/reducers/connections.tsx index 23f747a03..fe97666b2 100644 --- a/desktop/app/src/reducers/connections.tsx +++ b/desktop/app/src/reducers/connections.tsx @@ -171,7 +171,7 @@ const INITAL_STATE: State = { staticView: WelcomeScreen, }; -const reducer = (state: State = INITAL_STATE, action: Actions): State => { +export default (state: State = INITAL_STATE, action: Actions): State => { switch (action.type) { case 'SET_STATIC_VIEW': { const {payload} = action; @@ -444,33 +444,6 @@ const reducer = (state: State = INITAL_STATE, action: Actions): State => { } }; -export default (state: State = INITAL_STATE, action: Actions): State => { - const nextState = reducer(state, action); - - if (nextState.selectedDevice) { - const {selectedDevice} = nextState; - const deviceNotSupportedErrorMessage = 'iOS Devices are not yet supported'; - const error = - selectedDevice.os === 'iOS' && - selectedDevice.deviceType === 'physical' && - !iosUtil.isAvailable() - ? deviceNotSupportedErrorMessage - : null; - - if (error) { - const deviceNotSupportedError = nextState.errors.find( - (error) => error.message === deviceNotSupportedErrorMessage, - ); - if (deviceNotSupportedError) { - deviceNotSupportedError.message = error; - } else { - nextState.errors.push({message: error}); - } - } - } - return nextState; -}; - function mergeError( errors: FlipperError[], newError: FlipperError, diff --git a/desktop/app/src/utils/CertificateProvider.tsx b/desktop/app/src/utils/CertificateProvider.tsx index afc202712..7f594503d 100644 --- a/desktop/app/src/utils/CertificateProvider.tsx +++ b/desktop/app/src/utils/CertificateProvider.tsx @@ -78,6 +78,7 @@ export default class CertificateProvider { logger: Logger; adb: Promise; certificateSetup: Promise; + store: Store; server: Server; constructor(server: Server, logger: Logger, store: Store) { @@ -87,6 +88,7 @@ export default class CertificateProvider { this.ensureServerCertExists(), 'ensureServerCertExists', ); + this.store = store; this.server = server; } @@ -261,7 +263,13 @@ export default class CertificateProvider { return tmpDir({unsafeCleanup: true}).then((dir) => { const filePath = path.resolve(dir, filename); promisify(fs.writeFile)(filePath, contents).then(() => - iosUtil.push(udid, filePath, bundleId, destination), + iosUtil.push( + udid, + filePath, + bundleId, + destination, + this.store.getState().settingsState.idbPath, + ), ); }); } @@ -386,7 +394,13 @@ export default class CertificateProvider { return tmpDir({unsafeCleanup: true}) .then((dir) => { return iosUtil - .pull(deviceId, originalFile, bundleId, path.join(dir, csrFileName)) + .pull( + deviceId, + originalFile, + bundleId, + path.join(dir, csrFileName), + this.store.getState().settingsState.idbPath, + ) .then(() => dir); }) .then((dir) => { diff --git a/desktop/app/src/utils/iOSContainerUtility.tsx b/desktop/app/src/utils/iOSContainerUtility.tsx index 20b52bb9a..0ecd983b8 100644 --- a/desktop/app/src/utils/iOSContainerUtility.tsx +++ b/desktop/app/src/utils/iOSContainerUtility.tsx @@ -14,9 +14,8 @@ import {notNull} from './typeUtils'; const unsafeExec = promisify(child_process.exec); import {killOrphanedInstrumentsProcesses} from './processCleanup'; import {reportPlatformFailures} from './metrics'; -import config from '../fb-stubs/config'; +import {promises, constants} from 'fs'; -const idbPath = '/usr/local/bin/idb'; // Use debug to get helpful logs when idb fails const idbLogLevel = 'DEBUG'; const operationPrefix = 'iosContainerUtility'; @@ -29,8 +28,14 @@ export type DeviceTarget = { name: string; }; -function isAvailable(): boolean { - return config.isFBBuild; +function isAvailable(idbPath: string): Promise { + if (!idbPath) { + return Promise.resolve(false); + } + return promises + .access(idbPath, constants.X_OK) + .then((_) => true) + .catch((_) => false); } function safeExec(command: string): Promise<{stdout: string; stderr: string}> { @@ -61,12 +66,14 @@ async function targets(): Promise> { ); } -function push( +async function push( udid: string, src: string, bundleId: string, dst: string, + idbPath: string, ): Promise { + await checkIdbIsInstalled(idbPath); return wrapWithErrorMessage( reportPlatformFailures( safeExec( @@ -75,18 +82,20 @@ function push( .then(() => { return; }) - .catch(handleMissingIdb), + .catch((e) => handleMissingIdb(e, idbPath)), `${operationPrefix}:push`, ), ); } -function pull( +async function pull( udid: string, src: string, bundleId: string, dst: string, + idbPath: string, ): Promise { + await checkIdbIsInstalled(idbPath); return wrapWithErrorMessage( reportPlatformFailures( safeExec( @@ -95,15 +104,24 @@ function pull( .then(() => { return; }) - .catch(handleMissingIdb), + .catch((e) => handleMissingIdb(e, idbPath)), `${operationPrefix}:pull`, ), ); } -// The idb binary is a shim that downloads the proper one on first run. It requires sudo to do so. +async function checkIdbIsInstalled(idbPath: string): Promise { + const isInstalled = await isAvailable(idbPath); + if (!isInstalled) { + throw new Error( + `idb is required to use iOS devices. Install it with instructions from https://github.com/facebook/idb and set the installation path in Flipper settings.`, + ); + } +} + +// The fb-internal idb binary is a shim that downloads the proper one on first run. It requires sudo to do so. // If we detect this, Tell the user how to fix it. -function handleMissingIdb(e: Error): void { +function handleMissingIdb(e: Error, idbPath: string): void { if ( e.message && e.message.includes('sudo: no tty present and no askpass program specified')