Repository: react
Issue: Improve DevTools protocol version handling
Labels: Type: Discussion,Component: Developer Tools

# How does DevTools work and what is the "protocol version"?

The React DevTools extension consists of multiple pieces:
* The **frontend** portion is the extension you see (the Components tree, the Profiler, etc.).
* The **backend** portion is invisible. It runs in the same context as React itself.

The frontend displays the current React tree, but it has no way to observe the tree directly because it runs in different memory space. It relies on the backend to describe the tree by sending "messages" through an API like [`postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage).

The most optimized way for the backend to share the state of the React tree is to send a small packet of ["op codes"](https://github.com/facebook/react/blob/main/packages/react-devtools/OVERVIEW.md#serializing-the-tree) whenever the tree changes (e.g. whenever React DOM modifies the page). These "op codes" are just an array of numbers which correspond to _operations_ like "add something to the tree" or "remove something from the tree". For example, the operation to add a component to the tree might look like this:
```js
[
  // ...
  1,   // number 1 signifies an "add" operation
  2,   // component id
  1,   // type: class component
  1,   // parent id
  0,   // owner id
  1,   // id of component display name (corresponds to the string table)
  0,   // id of key string in the string table (zero indicates no key)
  // ...
]
```

The format described above is highly optimized but also inflexible. (For example, if the backend needs to add an additional piece of information to the above operation, the frontend needs to also know to advance its index within the operations array by one additional place.)

DevTools version 4.13 introduced the concept of a [_protocol version number_](https://github.com/facebook/react/pull/21331) to allow the frontend to ask the backend which version of the protcol it used. In hindsight, **this number should have included in the operations array itself** (as the first digit).

# The current problem
Generally the DevTools frontend and backend pieces are bundled together. For example, the most common DevTools surface– the browser extension– ships both pieces together and _injects_ the backend into the page during initialization. However there are some less common targets are not so tightly coupled– such as React Native (which embeds the backend into the application itself) or [Replay](https://replay.io/) (which records the backend "operations" array as part of its session data). In these cases, the frontend (UI) launched by the user (or embedded in the Replay player) may depend on an **incompatible protocol**.

This results in errors like #24142 and #23307 (and even https://github.com/RecordReplay/devtools/issues/5344):
<img width="791" alt="React DevTools Unsupported Bridge operation error" src="https://user-images.githubusercontent.com/29597/160892306-2bac3e5f-7bcc-4194-8a4d-4f2eecee618d.png">

# Potential solutions
As mentioned above, **the protocol version should have included in the operations array itself** (as the first digit). We should fix this when we eventually make a major breaking change (aka DevTools version 5) but in the meanwhile, maybe there's a way we can improve the current situation.

Some changes have already been made to support older protocol versions when possible (#24093) and to more clearly communicate _the reason_ for the error when that is not possible (#24147) but perhaps there's more still that we could do?

## Option 1: Add protocol version to the start of the "operations" array
We could retroactively update the "operations" array to always begin with the protocol version number.

We'd need a way to _detect_ this though (to distinguish this _newer_ message format from older ones). Currently, each "operations" message begins with two numbers– representing the renderer and the root (tree root). For example:
```js
// Older message format
[
  3, // renderer id
  1, // (tree) root element id
  0, // string table size
  // operations ...
]
```

Since the current operations array starts with a positive integer (the renderer ID) it would be ambiguous to insert another positive integer (the bridge protocol version). For this reason, I propose inserting _two_ new numbers to the start of the operations array: The first one being `0` (so we can reliably detect the newer format) and the second one being the protocol version. For example:
```js
// Newer message format
[
  0, // signifies the newer operations array format
  2, // bridge protocol version
  3, // renderer id
  1, // (tree) root element id
  0, // string table size
  // operations ...
]
```

The frontend could then reliably distinguish between these two backend formats:
```js
[3,1,0,...] // old format
[0,2,3,1,0,...] // new format
```

* **Pros**:
  * Would enable DevTools to get rid of the separate protocol version request method and more easily differentiate between different versions of the operations array when parsing.
  * Would also enable newer DevTools frontends to parse recorded operations arrays from older backends (e.g. the Replay case).
* **Cons**:
  * Would require us to retroactively publish patch updates to older backend releases.
  * Older frontends wouldn't support this change– and would error. (Although this is arguably no worse than the current situation.)

## Option 2: Automatically fall back to support older protocols
We could leave the current architecture in place (at least until version 5) but in the case of a protocol error (`UnsupportedBridgeOperationError`) we could have DevTools try to automatically force-downgrade replay the "operations" array with the assumption of an older protocol version number.

This is essentially how things already work for the React Native case, (as of version [4.24.1](https://github.com/facebook/react/blob/main/packages/react-devtools/CHANGELOG.md#4241)), but formalizing it would expand to also cover usecases like Replay.
* **Pros**:
  * Works with pre-existing DevTools backends.
* **Cons**:
  * Adds code complexity.

## Option 3: User configurable protocol version number
We could leave the current architecture in place (at least until version 5) but provide some sort of UI mechanism to allow users to override the default assumed protocol version number.
* **Pros**:
  * Works with pre-existing DevTools backends.
* **Cons**:
  * Poor/confusing UX.

# Solutions not considered
One solution _not_ mentined above would be to add a new message type (e.g. "new-operations") that begins with the bridge protocol version number. Newer frontends could listen for this message, but still fall back to listening for the old message ("operations"). Older frontends would continue to listen to the older message and not break (at least not in any new way).

The reason I think this solution is probably not worth pursuing is that it would double the amount of information the backend sends to the frontend via e.g. `postMessage` during performance-sensitive times. One of the main goals of the new DevTools was to reduce this kind of overhead, so I don't think that's worth compromising on.

A variation of this might be for backends to send the newer message type (e.g. "new-operations") _only_ and no longer continue to send the "operations" message. This would avoid the perfromance problem but would leave older frontends in a broken state (perpetually waiting on the "operations" array that is never sent).