Check if plugin status before opening
Summary: This diff takes care of current plugin status when handling deeplinks. It checks: 1. if the plugin failed to load 2. if the plugin is behind GK 3. if the plugin is installable from bundle 4. if the plugin is installable from marketplace Reviewed By: passy Differential Revision: D29875483 fbshipit-source-id: 8dac1aab63822f43a0d002b10efa5b4a756fce41
This commit is contained in:
committed by
Facebook GitHub Bot
parent
2cb21f2a06
commit
bf65da0e72
@@ -92,10 +92,7 @@ test('Triggering a deeplink will work', async () => {
|
||||
jest.runAllTimers();
|
||||
expect(linksSeen).toEqual(['universe']);
|
||||
expect(renderer.baseElement).toMatchInlineSnapshot(`
|
||||
<body
|
||||
class=""
|
||||
style=""
|
||||
>
|
||||
<body>
|
||||
<div>
|
||||
<div
|
||||
class="css-1x2cmzz-SandySplitContainer e1hsqii10"
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
export async function loadPluginsFromMarketplace() {
|
||||
// Marketplace is not implemented in public version of Flipper
|
||||
}
|
||||
|
||||
export default () => {
|
||||
// Marketplace is not implemented in public version of Flipper
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
import React from 'react';
|
||||
import {Dialog, getFlipperLib} from 'flipper-plugin';
|
||||
import {getUser} from '../fb-stubs/user';
|
||||
import {Store} from '../reducers/index';
|
||||
import {State, Store} from '../reducers/index';
|
||||
import {checkForUpdate} from '../fb-stubs/checkForUpdate';
|
||||
import {getAppVersion} from '../utils/info';
|
||||
import {ACTIVE_SHEET_SIGN_IN, setActiveSheet} from '../reducers/application';
|
||||
@@ -18,6 +18,12 @@ import {UserNotSignedInError} from '../utils/errors';
|
||||
import {selectPlugin} from '../reducers/connections';
|
||||
import {getUpdateAvailableMessage} from '../chrome/UpdateIndicator';
|
||||
import {Typography} from 'antd';
|
||||
import {getPluginStatus} from '../utils/pluginUtils';
|
||||
import {loadPluginsFromMarketplace} from './fb-stubs/pluginMarketplace';
|
||||
import {loadPlugin} from '../reducers/pluginManager';
|
||||
import {startPluginDownload} from '../reducers/pluginDownloads';
|
||||
import isProduction, {isTest} from '../utils/isProduction';
|
||||
import restart from '../utils/restartFlipper';
|
||||
|
||||
type OpenPluginParams = {
|
||||
pluginId: string;
|
||||
@@ -51,7 +57,9 @@ export async function handleOpenPluginDeeplink(store: Store, query: string) {
|
||||
return;
|
||||
}
|
||||
await verifyFlipperIsUpToDate(title);
|
||||
// await verifyPluginInstalled();
|
||||
if (!(await verifyPluginStatus(store, params.pluginId, title))) {
|
||||
return;
|
||||
}
|
||||
// await verifyDevices();
|
||||
// await verifyClient();
|
||||
// await verifyPluginEnabled();
|
||||
@@ -128,9 +136,17 @@ async function showPleaseLoginDialog(
|
||||
}
|
||||
|
||||
async function waitForLogin(store: Store) {
|
||||
return waitFor(store, (state) => !!state.user?.id);
|
||||
}
|
||||
|
||||
// make this more reusable?
|
||||
function waitFor(
|
||||
store: Store,
|
||||
predicate: (state: State) => boolean,
|
||||
): Promise<void> {
|
||||
return new Promise<void>((resolve) => {
|
||||
const unsub = store.subscribe(() => {
|
||||
if (store.getState().user?.id) {
|
||||
if (predicate(store.getState())) {
|
||||
unsub();
|
||||
resolve();
|
||||
}
|
||||
@@ -139,6 +155,9 @@ async function waitForLogin(store: Store) {
|
||||
}
|
||||
|
||||
async function verifyFlipperIsUpToDate(title: string) {
|
||||
if (!isProduction() || isTest()) {
|
||||
return;
|
||||
}
|
||||
const currentVersion = getAppVersion();
|
||||
const handle = Dialog.loading({
|
||||
title,
|
||||
@@ -173,6 +192,139 @@ async function verifyFlipperIsUpToDate(title: string) {
|
||||
}
|
||||
}
|
||||
|
||||
async function verifyPluginStatus(
|
||||
store: Store,
|
||||
pluginId: string,
|
||||
title: string,
|
||||
): Promise<boolean> {
|
||||
// make sure we have marketplace plugin data present
|
||||
if (!isTest() && !store.getState().plugins.marketplacePlugins.length) {
|
||||
// plugins not yet fetched
|
||||
// updates plugins from marketplace (if logged in), and stores them
|
||||
await loadPluginsFromMarketplace();
|
||||
}
|
||||
// while true loop; after pressing install or add GK, we want to check again if plugin is available
|
||||
while (true) {
|
||||
const [status, reason] = getPluginStatus(store, pluginId);
|
||||
switch (status) {
|
||||
case 'ready':
|
||||
return true;
|
||||
case 'unknown':
|
||||
await Dialog.alert({
|
||||
type: 'warning',
|
||||
title,
|
||||
message: `No plugin with id '${pluginId}' is known to Flipper. Please correct the deeplink, or install the plugin from NPM using the plugin manager.`,
|
||||
});
|
||||
return false;
|
||||
case 'failed':
|
||||
await Dialog.alert({
|
||||
type: 'error',
|
||||
title,
|
||||
message: `We found plugin '${pluginId}', but failed to load it: ${reason}. Please check the logs for more details`,
|
||||
});
|
||||
return false;
|
||||
case 'gatekeeped':
|
||||
if (
|
||||
!(await Dialog.confirm({
|
||||
title,
|
||||
message: (
|
||||
<p>
|
||||
{`To use plugin '${pluginId}', it is necessary to be a member of the GK '${reason}'. Click `}
|
||||
<Typography.Link
|
||||
href={`https://www.internalfb.com/intern/gatekeeper/projects/${reason}`}>
|
||||
here
|
||||
</Typography.Link>{' '}
|
||||
to enroll, restart Flipper, and click the link again.
|
||||
</p>
|
||||
),
|
||||
okText: 'Restart',
|
||||
onConfirm: async () => {
|
||||
restart();
|
||||
// intentionally forever pending, we're restarting...
|
||||
return new Promise(() => {});
|
||||
},
|
||||
}))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 'bundle_installable': {
|
||||
// For convenience, don't ask user to install bundled plugins, handle it directly
|
||||
await installBundledPlugin(store, pluginId, title);
|
||||
break;
|
||||
}
|
||||
case 'marketplace_installable': {
|
||||
if (!(await installMarketPlacePlugin(store, pluginId, title))) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Error('Unhandled state: ' + status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function installBundledPlugin(
|
||||
store: Store,
|
||||
pluginId: string,
|
||||
title: string,
|
||||
) {
|
||||
const plugin = store.getState().plugins.bundledPlugins.get(pluginId);
|
||||
if (!plugin || !plugin.isBundled) {
|
||||
throw new Error(`Failed to find bundled plugin '${pluginId}'`);
|
||||
}
|
||||
const loadingDialog = Dialog.loading({
|
||||
title,
|
||||
message: `Loading plugin '${pluginId}'...`,
|
||||
});
|
||||
store.dispatch(loadPlugin({plugin, enable: true, notifyIfFailed: true}));
|
||||
try {
|
||||
await waitFor(
|
||||
store,
|
||||
() => getPluginStatus(store, pluginId)[0] !== 'bundle_installable',
|
||||
);
|
||||
} finally {
|
||||
loadingDialog.close();
|
||||
}
|
||||
}
|
||||
|
||||
async function installMarketPlacePlugin(
|
||||
store: Store,
|
||||
pluginId: string,
|
||||
title: string,
|
||||
): Promise<boolean> {
|
||||
if (
|
||||
!(await Dialog.confirm({
|
||||
title,
|
||||
message: `The requested plugin '${pluginId}' is currently not installed, but can be downloaded from the Flipper plugin Marketplace. If you trust the source of the current link, press 'Install' to continue`,
|
||||
okText: 'Install',
|
||||
}))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
const plugin = store
|
||||
.getState()
|
||||
.plugins.marketplacePlugins.find((p) => p.id === pluginId);
|
||||
if (!plugin) {
|
||||
throw new Error(`Failed to find marketplace plugin '${pluginId}'`);
|
||||
}
|
||||
const loadingDialog = Dialog.loading({
|
||||
title,
|
||||
message: `Installing plugin '${pluginId}'...`,
|
||||
});
|
||||
try {
|
||||
store.dispatch(startPluginDownload({plugin, startedByUser: true}));
|
||||
await waitFor(
|
||||
store,
|
||||
() => getPluginStatus(store, pluginId)[0] !== 'marketplace_installable',
|
||||
);
|
||||
} finally {
|
||||
loadingDialog.close();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function openPlugin(store: Store, params: OpenPluginParams) {
|
||||
store.dispatch(
|
||||
selectPlugin({
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
import type {PluginDefinition} from '../plugin';
|
||||
import type {State} from '../reducers';
|
||||
import type {State, Store} from '../reducers';
|
||||
import type {State as PluginsState} from '../reducers/plugins';
|
||||
import type BaseDevice from '../server/devices/BaseDevice';
|
||||
import type Client from '../Client';
|
||||
@@ -401,3 +401,39 @@ export function computeActivePluginList({
|
||||
}
|
||||
return pluginList;
|
||||
}
|
||||
|
||||
export function getPluginStatus(
|
||||
store: Store,
|
||||
id: string,
|
||||
): [
|
||||
state:
|
||||
| 'ready'
|
||||
| 'unknown'
|
||||
| 'failed'
|
||||
| 'gatekeeped'
|
||||
| 'bundle_installable'
|
||||
| 'marketplace_installable',
|
||||
reason?: string,
|
||||
] {
|
||||
const state: PluginsState = store.getState().plugins;
|
||||
if (state.devicePlugins.has(id) || state.clientPlugins.has(id)) {
|
||||
return ['ready'];
|
||||
}
|
||||
const gateKeepedDetails = state.gatekeepedPlugins.find((d) => d.id === id);
|
||||
if (gateKeepedDetails) {
|
||||
return ['gatekeeped', gateKeepedDetails.gatekeeper];
|
||||
}
|
||||
const failedPluginEntry = state.failedPlugins.find(
|
||||
([details]) => details.id === id,
|
||||
);
|
||||
if (failedPluginEntry) {
|
||||
return ['failed', failedPluginEntry[1]];
|
||||
}
|
||||
if (state.bundledPlugins.has(id)) {
|
||||
return ['bundle_installable'];
|
||||
}
|
||||
if (state.marketplacePlugins.find((d) => d.id === id)) {
|
||||
return ['marketplace_installable'];
|
||||
}
|
||||
return ['unknown'];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user