Start FlipperServerImpl as part of flipper-server

Summary: The previous started up a dev / web server for bundling in flipper-server, this diff starts the flipper server itself, so that we can connect the client to it (done in next diffs)

Reviewed By: passy, aigoncharov

Differential Revision: D32627390

fbshipit-source-id: b48de20f076e1e13842368d16a090708d533b69e
This commit is contained in:
Michel Weststrate
2021-12-08 04:25:28 -08:00
committed by Facebook GitHub Bot
parent 0dfc73da93
commit 1308edc790
8 changed files with 267 additions and 91 deletions

View File

@@ -25,7 +25,6 @@ import {
import type {RenderHost} from 'flipper-ui-core';
import fs from 'fs';
import {setupMenuBar} from './setupMenuBar';
import os from 'os';
import {FlipperServer, FlipperServerConfig} from 'flipper-common';
declare global {
@@ -36,15 +35,6 @@ declare global {
}
}
if (process.env.NODE_ENV === 'development' && os.platform() === 'darwin') {
// By default Node.JS has its internal certificate storage and doesn't use
// the system store. Because of this, it's impossible to access ondemand / devserver
// which are signed using some internal self-issued FB certificates. These certificates
// are automatically installed to MacOS system store on FB machines, so here we're using
// this "mac-ca" library to load them into Node.JS.
global.electronRequire('mac-ca');
}
export function initializeElectron(
flipperServer: FlipperServer,
flipperServerConfig: FlipperServerConfig,

View File

@@ -160,7 +160,7 @@ export type FlipperServerCommands = {
'keychain-unset': (service: string) => Promise<void>;
};
type ENVIRONMENT_VARIABLES =
export type ENVIRONMENT_VARIABLES =
| 'NODE_ENV'
| 'DEV_SERVER_URL'
| 'CONFIG'

View File

@@ -12,8 +12,11 @@
"dependencies": {
"chalk": "^4.1.2",
"express": "^4.15.2",
"flipper-common": "0.0.0",
"flipper-pkg-lib": "0.0.0",
"flipper-server-core": "0.0.0",
"fs-extra": "^9.0.0",
"mac-ca": "^1.0.6",
"p-filter": "^2.1.0",
"socket.io": "^4.3.1"
},

View File

@@ -11,9 +11,35 @@
// needs to be come independently runnable, prebundled, distributed, etc!
// in future require conditionally
import {startWebServerDev} from './startWebServerDev';
import {startFlipperServer} from './startFlipperServer';
import {startBaseServer} from './startBaseServer';
import chalk from 'chalk';
import path from 'path';
startWebServerDev().catch((e) => {
const PORT = 52342;
const rootDir = path.resolve(__dirname, '..', '..');
const staticDir = path.join(rootDir, 'static');
async function start() {
const {app, server, socket} = await startBaseServer({
port: PORT,
staticDir,
entry: 'index.web.dev.html',
});
return Promise.all([
startFlipperServer(rootDir, staticDir),
startWebServerDev(app, server, socket, rootDir),
]);
}
start()
.then(() => {
console.log(
`Flipper DEV server started at http://localhost:${PORT}/index.web.dev.html`,
);
})
.catch((e) => {
console.error(chalk.red('Server error: '), e);
process.exit(1);
});

View File

@@ -0,0 +1,95 @@
/**
* 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 chalk from 'chalk';
import express, {Express} from 'express';
import http from 'http';
import path from 'path';
import fs from 'fs-extra';
import socketio from 'socket.io';
import {hostname} from 'os';
type Config = {
port: number;
staticDir: string;
entry: string;
};
export async function startBaseServer(config: Config): Promise<{
app: Express;
server: http.Server;
socket: socketio.Server;
}> {
checkDevServer();
const {app, server} = await startAssetServer(config);
const socket = addWebsocket(server);
return {
app,
server,
socket,
};
}
function startAssetServer(
config: Config,
): Promise<{app: Express; server: http.Server}> {
const app = express();
app.use((_req, res, next) => {
res.header('Cache-Control', 'private, no-cache, no-store, must-revalidate');
res.header('Expires', '-1');
res.header('Pragma', 'no-cache');
next();
});
app.get('/', (_req, res) => {
fs.readFile(path.join(config.staticDir, config.entry), (_err, content) => {
res.end(content);
});
});
app.use(express.static(config.staticDir));
const server = http.createServer(app);
return new Promise((resolve) => {
server.listen(config.port, 'localhost', () => resolve({app, server}));
});
}
function addWebsocket(server: http.Server) {
const io = new socketio.Server(server); // 3.1.0 socket.io doesn't have type definitions
io.on('connection', (client) => {
console.log(chalk.green(`Client connected ${client.id}`));
});
return io;
}
function looksLikeDevServer(): boolean {
const hn = hostname();
if (/^devvm.*\.facebook\.com$/.test(hn)) {
return true;
}
if (hn.endsWith('.od.fbinfra.net')) {
return true;
}
return false;
}
function checkDevServer() {
if (looksLikeDevServer()) {
console.log(
chalk.red(
`✖ It looks like you're trying to start Flipper on your OnDemand or DevServer, which is not supported. Please run this in a local checkout on your laptop or desktop instead.`,
),
);
}
}

View File

@@ -0,0 +1,120 @@
/**
* 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 os from 'os';
import {
FlipperServerImpl,
getGatekeepers,
loadLauncherSettings,
loadProcessConfig,
loadSettings,
} from 'flipper-server-core';
import {
ENVIRONMENT_VARIABLES,
isTest,
Logger,
setLoggerInstance,
} from 'flipper-common';
import path from 'path';
import fs from 'fs';
export async function startFlipperServer(rootDir: string, staticDir: string) {
if (os.platform() === 'darwin') {
// By default Node.JS has its internal certificate storage and doesn't use
// the system store. Because of this, it's impossible to access ondemand / devserver
// which are signed using some internal self-issued FB certificates. These certificates
// are automatically installed to MacOS system store on FB machines, so here we're using
// this "mac-ca" library to load them into Node.JS.
require('mac-ca');
}
const execPath = process.execPath;
const appPath = rootDir;
const isProduction =
process.env.NODE_ENV !== 'development' && process.env.NODE_ENV !== 'test';
const env = process.env;
let desktopPath = path.resolve(os.homedir(), 'Desktop');
// eslint-disable-next-line node/no-sync
if (!fs.existsSync(desktopPath)) {
console.warn('Failed to find desktop path, falling back to homedir');
desktopPath = os.homedir();
}
const logger = createLogger();
setLoggerInstance(logger);
let keytar: any = undefined;
try {
if (!isTest()) {
keytar = require(path.join(
staticDir,
'native-modules',
`keytar-${process.platform}.node`,
));
}
} catch (e) {
console.error('Failed to load keytar:', e);
}
const envVars: Partial<Record<ENVIRONMENT_VARIABLES, string | undefined>> =
Object.fromEntries(
([] as ENVIRONMENT_VARIABLES[]).map((v) => [v, process.env[v]] as const),
);
const flipperServer = new FlipperServerImpl(
{
env: envVars,
gatekeepers: getGatekeepers(),
isProduction,
paths: {
appPath,
homePath: os.homedir(),
execPath,
staticPath: staticDir,
tempPath: os.tmpdir(),
desktopPath: desktopPath,
},
launcherSettings: await loadLauncherSettings(),
processConfig: loadProcessConfig(env),
settings: await loadSettings(),
validWebSocketOrigins: ['localhost:', 'http://localhost:'],
},
logger,
keytar,
);
await flipperServer.connect();
}
function createLogger(): Logger {
return {
track(..._args: [any, any, any?, any?]) {
// TODO: only if verbose console.debug(...args);
// console.warn('(skipper track)', args);
},
trackTimeSince(..._args: [any, any, any?]) {
// TODO: only if verbose console.debug(...args);
// console.warn('(skipped trackTimeSince)', args);
},
debug(..._args: any[]) {
// TODO: only if verbose console.debug(...args);
},
error(...args: any[]) {
console.error(...args);
console.warn('(skipped error reporting)');
},
warn(...args: any[]) {
console.warn(...args);
console.warn('(skipped error reporting)');
},
info(...args: any[]) {
console.info(...args);
},
};
}

View File

@@ -7,9 +7,8 @@
* @format
*/
import {hostname} from 'os';
import chalk from 'chalk';
import express, {Express} from 'express';
import {Express} from 'express';
import http from 'http';
import path from 'path';
import fs from 'fs-extra';
@@ -18,15 +17,6 @@ import {getWatchFolders} from 'flipper-pkg-lib';
import Metro from 'metro';
import pFilter from 'p-filter';
const PORT = 52342;
const rootDir = path.resolve(__dirname, '..', '..');
const staticDir = path.join(rootDir, 'static');
const babelTransformationsDir = path.resolve(
rootDir,
'babel-transformer',
'src',
);
const uiSourceDirs = [
'flipper-ui-browser',
'flipper-ui-core',
@@ -35,79 +25,22 @@ const uiSourceDirs = [
];
// This file is heavily inspired by scripts/start-dev-server.ts!
export async function startWebServerDev() {
checkDevServer();
export async function startWebServerDev(
app: Express,
server: http.Server,
socket: socketio.Server,
rootDir: string,
) {
// await prepareDefaultPlugins(
// process.env.FLIPPER_RELEASE_CHANNEL === 'insiders',
// );
// await ensurePluginFoldersWatchable();
const {app, server} = await startAssetServer(PORT);
const socket = await addWebsocket(server);
await startMetroServer(app, server, socket);
await startMetroServer(app, server, socket, rootDir);
// await compileMain();
// if (dotenv && dotenv.parsed) {
// console.log('✅ Loaded env vars from .env file: ', dotenv.parsed);
// }
// shutdownElectron = launchElectron(port);
console.log(
`Flipper DEV server started at http://localhost:${PORT}/index.web.dev.html`,
);
}
function looksLikeDevServer(): boolean {
const hn = hostname();
if (/^devvm.*\.facebook\.com$/.test(hn)) {
return true;
}
if (hn.endsWith('.od.fbinfra.net')) {
return true;
}
return false;
}
function checkDevServer() {
if (looksLikeDevServer()) {
console.log(
chalk.red(
`✖ It looks like you're trying to start Flipper on your OnDemand or DevServer, which is not supported. Please run this in a local checkout on your laptop or desktop instead.`,
),
);
}
}
function startAssetServer(
port: number,
): Promise<{app: Express; server: http.Server}> {
const app = express();
app.use((_req, res, next) => {
res.header('Cache-Control', 'private, no-cache, no-store, must-revalidate');
res.header('Expires', '-1');
res.header('Pragma', 'no-cache');
next();
});
app.get('/', (_req, res) => {
fs.readFile(path.join(staticDir, 'index.web.dev.html'), (_err, content) => {
res.end(content);
});
});
app.use(express.static(staticDir));
const server = http.createServer(app);
return new Promise((resolve) => {
server.listen(port, 'localhost', () => resolve({app, server}));
});
}
async function addWebsocket(server: http.Server) {
const io = new socketio.Server(server); // 3.1.0 socket.io doesn't have type definitions
io.on('connection', (client) => {
console.log(chalk.green(`Client connected ${client.id}`));
});
// Refresh the app on changes.
// When Fast Refresh enabled, reloads are performed by HMRClient, so don't need to watch manually here.
@@ -115,14 +48,20 @@ async function addWebsocket(server: http.Server) {
// await startWatchChanges(io);
// }
return io;
console.log('DEV webserver started.');
}
async function startMetroServer(
app: Express,
server: http.Server,
socket: socketio.Server,
rootDir: string,
) {
const babelTransformationsDir = path.resolve(
rootDir,
'babel-transformer',
'src',
);
const watchFolders = await dedupeFolders(
(
await Promise.all(

View File

@@ -5,6 +5,9 @@
"rootDir": "src"
},
"references": [
{
"path": "../flipper-common"
},
{
"path": "../flipper-server-core"
},