diff --git a/desktop/flipper-ui-core/src/dispatcher/handleOpenPluginDeeplink.tsx b/desktop/flipper-ui-core/src/dispatcher/handleOpenPluginDeeplink.tsx index e620e0bc4..48ec33258 100644 --- a/desktop/flipper-ui-core/src/dispatcher/handleOpenPluginDeeplink.tsx +++ b/desktop/flipper-ui-core/src/dispatcher/handleOpenPluginDeeplink.tsx @@ -291,10 +291,10 @@ async function waitForLogin(store: Store) { async function verifyFlipperIsUpToDate(title: string) { const serverConfig = getRenderHostInstance().serverConfig; - // If this is not a headless build, do not check for updates. - if (!serverConfig.environmentInfo.isHeadlessBuild) { - return; - } + // // If this is not a headless build, do not check for updates. + // if (!serverConfig.environmentInfo.isHeadlessBuild) { + // return; + // } const config = serverConfig.processConfig; if ( !isProduction() || diff --git a/desktop/flipper-ui-core/src/sandy-chrome/Navbar.tsx b/desktop/flipper-ui-core/src/sandy-chrome/Navbar.tsx index 8e4023c1c..4327b7bf9 100644 --- a/desktop/flipper-ui-core/src/sandy-chrome/Navbar.tsx +++ b/desktop/flipper-ui-core/src/sandy-chrome/Navbar.tsx @@ -103,8 +103,7 @@ export const Navbar = withTrackingScope(function Navbar() { - {getRenderHostInstance().serverConfig.environmentInfo - .isHeadlessBuild && } + ); diff --git a/desktop/flipper-ui-core/src/sandy-chrome/SandyApp.tsx b/desktop/flipper-ui-core/src/sandy-chrome/SandyApp.tsx index b47791cf6..dfdb33568 100644 --- a/desktop/flipper-ui-core/src/sandy-chrome/SandyApp.tsx +++ b/desktop/flipper-ui-core/src/sandy-chrome/SandyApp.tsx @@ -15,32 +15,31 @@ import { Layout, Dialog, _PortalsManager, - getFlipperLib, - styled, } from 'flipper-plugin'; +import {Link, styled} from '../ui'; import {theme} from 'flipper-plugin'; -import {isProduction, Logger} from 'flipper-common'; +import {Logger} from 'flipper-common'; + import {Navbar} from './Navbar'; import {useStore} from '../utils/useStore'; import {AppInspect} from './appinspect/AppInspect'; import PluginContainer from '../PluginContainer'; import {ContentContainer} from './ContentContainer'; +import {showChangelog} from '../chrome/ChangelogSheet'; import PlatformSelectWizard, { hasPlatformWizardBeenDone, } from '../chrome/PlatformSelectWizard'; -import {getVersionString} from '../utils/versionString'; -import config from '../fb-stubs/config'; -import {WelcomeScreenStaticView} from './WelcomeScreen'; -import {isFBEmployee} from '../utils/fbEmployee'; -import {Button, Modal, notification} from 'antd'; -import {getRenderHostInstance} from 'flipper-frontend-core'; -import {WarningOutlined} from '@ant-design/icons'; import PWAInstallationWizard, { shouldShowPWAInstallationWizard, } from '../chrome/PWAppInstallationWizard'; -import {showChangelog} from '../chrome/ChangelogSheet'; -import {Link} from '../ui'; +import {getVersionString} from '../utils/versionString'; +import config from '../fb-stubs/config'; +import {WelcomeScreenStaticView} from './WelcomeScreen'; import fbConfig from '../fb-stubs/config'; +import {isFBEmployee} from '../utils/fbEmployee'; +import {notification} from 'antd'; +import isProduction from '../utils/isProduction'; +import {getRenderHostInstance} from 'flipper-frontend-core'; export function SandyApp() { const logger = useLogger(); @@ -49,17 +48,10 @@ export function SandyApp() { ); const staticView = useStore((state) => state.connections.staticView); - const serverConfig = getRenderHostInstance().serverConfig; - useEffect(() => { - let title = `Flipper (${getVersionString()}${ + document.title = `Flipper (${getVersionString()}${ config.isFBBuild ? '@FB' : '' })`; - if (!serverConfig.environmentInfo.isHeadlessBuild) { - title += ' (Unsupported)'; - } - - document.title = title; registerStartupTime(logger); @@ -79,55 +71,17 @@ export function SandyApp() { Dialog.showModal((onHide) => ); } - if (serverConfig.environmentInfo.isHeadlessBuild) { - showChangelog(true); - } + showChangelog(true); // don't warn about logger, even with a new logger we don't want to re-register // eslint-disable-next-line }, []); useEffect(() => { - isFBEmployee() - .then((isEmployee) => { - if (isEmployee) { - if (!serverConfig.environmentInfo.isHeadlessBuild) { - Dialog.showModal((onHide) => ( - onHide()} - width={570} - title={ - <> - This Version of Flipper is Unsupported - - } - footer={ - <> - - - - }> - This version is only meant to be used for React Native - debugging. It is not maintained and it doesn't receive updates. - Instead, you should be using the main Flipper version from - Managed Software Center for all other purposes. - - )); - } else if (fbConfig.warnFBEmployees && isProduction()) { + if (fbConfig.warnFBEmployees && isProduction()) { + isFBEmployee() + .then((isEmployee) => { + if (isEmployee) { notification.warning({ placement: 'bottomLeft', message: 'Please use Flipper@FB', @@ -144,11 +98,11 @@ export function SandyApp() { duration: null, }); } - } - }) - .catch((e) => { - console.warn('Failed to check if user is employee', e); - }); + }) + .catch((e) => { + console.warn('Failed to check if user is employee', e); + }); + } }, []); return ( diff --git a/desktop/flipper-ui-core/src/sandy-chrome/WelcomeScreen.tsx b/desktop/flipper-ui-core/src/sandy-chrome/WelcomeScreen.tsx index eba8a1089..a4ac5a43f 100644 --- a/desktop/flipper-ui-core/src/sandy-chrome/WelcomeScreen.tsx +++ b/desktop/flipper-ui-core/src/sandy-chrome/WelcomeScreen.tsx @@ -160,7 +160,7 @@ function WelcomeScreenContent() { }} width={125} height={125} - src={isHeadlessBuild ? './icon.png' : './icon-rn-only.png'} + src={isHeadlessBuild ? './icon.png' : './icon.png'} preview={false} /> Welcome to Flipper diff --git a/desktop/scripts/build-release.tsx b/desktop/scripts/build-release.tsx index f8f97ceec..99047dc04 100755 --- a/desktop/scripts/build-release.tsx +++ b/desktop/scripts/build-release.tsx @@ -265,7 +265,6 @@ async function buildDist(buildFolder: string) { }, mac: { bundleVersion: FIX_RELEASE_VERSION, - icon: path.resolve(buildFolder, 'icon-rn-only.icns'), }, win: { signAndEditExecutable: !isFB, diff --git a/desktop/static/launcher.tsx b/desktop/static/launcher.tsx new file mode 100644 index 000000000..abc597f51 --- /dev/null +++ b/desktop/static/launcher.tsx @@ -0,0 +1,100 @@ +/** + * Copyright (c) Meta Platforms, Inc. and 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 fs from 'fs'; +import path from 'path'; +import {spawn} from 'child_process'; +import xdg from 'xdg-basedir'; +import mkdirp from 'mkdirp'; + +const isProduction = () => + !/node_modules[\\/]electron[\\/]/.test(process.execPath); + +const isLauncherInstalled = async () => { + if (os.type() == 'Darwin') { + const receipt = 'com.facebook.flipper.launcher'; + const plistLocation = '/Applications/Flipper.app/Contents/Info.plist'; + try { + return ( + (await fs.promises.stat(plistLocation)) && + (await fs.promises.readFile(plistLocation)).indexOf(receipt) > 0 + ); + } catch (e) { + console.error('Error while reading Info.plist', e); + return false; + } + } + + return false; +}; + +const startLauncher = (argv: {file?: string; url?: string}) => { + const args = []; + if (argv.file) { + args.push('--file', argv.file); + } + if (argv.url) { + args.push('--url', argv.url); + } + if (os.type() == 'Darwin') { + spawn('open', ['/Applications/Flipper.app', '--args'].concat(args)); + } +}; + +const checkIsCycle = async () => { + const dir = path.join(xdg.cache!, 'flipper'); + const filePath = path.join(dir, 'last-launcher-run'); + // This isn't monotonically increasing, so there's a change we get time drift + // between the checks, but the worst case here is that we do two roundtrips + // before this check works. + const rightNow = Date.now(); + + let backThen; + try { + backThen = parseInt((await fs.promises.readFile(filePath)).toString(), 10); + } catch (e) { + backThen = 0; + } + + const delta = rightNow - backThen; + await mkdirp(dir); + await fs.promises.writeFile(filePath, '' + rightNow); + + // If the last startup was less than 5s ago, something's not okay. + return Math.abs(delta) < 5000; +}; + +/** + * Runs the launcher if required and returns a boolean based on whether + * it has. You should shut down this instance of the app in that case. + */ +export default async function delegateToLauncher(argv: { + launcher: boolean; + file?: string; + url?: string; +}) { + if (argv.launcher && isProduction() && (await isLauncherInstalled())) { + if (await checkIsCycle()) { + console.error( + 'Launcher cycle detected. Not delegating even though I usually would.', + ); + return false; + } + + console.warn('Delegating to Flipper Launcher ...'); + console.warn( + `You can disable this behavior by passing '--no-launcher' at startup.`, + ); + startLauncher(argv); + return true; + } + + return false; +} diff --git a/desktop/static/main.tsx b/desktop/static/main.tsx index 96d34c4ef..6b872f636 100644 --- a/desktop/static/main.tsx +++ b/desktop/static/main.tsx @@ -28,6 +28,7 @@ import fixPath from 'fix-path'; import {exec} from 'child_process'; import setup, {Config, configPath} from './setup'; import isFB from './fb-stubs/isFB'; +import delegateToLauncher from './launcher'; import yargs from 'yargs'; import {promisify} from 'util'; import process from 'process'; @@ -173,50 +174,58 @@ app.on('ready', async () => { const config = await setup(argv); processConfig(config); - appReady = true; - - app.commandLine.appendSwitch('scroll-bounce'); - configureSession(); - createWindow(config); - - // if in development install the react devtools extension - if (process.env.NODE_ENV === 'development') { - const { - default: installExtension, - REACT_DEVELOPER_TOOLS, - } = require('electron-devtools-installer'); - // if set, try to download a newever version of the dev tools - const forceDownload = process.env.FLIPPER_UPDATE_DEV_TOOLS === 'true'; - if (forceDownload) { - console.log('Force updating DevTools'); - } - // React - // Fix for extension loading (see D27685981) - // Work around per https://github.com/electron/electron/issues/23662#issuecomment-787420799 - const reactDevToolsPath = `${os.homedir()}/Library/Application Support/Electron/extensions/${ - REACT_DEVELOPER_TOOLS.id - }`; - if (await promisify(fs.exists)(reactDevToolsPath)) { - console.log('Loading React devtools from disk ' + reactDevToolsPath); - try { - await session.defaultSession.loadExtension( - reactDevToolsPath, - // @ts-ignore only supported (and needed) in Electron 12 - {allowFileAccess: true}, - ); - } catch (e) { - console.error('Failed to load React devtools from disk: ', e); + // If we delegate to the launcher, shut down this instance of the app. + delegateToLauncher(argv) + .then(async (hasLauncherInvoked: boolean) => { + if (hasLauncherInvoked) { + app.quit(); + return; } - } else { - try { - await installExtension(REACT_DEVELOPER_TOOLS.id, { - loadExtensionOptions: {allowFileAccess: true, forceDownload}, - }); - } catch (e) { - console.error('Failed to install React devtools extension', e); + appReady = true; + app.commandLine.appendSwitch('scroll-bounce'); + configureSession(); + createWindow(config); + + // if in development install the react devtools extension + if (process.env.NODE_ENV === 'development') { + const { + default: installExtension, + REACT_DEVELOPER_TOOLS, + } = require('electron-devtools-installer'); + // if set, try to download a newever version of the dev tools + const forceDownload = process.env.FLIPPER_UPDATE_DEV_TOOLS === 'true'; + if (forceDownload) { + console.log('Force updating DevTools'); + } + // React + // Fix for extension loading (see D27685981) + // Work around per https://github.com/electron/electron/issues/23662#issuecomment-787420799 + const reactDevToolsPath = `${os.homedir()}/Library/Application Support/Electron/extensions/${ + REACT_DEVELOPER_TOOLS.id + }`; + if (await promisify(fs.exists)(reactDevToolsPath)) { + console.log('Loading React devtools from disk ' + reactDevToolsPath); + try { + await session.defaultSession.loadExtension( + reactDevToolsPath, + // @ts-ignore only supported (and needed) in Electron 12 + {allowFileAccess: true}, + ); + } catch (e) { + console.error('Failed to load React devtools from disk: ', e); + } + } else { + try { + await installExtension(REACT_DEVELOPER_TOOLS.id, { + loadExtensionOptions: {allowFileAccess: true, forceDownload}, + }); + } catch (e) { + console.error('Failed to install React devtools extension', e); + } + } } - } - } + }) + .catch((e: any) => console.error('Error while delegating app launch', e)); }); app.on('web-contents-created', (_event, contents) => {