From ac7980993c4212bb00e86f5775801e80366ec40b Mon Sep 17 00:00:00 2001 From: Pritesh Nandgaonkar Date: Thu, 11 Oct 2018 15:19:21 -0700 Subject: [PATCH] FlipperBackgroundPlugin Summary: Adds a new type of plugin: `FlipperBackgroundPlugin` Background plugins are not torn down when the user switches to another plugin so they keep receiving messages in the background. Background plugins need to use persistedState to keep their data. To handle the messages received in the background they need to implement a static method that merges a message with the current state from redux. The plugin doesn't need to call this method itself, it is called from `client.js`. ```static persistedStateReducer = ( persistedState: PersistedState, data: Object, ): PersistedState ``` This method is used to handle messages in both foreground and background. Reviewed By: danielbuechele Differential Revision: D10256305 fbshipit-source-id: d86da9caa1b75178841a9a347eb427112141eaa3 --- src/Client.js | 82 ++++++++++++++------------------ src/PluginContainer.js | 5 +- src/__tests__/server.electron.js | 5 +- src/dispatcher/server.js | 9 +--- src/plugin.js | 6 +++ src/server.js | 7 ++- 6 files changed, 53 insertions(+), 61 deletions(-) diff --git a/src/Client.js b/src/Client.js index 19296a909..73a808e12 100644 --- a/src/Client.js +++ b/src/Client.js @@ -8,6 +8,7 @@ import type {FlipperPlugin} from './plugin.js'; import type {App} from './App.js'; import type Logger from './fb-stubs/Logger.js'; +import type {Store} from './reducers/index.js'; import {clientPlugins} from './plugins/index.js'; import {ReactiveSocket, PartialResponder} from 'rsocket-core'; @@ -32,6 +33,7 @@ export default class Client extends EventEmitter { query: ClientQuery, conn: ReactiveSocket, logger: Logger, + store: Store, ) { super(); @@ -42,8 +44,8 @@ export default class Client extends EventEmitter { this.query = query; this.messageIdCounter = 0; this.logger = logger; + this.store = store; - this.bufferedMessages = new Map(); this.broadcastCallbacks = new Map(); this.requestCallbacks = new Map(); @@ -77,8 +79,8 @@ export default class Client extends EventEmitter { plugins: Plugins; connection: ReactiveSocket; responder: PartialResponder; + store: Store; - bufferedMessages: Map>; broadcastCallbacks: Map>>; requestCallbacks: Map< @@ -159,19 +161,38 @@ export default class Client extends EventEmitter { const params = data.params; invariant(params, 'expected params'); - const apiCallbacks = this.broadcastCallbacks.get(params.api); - if (!apiCallbacks) { - return; - } + const persistingPlugin: ?Class> = clientPlugins.find( + (p: Class>) => + p.id === params.api && p.persistedStateReducer, + ); - const methodCallbacks: ?Set = apiCallbacks.get(params.method); - if (this.selectedPlugin != params.api) { - this.bufferMessage(params); - return; - } - if (methodCallbacks && methodCallbacks.size > 0) { - for (const callback of methodCallbacks) { - callback(params.params); + if (persistingPlugin) { + const pluginKey = `${this.id}#${params.api}`; + const persistedState = this.store.getState().pluginStates[pluginKey]; + this.store.dispatch({ + type: 'SET_PLUGIN_STATE', + payload: { + pluginKey, + // $FlowFixMe: We checked persistedStateReducer exists + state: persistingPlugin.persistedStateReducer( + persistedState, + params.params, + ), + }, + }); + } else { + const apiCallbacks = this.broadcastCallbacks.get(params.api); + if (!apiCallbacks) { + return; + } + + const methodCallbacks: ?Set = apiCallbacks.get( + params.method, + ); + if (methodCallbacks) { + for (const callback of methodCallbacks) { + callback(params.params); + } } } } @@ -194,39 +215,6 @@ export default class Client extends EventEmitter { } } - readBufferedMessages(id: string) { - const paramsArray = this.bufferedMessages.get(id); - if (!paramsArray) { - return; - } - paramsArray.forEach((params, i) => - setTimeout(() => { - const apiCallbacks = this.broadcastCallbacks.get(params.api); - if (!apiCallbacks) { - return; - } - - const methodCallbacks: ?Set = apiCallbacks.get(params.method); - if (methodCallbacks) { - for (const callback of methodCallbacks) { - callback(params.params); - } - } - }, i * 20), - ); - this.bufferedMessages.delete(id); - } - - bufferMessage(msg: Object) { - const arr = this.bufferedMessages.get(msg.api); - if (arr) { - arr.push(msg); - this.bufferedMessages.set(msg.api, arr); - } else { - this.bufferedMessages.set(msg.api, [msg]); - } - } - toJSON() { return ``; } diff --git a/src/PluginContainer.js b/src/PluginContainer.js index 283660666..e008b0ac7 100644 --- a/src/PluginContainer.js +++ b/src/PluginContainer.js @@ -6,6 +6,7 @@ */ import type {FlipperPlugin, FlipperBasePlugin} from './plugin.js'; import type LogManager from './fb-stubs/Logger'; +import type Client from './Client.js'; import type BaseDevice from './devices/BaseDevice.js'; import type {Props as PluginProps} from './plugin'; @@ -19,7 +20,6 @@ import { styled, } from 'flipper'; import React from 'react'; -import Client from './Client.js'; import {connect} from 'react-redux'; import {setPluginState} from './reducers/pluginStates.js'; import {setActiveNotifications} from './reducers/notifications.js'; @@ -119,9 +119,6 @@ class PluginContainer extends Component { if (ref && target) { activateMenuItems(ref); ref._init(); - if (target instanceof Client) { - target.readBufferedMessages(ref.constructor.id); - } this.props.logger.trackTimeSince(`activePlugin-${ref.constructor.id}`); this.plugin = ref; } diff --git a/src/__tests__/server.electron.js b/src/__tests__/server.electron.js index dae62eac0..557bd4751 100644 --- a/src/__tests__/server.electron.js +++ b/src/__tests__/server.electron.js @@ -7,11 +7,14 @@ import Server, {SECURE_PORT, INSECURE_PORT} from '../server.js'; import LogManager from '../fb-stubs/Logger'; +import reducers from '../reducers/index.js'; +import configureStore from 'redux-mock-store'; import path from 'path'; import os from 'os'; import fs from 'fs'; let server; +const mockStore = configureStore([])(reducers(undefined, {type: 'INIT'})); beforeAll(() => { // create config directory, which is usually created by static/index.js @@ -20,7 +23,7 @@ beforeAll(() => { fs.mkdirSync(flipperDir); } - server = new Server(new LogManager()); + server = new Server(new LogManager(), mockStore); }); test('servers starting at ports', done => { diff --git a/src/dispatcher/server.js b/src/dispatcher/server.js index a2f996f84..8eab4b1cf 100644 --- a/src/dispatcher/server.js +++ b/src/dispatcher/server.js @@ -12,13 +12,8 @@ import type Logger from '../fb-stubs/Logger.js'; import type Client from '../Client.js'; export default (store: Store, logger: Logger) => { - const server = new Server(logger); - store.subscribe(() => { - let currentState = store.getState(); - currentState.connections.clients.forEach((client: Client) => { - client.selectedPlugin = currentState.connections.selectedPlugin; - }); - }); + const server = new Server(logger, store); + server.addListener('new-client', (client: Client) => { store.dispatch({ type: 'NEW_CLIENT', diff --git a/src/plugin.js b/src/plugin.js index 53f92c6f5..5a67ff781 100644 --- a/src/plugin.js +++ b/src/plugin.js @@ -113,6 +113,8 @@ export class FlipperPlugin extends FlipperBasePlugin< A, P, > { + static persistedStateReducer: ?(persistedState: P, data: Object) => $Shape

