diff --git a/desktop/app/src/Client.tsx b/desktop/app/src/Client.tsx index 17fb48b0b..4fa7c8097 100644 --- a/desktop/app/src/Client.tsx +++ b/desktop/app/src/Client.tsx @@ -601,46 +601,48 @@ export default class Client extends EventEmitter { const mark = this.getPerformanceMark(metadata); performance.mark(mark); + if (!this.connected) { + reject(new Error('Not connected to client')); + } if (!fromPlugin || this.isAcceptingMessagesFromPlugin(plugin)) { - this.connection && - this.connection - .requestResponse({data: JSON.stringify(data)}) - .subscribe({ - onComplete: (payload) => { - if (!fromPlugin || this.isAcceptingMessagesFromPlugin(plugin)) { - const logEventName = this.getLogEventName(data); - this.logger.trackTimeSince(mark, logEventName); - emitBytesReceived( - plugin || 'unknown', - payload.data.length * 2, - ); - const response: { - success?: Object; - error?: ErrorType; - } = JSON.parse(payload.data); + this.connection!.requestResponse({ + data: JSON.stringify(data), + }).subscribe({ + onComplete: (payload) => { + if (!fromPlugin || this.isAcceptingMessagesFromPlugin(plugin)) { + const logEventName = this.getLogEventName(data); + this.logger.trackTimeSince(mark, logEventName); + emitBytesReceived(plugin || 'unknown', payload.data.length * 2); + const response: { + success?: Object; + error?: ErrorType; + } = JSON.parse(payload.data); - this.onResponse(response, resolve, reject); + this.onResponse(response, resolve, reject); - if (flipperMessagesClientPlugin.isConnected()) { - flipperMessagesClientPlugin.newMessage({ - device: this.deviceSync?.displayTitle(), - app: this.query.app, - flipperInternalMethod: method, - payload: response, - plugin, - pluginMethod: params?.method, - direction: 'toFlipper:response', - }); - } - } - }, - // Open fresco then layout and you get errors because responses come back after deinit. - onError: (e) => { - if (this.isAcceptingMessagesFromPlugin(plugin)) { - reject(e); - } - }, - }); + if (flipperMessagesClientPlugin.isConnected()) { + flipperMessagesClientPlugin.newMessage({ + device: this.deviceSync?.displayTitle(), + app: this.query.app, + flipperInternalMethod: method, + payload: response, + plugin, + pluginMethod: params?.method, + direction: 'toFlipper:response', + }); + } + } + }, + onError: (e) => { + reject(e); + }, + }); + } else { + reject( + new Error( + `Cannot send ${method}, client is not accepting messages for plugin ${plugin}`, + ), + ); } if (flipperMessagesClientPlugin.isConnected()) { diff --git a/desktop/app/src/plugin.tsx b/desktop/app/src/plugin.tsx index d56eb9021..f35912e58 100644 --- a/desktop/app/src/plugin.tsx +++ b/desktop/app/src/plugin.tsx @@ -61,6 +61,7 @@ export function supportsMethod( } export interface PluginClient { + isConnected: boolean; // eslint-disable-next-line send(method: string, params?: Parameters): void; // eslint-disable-next-line @@ -130,7 +131,9 @@ export abstract class FlipperBasePlugin< static maxQueueSize: number = DEFAULT_MAX_QUEUE_SIZE; static exportPersistedState: | (( - callClient: (method: string, params?: any) => Promise, + callClient: + | undefined + | ((method: string, params?: any) => Promise), persistedState: StaticPersistedState | undefined, store: ReduxState | undefined, idler?: Idler, @@ -256,8 +259,11 @@ export class FlipperPlugin< // @ts-ignore constructor should be assigned already const {id} = this.constructor; this.subscriptions = []; - this.realClient = props.target as Client; + const realClient = (this.realClient = props.target as Client); this.client = { + get isConnected() { + return realClient.connected.get(); + }, call: (method, params) => this.realClient.call(id, method, true, params), send: (method, params) => this.realClient.send(id, method, params), subscribe: (method, callback) => { diff --git a/desktop/app/src/utils/exportData.tsx b/desktop/app/src/utils/exportData.tsx index 61d013f31..5254b1ef4 100644 --- a/desktop/app/src/utils/exportData.tsx +++ b/desktop/app/src/utils/exportData.tsx @@ -518,9 +518,13 @@ export async function fetchMetadata( client, pluginKey, } of pluginsToProcess) { - const exportState = pluginClass ? pluginClass.exportPersistedState : null; + const exportState = + pluginClass && !isSandyPlugin(pluginClass) + ? pluginClass.exportPersistedState + : null; if (exportState) { const fetchMetaDataMarker = `${EXPORT_FLIPPER_TRACE_EVENT}:fetch-meta-data-per-plugin`; + const isConnected = client.connected.get(); performance.mark(fetchMetaDataMarker); try { statusUpdate && @@ -528,12 +532,14 @@ export async function fetchMetadata( const data = await promiseTimeout( 240000, // Fetching MobileConfig data takes ~ 3 mins, thus keeping timeout at 4 mins. exportState( - callClient(client, pluginId), + isConnected ? callClient(client, pluginId) : undefined, newPluginState[pluginKey], state, idler, statusUpdate, - supportsMethod(client, pluginId), + isConnected + ? supportsMethod(client, pluginId) + : () => Promise.resolve(false), ), `Timed out while collecting data for ${pluginName}`, ); diff --git a/desktop/app/src/utils/pluginUtils.tsx b/desktop/app/src/utils/pluginUtils.tsx index c95e238b1..11ec3b4a6 100644 --- a/desktop/app/src/utils/pluginUtils.tsx +++ b/desktop/app/src/utils/pluginUtils.tsx @@ -110,7 +110,7 @@ function isExportablePlugin( plugin: PluginDefinition, ): boolean { // can generate an export when requested - if (plugin.exportPersistedState) { + if (!isSandyPlugin(plugin) && plugin.exportPersistedState) { return true; } const pluginKey = isDevicePluginDefinition(plugin) diff --git a/desktop/flipper-plugin/src/plugin/SandyPluginDefinition.tsx b/desktop/flipper-plugin/src/plugin/SandyPluginDefinition.tsx index bfb4cb41e..0dbe8d3e8 100644 --- a/desktop/flipper-plugin/src/plugin/SandyPluginDefinition.tsx +++ b/desktop/flipper-plugin/src/plugin/SandyPluginDefinition.tsx @@ -45,18 +45,6 @@ export class SandyPluginDefinition { details: ActivatablePluginDetails; isDevicePlugin: boolean; - // TODO: Implement T68683476 - exportPersistedState: - | (( - callClient: (method: string, params?: any) => Promise, - persistedState: any, // TODO: type StaticPersistedState | undefined, - store: any, // TODO: ReduxState | undefined, - idler?: any, // TODO: Idler, - statusUpdate?: (msg: string) => void, - supportsMethod?: (method: string) => Promise, - ) => Promise) - | undefined = undefined; - constructor( details: ActivatablePluginDetails, module: FlipperPluginModule | FlipperDevicePluginModule, diff --git a/desktop/plugins/fresco/index.tsx b/desktop/plugins/fresco/index.tsx index 0d6fa8847..c01b3dc54 100644 --- a/desktop/plugins/fresco/index.tsx +++ b/desktop/plugins/fresco/index.tsx @@ -96,7 +96,7 @@ export default class FlipperImagesPlugin extends FlipperPlugin< }; static exportPersistedState = ( - callClient: (method: string, params?: any) => Promise, + callClient: undefined | ((method: string, params?: any) => Promise), persistedState: PersistedState, store?: ReduxState, ): Promise => { @@ -104,7 +104,7 @@ export default class FlipperImagesPlugin extends FlipperPlugin< if (!persistedState) { persistedState = FlipperImagesPlugin.defaultPersistedState; } - if (!store) { + if (!store || !callClient) { return defaultPromise; } return Promise.all([ diff --git a/desktop/plugins/layout/ProxyArchiveClient.tsx b/desktop/plugins/layout/ProxyArchiveClient.tsx index ae77aad47..ff26e0e52 100644 --- a/desktop/plugins/layout/ProxyArchiveClient.tsx +++ b/desktop/plugins/layout/ProxyArchiveClient.tsx @@ -91,6 +91,9 @@ class ProxyArchiveClient { this.persistedState = cloneDeep(persistedState); this.onElementHighlighted = onElementHighlighted; } + + isConnected = true; + persistedState: PersistedState; onElementHighlighted: ((id: string) => void) | undefined; subscribe(_method: string, _callback: (params: any) => void): void { diff --git a/desktop/plugins/layout/__tests__/Inspector.node.tsx b/desktop/plugins/layout/__tests__/Inspector.node.tsx index 6312e9226..08ecb5266 100644 --- a/desktop/plugins/layout/__tests__/Inspector.node.tsx +++ b/desktop/plugins/layout/__tests__/Inspector.node.tsx @@ -25,6 +25,7 @@ beforeEach(() => { extraInfo: {}, }; const client: PluginClient = { + isConnected: true, send: () => {}, call: () => Promise.resolve(mockRoot), subscribe: () => {}, diff --git a/desktop/plugins/layout/index.tsx b/desktop/plugins/layout/index.tsx index 818456da9..b61cd7e4e 100644 --- a/desktop/plugins/layout/index.tsx +++ b/desktop/plugins/layout/index.tsx @@ -80,14 +80,16 @@ export default class LayoutPlugin extends FlipperPlugin< PersistedState > { static exportPersistedState = async ( - callClient: (method: ClientMethodCalls, params?: any) => Promise, + callClient: + | undefined + | ((method: ClientMethodCalls, params?: any) => Promise), persistedState: PersistedState | undefined, store: ReduxState | undefined, _idler?: Idler | undefined, statusUpdate?: (msg: string) => void, supportsMethod?: (method: ClientMethodCalls) => Promise, ): Promise => { - if (!store) { + if (!store || !callClient) { return persistedState; } statusUpdate && statusUpdate('Fetching Root Node...'); @@ -211,26 +213,29 @@ export default class LayoutPlugin extends FlipperPlugin< // If the selected plugin from the previous session was layout, then while importing the flipper export, the redux store doesn't get updated in the first render, due to which the plugin crashes, as it has no persisted state this.props.setPersistedState(this.constructor.defaultPersistedState); } - // persist searchActive state when moving between plugins to prevent multiple - // TouchOverlayViews since we can't edit the view heirarchy in onDisconnect - this.client.call('isSearchActive').then(({isSearchActive}) => { - this.setState({inTargetMode: isSearchActive}); - }); - // disable target mode after - this.client.subscribe('select', () => { - if (this.state.inTargetMode) { - this.onToggleTargetMode(); - } - }); + if (this.client.isConnected) { + // persist searchActive state when moving between plugins to prevent multiple + // TouchOverlayViews since we can't edit the view heirarchy in onDisconnect + this.client.call('isSearchActive').then(({isSearchActive}) => { + this.setState({inTargetMode: isSearchActive}); + }); - this.client.subscribe('resolvePath', (params: ClassFileParams) => { - this.resolvePath(params); - }); + // disable target mode after + this.client.subscribe('select', () => { + if (this.state.inTargetMode) { + this.onToggleTargetMode(); + } + }); - this.client.subscribe('openInIDE', (params: OpenFileParams) => { - this.openInIDE(params); - }); + this.client.subscribe('resolvePath', (params: ClassFileParams) => { + this.resolvePath(params); + }); + + this.client.subscribe('openInIDE', (params: OpenFileParams) => { + this.openInIDE(params); + }); + } // since the first launch of Myles might produce a lag (Myles daemon needs to start) // try to invoke Myles during the first launch of the Layout Plugin @@ -270,10 +275,12 @@ export default class LayoutPlugin extends FlipperPlugin< params.dirRoot, ); const resolvedPath = IDEFileResolver.getBestPath(paths, params.className); - this.client.send('setResolvedPath', { - className: params.className, - resolvedPath: resolvedPath, - }); + if (this.client.isConnected) { + this.client.send('setResolvedPath', { + className: params.className, + resolvedPath: resolvedPath, + }); + } }; openInIDE = async (params: OpenFileParams) => { @@ -294,9 +301,11 @@ export default class LayoutPlugin extends FlipperPlugin< }; onToggleTargetMode = () => { - const inTargetMode = !this.state.inTargetMode; - this.setState({inTargetMode}); - this.client.send('setSearchActive', {active: inTargetMode}); + if (this.client.isConnected) { + const inTargetMode = !this.state.inTargetMode; + this.setState({inTargetMode}); + this.client.send('setSearchActive', {active: inTargetMode}); + } }; onToggleAXMode = () => { @@ -311,13 +320,15 @@ export default class LayoutPlugin extends FlipperPlugin< : this.client; } onToggleAlignmentMode = () => { - if (this.state.selectedElement) { - this.client.send('setHighlighted', { - id: this.state.selectedElement, - inAlignmentMode: !this.state.inAlignmentMode, - }); + if (this.client.isConnected) { + if (this.state.selectedElement) { + this.client.send('setHighlighted', { + id: this.state.selectedElement, + inAlignmentMode: !this.state.inAlignmentMode, + }); + this.setState({inAlignmentMode: !this.state.inAlignmentMode}); + } } - this.setState({inAlignmentMode: !this.state.inAlignmentMode}); }; onToggleVisualizer = () => {