Make flipper messages generally available, remove self inspection infra structure
Summary: Changelog: Flipper message debugging moved from a separate device to the console tab This makes message debugging easier accessible, and in production (recently requested at GH). Also it clears up a lot of infra that was created just to make flipper a self recursive inspection device + a separate plugin. While fun, a hardcoded setup is just a bit more simpler (no exception rules and better static verification) Reviewed By: nikoant Differential Revision: D29487811 fbshipit-source-id: b412adc3ef5bd831001333443b432b6c0f934a5e
This commit is contained in:
committed by
Facebook GitHub Bot
parent
8da7495a1a
commit
328ba9513c
@@ -31,10 +31,13 @@ import {
|
||||
_SandyPluginInstance,
|
||||
_getFlipperLibImplementation,
|
||||
} from 'flipper-plugin';
|
||||
import {flipperMessagesClientPlugin} from './utils/self-inspection/plugins/FlipperMessagesClientPlugin';
|
||||
import {freeze} from 'immer';
|
||||
import GK from './fb-stubs/GK';
|
||||
import {message} from 'antd';
|
||||
import {
|
||||
isFlipperMessageDebuggingEnabled,
|
||||
registerFlipperDebugMessage,
|
||||
} from './chrome/FlipperMessages';
|
||||
|
||||
type Plugins = Set<string>;
|
||||
type PluginsArr = Array<string>;
|
||||
@@ -384,11 +387,8 @@ export default class Client extends EventEmitter {
|
||||
|
||||
const {id, method} = data;
|
||||
|
||||
if (
|
||||
data.params?.api != 'flipper-messages' &&
|
||||
flipperMessagesClientPlugin.isConnected()
|
||||
) {
|
||||
flipperMessagesClientPlugin.newMessage({
|
||||
if (isFlipperMessageDebuggingEnabled()) {
|
||||
registerFlipperDebugMessage({
|
||||
device: this.deviceSync?.displayTitle(),
|
||||
app: this.query.app,
|
||||
flipperInternalMethod: method,
|
||||
@@ -416,7 +416,7 @@ export default class Client extends EventEmitter {
|
||||
const params: Params = data.params;
|
||||
const bytes = msg.length * 2; // string lengths are measured in UTF-16 units (not characters), so 2 bytes per char
|
||||
emitBytesReceived(params.api, bytes);
|
||||
if (bytes > 5 * 1024 * 1024 && params.api !== 'flipper-messages') {
|
||||
if (bytes > 5 * 1024 * 1024) {
|
||||
console.warn(
|
||||
`Plugin '${params.api}' received excessively large message for '${
|
||||
params.method
|
||||
@@ -462,12 +462,7 @@ export default class Client extends EventEmitter {
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: Flipper debug as full client is overkill, clean up
|
||||
if (
|
||||
!handled &&
|
||||
!isProduction() &&
|
||||
params.api !== 'flipper-messages'
|
||||
) {
|
||||
if (!handled && !isProduction()) {
|
||||
console.warn(`Unhandled message ${params.api}.${params.method}`);
|
||||
}
|
||||
}
|
||||
@@ -597,8 +592,8 @@ export default class Client extends EventEmitter {
|
||||
|
||||
this.onResponse(response, resolve, reject);
|
||||
|
||||
if (flipperMessagesClientPlugin.isConnected()) {
|
||||
flipperMessagesClientPlugin.newMessage({
|
||||
if (isFlipperMessageDebuggingEnabled()) {
|
||||
registerFlipperDebugMessage({
|
||||
device: this.deviceSync?.displayTitle(),
|
||||
app: this.query.app,
|
||||
flipperInternalMethod: method,
|
||||
@@ -625,8 +620,8 @@ export default class Client extends EventEmitter {
|
||||
);
|
||||
}
|
||||
|
||||
if (flipperMessagesClientPlugin.isConnected()) {
|
||||
flipperMessagesClientPlugin.newMessage({
|
||||
if (isFlipperMessageDebuggingEnabled()) {
|
||||
registerFlipperDebugMessage({
|
||||
device: this.deviceSync?.displayTitle(),
|
||||
app: this.query.app,
|
||||
flipperInternalMethod: method,
|
||||
@@ -711,8 +706,8 @@ export default class Client extends EventEmitter {
|
||||
this.connection.fireAndForget({data: JSON.stringify(data)});
|
||||
}
|
||||
|
||||
if (flipperMessagesClientPlugin.isConnected()) {
|
||||
flipperMessagesClientPlugin.newMessage({
|
||||
if (isFlipperMessageDebuggingEnabled()) {
|
||||
registerFlipperDebugMessage({
|
||||
device: this.deviceSync?.displayTitle(),
|
||||
app: this.query.app,
|
||||
flipperInternalMethod: method,
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
import {useMemo} from 'react';
|
||||
import {Button, ButtonGroup, Layout} from '../ui';
|
||||
import {Button, Layout} from '../ui';
|
||||
import React from 'react';
|
||||
import {Console, Hook} from 'console-feed';
|
||||
import type {Methods} from 'console-feed/lib/definitions/Methods';
|
||||
@@ -92,13 +92,11 @@ export function ConsoleLogs() {
|
||||
|
||||
return (
|
||||
<Layout.Top>
|
||||
<Toolbar>
|
||||
<ButtonGroup>
|
||||
<Toolbar wash>
|
||||
<Button onClick={clearLogs} icon="trash">
|
||||
Clear Logs
|
||||
</Button>
|
||||
<Button dropdown={dropdown}>Log Levels</Button>
|
||||
</ButtonGroup>
|
||||
</Toolbar>
|
||||
<Layout.ScrollContainer vertical>
|
||||
<Console
|
||||
|
||||
29
desktop/app/src/chrome/FlipperDevTools.tsx
Normal file
29
desktop/app/src/chrome/FlipperDevTools.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its 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 {Layout} from '../ui';
|
||||
import React from 'react';
|
||||
import {Tab, Tabs} from 'flipper-plugin';
|
||||
import {ConsoleLogs} from './ConsoleLogs';
|
||||
import {FlipperMessages} from './FlipperMessages';
|
||||
|
||||
export function FlipperDevTools() {
|
||||
return (
|
||||
<Layout.Container pad grow>
|
||||
<Tabs grow>
|
||||
<Tab tab="Console">
|
||||
<ConsoleLogs />
|
||||
</Tab>
|
||||
<Tab tab="Messages">
|
||||
<FlipperMessages />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</Layout.Container>
|
||||
);
|
||||
}
|
||||
209
desktop/app/src/chrome/FlipperMessages.tsx
Normal file
209
desktop/app/src/chrome/FlipperMessages.tsx
Normal file
@@ -0,0 +1,209 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its 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 {
|
||||
DataInspector,
|
||||
DataTable,
|
||||
DataTableColumn,
|
||||
Layout,
|
||||
createState,
|
||||
createDataSource,
|
||||
DetailSidebar,
|
||||
Panel,
|
||||
theme,
|
||||
styled,
|
||||
useValue,
|
||||
} from 'flipper-plugin';
|
||||
import {Button} from 'antd';
|
||||
import {
|
||||
DeleteOutlined,
|
||||
PauseCircleOutlined,
|
||||
PlayCircleOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import React, {useCallback, useState} from 'react';
|
||||
|
||||
export type MessageInfo = {
|
||||
time?: Date;
|
||||
device?: string;
|
||||
app: string;
|
||||
flipperInternalMethod?: string;
|
||||
plugin?: string;
|
||||
pluginMethod?: string;
|
||||
payload?: any;
|
||||
direction:
|
||||
| 'toClient:call'
|
||||
| 'toClient:send'
|
||||
| 'toFlipper:message'
|
||||
| 'toFlipper:response';
|
||||
};
|
||||
|
||||
export interface MessageRow extends MessageInfo {
|
||||
time: Date;
|
||||
}
|
||||
|
||||
const Placeholder = styled(Layout.Container)({
|
||||
center: true,
|
||||
color: theme.textColorPlaceholder,
|
||||
fontSize: 18,
|
||||
});
|
||||
|
||||
function createRow(message: MessageInfo): MessageRow {
|
||||
return {
|
||||
...message,
|
||||
time: message.time == null ? new Date() : message.time,
|
||||
};
|
||||
}
|
||||
|
||||
const COLUMN_CONFIG: DataTableColumn<MessageRow>[] = [
|
||||
{
|
||||
key: 'time',
|
||||
title: 'Time',
|
||||
},
|
||||
{
|
||||
key: 'device',
|
||||
title: 'Device',
|
||||
},
|
||||
{
|
||||
key: 'app',
|
||||
title: 'App',
|
||||
},
|
||||
{
|
||||
key: 'flipperInternalMethod',
|
||||
title: 'Flipper Internal Method',
|
||||
},
|
||||
{
|
||||
key: 'plugin',
|
||||
title: 'Plugin',
|
||||
},
|
||||
{
|
||||
key: 'pluginMethod',
|
||||
title: 'Method',
|
||||
},
|
||||
{
|
||||
key: 'direction',
|
||||
title: 'Direction',
|
||||
},
|
||||
];
|
||||
|
||||
const flipperDebugMessages = createDataSource<MessageRow>([], {
|
||||
limit: 1024 * 10,
|
||||
persist: 'messages',
|
||||
});
|
||||
const flipperDebugMessagesEnabled = createState(false);
|
||||
|
||||
export function registerFlipperDebugMessage(message: MessageInfo) {
|
||||
if (flipperDebugMessagesEnabled.get()) {
|
||||
flipperDebugMessages.append(createRow(message));
|
||||
}
|
||||
}
|
||||
|
||||
export function isFlipperMessageDebuggingEnabled(): boolean {
|
||||
return flipperDebugMessagesEnabled.get();
|
||||
}
|
||||
|
||||
// exposed for testing
|
||||
export function setFlipperMessageDebuggingEnabled(value: boolean) {
|
||||
flipperDebugMessagesEnabled.set(value);
|
||||
}
|
||||
|
||||
// exposed for testing
|
||||
export function clearFlipperDebugMessages() {
|
||||
flipperDebugMessages.clear();
|
||||
}
|
||||
|
||||
// exposed for testing ONLY!
|
||||
export function getFlipperDebugMessages() {
|
||||
return flipperDebugMessages.records();
|
||||
}
|
||||
|
||||
function Sidebar({selection}: {selection: undefined | MessageRow}) {
|
||||
const renderExtra = (extra: any) => (
|
||||
<Panel title={'Payload'} collapsible={false}>
|
||||
<DataInspector data={extra} expandRoot={false} />
|
||||
</Panel>
|
||||
);
|
||||
|
||||
return (
|
||||
<DetailSidebar>
|
||||
{selection != null ? (
|
||||
renderExtra(selection.payload)
|
||||
) : (
|
||||
<Placeholder grow pad="large">
|
||||
Select a message to view details
|
||||
</Placeholder>
|
||||
)}
|
||||
</DetailSidebar>
|
||||
);
|
||||
}
|
||||
|
||||
const PauseResumeButton = () => {
|
||||
const paused = !useValue(flipperDebugMessagesEnabled);
|
||||
|
||||
return (
|
||||
<Button
|
||||
title={`Click to enable tracing flipper messages`}
|
||||
danger={!paused}
|
||||
onClick={() => {
|
||||
flipperDebugMessagesEnabled.update((v) => !v);
|
||||
}}>
|
||||
{paused ? <PlayCircleOutlined /> : <PauseCircleOutlined />}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export function FlipperMessages() {
|
||||
const [selection, setSelection] = useState<MessageRow | undefined>();
|
||||
const paused = !useValue(flipperDebugMessagesEnabled);
|
||||
|
||||
const clearTableButton = (
|
||||
<Button
|
||||
title="Clear logs"
|
||||
onClick={() => {
|
||||
clearFlipperDebugMessages();
|
||||
setSelection(undefined);
|
||||
}}>
|
||||
<DeleteOutlined />
|
||||
</Button>
|
||||
);
|
||||
|
||||
const renderEmpty = useCallback(
|
||||
() => (
|
||||
<Layout.Container center pad gap style={{width: '100%', marginTop: 200}}>
|
||||
{paused ? (
|
||||
<>
|
||||
Click to enable debugging Flipper messages between the Flipper
|
||||
application and connected clients: <PauseResumeButton />
|
||||
</>
|
||||
) : (
|
||||
'Waiting for data...'
|
||||
)}
|
||||
</Layout.Container>
|
||||
),
|
||||
[paused],
|
||||
);
|
||||
|
||||
return (
|
||||
<Layout.Container grow>
|
||||
<DataTable<MessageRow>
|
||||
dataSource={flipperDebugMessages}
|
||||
columns={COLUMN_CONFIG}
|
||||
onSelect={setSelection}
|
||||
enableAutoScroll
|
||||
onRenderEmpty={renderEmpty}
|
||||
extraActions={
|
||||
<>
|
||||
<PauseResumeButton />
|
||||
{clearTableButton}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Sidebar selection={selection} />
|
||||
</Layout.Container>
|
||||
);
|
||||
}
|
||||
@@ -7,45 +7,67 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {TestUtils} from 'flipper-plugin';
|
||||
import * as React from 'react';
|
||||
import {act, render} from '@testing-library/react';
|
||||
|
||||
import * as Plugin from '../';
|
||||
import {MessageRow} from '../';
|
||||
import {
|
||||
clearFlipperDebugMessages,
|
||||
FlipperMessages,
|
||||
getFlipperDebugMessages,
|
||||
MessageRow,
|
||||
registerFlipperDebugMessage,
|
||||
setFlipperMessageDebuggingEnabled,
|
||||
} from '../FlipperMessages';
|
||||
|
||||
const fixRowTimestamps = (r: MessageRow): MessageRow => ({
|
||||
...r,
|
||||
time: new Date(Date.UTC(0, 0, 0, 0, 0, 0)),
|
||||
});
|
||||
|
||||
test('It can store rows', () => {
|
||||
const {instance, ...plugin} = TestUtils.startPlugin(Plugin);
|
||||
|
||||
expect(instance.rows.records()).toEqual([]);
|
||||
expect(instance.highlightedRow.get()).toBeUndefined();
|
||||
|
||||
plugin.sendEvent('newMessage', {
|
||||
app: 'Flipper',
|
||||
direction: 'toFlipper',
|
||||
beforeEach(() => {
|
||||
clearFlipperDebugMessages();
|
||||
setFlipperMessageDebuggingEnabled(true);
|
||||
});
|
||||
|
||||
plugin.sendEvent('newMessage', {
|
||||
afterEach(() => {
|
||||
clearFlipperDebugMessages();
|
||||
setFlipperMessageDebuggingEnabled(false);
|
||||
});
|
||||
|
||||
test('It can store rows', () => {
|
||||
registerFlipperDebugMessage({
|
||||
app: 'Flipper',
|
||||
direction: 'toFlipper:message',
|
||||
});
|
||||
|
||||
registerFlipperDebugMessage({
|
||||
app: 'FB4A',
|
||||
direction: 'toClient',
|
||||
direction: 'toClient:call',
|
||||
device: 'Android Phone',
|
||||
payload: {hello: 'world'},
|
||||
});
|
||||
|
||||
expect(instance.rows.records().map(fixRowTimestamps)).toMatchInlineSnapshot(`
|
||||
setFlipperMessageDebuggingEnabled(false);
|
||||
|
||||
registerFlipperDebugMessage({
|
||||
app: 'FB4A',
|
||||
direction: 'toClient:call',
|
||||
device: 'Android PhoneTEst',
|
||||
payload: {hello: 'world'},
|
||||
});
|
||||
|
||||
expect(getFlipperDebugMessages().map(fixRowTimestamps))
|
||||
.toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"app": "Flipper",
|
||||
"direction": "toFlipper",
|
||||
"direction": "toFlipper:message",
|
||||
"time": 1899-12-31T00:00:00.000Z,
|
||||
},
|
||||
Object {
|
||||
"app": "FB4A",
|
||||
"device": "Android Phone",
|
||||
"direction": "toClient",
|
||||
"direction": "toClient:call",
|
||||
"payload": Object {
|
||||
"hello": "world",
|
||||
},
|
||||
@@ -56,62 +78,44 @@ test('It can store rows', () => {
|
||||
});
|
||||
|
||||
test('It can clear', () => {
|
||||
const {instance, ...plugin} = TestUtils.startPlugin(Plugin);
|
||||
|
||||
expect(instance.rows.records()).toEqual([]);
|
||||
expect(instance.highlightedRow.get()).toBeUndefined();
|
||||
|
||||
plugin.sendEvent('newMessage', {
|
||||
registerFlipperDebugMessage({
|
||||
app: 'Flipper',
|
||||
direction: 'toFlipper',
|
||||
direction: 'toFlipper:message',
|
||||
});
|
||||
|
||||
instance.clear();
|
||||
|
||||
const newRows = instance.rows.records().map(fixRowTimestamps);
|
||||
expect(newRows).toEqual([]);
|
||||
});
|
||||
|
||||
test('It can highlight a row', () => {
|
||||
const {instance, ...plugin} = TestUtils.startPlugin(Plugin);
|
||||
|
||||
plugin.sendEvent('newMessage', {
|
||||
app: 'Flipper',
|
||||
direction: 'toFlipper',
|
||||
});
|
||||
|
||||
instance.setHighlightedRow(instance.rows.records()[0]);
|
||||
|
||||
expect(instance.rows.records()).toHaveLength(1);
|
||||
expect(instance.highlightedRow.get()?.app).toEqual('Flipper');
|
||||
clearFlipperDebugMessages();
|
||||
expect(getFlipperDebugMessages()).toEqual([]);
|
||||
});
|
||||
|
||||
test('It can render empty', async () => {
|
||||
const {renderer} = TestUtils.renderPlugin(Plugin);
|
||||
const renderer = render(<FlipperMessages />);
|
||||
|
||||
// Default message without any highlighted rows.
|
||||
expect(
|
||||
await renderer.findByText('Select a message to view details'),
|
||||
).not.toBeNull();
|
||||
renderer.unmount();
|
||||
});
|
||||
|
||||
test('It can render rows', async () => {
|
||||
const {renderer, ...plugin} = TestUtils.renderPlugin(Plugin);
|
||||
const renderer = render(<FlipperMessages />);
|
||||
|
||||
plugin.sendEvent('newMessage', {
|
||||
act(() => {
|
||||
registerFlipperDebugMessage({
|
||||
time: new Date(0, 0, 0, 0, 0, 0),
|
||||
app: 'Flipper',
|
||||
direction: 'toFlipper',
|
||||
direction: 'toFlipper:message',
|
||||
});
|
||||
|
||||
plugin.sendEvent('newMessage', {
|
||||
registerFlipperDebugMessage({
|
||||
time: new Date(0, 0, 0, 0, 0, 0),
|
||||
app: 'FB4A',
|
||||
direction: 'toClient',
|
||||
direction: 'toClient:send',
|
||||
device: 'Android Phone',
|
||||
flipperInternalMethod: 'unique-string',
|
||||
payload: {hello: 'world'},
|
||||
});
|
||||
});
|
||||
|
||||
expect((await renderer.findByText('unique-string')).parentElement)
|
||||
.toMatchInlineSnapshot(`
|
||||
@@ -154,8 +158,10 @@ test('It can render rows', async () => {
|
||||
class="css-1vr131n-TableBodyColumnContainer e1luu51r0"
|
||||
width="14%"
|
||||
>
|
||||
toClient
|
||||
toClient:send
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
renderer.unmount();
|
||||
});
|
||||
@@ -16,7 +16,7 @@ import {Logger} from '../fb-interfaces/Logger';
|
||||
|
||||
import {LeftRail} from './LeftRail';
|
||||
import {useStore, useDispatch} from '../utils/useStore';
|
||||
import {ConsoleLogs} from '../chrome/ConsoleLogs';
|
||||
import {FlipperDevTools} from '../chrome/FlipperDevTools';
|
||||
import {setStaticView} from '../reducers/connections';
|
||||
import {
|
||||
ACTIVE_SHEET_CHANGELOG_RECENT_ONLY,
|
||||
@@ -79,7 +79,7 @@ export function SandyApp() {
|
||||
}
|
||||
switch (newSelection) {
|
||||
case 'flipperlogs':
|
||||
dispatch(setStaticView(ConsoleLogs));
|
||||
dispatch(setStaticView(FlipperDevTools));
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
@@ -37,7 +37,6 @@ import {WebsocketClientFlipperConnection} from './utils/js-client-server-utils/w
|
||||
import querystring from 'querystring';
|
||||
import {IncomingMessage} from 'http';
|
||||
import ws from 'ws';
|
||||
import {initSelfInpector} from './utils/self-inspection/selfInspectionUtils';
|
||||
import DummyDevice from './devices/DummyDevice';
|
||||
import BaseDevice from './devices/BaseDevice';
|
||||
import {sideEffect} from './utils/sideEffect';
|
||||
@@ -106,10 +105,6 @@ class Server extends EventEmitter {
|
||||
}
|
||||
|
||||
init() {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
initSelfInpector(this.store, this.logger, this, this.connections);
|
||||
}
|
||||
|
||||
const {insecure, secure} = this.store.getState().application.serverPorts;
|
||||
this.initialisePromise = this.certificateProvider
|
||||
.loadSecureServerConfig()
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
import {notification, Typography} from 'antd';
|
||||
import React from 'react';
|
||||
import {ConsoleLogs} from '../chrome/ConsoleLogs';
|
||||
import {FlipperDevTools} from '../chrome/FlipperDevTools';
|
||||
import {setStaticView} from '../reducers/connections';
|
||||
import {getStore} from '../store';
|
||||
import {Layout} from '../ui';
|
||||
@@ -29,7 +29,7 @@ export function showErrorNotification(message: string, description?: string) {
|
||||
See{' '}
|
||||
<Link
|
||||
onClick={() => {
|
||||
getStore().dispatch(setStaticView(ConsoleLogs));
|
||||
getStore().dispatch(setStaticView(FlipperDevTools));
|
||||
notification.close(key);
|
||||
}}>
|
||||
logs
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its 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 {FlipperConnection, FlipperPlugin} from 'flipper-client-sdk';
|
||||
|
||||
export type MessageInfo = {
|
||||
device?: string;
|
||||
app: string;
|
||||
flipperInternalMethod?: string;
|
||||
plugin?: string;
|
||||
pluginMethod?: string;
|
||||
payload?: any;
|
||||
direction:
|
||||
| 'toClient:call'
|
||||
| 'toClient:send'
|
||||
| 'toFlipper:message'
|
||||
| 'toFlipper:response';
|
||||
};
|
||||
|
||||
export class FlipperMessagesClientPlugin implements FlipperPlugin {
|
||||
protected connection: FlipperConnection | null = null;
|
||||
|
||||
onConnect(connection: FlipperConnection): void {
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
onDisconnect(): void {
|
||||
this.connection = null;
|
||||
}
|
||||
|
||||
getId(): string {
|
||||
return 'flipper-messages';
|
||||
}
|
||||
|
||||
runInBackground(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
newMessage(message: MessageInfo) {
|
||||
this.connection?.send('newMessage', message);
|
||||
}
|
||||
|
||||
isConnected() {
|
||||
return this.connection != null;
|
||||
}
|
||||
}
|
||||
|
||||
export const flipperMessagesClientPlugin = new FlipperMessagesClientPlugin();
|
||||
@@ -1,117 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its 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 {FlipperClientConnection} from '../../Client';
|
||||
import {Flowable, Single} from 'rsocket-flowable';
|
||||
import {Payload, ConnectionStatus, ISubscriber} from 'rsocket-types';
|
||||
|
||||
import {FlipperClient} from 'flipper-client-sdk';
|
||||
|
||||
// somehow linter isn't happy with next import so type definitions are copied
|
||||
// import {IFutureSubject} from 'rsocket-flowable/Single';
|
||||
|
||||
type CancelCallback = () => void;
|
||||
|
||||
interface IFutureSubject<T> {
|
||||
onComplete: (value: T) => void;
|
||||
onError: (error: Error) => void;
|
||||
onSubscribe: (cancel: CancelCallback | null | undefined) => void;
|
||||
}
|
||||
|
||||
export class SelfInspectionFlipperClient<M>
|
||||
extends FlipperClient
|
||||
implements FlipperClientConnection<string, M>
|
||||
{
|
||||
connStatusSubscribers: Set<ISubscriber<ConnectionStatus>> = new Set();
|
||||
connStatus: ConnectionStatus = {kind: 'CONNECTED'};
|
||||
|
||||
connectionStatus(): Flowable<ConnectionStatus> {
|
||||
return new Flowable<ConnectionStatus>((subscriber) => {
|
||||
subscriber.onSubscribe({
|
||||
cancel: () => {
|
||||
this.connStatusSubscribers.delete(subscriber);
|
||||
},
|
||||
request: (_) => {
|
||||
this.connStatusSubscribers.add(subscriber);
|
||||
subscriber.onNext(this.connStatus);
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
close(): void {
|
||||
this.connStatus = {kind: 'CLOSED'};
|
||||
this.connStatusSubscribers.forEach((subscriber) => {
|
||||
subscriber.onNext(this.connStatus);
|
||||
});
|
||||
}
|
||||
|
||||
fireAndForget(payload: Payload<string, M>): void {
|
||||
if (payload.data == null) {
|
||||
return;
|
||||
}
|
||||
const message = JSON.parse(payload.data) as {
|
||||
method: string;
|
||||
id: number;
|
||||
params: any;
|
||||
};
|
||||
this.onMessageReceived(message);
|
||||
}
|
||||
|
||||
activeRequests = new Map<number, IFutureSubject<Payload<string, M>>>();
|
||||
|
||||
requestResponse(payload: Payload<string, M>): Single<Payload<string, M>> {
|
||||
return new Single((subscriber) => {
|
||||
subscriber.onSubscribe(() => {});
|
||||
if (payload.data == null) {
|
||||
subscriber.onError(new Error('empty payload'));
|
||||
return;
|
||||
}
|
||||
const message = JSON.parse(payload.data) as {
|
||||
method: string;
|
||||
id: number;
|
||||
params: any;
|
||||
};
|
||||
this.activeRequests.set(message.id, subscriber);
|
||||
this.onMessageReceived(message);
|
||||
});
|
||||
}
|
||||
|
||||
// Client methods
|
||||
|
||||
messagesHandler: ((message: any) => void) | undefined;
|
||||
|
||||
start(_appName: string): void {
|
||||
this.onConnect();
|
||||
}
|
||||
|
||||
stop(): void {}
|
||||
|
||||
sendData(payload: any): void {
|
||||
if (payload['success'] != null) {
|
||||
const message = payload as {id: number; success: unknown};
|
||||
const sub = this.activeRequests.get(message.id);
|
||||
sub?.onComplete({data: JSON.stringify(message)});
|
||||
this.activeRequests.delete(message.id);
|
||||
return;
|
||||
}
|
||||
|
||||
this.messagesHandler && this.messagesHandler(payload);
|
||||
}
|
||||
|
||||
isAvailable(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
subscibeForClientMessages(handler: (message: any) => void) {
|
||||
this.messagesHandler = handler;
|
||||
}
|
||||
}
|
||||
|
||||
export const selfInspectionClient = new SelfInspectionFlipperClient();
|
||||
@@ -1,94 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its 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 Client, {ClientQuery} from '../../Client';
|
||||
import {FlipperClientConnection} from '../../Client';
|
||||
import {Store} from '../../reducers';
|
||||
import {Logger} from '../../fb-interfaces/Logger';
|
||||
|
||||
import Server from '../../server';
|
||||
import {buildClientId} from '../clientUtils';
|
||||
import {selfInspectionClient} from './selfInspectionClient';
|
||||
import {flipperMessagesClientPlugin} from './plugins/FlipperMessagesClientPlugin';
|
||||
import {destroyDevice} from '../../reducers/connections';
|
||||
|
||||
export function initSelfInpector(
|
||||
store: Store,
|
||||
logger: Logger,
|
||||
flipperServer: Server,
|
||||
flipperConnections: Map<
|
||||
string,
|
||||
{
|
||||
connection: FlipperClientConnection<any, any> | null | undefined;
|
||||
client: Client;
|
||||
}
|
||||
>,
|
||||
) {
|
||||
const appName = 'Flipper';
|
||||
|
||||
selfInspectionClient.addPlugin(flipperMessagesClientPlugin);
|
||||
const hostDevice = store
|
||||
.getState()
|
||||
.connections.devices.find((d) => d.serial === '');
|
||||
if (!hostDevice) {
|
||||
console.error('Failed to find host device for self inspector');
|
||||
return;
|
||||
}
|
||||
|
||||
const query: ClientQuery = {
|
||||
app: appName,
|
||||
os: 'MacOS',
|
||||
device: 'emulator',
|
||||
device_id: '',
|
||||
sdk_version: 4,
|
||||
};
|
||||
const clientId = buildClientId(query);
|
||||
|
||||
const client = new Client(
|
||||
clientId,
|
||||
query,
|
||||
selfInspectionClient,
|
||||
logger,
|
||||
store,
|
||||
undefined,
|
||||
hostDevice,
|
||||
);
|
||||
|
||||
flipperConnections.set(clientId, {
|
||||
connection: selfInspectionClient,
|
||||
client: client,
|
||||
});
|
||||
|
||||
selfInspectionClient.connectionStatus().subscribe({
|
||||
onNext(payload) {
|
||||
if (payload.kind == 'ERROR' || payload.kind == 'CLOSED') {
|
||||
console.debug(`Device disconnected ${client.id}`, 'server');
|
||||
flipperServer.removeConnection(client.id);
|
||||
destroyDevice(store, logger, client.id);
|
||||
}
|
||||
},
|
||||
onSubscribe(subscription) {
|
||||
subscription.request(Number.MAX_SAFE_INTEGER);
|
||||
},
|
||||
});
|
||||
|
||||
client.init().then(() => {
|
||||
flipperServer.emit('new-client', client);
|
||||
flipperServer.emit('clients-change');
|
||||
client.emit('plugins-change');
|
||||
|
||||
selfInspectionClient.subscibeForClientMessages((payload: any) => {
|
||||
// let's break the possible recursion problems here
|
||||
// for example we want to send init plugin message, but store state is being updated when we enable plugins
|
||||
setImmediate(() => {
|
||||
client.onMessage(JSON.stringify(payload));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -25,6 +25,13 @@ export function usePluginInstance():
|
||||
return pluginInstance;
|
||||
}
|
||||
|
||||
export function usePluginInstanceMaybe():
|
||||
| SandyPluginInstance
|
||||
| SandyDevicePluginInstance
|
||||
| undefined {
|
||||
return useContext(SandyPluginContext);
|
||||
}
|
||||
|
||||
export function usePlugin<
|
||||
Factory extends PluginFactory<any, any> | DevicePluginFactory,
|
||||
>(plugin: Factory): ReturnType<Factory> {
|
||||
|
||||
@@ -48,7 +48,7 @@ import {Typography} from 'antd';
|
||||
import {CoffeeOutlined, SearchOutlined, PushpinFilled} from '@ant-design/icons';
|
||||
import {useAssertStableRef} from '../../utils/useAssertStableRef';
|
||||
import {Formatter} from '../DataFormatter';
|
||||
import {usePluginInstance} from '../../plugin/PluginContext';
|
||||
import {usePluginInstanceMaybe} from '../../plugin/PluginContext';
|
||||
import {debounce} from 'lodash';
|
||||
import {useInUnitTest} from '../../utils/useInUnitTest';
|
||||
import {createDataSource} from 'flipper-plugin/src/state/createDataSource';
|
||||
@@ -142,7 +142,7 @@ export function DataTable<T extends object>(
|
||||
const isUnitTest = useInUnitTest();
|
||||
|
||||
// eslint-disable-next-line
|
||||
const scope = isUnitTest ? "" : usePluginInstance().pluginKey;
|
||||
const scope = isUnitTest ? "" : usePluginInstanceMaybe()?.pluginKey ?? "";
|
||||
const virtualizerRef = useRef<DataSourceVirtualizer | undefined>();
|
||||
const [tableState, dispatch] = useReducer(
|
||||
dataTableManagerReducer as DataTableReducer<T>,
|
||||
|
||||
@@ -1,163 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its 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 {
|
||||
DataInspector,
|
||||
DataTable,
|
||||
DataTableColumn,
|
||||
Layout,
|
||||
createState,
|
||||
PluginClient,
|
||||
usePlugin,
|
||||
useValue,
|
||||
createDataSource,
|
||||
DetailSidebar,
|
||||
Panel,
|
||||
theme,
|
||||
styled,
|
||||
} from 'flipper-plugin';
|
||||
import {Button} from 'antd';
|
||||
import {DeleteOutlined} from '@ant-design/icons';
|
||||
import React from 'react';
|
||||
|
||||
export interface MessageInfo {
|
||||
time?: Date;
|
||||
device?: string;
|
||||
app: string;
|
||||
flipperInternalMethod?: string;
|
||||
plugin?: string;
|
||||
pluginMethod?: string;
|
||||
payload?: any;
|
||||
direction: 'toClient' | 'toFlipper';
|
||||
}
|
||||
|
||||
export interface MessageRow extends MessageInfo {
|
||||
time: Date;
|
||||
}
|
||||
|
||||
const Placeholder = styled(Layout.Container)({
|
||||
center: true,
|
||||
color: theme.textColorPlaceholder,
|
||||
fontSize: 18,
|
||||
});
|
||||
|
||||
function createRow(message: MessageInfo): MessageRow {
|
||||
return {
|
||||
...message,
|
||||
time: message.time == null ? new Date() : message.time,
|
||||
};
|
||||
}
|
||||
|
||||
type Events = {
|
||||
newMessage: MessageInfo;
|
||||
};
|
||||
|
||||
const COLUMN_CONFIG: DataTableColumn<MessageRow>[] = [
|
||||
{
|
||||
key: 'time',
|
||||
title: 'Time',
|
||||
},
|
||||
{
|
||||
key: 'device',
|
||||
title: 'Device',
|
||||
},
|
||||
{
|
||||
key: 'app',
|
||||
title: 'App',
|
||||
},
|
||||
{
|
||||
key: 'flipperInternalMethod',
|
||||
title: 'Flipper Internal Method',
|
||||
},
|
||||
{
|
||||
key: 'plugin',
|
||||
title: 'Plugin',
|
||||
},
|
||||
{
|
||||
key: 'pluginMethod',
|
||||
title: 'Method',
|
||||
},
|
||||
{
|
||||
key: 'direction',
|
||||
title: 'Direction',
|
||||
},
|
||||
];
|
||||
|
||||
export function plugin(client: PluginClient<Events, {}>) {
|
||||
const highlightedRow = createState<MessageRow>();
|
||||
const rows = createDataSource<MessageRow>([], {
|
||||
limit: 1024 * 10,
|
||||
persist: 'messages',
|
||||
});
|
||||
|
||||
const setHighlightedRow = (record: MessageRow) => {
|
||||
highlightedRow.set(record);
|
||||
};
|
||||
|
||||
const clear = () => {
|
||||
highlightedRow.set(undefined);
|
||||
rows.clear();
|
||||
};
|
||||
|
||||
client.onMessage('newMessage', (payload) => {
|
||||
rows.append(createRow(payload));
|
||||
});
|
||||
|
||||
return {
|
||||
rows,
|
||||
highlightedRow,
|
||||
setHighlightedRow,
|
||||
clear,
|
||||
};
|
||||
}
|
||||
|
||||
function Sidebar() {
|
||||
const instance = usePlugin(plugin);
|
||||
const message = useValue(instance.highlightedRow);
|
||||
|
||||
const renderExtra = (extra: any) => (
|
||||
<Panel title={'Payload'} collapsible={false}>
|
||||
<DataInspector data={extra} expandRoot={false} />
|
||||
</Panel>
|
||||
);
|
||||
|
||||
return (
|
||||
<DetailSidebar>
|
||||
{message != null ? (
|
||||
renderExtra(message.payload)
|
||||
) : (
|
||||
<Placeholder grow pad="large">
|
||||
Select a message to view details
|
||||
</Placeholder>
|
||||
)}
|
||||
</DetailSidebar>
|
||||
);
|
||||
}
|
||||
|
||||
export function Component() {
|
||||
const instance = usePlugin(plugin);
|
||||
|
||||
const clearTableButton = (
|
||||
<Button title="Clear logs" onClick={instance.clear}>
|
||||
<DeleteOutlined />
|
||||
</Button>
|
||||
);
|
||||
|
||||
return (
|
||||
<Layout.Container grow>
|
||||
<DataTable<MessageRow>
|
||||
dataSource={instance.rows}
|
||||
columns={COLUMN_CONFIG}
|
||||
onSelect={instance.setHighlightedRow}
|
||||
extraActions={clearTableButton}
|
||||
/>
|
||||
<Sidebar />
|
||||
</Layout.Container>
|
||||
);
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"$schema": "https://fbflipper.com/schemas/plugin-package/v2.json",
|
||||
"name": "flipper-plugin-flipper-messages",
|
||||
"id": "flipper-messages",
|
||||
"title": "Flipper Messages",
|
||||
"icon": "bird",
|
||||
"version": "0.0.0",
|
||||
"description": "Flipper self inspection: Messages to and from client",
|
||||
"main": "dist/bundle.js",
|
||||
"flipperBundlerEntry": "index.tsx",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"flipper-plugin"
|
||||
],
|
||||
"bugs": {
|
||||
"url": "https://github.com/facebook/flipper/issues"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "flipper-pkg lint",
|
||||
"build": "flipper-pkg bundle",
|
||||
"watch": "flipper-pkg bundle --watch",
|
||||
"prepack": "flipper-pkg lint && flipper-pkg bundle --production"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"flipper": "*",
|
||||
"flipper-pkg": "*",
|
||||
"flipper-plugin": "*"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user