Plugin Marketplace state refresh and cache

Summary:
Separate dispatcher for periodic refreshing available plugins data from the Marketplace backend and caching it locally.

The plugin auto update downloader subscribes to these state refreshes and automatically schedules plugin update downloads when required.

Reviewed By: passy

Differential Revision: D25360897

fbshipit-source-id: 5b6d95b63ff47b8ae9ad8b12e2480d1fed524ca5
This commit is contained in:
Anton Nikolaev
2020-12-15 09:28:58 -08:00
committed by Facebook GitHub Bot
parent 5b26f36672
commit f3e1a48ff3
9 changed files with 63 additions and 7 deletions

View File

@@ -56,6 +56,7 @@ Object {
"disabledPlugins": Array [], "disabledPlugins": Array [],
"failedPlugins": Array [], "failedPlugins": Array [],
"gatekeepedPlugins": Array [], "gatekeepedPlugins": Array [],
"marketplacePlugins": Array [],
"selectedPlugins": Array [], "selectedPlugins": Array [],
} }
`; `;

View File

@@ -0,0 +1,12 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
export default () => {
// Marketplace is not implemented in public version of Flipper
};

View File

@@ -21,6 +21,7 @@ import user from './user';
import pluginManager from './pluginManager'; import pluginManager from './pluginManager';
import reactNative from './reactNative'; import reactNative from './reactNative';
import pluginAutoUpdate from './fb-stubs/pluginAutoUpdate'; import pluginAutoUpdate from './fb-stubs/pluginAutoUpdate';
import pluginMarketplace from './fb-stubs/pluginMarketplace';
import {Logger} from '../fb-interfaces/Logger'; import {Logger} from '../fb-interfaces/Logger';
import {Store} from '../reducers/index'; import {Store} from '../reducers/index';
@@ -33,7 +34,6 @@ export default function (store: Store, logger: Logger): () => Promise<void> {
if (process.env.NODE_ENV === 'development') { if (process.env.NODE_ENV === 'development') {
remote.globalShortcut.unregisterAll(); remote.globalShortcut.unregisterAll();
} }
const dispatchers: Array<Dispatcher> = [ const dispatchers: Array<Dispatcher> = [
application, application,
store.getState().settingsState.enableAndroid ? androidDevice : null, store.getState().settingsState.enableAndroid ? androidDevice : null,
@@ -48,6 +48,7 @@ export default function (store: Store, logger: Logger): () => Promise<void> {
pluginManager, pluginManager,
reactNative, reactNative,
pluginAutoUpdate, pluginAutoUpdate,
pluginMarketplace,
].filter(notNull); ].filter(notNull);
const globalCleanup = dispatchers const globalCleanup = dispatchers
.map((dispatcher) => dispatcher(store, logger)) .map((dispatcher) => dispatcher(store, logger))

View File

@@ -35,6 +35,7 @@ test('add clientPlugin', () => {
failedPlugins: [], failedPlugins: [],
disabledPlugins: [], disabledPlugins: [],
selectedPlugins: [], selectedPlugins: [],
marketplacePlugins: [],
}, },
registerPlugins([testPlugin]), registerPlugins([testPlugin]),
); );
@@ -50,6 +51,7 @@ test('add devicePlugin', () => {
failedPlugins: [], failedPlugins: [],
disabledPlugins: [], disabledPlugins: [],
selectedPlugins: [], selectedPlugins: [],
marketplacePlugins: [],
}, },
registerPlugins([testDevicePlugin]), registerPlugins([testDevicePlugin]),
); );
@@ -65,6 +67,7 @@ test('do not add plugin twice', () => {
failedPlugins: [], failedPlugins: [],
disabledPlugins: [], disabledPlugins: [],
selectedPlugins: [], selectedPlugins: [],
marketplacePlugins: [],
}, },
registerPlugins([testPlugin, testPlugin]), registerPlugins([testPlugin, testPlugin]),
); );
@@ -95,6 +98,7 @@ test('add gatekeeped plugin', () => {
failedPlugins: [], failedPlugins: [],
disabledPlugins: [], disabledPlugins: [],
selectedPlugins: [], selectedPlugins: [],
marketplacePlugins: [],
}, },
addGatekeepedPlugins(gatekeepedPlugins), addGatekeepedPlugins(gatekeepedPlugins),
); );

