For quite some time already, Flipper has secretly provided an experimental
JavaScript SDK to support connections from browsers and Node.js under the name
of flipper-js-client-sdk
. With the ongoing migration of all our clients to
WebSockets, we have committed to providing an official documented SDK for
JavaScript clients. Without further ado, welcome
js-flipper!
In this post we will:
- See what
js-flipper
is - Get acquainted with how to build a Flipper plugin for a React app
- Learn how Flipper talks to a mobile device
- Dive deeper into the message structure
- Glance at what it takes to support a new platform
What js-flipper
is and why it mattersβ
Flipper supports native iOS, native Android apps and React Native apps out of
the box. Now with js-flipper
, Flipper also supports JavaScript apps. Any
JavaScript app, whether they run in your browser or on your Node.js server, can
now connect to Flipper for a debugging session.
js-flipper
is a new NPM package that exposes a Flipper client to your
JavaScript apps. Any Flipper client, in its turn, is a set of abstractions that
let your device connect and talk to Flipper. Long story short, js-flipper
allows you to easily write Flipper plugins for your web and Node.js apps.
Here is how you can write your first simple plugin.
Why does it matter?
It's a huge deal for two reasons:
- Flipper at its core is just a device discovery service with a message bus. Its power comes from the plugins and the ecosystem.
- It brings us one step closer to our goal of running Flipper everywhere. Bring Flipper to your microwave! On a serious note, more platforms -> bigger community -> more developers -> more plugins -> better Flipper for everyone.
Let's take a quick look at the principal architecture of Flipper:
Here is what happens there:
- Flipper constantly polls ADB for available Android devices and IDB for available iOS devices.
- If the device is running an app with an enabled Flipper client, the client tries to connect to Flipper on your laptop. It lets Flipper know that there is an app that it can talk to. Flipper and app chit-chat a bit negotiating the security and the list of supported plugins.
- The developer picks one of the connected apps / devices. Say, it's the app.
- The developer clicks one of the available plugins.
- The plugin starts talking to the app on the device via the message bus exposed by Flipper. The plugin asks for necessary data from the app and shows it in a pretty UI.
At Meta, we have many active plugins, across a wide variety of devices, not just phones, but also Quests, desktop applications, etc. At its core, Flipper is data-agnostic and connect data flows to plugin displays. All Flipper core (we call it Flipper Server) knows is what devices and Flipper-enabled apps are out there. I hope it gets us on the same page regarding why plugins (and plugin developers!) are crucial for Flipper.
Another important conclusion you could draw from the diagram is that the state of Flipper plugins is ephemeral and lives in the UI.
How Flipper talks to a mobile deviceβ
Let's dive a bit deeper into how exactly the device and Flipper talk. Flipper pulls device logs from ADB/IDB. For everything else, Flipper expects the app (Flipper client inside of the app) to open a WebSocket connection to Flipper.
The algorithm looks like this:
- The app opens a WebSocket connection to Flipper.
- They exchange certificates. Flipper connects to the app using ADB/IDB and writes a certificate to the app storage.
- The app opens a secure WebSocket connection to Flipper using the certificate.
Why do we even bother with the certificate exchange process? One of the potential attack vectors is that a developer could install a malicious app on the testing device. That app could spin up a WebSocket server and mask itself as Flipper. However, unlike Flipper, the malicious app can't access the file storage of another app. As a result, it can't complete the certificate exchange process.
On mobile devices certificate exchange is important, so that other apps on the phone can't impersonate Flipper. For browser apps this isn't an issue as the browser already makes sure a malicious page cannot act as Flipper server. For platforms like this, we use a simplified connection algorithm:
- The app opens a WebSocket connection to Flipper.
- Bingo!
js-flipper
implements the second algorithm, without the certificate exchange.
Message protocol and structureβ
Once the final WebSocket connection is established, Flipper starts talking to the app:
- It sends
getPlugins
andgetBackgroundPlugins
messages to get a list of plugins supported by the app. - Flipper displays the available plugins to the developer.
- Developer clicks on one of the plugins (enables a plugin).
- Flipper loads the UI for the plugin. Let's settle on calling the part of the plugin "desktop plugin" and the device part of the plugin "client plugin".
- Flipper sends
init
message to the app. - Client plugin
onConnect
code is executed. Read more about Client Plugin API here. - Whenever a "desktop plugin" needs some data from the device it sends an
execute
message to the "client plugin" on the device. - "Client plugin" replies with the data.
- "Client plugin" might force the "desktop plugin" to do something as well by
sending an
execute
message as well. However, it is rare. In the current implementation, the "client plugin" can never expect a reply back from the "desktop plugin". In other words, consider it as an event sink, not as a way to extract some data from the "desktop plugin". - When the plugin is deactivated a
deinit
message is sent to the "client plugin". - Client plugin
onDisconnect
code is executed.
The process above is for the insecure WebSocket connections we currently use in
js-flipper
. It is more complicated for secure WebSocket connections that require certificate exchange.
Flipper expects each message to have the following structure:
export interface FlipperRequest {
method: string; // 'getPlugins' | 'getBackgroundPlugins' | 'init' | 'deinit' | 'execute' | 'isMethodSupported'
params?: {
api: string; // Plugin ID (name)
// These nested `method` and `params` could be anything.
// You set them yourself as you see fit to support the data exchange between the "desktop plugin" and the "client plugin".
// For example, for 'ReactNativeTicTacToe' we support 2 methods: 'SetState' and 'GetState'.
// We pass a game state with a 'SetState' message. See https://fbflipper.com/docs/tutorial/javascript/#step-3-call-addplugin-to-add-your-plugin
method: string;
params?: unknown;
};
}
The only exception is the response message the "client plugin" sends back when the data is requested.
export type FlipperResponse = {
id: number;
success?: object | string | number | boolean | null;
error?: {
message: string;
stacktrace?: string;
name?: string;
};
};
Building a new clientβ
At this point, you know what messages your client needs to support in a Flipper client:
getPlugins
getBackgroundPlugins
init
deinit
execute
One other message we did not mention before is isMethodSupported
. Its job is
to reply back to a "desktop plugin" whether a "client plugin" supports one of
plugin messages (that nested method
field). It's useful when you have a single
"desktop plugin" implementation, but different "client plugin" implementations.
For example, some operations might not be supported on iOS, but are supported on
Android. Alternatively, it can address version differences between the plugin
installed on the device and the one loaded into Flipper.
If you want to build a proper Flipper client, you also need to provide an abstraction for plugin developers. Consider matching what we have for existing clients.
Most of the groundwork for handling connections and doing certificate exchange
is already done in our
C++ engine. Our iOS,
Android, React Native clients use it under the hood. js-flipper
implements
everything from scratch using native browser APIs (for Node.js apps we
require developers to provide a WebSocket implementation).
Here is a detailed document on how to implement a client. You might also want to check the source code of our existing clients:
What's next?β
As of now, we do not provide any default plugins you might be used to for
js-flipper
(Layout, Logs, Navigation, Crash Reporter, and others). We hope
this will change in the future with the help of ur beloved open-source
community!
Call to action!
We would like to encourage you to play with js-flipper
. See how it fits your
use-case and get back back to us with your feedback on
GitHub. If you find yourself
implementing one of your favorite Flipper plugins for js-flipper
, do not
hesitate and raise a PR!
Plugins can be either generic or very application specific. Plugins can interact with Redux or MobX stores, read performance data or console logs from the browser. At Meta, we also see a lot of plugins that are very application specific. For example, plugins that allow logging in as specific test users with a single click, reading the internal state of NewsFeed and interacting with it, simulating photos captured by a smartphone, etc. A Flipper plugin can be any form of UI that is useful to speed up debugging and tasks on things you work on frequently!
P.S. Flipper needs you!β
Flipper is maintained by a small team at Meta, yet is serving over a hundred plugins and dozens of different targets. Our team's goal is to support Flipper as a plugin-based platform for which we maintain the infrastructure. We don't typically invest in individual plugins, but we do love plugin improvements. For example, the support for mocking network requests (on Android) was entirely contributed by the community (thanks James Harmon!). As was Protobuf support (thanks Harold Martin!).
For that reason, we've marked many requests in the issue tracker as
PR Welcome.
Contributing changes should be as simple as cloning the
repository and running
yarn && yarn start
in the desktop/
folder.
Investing in debugging tools, both generic ones or just for specific apps, will benefit iteration speed. And we hope Flipper will make it as hassle free as possible to create your debugging tools. For an overview of Flipper for React Native, and why and how to build your own plugins, we recommend checking out the Flipper: The Extensible DevTool Platform for React Native talk.
Happy debugging!