diff --git a/desktop/app/src/devices/AndroidDevice.tsx b/desktop/app/src/devices/AndroidDevice.tsx index 42012d2f1f5..b25be5f2841 100644 --- a/desktop/app/src/devices/AndroidDevice.tsx +++ b/desktop/app/src/devices/AndroidDevice.tsx @@ -93,7 +93,10 @@ export default class AndroidDevice extends BaseDevice { this.adb.shell(this.serial, shellCommand); } - screenshot(): Promise { + async screenshot(): Promise { + if (this.isArchived) { + return Buffer.from([]); + } return new Promise((resolve, reject) => { this.adb.screencap(this.serial).then((stream) => { const chunks: Array = []; @@ -108,6 +111,9 @@ export default class AndroidDevice extends BaseDevice { } async screenCaptureAvailable(): Promise { + if (this.isArchived) { + return false; + } try { await this.executeShell( `[ ! -f /system/bin/screenrecord ] && echo "File does not exist"`, diff --git a/desktop/app/src/devices/BaseDevice.tsx b/desktop/app/src/devices/BaseDevice.tsx index accfe0ce471..2803d67d553 100644 --- a/desktop/app/src/devices/BaseDevice.tsx +++ b/desktop/app/src/devices/BaseDevice.tsx @@ -99,11 +99,15 @@ export default class BaseDevice { async exportState( idler: Idler, onStatusMessage: (msg: string) => void, + selectedPlugins: string[], ): Promise> { const pluginStates: Record = {}; for (const instance of this.sandyPluginStates.values()) { - if (instance.isPersistable()) { + if ( + selectedPlugins.includes(instance.definition.id) && + instance.isPersistable() + ) { pluginStates[instance.definition.id] = await instance.exportState( idler, onStatusMessage, diff --git a/desktop/app/src/devices/IOSDevice.tsx b/desktop/app/src/devices/IOSDevice.tsx index 4c3fe718c28..31584f65da0 100644 --- a/desktop/app/src/devices/IOSDevice.tsx +++ b/desktop/app/src/devices/IOSDevice.tsx @@ -52,7 +52,10 @@ export default class IOSDevice extends BaseDevice { this.startLogListener(); } - screenshot(): Promise { + async screenshot(): Promise { + if (this.isArchived) { + return Buffer.from([]); + } const tmpImageName = uuid() + '.png'; const tmpDirectory = (electron.app || electron.remote.app).getPath('temp'); const tmpFilePath = path.join(tmpDirectory, tmpImageName); @@ -189,7 +192,7 @@ export default class IOSDevice extends BaseDevice { } async screenCaptureAvailable() { - return this.deviceType === 'emulator'; + return this.deviceType === 'emulator' && !this.isArchived; } async startScreenCapture(destination: string) { diff --git a/desktop/app/src/sandy-chrome/appinspect/PluginList.tsx b/desktop/app/src/sandy-chrome/appinspect/PluginList.tsx index 8600fa86acd..1ae0c5e1fe2 100644 --- a/desktop/app/src/sandy-chrome/appinspect/PluginList.tsx +++ b/desktop/app/src/sandy-chrome/appinspect/PluginList.tsx @@ -18,7 +18,7 @@ import { DownloadOutlined, } from '@ant-design/icons'; import {Glyph, Layout, styled} from '../../ui'; -import {theme, NUX, Tracked} from 'flipper-plugin'; +import {theme, NUX, Tracked, useValue} from 'flipper-plugin'; import {useDispatch, useStore} from '../../utils/useStore'; import { computePluginLists, @@ -85,7 +85,7 @@ export const PluginList = memo(function PluginList({ connections.userStarredPlugins, pluginsChanged, ]); - const isArchived = !!activeDevice?.isArchived; + const isArchived = useValue(activeDevice?.archivedState, false); const annotatedDownloadablePlugins = useMemoize< [ diff --git a/desktop/app/src/utils/__tests__/exportData.node.tsx b/desktop/app/src/utils/__tests__/exportData.node.tsx index 9bf0c6ed22f..2a5203c57ef 100644 --- a/desktop/app/src/utils/__tests__/exportData.node.tsx +++ b/desktop/app/src/utils/__tests__/exportData.node.tsx @@ -20,7 +20,7 @@ import { import {FlipperPlugin, FlipperDevicePlugin} from '../../plugin'; import {Notification} from '../../plugin'; import {default as Client, ClientExport} from '../../Client'; -import {State as PluginsState} from '../../reducers/plugins'; +import {selectedPlugins, State as PluginsState} from '../../reducers/plugins'; import {createMockFlipperWithPlugin} from '../../test-utils/createMockFlipperWithPlugin'; import { TestUtils, @@ -1321,13 +1321,19 @@ test('Sandy device plugins are exported / imported properly', async () => { const device2 = store.getState().connections.devices[1]; expect(device2).not.toBeFalsy(); expect(device2).not.toBe(device); - expect(device2.devicePlugins).toEqual([TestPlugin.id]); + expect(device2.devicePlugins).toEqual([sandyDeviceTestPlugin.id]); - const {counter} = device2.sandyPluginStates.get(TestPlugin.id)?.instanceApi; + const {counter} = device2.sandyPluginStates.get( + sandyDeviceTestPlugin.id, + )?.instanceApi; counter.set(counter.get() + 1); expect( - (await device.exportState(testIdler, testOnStatusMessage))[TestPlugin.id], + ( + await device.exportState(testIdler, testOnStatusMessage, [ + sandyDeviceTestPlugin.id, + ]) + )[sandyDeviceTestPlugin.id], ).toMatchInlineSnapshot(` Object { "counter": 0, @@ -1336,8 +1342,11 @@ test('Sandy device plugins are exported / imported properly', async () => { }, } `); - expect(await device2.exportState(testIdler, testOnStatusMessage)) - .toMatchInlineSnapshot(` + expect( + await device2.exportState(testIdler, testOnStatusMessage, [ + sandyDeviceTestPlugin.id, + ]), + ).toMatchInlineSnapshot(` Object { "TestPlugin": Object { "counter": 4, @@ -1358,6 +1367,7 @@ test('Sandy device plugins with custom export are export properly', async () => .get(sandyDeviceTestPlugin.id) ?.instanceApi.enableCustomExport(); + store.dispatch(selectedPlugins([sandyDeviceTestPlugin.id])); const storeExport = await exportStore(store); expect(storeExport.exportStoreData.device!.pluginStates).toEqual({ [sandyDeviceTestPlugin.id]: {customExport: true}, @@ -1522,8 +1532,9 @@ test('Sandy plugins with complex data are imported / exported correctly', async }, ); - const {store} = await createMockFlipperWithPlugin(plugin); + const {store, client} = await createMockFlipperWithPlugin(plugin); + client.disconnect(); // lets make sure we can still export disconnected clients const data = await exportStore(store); expect(Object.values(data.exportStoreData.pluginStates2)).toMatchObject([ { @@ -1591,6 +1602,7 @@ test('Sandy device plugins with complex data are imported / exported correctly' ); const {store} = await createMockFlipperWithPlugin(deviceplugin); + store.dispatch(selectedPlugins([deviceplugin.id])); const data = await exportStore(store); expect(data.exportStoreData.device?.pluginStates).toMatchObject({ diff --git a/desktop/app/src/utils/exportData.tsx b/desktop/app/src/utils/exportData.tsx index f209ebf37fd..24b3a02c35c 100644 --- a/desktop/app/src/utils/exportData.tsx +++ b/desktop/app/src/utils/exportData.tsx @@ -431,10 +431,14 @@ export async function processStore( statusUpdate = () => {}; } statusUpdate('Capturing screenshot...'); - const deviceScreenshot = await capture(device).catch((e) => { - console.warn('Failed to capture device screenshot when exporting. ' + e); - return null; - }); + const deviceScreenshot = device.isArchived + ? null + : await capture(device).catch((e) => { + console.warn( + 'Failed to capture device screenshot when exporting. ' + e, + ); + return null; + }); const processedClients = processClients(clients, serial, statusUpdate); const processedPluginStates = processPluginStates({ clients: processedClients, @@ -461,7 +465,7 @@ export async function processStore( ); const devicePluginStates = await makeObjectSerializable( - await device.exportState(idler, statusUpdate), + await device.exportState(idler, statusUpdate, selectedPlugins), idler, statusUpdate, 'Serializing device plugins', @@ -610,11 +614,7 @@ export function determinePluginsToProcess( const selectedPlugins = plugins.selectedPlugins; for (const client of clients) { - if ( - !selectedDevice || - selectedDevice.isArchived || - client.query.device_id !== selectedDevice.serial - ) { + if (!selectedDevice || client.query.device_id !== selectedDevice.serial) { continue; } const selectedFilteredPlugins = client diff --git a/desktop/app/src/utils/pluginUtils.tsx b/desktop/app/src/utils/pluginUtils.tsx index a123d42faf8..c95e238b188 100644 --- a/desktop/app/src/utils/pluginUtils.tsx +++ b/desktop/app/src/utils/pluginUtils.tsx @@ -27,6 +27,7 @@ import type { PluginDetails, } from 'flipper-plugin-lib'; import {filterNewestVersionOfEachPlugin} from '../dispatcher/plugins'; +import ArchivedDevice from '../devices/ArchivedDevice'; export const defaultEnabledBackgroundPlugins = ['Navigation']; // The navigation plugin is enabled always, to make sure the navigation features works @@ -300,11 +301,11 @@ function getFavoritePlugins( starredPlugins: undefined | string[], returnFavoredPlugins: boolean, // if false, unfavoried plugins are returned ): PluginDefinition[] { - if (device.isArchived) { + if (device instanceof ArchivedDevice) { if (!returnFavoredPlugins) { return []; } - // for archived plugins, all stored plugins are enabled + // for *imported* devices, all stored plugins are enabled return allPlugins.filter( (plugin) => client.plugins.indexOf(plugin.id) !== -1, ); diff --git a/desktop/app/src/utils/screenshot.tsx b/desktop/app/src/utils/screenshot.tsx index f102daa1b29..f74ac014a94 100644 --- a/desktop/app/src/utils/screenshot.tsx +++ b/desktop/app/src/utils/screenshot.tsx @@ -26,7 +26,11 @@ export function getFileName(extension: 'png' | 'mp4'): string { return `screencap-${new Date().toISOString().replace(/:/g, '')}.${extension}`; } -export function capture(device: BaseDevice): Promise { +export async function capture(device: BaseDevice): Promise { + if (device.isArchived) { + console.log('Skipping screenshot for archived device'); + return ''; + } const pngPath = path.join(CAPTURE_LOCATION, getFileName('png')); return reportPlatformFailures( device.screenshot().then((buffer) => writeBufferToFile(pngPath, buffer)), diff --git a/desktop/flipper-plugin/src/plugin/Plugin.tsx b/desktop/flipper-plugin/src/plugin/Plugin.tsx index cd716802f04..7dc7b6d1f60 100644 --- a/desktop/flipper-plugin/src/plugin/Plugin.tsx +++ b/desktop/flipper-plugin/src/plugin/Plugin.tsx @@ -276,7 +276,13 @@ export class SandyPluginInstance extends BasePluginInstance { private assertConnected() { this.assertNotDestroyed(); - if (!this.connected.get()) { + if ( + // This is a better-safe-than-sorry; just the first condition should suffice + !this.connected.get() || + !this.realClient.connected.get() || + !this.device.isConnected || + this.device.isArchived + ) { throw new Error('Plugin is not connected'); } } diff --git a/desktop/flipper-plugin/src/test-utils/test-utils.tsx b/desktop/flipper-plugin/src/test-utils/test-utils.tsx index 885a2f00fab..54adef10cb8 100644 --- a/desktop/flipper-plugin/src/test-utils/test-utils.tsx +++ b/desktop/flipper-plugin/src/test-utils/test-utils.tsx @@ -208,7 +208,7 @@ export function startPlugin>( isBackgroundPlugin(_pluginId: string) { return !!options?.isBackgroundPlugin; }, - connected: createState(false), + connected: createState(true), initPlugin() { this.connected.set(true); pluginInstance.connect(); diff --git a/desktop/plugins/logs/index.tsx b/desktop/plugins/logs/index.tsx index 033671ad1bf..aad737d7c99 100644 --- a/desktop/plugins/logs/index.tsx +++ b/desktop/plugins/logs/index.tsx @@ -345,7 +345,7 @@ export function devicePlugin(client: DevicePluginClient) { return { logs: entries .get() - .slice(-10000) + .slice(-100 * 1000) .map((e) => e.entry), }; });