View File

@@ -91,7 +91,7 @@ export type State = {
pluginStates: PluginStatesState; pluginStates: PluginStatesState;
pluginMessageQueue: PluginMessageQueueState; pluginMessageQueue: PluginMessageQueueState;
notifications: NotificationsState & PersistPartial; notifications: NotificationsState & PersistPartial;
plugins: PluginsState; plugins: PluginsState & PersistPartial;
user: UserState & PersistPartial; user: UserState & PersistPartial;
settingsState: SettingsState & PersistPartial; settingsState: SettingsState & PersistPartial;
launcherSettingsState: LauncherSettingsState & PersistPartial; launcherSettingsState: LauncherSettingsState & PersistPartial;
@@ -141,7 +141,14 @@ export default combineReducers<State, Actions>({
}, },
notifications, notifications,
), ),
plugins: persistReducer<PluginsState, Actions>(
{
key: 'plugins',
storage,
whitelist: ['marketplacePlugins'],
},
plugins, plugins,
),
supportForm, supportForm,
pluginManager, pluginManager,
user: persistReducer( user: persistReducer(

View File

@@ -8,7 +8,7 @@
*/ */
import {DevicePluginMap, ClientPluginMap, PluginDefinition} from '../plugin'; import {DevicePluginMap, ClientPluginMap, PluginDefinition} from '../plugin';
import {PluginDetails} from 'flipper-plugin-lib'; import {PluginDetails, DownloadablePluginDetails} from 'flipper-plugin-lib';
import {Actions} from '.'; import {Actions} from '.';
import produce from 'immer'; import produce from 'immer';
import {isDevicePluginDefinition} from '../utils/pluginUtils'; import {isDevicePluginDefinition} from '../utils/pluginUtils';
@@ -20,6 +20,7 @@ export type State = {
disabledPlugins: Array<PluginDetails>; disabledPlugins: Array<PluginDetails>;
failedPlugins: Array<[PluginDetails, string]>; failedPlugins: Array<[PluginDetails, string]>;
selectedPlugins: Array<string>; selectedPlugins: Array<string>;
marketplacePlugins: Array<DownloadablePluginDetails>;
}; };
export type RegisterPluginAction = { export type RegisterPluginAction = {
@@ -44,6 +45,10 @@ export type Action =
| { | {
type: 'SELECTED_PLUGINS'; type: 'SELECTED_PLUGINS';
payload: Array<string>; payload: Array<string>;
}
| {
type: 'MARKETPLACE_PLUGINS';
payload: Array<DownloadablePluginDetails>;
}; };
const INITIAL_STATE: State = { const INITIAL_STATE: State = {
@@ -53,6 +58,7 @@ const INITIAL_STATE: State = {
disabledPlugins: [], disabledPlugins: [],
failedPlugins: [], failedPlugins: [],
selectedPlugins: [], selectedPlugins: [],
marketplacePlugins: [],
}; };
export default function reducer( export default function reducer(
@@ -94,6 +100,11 @@ export default function reducer(
...state, ...state,
selectedPlugins: action.payload, selectedPlugins: action.payload,
}; };
} else if (action.type === 'MARKETPLACE_PLUGINS') {
return {
...state,
marketplacePlugins: action.payload,
};
} else { } else {
return state; return state;
} }
@@ -127,3 +138,10 @@ export const addFailedPlugins = (
type: 'FAILED_PLUGINS', type: 'FAILED_PLUGINS',
payload, payload,
}); });
export const registerMarketplacePlugins = (
payload: Array<DownloadablePluginDetails>,
): Action => ({
type: 'MARKETPLACE_PLUGINS',
payload,
});

View File

