/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @format */ import produce from 'immer'; import {deconstructPluginKey} from 'flipper-common'; export const DEFAULT_MAX_QUEUE_SIZE = 10000; export type Message = { method: string; params?: any; }; export type State = { [pluginKey: string]: Message[]; }; export type Action = | { type: 'QUEUE_MESSAGES'; payload: { pluginKey: string; // client + plugin maxQueueSize: number; messages: Message[]; }; } | { type: 'CLEAR_MESSAGE_QUEUE'; payload: { pluginKey: string; // client + plugin amount?: number; }; } | { type: 'CLEAR_CLIENT_PLUGINS_STATE'; payload: {clientId: string; devicePlugins: Set}; } | { type: 'CLEAR_PLUGIN_STATE'; payload: {pluginId: string}; }; const INITIAL_STATE: State = {}; export default function reducer( state: State | undefined = INITIAL_STATE, action: Action, ): State { switch (action.type) { case 'QUEUE_MESSAGES': { const {pluginKey, messages, maxQueueSize} = action.payload; // this is hit very often, so try to do it a bit optimal const currentMessages = state[pluginKey] || []; let newMessages = currentMessages.concat(messages); if (newMessages.length > maxQueueSize) { // only keep last 90% of max queue size newMessages = newMessages.slice( newMessages.length - 1 - Math.ceil(maxQueueSize * 0.9), ); } return { ...state, [pluginKey]: newMessages, }; } case 'CLEAR_MESSAGE_QUEUE': { const {pluginKey, amount} = action.payload; return produce(state, (draft) => { const messages = draft[pluginKey]; if (messages) { if (amount === undefined) { delete draft[pluginKey]; } else { messages.splice(0, amount); } } }); } case 'CLEAR_CLIENT_PLUGINS_STATE': { const {payload} = action; return Object.keys(state).reduce((newState: State, pluginKey) => { // Only add the pluginState, if its from a plugin other than the one that // was removed. pluginKeys are in the form of ${clientID}#${pluginID}. const plugin = deconstructPluginKey(pluginKey); const clientId = plugin.client; const pluginId = plugin.pluginName; if ( clientId !== payload.clientId || (pluginId && payload.devicePlugins.has(pluginId)) ) { newState[pluginKey] = state[pluginKey]; } return newState; }, {}); } case 'CLEAR_PLUGIN_STATE': { const {pluginId} = action.payload; return produce(state, (draft) => { Object.keys(draft).forEach((pluginKey) => { const pluginKeyParts = deconstructPluginKey(pluginKey); if (pluginKeyParts.pluginName === pluginId) { delete draft[pluginKey]; } }); }); } default: return state; } } export const queueMessages = ( pluginKey: string, messages: Message[], maxQueueSize: number | undefined, ): Action => ({ type: 'QUEUE_MESSAGES', payload: { pluginKey, messages, maxQueueSize: maxQueueSize || DEFAULT_MAX_QUEUE_SIZE, }, }); export const clearMessageQueue = ( pluginKey: string, amount?: number, ): Action => ({ type: 'CLEAR_MESSAGE_QUEUE', payload: {pluginKey, amount}, });