diff --git a/src/__tests__/server.device.js b/src/__tests__/server.device.js index a8527e7c1..f3c62f4c2 100644 --- a/src/__tests__/server.device.js +++ b/src/__tests__/server.device.js @@ -50,7 +50,7 @@ test( // Make sure it only connects once registeredClients.push(client); - expect(registeredClients.length).toBe(1); + expect(registeredClients).toHaveLength(1); // Make sure client stays connected for some time before passing test setTimeout(() => { diff --git a/src/__tests__/server.electron.js b/src/__tests__/server.electron.js index 08021af3c..c2f16e608 100644 --- a/src/__tests__/server.electron.js +++ b/src/__tests__/server.electron.js @@ -5,8 +5,8 @@ * @format */ -import Server, {SECURE_PORT, INSECURE_PORT} from '../server.js'; import {init as initLogger} from '../fb-stubs/Logger'; +import Server from '../server'; import reducers from '../reducers/index.js'; import configureStore from 'redux-mock-store'; import path from 'path'; @@ -28,7 +28,8 @@ beforeAll(() => { }); test('servers starting at ports', done => { - const serversToBeStarted = new Set([SECURE_PORT, INSECURE_PORT]); + const ports = mockStore.getState().application.serverPorts; + const serversToBeStarted = new Set([ports.secure, ports.insecure]); // Resolve promise when we get a listen event for each port const listenerPromise = new Promise((resolve, reject) => { diff --git a/src/devices/AndroidDevice.js b/src/devices/AndroidDevice.js index f8e8043a2..77b912e4b 100644 --- a/src/devices/AndroidDevice.js +++ b/src/devices/AndroidDevice.js @@ -6,11 +6,11 @@ */ import type {DeviceType, DeviceShell} from './BaseDevice.js'; +import type {Store} from '../reducers/index'; import {Priority} from 'adbkit-logcat-fb'; import child_process from 'child_process'; import BaseDevice from './BaseDevice.js'; -import {SECURE_PORT, INSECURE_PORT} from '../server'; type ADBClient = any; @@ -69,16 +69,14 @@ export default class AndroidDevice extends BaseDevice { return ['date', 'pid', 'tid', 'tag', 'message', 'type', 'time']; } - reverse(): Promise { - return this.adb - .reverse(this.serial, `tcp:${SECURE_PORT}`, `tcp:${SECURE_PORT}`) - .then(() => - this.adb.reverse( - this.serial, - `tcp:${INSECURE_PORT}`, - `tcp:${INSECURE_PORT}`, - ), - ); + reverse(ports: [number]): Promise { + return Promise.all( + ports.map(port => + this.adb.reverse(this.serial, `tcp:${port}`, `tcp:${port}`), + ), + ).then(() => { + return; + }); } spawnShell(): DeviceShell { diff --git a/src/dispatcher/androidDevice.js b/src/dispatcher/androidDevice.js index 653da0993..847757958 100644 --- a/src/dispatcher/androidDevice.js +++ b/src/dispatcher/androidDevice.js @@ -15,7 +15,11 @@ import {registerDeviceCallbackOnPlugins} from '../utils/onRegisterDevice.js'; import {recordSuccessMetric} from '../utils/metrics'; const adb = require('adbkit-fb'); -function createDevice(adbClient, device): Promise { +function createDevice( + adbClient: any, + device: any, + store: Store, +): Promise { return new Promise((resolve, reject) => { const type = device.type !== 'device' || device.id.startsWith('emulator') @@ -28,7 +32,8 @@ function createDevice(adbClient, device): Promise { name = (await getRunningEmulatorName(device.id)) || name; } const androidDevice = new AndroidDevice(device.id, type, name, adbClient); - androidDevice.reverse(); + const ports = store.getState().application.serverPorts; + androidDevice.reverse([ports.secure, ports.insecure]); resolve(androidDevice); }); }); @@ -127,7 +132,7 @@ export default (store: Store, logger: Logger) => { tracker.on('add', async device => { if (device.type !== 'offline') { - registerDevice(client, device); + registerDevice(client, device, store); } }); @@ -135,7 +140,7 @@ export default (store: Store, logger: Logger) => { if (device.type === 'offline') { unregisterDevices([device.id]); } else { - registerDevice(client, device); + registerDevice(client, device, store); } }); @@ -156,8 +161,8 @@ export default (store: Store, logger: Logger) => { }); }; - async function registerDevice(adbClient: any, deviceData: any) { - const androidDevice = await createDevice(adbClient, deviceData); + async function registerDevice(adbClient: any, deviceData: any, store: Store) { + const androidDevice = await createDevice(adbClient, deviceData, store); logger.track('usage', 'register-device', { os: 'Android', name: androidDevice.title, diff --git a/src/dispatcher/application.js b/src/dispatcher/application.js index 5b8f4cd24..a506c0014 100644 --- a/src/dispatcher/application.js +++ b/src/dispatcher/application.js @@ -8,6 +8,7 @@ import {remote, ipcRenderer} from 'electron'; import type {Store} from '../reducers/index.js'; import type Logger from '../fb-stubs/Logger.js'; +import {parseFlipperPorts} from '../utils/environmentVariables'; import {selectPlugin, userPreferredPlugin} from '../reducers/connections'; export const uriComponents = (url: string) => { @@ -61,4 +62,14 @@ export default (store: Store, logger: Logger) => { store.dispatch(userPreferredPlugin(match[1])); } }); + + if (process.env.FLIPPER_PORTS) { + const portOverrides = parseFlipperPorts(process.env.FLIPPER_PORTS); + if (portOverrides) { + store.dispatch({ + type: 'SET_SERVER_PORTS', + payload: portOverrides, + }); + } + } }; diff --git a/src/reducers/application.js b/src/reducers/application.js index 6aac10874..1c8d4be2c 100644 --- a/src/reducers/application.js +++ b/src/reducers/application.js @@ -26,6 +26,10 @@ export type State = { windowIsFocused: boolean, activeSheet: ActiveSheet, sessionId: ?string, + serverPorts: { + insecure: number, + secure: number, + }, }; type BooleanActionType = @@ -42,6 +46,13 @@ export type Action = | { type: 'SET_ACTIVE_SHEET', payload: ActiveSheet, + } + | { + type: 'SET_SERVER_PORTS', + payload: { + insecure: number, + secure: number, + }, }; const initialState: () => State = () => ({ @@ -51,6 +62,10 @@ const initialState: () => State = () => ({ windowIsFocused: remote.getCurrentWindow().isFocused(), activeSheet: null, sessionId: uuidv1(), + serverPorts: { + insecure: 8089, + secure: 8088, + }, }); export default function reducer(state: State, action: Action): State { @@ -80,6 +95,12 @@ export default function reducer(state: State, action: Action): State { ...state, activeSheet: action.payload, }; + } + if (action.type === 'SET_SERVER_PORTS') { + return { + ...state, + serverPorts: action.payload, + }; } else { return state; } diff --git a/src/server.js b/src/server.js index 1d49e704c..5a4fb17e6 100644 --- a/src/server.js +++ b/src/server.js @@ -24,9 +24,6 @@ const invariant = require('invariant'); const tls = require('tls'); const net = require('net'); -export const SECURE_PORT = 8088; -export const INSECURE_PORT = 8089; - type RSocket = {| fireAndForget(payload: {data: string}): void, connectionStatus(): any, @@ -62,13 +59,12 @@ export default class Server extends EventEmitter { ((event: 'clients-change', callback: () => void) => void); init() { + const {insecure, secure} = this.store.getState().application.serverPorts; this.initialisePromise = this.certificateProvider .loadSecureServerConfig() - .then( - options => (this.secureServer = this.startServer(SECURE_PORT, options)), - ) + .then(options => (this.secureServer = this.startServer(secure, options))) .then(() => { - this.insecureServer = this.startServer(INSECURE_PORT); + this.insecureServer = this.startServer(insecure); return; }); recordSuccessMetric(this.initialisePromise, 'initializeServer'); diff --git a/src/utils/__tests__/environmentVariables.node.js b/src/utils/__tests__/environmentVariables.node.js new file mode 100644 index 000000000..a1072301d --- /dev/null +++ b/src/utils/__tests__/environmentVariables.node.js @@ -0,0 +1,36 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +import {parseFlipperPorts} from '../environmentVariables'; + +test('Valid port overrides are parsed correctly', () => { + const overrides = parseFlipperPorts('1111,2222'); + expect(overrides).toEqual({insecure: 1111, secure: 2222}); +}); + +test('Malformed numbers are ignored', () => { + const malformed1 = parseFlipperPorts('1111,22s22'); + expect(malformed1).toBe(undefined); + + const malformed2 = parseFlipperPorts('11a11,2222'); + expect(malformed2).toBe(undefined); +}); + +test('Wrong number of values is ignored', () => { + const overrides = parseFlipperPorts('1111'); + expect(overrides).toBe(undefined); +}); + +test('Empty values are ignored', () => { + const overrides = parseFlipperPorts('1111,'); + expect(overrides).toBe(undefined); +}); + +test('Negative values are ignored', () => { + const overrides = parseFlipperPorts('-1111,2222'); + expect(overrides).toBe(undefined); +}); diff --git a/src/utils/environmentVariables.js b/src/utils/environmentVariables.js new file mode 100644 index 000000000..cd16c7be6 --- /dev/null +++ b/src/utils/environmentVariables.js @@ -0,0 +1,29 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +export function parseFlipperPorts( + envVar: string, +): ?{insecure: number, secure: number} { + const components = envVar.split(','); + const ports = components.map(x => parseInt(x, 10)); + + // Malformed numbers will get parsed to NaN which is not > 0 + if ( + ports.length === 2 && + components.every(x => /^[0-9]+$/.test(x)) && + ports.every(x => x > 0) + ) { + return { + insecure: ports[0], + secure: ports[1], + }; + } else { + console.error( + `Ignoring malformed FLIPPER_PORTS env variable: "${envVar}". Example expected format: "1111,2222".`, + ); + } +}