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
This commit is contained in:
committed by
Facebook Github Bot
parent
5bbfa58909
commit
ac7980993c
@@ -8,6 +8,7 @@
|
|||||||
import type {FlipperPlugin} from './plugin.js';
|
import type {FlipperPlugin} from './plugin.js';
|
||||||
import type {App} from './App.js';
|
import type {App} from './App.js';
|
||||||
import type Logger from './fb-stubs/Logger.js';
|
import type Logger from './fb-stubs/Logger.js';
|
||||||
|
import type {Store} from './reducers/index.js';
|
||||||
|
|
||||||
import {clientPlugins} from './plugins/index.js';
|
import {clientPlugins} from './plugins/index.js';
|
||||||
import {ReactiveSocket, PartialResponder} from 'rsocket-core';
|
import {ReactiveSocket, PartialResponder} from 'rsocket-core';
|
||||||
@@ -32,6 +33,7 @@ export default class Client extends EventEmitter {
|
|||||||
query: ClientQuery,
|
query: ClientQuery,
|
||||||
conn: ReactiveSocket,
|
conn: ReactiveSocket,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
|
store: Store,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
@@ -42,8 +44,8 @@ export default class Client extends EventEmitter {
|
|||||||
this.query = query;
|
this.query = query;
|
||||||
this.messageIdCounter = 0;
|
this.messageIdCounter = 0;
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
|
this.store = store;
|
||||||
|
|
||||||
this.bufferedMessages = new Map();
|
|
||||||
this.broadcastCallbacks = new Map();
|
this.broadcastCallbacks = new Map();
|
||||||
this.requestCallbacks = new Map();
|
this.requestCallbacks = new Map();
|
||||||
|
|
||||||
@@ -77,8 +79,8 @@ export default class Client extends EventEmitter {
|
|||||||
plugins: Plugins;
|
plugins: Plugins;
|
||||||
connection: ReactiveSocket;
|
connection: ReactiveSocket;
|
||||||
responder: PartialResponder;
|
responder: PartialResponder;
|
||||||
|
store: Store;
|
||||||
|
|
||||||
bufferedMessages: Map<string, Array<Object>>;
|
|
||||||
broadcastCallbacks: Map<?string, Map<string, Set<Function>>>;
|
broadcastCallbacks: Map<?string, Map<string, Set<Function>>>;
|
||||||
|
|
||||||
requestCallbacks: Map<
|
requestCallbacks: Map<
|
||||||
@@ -159,22 +161,41 @@ export default class Client extends EventEmitter {
|
|||||||
const params = data.params;
|
const params = data.params;
|
||||||
invariant(params, 'expected params');
|
invariant(params, 'expected params');
|
||||||
|
|
||||||
|
const persistingPlugin: ?Class<FlipperPlugin<>> = clientPlugins.find(
|
||||||
|
(p: Class<FlipperPlugin<>>) =>
|
||||||
|
p.id === params.api && p.persistedStateReducer,
|
||||||
|
);
|
||||||
|
|
||||||
|
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);
|
const apiCallbacks = this.broadcastCallbacks.get(params.api);
|
||||||
if (!apiCallbacks) {
|
if (!apiCallbacks) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const methodCallbacks: ?Set<Function> = apiCallbacks.get(params.method);
|
const methodCallbacks: ?Set<Function> = apiCallbacks.get(
|
||||||
if (this.selectedPlugin != params.api) {
|
params.method,
|
||||||
this.bufferMessage(params);
|
);
|
||||||
return;
|
if (methodCallbacks) {
|
||||||
}
|
|
||||||
if (methodCallbacks && methodCallbacks.size > 0) {
|
|
||||||
for (const callback of methodCallbacks) {
|
for (const callback of methodCallbacks) {
|
||||||
callback(params.params);
|
callback(params.params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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<Function> = 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() {
|
toJSON() {
|
||||||
return `<Client#${this.id}>`;
|
return `<Client#${this.id}>`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
import type {FlipperPlugin, FlipperBasePlugin} from './plugin.js';
|
import type {FlipperPlugin, FlipperBasePlugin} from './plugin.js';
|
||||||
import type LogManager from './fb-stubs/Logger';
|
import type LogManager from './fb-stubs/Logger';
|
||||||
|
import type Client from './Client.js';
|
||||||
import type BaseDevice from './devices/BaseDevice.js';
|
import type BaseDevice from './devices/BaseDevice.js';
|
||||||
import type {Props as PluginProps} from './plugin';
|
import type {Props as PluginProps} from './plugin';
|
||||||
|
|
||||||
@@ -19,7 +20,6 @@ import {
|
|||||||
styled,
|
styled,
|
||||||
} from 'flipper';
|
} from 'flipper';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Client from './Client.js';
|
|
||||||
import {connect} from 'react-redux';
|
import {connect} from 'react-redux';
|
||||||
import {setPluginState} from './reducers/pluginStates.js';
|
import {setPluginState} from './reducers/pluginStates.js';
|
||||||
import {setActiveNotifications} from './reducers/notifications.js';
|
import {setActiveNotifications} from './reducers/notifications.js';
|
||||||
@@ -119,9 +119,6 @@ class PluginContainer extends Component<Props, State> {
|
|||||||
if (ref && target) {
|
if (ref && target) {
|
||||||
activateMenuItems(ref);
|
activateMenuItems(ref);
|
||||||
ref._init();
|
ref._init();
|
||||||
if (target instanceof Client) {
|
|
||||||
target.readBufferedMessages(ref.constructor.id);
|
|
||||||
}
|
|
||||||
this.props.logger.trackTimeSince(`activePlugin-${ref.constructor.id}`);
|
this.props.logger.trackTimeSince(`activePlugin-${ref.constructor.id}`);
|
||||||
this.plugin = ref;
|
this.plugin = ref;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,11 +7,14 @@
|
|||||||
|
|
||||||
import Server, {SECURE_PORT, INSECURE_PORT} from '../server.js';
|
import Server, {SECURE_PORT, INSECURE_PORT} from '../server.js';
|
||||||
import LogManager from '../fb-stubs/Logger';
|
import LogManager from '../fb-stubs/Logger';
|
||||||
|
import reducers from '../reducers/index.js';
|
||||||
|
import configureStore from 'redux-mock-store';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
|
||||||
let server;
|
let server;
|
||||||
|
const mockStore = configureStore([])(reducers(undefined, {type: 'INIT'}));
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
// create config directory, which is usually created by static/index.js
|
// create config directory, which is usually created by static/index.js
|
||||||
@@ -20,7 +23,7 @@ beforeAll(() => {
|
|||||||
fs.mkdirSync(flipperDir);
|
fs.mkdirSync(flipperDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
server = new Server(new LogManager());
|
server = new Server(new LogManager(), mockStore);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('servers starting at ports', done => {
|
test('servers starting at ports', done => {
|
||||||
|
|||||||
@@ -12,13 +12,8 @@ import type Logger from '../fb-stubs/Logger.js';
|
|||||||
import type Client from '../Client.js';
|
import type Client from '../Client.js';
|
||||||
|
|
||||||
export default (store: Store, logger: Logger) => {
|
export default (store: Store, logger: Logger) => {
|
||||||
const server = new Server(logger);
|
const server = new Server(logger, store);
|
||||||
store.subscribe(() => {
|
|
||||||
let currentState = store.getState();
|
|
||||||
currentState.connections.clients.forEach((client: Client) => {
|
|
||||||
client.selectedPlugin = currentState.connections.selectedPlugin;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
server.addListener('new-client', (client: Client) => {
|
server.addListener('new-client', (client: Client) => {
|
||||||
store.dispatch({
|
store.dispatch({
|
||||||
type: 'NEW_CLIENT',
|
type: 'NEW_CLIENT',
|
||||||
|
|||||||
@@ -113,6 +113,8 @@ export class FlipperPlugin<S = *, A = *, P = *> extends FlipperBasePlugin<
|
|||||||
A,
|
A,
|
||||||
P,
|
P,
|
||||||
> {
|
> {
|
||||||
|
static persistedStateReducer: ?(persistedState: P, data: Object) => $Shape<P>;
|
||||||
|
|
||||||
constructor(props: Props<*>) {
|
constructor(props: Props<*>) {
|
||||||
super(props);
|
super(props);
|
||||||
const {id} = this.constructor;
|
const {id} = this.constructor;
|
||||||
@@ -163,6 +165,10 @@ export class FlipperPlugin<S = *, A = *, P = *> extends FlipperBasePlugin<
|
|||||||
}
|
}
|
||||||
|
|
||||||
_teardown() {
|
_teardown() {
|
||||||
|
if (this.constructor.persistedStateReducer) {
|
||||||
|
// do not tear down when persistedStateReducer is set
|
||||||
|
return;
|
||||||
|
}
|
||||||
// automatically unsubscribe subscriptions
|
// automatically unsubscribe subscriptions
|
||||||
for (const {method, callback} of this.subscriptions) {
|
for (const {method, callback} of this.subscriptions) {
|
||||||
this.realClient.unsubscribe(this.constructor.id, method, callback);
|
this.realClient.unsubscribe(this.constructor.id, method, callback);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
import type {SecureServerConfig} from './utils/CertificateProvider';
|
import type {SecureServerConfig} from './utils/CertificateProvider';
|
||||||
import type Logger from './fb-stubs/Logger';
|
import type Logger from './fb-stubs/Logger';
|
||||||
import type {ClientQuery} from './Client.js';
|
import type {ClientQuery} from './Client.js';
|
||||||
|
import type {Store} from './reducers/index.js';
|
||||||
|
|
||||||
import CertificateProvider from './utils/CertificateProvider';
|
import CertificateProvider from './utils/CertificateProvider';
|
||||||
import {RSocketServer, ReactiveSocket} from 'rsocket-core';
|
import {RSocketServer, ReactiveSocket} from 'rsocket-core';
|
||||||
@@ -42,13 +43,15 @@ export default class Server extends EventEmitter {
|
|||||||
certificateProvider: CertificateProvider;
|
certificateProvider: CertificateProvider;
|
||||||
connectionTracker: ConnectionTracker;
|
connectionTracker: ConnectionTracker;
|
||||||
logger: Logger;
|
logger: Logger;
|
||||||
|
store: Store;
|
||||||
|
|
||||||
constructor(logger: Logger) {
|
constructor(logger: Logger, store: Store) {
|
||||||
super();
|
super();
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.connections = new Map();
|
this.connections = new Map();
|
||||||
this.certificateProvider = new CertificateProvider(this, logger);
|
this.certificateProvider = new CertificateProvider(this, logger);
|
||||||
this.connectionTracker = new ConnectionTracker(logger);
|
this.connectionTracker = new ConnectionTracker(logger);
|
||||||
|
this.store = store;
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,7 +247,7 @@ export default class Server extends EventEmitter {
|
|||||||
const id = `${query.app}-${query.os}-${query.device}-${query.device_id}`;
|
const id = `${query.app}-${query.os}-${query.device}-${query.device_id}`;
|
||||||
console.debug(`Device connected: ${id}`, 'server');
|
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 = {
|
const info = {
|
||||||
client,
|
client,
|
||||||
|
|||||||
Reference in New Issue
Block a user