Summary:
This stack takes care of handling care of moving all device interactions over the (possible) async channel FlipperServer. The FlipperServer interface (see previous diff) allows listening to specific server events using `on`, and emit commands to be executed by the server by using `exec` (e.g. `exec('take-screenshot', serial) => Promise<buffer>`).
FlipperServerImpl implements this interface on the server side.
The device implementations are split as follows
```
server / backend process:
ServerDevice
- iOSDevice
- AndroidDevice
- MetroDevice
- DummyDevice
- Mac/Windows Device
frontend / ui:
BaseDevice: a normal connected, device, implements device apis as they already existed
- ArchivedDevice (note that this doesn't have a server counterpart)
- TestDevice (for unit tests, with stubbed backend communication)
```
All features of devices are for simplicity unified (since the deviations are small), where specific device types might not implement certain features like taking screenshots or running shell commands.
To avoid making this diff unnecessarily big, some open Todo's will be addressed later in this stack, and it shouldn't be landed alone.
Reviewed By: timur-valiev
Differential Revision: D30909346
fbshipit-source-id: cce0bee94fdd5db59bebe3577a6084219a038719
170 lines
4.3 KiB
TypeScript
170 lines
4.3 KiB
TypeScript
/**
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @format
|
|
*/
|
|
|
|
import React from 'react';
|
|
import {Store} from '../reducers/index';
|
|
import {Logger} from '../fb-interfaces/Logger';
|
|
import {FlipperServerImpl} from '../server/FlipperServerImpl';
|
|
import {selectClient, selectDevice} from '../reducers/connections';
|
|
import Client from '../Client';
|
|
import {notification} from 'antd';
|
|
import BaseDevice from '../devices/BaseDevice';
|
|
|
|
export default async (store: Store, logger: Logger) => {
|
|
const {enableAndroid, androidHome, idbPath, enableIOS, enablePhysicalIOS} =
|
|
store.getState().settingsState;
|
|
const server = new FlipperServerImpl(
|
|
{
|
|
enableAndroid,
|
|
androidHome,
|
|
idbPath,
|
|
enableIOS,
|
|
enablePhysicalIOS,
|
|
serverPorts: store.getState().application.serverPorts,
|
|
},
|
|
store,
|
|
logger,
|
|
);
|
|
|
|
store.dispatch({
|
|
type: 'SET_FLIPPER_SERVER',
|
|
payload: server,
|
|
});
|
|
|
|
server.on('notification', (notif) => {
|
|
notification.open({
|
|
message: notif.title,
|
|
description: notif.description,
|
|
type: notif.type,
|
|
});
|
|
});
|
|
|
|
server.on('server-error', (err) => {
|
|
notification.error({
|
|
message: 'Failed to start connection server',
|
|
description:
|
|
err.code === 'EADDRINUSE' ? (
|
|
<>
|
|
Couldn't start connection server. Looks like you have multiple
|
|
copies of Flipper running or another process is using the same
|
|
port(s). As a result devices will not be able to connect to Flipper.
|
|
<br />
|
|
<br />
|
|
Please try to kill the offending process by running{' '}
|
|
<code>kill $(lsof -ti:PORTNUMBER)</code> and restart flipper.
|
|
<br />
|
|
<br />
|
|
{'' + err}
|
|
</>
|
|
) : (
|
|
<>Failed to start Flipper server: ${err.message}</>
|
|
),
|
|
duration: null,
|
|
});
|
|
});
|
|
|
|
server.on('device-connected', (deviceInfo) => {
|
|
logger.track('usage', 'register-device', {
|
|
os: deviceInfo.os,
|
|
name: deviceInfo.title,
|
|
serial: deviceInfo.serial,
|
|
});
|
|
|
|
const device = new BaseDevice(server, deviceInfo);
|
|
device.loadDevicePlugins(
|
|
store.getState().plugins.devicePlugins,
|
|
store.getState().connections.enabledDevicePlugins,
|
|
);
|
|
|
|
store.dispatch({
|
|
type: 'REGISTER_DEVICE',
|
|
payload: device,
|
|
});
|
|
});
|
|
|
|
server.on('device-disconnected', (device) => {
|
|
logger.track('usage', 'unregister-device', {
|
|
os: device.os,
|
|
serial: device.serial,
|
|
});
|
|
// N.B.: note that we don't remove the device, we keep it in offline
|
|
});
|
|
|
|
server.on('client-connected', (payload) =>
|
|
// TODO: fixed later in this stack
|
|
handleClientConnected(store, payload as any),
|
|
);
|
|
|
|
if (typeof window !== 'undefined') {
|
|
window.addEventListener('beforeunload', () => {
|
|
server.close();
|
|
});
|
|
}
|
|
|
|
server
|
|
.start()
|
|
.then(() => {
|
|
console.log(
|
|
'Flipper server started and accepting device / client connections',
|
|
);
|
|
})
|
|
.catch((e) => {
|
|
console.error('Failed to start Flipper server', e);
|
|
notification.error({
|
|
message: 'Failed to start Flipper server',
|
|
description: 'error: ' + e,
|
|
});
|
|
});
|
|
|
|
return () => {
|
|
server.close();
|
|
};
|
|
};
|
|
|
|
export async function handleClientConnected(store: Store, client: Client) {
|
|
const {connections} = store.getState();
|
|
const existingClient = connections.clients.find((c) => c.id === client.id);
|
|
|
|
if (existingClient) {
|
|
existingClient.destroy();
|
|
store.dispatch({
|
|
type: 'CLEAR_CLIENT_PLUGINS_STATE',
|
|
payload: {
|
|
clientId: client.id,
|
|
devicePlugins: new Set(),
|
|
},
|
|
});
|
|
store.dispatch({
|
|
type: 'CLIENT_REMOVED',
|
|
payload: client.id,
|
|
});
|
|
}
|
|
|
|
console.debug(
|
|
`Device client initialized: ${client.id}. Supported plugins: ${Array.from(
|
|
client.plugins,
|
|
).join(', ')}`,
|
|
'server',
|
|
);
|
|
|
|
store.dispatch({
|
|
type: 'NEW_CLIENT',
|
|
payload: client,
|
|
});
|
|
|
|
// eslint-disable-next-line node/no-sync
|
|
const device = client.deviceSync;
|
|
if (device) {
|
|
store.dispatch(selectDevice(device));
|
|
store.dispatch(selectClient(client.id));
|
|
}
|
|
|
|
client.emit('plugins-change');
|
|
}
|