From 9b16d0c29af3b9d41185127cd4f90212f9ffdd5b Mon Sep 17 00:00:00 2001 From: Andrey Goncharov Date: Fri, 22 Oct 2021 06:27:44 -0700 Subject: [PATCH] Add documentation Summary: Document Flipper integration with JavaScript clients. Reviewed By: passy Differential Revision: D31827187 fbshipit-source-id: c40d8820241c0f85bd2366a0c087d4270d316c71 --- README.md | 83 +++++++---- docs/extending/create-plugin.mdx | 139 +++++++++++++++--- docs/getting-started/index.mdx | 10 +- docs/getting-started/javascript.mdx | 83 +++++++++++ docs/tutorial/javascript.mdx | 49 ++++++ js/js-flipper/src/plugin.ts | 3 + .../src/FlipperTicTacToe.tsx | 15 +- website/sidebars.js | 2 + 8 files changed, 335 insertions(+), 49 deletions(-) create mode 100644 docs/getting-started/javascript.mdx create mode 100644 docs/tutorial/javascript.mdx diff --git a/README.md b/README.md index 5c14ff64b..36d199623 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@

- Flipper (formerly Sonar) is a platform for debugging mobile apps on iOS and Android. Visualize, inspect, and control your apps from a simple desktop interface. Use Flipper as is or extend it using the plugin API. + Flipper (formerly Sonar) is a platform for debugging mobile apps on iOS and Android and, recently, even JS apps in your browser or in Node.js. Visualize, inspect, and control your apps from a simple desktop interface. Use Flipper as is or extend it using the plugin API.

