website/blog/2022-02-21-js-flipper-announcement.md
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:
js-flipper isjs-flipper is and why it mattersFlipper 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:
Let's take a quick look at the principal architecture of Flipper:
Here is what happens there:
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.
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:
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:
js-flipper implements the second algorithm, without the certificate exchange.
Once the final WebSocket connection is established, Flipper starts talking to the app:
getPlugins and getBackgroundPlugins messages to get a list of
plugins supported by the app.init message to the app.onConnect code is executed. Read more about Client Plugin API
here.execute message to the "client plugin" on the device.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".deinit message is sent to the "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;
};
};
At this point, you know what messages your client needs to support in a Flipper client:
getPluginsgetBackgroundPluginsinitdeinitexecuteOne 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:
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!
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!