Self inspection
Summary: let's finally inspect flipper with flipper! Here we have: 1) a self inspection client which implements FlipperClient interface from js sdk and FlipperClientConnection. It links back and front parts of self inspection 2) simple plugin (UI) to show messages 3) back part of that plugin - it sends all received messages to UI part via client 4) we initialize self inspection for dev builds only P. S. filesystem dependency will be replaced with npm one before I ship it (need to publish to npm first) Reviewed By: mweststrate Differential Revision: D22524533 fbshipit-source-id: 5c77e2f7b50e24ff7314e791a4dfe3c349dccdee
This commit is contained in:
committed by
Facebook GitHub Bot
parent
7dbcfc89b0
commit
d28e763cca
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* 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();
|
||||
115
desktop/app/src/utils/self-inspection/selfInspectionClient.tsx
Normal file
115
desktop/app/src/utils/self-inspection/selfInspectionClient.tsx
Normal file
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* 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();
|
||||
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* 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 FlipperSelfInspectionDevice from '../../devices/FlipperSelfInspectionDevice';
|
||||
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';
|
||||
|
||||
export function initSelfInpector(
|
||||
store: Store,
|
||||
logger: Logger,
|
||||
flipperServer: Server,
|
||||
flipperConnections: Map<
|
||||
string,
|
||||
{
|
||||
connection: FlipperClientConnection<any, any> | null | undefined;
|
||||
client: Client;
|
||||
}
|
||||
>,
|
||||
) {
|
||||
const appName = 'Flipper';
|
||||
const device_id = 'FlipperSelfInspectionDevice';
|
||||
store.dispatch({
|
||||
type: 'REGISTER_DEVICE',
|
||||
payload: new FlipperSelfInspectionDevice(
|
||||
device_id,
|
||||
'emulator',
|
||||
appName,
|
||||
'JSWebApp',
|
||||
),
|
||||
});
|
||||
|
||||
selfInspectionClient.addPlugin(flipperMessagesClientPlugin);
|
||||
|
||||
const query: ClientQuery = {
|
||||
app: appName,
|
||||
os: 'JSWebApp',
|
||||
device: 'emulator',
|
||||
device_id,
|
||||
sdk_version: 4,
|
||||
};
|
||||
const clientId = buildClientId(query);
|
||||
|
||||
const client = new Client(
|
||||
clientId,
|
||||
query,
|
||||
selfInspectionClient,
|
||||
logger,
|
||||
store,
|
||||
);
|
||||
|
||||
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);
|
||||
const toUnregister = new Set<string>();
|
||||
store.dispatch({
|
||||
type: 'UNREGISTER_DEVICES',
|
||||
payload: toUnregister,
|
||||
});
|
||||
}
|
||||
},
|
||||
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));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user