Implement React example of WS integration with Flipper
Summary: Create an example of how one can use `js-flipper` in a browser to connect to Flipper over WS. Reviewed By: mweststrate Differential Revision: D31688114 fbshipit-source-id: 135f826daeddeda8dca5b3df6504cc2bdc04dd1b
This commit is contained in:
committed by
Facebook GitHub Bot
parent
25a6fc1ab1
commit
02115722b3
@@ -23,11 +23,21 @@ parameter, that registers a client plugin and will fire the relevant callbacks
|
||||
if the corresponding desktop plugin is selected in the Flipper Desktop. The full
|
||||
plugin API is documented
|
||||
[here](https://fbflipper.com/docs/extending/create-plugin).
|
||||
- `start` method. It starts the client.
|
||||
- `appName` setter to set the app name displayed in Flipper
|
||||
- `onError` setter to override how errors are handled (it is simple `console.error` by default)
|
||||
- `websocketFactory` setter to override WebSocket implementation (Node.js folks, it is for you!)
|
||||
- `urlBase` setter to make the client connect to a different URL
|
||||
- `start` method. It starts the client. It has two arguments:
|
||||
- `appName` - (required) the name dsplayed in Flipper
|
||||
- `options` which conforms to the infterface
|
||||
```ts
|
||||
interface FlipperClientOptions {
|
||||
// Make the client connect to a different URL
|
||||
urlBase?: string;
|
||||
// Override WebSocket implementation (Node.js folks, it is for you!)
|
||||
websocketFactory?: (url: string) => FlipperWebSocket;
|
||||
// Override how errors are handled (it is simple `console.error` by default)
|
||||
onError?: (e: unknown) => void;
|
||||
// Timeout after which client tries to reconnect to Flipper
|
||||
reconnectTimeout?: number;
|
||||
}
|
||||
```
|
||||
|
||||
## Example (web)
|
||||
|
||||
@@ -49,18 +59,15 @@ interface of the
|
||||
[web version](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket).
|
||||
|
||||
```ts
|
||||
import {setWebSocketImplementation, start} from 'js-flipper';
|
||||
import flipperClient from 'js-flipper';
|
||||
// Say, you decided to go with 'ws'
|
||||
// https://github.com/websockets/ws
|
||||
import WebSocket from 'ws';
|
||||
|
||||
// You need to let the flipper client know about it.
|
||||
setWebSocketImplementation(url => new WebSocket(url, {origin: 'localhost:'}));
|
||||
// Start the client and pass some options
|
||||
// You might ask yourself why there is the second argument `{ origin: 'localhost:' }`
|
||||
// Flipper Desktop verifies the `Origin` header for every WS connection. You need to set it to one of the whitelisted values (see `VALID_WEB_SOCKET_REQUEST_ORIGIN_PREFIXES`).
|
||||
|
||||
// Now, the last bit. You need to start the client.
|
||||
start();
|
||||
flipperClient.start('My cool nodejs app', { websocketFactory: url => new WebSocket(url, {origin: 'localhost:'}) });
|
||||
```
|
||||
|
||||
An example plugin should be somewhat similar to
|
||||
|
||||
@@ -15,6 +15,7 @@ import {assert, detectDevice, detectOS} from './util';
|
||||
import {RECONNECT_TIMEOUT} from './consts';
|
||||
|
||||
// TODO: Share with flipper-server-core
|
||||
// Borrowed from https://github.com/strong-roots-capital/websocket-close-codes
|
||||
/**
|
||||
* IANA WebSocket close code definitions.
|
||||
*
|
||||
@@ -123,25 +124,33 @@ export interface FlipperWebSocket {
|
||||
readyState: number;
|
||||
}
|
||||
|
||||
interface FlipperClientOptions {
|
||||
// Make the client connect to a different URL
|
||||
urlBase?: string;
|
||||
// Override WebSocket implementation (Node.js folks, it is for you!)
|
||||
websocketFactory?: (url: string) => FlipperWebSocket;
|
||||
// Override how errors are handled (it is simple `console.error` by default)
|
||||
onError?: (e: unknown) => void;
|
||||
// Timeout after which client tries to reconnect to Flipper
|
||||
reconnectTimeout?: number;
|
||||
}
|
||||
|
||||
export class FlipperClient {
|
||||
protected plugins: Map<string, FlipperPlugin> = new Map();
|
||||
protected connections: Map<string, FlipperConnection> = new Map();
|
||||
private readonly plugins: Map<string, FlipperPlugin> = new Map();
|
||||
private readonly connections: Map<string, FlipperConnection> = new Map();
|
||||
private ws?: FlipperWebSocket;
|
||||
private devicePseudoId = `${Date.now()}.${Math.random()}`;
|
||||
private os = detectOS();
|
||||
private device = detectDevice();
|
||||
private _appName = 'JS App';
|
||||
private readonly devicePseudoId = `${Date.now()}.${Math.random()}`;
|
||||
private readonly os = detectOS();
|
||||
private readonly device = detectDevice();
|
||||
private reconnectionTimer?: NodeJS.Timeout;
|
||||
private resolveStartPromise?: () => void;
|
||||
private urlBase!: string;
|
||||
private websocketFactory!: (url: string) => FlipperWebSocket;
|
||||
private onError!: (e: unknown) => void;
|
||||
private reconnectTimeout!: number;
|
||||
private appName!: string;
|
||||
|
||||
public urlBase = `localhost:8333`;
|
||||
|
||||
public websocketFactory: (url: string) => FlipperWebSocket = (url) =>
|
||||
new WebSocket(url) as FlipperWebSocket;
|
||||
public onError: (e: unknown) => void = (e: unknown) =>
|
||||
console.error('WebSocket error', e);
|
||||
|
||||
constructor(public readonly reconnectTimeout = RECONNECT_TIMEOUT) {}
|
||||
constructor() {}
|
||||
|
||||
addPlugin(plugin: FlipperPlugin) {
|
||||
this.plugins.set(plugin.getId(), plugin);
|
||||
@@ -155,11 +164,25 @@ export class FlipperClient {
|
||||
return this.plugins.get(id);
|
||||
}
|
||||
|
||||
async start(): Promise<void> {
|
||||
async start(
|
||||
appName: string,
|
||||
{
|
||||
urlBase = 'localhost:8333',
|
||||
websocketFactory = (url) => new WebSocket(url) as FlipperWebSocket,
|
||||
onError = (e) => console.error('WebSocket error', e),
|
||||
reconnectTimeout = RECONNECT_TIMEOUT,
|
||||
}: FlipperClientOptions = {},
|
||||
): Promise<void> {
|
||||
if (this.ws) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.appName = appName;
|
||||
this.onError = onError;
|
||||
this.urlBase = urlBase;
|
||||
this.websocketFactory = websocketFactory;
|
||||
this.reconnectTimeout = reconnectTimeout;
|
||||
|
||||
return new Promise<void>((resolve) => {
|
||||
this.resolveStartPromise = resolve;
|
||||
this.connectToFlipper();
|
||||
@@ -189,17 +212,6 @@ export class FlipperClient {
|
||||
return !!this.ws && this.ws.readyState === 1;
|
||||
}
|
||||
|
||||
get appName() {
|
||||
return this._appName;
|
||||
}
|
||||
|
||||
set appName(newAppName: string) {
|
||||
this._appName = newAppName;
|
||||
|
||||
this.ws?.close(WSCloseCode.NormalClosure);
|
||||
this.reconnect(true);
|
||||
}
|
||||
|
||||
private connectToFlipper() {
|
||||
const url = `ws://${this.urlBase}?device_id=${this.device}${this.devicePseudoId}&device=${this.device}&app=${this.appName}&os=${this.os}`;
|
||||
|
||||
@@ -211,7 +223,7 @@ export class FlipperClient {
|
||||
this.ws.onclose = ({code}) => {
|
||||
// Some WS implementations do not properly set `wasClean`
|
||||
if (code !== WSCloseCode.NormalClosure) {
|
||||
this.reconnect(false);
|
||||
this.reconnect();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -235,7 +247,7 @@ export class FlipperClient {
|
||||
}
|
||||
|
||||
// TODO: Reconnect in a loop with an exponential backoff
|
||||
private reconnect(now?: boolean) {
|
||||
private reconnect() {
|
||||
this.ws = undefined;
|
||||
|
||||
if (this.reconnectionTimer) {
|
||||
@@ -243,12 +255,9 @@ export class FlipperClient {
|
||||
this.reconnectionTimer = undefined;
|
||||
}
|
||||
|
||||
this.reconnectionTimer = setTimeout(
|
||||
() => {
|
||||
this.connectToFlipper();
|
||||
},
|
||||
now ? 0 : this.reconnectTimeout,
|
||||
);
|
||||
this.reconnectionTimer = setTimeout(() => {
|
||||
this.connectToFlipper();
|
||||
}, this.reconnectTimeout);
|
||||
}
|
||||
|
||||
private onMessageReceived(message: {
|
||||
|
||||
@@ -7,10 +7,9 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {FlipperClient, FlipperWebSocket, WSCloseCode} from './client';
|
||||
import {FlipperPlugin} from './plugin';
|
||||
import {FlipperClient} from './client';
|
||||
|
||||
export * from './client';
|
||||
export * from './plugin';
|
||||
export {FlipperWebSocket};
|
||||
|
||||
export const flipperClient = new FlipperClient();
|
||||
|
||||
Reference in New Issue
Block a user