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();
|
jest.runAllTimers();
|
||||||
expect(linksSeen).toEqual(['universe']);
|
expect(linksSeen).toEqual(['universe']);
|
||||||
expect(renderer.baseElement).toMatchInlineSnapshot(`
|
expect(renderer.baseElement).toMatchInlineSnapshot(`
|
||||||
<body
|
<body>
|
||||||
class=""
|
|
||||||
style=""
|
|
||||||
>
|
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="css-1x2cmzz-SandySplitContainer e1hsqii10"
|
class="css-1x2cmzz-SandySplitContainer e1hsqii10"
|
||||||
|
|||||||
@@ -7,6 +7,10 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export async function loadPluginsFromMarketplace() {
|
||||||
|
// Marketplace is not implemented in public version of Flipper
|
||||||
|
}
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
// Marketplace is not implemented in public version of Flipper
|
// Marketplace is not implemented in public version of Flipper
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {Dialog, getFlipperLib} from 'flipper-plugin';
|
import {Dialog, getFlipperLib} from 'flipper-plugin';
|
||||||
import {getUser} from '../fb-stubs/user';
|
import {getUser} from '../fb-stubs/user';
|
||||||
import {Store} from '../reducers/index';
|
import {State, Store} from '../reducers/index';
|
||||||
import {checkForUpdate} from '../fb-stubs/checkForUpdate';
|
import {checkForUpdate} from '../fb-stubs/checkForUpdate';
|
||||||
import {getAppVersion} from '../utils/info';
|
import {getAppVersion} from '../utils/info';
|
||||||
import {ACTIVE_SHEET_SIGN_IN, setActiveSheet} from '../reducers/application';
|
import {ACTIVE_SHEET_SIGN_IN, setActiveSheet} from '../reducers/application';
|
||||||
@@ -18,6 +18,12 @@ import {UserNotSignedInError} from '../utils/errors';
|
|||||||
import {selectPlugin} from '../reducers/connections';
|
import {selectPlugin} from '../reducers/connections';
|
||||||
import {getUpdateAvailableMessage} from '../chrome/UpdateIndicator';
|
import {getUpdateAvailableMessage} from '../chrome/UpdateIndicator';
|
||||||
import {Typography} from 'antd';
|
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 = {
|
type OpenPluginParams = {
|
||||||
pluginId: string;
|
pluginId: string;
|
||||||
@@ -51,7 +57,9 @@ export async function handleOpenPluginDeeplink(store: Store, query: string) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await verifyFlipperIsUpToDate(title);
|
await verifyFlipperIsUpToDate(title);
|
||||||
// await verifyPluginInstalled();
|
if (!(await verifyPluginStatus(store, params.pluginId, title))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// await verifyDevices();
|
// await verifyDevices();
|
||||||
// await verifyClient();
|
// await verifyClient();
|
||||||
// await verifyPluginEnabled();
|
// await verifyPluginEnabled();
|
||||||
@@ -128,9 +136,17 @@ async function showPleaseLoginDialog(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function waitForLogin(store: Store) {
|
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) => {
|
return new Promise<void>((resolve) => {
|
||||||
const unsub = store.subscribe(() => {
|
const unsub = store.subscribe(() => {
|
||||||
if (store.getState().user?.id) {
|
if (predicate(store.getState())) {
|
||||||
unsub();
|
unsub();
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
@@ -139,6 +155,9 @@ async function waitForLogin(store: Store) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function verifyFlipperIsUpToDate(title: string) {
|
async function verifyFlipperIsUpToDate(title: string) {
|
||||||
|
if (!isProduction() || isTest()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const currentVersion = getAppVersion();
|
const currentVersion = getAppVersion();
|
||||||
const handle = Dialog.loading({
|
const handle = Dialog.loading({
|
||||||
title,
|
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) {
|
function openPlugin(store: Store, params: OpenPluginParams) {
|
||||||
store.dispatch(
|
store.dispatch(
|
||||||
selectPlugin({
|
selectPlugin({
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type {PluginDefinition} from '../plugin';
|
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 {State as PluginsState} from '../reducers/plugins';
|
||||||
import type BaseDevice from '../server/devices/BaseDevice';
|
import type BaseDevice from '../server/devices/BaseDevice';
|
||||||
import type Client from '../Client';
|
import type Client from '../Client';
|
||||||
@@ -401,3 +401,39 @@ export function computeActivePluginList({
|
|||||||
}
|
}
|
||||||
return pluginList;
|
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