From f3e1a48ff36e4ae01e3f5ebfc725fad5d048cf97 Mon Sep 17 00:00:00 2001 From: Anton Nikolaev Date: Tue, 15 Dec 2020 09:28:58 -0800 Subject: [PATCH] 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 --- .../createMockFlipperWithPlugin.node.tsx.snap | 1 + .../dispatcher/fb-stubs/pluginMarketplace.tsx | 12 +++++++++++ desktop/app/src/dispatcher/index.tsx | 3 ++- .../src/reducers/__tests__/plugins.node.tsx | 4 ++++ desktop/app/src/reducers/index.tsx | 11 ++++++++-- desktop/app/src/reducers/plugins.tsx | 20 ++++++++++++++++++- .../src/utils/__tests__/exportData.node.tsx | 5 +++++ desktop/app/src/utils/loadDynamicPlugins.tsx | 6 +++++- desktop/scripts/start-dev-server.ts | 8 ++++++-- 9 files changed, 63 insertions(+), 7 deletions(-) create mode 100644 desktop/app/src/dispatcher/fb-stubs/pluginMarketplace.tsx diff --git a/desktop/app/src/__tests__/__snapshots__/createMockFlipperWithPlugin.node.tsx.snap b/desktop/app/src/__tests__/__snapshots__/createMockFlipperWithPlugin.node.tsx.snap index 1b4387c78..85632831e 100644 --- a/desktop/app/src/__tests__/__snapshots__/createMockFlipperWithPlugin.node.tsx.snap +++ b/desktop/app/src/__tests__/__snapshots__/createMockFlipperWithPlugin.node.tsx.snap @@ -56,6 +56,7 @@ Object { "disabledPlugins": Array [], "failedPlugins": Array [], "gatekeepedPlugins": Array [], + "marketplacePlugins": Array [], "selectedPlugins": Array [], } `; diff --git a/desktop/app/src/dispatcher/fb-stubs/pluginMarketplace.tsx b/desktop/app/src/dispatcher/fb-stubs/pluginMarketplace.tsx new file mode 100644 index 000000000..58ec3036f --- /dev/null +++ b/desktop/app/src/dispatcher/fb-stubs/pluginMarketplace.tsx @@ -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 +}; diff --git a/desktop/app/src/dispatcher/index.tsx b/desktop/app/src/dispatcher/index.tsx index f0b31f2b2..5fc83f775 100644 --- a/desktop/app/src/dispatcher/index.tsx +++ b/desktop/app/src/dispatcher/index.tsx @@ -21,6 +21,7 @@ import user from './user'; import pluginManager from './pluginManager'; import reactNative from './reactNative'; import pluginAutoUpdate from './fb-stubs/pluginAutoUpdate'; +import pluginMarketplace from './fb-stubs/pluginMarketplace'; import {Logger} from '../fb-interfaces/Logger'; import {Store} from '../reducers/index'; @@ -33,7 +34,6 @@ export default function (store: Store, logger: Logger): () => Promise { if (process.env.NODE_ENV === 'development') { remote.globalShortcut.unregisterAll(); } - const dispatchers: Array = [ application, store.getState().settingsState.enableAndroid ? androidDevice : null, @@ -48,6 +48,7 @@ export default function (store: Store, logger: Logger): () => Promise { pluginManager, reactNative, pluginAutoUpdate, + pluginMarketplace, ].filter(notNull); const globalCleanup = dispatchers .map((dispatcher) => dispatcher(store, logger)) diff --git a/desktop/app/src/reducers/__tests__/plugins.node.tsx b/desktop/app/src/reducers/__tests__/plugins.node.tsx index f69dc21d7..f16d15914 100644 --- a/desktop/app/src/reducers/__tests__/plugins.node.tsx +++ b/desktop/app/src/reducers/__tests__/plugins.node.tsx @@ -35,6 +35,7 @@ test('add clientPlugin', () => { failedPlugins: [], disabledPlugins: [], selectedPlugins: [], + marketplacePlugins: [], }, registerPlugins([testPlugin]), ); @@ -50,6 +51,7 @@ test('add devicePlugin', () => { failedPlugins: [], disabledPlugins: [], selectedPlugins: [], + marketplacePlugins: [], }, registerPlugins([testDevicePlugin]), ); @@ -65,6 +67,7 @@ test('do not add plugin twice', () => { failedPlugins: [], disabledPlugins: [], selectedPlugins: [], + marketplacePlugins: [], }, registerPlugins([testPlugin, testPlugin]), ); @@ -95,6 +98,7 @@ test('add gatekeeped plugin', () => { failedPlugins: [], disabledPlugins: [], selectedPlugins: [], + marketplacePlugins: [], }, addGatekeepedPlugins(gatekeepedPlugins), ); diff --git a/desktop/app/src/reducers/index.tsx b/desktop/app/src/reducers/index.tsx index d1d383a5b..bdb189f32 100644 --- a/desktop/app/src/reducers/index.tsx +++ b/desktop/app/src/reducers/index.tsx @@ -91,7 +91,7 @@ export type State = { pluginStates: PluginStatesState; pluginMessageQueue: PluginMessageQueueState; notifications: NotificationsState & PersistPartial; - plugins: PluginsState; + plugins: PluginsState & PersistPartial; user: UserState & PersistPartial; settingsState: SettingsState & PersistPartial; launcherSettingsState: LauncherSettingsState & PersistPartial; @@ -141,7 +141,14 @@ export default combineReducers({ }, notifications, ), - plugins, + plugins: persistReducer( + { + key: 'plugins', + storage, + whitelist: ['marketplacePlugins'], + }, + plugins, + ), supportForm, pluginManager, user: persistReducer( diff --git a/desktop/app/src/reducers/plugins.tsx b/desktop/app/src/reducers/plugins.tsx index 9dc450958..2d84f08ae 100644 --- a/desktop/app/src/reducers/plugins.tsx +++ b/desktop/app/src/reducers/plugins.tsx @@ -8,7 +8,7 @@ */ import {DevicePluginMap, ClientPluginMap, PluginDefinition} from '../plugin'; -import {PluginDetails} from 'flipper-plugin-lib'; +import {PluginDetails, DownloadablePluginDetails} from 'flipper-plugin-lib'; import {Actions} from '.'; import produce from 'immer'; import {isDevicePluginDefinition} from '../utils/pluginUtils'; @@ -20,6 +20,7 @@ export type State = { disabledPlugins: Array; failedPlugins: Array<[PluginDetails, string]>; selectedPlugins: Array; + marketplacePlugins: Array; }; export type RegisterPluginAction = { @@ -44,6 +45,10 @@ export type Action = | { type: 'SELECTED_PLUGINS'; payload: Array; + } + | { + type: 'MARKETPLACE_PLUGINS'; + payload: Array; }; const INITIAL_STATE: State = { @@ -53,6 +58,7 @@ const INITIAL_STATE: State = { disabledPlugins: [], failedPlugins: [], selectedPlugins: [], + marketplacePlugins: [], }; export default function reducer( @@ -94,6 +100,11 @@ export default function reducer( ...state, selectedPlugins: action.payload, }; + } else if (action.type === 'MARKETPLACE_PLUGINS') { + return { + ...state, + marketplacePlugins: action.payload, + }; } else { return state; } @@ -127,3 +138,10 @@ export const addFailedPlugins = ( type: 'FAILED_PLUGINS', payload, }); + +export const registerMarketplacePlugins = ( + payload: Array, +): Action => ({ + type: 'MARKETPLACE_PLUGINS', + payload, +}); diff --git a/desktop/app/src/utils/__tests__/exportData.node.tsx b/desktop/app/src/utils/__tests__/exportData.node.tsx index 2d496ee93..253ce4e98 100644 --- a/desktop/app/src/utils/__tests__/exportData.node.tsx +++ b/desktop/app/src/utils/__tests__/exportData.node.tsx @@ -766,6 +766,7 @@ test('test determinePluginsToProcess for mutilple clients having plugins present disabledPlugins: [], failedPlugins: [], selectedPlugins: ['TestPlugin'], + marketplacePlugins: [], }; const op = determinePluginsToProcess( [client1, client2, client3], @@ -831,6 +832,7 @@ test('test determinePluginsToProcess for no selected plugin present in any clien disabledPlugins: [], failedPlugins: [], selectedPlugins: ['RandomPlugin'], + marketplacePlugins: [], }; const op = determinePluginsToProcess([client1, client2], device1, plugins); expect(op).toBeDefined(); @@ -874,6 +876,7 @@ test('test determinePluginsToProcess for multiple clients on same device', async disabledPlugins: [], failedPlugins: [], selectedPlugins: ['TestPlugin'], + marketplacePlugins: [], }; const op = determinePluginsToProcess([client1, client2], device1, plugins); expect(op).toBeDefined(); @@ -955,6 +958,7 @@ test('test determinePluginsToProcess for multiple clients on different device', disabledPlugins: [], failedPlugins: [], selectedPlugins: ['TestPlugin'], + marketplacePlugins: [], }; const op = determinePluginsToProcess( [client1Device1, client2Device1, client1Device2, client2Device2], @@ -1033,6 +1037,7 @@ test('test determinePluginsToProcess to ignore archived clients', async () => { disabledPlugins: [], failedPlugins: [], selectedPlugins: ['TestPlugin'], + marketplacePlugins: [], }; const op = determinePluginsToProcess( [client, archivedClient], diff --git a/desktop/app/src/utils/loadDynamicPlugins.tsx b/desktop/app/src/utils/loadDynamicPlugins.tsx index 643ba9e1d..6208d0ccd 100644 --- a/desktop/app/src/utils/loadDynamicPlugins.tsx +++ b/desktop/app/src/utils/loadDynamicPlugins.tsx @@ -56,7 +56,11 @@ export default async function loadDynamicPlugins(): Promise { const compiledDynamicPlugins = (await compilations).filter( (c) => c !== null, ) as PluginDetails[]; - console.log('✅ Loaded all plugins.'); + console.log( + `✅ Loaded ${dynamicPlugins.length} dynamic plugins: ${dynamicPlugins + .map((x) => x.title) + .join(', ')}.`, + ); return compiledDynamicPlugins; } async function loadPlugin( diff --git a/desktop/scripts/start-dev-server.ts b/desktop/scripts/start-dev-server.ts index db67bc91f..eeca20985 100644 --- a/desktop/scripts/start-dev-server.ts +++ b/desktop/scripts/start-dev-server.ts @@ -104,8 +104,12 @@ if (argv['fast-refresh'] === true) { } // By default plugin auto-update is disabled in dev mode, -// but it is possible to enable it using this command line argument. -if (argv['plugin-auto-update'] === true) { +// but it is possible to enable it using this command line +// argument or env var. +if ( + argv['plugin-auto-update'] === true || + process.env.FLIPPER_PLUGIN_AUTO_UPDATE +) { delete process.env.FLIPPER_DISABLE_PLUGIN_AUTO_UPDATE; } else { process.env.FLIPPER_DISABLE_PLUGIN_AUTO_UPDATE = 'true';