diff --git a/desktop/app/src/Client.tsx b/desktop/app/src/Client.tsx index d464e772c..1b380886d 100644 --- a/desktop/app/src/Client.tsx +++ b/desktop/app/src/Client.tsx @@ -26,6 +26,7 @@ import invariant from 'invariant'; import {flipperRecorderAddEvent} from './utils/pluginStateRecorder'; import {getPluginKey} from './utils/pluginUtils'; import {processMessageLater} from './utils/messageQueue'; +import {sideEffect} from './utils/sideEffect'; type Plugins = Array; @@ -207,21 +208,25 @@ export default class Client extends EventEmitter { console.error(error); reject(error); }, 5000); - unsubscribe = this.store.subscribe(() => { - const newDeviceList = this.store.getState().connections.devices; - if (newDeviceList === this.lastSeenDeviceList) { - return; - } - this.lastSeenDeviceList = this.store.getState().connections.devices; - const matchingDevice = newDeviceList.find( - (device) => device.serial === this.query.device_id, - ); - if (matchingDevice) { - clearTimeout(timeout); - resolve(matchingDevice); - unsubscribe(); - } - }); + unsubscribe = sideEffect( + this.store, + {name: 'waitForDevice', throttleMs: 100}, + (state) => state.connections.devices, + (newDeviceList) => { + if (newDeviceList === this.lastSeenDeviceList) { + return; + } + this.lastSeenDeviceList = newDeviceList; + const matchingDevice = newDeviceList.find( + (device) => device.serial === this.query.device_id, + ); + if (matchingDevice) { + clearTimeout(timeout); + resolve(matchingDevice); + unsubscribe(); + } + }, + ); }), 'client-setMatchingDevice', ).then((device) => { diff --git a/desktop/app/src/dispatcher/notifications.tsx b/desktop/app/src/dispatcher/notifications.tsx index c0da098eb..305966174 100644 --- a/desktop/app/src/dispatcher/notifications.tsx +++ b/desktop/app/src/dispatcher/notifications.tsx @@ -24,6 +24,7 @@ import GK from '../fb-stubs/GK'; import {deconstructPluginKey} from '../utils/clientUtils'; import NotificationScreen from '../chrome/NotificationScreen'; import {getPluginTitle} from '../utils/pluginUtils'; +import {sideEffect} from '../utils/sideEffect'; type NotificationEvents = 'show' | 'click' | 'close' | 'reply' | 'action'; const NOTIFICATION_THROTTLE = 5 * 1000; // in milliseconds @@ -84,118 +85,114 @@ export default (store: Store, logger: Logger) => { }, ); - store.subscribe(() => { - const {notifications, pluginStates} = store.getState(); + sideEffect( + store, + {name: 'notifications', throttleMs: 500}, + ({notifications, pluginStates, plugins}) => ({ + notifications, + pluginStates, + devicePlugins: plugins.devicePlugins, + clientPlugins: plugins.clientPlugins, + }), + ({notifications, pluginStates, devicePlugins, clientPlugins}, store) => { + function getPlugin(name: string) { + return devicePlugins.get(name) ?? clientPlugins.get(name); + } - const clientPlugins: Map = store.getState() - .plugins.clientPlugins; + Object.keys(pluginStates).forEach((key) => { + if (knownPluginStates.get(key) !== pluginStates[key]) { + knownPluginStates.set(key, pluginStates[key]); + const plugin = deconstructPluginKey(key); + const pluginName = plugin.pluginName; + const client = plugin.client; - const devicePlugins: Map< - string, - typeof FlipperDevicePlugin - > = store.getState().plugins.devicePlugins; + if (!pluginName) { + return; + } - const pluginMap: Map< - string, - typeof FlipperPlugin | typeof FlipperDevicePlugin - > = new Map([ - ...clientPlugins, - ...devicePlugins, - ]); - - Object.keys(pluginStates).forEach((key) => { - if (knownPluginStates.get(key) !== pluginStates[key]) { - knownPluginStates.set(key, pluginStates[key]); - const plugin = deconstructPluginKey(key); - const pluginName = plugin.pluginName; - const client = plugin.client; - - if (!pluginName) { - return; - } - - const persistingPlugin: - | undefined - | typeof FlipperPlugin - | typeof FlipperDevicePlugin = pluginMap.get(pluginName); - if (persistingPlugin && persistingPlugin.getActiveNotifications) { - try { - const notifications = persistingPlugin.getActiveNotifications( - pluginStates[key], - ); - store.dispatch( - setActiveNotifications({ - notifications, - client, - pluginId: pluginName, - }), - ); - } catch (e) { - console.error( - 'Failed to compute notifications for plugin ' + pluginName, - e, - ); + const persistingPlugin: + | undefined + | typeof FlipperPlugin + | typeof FlipperDevicePlugin = getPlugin(pluginName); + if (persistingPlugin && persistingPlugin.getActiveNotifications) { + try { + const notifications = persistingPlugin.getActiveNotifications( + pluginStates[key], + ); + store.dispatch( + setActiveNotifications({ + notifications, + client, + pluginId: pluginName, + }), + ); + } catch (e) { + console.error( + 'Failed to compute notifications for plugin ' + pluginName, + e, + ); + } } } - } - }); + }); - const { - activeNotifications, - blacklistedPlugins, - blacklistedCategories, - } = notifications; - - activeNotifications.forEach((n: PluginNotification) => { - if ( - !isHeadless() && - store.getState().connections.selectedPlugin !== 'notifications' && - !knownNotifications.has(n.notification.id) && - blacklistedPlugins.indexOf(n.pluginId) === -1 && - (!n.notification.category || - blacklistedCategories.indexOf(n.notification.category) === -1) - ) { - const prevNotificationTime: number = - lastNotificationTime.get(n.pluginId) || 0; - lastNotificationTime.set(n.pluginId, new Date().getTime()); - knownNotifications.add(n.notification.id); + const { + activeNotifications, + blacklistedPlugins, + blacklistedCategories, + } = notifications; + activeNotifications.forEach((n: PluginNotification) => { if ( - new Date().getTime() - prevNotificationTime < - NOTIFICATION_THROTTLE + !isHeadless() && + store.getState().connections.selectedPlugin !== 'notifications' && + !knownNotifications.has(n.notification.id) && + blacklistedPlugins.indexOf(n.pluginId) === -1 && + (!n.notification.category || + blacklistedCategories.indexOf(n.notification.category) === -1) ) { - // Don't send a notification if the plugin has sent a notification - // within the NOTIFICATION_THROTTLE. - return; + const prevNotificationTime: number = + lastNotificationTime.get(n.pluginId) || 0; + lastNotificationTime.set(n.pluginId, new Date().getTime()); + knownNotifications.add(n.notification.id); + + if ( + new Date().getTime() - prevNotificationTime < + NOTIFICATION_THROTTLE + ) { + // Don't send a notification if the plugin has sent a notification + // within the NOTIFICATION_THROTTLE. + return; + } + const plugin = getPlugin(n.pluginId); + ipcRenderer.send('sendNotification', { + payload: { + title: n.notification.title, + body: textContent(n.notification.message), + actions: [ + { + type: 'button', + text: 'Show', + }, + { + type: 'button', + text: 'Hide similar', + }, + { + type: 'button', + text: `Hide all ${ + plugin != null ? getPluginTitle(plugin) : '' + }`, + }, + ], + closeButtonText: 'Hide', + }, + closeAfter: 10000, + pluginNotification: n, + }); + logger.track('usage', 'native-notification', n.notification); } - const plugin = pluginMap.get(n.pluginId); - ipcRenderer.send('sendNotification', { - payload: { - title: n.notification.title, - body: textContent(n.notification.message), - actions: [ - { - type: 'button', - text: 'Show', - }, - { - type: 'button', - text: 'Hide similar', - }, - { - type: 'button', - text: `Hide all ${ - plugin != null ? getPluginTitle(plugin) : '' - }`, - }, - ], - closeButtonText: 'Hide', - }, - closeAfter: 10000, - pluginNotification: n, - }); - logger.track('usage', 'native-notification', n.notification); - } - }); - }); + }); + }, + ); }; diff --git a/desktop/app/src/dispatcher/plugins.tsx b/desktop/app/src/dispatcher/plugins.tsx index 2069e8828..df95abb58 100644 --- a/desktop/app/src/dispatcher/plugins.tsx +++ b/desktop/app/src/dispatcher/plugins.tsx @@ -29,6 +29,7 @@ import path from 'path'; import {default as config} from '../utils/processConfig'; import isProduction from '../utils/isProduction'; import {notNull} from '../utils/typeUtils'; +import {sideEffect} from '../utils/sideEffect'; export type PluginDefinition = { id?: string; @@ -63,20 +64,17 @@ export default (store: Store, _logger: Logger) => { store.dispatch(addFailedPlugins(failedPlugins)); store.dispatch(registerPlugins(initialPlugins)); - let state: State | null = null; - store.subscribe(() => { - const newState = store.getState().plugins; - if (state !== newState) { + sideEffect( + store, + {name: 'setupMenuBar', throttleMs: 100}, + (state) => state.plugins, + (plugins, store) => { setupMenuBar( - [ - ...newState.devicePlugins.values(), - ...newState.clientPlugins.values(), - ], + [...plugins.devicePlugins.values(), ...plugins.clientPlugins.values()], store, ); - } - state = newState; - }); + }, + ); }; function getBundledPlugins(): Array { diff --git a/desktop/app/src/dispatcher/user.tsx b/desktop/app/src/dispatcher/user.tsx index 02b4fb96d..34a79ab13 100644 --- a/desktop/app/src/dispatcher/user.tsx +++ b/desktop/app/src/dispatcher/user.tsx @@ -11,6 +11,7 @@ import {Store} from '../reducers/index'; import {Logger} from '../fb-interfaces/Logger'; import {login, logout} from '../reducers/user'; import {getUser, logoutUser} from '../fb-stubs/user'; +import {sideEffect} from '../utils/sideEffect'; export default (store: Store, _logger: Logger) => { getUser() @@ -23,10 +24,15 @@ export default (store: Store, _logger: Logger) => { }); let prevUserName = store.getState().user.name; - store.subscribe(() => { - if (prevUserName && !store.getState().user.name) { - logoutUser(); - } - prevUserName = store.getState().user.name; - }); + sideEffect( + store, + {name: 'logout', throttleMs: 500}, + (state) => state.user.name, + (userName) => { + if (prevUserName && !userName) { + logoutUser(); + } + prevUserName = userName; + }, + ); };