Files
flipper/desktop/app/src/electron/initializeElectron.tsx
Lorenzo Blasa ff6f98fc0d Import File implementation
Summary: Implementation was missing for the browser. This provides a default implementation.

Reviewed By: aigoncharov

Differential Revision: D48311198

fbshipit-source-id: fd067600f571234e0fbccfb90853b62f175ff8fb
2023-08-14 11:33:06 -07:00

238 lines
7.0 KiB
TypeScript

/**
* 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 path from 'path';
import {
_NuxManagerContext,
_createNuxManager,
_setGlobalInteractionReporter,
_LoggerContext,
} from 'flipper-plugin';
// eslint-disable-next-line no-restricted-imports,flipper/no-electron-remote-imports
import {ipcRenderer, SaveDialogReturnValue, clipboard, shell} from 'electron';
import fs from 'fs';
import {setupMenuBarTracking} from './setupMenuBar';
import {FlipperServer, FlipperServerConfig} from 'flipper-common';
import type {Icon, RenderHost} from 'flipper-ui-core';
import {getLocalIconUrl} from '../utils/icons';
import {getCPUUsage} from 'process';
import {ElectronIpcClientRenderer} from '../electronIpc';
export async function initializeElectron(
flipperServer: FlipperServer,
flipperServerConfig: FlipperServerConfig,
electronIpcClient: ElectronIpcClientRenderer,
) {
const [electronProcess, electronTheme] = await Promise.all([
electronIpcClient.send('getProcess'),
electronIpcClient.send('getNativeTheme'),
]);
const execPath = process.execPath || electronProcess.execPath;
const isProduction = !/node_modules[\\/]electron[\\/]/.test(execPath);
setupMenuBarTracking(electronIpcClient);
function restart(update: boolean = false) {
if (isProduction) {
if (update) {
const options = {
args: process.argv
.splice(0, 1)
.filter((arg) => arg !== '--no-launcher' && arg !== '--no-updater'),
};
electronIpcClient.send('relaunch', options);
} else {
electronIpcClient.send('relaunch');
}
electronIpcClient.send('exit');
} else {
// Relaunching the process with the standard way doesn't work in dev mode.
// So instead we're sending a signal to dev server to kill the current instance of electron and launch new.
fetch(`${flipperServerConfig.env.DEV_SERVER_URL}/_restartElectron`, {
method: 'POST',
});
}
}
FlipperRenderHostInstance = {
processId: electronProcess.pid,
isProduction,
readTextFromClipboard() {
return Promise.resolve(clipboard.readText());
},
writeTextToClipboard(text: string) {
clipboard.writeText(text);
},
async showSaveDialog(options) {
return (await electronIpcClient.send('showSaveDialog', options))
?.filePath;
},
async showOpenDialog({filter, defaultPath}) {
const result = await electronIpcClient.send('showOpenDialog', {
defaultPath,
properties: ['openFile'],
filters: filter ? [filter] : undefined,
});
return result.filePaths?.[0];
},
showSelectDirectoryDialog(defaultPath = path.resolve('/')) {
return electronIpcClient
.send('showOpenDialog', {
properties: ['openDirectory'],
defaultPath,
})
.then((result: SaveDialogReturnValue & {filePaths: string[]}) => {
if (result.filePath) {
return result.filePath.toString();
}
// Electron typings seem of here, just in case,
// (can be tested with settings dialog)
// handle both situations
if (result.filePaths) {
return result.filePaths[0];
}
return undefined;
});
},
importFile: (async ({
defaultPath,
extensions,
title,
encoding = 'utf-8',
multi,
} = {}) => {
let {filePaths} = await electronIpcClient.send('showOpenDialog', {
defaultPath,
properties: [
'openFile',
...(multi ? (['multiSelections'] as const) : []),
],
filters: extensions ? [{extensions, name: ''}] : undefined,
title,
});
if (!filePaths.length) {
return;
}
if (!multi) {
filePaths = [filePaths[0]];
}
const descriptors = await Promise.all(
filePaths.map(async (filePath) => {
const fileName = path.basename(filePath);
const data = await fs.promises.readFile(filePath, {encoding});
return {
data,
name: fileName,
};
}),
);
return multi ? descriptors : descriptors[0];
}) as RenderHost['importFile'],
async exportFile(data, {defaultPath, encoding = 'utf-8'} = {}) {
const {filePath} = await electronIpcClient.send('showSaveDialog', {
defaultPath,
});
if (!filePath) {
return;
}
await fs.promises.writeFile(filePath, data, {encoding});
return filePath;
},
async exportFileBinary(data, {defaultPath} = {}) {
const {filePath} = await electronIpcClient.send('showSaveDialog', {
defaultPath,
});
if (!filePath) {
return;
}
await fs.promises.writeFile(filePath, data, {encoding: 'binary'});
return filePath;
},
openLink(url: string) {
shell.openExternal(url);
},
hasFocus() {
// eslint-disable-next-line node/no-sync
return electronIpcClient.sendSync('getCurrentWindowState').isFocused;
},
onIpcEvent(event, callback) {
ipcRenderer.on(event, (_ev, ...args: any[]) => {
callback(...(args as any));
});
},
sendIpcEvent(event, ...args: any[]) {
ipcRenderer.send(event, ...args);
},
shouldUseDarkColors() {
return electronTheme.shouldUseDarkColors;
},
restartFlipper(update: boolean = false) {
restart(update);
},
serverConfig: flipperServerConfig,
GK(gatekeeper) {
return flipperServerConfig.gatekeepers[gatekeeper] ?? false;
},
flipperServer,
async requirePlugin(path): Promise<{plugin: any; css?: string}> {
const plugin = electronRequire(path);
/**
* Check if the plugin includes a bundled css. If so,
* load its content too.
*/
const idx = path.lastIndexOf('.');
const cssPath = path.substring(0, idx < 0 ? path.length : idx) + '.css';
try {
await fs.promises.access(cssPath);
const buffer = await fs.promises.readFile(cssPath, {encoding: 'utf-8'});
const css = buffer.toString();
return {plugin, css};
} catch (e) {}
return {plugin};
},
getStaticResourceUrl(relativePath): string {
return (
'file://' +
path.resolve(flipperServerConfig.paths.staticPath, relativePath)
);
},
getLocalIconUrl(icon: Icon, url: string): string {
return getLocalIconUrl(
icon,
url,
flipperServerConfig.paths.staticPath,
!flipperServerConfig.environmentInfo.isProduction,
);
},
unloadModule(path: string) {
const resolvedPath = electronRequire.resolve(path);
if (!resolvedPath || !electronRequire.cache[resolvedPath]) {
return;
}
delete electronRequire.cache[resolvedPath];
},
getPercentCPUUsage() {
return getCPUUsage().percentCPUUsage;
},
} as RenderHost;
}