; + constructor(props: Props<*>) { super(props); const {id} = this.constructor; @@ -163,6 +165,10 @@ export class FlipperPlugin extends FlipperBasePlugin< } _teardown() { + if (this.constructor.persistedStateReducer) { + // do not tear down when persistedStateReducer is set + return; + } // automatically unsubscribe subscriptions for (const {method, callback} of this.subscriptions) { this.realClient.unsubscribe(this.constructor.id, method, callback); diff --git a/src/server.js b/src/server.js index c95b90d2d..f07018b20 100644 --- a/src/server.js +++ b/src/server.js @@ -8,6 +8,7 @@ import type {SecureServerConfig} from './utils/CertificateProvider'; import type Logger from './fb-stubs/Logger'; import type {ClientQuery} from './Client.js'; +import type {Store} from './reducers/index.js'; import CertificateProvider from './utils/CertificateProvider'; import {RSocketServer, ReactiveSocket} from 'rsocket-core'; @@ -42,13 +43,15 @@ export default class Server extends EventEmitter { certificateProvider: CertificateProvider; connectionTracker: ConnectionTracker; logger: Logger; + store: Store; - constructor(logger: Logger) { + constructor(logger: Logger, store: Store) { super(); this.logger = logger; this.connections = new Map(); this.certificateProvider = new CertificateProvider(this, logger); this.connectionTracker = new ConnectionTracker(logger); + this.store = store; this.init(); } @@ -244,7 +247,7 @@ export default class Server extends EventEmitter { const id = `${query.app}-${query.os}-${query.device}-${query.device_id}`; console.debug(`Device connected: ${id}`, 'server'); - const client = new Client(id, query, conn, this.logger); + const client = new Client(id, query, conn, this.logger, this.store); const info = { client,