diff --git a/desktop/app/src/electron/initializeElectron.tsx b/desktop/app/src/electron/initializeElectron.tsx index 4d3291961..1d3e9d3c7 100644 --- a/desktop/app/src/electron/initializeElectron.tsx +++ b/desktop/app/src/electron/initializeElectron.tsx @@ -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, diff --git a/desktop/flipper-common/src/server-types.tsx b/desktop/flipper-common/src/server-types.tsx index 55052508b..7a86bc953 100644 --- a/desktop/flipper-common/src/server-types.tsx +++ b/desktop/flipper-common/src/server-types.tsx @@ -160,7 +160,7 @@ export type FlipperServerCommands = { 'keychain-unset': (service: string) => Promise; }; -type ENVIRONMENT_VARIABLES = +export type ENVIRONMENT_VARIABLES = | 'NODE_ENV' | 'DEV_SERVER_URL' | 'CONFIG' diff --git a/desktop/flipper-server/package.json b/desktop/flipper-server/package.json index 776fae76e..f44ddf3f0 100644 --- a/desktop/flipper-server/package.json +++ b/desktop/flipper-server/package.json @@ -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" }, diff --git a/desktop/flipper-server/src/index.tsx b/desktop/flipper-server/src/index.tsx index dba7e8b0b..a4f2879de 100644 --- a/desktop/flipper-server/src/index.tsx +++ b/desktop/flipper-server/src/index.tsx @@ -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) => { - console.error(chalk.red('Server error: '), e); - process.exit(1); -}); +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); + }); diff --git a/desktop/flipper-server/src/startBaseServer.tsx b/desktop/flipper-server/src/startBaseServer.tsx new file mode 100644 index 000000000..9cd23da30 --- /dev/null +++ b/desktop/flipper-server/src/startBaseServer.tsx @@ -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.`, + ), + ); + } +} diff --git a/desktop/flipper-server/src/startFlipperServer.tsx b/desktop/flipper-server/src/startFlipperServer.tsx new file mode 100644 index 000000000..863306650 --- /dev/null +++ b/desktop/flipper-server/src/startFlipperServer.tsx @@ -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> = + 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); + }, + }; +} diff --git a/desktop/flipper-server/src/startWebServerDev.tsx b/desktop/flipper-server/src/startWebServerDev.tsx index 2cde4311a..a4ca52c96 100644 --- a/desktop/flipper-server/src/startWebServerDev.tsx +++ b/desktop/flipper-server/src/startWebServerDev.tsx @@ -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( diff --git a/desktop/flipper-server/tsconfig.json b/desktop/flipper-server/tsconfig.json index 69d14c891..6233f9519 100644 --- a/desktop/flipper-server/tsconfig.json +++ b/desktop/flipper-server/tsconfig.json @@ -5,6 +5,9 @@ "rootDir": "src" }, "references": [ + { + "path": "../flipper-common" + }, { "path": "../flipper-server-core" },