Decouple iOS devices from Store / core
Summary: Decouple iOS device detection from Redux Reviewed By: timur-valiev Differential Revision: D30309258 fbshipit-source-id: 74b4e3dd2e6b83fcefc75909794c39bfc8c987cf
This commit is contained in:
committed by
Facebook GitHub Bot
parent
3736cbc480
commit
ea58f2b050
@@ -357,10 +357,11 @@ class SettingsSheet extends Component<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default connect<StateFromProps, DispatchFromProps, OwnProps, Store>(
|
export default connect<StateFromProps, DispatchFromProps, OwnProps, Store>(
|
||||||
({settingsState, launcherSettingsState, application}) => ({
|
({settingsState, launcherSettingsState, connections}) => ({
|
||||||
settings: settingsState,
|
settings: settingsState,
|
||||||
launcherSettings: launcherSettingsState,
|
launcherSettings: launcherSettingsState,
|
||||||
isXcodeDetected: application.xcodeCommandLineToolsDetected,
|
isXcodeDetected:
|
||||||
|
connections.flipperServer?.ios.xcodeCommandLineToolsDetected ?? false,
|
||||||
}),
|
}),
|
||||||
{updateSettings, updateLauncherSettings},
|
{updateSettings, updateLauncherSettings},
|
||||||
)(withTrackingScope(SettingsSheet));
|
)(withTrackingScope(SettingsSheet));
|
||||||
|
|||||||
@@ -16,11 +16,15 @@ import Client from '../Client';
|
|||||||
import {notification} from 'antd';
|
import {notification} from 'antd';
|
||||||
|
|
||||||
export default async (store: Store, logger: Logger) => {
|
export default async (store: Store, logger: Logger) => {
|
||||||
const {enableAndroid, androidHome} = store.getState().settingsState;
|
const {enableAndroid, androidHome, idbPath, enableIOS, enablePhysicalIOS} =
|
||||||
|
store.getState().settingsState;
|
||||||
const server = new FlipperServer(
|
const server = new FlipperServer(
|
||||||
{
|
{
|
||||||
enableAndroid,
|
enableAndroid,
|
||||||
androidHome,
|
androidHome,
|
||||||
|
idbPath,
|
||||||
|
enableIOS,
|
||||||
|
enablePhysicalIOS,
|
||||||
serverPorts: store.getState().application.serverPorts,
|
serverPorts: store.getState().application.serverPorts,
|
||||||
},
|
},
|
||||||
store,
|
store,
|
||||||
|
|||||||
@@ -86,7 +86,6 @@ export type State = {
|
|||||||
serverPorts: ServerPorts;
|
serverPorts: ServerPorts;
|
||||||
launcherMsg: LauncherMsg;
|
launcherMsg: LauncherMsg;
|
||||||
statusMessages: Array<string>;
|
statusMessages: Array<string>;
|
||||||
xcodeCommandLineToolsDetected: boolean;
|
|
||||||
pastedToken?: string;
|
pastedToken?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -149,12 +148,6 @@ export type Action =
|
|||||||
type: 'REMOVE_STATUS_MSG';
|
type: 'REMOVE_STATUS_MSG';
|
||||||
payload: {msg: string; sender: string};
|
payload: {msg: string; sender: string};
|
||||||
}
|
}
|
||||||
| {
|
|
||||||
type: 'SET_XCODE_DETECTED';
|
|
||||||
payload: {
|
|
||||||
isDetected: boolean;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
| {
|
| {
|
||||||
type: 'SET_PASTED_TOKEN';
|
type: 'SET_PASTED_TOKEN';
|
||||||
payload?: string;
|
payload?: string;
|
||||||
@@ -177,7 +170,6 @@ export const initialState: () => State = () => ({
|
|||||||
message: '',
|
message: '',
|
||||||
},
|
},
|
||||||
statusMessages: [],
|
statusMessages: [],
|
||||||
xcodeCommandLineToolsDetected: false,
|
|
||||||
trackingTimeline: [],
|
trackingTimeline: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -288,8 +280,6 @@ export default function reducer(
|
|||||||
return {...state, statusMessages};
|
return {...state, statusMessages};
|
||||||
}
|
}
|
||||||
return state;
|
return state;
|
||||||
} else if (action.type === 'SET_XCODE_DETECTED') {
|
|
||||||
return {...state, xcodeCommandLineToolsDetected: action.payload.isDetected};
|
|
||||||
} else if (action.type === 'SET_PASTED_TOKEN') {
|
} else if (action.type === 'SET_PASTED_TOKEN') {
|
||||||
return produce(state, (draft) => {
|
return produce(state, (draft) => {
|
||||||
draft.pastedToken = action.payload;
|
draft.pastedToken = action.payload;
|
||||||
@@ -368,11 +358,6 @@ export const removeStatusMessage = (payload: StatusMessageType): Action => ({
|
|||||||
payload,
|
payload,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const setXcodeDetected = (isDetected: boolean): Action => ({
|
|
||||||
type: 'SET_XCODE_DETECTED',
|
|
||||||
payload: {isDetected},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const setPastedToken = (pastedToken?: string): Action => ({
|
export const setPastedToken = (pastedToken?: string): Action => ({
|
||||||
type: 'SET_PASTED_TOKEN',
|
type: 'SET_PASTED_TOKEN',
|
||||||
payload: pastedToken,
|
payload: pastedToken,
|
||||||
|
|||||||
@@ -21,8 +21,7 @@ import {launchEmulator} from '../../server/devices/android/AndroidDevice';
|
|||||||
import {Layout, renderReactRoot, withTrackingScope} from 'flipper-plugin';
|
import {Layout, renderReactRoot, withTrackingScope} from 'flipper-plugin';
|
||||||
import {Provider} from 'react-redux';
|
import {Provider} from 'react-redux';
|
||||||
import {
|
import {
|
||||||
launchSimulator,
|
launchSimulator, // TODO: move to iOSDeviceManager
|
||||||
getSimulators,
|
|
||||||
IOSDeviceParams,
|
IOSDeviceParams,
|
||||||
} from '../../server/devices/ios/iOSDeviceManager';
|
} from '../../server/devices/ios/iOSDeviceManager';
|
||||||
import GK from '../../fb-stubs/GK';
|
import GK from '../../fb-stubs/GK';
|
||||||
@@ -39,19 +38,16 @@ export function showEmulatorLauncher(store: Store) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function LaunchEmulatorContainer({onClose}: {onClose: () => void}) {
|
function LaunchEmulatorContainer({onClose}: {onClose: () => void}) {
|
||||||
const store = useStore();
|
|
||||||
const flipperServer = useStore((state) => state.connections.flipperServer);
|
const flipperServer = useStore((state) => state.connections.flipperServer);
|
||||||
return (
|
return (
|
||||||
<LaunchEmulatorDialog
|
<LaunchEmulatorDialog
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
getSimulators={getSimulators.bind(store)}
|
getSimulators={() => flipperServer!.ios.getSimulators(false)}
|
||||||
getEmulators={() => flipperServer!.android.getAndroidEmulators()}
|
getEmulators={() => flipperServer!.android.getAndroidEmulators()}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetSimulators = typeof getSimulators;
|
|
||||||
|
|
||||||
export const LaunchEmulatorDialog = withTrackingScope(
|
export const LaunchEmulatorDialog = withTrackingScope(
|
||||||
function LaunchEmulatorDialog({
|
function LaunchEmulatorDialog({
|
||||||
onClose,
|
onClose,
|
||||||
@@ -59,7 +55,7 @@ export const LaunchEmulatorDialog = withTrackingScope(
|
|||||||
getEmulators,
|
getEmulators,
|
||||||
}: {
|
}: {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
getSimulators: GetSimulators;
|
getSimulators: () => Promise<IOSDeviceParams[]>;
|
||||||
getEmulators: () => Promise<string[]>;
|
getEmulators: () => Promise<string[]>;
|
||||||
}) {
|
}) {
|
||||||
const iosEnabled = useStore((state) => state.settingsState.enableIOS);
|
const iosEnabled = useStore((state) => state.settingsState.enableIOS);
|
||||||
@@ -74,15 +70,19 @@ export const LaunchEmulatorDialog = withTrackingScope(
|
|||||||
if (!iosEnabled) {
|
if (!iosEnabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
getSimulators(store, false).then((emulators) => {
|
getSimulators()
|
||||||
setIosEmulators(
|
.then((emulators) => {
|
||||||
emulators.filter(
|
setIosEmulators(
|
||||||
(device) =>
|
emulators.filter(
|
||||||
device.state === 'Shutdown' &&
|
(device) =>
|
||||||
device.deviceTypeIdentifier?.match(/iPhone|iPad/i),
|
device.state === 'Shutdown' &&
|
||||||
),
|
device.deviceTypeIdentifier?.match(/iPhone|iPad/i),
|
||||||
);
|
),
|
||||||
});
|
);
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.warn('Failed to find simulators', e);
|
||||||
|
});
|
||||||
}, [iosEnabled, getSimulators, store]);
|
}, [iosEnabled, getSimulators, store]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -94,7 +94,7 @@ export const LaunchEmulatorDialog = withTrackingScope(
|
|||||||
setAndroidEmulators(emulators);
|
setAndroidEmulators(emulators);
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.warn('Failed to find emulatiors', e);
|
console.warn('Failed to find emulators', e);
|
||||||
});
|
});
|
||||||
}, [androidEnabled, getEmulators]);
|
}, [androidEnabled, getEmulators]);
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import {
|
|||||||
setActiveSheet,
|
setActiveSheet,
|
||||||
} from '../reducers/application';
|
} from '../reducers/application';
|
||||||
import {AndroidDeviceManager} from './devices/android/androidDeviceManager';
|
import {AndroidDeviceManager} from './devices/android/androidDeviceManager';
|
||||||
import iOSDevice from './devices/ios/iOSDeviceManager';
|
import {IOSDeviceManager} from './devices/ios/iOSDeviceManager';
|
||||||
import metroDevice from './devices/metro/metroDeviceManager';
|
import metroDevice from './devices/metro/metroDeviceManager';
|
||||||
import desktopDevice from './devices/desktop/desktopDeviceManager';
|
import desktopDevice from './devices/desktop/desktopDeviceManager';
|
||||||
import BaseDevice from './devices/BaseDevice';
|
import BaseDevice from './devices/BaseDevice';
|
||||||
@@ -45,6 +45,9 @@ type FlipperServerEvents = {
|
|||||||
export interface FlipperServerConfig {
|
export interface FlipperServerConfig {
|
||||||
enableAndroid: boolean;
|
enableAndroid: boolean;
|
||||||
androidHome: string;
|
androidHome: string;
|
||||||
|
enableIOS: boolean;
|
||||||
|
idbPath: string;
|
||||||
|
enablePhysicalIOS: boolean;
|
||||||
serverPorts: ServerPorts;
|
serverPorts: ServerPorts;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,6 +57,9 @@ type ServerState = 'pending' | 'starting' | 'started' | 'error' | 'closed';
|
|||||||
const defaultConfig: FlipperServerConfig = {
|
const defaultConfig: FlipperServerConfig = {
|
||||||
androidHome: '',
|
androidHome: '',
|
||||||
enableAndroid: false,
|
enableAndroid: false,
|
||||||
|
enableIOS: false,
|
||||||
|
enablePhysicalIOS: false,
|
||||||
|
idbPath: '',
|
||||||
serverPorts: {
|
serverPorts: {
|
||||||
insecure: -1,
|
insecure: -1,
|
||||||
secure: -1,
|
secure: -1,
|
||||||
@@ -78,6 +84,7 @@ export class FlipperServer {
|
|||||||
private readonly devices = new Map<string, BaseDevice>();
|
private readonly devices = new Map<string, BaseDevice>();
|
||||||
state: ServerState = 'pending';
|
state: ServerState = 'pending';
|
||||||
android: AndroidDeviceManager;
|
android: AndroidDeviceManager;
|
||||||
|
ios: IOSDeviceManager;
|
||||||
|
|
||||||
// TODO: remove store argument
|
// TODO: remove store argument
|
||||||
constructor(
|
constructor(
|
||||||
@@ -89,6 +96,7 @@ export class FlipperServer {
|
|||||||
this.config = {...defaultConfig, ...config};
|
this.config = {...defaultConfig, ...config};
|
||||||
const server = (this.server = new ServerController(this));
|
const server = (this.server = new ServerController(this));
|
||||||
this.android = new AndroidDeviceManager(this);
|
this.android = new AndroidDeviceManager(this);
|
||||||
|
this.ios = new IOSDeviceManager(this);
|
||||||
|
|
||||||
server.addListener('new-client', (client: Client) => {
|
server.addListener('new-client', (client: Client) => {
|
||||||
this.emit('client-connected', client);
|
this.emit('client-connected', client);
|
||||||
@@ -200,7 +208,7 @@ export class FlipperServer {
|
|||||||
async startDeviceListeners() {
|
async startDeviceListeners() {
|
||||||
this.disposers.push(
|
this.disposers.push(
|
||||||
await this.android.watchAndroidDevices(),
|
await this.android.watchAndroidDevices(),
|
||||||
iOSDevice(this.store, this.logger),
|
await this.ios.watchIOSDevices(),
|
||||||
metroDevice(this),
|
metroDevice(this),
|
||||||
desktopDevice(this),
|
desktopDevice(this),
|
||||||
);
|
);
|
||||||
@@ -271,6 +279,10 @@ export class FlipperServer {
|
|||||||
return Array.from(this.devices.keys());
|
return Array.from(this.devices.keys());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getDevices(): BaseDevice[] {
|
||||||
|
return Array.from(this.devices.values());
|
||||||
|
}
|
||||||
|
|
||||||
public async close() {
|
public async close() {
|
||||||
this.server.close();
|
this.server.close();
|
||||||
for (const device of this.devices.values()) {
|
for (const device of this.devices.values()) {
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
import AndroidDevice from './AndroidDevice';
|
import AndroidDevice from './AndroidDevice';
|
||||||
import KaiOSDevice from './KaiOSDevice';
|
import KaiOSDevice from './KaiOSDevice';
|
||||||
import child_process from 'child_process';
|
import child_process from 'child_process';
|
||||||
import BaseDevice from '../BaseDevice';
|
|
||||||
import {getAdbClient} from './adbClient';
|
import {getAdbClient} from './adbClient';
|
||||||
import which from 'which';
|
import which from 'which';
|
||||||
import {promisify} from 'util';
|
import {promisify} from 'util';
|
||||||
@@ -22,11 +21,10 @@ import {notNull} from '../../utils/typeUtils';
|
|||||||
export class AndroidDeviceManager {
|
export class AndroidDeviceManager {
|
||||||
// cache emulator path
|
// cache emulator path
|
||||||
private emulatorPath: string | undefined;
|
private emulatorPath: string | undefined;
|
||||||
private devices: Map<string, AndroidDevice> = new Map();
|
|
||||||
|
|
||||||
constructor(public flipperServer: FlipperServer) {}
|
constructor(public flipperServer: FlipperServer) {}
|
||||||
|
|
||||||
createDevice(
|
private createDevice(
|
||||||
adbClient: ADBClient,
|
adbClient: ADBClient,
|
||||||
device: any,
|
device: any,
|
||||||
): Promise<AndroidDevice | undefined> {
|
): Promise<AndroidDevice | undefined> {
|
||||||
@@ -106,15 +104,6 @@ export class AndroidDeviceManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getActiveAndroidDevices(): Promise<Array<BaseDevice>> {
|
|
||||||
const client = await getAdbClient(this.flipperServer.config);
|
|
||||||
const androidDevices = await client.listDevices();
|
|
||||||
const devices = await Promise.all(
|
|
||||||
androidDevices.map((device) => this.createDevice(client, device)),
|
|
||||||
);
|
|
||||||
return devices.filter(Boolean) as any;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getEmulatorPath(): Promise<string> {
|
async getEmulatorPath(): Promise<string> {
|
||||||
if (this.emulatorPath) {
|
if (this.emulatorPath) {
|
||||||
return this.emulatorPath;
|
return this.emulatorPath;
|
||||||
@@ -154,7 +143,9 @@ export class AndroidDeviceManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getRunningEmulatorName(id: string): Promise<string | null | undefined> {
|
private async getRunningEmulatorName(
|
||||||
|
id: string,
|
||||||
|
): Promise<string | null | undefined> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const port = id.replace('emulator-', '');
|
const port = id.replace('emulator-', '');
|
||||||
// The GNU version of netcat doesn't terminate after 1s when
|
// The GNU version of netcat doesn't terminate after 1s when
|
||||||
@@ -184,8 +175,13 @@ export class AndroidDeviceManager {
|
|||||||
.then((tracker) => {
|
.then((tracker) => {
|
||||||
tracker.on('error', (err) => {
|
tracker.on('error', (err) => {
|
||||||
if (err.message === 'Connection closed') {
|
if (err.message === 'Connection closed') {
|
||||||
this.unregisterDevices(Array.from(this.devices.keys()));
|
|
||||||
console.warn('adb server was shutdown');
|
console.warn('adb server was shutdown');
|
||||||
|
this.flipperServer
|
||||||
|
.getDevices()
|
||||||
|
.filter((d) => d instanceof AndroidDevice)
|
||||||
|
.forEach((d) => {
|
||||||
|
this.flipperServer.unregisterDevice(d.serial);
|
||||||
|
});
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.watchAndroidDevices();
|
this.watchAndroidDevices();
|
||||||
}, 500);
|
}, 500);
|
||||||
@@ -202,14 +198,14 @@ export class AndroidDeviceManager {
|
|||||||
|
|
||||||
tracker.on('change', async (device) => {
|
tracker.on('change', async (device) => {
|
||||||
if (device.type === 'offline') {
|
if (device.type === 'offline') {
|
||||||
this.unregisterDevices([device.id]);
|
this.flipperServer.unregisterDevice(device.id);
|
||||||
} else {
|
} else {
|
||||||
this.registerDevice(client, device);
|
this.registerDevice(client, device);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
tracker.on('remove', (device) => {
|
tracker.on('remove', (device) => {
|
||||||
this.unregisterDevices([device.id]);
|
this.flipperServer.unregisterDevice(device.id);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((err: {code: string}) => {
|
.catch((err: {code: string}) => {
|
||||||
@@ -224,7 +220,7 @@ export class AndroidDeviceManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async registerDevice(adbClient: ADBClient, deviceData: any) {
|
private async registerDevice(adbClient: ADBClient, deviceData: any) {
|
||||||
const androidDevice = await this.createDevice(adbClient, deviceData);
|
const androidDevice = await this.createDevice(adbClient, deviceData);
|
||||||
if (!androidDevice) {
|
if (!androidDevice) {
|
||||||
return;
|
return;
|
||||||
@@ -232,10 +228,4 @@ export class AndroidDeviceManager {
|
|||||||
|
|
||||||
this.flipperServer.registerDevice(androidDevice);
|
this.flipperServer.registerDevice(androidDevice);
|
||||||
}
|
}
|
||||||
|
|
||||||
unregisterDevices(serials: Array<string>) {
|
|
||||||
serials.forEach((serial) => {
|
|
||||||
this.flipperServer.unregisterDevice(serial);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,14 +7,12 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {parseXcodeFromCoreSimPath} from '../iOSDeviceManager';
|
||||||
parseXcodeFromCoreSimPath,
|
|
||||||
getAllPromisesForQueryingDevices,
|
|
||||||
} from '../iOSDeviceManager';
|
|
||||||
import configureStore from 'redux-mock-store';
|
import configureStore from 'redux-mock-store';
|
||||||
import {State, createRootReducer} from '../../../../reducers/index';
|
import {State, createRootReducer} from '../../../../reducers/index';
|
||||||
import {getInstance} from '../../../../fb-stubs/Logger';
|
import {getInstance} from '../../../../fb-stubs/Logger';
|
||||||
import {IOSBridge} from '../IOSBridge';
|
import {IOSBridge} from '../IOSBridge';
|
||||||
|
import {FlipperServer} from '../../../FlipperServer';
|
||||||
|
|
||||||
const mockStore = configureStore<State, {}>([])(
|
const mockStore = configureStore<State, {}>([])(
|
||||||
createRootReducer()(undefined, {type: 'INIT'}),
|
createRootReducer()(undefined, {type: 'INIT'}),
|
||||||
@@ -62,21 +60,15 @@ test('test parseXcodeFromCoreSimPath from standard locations', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('test getAllPromisesForQueryingDevices when xcode detected', () => {
|
test('test getAllPromisesForQueryingDevices when xcode detected', () => {
|
||||||
const promises = getAllPromisesForQueryingDevices(
|
const flipperServer = new FlipperServer({}, mockStore, getInstance());
|
||||||
mockStore,
|
flipperServer.ios.iosBridge = {} as IOSBridge;
|
||||||
logger,
|
const promises = flipperServer.ios.getAllPromisesForQueryingDevices(true);
|
||||||
{} as IOSBridge,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
expect(promises.length).toEqual(3);
|
expect(promises.length).toEqual(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('test getAllPromisesForQueryingDevices when xcode is not detected', () => {
|
test('test getAllPromisesForQueryingDevices when xcode is not detected', () => {
|
||||||
const promises = getAllPromisesForQueryingDevices(
|
const flipperServer = new FlipperServer({}, mockStore, getInstance());
|
||||||
mockStore,
|
flipperServer.ios.iosBridge = {} as IOSBridge;
|
||||||
logger,
|
const promises = flipperServer.ios.getAllPromisesForQueryingDevices(false);
|
||||||
{} as IOSBridge,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
expect(promises.length).toEqual(1);
|
expect(promises.length).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,9 +8,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {ChildProcess} from 'child_process';
|
import {ChildProcess} from 'child_process';
|
||||||
import {Store} from '../../../reducers/index';
|
|
||||||
import {setXcodeDetected} from '../../../reducers/application';
|
|
||||||
import {Logger} from '../../../fb-interfaces/Logger';
|
|
||||||
import type {DeviceType} from 'flipper-plugin';
|
import type {DeviceType} from 'flipper-plugin';
|
||||||
import {promisify} from 'util';
|
import {promisify} from 'util';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
@@ -18,14 +15,13 @@ import child_process from 'child_process';
|
|||||||
const execFile = child_process.execFile;
|
const execFile = child_process.execFile;
|
||||||
import iosUtil from './iOSContainerUtility';
|
import iosUtil from './iOSContainerUtility';
|
||||||
import IOSDevice from './IOSDevice';
|
import IOSDevice from './IOSDevice';
|
||||||
import {addErrorNotification} from '../../../reducers/notifications';
|
|
||||||
import {getStaticPath} from '../../../utils/pathUtils';
|
import {getStaticPath} from '../../../utils/pathUtils';
|
||||||
import {destroyDevice} from '../../../reducers/connections';
|
|
||||||
import {
|
import {
|
||||||
ERR_NO_IDB_OR_XCODE_AVAILABLE,
|
ERR_NO_IDB_OR_XCODE_AVAILABLE,
|
||||||
IOSBridge,
|
IOSBridge,
|
||||||
makeIOSBridge,
|
makeIOSBridge,
|
||||||
} from './IOSBridge';
|
} from './IOSBridge';
|
||||||
|
import {FlipperServer} from '../../FlipperServer';
|
||||||
|
|
||||||
type iOSSimulatorDevice = {
|
type iOSSimulatorDevice = {
|
||||||
state: 'Booted' | 'Shutdown' | 'Shutting Down';
|
state: 'Booted' | 'Shutdown' | 'Shutting Down';
|
||||||
@@ -45,8 +41,6 @@ export type IOSDeviceParams = {
|
|||||||
|
|
||||||
const exec = promisify(child_process.exec);
|
const exec = promisify(child_process.exec);
|
||||||
|
|
||||||
let portForwarders: Array<ChildProcess> = [];
|
|
||||||
|
|
||||||
function isAvailable(simulator: iOSSimulatorDevice): boolean {
|
function isAvailable(simulator: iOSSimulatorDevice): boolean {
|
||||||
// For some users "availability" is set, for others it's "isAvailable"
|
// For some users "availability" is set, for others it's "isAvailable"
|
||||||
// It's not clear which key is set, so we are checking both.
|
// It's not clear which key is set, so we are checking both.
|
||||||
@@ -58,142 +52,239 @@ function isAvailable(simulator: iOSSimulatorDevice): boolean {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const portforwardingClient = getStaticPath(
|
export class IOSDeviceManager {
|
||||||
path.join(
|
private portForwarders: Array<ChildProcess> = [];
|
||||||
'PortForwardingMacApp.app',
|
|
||||||
'Contents',
|
|
||||||
'MacOS',
|
|
||||||
'PortForwardingMacApp',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
function forwardPort(port: number, multiplexChannelPort: number) {
|
private portforwardingClient = getStaticPath(
|
||||||
const childProcess = execFile(
|
path.join(
|
||||||
portforwardingClient,
|
'PortForwardingMacApp.app',
|
||||||
[`-portForward=${port}`, `-multiplexChannelPort=${multiplexChannelPort}`],
|
'Contents',
|
||||||
(err, stdout, stderr) => {
|
'MacOS',
|
||||||
// This happens on app reloads and doesn't need to be treated as an error.
|
'PortForwardingMacApp',
|
||||||
console.warn('Port forwarding app failed to start', err, stdout, stderr);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
console.log('Port forwarding app started', childProcess);
|
|
||||||
childProcess.addListener('error', (err) =>
|
|
||||||
console.warn('Port forwarding app error', err),
|
|
||||||
);
|
|
||||||
childProcess.addListener('exit', (code) =>
|
|
||||||
console.log(`Port forwarding app exited with code ${code}`),
|
|
||||||
);
|
|
||||||
return childProcess;
|
|
||||||
}
|
|
||||||
|
|
||||||
function startDevicePortForwarders(): void {
|
|
||||||
if (portForwarders.length > 0) {
|
|
||||||
// Only ever start them once.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// start port forwarding server for real device connections
|
|
||||||
portForwarders = [forwardPort(8089, 8079), forwardPort(8088, 8078)];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof window !== 'undefined') {
|
|
||||||
window.addEventListener('beforeunload', () => {
|
|
||||||
portForwarders.forEach((process) => process.kill());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getAllPromisesForQueryingDevices(
|
|
||||||
store: Store,
|
|
||||||
logger: Logger,
|
|
||||||
iosBridge: IOSBridge,
|
|
||||||
isXcodeDetected: boolean,
|
|
||||||
): Array<Promise<any>> {
|
|
||||||
const promArray = [
|
|
||||||
getActiveDevices(
|
|
||||||
store.getState().settingsState.idbPath,
|
|
||||||
store.getState().settingsState.enablePhysicalIOS,
|
|
||||||
).then((devices: IOSDeviceParams[]) => {
|
|
||||||
console.log('Active iOS devices:', devices);
|
|
||||||
processDevices(store, logger, iosBridge, devices, 'physical');
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
if (isXcodeDetected) {
|
|
||||||
promArray.push(
|
|
||||||
...[
|
|
||||||
checkXcodeVersionMismatch(store),
|
|
||||||
getSimulators(store, true).then((devices) => {
|
|
||||||
processDevices(store, logger, iosBridge, devices, 'emulator');
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return promArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function queryDevices(
|
|
||||||
store: Store,
|
|
||||||
logger: Logger,
|
|
||||||
iosBridge: IOSBridge,
|
|
||||||
): Promise<any> {
|
|
||||||
const isXcodeInstalled = await iosUtil.isXcodeDetected();
|
|
||||||
return Promise.all(
|
|
||||||
getAllPromisesForQueryingDevices(
|
|
||||||
store,
|
|
||||||
logger,
|
|
||||||
iosBridge,
|
|
||||||
isXcodeInstalled,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
iosBridge: IOSBridge | undefined;
|
||||||
|
private xcodeVersionMismatchFound = false;
|
||||||
|
public xcodeCommandLineToolsDetected = false;
|
||||||
|
|
||||||
function processDevices(
|
constructor(private flipperServer: FlipperServer) {
|
||||||
store: Store,
|
if (typeof window !== 'undefined') {
|
||||||
logger: Logger,
|
window.addEventListener('beforeunload', () => {
|
||||||
iosBridge: IOSBridge,
|
this.portForwarders.forEach((process) => process.kill());
|
||||||
activeDevices: IOSDeviceParams[],
|
|
||||||
type: 'physical' | 'emulator',
|
|
||||||
) {
|
|
||||||
const {connections} = store.getState();
|
|
||||||
const currentDeviceIDs: Set<string> = new Set(
|
|
||||||
connections.devices
|
|
||||||
.filter(
|
|
||||||
(device) =>
|
|
||||||
device instanceof IOSDevice &&
|
|
||||||
device.deviceType === type &&
|
|
||||||
device.connected.get(),
|
|
||||||
)
|
|
||||||
.map((device) => device.serial),
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const {udid, type, name} of activeDevices) {
|
|
||||||
if (currentDeviceIDs.has(udid)) {
|
|
||||||
currentDeviceIDs.delete(udid);
|
|
||||||
} else {
|
|
||||||
// clean up offline device
|
|
||||||
destroyDevice(store, logger, udid);
|
|
||||||
logger.track('usage', 'register-device', {
|
|
||||||
os: 'iOS',
|
|
||||||
type: type,
|
|
||||||
name: name,
|
|
||||||
serial: udid,
|
|
||||||
});
|
|
||||||
const iOSDevice = new IOSDevice(iosBridge, udid, type, name);
|
|
||||||
iOSDevice.loadDevicePlugins(
|
|
||||||
store.getState().plugins.devicePlugins,
|
|
||||||
store.getState().connections.enabledDevicePlugins,
|
|
||||||
);
|
|
||||||
store.dispatch({
|
|
||||||
type: 'REGISTER_DEVICE',
|
|
||||||
payload: iOSDevice,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
currentDeviceIDs.forEach((id) => {
|
private forwardPort(port: number, multiplexChannelPort: number) {
|
||||||
const device = store
|
const childProcess = execFile(
|
||||||
.getState()
|
this.portforwardingClient,
|
||||||
.connections.devices.find((device) => device.serial === id);
|
[`-portForward=${port}`, `-multiplexChannelPort=${multiplexChannelPort}`],
|
||||||
device?.disconnect();
|
(err, stdout, stderr) => {
|
||||||
});
|
// This happens on app reloads and doesn't need to be treated as an error.
|
||||||
|
console.warn(
|
||||||
|
'Port forwarding app failed to start',
|
||||||
|
err,
|
||||||
|
stdout,
|
||||||
|
stderr,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
console.log('Port forwarding app started', childProcess);
|
||||||
|
childProcess.addListener('error', (err) =>
|
||||||
|
console.warn('Port forwarding app error', err),
|
||||||
|
);
|
||||||
|
childProcess.addListener('exit', (code) =>
|
||||||
|
console.log(`Port forwarding app exited with code ${code}`),
|
||||||
|
);
|
||||||
|
return childProcess;
|
||||||
|
}
|
||||||
|
|
||||||
|
private startDevicePortForwarders(): void {
|
||||||
|
if (this.portForwarders.length > 0) {
|
||||||
|
// Only ever start them once.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// start port forwarding server for real device connections
|
||||||
|
// TODO: ports should be picked up from flipperServer.config?
|
||||||
|
this.portForwarders = [
|
||||||
|
this.forwardPort(8089, 8079),
|
||||||
|
this.forwardPort(8088, 8078),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
getAllPromisesForQueryingDevices(
|
||||||
|
isXcodeDetected: boolean,
|
||||||
|
): Array<Promise<any>> {
|
||||||
|
const {config} = this.flipperServer;
|
||||||
|
const promArray = [
|
||||||
|
getActiveDevices(config.idbPath, config.enablePhysicalIOS).then(
|
||||||
|
(devices: IOSDeviceParams[]) => {
|
||||||
|
this.processDevices(devices, 'physical');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
];
|
||||||
|
if (isXcodeDetected) {
|
||||||
|
promArray.push(
|
||||||
|
...[
|
||||||
|
this.checkXcodeVersionMismatch(),
|
||||||
|
this.getSimulators(true).then((devices) => {
|
||||||
|
this.processDevices(devices, 'emulator');
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return promArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async queryDevices(): Promise<any> {
|
||||||
|
const isXcodeInstalled = await iosUtil.isXcodeDetected();
|
||||||
|
return Promise.all(this.getAllPromisesForQueryingDevices(isXcodeInstalled));
|
||||||
|
}
|
||||||
|
|
||||||
|
private processDevices(
|
||||||
|
activeDevices: IOSDeviceParams[],
|
||||||
|
type: 'physical' | 'emulator',
|
||||||
|
) {
|
||||||
|
if (!this.iosBridge) {
|
||||||
|
throw new Error('iOS bridge not yet initialized');
|
||||||
|
}
|
||||||
|
const currentDeviceIDs = new Set(
|
||||||
|
this.flipperServer
|
||||||
|
.getDevices()
|
||||||
|
.filter(
|
||||||
|
(device) =>
|
||||||
|
device instanceof IOSDevice &&
|
||||||
|
device.deviceType === type &&
|
||||||
|
device.connected.get(),
|
||||||
|
)
|
||||||
|
.map((device) => device.serial),
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const {udid, type, name} of activeDevices) {
|
||||||
|
if (currentDeviceIDs.has(udid)) {
|
||||||
|
currentDeviceIDs.delete(udid);
|
||||||
|
} else {
|
||||||
|
const iOSDevice = new IOSDevice(this.iosBridge, udid, type, name);
|
||||||
|
this.flipperServer.registerDevice(iOSDevice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentDeviceIDs.forEach((id) => {
|
||||||
|
this.flipperServer.unregisterDevice(id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async watchIOSDevices() {
|
||||||
|
// TODO: pull this condition up
|
||||||
|
if (!this.flipperServer.config.enableIOS) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const isDetected = await iosUtil.isXcodeDetected();
|
||||||
|
this.xcodeCommandLineToolsDetected = isDetected;
|
||||||
|
if (this.flipperServer.config.enablePhysicalIOS) {
|
||||||
|
this.startDevicePortForwarders();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// Awaiting the promise here to trigger immediate error handling.
|
||||||
|
this.iosBridge = await makeIOSBridge(
|
||||||
|
this.flipperServer.config.idbPath,
|
||||||
|
isDetected,
|
||||||
|
);
|
||||||
|
this.queryDevicesForever();
|
||||||
|
} catch (err) {
|
||||||
|
// This case is expected if both Xcode and idb are missing.
|
||||||
|
if (err.message === ERR_NO_IDB_OR_XCODE_AVAILABLE) {
|
||||||
|
console.warn(
|
||||||
|
'Failed to init iOS device. You may want to disable iOS support in the settings.',
|
||||||
|
err,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.error('Failed to initialize iOS dispatcher:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error while querying iOS devices:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getSimulators(bootedOnly: boolean): Promise<Array<IOSDeviceParams>> {
|
||||||
|
return promisify(execFile)(
|
||||||
|
'xcrun',
|
||||||
|
['simctl', ...getDeviceSetPath(), 'list', 'devices', '--json'],
|
||||||
|
{
|
||||||
|
encoding: 'utf8',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.then(({stdout}) => JSON.parse(stdout).devices)
|
||||||
|
.then((simulatorDevices: Array<iOSSimulatorDevice>) => {
|
||||||
|
const simulators = Object.values(simulatorDevices).flat();
|
||||||
|
return simulators
|
||||||
|
.filter(
|
||||||
|
(simulator) =>
|
||||||
|
(!bootedOnly || simulator.state === 'Booted') &&
|
||||||
|
isAvailable(simulator),
|
||||||
|
)
|
||||||
|
.map((simulator) => {
|
||||||
|
return {
|
||||||
|
...simulator,
|
||||||
|
type: 'emulator',
|
||||||
|
} as IOSDeviceParams;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((e: Error) => {
|
||||||
|
console.warn('Failed to query simulators:', e);
|
||||||
|
if (e.message.includes('Xcode license agreements')) {
|
||||||
|
this.flipperServer.emit('notification', {
|
||||||
|
type: 'error',
|
||||||
|
title: 'Xcode license requires approval',
|
||||||
|
description:
|
||||||
|
'The Xcode license agreement has changed. You need to either open Xcode and agree to the terms or run `sudo xcodebuild -license` in a Terminal to allow simulators to work with Flipper.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Promise.resolve([]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private queryDevicesForever() {
|
||||||
|
return this.queryDevices()
|
||||||
|
.then(() => {
|
||||||
|
// It's important to schedule the next check AFTER the current one has completed
|
||||||
|
// to avoid simultaneous queries which can cause multiple user input prompts.
|
||||||
|
setTimeout(() => this.queryDevicesForever(), 3000);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.warn('Failed to continuously query devices:', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async checkXcodeVersionMismatch() {
|
||||||
|
if (this.xcodeVersionMismatchFound) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
let {stdout: xcodeCLIVersion} = await exec('xcode-select -p');
|
||||||
|
xcodeCLIVersion = xcodeCLIVersion.trim();
|
||||||
|
const {stdout} = await exec('ps aux | grep CoreSimulator');
|
||||||
|
for (const line of stdout.split('\n')) {
|
||||||
|
const match = parseXcodeFromCoreSimPath(line);
|
||||||
|
const runningVersion =
|
||||||
|
match && match.length > 0 ? match[0].trim() : null;
|
||||||
|
if (runningVersion && runningVersion !== xcodeCLIVersion) {
|
||||||
|
const errorMessage = `Xcode version mismatch: Simulator is running from "${runningVersion}" while Xcode CLI is "${xcodeCLIVersion}". Running "xcode-select --switch ${runningVersion}" can fix this. For example: "sudo xcode-select -s /Applications/Xcode.app/Contents/Developer"`;
|
||||||
|
this.flipperServer.emit('notification', {
|
||||||
|
type: 'error',
|
||||||
|
title: 'Xcode version mismatch',
|
||||||
|
description: '' + errorMessage,
|
||||||
|
});
|
||||||
|
this.xcodeVersionMismatchFound = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to determine Xcode version:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDeviceSetPath() {
|
function getDeviceSetPath() {
|
||||||
@@ -202,47 +293,6 @@ function getDeviceSetPath() {
|
|||||||
: [];
|
: [];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSimulators(
|
|
||||||
store: Store,
|
|
||||||
bootedOnly: boolean,
|
|
||||||
): Promise<Array<IOSDeviceParams>> {
|
|
||||||
return promisify(execFile)(
|
|
||||||
'xcrun',
|
|
||||||
['simctl', ...getDeviceSetPath(), 'list', 'devices', '--json'],
|
|
||||||
{
|
|
||||||
encoding: 'utf8',
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.then(({stdout}) => JSON.parse(stdout).devices)
|
|
||||||
.then((simulatorDevices: Array<iOSSimulatorDevice>) => {
|
|
||||||
const simulators = Object.values(simulatorDevices).flat();
|
|
||||||
return simulators
|
|
||||||
.filter(
|
|
||||||
(simulator) =>
|
|
||||||
(!bootedOnly || simulator.state === 'Booted') &&
|
|
||||||
isAvailable(simulator),
|
|
||||||
)
|
|
||||||
.map((simulator) => {
|
|
||||||
return {
|
|
||||||
...simulator,
|
|
||||||
type: 'emulator',
|
|
||||||
} as IOSDeviceParams;
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((e: Error) => {
|
|
||||||
console.warn('Failed to query simulators:', e);
|
|
||||||
if (e.message.includes('Xcode license agreements')) {
|
|
||||||
store.dispatch(
|
|
||||||
addErrorNotification(
|
|
||||||
'Xcode license requires approval',
|
|
||||||
'The Xcode license agreement has changed. You need to either open Xcode and agree to the terms or run `sudo xcodebuild -license` in a Terminal to allow simulators to work with Flipper.',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return Promise.resolve([]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function launchSimulator(udid: string): Promise<any> {
|
export async function launchSimulator(udid: string): Promise<any> {
|
||||||
await promisify(execFile)(
|
await promisify(execFile)(
|
||||||
'xcrun',
|
'xcrun',
|
||||||
@@ -262,88 +312,8 @@ function getActiveDevices(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function queryDevicesForever(
|
|
||||||
store: Store,
|
|
||||||
logger: Logger,
|
|
||||||
iosBridge: IOSBridge,
|
|
||||||
) {
|
|
||||||
return queryDevices(store, logger, iosBridge)
|
|
||||||
.then(() => {
|
|
||||||
// It's important to schedule the next check AFTER the current one has completed
|
|
||||||
// to avoid simultaneous queries which can cause multiple user input prompts.
|
|
||||||
setTimeout(() => queryDevicesForever(store, logger, iosBridge), 3000);
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.warn('Failed to continuously query devices:', err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parseXcodeFromCoreSimPath(
|
export function parseXcodeFromCoreSimPath(
|
||||||
line: string,
|
line: string,
|
||||||
): RegExpMatchArray | null {
|
): RegExpMatchArray | null {
|
||||||
return line.match(/\/[\/\w@)(\-\+]*\/Xcode[^/]*\.app\/Contents\/Developer/);
|
return line.match(/\/[\/\w@)(\-\+]*\/Xcode[^/]*\.app\/Contents\/Developer/);
|
||||||
}
|
}
|
||||||
|
|
||||||
let xcodeVersionMismatchFound = false;
|
|
||||||
|
|
||||||
async function checkXcodeVersionMismatch(store: Store) {
|
|
||||||
if (xcodeVersionMismatchFound) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
let {stdout: xcodeCLIVersion} = await exec('xcode-select -p');
|
|
||||||
xcodeCLIVersion = xcodeCLIVersion.trim();
|
|
||||||
const {stdout} = await exec('ps aux | grep CoreSimulator');
|
|
||||||
for (const line of stdout.split('\n')) {
|
|
||||||
const match = parseXcodeFromCoreSimPath(line);
|
|
||||||
const runningVersion = match && match.length > 0 ? match[0].trim() : null;
|
|
||||||
if (runningVersion && runningVersion !== xcodeCLIVersion) {
|
|
||||||
const errorMessage = `Xcode version mismatch: Simulator is running from "${runningVersion}" while Xcode CLI is "${xcodeCLIVersion}". Running "xcode-select --switch ${runningVersion}" can fix this. For example: "sudo xcode-select -s /Applications/Xcode.app/Contents/Developer"`;
|
|
||||||
store.dispatch(
|
|
||||||
addErrorNotification('Xcode version mismatch', errorMessage),
|
|
||||||
);
|
|
||||||
xcodeVersionMismatchFound = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Failed to determine Xcode version:', e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default (store: Store, logger: Logger) => {
|
|
||||||
if (!store.getState().settingsState.enableIOS) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
iosUtil
|
|
||||||
.isXcodeDetected()
|
|
||||||
.then(async (isDetected) => {
|
|
||||||
store.dispatch(setXcodeDetected(isDetected));
|
|
||||||
if (store.getState().settingsState.enablePhysicalIOS) {
|
|
||||||
startDevicePortForwarders();
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
// Awaiting the promise here to trigger immediate error handling.
|
|
||||||
return await makeIOSBridge(
|
|
||||||
store.getState().settingsState.idbPath,
|
|
||||||
isDetected,
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
// This case is expected if both Xcode and idb are missing.
|
|
||||||
if (err.message === ERR_NO_IDB_OR_XCODE_AVAILABLE) {
|
|
||||||
console.warn(
|
|
||||||
'Failed to init iOS device. You may want to disable iOS support in the settings.',
|
|
||||||
err,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
console.error('Failed to initialize iOS dispatcher:', err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(
|
|
||||||
(iosBridge) => iosBridge && queryDevicesForever(store, logger, iosBridge),
|
|
||||||
)
|
|
||||||
.catch((err) => {
|
|
||||||
console.error('Error while querying iOS devices:', err);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import {Client as ADBClient} from 'adbkit';
|
|||||||
import archiver from 'archiver';
|
import archiver from 'archiver';
|
||||||
import {timeout} from 'flipper-plugin';
|
import {timeout} from 'flipper-plugin';
|
||||||
import {v4 as uuid} from 'uuid';
|
import {v4 as uuid} from 'uuid';
|
||||||
|
import {isTest} from '../../utils/isProduction';
|
||||||
|
|
||||||
export type CertificateExchangeMedium = 'FS_ACCESS' | 'WWW';
|
export type CertificateExchangeMedium = 'FS_ACCESS' | 'WWW';
|
||||||
|
|
||||||
@@ -581,7 +582,10 @@ export default class CertificateProvider {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ensureCertificateAuthorityExists(): Promise<void> {
|
async ensureCertificateAuthorityExists(): Promise<void> {
|
||||||
|
if (isTest()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!fs.existsSync(caKey)) {
|
if (!fs.existsSync(caKey)) {
|
||||||
return this.generateCertificateAuthority();
|
return this.generateCertificateAuthority();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user