![Flipper](/website/static/img/layout.png) @@ -39,44 +39,59 @@ ## Mobile development -Flipper aims to be your number one companion for mobile app development on iOS and Android. Therefore, we provide a bunch of useful tools, including a log viewer, interactive layout inspector, and network inspector. +Flipper aims to be your number one companion for mobile app development on iOS +and Android. Therefore, we provide a bunch of useful tools, including a log +viewer, interactive layout inspector, and network inspector. ## Extending Flipper -Flipper is built as a platform. In addition to using the tools already included, you can create your own plugins to visualize and debug data from your mobile apps. Flipper takes care of sending data back and forth, calling functions, and listening for events on the mobile app. +Flipper is built as a platform. In addition to using the tools already included, +you can create your own plugins to visualize and debug data from your mobile +apps. Flipper takes care of sending data back and forth, calling functions, and +listening for events on the mobile app. ## Contributing to Flipper -Both Flipper's desktop app and native mobile SDKs are open-source and MIT licensed. This enables you to see and understand how we are building plugins, and of course, join the community and help to improve Flipper. We are excited to see what you will build on this platform. +Both Flipper's desktop app, native mobile SDKs, JS SDKs are open-source and MIT +licensed. This enables you to see and understand how we are building plugins, +and of course, join the community and help to improve Flipper. We are excited to +see what you will build on this platform. # In this repo This repository includes all parts of Flipper. This includes: -* Flipper's desktop app built using [Electron](https://electronjs.org) (`/desktop`) -* native Flipper SDKs for iOS (`/iOS`) -* native Flipper SDKs for Android (`/android`) -* Plugins: - * Logs (`/desktop/plugins/public/logs`) - * Layout inspector (`/desktop/plugins/public/layout`) - * Network inspector (`/desktop/plugins/public/network`) - * Shared Preferences/NSUserDefaults inspector (`/desktop/plugins/public/shared_preferences`) -* website and documentation (`/website` / `/docs`) +- Flipper's desktop app built using [Electron](https://electronjs.org) + (`/desktop`) +- native Flipper SDKs for iOS (`/iOS`) +- native Flipper SDKs for Android (`/android`) +- React Native Flipper SDK (`/react-native`) +- JS Flipper SDK (`/js`) +- Plugins: + - Logs (`/desktop/plugins/public/logs`) + - Layout inspector (`/desktop/plugins/public/layout`) + - Network inspector (`/desktop/plugins/public/network`) + - Shared Preferences/NSUserDefaults inspector + (`/desktop/plugins/public/shared_preferences`) +- website and documentation (`/website` / `/docs`) # Getting started -Please refer to our [Getting Started guide](https://fbflipper.com/docs/getting-started/index) to set up Flipper. +Please refer to our +[Getting Started guide](https://fbflipper.com/docs/getting-started/index) to set +up Flipper. ## Requirements -* node >= 8 -* yarn >= 1.5 -* iOS developer tools (for developing iOS plugins) -* Android SDK and adb +- node >= 8 +- yarn >= 1.5 +- iOS developer tools (for developing iOS plugins) +- Android SDK and adb # Building from Source ## Desktop + ### Running from source ```bash @@ -86,12 +101,13 @@ yarn yarn start ``` -NOTE: If you're on Windows, you need to use Yarn 1.5.1 until [this issue](https://github.com/yarnpkg/yarn/issues/6048) is resolved. +NOTE: If you're on Windows, you need to use Yarn 1.5.1 until +[this issue](https://github.com/yarnpkg/yarn/issues/6048) is resolved. ### Building standalone application -Provide either `--mac`, `--win`, `--linux` or any combination of them -to `yarn build` to build a release zip file for the given platform(s). E.g. +Provide either `--mac`, `--win`, `--linux` or any combination of them to +`yarn build` to build a release zip file for the given platform(s). E.g. ```bash yarn build --mac --version $buildNumber @@ -109,11 +125,13 @@ open Sample.xcworkspace ``` -You can omit `--repo-update` to speed up the installation, but watch out as you may be building against outdated dependencies. +You can omit `--repo-update` to speed up the installation, but watch out as you +may be building against outdated dependencies. ## Android SDK + Sample app Start up an android emulator and run the following in the project root: + ```bash ./gradlew :sample:installDebug ``` @@ -130,16 +148,27 @@ Note that the first 2 steps need to be done only once. Alternatively, the app can be started on `iOS` by running `yarn ios`. +## JS SDK + Sample React app + +```bash +cd js/react-flipper-example +yarn +yarn start +``` + #### Troubleshooting -Older yarn versions might show an error / hang with the message 'Waiting for the other yarn instance to finish'. If that happens, run the command `yarn` first separately in the directory `react-native/react-native-flipper`. +Older yarn versions might show an error / hang with the message 'Waiting for the +other yarn instance to finish'. If that happens, run the command `yarn` first +separately in the directory `react-native/react-native-flipper`. # Documentation -Find the full documentation for this project at [fbflipper.com](https://fbflipper.com/). +Find the full documentation for this project at +[fbflipper.com](https://fbflipper.com/). -Our documentation is built with [Docusaurus](https://docusaurus.io/). You can build -it locally by running this: +Our documentation is built with [Docusaurus](https://docusaurus.io/). You can +build it locally by running this: ```bash cd website @@ -148,7 +177,9 @@ yarn start ``` ## Contributing + See the [CONTRIBUTING](/CONTRIBUTING.md) file for how to help out. ## License + Flipper is MIT licensed, as found in the [LICENSE](/LICENSE) file. diff --git a/docs/extending/create-plugin.mdx b/docs/extending/create-plugin.mdx index f67809974..8e7eb84aa 100644 --- a/docs/extending/create-plugin.mdx +++ b/docs/extending/create-plugin.mdx @@ -15,7 +15,7 @@ To build a client plugin, implement the `FlipperPlugin` interface. The ID that is returned from your implementation needs to match the `name` defined in your JavaScript counterpart's `package.json`. - + ```java @@ -91,15 +91,54 @@ addPlugin({ return 'MyFlipperPlugin'; }, onConnect(connection) { - console.log("connected"); + console.log('connected'); }, onDisconnect() { - console.log("disconnected"); + console.log('disconnected'); }, runInBackground() { return false; - } -}) + }, +}); +``` + + + +
+ +Please note that using Flipper from JavaScript in your browser requires the package [`js-flipper`](https://www.npmjs.com/package/js-flipper) to be installed in the hosting application. + +
+ +```javascript +import {flipperClient} from 'js-flipper'; + +// We want to import and start flipper client only in development and test modes +// We want to exclude it from our production build +let flipperClientPromise; +if (process.env.NODE_ENV !== 'production') { + flipperClientPromise = import('js-flipper').then(({flipperClient}) => { + flipperClient.start('React Tic-Tac-Toe'); + return flipperClient; + }); +} + +flipperClientPromise?.then((flipperClient) => { + flipperClient.addPlugin({ + getId() { + return 'MyFlipperPlugin'; + }, + onConnect(connection) { + console.log('connected'); + }, + onDisconnect() { + console.log('disconnected'); + }, + runInBackground() { + return false; + }, + }); +}); ```
@@ -109,7 +148,7 @@ addPlugin({ `onConnect` will be called when your plugin becomes active. This will provide a `FlipperConnection` allowing you to register receivers for desktop method calls and respond with data. - + ```java @@ -171,17 +210,61 @@ addPlugin({ return 'MyFlipperPlugin'; }, onConnect(connection) { - console.log("connected"); - connection.receive("getData", (data, responder) => { - console.log("incoming data", data); + console.log('connected'); + connection.receive('getData', (data, responder) => { + console.log('incoming data', data); // respond with some data responder.success({ - ack: true + ack: true, }); }); }, // ...as-is -}) +}); +``` + + + + +```javascript +flipperClient.addPlugin({ + getId() { + return 'MyFlipperPlugin'; + }, + onConnect(connection) { + console.log('connected'); + connection.receive('getData', (data) => { + console.log('incoming data', data); + // return data to send it as a response + return { + ack: true, + }; + }); + // Flipper client can also send the data you return from your async functions + connection.receive('getDataAsync', async (data) => { + console.log('incoming data', data); + const myAsyncData = await doAsyncStuff(); + // return data to send it as a response + return { + data: myAsyncData, + }; + }); + // Flipper client catches your exceptions and sends them as an error response to the desktop + connection.receive('getErrorData', (data) => { + console.log('incoming data', data); + throw new Error('Ooops'); + }); + // It catches the execptions in your async functions as well + connection.receive('getErrorDataAsync', async (data) => { + console.log('incoming data', data); + const myAsyncData = await doAsyncStuff(); + if (!myAsyncData) { + throw new Error('Ooops! Async data is not there!!!'); + } + }); + }, + // ...as-is +}); ``` @@ -191,7 +274,7 @@ addPlugin({ You don't have to wait for the desktop to request data though, you can also push data directly to the desktop. If the JS plugin subscribes to the same method, it will receive the data. - + ```java @@ -227,11 +310,27 @@ addPlugin({ return 'MyFlipperPlugin'; }, onConnect(connection) { - console.log("connected"); - connection.send("newRow", { message: "Hello" }); + console.log('connected'); + connection.send('newRow', {message: 'Hello'}); }, // ...as-is -}) +}); +``` + + + + +```javascript +flipperClient.addPlugin({ + getId() { + return 'MyFlipperPlugin'; + }, + onConnect(connection) { + console.log('connected'); + connection.send('newRow', {message: 'Hello'}); + }, + // ...as-is +}); ``` @@ -299,10 +398,16 @@ Here, `sendData` is an example of a method that might be implemented by the Flip ### Bi-directional communication demo -An minimal communication demo can be found in our [Sample project]: +An minimal communication demo for Android and iOS can be found in our Sample project: * [Desktop implementation](https://github.com/facebook/flipper/blob/main/desktop/plugins/public/example/index.tsx) -* [Android implementation](https://github.com/facebook/flipper/blob/main/android/sample/src/debug/java/com/facebook/flipper/plugins/example/ExampleFlipperPlugin.java) / [iOS implementation](https://github.com/facebook/flipper/tree/7bd4f80c2570bebb52af3cf49e45fc6130d6a473/iOS/Plugins/FlipperKitExamplePlugin/FlipperKitExamplePlugin) +* [Android](https://github.com/facebook/flipper/blob/main/android/sample/src/debug/java/com/facebook/flipper/plugins/example/ExampleFlipperPlugin.java) / [iOS](https://github.com/facebook/flipper/tree/7bd4f80c2570bebb52af3cf49e45fc6130d6a473/iOS/Plugins/FlipperKitExamplePlugin/FlipperKitExamplePlugin) + +For React Native and JavaScript we have a simple game of Tic Tac Toe: + +* [Desktop implementation](https://github.com/facebook/flipper/blob/main/desktop/plugins/public/rn-tic-tac-toe/index.tsx) +* [React Native implementation](https://github.com/facebook/flipper/tree/main/react-native/ReactNativeFlipperExample) / [JavaScript (React) implementation](https://github.com/facebook/flipper/tree/main/js/react-flipper-example) + ## Background Plugins diff --git a/docs/getting-started/index.mdx b/docs/getting-started/index.mdx index abd00f4ea..dff363102 100644 --- a/docs/getting-started/index.mdx +++ b/docs/getting-started/index.mdx @@ -6,12 +6,13 @@ import useBaseUrl from '@docusaurus/useBaseUrl'; import FbInstallation from './fb/installation.mdx'; -Flipper helps you debug Android and iOS apps running in an emulator/simulator or connected physical development devices. Flipper consists of two parts: +Flipper helps you debug Android, iOS, and even web apps running in an emulator/simulator, connected physical development devices, or in your browser. Flipper consists of two parts: - The desktop app -- The native mobile SDKs for Android and iOS +- The native mobile SDKs for Android and iOS, the client for JavaScript, or even a third-party client you could implement yourself or find on the web Once you start Flipper and launch an emulator/simulator or connect a device, you'll start to see the device logs (and any other device-level plugins that work with your device). +For web apps, we do not ship any built in plugins yet. To see app specific data, you need to integrate the Flipper SDK into your app. See the relevant section in the sidebar for how to do that. @@ -21,9 +22,10 @@ To see app specific data, you need to integrate the Flipper SDK into your app. S The desktop part of Flipper doesn't need any particular setup. Simply download the latest build for [Mac](https://www.facebook.com/fbflipper/public/mac), [Linux](https://www.facebook.com/fbflipper/public/linux) or [Windows](https://www.facebook.com/fbflipper/public/windows) and launch it. If you're on macOS, you can run `brew install --cask flipper` to let `homebrew` manage installation and upgrades (simply run `brew upgrade` to upgrade when a new version is released, although it might take a few hours up to a day for the package to be upgraded on `homebrew`). -In order to work properly, Flipper requires a working installation of the Android and (if where applicable) iOS development tools on your system, as well as the [OpenSSL](https://www.openssl.org) binary on your `$PATH`. +To work properly with mobile apps, Flipper requires a working installation of the Android and (if where applicable) iOS development tools on your system, as well as the [OpenSSL](https://www.openssl.org) binary on your `$PATH`. A compatible OpenSSL for Windows can be downloaded [here](https://slproweb.com/products/Win32OpenSSL.html) or from Chocolatey with `choco install openssl`. + +If you are hacking a JS app, you should be good to go without any extra dependencies installed. -A compatible OpenSSL for Windows can be downloaded [here](https://slproweb.com/products/Win32OpenSSL.html) or from Chocolatey with `choco install openssl`. diff --git a/docs/getting-started/javascript.mdx b/docs/getting-started/javascript.mdx new file mode 100644 index 000000000..b27628592 --- /dev/null +++ b/docs/getting-started/javascript.mdx @@ -0,0 +1,83 @@ +--- +id: javascript +title: Set up your JavaScript App +sidebar_label: JavaScript (browser / Node.js) +--- +import useBaseUrl from '@docusaurus/useBaseUrl'; +import Link from '@docusaurus/Link'; + +To set up Flipper in your JavaScript app, you need to add the neccessary dependencies to your +app, initialize the Flipper client and enable the plugins you want to use. + +Currently, we do not ship any plugins for JavaScript environments you can use right away, but we encourage you to write your own! + +## Dependencies + +Flipper JavaScript SDK is distiributed via NPM. To add it to your app run: + +```sh +npm install js-flipper +``` + +or + +```sh +yarn add js-flipper +``` + +## Application Setup + +Flipper SDK works in browser and Node.js environments. For browsers, it works out-of-the-box as long as your browser supports [WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket). For node.js, it requires a compatible WebSocket implementation (e.g. [ws](https://github.com/websockets/ws)). + +You MUST NOT start Flipper client in production. In browser environments, you should think about not including it in the final production build at all. + +Here is how you setup Flipper in your browser: + +```ts +import flipperClient from 'js-flipper'; + +// Start the client and pass your app name +flipperClient.start('My cool browser app'); +``` + +Here is how you can do it in your Node.js app: + +```ts +import flipperClient from 'js-flipper'; +// Say, you decided to go with 'ws' as your WebSocket implementation +// https://github.com/websockets/ws +import WebSocket from 'ws'; + +// Start the client and pass your app name +// 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`). +flipperClient.start('My cool nodejs app', { websocketFactory: url => new WebSocket(url, {origin: 'localhost:'}) }); +``` + +As you can see, `flipperClient` accepts an options object as a second parameter to its `start` method. Here is what you can pass there: + +```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; +} +``` + +## Enabling plugins + +Flipper is just a communication channel between the desktop app and your application. Its true power comes from its plugins. + +All plugins must be added to the client. Client communicates the list of available plugins to the desktop upon connection. +You can add a plugin by calling: + +```ts +flipperClient.addPlugin(/* your plugin */) +``` + +Chekc out documentation on creating plugins to write your own! diff --git a/docs/tutorial/javascript.mdx b/docs/tutorial/javascript.mdx new file mode 100644 index 000000000..c9fafb03e --- /dev/null +++ b/docs/tutorial/javascript.mdx @@ -0,0 +1,49 @@ +--- +id: javascript +title: Building a JavaScript (browser) Plugin +--- +import useBaseUrl from '@docusaurus/useBaseUrl'; +import Link from '@docusaurus/Link'; + +This tutorial requires a browser that supports [WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket). + +## Step 1. Install Flipper JavaScript SDK + +Add Flipper client to your web application. Run `npm install js-flipper` (`yarn add js-flipper`) + +## Step 2. Start Flipper client + +
+ +Do not start Flipper client in production! Preferably, do not even include Flipper in your production builds! + +
+ +```tsx file=js/react-flipper-example/src/FlipperTicTacToe.tsx start=DOCS_START_CLIENT_START end=DOCS_START_CLIENT_END +``` + +## Step 3. Call `addPlugin` to add your plugin + +To register a new plugin with Flipper call `flipperClient.addPlugin` and pass your plugin as an object. Your plugin must conform to the following interface: + +```ts file=js/js-flipper/src/plugin.ts start=DOCS_FLIPPER_PLUGIN_START end=DOCS_FLIPPER_PLUGIN_END +``` + +These `onConnect` and `onDisconnect` events are triggered every time the plugin becomes (in)active in the Flipper desktop application. +If the plugin is a background plugin, these events are triggered typically only once (they might be triggered never, if the Desktop user didn't enable the plugin, or multiple times if they enabled or disabled the plugin a few times). + +The `onConnect` callback receive a `connection` which can be used to communicate with the backend: + +```tsx file=js/react-flipper-example/src/FlipperTicTacToe.tsx start=DOCS_ADD_PLUGIN_START end=DOCS_ADD_PLUGIN_END +``` + +You might want to store the connection somewhere to be able to send more events as long as `onDisconnect` event hasn't been fired. + +The `connection` object can also be used to listen to messages coming from the Desktop plugin. See Client Plugin API for details. + +## Live demo + +An example plugin to play a little Tic-Tac-Toe between the Flipper Desktop and a React app can be found inside this repository as well (run `yarn && yarn start` in `js/react-flipper-example` to start the test project): + + * The React plugin implementation: [FlipperTicTacToe.tsx](https://github.com/facebook/flipper/tree/main/js/react-flipper-example/src/FlipperTicTacToe.tsx) + * The Flipper Desktop plugin implementation: [rn-tic-tac-toe/index.tsx](https://github.com/facebook/flipper/blob/main/desktop/plugins/public/rn-tic-tac-toe/index.tsx) diff --git a/js/js-flipper/src/plugin.ts b/js/js-flipper/src/plugin.ts index 2b0f51e42..72acc857f 100644 --- a/js/js-flipper/src/plugin.ts +++ b/js/js-flipper/src/plugin.ts @@ -41,6 +41,8 @@ export interface FlipperPluginConnection { */ receive(method: string, receiver: FlipperPluginReceiver): void; } + +// DOCS_FLIPPER_PLUGIN_START export interface FlipperPlugin { /** * @return The id of this plugin. This is the namespace which Flipper desktop plugins will call @@ -66,3 +68,4 @@ export interface FlipperPlugin { */ runInBackground?(): boolean; } +// DOCS_FLIPPER_PLUGIN_END diff --git a/js/react-flipper-example/src/FlipperTicTacToe.tsx b/js/react-flipper-example/src/FlipperTicTacToe.tsx index 6da9f26f4..b7ce096ae 100644 --- a/js/react-flipper-example/src/FlipperTicTacToe.tsx +++ b/js/react-flipper-example/src/FlipperTicTacToe.tsx @@ -11,6 +11,7 @@ import {useState, useEffect, FC} from 'react'; import type {FlipperPluginConnection, FlipperClient} from 'js-flipper'; import './FlipperTicTacToe.css'; +// DOCS_START_CLIENT_START // We want to import and start flipper client only in development and test modes let flipperClientPromise: Promise | undefined; if (process.env.NODE_ENV !== 'production') { @@ -19,6 +20,7 @@ if (process.env.NODE_ENV !== 'production') { return flipperClient; }); } +// DOCS_START_CLIENT_END interface GameState { cells: string[]; @@ -27,25 +29,32 @@ interface GameState { } const FlipperTicTacToe: FC = () => { + // DOCS_ADD_PLUGIN_START + // TicTacToe game status const [status, setStatus] = useState('Waiting for Flipper Desktop Player...'); + // TicTacToe game state const [gameState, setGameState] = useState({ cells: [], turn: ' ', winner: ' ', }); + // Flipper connection instance const [connection, setConnection] = useState(); useEffect(() => { flipperClientPromise?.then(flipperClient => { flipperClient.addPlugin({ getId() { + // Name of the plugin return 'ReactNativeTicTacToe'; }, onConnect(connection) { + // Once we connected, we display it to the user setStatus('Desktop player present'); + // And stash the connection object setConnection(connection); - // listen to updates + // We start listening to updates from Flipper Desktop connection.receive('SetState', (gameState: GameState) => { if (gameState.winner !== ' ') { setStatus( @@ -61,16 +70,18 @@ const FlipperTicTacToe: FC = () => { setGameState(gameState); }); - // request initial state + // We also request the initial state of the game from Flipper Desktop connection.send('GetState'); }, onDisconnect() { + // When Flipper Desktop disconnects, we show it to the user setConnection(undefined); setStatus('Desktop player gone...'); }, }); }); }, []); + // DOCS_ADD_PLUGIN_END return (
diff --git a/website/sidebars.js b/website/sidebars.js index 4f3567a90..0ba0fb216 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -54,6 +54,7 @@ module.exports = { 'getting-started/react-native-ios', ], }, + 'getting-started/javascript' ], }, ...fbInternalOnly(['getting-started/fb/connecting-to-flipper']), @@ -84,6 +85,7 @@ module.exports = { 'tutorial/ios', 'tutorial/android', 'tutorial/react-native', + 'tutorial/javascript', 'tutorial/js-setup', 'tutorial/js-table', 'tutorial/js-custom',