@@ -766,6 +766,7 @@ test('test determinePluginsToProcess for mutilple clients having plugins present
disabledPlugins: [], disabledPlugins: [],
failedPlugins: [], failedPlugins: [],
selectedPlugins: ['TestPlugin'], selectedPlugins: ['TestPlugin'],
marketplacePlugins: [],
}; };
const op = determinePluginsToProcess( const op = determinePluginsToProcess(
[client1, client2, client3], [client1, client2, client3],
@@ -831,6 +832,7 @@ test('test determinePluginsToProcess for no selected plugin present in any clien
disabledPlugins: [], disabledPlugins: [],
failedPlugins: [], failedPlugins: [],
selectedPlugins: ['RandomPlugin'], selectedPlugins: ['RandomPlugin'],
marketplacePlugins: [],
}; };
const op = determinePluginsToProcess([client1, client2], device1, plugins); const op = determinePluginsToProcess([client1, client2], device1, plugins);
expect(op).toBeDefined(); expect(op).toBeDefined();
@@ -874,6 +876,7 @@ test('test determinePluginsToProcess for multiple clients on same device', async
disabledPlugins: [], disabledPlugins: [],
failedPlugins: [], failedPlugins: [],
selectedPlugins: ['TestPlugin'], selectedPlugins: ['TestPlugin'],
marketplacePlugins: [],
}; };
const op = determinePluginsToProcess([client1, client2], device1, plugins); const op = determinePluginsToProcess([client1, client2], device1, plugins);
expect(op).toBeDefined(); expect(op).toBeDefined();
@@ -955,6 +958,7 @@ test('test determinePluginsToProcess for multiple clients on different device',
disabledPlugins: [], disabledPlugins: [],
failedPlugins: [], failedPlugins: [],
selectedPlugins: ['TestPlugin'], selectedPlugins: ['TestPlugin'],
marketplacePlugins: [],
}; };
const op = determinePluginsToProcess( const op = determinePluginsToProcess(
[client1Device1, client2Device1, client1Device2, client2Device2], [client1Device1, client2Device1, client1Device2, client2Device2],
@@ -1033,6 +1037,7 @@ test('test determinePluginsToProcess to ignore archived clients', async () => {
disabledPlugins: [], disabledPlugins: [],
failedPlugins: [], failedPlugins: [],
selectedPlugins: ['TestPlugin'], selectedPlugins: ['TestPlugin'],
marketplacePlugins: [],
}; };
const op = determinePluginsToProcess( const op = determinePluginsToProcess(
[client, archivedClient], [client, archivedClient],

View File

@@ -56,7 +56,11 @@ export default async function loadDynamicPlugins(): Promise<PluginDetails[]> {
const compiledDynamicPlugins = (await compilations).filter( const compiledDynamicPlugins = (await compilations).filter(
(c) => c !== null, (c) => c !== null,
) as PluginDetails[]; ) as PluginDetails[];
console.log('✅ Loaded all plugins.'); console.log(
`✅ Loaded ${dynamicPlugins.length} dynamic plugins: ${dynamicPlugins
.map((x) => x.title)
.join(', ')}.`,
);
return compiledDynamicPlugins; return compiledDynamicPlugins;
} }
async function loadPlugin( async function loadPlugin(

View File

@@ -104,8 +104,12 @@ if (argv['fast-refresh'] === true) {
} }
// By default plugin auto-update is disabled in dev mode, // By default plugin auto-update is disabled in dev mode,
// but it is possible to enable it using this command line argument. // but it is possible to enable it using this command line
if (argv['plugin-auto-update'] === true) { // argument or env var.
if (
argv['plugin-auto-update'] === true ||
process.env.FLIPPER_PLUGIN_AUTO_UPDATE
) {
delete process.env.FLIPPER_DISABLE_PLUGIN_AUTO_UPDATE; delete process.env.FLIPPER_DISABLE_PLUGIN_AUTO_UPDATE;
} else { } else {
process.env.FLIPPER_DISABLE_PLUGIN_AUTO_UPDATE = 'true'; process.env.FLIPPER_DISABLE_PLUGIN_AUTO_UPDATE = 'true';