Make plugin loading async
Summary: This diff makes plugin loading async, which we'd need in a browser env (either because we'd use `import()` or we need to fetch the source and than eval it), and deals with all the fallout of that Reviewed By: timur-valiev Differential Revision: D32669995 fbshipit-source-id: 73babf38a6757c451b8200c3b320409f127b8b5b
This commit is contained in:
committed by
Facebook GitHub Bot
parent
64747dc417
commit
de59bbedd2
@@ -177,6 +177,9 @@ export function initializeElectron(
|
|||||||
return flipperServerConfig.gatekeepers[gatekeeper] ?? false;
|
return flipperServerConfig.gatekeepers[gatekeeper] ?? false;
|
||||||
},
|
},
|
||||||
flipperServer,
|
flipperServer,
|
||||||
|
async requirePlugin(path) {
|
||||||
|
return (window as any).electronRequire(path);
|
||||||
|
},
|
||||||
} as RenderHost;
|
} as RenderHost;
|
||||||
|
|
||||||
setupMenuBar();
|
setupMenuBar();
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export {
|
|||||||
reportPlatformFailures,
|
reportPlatformFailures,
|
||||||
reportUsage,
|
reportUsage,
|
||||||
reportPluginFailures,
|
reportPluginFailures,
|
||||||
|
tryCatchReportPluginFailuresAsync,
|
||||||
tryCatchReportPlatformFailures,
|
tryCatchReportPlatformFailures,
|
||||||
tryCatchReportPluginFailures,
|
tryCatchReportPluginFailures,
|
||||||
UnsupportedError,
|
UnsupportedError,
|
||||||
|
|||||||
@@ -169,6 +169,7 @@ export type FlipperServerCommands = {
|
|||||||
'plugin-start-download': (
|
'plugin-start-download': (
|
||||||
plugin: DownloadablePluginDetails,
|
plugin: DownloadablePluginDetails,
|
||||||
) => Promise<InstalledPluginDetails>;
|
) => Promise<InstalledPluginDetails>;
|
||||||
|
'plugin-source': (path: string) => Promise<string>;
|
||||||
'plugins-install-from-npm': (name: string) => Promise<InstalledPluginDetails>;
|
'plugins-install-from-npm': (name: string) => Promise<InstalledPluginDetails>;
|
||||||
'plugins-install-from-file': (
|
'plugins-install-from-file': (
|
||||||
path: string,
|
path: string,
|
||||||
|
|||||||
@@ -134,6 +134,29 @@ export function tryCatchReportPluginFailures<T>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wraps a closure, preserving it's functionality but logging the success or
|
||||||
|
failure state of it.
|
||||||
|
*/
|
||||||
|
export async function tryCatchReportPluginFailuresAsync<T>(
|
||||||
|
closure: () => Promise<T>,
|
||||||
|
name: string,
|
||||||
|
plugin: string,
|
||||||
|
): Promise<T> {
|
||||||
|
try {
|
||||||
|
const result = await closure();
|
||||||
|
logPluginSuccessRate(name, plugin, {kind: 'success'});
|
||||||
|
return result;
|
||||||
|
} catch (e) {
|
||||||
|
logPluginSuccessRate(name, plugin, {
|
||||||
|
kind: 'failure',
|
||||||
|
supportedOperation: !(e instanceof UnsupportedError),
|
||||||
|
error: e,
|
||||||
|
});
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Track usage of a feature.
|
* Track usage of a feature.
|
||||||
* @param action Unique name for the action performed. E.g. captureScreenshot
|
* @param action Unique name for the action performed. E.g. captureScreenshot
|
||||||
|
|||||||
@@ -268,6 +268,7 @@ export class FlipperServerImpl implements FlipperServer {
|
|||||||
this.pluginManager.installPluginFromFile(path),
|
this.pluginManager.installPluginFromFile(path),
|
||||||
'plugins-install-from-npm': (name) =>
|
'plugins-install-from-npm': (name) =>
|
||||||
this.pluginManager.installPluginFromNpm(name),
|
this.pluginManager.installPluginFromNpm(name),
|
||||||
|
'plugin-source': (path) => this.pluginManager.loadSource(path),
|
||||||
};
|
};
|
||||||
|
|
||||||
registerDevice(device: ServerDevice) {
|
registerDevice(device: ServerDevice) {
|
||||||
|
|||||||
@@ -61,6 +61,10 @@ export class PluginManager {
|
|||||||
installPluginFromFile = installPluginFromFile;
|
installPluginFromFile = installPluginFromFile;
|
||||||
installPluginFromNpm = installPluginFromNpm;
|
installPluginFromNpm = installPluginFromNpm;
|
||||||
|
|
||||||
|
async loadSource(path: string) {
|
||||||
|
return await fs.readFile(path, 'utf8');
|
||||||
|
}
|
||||||
|
|
||||||
async getBundledPlugins(): Promise<Array<BundledPluginDetails>> {
|
async getBundledPlugins(): Promise<Array<BundledPluginDetails>> {
|
||||||
if (process.env.NODE_ENV === 'test') {
|
if (process.env.NODE_ENV === 'test') {
|
||||||
return [];
|
return [];
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {FlipperServer, FlipperServerConfig} from 'flipper-common';
|
import {FlipperServer, FlipperServerConfig} from 'flipper-common';
|
||||||
|
import {getRenderHostInstance} from 'flipper-ui-core';
|
||||||
|
|
||||||
export function initializeRenderHost(
|
export function initializeRenderHost(
|
||||||
flipperServer: FlipperServer,
|
flipperServer: FlipperServer,
|
||||||
@@ -62,6 +63,15 @@ export function initializeRenderHost(
|
|||||||
return flipperServerConfig.gatekeepers[gatekeeper] ?? false;
|
return flipperServerConfig.gatekeepers[gatekeeper] ?? false;
|
||||||
},
|
},
|
||||||
flipperServer,
|
flipperServer,
|
||||||
|
async requirePlugin(path) {
|
||||||
|
// TODO: use `await import(path)`?
|
||||||
|
const source = await getRenderHostInstance().flipperServer.exec(
|
||||||
|
'plugin-source',
|
||||||
|
path,
|
||||||
|
);
|
||||||
|
// eslint-disable-next-line no-eval
|
||||||
|
return eval(source);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -242,6 +242,10 @@ class PluginContainer extends PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderPluginInfo() {
|
renderPluginInfo() {
|
||||||
|
if (isTest()) {
|
||||||
|
// Plugin info uses Antd animations, generating a gazillion warnings
|
||||||
|
return 'Stubbed plugin info';
|
||||||
|
}
|
||||||
return <PluginInfo />;
|
return <PluginInfo />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ export interface RenderHost {
|
|||||||
GK(gatekeeper: string): boolean;
|
GK(gatekeeper: string): boolean;
|
||||||
flipperServer: FlipperServer;
|
flipperServer: FlipperServer;
|
||||||
serverConfig: FlipperServerConfig;
|
serverConfig: FlipperServerConfig;
|
||||||
|
requirePlugin(path: string): Promise<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRenderHostInstance(): RenderHost {
|
export function getRenderHostInstance(): RenderHost {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
jest.useFakeTimers();
|
// jest.useFakeTimers();
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import produce from 'immer';
|
import produce from 'immer';
|
||||||
@@ -22,10 +22,12 @@ import {
|
|||||||
DevicePluginClient,
|
DevicePluginClient,
|
||||||
DeviceLogEntry,
|
DeviceLogEntry,
|
||||||
useValue,
|
useValue,
|
||||||
|
sleep,
|
||||||
} from 'flipper-plugin';
|
} from 'flipper-plugin';
|
||||||
import {selectPlugin} from '../reducers/connections';
|
import {selectPlugin} from '../reducers/connections';
|
||||||
import {updateSettings} from '../reducers/settings';
|
import {updateSettings} from '../reducers/settings';
|
||||||
import {switchPlugin} from '../reducers/pluginManager';
|
import {switchPlugin} from '../reducers/pluginManager';
|
||||||
|
import {awaitPluginCommandQueueEmpty} from '../dispatcher/pluginManager';
|
||||||
|
|
||||||
interface PersistedState {
|
interface PersistedState {
|
||||||
count: 1;
|
count: 1;
|
||||||
@@ -57,7 +59,7 @@ class TestPlugin extends FlipperPlugin<any, any, any> {
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<h1>
|
<h1>
|
||||||
Hello:{' '}
|
<span>Hello</span>
|
||||||
<span data-testid="counter">{this.props.persistedState.count}</span>
|
<span data-testid="counter">{this.props.persistedState.count}</span>
|
||||||
</h1>
|
</h1>
|
||||||
);
|
);
|
||||||
@@ -82,8 +84,9 @@ test('Plugin container can render plugin and receive updates', async () => {
|
|||||||
class="css-1woty6b-Container"
|
class="css-1woty6b-Container"
|
||||||
>
|
>
|
||||||
<h1>
|
<h1>
|
||||||
Hello:
|
<span>
|
||||||
|
Hello
|
||||||
|
</span>
|
||||||
<span
|
<span
|
||||||
data-testid="counter"
|
data-testid="counter"
|
||||||
>
|
>
|
||||||
@@ -348,19 +351,21 @@ test('PluginContainer can render Sandy plugins', async () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// note: this is the old pluginInstance, so that one is not reconnected!
|
// note: this is the old pluginInstance, so that one is not reconnected!
|
||||||
expect(pluginInstance.connectedStub).toBeCalledTimes(2);
|
expect(pluginInstance.connectedStub).toBeCalledTimes(2);
|
||||||
expect(pluginInstance.disconnectedStub).toBeCalledTimes(2);
|
expect(pluginInstance.disconnectedStub).toBeCalledTimes(2);
|
||||||
expect(pluginInstance.activatedStub).toBeCalledTimes(2);
|
expect(pluginInstance.activatedStub).toBeCalledTimes(2);
|
||||||
expect(pluginInstance.deactivatedStub).toBeCalledTimes(2);
|
expect(pluginInstance.deactivatedStub).toBeCalledTimes(2);
|
||||||
|
|
||||||
expect(
|
await awaitPluginCommandQueueEmpty(store);
|
||||||
client.sandyPluginStates.get('TestPlugin')!.instanceApi.connectedStub,
|
await sleep(10);
|
||||||
).toBeCalledTimes(1);
|
const newPluginInstance =
|
||||||
|
client.sandyPluginStates.get('TestPlugin')!.instanceApi;
|
||||||
|
expect(newPluginInstance).not.toBe(pluginInstance);
|
||||||
|
expect(newPluginInstance.connectedStub).toBeCalledTimes(1);
|
||||||
expect(client.rawSend).toBeCalledWith('init', {plugin: 'TestPlugin'});
|
expect(client.rawSend).toBeCalledWith('init', {plugin: 'TestPlugin'});
|
||||||
expect(
|
expect(newPluginInstance.count.get()).toBe(0);
|
||||||
client.sandyPluginStates.get('TestPlugin')!.instanceApi.count.get(),
|
|
||||||
).toBe(0);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('PluginContainer triggers correct lifecycles for background plugin', async () => {
|
test('PluginContainer triggers correct lifecycles for background plugin', async () => {
|
||||||
@@ -478,6 +483,9 @@ test('PluginContainer triggers correct lifecycles for background plugin', async
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await awaitPluginCommandQueueEmpty(store);
|
||||||
|
|
||||||
// note: this is the old pluginInstance, so that one is not reconnected!
|
// note: this is the old pluginInstance, so that one is not reconnected!
|
||||||
expect(pluginInstance.connectedStub).toBeCalledTimes(1);
|
expect(pluginInstance.connectedStub).toBeCalledTimes(1);
|
||||||
expect(pluginInstance.disconnectedStub).toBeCalledTimes(1);
|
expect(pluginInstance.disconnectedStub).toBeCalledTimes(1);
|
||||||
@@ -533,7 +541,12 @@ test('PluginContainer + Sandy plugin supports deeplink', async () => {
|
|||||||
Component() {
|
Component() {
|
||||||
const instance = usePlugin(plugin);
|
const instance = usePlugin(plugin);
|
||||||
const linkState = useValue(instance.linkState);
|
const linkState = useValue(instance.linkState);
|
||||||
return <h1>hello {linkState || 'world'}</h1>;
|
return (
|
||||||
|
<h1>
|
||||||
|
<span>hello</span>
|
||||||
|
<span>{linkState || 'world'}</span>
|
||||||
|
</h1>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -558,8 +571,12 @@ test('PluginContainer + Sandy plugin supports deeplink', async () => {
|
|||||||
class="css-1woty6b-Container"
|
class="css-1woty6b-Container"
|
||||||
>
|
>
|
||||||
<h1>
|
<h1>
|
||||||
hello
|
<span>
|
||||||
world
|
hello
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
world
|
||||||
|
</span>
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -582,7 +599,7 @@ test('PluginContainer + Sandy plugin supports deeplink', async () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
jest.runAllTimers();
|
await sleep(100);
|
||||||
expect(linksSeen).toEqual(['universe!']);
|
expect(linksSeen).toEqual(['universe!']);
|
||||||
expect(renderer.baseElement).toMatchInlineSnapshot(`
|
expect(renderer.baseElement).toMatchInlineSnapshot(`
|
||||||
<body>
|
<body>
|
||||||
@@ -598,8 +615,12 @@ test('PluginContainer + Sandy plugin supports deeplink', async () => {
|
|||||||
class="css-1woty6b-Container"
|
class="css-1woty6b-Container"
|
||||||
>
|
>
|
||||||
<h1>
|
<h1>
|
||||||
hello
|
<span>
|
||||||
universe!
|
hello
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
universe!
|
||||||
|
</span>
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -622,7 +643,7 @@ test('PluginContainer + Sandy plugin supports deeplink', async () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
jest.runAllTimers();
|
await awaitPluginCommandQueueEmpty(store);
|
||||||
expect(linksSeen).toEqual(['universe!']);
|
expect(linksSeen).toEqual(['universe!']);
|
||||||
|
|
||||||
// ...nor does a random other store update that does trigger a plugin container render
|
// ...nor does a random other store update that does trigger a plugin container render
|
||||||
@@ -645,7 +666,8 @@ test('PluginContainer + Sandy plugin supports deeplink', async () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
jest.runAllTimers();
|
await awaitPluginCommandQueueEmpty(store);
|
||||||
|
await sleep(10);
|
||||||
expect(linksSeen).toEqual(['universe!', 'london!']);
|
expect(linksSeen).toEqual(['universe!', 'london!']);
|
||||||
|
|
||||||
// and same link does trigger if something else was selected in the mean time
|
// and same link does trigger if something else was selected in the mean time
|
||||||
@@ -667,7 +689,8 @@ test('PluginContainer + Sandy plugin supports deeplink', async () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
jest.runAllTimers();
|
await awaitPluginCommandQueueEmpty(store);
|
||||||
|
await sleep(10);
|
||||||
expect(linksSeen).toEqual(['universe!', 'london!', 'london!']);
|
expect(linksSeen).toEqual(['universe!', 'london!', 'london!']);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -689,7 +712,12 @@ test('PluginContainer can render Sandy device plugins', async () => {
|
|||||||
});
|
});
|
||||||
}).toThrowError(/didn't match the type of the requested plugin/);
|
}).toThrowError(/didn't match the type of the requested plugin/);
|
||||||
const lastLogMessage = useValue(sandyApi.lastLogMessage);
|
const lastLogMessage = useValue(sandyApi.lastLogMessage);
|
||||||
return <div>Hello from Sandy: {lastLogMessage?.message}</div>;
|
return (
|
||||||
|
<div>
|
||||||
|
<span>Hello from Sandy:</span>
|
||||||
|
<span>{lastLogMessage?.message}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const devicePlugin = (client: DevicePluginClient) => {
|
const devicePlugin = (client: DevicePluginClient) => {
|
||||||
@@ -730,7 +758,10 @@ test('PluginContainer can render Sandy device plugins', async () => {
|
|||||||
class="css-1woty6b-Container"
|
class="css-1woty6b-Container"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
Hello from Sandy:
|
<span>
|
||||||
|
Hello from Sandy:
|
||||||
|
</span>
|
||||||
|
<span />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -754,6 +785,8 @@ test('PluginContainer can render Sandy device plugins', async () => {
|
|||||||
tag: 'test',
|
tag: 'test',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
await sleep(10); // links are handled async
|
||||||
|
|
||||||
expect(renders).toBe(2);
|
expect(renders).toBe(2);
|
||||||
|
|
||||||
expect(renderer.baseElement).toMatchInlineSnapshot(`
|
expect(renderer.baseElement).toMatchInlineSnapshot(`
|
||||||
@@ -770,8 +803,12 @@ test('PluginContainer can render Sandy device plugins', async () => {
|
|||||||
class="css-1woty6b-Container"
|
class="css-1woty6b-Container"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
Hello from Sandy:
|
<span>
|
||||||
helleuh
|
Hello from Sandy:
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
helleuh
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -847,7 +884,12 @@ test('PluginContainer + Sandy device plugin supports deeplink', async () => {
|
|||||||
Component() {
|
Component() {
|
||||||
const instance = usePlugin(devicePlugin);
|
const instance = usePlugin(devicePlugin);
|
||||||
const linkState = useValue(instance.linkState);
|
const linkState = useValue(instance.linkState);
|
||||||
return <h1>hello {linkState || 'world'}</h1>;
|
return (
|
||||||
|
<h1>
|
||||||
|
<span>hello</span>
|
||||||
|
<span>{linkState || 'world'}</span>
|
||||||
|
</h1>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -877,8 +919,12 @@ test('PluginContainer + Sandy device plugin supports deeplink', async () => {
|
|||||||
class="css-1woty6b-Container"
|
class="css-1woty6b-Container"
|
||||||
>
|
>
|
||||||
<h1>
|
<h1>
|
||||||
hello
|
<span>
|
||||||
world
|
hello
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
world
|
||||||
|
</span>
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -902,7 +948,8 @@ test('PluginContainer + Sandy device plugin supports deeplink', async () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
jest.runAllTimers();
|
await awaitPluginCommandQueueEmpty(store);
|
||||||
|
await sleep(10); // links are handled async
|
||||||
expect(linksSeen).toEqual([theUniverse]);
|
expect(linksSeen).toEqual([theUniverse]);
|
||||||
expect(renderer.baseElement).toMatchInlineSnapshot(`
|
expect(renderer.baseElement).toMatchInlineSnapshot(`
|
||||||
<body>
|
<body>
|
||||||
@@ -918,8 +965,12 @@ test('PluginContainer + Sandy device plugin supports deeplink', async () => {
|
|||||||
class="css-1woty6b-Container"
|
class="css-1woty6b-Container"
|
||||||
>
|
>
|
||||||
<h1>
|
<h1>
|
||||||
hello
|
<span>
|
||||||
{"thisIs":"theUniverse"}
|
hello
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
{"thisIs":"theUniverse"}
|
||||||
|
</span>
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -943,7 +994,7 @@ test('PluginContainer + Sandy device plugin supports deeplink', async () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
jest.runAllTimers();
|
await awaitPluginCommandQueueEmpty(store);
|
||||||
expect(linksSeen).toEqual([theUniverse]);
|
expect(linksSeen).toEqual([theUniverse]);
|
||||||
|
|
||||||
// ...nor does a random other store update that does trigger a plugin container render
|
// ...nor does a random other store update that does trigger a plugin container render
|
||||||
@@ -967,7 +1018,8 @@ test('PluginContainer + Sandy device plugin supports deeplink', async () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
jest.runAllTimers();
|
await awaitPluginCommandQueueEmpty(store);
|
||||||
|
await sleep(10);
|
||||||
expect(linksSeen).toEqual([theUniverse, 'london!']);
|
expect(linksSeen).toEqual([theUniverse, 'london!']);
|
||||||
|
|
||||||
// and same link does trigger if something else was selected in the mean time
|
// and same link does trigger if something else was selected in the mean time
|
||||||
@@ -991,7 +1043,8 @@ test('PluginContainer + Sandy device plugin supports deeplink', async () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
jest.runAllTimers();
|
await awaitPluginCommandQueueEmpty(store);
|
||||||
|
await sleep(10);
|
||||||
expect(linksSeen).toEqual([theUniverse, 'london!', 'london!']);
|
expect(linksSeen).toEqual([theUniverse, 'london!', 'london!']);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1087,12 +1140,13 @@ test('Sandy plugins support isPluginSupported + selectPlugin', async () => {
|
|||||||
pluginInstance.selectPlugin(definition.id, 'data');
|
pluginInstance.selectPlugin(definition.id, 'data');
|
||||||
expect(store.getState().connections.selectedPlugin).toBe(definition.id);
|
expect(store.getState().connections.selectedPlugin).toBe(definition.id);
|
||||||
expect(pluginInstance.activatedStub).toBeCalledTimes(2);
|
expect(pluginInstance.activatedStub).toBeCalledTimes(2);
|
||||||
jest.runAllTimers();
|
await awaitPluginCommandQueueEmpty(store);
|
||||||
expect(renderer.baseElement.querySelector('h1')).toMatchInlineSnapshot(`
|
expect(renderer.baseElement.querySelector('h1')).toMatchInlineSnapshot(`
|
||||||
<h1>
|
<h1>
|
||||||
Plugin1
|
Plugin1
|
||||||
</h1>
|
</h1>
|
||||||
`);
|
`);
|
||||||
|
await sleep(10); // links are handled async
|
||||||
expect(linksSeen).toEqual(['data']);
|
expect(linksSeen).toEqual(['data']);
|
||||||
|
|
||||||
// try to plugin 2 - it should be possible to select it even if it is not enabled
|
// try to plugin 2 - it should be possible to select it even if it is not enabled
|
||||||
@@ -1137,7 +1191,12 @@ test('PluginContainer can render Sandy plugins for archived devices', async () =
|
|||||||
return {};
|
return {};
|
||||||
});
|
});
|
||||||
}).toThrowError(/didn't match the type of the requested plugin/);
|
}).toThrowError(/didn't match the type of the requested plugin/);
|
||||||
return <div>Hello from Sandy{count}</div>;
|
return (
|
||||||
|
<div>
|
||||||
|
<span>Hello from Sandy</span>
|
||||||
|
<span>{count}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
type Events = {
|
type Events = {
|
||||||
@@ -1198,8 +1257,12 @@ test('PluginContainer can render Sandy plugins for archived devices', async () =
|
|||||||
class="css-1woty6b-Container"
|
class="css-1woty6b-Container"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
Hello from Sandy
|
<span>
|
||||||
0
|
Hello from Sandy
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
0
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -1272,8 +1335,12 @@ test('PluginContainer can render Sandy plugins for archived devices', async () =
|
|||||||
class="css-1woty6b-Container"
|
class="css-1woty6b-Container"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
Hello from Sandy
|
<span>
|
||||||
0
|
Hello from Sandy
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
0
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -292,20 +292,20 @@ test('log listeners are resumed and suspended automatically - 2', async () => {
|
|||||||
expect(entries.length).toBe(2);
|
expect(entries.length).toBe(2);
|
||||||
|
|
||||||
// disable one plugin
|
// disable one plugin
|
||||||
flipper.togglePlugin(Plugin.id);
|
await flipper.togglePlugin(Plugin.id);
|
||||||
expect(device.stopLogging).toBeCalledTimes(0);
|
expect(device.stopLogging).toBeCalledTimes(0);
|
||||||
device.addLogEntry(message);
|
device.addLogEntry(message);
|
||||||
expect(entries.length).toBe(3);
|
expect(entries.length).toBe(3);
|
||||||
|
|
||||||
// disable the other plugin
|
// disable the other plugin
|
||||||
flipper.togglePlugin(DevicePlugin.id);
|
await flipper.togglePlugin(DevicePlugin.id);
|
||||||
|
|
||||||
expect(device.stopLogging).toBeCalledTimes(1);
|
expect(device.stopLogging).toBeCalledTimes(1);
|
||||||
device.addLogEntry(message);
|
device.addLogEntry(message);
|
||||||
expect(entries.length).toBe(3);
|
expect(entries.length).toBe(3);
|
||||||
|
|
||||||
// re-enable plugn
|
// re-enable plugn
|
||||||
flipper.togglePlugin(Plugin.id);
|
await flipper.togglePlugin(Plugin.id);
|
||||||
expect(device.startLogging).toBeCalledTimes(2);
|
expect(device.startLogging).toBeCalledTimes(2);
|
||||||
device.addLogEntry(message);
|
device.addLogEntry(message);
|
||||||
expect(entries.length).toBe(4);
|
expect(entries.length).toBe(4);
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import MockFlipper from '../../test-utils/MockFlipper';
|
|||||||
import Client from '../../Client';
|
import Client from '../../Client';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import BaseDevice from '../../devices/BaseDevice';
|
import BaseDevice from '../../devices/BaseDevice';
|
||||||
|
import {awaitPluginCommandQueueEmpty} from '../pluginManager';
|
||||||
|
|
||||||
const pluginDetails1 = TestUtils.createMockPluginDetails({
|
const pluginDetails1 = TestUtils.createMockPluginDetails({
|
||||||
id: 'plugin1',
|
id: 'plugin1',
|
||||||
@@ -71,7 +72,7 @@ let mockDevice: BaseDevice;
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
mockedRequirePlugin.mockImplementation(
|
mockedRequirePlugin.mockImplementation(
|
||||||
(details) =>
|
async (details) =>
|
||||||
(details === pluginDetails1
|
(details === pluginDetails1
|
||||||
? pluginDefinition1
|
? pluginDefinition1
|
||||||
: details === pluginDetails2
|
: details === pluginDetails2
|
||||||
@@ -99,6 +100,8 @@ test('load plugin when no other version loaded', async () => {
|
|||||||
mockFlipper.dispatch(
|
mockFlipper.dispatch(
|
||||||
loadPlugin({plugin: pluginDetails1, enable: false, notifyIfFailed: false}),
|
loadPlugin({plugin: pluginDetails1, enable: false, notifyIfFailed: false}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await awaitPluginCommandQueueEmpty(mockFlipper.store);
|
||||||
expect(mockFlipper.getState().plugins.clientPlugins.get('plugin1')).toBe(
|
expect(mockFlipper.getState().plugins.clientPlugins.get('plugin1')).toBe(
|
||||||
pluginDefinition1,
|
pluginDefinition1,
|
||||||
);
|
);
|
||||||
@@ -119,6 +122,8 @@ test('load plugin when other version loaded', async () => {
|
|||||||
notifyIfFailed: false,
|
notifyIfFailed: false,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await awaitPluginCommandQueueEmpty(mockFlipper.store);
|
||||||
expect(mockFlipper.getState().plugins.clientPlugins.get('plugin1')).toBe(
|
expect(mockFlipper.getState().plugins.clientPlugins.get('plugin1')).toBe(
|
||||||
pluginDefinition1V2,
|
pluginDefinition1V2,
|
||||||
);
|
);
|
||||||
@@ -132,6 +137,8 @@ test('load and enable Sandy plugin', async () => {
|
|||||||
mockFlipper.dispatch(
|
mockFlipper.dispatch(
|
||||||
loadPlugin({plugin: pluginDetails1, enable: true, notifyIfFailed: false}),
|
loadPlugin({plugin: pluginDetails1, enable: true, notifyIfFailed: false}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await awaitPluginCommandQueueEmpty(mockFlipper.store);
|
||||||
expect(mockFlipper.getState().plugins.clientPlugins.get('plugin1')).toBe(
|
expect(mockFlipper.getState().plugins.clientPlugins.get('plugin1')).toBe(
|
||||||
pluginDefinition1,
|
pluginDefinition1,
|
||||||
);
|
);
|
||||||
@@ -146,6 +153,8 @@ test('uninstall plugin', async () => {
|
|||||||
loadPlugin({plugin: pluginDetails1, enable: true, notifyIfFailed: false}),
|
loadPlugin({plugin: pluginDetails1, enable: true, notifyIfFailed: false}),
|
||||||
);
|
);
|
||||||
mockFlipper.dispatch(uninstallPlugin({plugin: pluginDefinition1}));
|
mockFlipper.dispatch(uninstallPlugin({plugin: pluginDefinition1}));
|
||||||
|
|
||||||
|
await awaitPluginCommandQueueEmpty(mockFlipper.store);
|
||||||
expect(
|
expect(
|
||||||
mockFlipper.getState().plugins.clientPlugins.has('plugin1'),
|
mockFlipper.getState().plugins.clientPlugins.has('plugin1'),
|
||||||
).toBeFalsy();
|
).toBeFalsy();
|
||||||
@@ -167,11 +176,13 @@ test('uninstall bundled plugin', async () => {
|
|||||||
version: '0.43.0',
|
version: '0.43.0',
|
||||||
});
|
});
|
||||||
const pluginDefinition = new SandyPluginDefinition(pluginDetails, TestPlugin);
|
const pluginDefinition = new SandyPluginDefinition(pluginDetails, TestPlugin);
|
||||||
mockedRequirePlugin.mockReturnValue(pluginDefinition);
|
mockedRequirePlugin.mockReturnValue(Promise.resolve(pluginDefinition));
|
||||||
mockFlipper.dispatch(
|
mockFlipper.dispatch(
|
||||||
loadPlugin({plugin: pluginDetails, enable: true, notifyIfFailed: false}),
|
loadPlugin({plugin: pluginDetails, enable: true, notifyIfFailed: false}),
|
||||||
);
|
);
|
||||||
mockFlipper.dispatch(uninstallPlugin({plugin: pluginDefinition}));
|
mockFlipper.dispatch(uninstallPlugin({plugin: pluginDefinition}));
|
||||||
|
|
||||||
|
await awaitPluginCommandQueueEmpty(mockFlipper.store);
|
||||||
expect(
|
expect(
|
||||||
mockFlipper.getState().plugins.clientPlugins.has('bundled-plugin'),
|
mockFlipper.getState().plugins.clientPlugins.has('bundled-plugin'),
|
||||||
).toBeFalsy();
|
).toBeFalsy();
|
||||||
@@ -196,6 +207,8 @@ test('star plugin', async () => {
|
|||||||
selectedApp: mockClient.query.app,
|
selectedApp: mockClient.query.app,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await awaitPluginCommandQueueEmpty(mockFlipper.store);
|
||||||
expect(
|
expect(
|
||||||
mockFlipper.getState().connections.enabledPlugins[mockClient.query.app],
|
mockFlipper.getState().connections.enabledPlugins[mockClient.query.app],
|
||||||
).toContain('plugin1');
|
).toContain('plugin1');
|
||||||
@@ -218,6 +231,8 @@ test('disable plugin', async () => {
|
|||||||
selectedApp: mockClient.query.app,
|
selectedApp: mockClient.query.app,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await awaitPluginCommandQueueEmpty(mockFlipper.store);
|
||||||
expect(
|
expect(
|
||||||
mockFlipper.getState().connections.enabledPlugins[mockClient.query.app],
|
mockFlipper.getState().connections.enabledPlugins[mockClient.query.app],
|
||||||
).not.toContain('plugin1');
|
).not.toContain('plugin1');
|
||||||
@@ -237,6 +252,8 @@ test('star device plugin', async () => {
|
|||||||
plugin: devicePluginDefinition,
|
plugin: devicePluginDefinition,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await awaitPluginCommandQueueEmpty(mockFlipper.store);
|
||||||
expect(
|
expect(
|
||||||
mockFlipper.getState().connections.enabledDevicePlugins.has('device'),
|
mockFlipper.getState().connections.enabledDevicePlugins.has('device'),
|
||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
@@ -261,6 +278,8 @@ test('disable device plugin', async () => {
|
|||||||
plugin: devicePluginDefinition,
|
plugin: devicePluginDefinition,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await awaitPluginCommandQueueEmpty(mockFlipper.store);
|
||||||
expect(
|
expect(
|
||||||
mockFlipper.getState().connections.enabledDevicePlugins.has('device'),
|
mockFlipper.getState().connections.enabledDevicePlugins.has('device'),
|
||||||
).toBeFalsy();
|
).toBeFalsy();
|
||||||
|
|||||||
@@ -135,9 +135,9 @@ test('checkGK for failing plugin', () => {
|
|||||||
expect(gatekeepedPlugins[0].name).toEqual(name);
|
expect(gatekeepedPlugins[0].name).toEqual(name);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('requirePlugin returns null for invalid requires', () => {
|
test('requirePlugin returns null for invalid requires', async () => {
|
||||||
const requireFn = createRequirePluginFunction([], require);
|
const requireFn = createRequirePluginFunction([]);
|
||||||
const plugin = requireFn({
|
const plugin = await requireFn({
|
||||||
...sampleInstalledPluginDetails,
|
...sampleInstalledPluginDetails,
|
||||||
name: 'pluginID',
|
name: 'pluginID',
|
||||||
dir: '/Users/mock/.flipper/thirdparty/flipper-plugin-sample',
|
dir: '/Users/mock/.flipper/thirdparty/flipper-plugin-sample',
|
||||||
@@ -148,10 +148,10 @@ test('requirePlugin returns null for invalid requires', () => {
|
|||||||
expect(plugin).toBeNull();
|
expect(plugin).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('requirePlugin loads plugin', () => {
|
test('requirePlugin loads plugin', async () => {
|
||||||
const name = 'pluginID';
|
const name = 'pluginID';
|
||||||
const requireFn = createRequirePluginFunction([], require);
|
const requireFn = createRequirePluginFunction([]);
|
||||||
const plugin = requireFn({
|
const plugin = await requireFn({
|
||||||
...sampleInstalledPluginDetails,
|
...sampleInstalledPluginDetails,
|
||||||
name,
|
name,
|
||||||
dir: '/Users/mock/.flipper/thirdparty/flipper-plugin-sample',
|
dir: '/Users/mock/.flipper/thirdparty/flipper-plugin-sample',
|
||||||
@@ -224,10 +224,10 @@ test('newest version of each plugin is used', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('requirePlugin loads valid Sandy plugin', () => {
|
test('requirePlugin loads valid Sandy plugin', async () => {
|
||||||
const name = 'pluginID';
|
const name = 'pluginID';
|
||||||
const requireFn = createRequirePluginFunction([], require);
|
const requireFn = createRequirePluginFunction([]);
|
||||||
const plugin = requireFn({
|
const plugin = (await requireFn({
|
||||||
...sampleInstalledPluginDetails,
|
...sampleInstalledPluginDetails,
|
||||||
name,
|
name,
|
||||||
dir: path.join(
|
dir: path.join(
|
||||||
@@ -240,7 +240,7 @@ test('requirePlugin loads valid Sandy plugin', () => {
|
|||||||
),
|
),
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
flipperSDKVersion: '0.0.0',
|
flipperSDKVersion: '0.0.0',
|
||||||
}) as _SandyPluginDefinition;
|
})) as _SandyPluginDefinition;
|
||||||
expect(plugin).not.toBeNull();
|
expect(plugin).not.toBeNull();
|
||||||
expect(plugin).toBeInstanceOf(_SandyPluginDefinition);
|
expect(plugin).toBeInstanceOf(_SandyPluginDefinition);
|
||||||
expect(plugin.id).toBe('Sample');
|
expect(plugin.id).toBe('Sample');
|
||||||
@@ -261,11 +261,11 @@ test('requirePlugin loads valid Sandy plugin', () => {
|
|||||||
expect(typeof plugin.asPluginModule().plugin).toBe('function');
|
expect(typeof plugin.asPluginModule().plugin).toBe('function');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('requirePlugin errors on invalid Sandy plugin', () => {
|
test('requirePlugin errors on invalid Sandy plugin', async () => {
|
||||||
const name = 'pluginID';
|
const name = 'pluginID';
|
||||||
const failedPlugins: any[] = [];
|
const failedPlugins: any[] = [];
|
||||||
const requireFn = createRequirePluginFunction(failedPlugins, require);
|
const requireFn = createRequirePluginFunction(failedPlugins);
|
||||||
requireFn({
|
await requireFn({
|
||||||
...sampleInstalledPluginDetails,
|
...sampleInstalledPluginDetails,
|
||||||
name,
|
name,
|
||||||
// Intentionally the wrong file:
|
// Intentionally the wrong file:
|
||||||
@@ -279,10 +279,10 @@ test('requirePlugin errors on invalid Sandy plugin', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('requirePlugin loads valid Sandy Device plugin', () => {
|
test('requirePlugin loads valid Sandy Device plugin', async () => {
|
||||||
const name = 'pluginID';
|
const name = 'pluginID';
|
||||||
const requireFn = createRequirePluginFunction([], require);
|
const requireFn = createRequirePluginFunction([]);
|
||||||
const plugin = requireFn({
|
const plugin = (await requireFn({
|
||||||
...sampleInstalledPluginDetails,
|
...sampleInstalledPluginDetails,
|
||||||
pluginType: 'device',
|
pluginType: 'device',
|
||||||
name,
|
name,
|
||||||
@@ -296,7 +296,7 @@ test('requirePlugin loads valid Sandy Device plugin', () => {
|
|||||||
),
|
),
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
flipperSDKVersion: '0.0.0',
|
flipperSDKVersion: '0.0.0',
|
||||||
}) as _SandyPluginDefinition;
|
})) as _SandyPluginDefinition;
|
||||||
expect(plugin).not.toBeNull();
|
expect(plugin).not.toBeNull();
|
||||||
expect(plugin).toBeInstanceOf(_SandyPluginDefinition);
|
expect(plugin).toBeInstanceOf(_SandyPluginDefinition);
|
||||||
expect(plugin.id).toBe('Sample');
|
expect(plugin.id).toBe('Sample');
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ export default (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let running = false;
|
||||||
const unsubscribeHandlePluginCommands = sideEffect(
|
const unsubscribeHandlePluginCommands = sideEffect(
|
||||||
store,
|
store,
|
||||||
{
|
{
|
||||||
@@ -85,14 +86,49 @@ export default (
|
|||||||
noTimeBudgetWarns: true, // These side effects are critical, so we're doing them with zero throttling and want to avoid unnecessary warns
|
noTimeBudgetWarns: true, // These side effects are critical, so we're doing them with zero throttling and want to avoid unnecessary warns
|
||||||
},
|
},
|
||||||
(state) => state.pluginManager.pluginCommandsQueue,
|
(state) => state.pluginManager.pluginCommandsQueue,
|
||||||
processPluginCommandsQueue,
|
async (_queue: PluginCommand[], store: Store) => {
|
||||||
|
// To make sure all commands are running in order, and not kicking off parallel command
|
||||||
|
// processing when new commands arrive (sideEffect doesn't await)
|
||||||
|
// we keep the 'running' flag, and keep running in a loop until the commandQueue is empty,
|
||||||
|
// to make sure any commands that have arrived during execution are executed
|
||||||
|
if (running) {
|
||||||
|
return; // will be picked up in while(true) loop
|
||||||
|
}
|
||||||
|
running = true;
|
||||||
|
try {
|
||||||
|
while (true) {
|
||||||
|
const remaining = store.getState().pluginManager.pluginCommandsQueue;
|
||||||
|
if (!remaining.length) {
|
||||||
|
return; // done
|
||||||
|
}
|
||||||
|
await processPluginCommandsQueue(remaining, store);
|
||||||
|
store.dispatch(pluginCommandsProcessed(remaining.length));
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
running = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
);
|
);
|
||||||
return async () => {
|
return async () => {
|
||||||
unsubscribeHandlePluginCommands();
|
unsubscribeHandlePluginCommands();
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export function processPluginCommandsQueue(
|
export async function awaitPluginCommandQueueEmpty(store: Store) {
|
||||||
|
if (store.getState().pluginManager.pluginCommandsQueue.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return new Promise<void>((resolve) => {
|
||||||
|
const unsubscribe = store.subscribe(() => {
|
||||||
|
if (store.getState().pluginManager.pluginCommandsQueue.length === 0) {
|
||||||
|
unsubscribe();
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function processPluginCommandsQueue(
|
||||||
queue: PluginCommand[],
|
queue: PluginCommand[],
|
||||||
store: Store,
|
store: Store,
|
||||||
) {
|
) {
|
||||||
@@ -100,7 +136,7 @@ export function processPluginCommandsQueue(
|
|||||||
try {
|
try {
|
||||||
switch (command.type) {
|
switch (command.type) {
|
||||||
case 'LOAD_PLUGIN':
|
case 'LOAD_PLUGIN':
|
||||||
loadPlugin(store, command.payload);
|
await loadPlugin(store, command.payload);
|
||||||
break;
|
break;
|
||||||
case 'UNINSTALL_PLUGIN':
|
case 'UNINSTALL_PLUGIN':
|
||||||
uninstallPlugin(store, command.payload);
|
uninstallPlugin(store, command.payload);
|
||||||
@@ -121,12 +157,11 @@ export function processPluginCommandsQueue(
|
|||||||
console.error('Failed to process command', command);
|
console.error('Failed to process command', command);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
store.dispatch(pluginCommandsProcessed(queue.length));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadPlugin(store: Store, payload: LoadPluginActionPayload) {
|
async function loadPlugin(store: Store, payload: LoadPluginActionPayload) {
|
||||||
try {
|
try {
|
||||||
const plugin = requirePlugin(payload.plugin);
|
const plugin = await requirePlugin(payload.plugin);
|
||||||
const enablePlugin = payload.enable;
|
const enablePlugin = payload.enable;
|
||||||
updatePlugin(store, {plugin, enablePlugin});
|
updatePlugin(store, {plugin, enablePlugin});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -8,7 +8,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type {Store} from '../reducers/index';
|
import type {Store} from '../reducers/index';
|
||||||
import type {InstalledPluginDetails, Logger} from 'flipper-common';
|
import {
|
||||||
|
InstalledPluginDetails,
|
||||||
|
Logger,
|
||||||
|
tryCatchReportPluginFailuresAsync,
|
||||||
|
} from 'flipper-common';
|
||||||
import {PluginDefinition} from '../plugin';
|
import {PluginDefinition} from '../plugin';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
@@ -31,7 +35,7 @@ import {
|
|||||||
BundledPluginDetails,
|
BundledPluginDetails,
|
||||||
ConcretePluginDetails,
|
ConcretePluginDetails,
|
||||||
} from 'flipper-common';
|
} from 'flipper-common';
|
||||||
import {tryCatchReportPluginFailures, reportUsage} from 'flipper-common';
|
import {reportUsage} from 'flipper-common';
|
||||||
import * as FlipperPluginSDK from 'flipper-plugin';
|
import * as FlipperPluginSDK from 'flipper-plugin';
|
||||||
import {_SandyPluginDefinition} from 'flipper-plugin';
|
import {_SandyPluginDefinition} from 'flipper-plugin';
|
||||||
import * as Immer from 'immer';
|
import * as Immer from 'immer';
|
||||||
@@ -46,6 +50,8 @@ import isPluginCompatible from '../utils/isPluginCompatible';
|
|||||||
import isPluginVersionMoreRecent from '../utils/isPluginVersionMoreRecent';
|
import isPluginVersionMoreRecent from '../utils/isPluginVersionMoreRecent';
|
||||||
import {createSandyPluginWrapper} from '../utils/createSandyPluginWrapper';
|
import {createSandyPluginWrapper} from '../utils/createSandyPluginWrapper';
|
||||||
import {getRenderHostInstance} from '../RenderHost';
|
import {getRenderHostInstance} from '../RenderHost';
|
||||||
|
import pMap from 'p-map';
|
||||||
|
|
||||||
let defaultPluginsIndex: any = null;
|
let defaultPluginsIndex: any = null;
|
||||||
|
|
||||||
export default async (store: Store, _logger: Logger) => {
|
export default async (store: Store, _logger: Logger) => {
|
||||||
@@ -88,12 +94,15 @@ export default async (store: Store, _logger: Logger) => {
|
|||||||
const loadedPlugins =
|
const loadedPlugins =
|
||||||
getLatestCompatibleVersionOfEachPlugin(allLocalVersions);
|
getLatestCompatibleVersionOfEachPlugin(allLocalVersions);
|
||||||
|
|
||||||
const initialPlugins: PluginDefinition[] = loadedPlugins
|
const pluginsToLoad = loadedPlugins
|
||||||
.map(reportVersion)
|
.map(reportVersion)
|
||||||
.filter(checkDisabled(disabledPlugins))
|
.filter(checkDisabled(disabledPlugins))
|
||||||
.filter(checkGK(gatekeepedPlugins))
|
.filter(checkGK(gatekeepedPlugins));
|
||||||
.map(createRequirePluginFunction(failedPlugins))
|
const loader = createRequirePluginFunction(failedPlugins);
|
||||||
.filter(notNull);
|
|
||||||
|
const initialPlugins: PluginDefinition[] = (
|
||||||
|
await pMap(pluginsToLoad, loader)
|
||||||
|
).filter(notNull);
|
||||||
|
|
||||||
const classicPlugins = initialPlugins.filter(
|
const classicPlugins = initialPlugins.filter(
|
||||||
(p) => !isSandyPlugin(p.details),
|
(p) => !isSandyPlugin(p.details),
|
||||||
@@ -235,11 +244,12 @@ export const checkDisabled = (
|
|||||||
|
|
||||||
export const createRequirePluginFunction = (
|
export const createRequirePluginFunction = (
|
||||||
failedPlugins: Array<[ActivatablePluginDetails, string]>,
|
failedPlugins: Array<[ActivatablePluginDetails, string]>,
|
||||||
reqFn: Function = global.electronRequire,
|
|
||||||
) => {
|
) => {
|
||||||
return (pluginDetails: ActivatablePluginDetails): PluginDefinition | null => {
|
return async (
|
||||||
|
pluginDetails: ActivatablePluginDetails,
|
||||||
|
): Promise<PluginDefinition | null> => {
|
||||||
try {
|
try {
|
||||||
const pluginDefinition = requirePlugin(pluginDetails, reqFn);
|
const pluginDefinition = await requirePlugin(pluginDetails);
|
||||||
if (
|
if (
|
||||||
pluginDefinition &&
|
pluginDefinition &&
|
||||||
isDevicePluginDefinition(pluginDefinition) &&
|
isDevicePluginDefinition(pluginDefinition) &&
|
||||||
@@ -260,8 +270,7 @@ export const createRequirePluginFunction = (
|
|||||||
|
|
||||||
export const requirePlugin = (
|
export const requirePlugin = (
|
||||||
pluginDetails: ActivatablePluginDetails,
|
pluginDetails: ActivatablePluginDetails,
|
||||||
reqFn: Function = global.electronRequire,
|
): Promise<PluginDefinition> => {
|
||||||
): PluginDefinition => {
|
|
||||||
reportUsage(
|
reportUsage(
|
||||||
'plugin:load',
|
'plugin:load',
|
||||||
{
|
{
|
||||||
@@ -269,8 +278,8 @@ export const requirePlugin = (
|
|||||||
},
|
},
|
||||||
pluginDetails.id,
|
pluginDetails.id,
|
||||||
);
|
);
|
||||||
return tryCatchReportPluginFailures(
|
return tryCatchReportPluginFailuresAsync(
|
||||||
() => requirePluginInternal(pluginDetails, reqFn),
|
() => requirePluginInternal(pluginDetails),
|
||||||
'plugin:load',
|
'plugin:load',
|
||||||
pluginDetails.id,
|
pluginDetails.id,
|
||||||
);
|
);
|
||||||
@@ -280,13 +289,12 @@ const isSandyPlugin = (pluginDetails: ActivatablePluginDetails) => {
|
|||||||
return !!pluginDetails.flipperSDKVersion;
|
return !!pluginDetails.flipperSDKVersion;
|
||||||
};
|
};
|
||||||
|
|
||||||
const requirePluginInternal = (
|
const requirePluginInternal = async (
|
||||||
pluginDetails: ActivatablePluginDetails,
|
pluginDetails: ActivatablePluginDetails,
|
||||||
reqFn: Function = global.electronRequire,
|
): Promise<PluginDefinition> => {
|
||||||
): PluginDefinition => {
|
|
||||||
let plugin = pluginDetails.isBundled
|
let plugin = pluginDetails.isBundled
|
||||||
? defaultPluginsIndex[pluginDetails.name]
|
? defaultPluginsIndex[pluginDetails.name]
|
||||||
: reqFn(pluginDetails.entry);
|
: await getRenderHostInstance().requirePlugin(pluginDetails.entry);
|
||||||
if (isSandyPlugin(pluginDetails)) {
|
if (isSandyPlugin(pluginDetails)) {
|
||||||
// Sandy plugin
|
// Sandy plugin
|
||||||
return new _SandyPluginDefinition(pluginDetails, plugin);
|
return new _SandyPluginDefinition(pluginDetails, plugin);
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
TestUtils,
|
TestUtils,
|
||||||
} from 'flipper-plugin';
|
} from 'flipper-plugin';
|
||||||
import {switchPlugin} from '../pluginManager';
|
import {switchPlugin} from '../pluginManager';
|
||||||
|
import {awaitPluginCommandQueueEmpty} from '../../dispatcher/pluginManager';
|
||||||
|
|
||||||
const pluginDetails = TestUtils.createMockPluginDetails();
|
const pluginDetails = TestUtils.createMockPluginDetails();
|
||||||
|
|
||||||
@@ -184,6 +185,8 @@ test('it should not initialize a sandy plugin if not enabled', async () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await awaitPluginCommandQueueEmpty(store);
|
||||||
|
|
||||||
expect(client.sandyPluginStates.get(Plugin2.id)).toBeUndefined();
|
expect(client.sandyPluginStates.get(Plugin2.id)).toBeUndefined();
|
||||||
expect(instance.connectStub).toHaveBeenCalledTimes(0);
|
expect(instance.connectStub).toHaveBeenCalledTimes(0);
|
||||||
// disconnect wasn't called because connect was never called
|
// disconnect wasn't called because connect was never called
|
||||||
|
|||||||
@@ -28,4 +28,8 @@ export class TestDevice extends BaseDevice {
|
|||||||
specs,
|
specs,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async startLogging() {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import {switchPlugin} from '../reducers/pluginManager';
|
|||||||
import {createSandyPluginFromClassicPlugin} from '../dispatcher/plugins';
|
import {createSandyPluginFromClassicPlugin} from '../dispatcher/plugins';
|
||||||
import {createMockActivatablePluginDetails} from '../utils/testUtils';
|
import {createMockActivatablePluginDetails} from '../utils/testUtils';
|
||||||
import {_SandyPluginDefinition} from 'flipper-plugin';
|
import {_SandyPluginDefinition} from 'flipper-plugin';
|
||||||
|
import {awaitPluginCommandQueueEmpty} from '../dispatcher/pluginManager';
|
||||||
|
|
||||||
export type MockFlipperResult = {
|
export type MockFlipperResult = {
|
||||||
client: Client;
|
client: Client;
|
||||||
@@ -54,7 +55,7 @@ export type MockFlipperResult = {
|
|||||||
skipRegister?: boolean,
|
skipRegister?: boolean,
|
||||||
): Promise<Client>;
|
): Promise<Client>;
|
||||||
logger: Logger;
|
logger: Logger;
|
||||||
togglePlugin(plugin?: string): void;
|
togglePlugin(plugin?: string): Promise<void>;
|
||||||
selectPlugin(
|
selectPlugin(
|
||||||
id?: string,
|
id?: string,
|
||||||
client?: Client,
|
client?: Client,
|
||||||
@@ -168,6 +169,7 @@ export async function createMockFlipperWithPlugin(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
await awaitPluginCommandQueueEmpty(store);
|
||||||
return client;
|
return client;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -233,7 +235,7 @@ export async function createMockFlipperWithPlugin(
|
|||||||
createClient,
|
createClient,
|
||||||
logger,
|
logger,
|
||||||
pluginKey: getPluginKey(client.id, device, pluginClazz.id),
|
pluginKey: getPluginKey(client.id, device, pluginClazz.id),
|
||||||
togglePlugin(id?: string) {
|
async togglePlugin(id?: string) {
|
||||||
const plugin = id
|
const plugin = id
|
||||||
? store.getState().plugins.clientPlugins.get(id) ??
|
? store.getState().plugins.clientPlugins.get(id) ??
|
||||||
store.getState().plugins.devicePlugins.get(id)
|
store.getState().plugins.devicePlugins.get(id)
|
||||||
@@ -247,6 +249,7 @@ export async function createMockFlipperWithPlugin(
|
|||||||
selectedApp: client.query.app,
|
selectedApp: client.query.app,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
await awaitPluginCommandQueueEmpty(store);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import pluginMessageQueue, {
|
|||||||
State,
|
State,
|
||||||
queueMessages,
|
queueMessages,
|
||||||
} from '../../reducers/pluginMessageQueue';
|
} from '../../reducers/pluginMessageQueue';
|
||||||
|
import {awaitPluginCommandQueueEmpty} from '../../dispatcher/pluginManager';
|
||||||
|
|
||||||
type Events = {
|
type Events = {
|
||||||
inc: {
|
inc: {
|
||||||
@@ -67,13 +68,14 @@ const TestPlugin = new _SandyPluginDefinition(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
function switchTestPlugin(store: Store, client: Client) {
|
async function switchTestPlugin(store: Store, client: Client) {
|
||||||
store.dispatch(
|
store.dispatch(
|
||||||
switchPlugin({
|
switchPlugin({
|
||||||
plugin: TestPlugin,
|
plugin: TestPlugin,
|
||||||
selectedApp: client.query.app,
|
selectedApp: client.query.app,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
await awaitPluginCommandQueueEmpty(store);
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectDeviceLogs(store: Store) {
|
function selectDeviceLogs(store: Store) {
|
||||||
@@ -190,7 +192,7 @@ test('queue - events are NOT processed immediately if plugin is NOT selected (bu
|
|||||||
});
|
});
|
||||||
|
|
||||||
// disable. Messages don't arrive anymore
|
// disable. Messages don't arrive anymore
|
||||||
switchTestPlugin(store, client);
|
await switchTestPlugin(store, client);
|
||||||
// weird state...
|
// weird state...
|
||||||
selectTestPlugin(store, client);
|
selectTestPlugin(store, client);
|
||||||
sendMessage('inc', {delta: 3});
|
sendMessage('inc', {delta: 3});
|
||||||
@@ -206,7 +208,7 @@ test('queue - events are NOT processed immediately if plugin is NOT selected (bu
|
|||||||
expect(store.getState().pluginMessageQueue).toEqual({});
|
expect(store.getState().pluginMessageQueue).toEqual({});
|
||||||
|
|
||||||
// star again, plugin still not selected, message is queued
|
// star again, plugin still not selected, message is queued
|
||||||
switchTestPlugin(store, client);
|
await switchTestPlugin(store, client);
|
||||||
sendMessage('inc', {delta: 5});
|
sendMessage('inc', {delta: 5});
|
||||||
client.flushMessageBuffer();
|
client.flushMessageBuffer();
|
||||||
|
|
||||||
@@ -699,14 +701,14 @@ test('queue - messages that have not yet flushed be lost when disabling the plug
|
|||||||
`);
|
`);
|
||||||
|
|
||||||
// disable
|
// disable
|
||||||
switchTestPlugin(store, client);
|
await switchTestPlugin(store, client);
|
||||||
expect(client.messageBuffer).toMatchInlineSnapshot(`Object {}`);
|
expect(client.messageBuffer).toMatchInlineSnapshot(`Object {}`);
|
||||||
expect(store.getState().pluginMessageQueue).toMatchInlineSnapshot(
|
expect(store.getState().pluginMessageQueue).toMatchInlineSnapshot(
|
||||||
`Object {}`,
|
`Object {}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
// re-enable, no messages arrive
|
// re-enable, no messages arrive
|
||||||
switchTestPlugin(store, client);
|
await switchTestPlugin(store, client);
|
||||||
client.flushMessageBuffer();
|
client.flushMessageBuffer();
|
||||||
processMessageQueue(
|
processMessageQueue(
|
||||||
client.sandyPluginStates.get(TestPlugin.id)!,
|
client.sandyPluginStates.get(TestPlugin.id)!,
|
||||||
|
|||||||
@@ -168,5 +168,8 @@ function createStubRenderHost(): RenderHost {
|
|||||||
return stubConfig.gatekeepers[gk] ?? false;
|
return stubConfig.gatekeepers[gk] ?? false;
|
||||||
},
|
},
|
||||||
flipperServer: TestUtils.createFlipperServerMock(),
|
flipperServer: TestUtils.createFlipperServerMock(),
|
||||||
|
async requirePlugin(path: string) {
|
||||||
|
return require(path);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user