move plugin management from ui-core to server-core

Summary:
Follow up of D32665064, this diff moves all plugin management logic from flipper-ui to flipper-server. Things like downloading, installing, querying new plugins.

Loading plugins is handled separately in the next diff.

Reviewed By: nikoant

Differential Revision: D32666537

fbshipit-source-id: 9786b82987f00180bb26200e38735b334dc4d5c3
This commit is contained in:
Michel Weststrate
2021-12-08 04:25:28 -08:00
committed by Facebook GitHub Bot
parent f9b72ac69e
commit 64747dc417
25 changed files with 441 additions and 276 deletions

View File

@@ -7,15 +7,7 @@
* @format
*/
import {
getInstalledPluginDetails,
getPluginVersionInstallationDir,
installPluginFromFile,
} from 'flipper-plugin-lib';
import {
InstalledPluginDetails,
DownloadablePluginDetails,
} from 'flipper-common';
import {DownloadablePluginDetails} from 'flipper-common';
import {State, Store} from '../reducers/index';
import {
PluginDownloadStatus,
@@ -23,25 +15,12 @@ import {
pluginDownloadFinished,
} from '../reducers/pluginDownloads';
import {sideEffect} from '../utils/sideEffect';
import {default as axios} from 'axios';
import fs from 'fs-extra';
import path from 'path';
import tmp from 'tmp';
import {promisify} from 'util';
import {reportPlatformFailures, reportUsage} from 'flipper-common';
import {loadPlugin} from '../reducers/pluginManager';
import {showErrorNotification} from '../utils/notifications';
import {pluginInstalled} from '../reducers/plugins';
import {getAllClients} from '../reducers/connections';
// Adapter which forces node.js implementation for axios instead of browser implementation
// used by default in Electron. Node.js implementation is better, because it
// supports streams which can be used for direct downloading to disk.
const axiosHttpAdapter = require('axios/lib/adapters/http'); // eslint-disable-line import/no-commonjs
const getTempDirName = promisify(tmp.dir) as (
options?: tmp.DirOptions,
) => Promise<string>;
import {getRenderHostInstance} from '../RenderHost';
export default (store: Store) => {
sideEffect(
@@ -79,61 +58,18 @@ async function handlePluginDownload(
startedByUser: boolean,
store: Store,
) {
const {title, version, downloadUrl} = plugin;
const dispatch = store.dispatch;
const {name, title, version, downloadUrl} = plugin;
const installationDir = getPluginVersionInstallationDir(name, version);
console.log(
`Downloading plugin "${title}" v${version} from "${downloadUrl}" to "${installationDir}".`,
`Downloading plugin "${title}" v${version} from "${downloadUrl}".`,
);
const tmpDir = await getTempDirName();
const tmpFile = path.join(tmpDir, `${name}-${version}.tgz`);
let installedPlugin: InstalledPluginDetails | undefined;
try {
const cancelationSource = axios.CancelToken.source();
dispatch(pluginDownloadStarted({plugin, cancel: cancelationSource.cancel}));
if (await fs.pathExists(installationDir)) {
console.log(
`Using existing files instead of downloading plugin "${title}" v${version} from "${downloadUrl}" to "${installationDir}"`,
);
installedPlugin = await getInstalledPluginDetails(installationDir);
} else {
await fs.ensureDir(tmpDir);
let percentCompleted = 0;
const response = await axios.get(plugin.downloadUrl, {
adapter: axiosHttpAdapter,
cancelToken: cancelationSource.token,
responseType: 'stream',
headers: {
'Sec-Fetch-Site': 'none',
'Sec-Fetch-Mode': 'navigate',
},
onDownloadProgress: async (progressEvent) => {
const newPercentCompleted = !progressEvent.total
? 0
: Math.round((progressEvent.loaded * 100) / progressEvent.total);
if (newPercentCompleted - percentCompleted >= 20) {
percentCompleted = newPercentCompleted;
console.log(
`Downloading plugin "${title}" v${version} from "${downloadUrl}": ${percentCompleted}% completed (${progressEvent.loaded} from ${progressEvent.total})`,
);
}
},
});
if (response.headers['content-type'] !== 'application/octet-stream') {
throw new Error(
`It looks like you are not on VPN/Lighthouse. Unexpected content type received: ${response.headers['content-type']}.`,
);
}
const responseStream = response.data as fs.ReadStream;
const writeStream = responseStream.pipe(
fs.createWriteStream(tmpFile, {autoClose: true}),
);
await new Promise((resolve, reject) =>
writeStream.once('finish', resolve).once('error', reject),
);
installedPlugin = await installPluginFromFile(tmpFile);
dispatch(pluginInstalled(installedPlugin));
}
dispatch(pluginDownloadStarted({plugin}));
const installedPlugin = await getRenderHostInstance().flipperServer!.exec(
'plugin-start-download',
plugin,
);
dispatch(pluginInstalled(installedPlugin));
if (pluginIsDisabledForAllConnectedClients(store.getState(), plugin)) {
dispatch(
loadPlugin({
@@ -144,11 +80,11 @@ async function handlePluginDownload(
);
}
console.log(
`Successfully downloaded and installed plugin "${title}" v${version} from "${downloadUrl}" to "${installationDir}".`,
`Successfully downloaded and installed plugin "${title}" v${version} from "${downloadUrl}".`,
);
} catch (error) {
console.error(
`Failed to download plugin "${title}" v${version} from "${downloadUrl}" to "${installationDir}".`,
`Failed to download plugin "${title}" v${version} from "${downloadUrl}".`,
error,
);
if (startedByUser) {
@@ -160,7 +96,6 @@ async function handlePluginDownload(
throw error;
} finally {
dispatch(pluginDownloadFinished({plugin}));
await fs.remove(tmpDir);
}
}