Skip to content

Commit

Permalink
feat[devtools]: add method for connecting backend with custom messagi…
Browse files Browse the repository at this point in the history
…ng protocol (#28552)

## Summary

RDT backend will now expose method `connectWithCustomMessagingProtocol`,
which will be similar to the classic `connectToDevTools` one, but with
few differences:
1. It delegates the communication management between frontend and
backend to the owner (whos injecting RDT backend). Unlike the
`connectToDevTools`, which is relying on websocket connection and
receives host and port as an arguments.
2. It returns a callback, which can be used for unsubscribing the
current backend instance from the global DevTools hook.


This is a prerequisite for any non-browser RDT integration, which is not
designed to be based on websocket.
  • Loading branch information
hoxyq authored Apr 12, 2024
1 parent c8a0350 commit d012a32
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 1 deletion.
10 changes: 10 additions & 0 deletions packages/react-devtools-core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,16 @@ if (process.env.NODE_ENV !== 'production') {
| `useHttps` | `false` | Socket connection to frontend should use secure protocol (wss). |
| `websocket` | | Custom `WebSocket` connection to frontend; overrides `host` and `port` settings. |


### `connectWithCustomMessagingProtocol` options
| Prop | Description |
|-----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `onSubscribe` | Function, which receives listener (function, with a single argument) as an argument. Called when backend subscribes to messages from the other end (frontend). |
| `onUnsubscribe` | Function, which receives listener (function) as an argument. Called when backend unsubscribes to messages from the other end (frontend). |
| `onMessage` | Function, which receives 2 arguments: event (string) and payload (any). Called when backend emits a message, which should be sent to the frontend. |

Unlike `connectToDevTools`, `connectWithCustomMessagingProtocol` returns a callback, which can be used for unsubscribing the backend from the global DevTools hook.

# Frontend API

Frontend APIs can be used to render the DevTools UI into a DOM node. One example of this is [`react-devtools`](https://github.com/facebook/react/tree/main/packages/react-devtools) which wraps DevTools in an Electron app.
Expand Down
96 changes: 95 additions & 1 deletion packages/react-devtools-core/src/backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ import {
} from './cachedSettings';

import type {BackendBridge} from 'react-devtools-shared/src/bridge';
import type {ComponentFilter} from 'react-devtools-shared/src/frontend/types';
import type {
ComponentFilter,
Wall,
} from 'react-devtools-shared/src/frontend/types';
import type {DevToolsHook} from 'react-devtools-shared/src/backend/types';
import type {ResolveNativeStyle} from 'react-devtools-shared/src/backend/NativeStyleEditor/setupNativeStyleEditor';

Expand Down Expand Up @@ -310,3 +313,94 @@ export function connectToDevTools(options: ?ConnectOptions) {
});
}
}

type ConnectWithCustomMessagingOptions = {
onSubscribe: (cb: Function) => void,
onUnsubscribe: (cb: Function) => void,
onMessage: (event: string, payload: any) => void,
settingsManager: ?DevToolsSettingsManager,
nativeStyleEditorValidAttributes?: $ReadOnlyArray<string>,
resolveRNStyle?: ResolveNativeStyle,
};

export function connectWithCustomMessagingProtocol({
onSubscribe,
onUnsubscribe,
onMessage,
settingsManager,
nativeStyleEditorValidAttributes,
resolveRNStyle,
}: ConnectWithCustomMessagingOptions): Function {
if (hook == null) {
// DevTools didn't get injected into this page (maybe b'c of the contentType).
return;
}

if (settingsManager != null) {
try {
initializeUsingCachedSettings(settingsManager);
} catch (e) {
// If we call a method on devToolsSettingsManager that throws, or if
// is invalid data read out, don't throw and don't interrupt initialization
console.error(e);
}
}

const wall: Wall = {
listen(fn: Function) {
onSubscribe(fn);

return () => {
onUnsubscribe(fn);
};
},
send(event: string, payload: any) {
onMessage(event, payload);
},
};

const bridge: BackendBridge = new Bridge(wall);

bridge.addListener(
'updateComponentFilters',
(componentFilters: Array<ComponentFilter>) => {
// Save filter changes in memory, in case DevTools is reloaded.
// In that case, the renderer will already be using the updated values.
// We'll lose these in between backend reloads but that can't be helped.
savedComponentFilters = componentFilters;
},
);

if (settingsManager != null) {
bridge.addListener('updateConsolePatchSettings', consolePatchSettings =>
cacheConsolePatchSettings(settingsManager, consolePatchSettings),
);
}

if (window.__REACT_DEVTOOLS_COMPONENT_FILTERS__ == null) {
bridge.send('overrideComponentFilters', savedComponentFilters);
}

const agent = new Agent(bridge);
agent.addListener('shutdown', () => {
// If we received 'shutdown' from `agent`, we assume the `bridge` is already shutting down,
// and that caused the 'shutdown' event on the `agent`, so we don't need to call `bridge.shutdown()` here.
hook.emit('shutdown');
});

const unsubscribeBackend = initBackend(hook, agent, window);

const nativeStyleResolver: ResolveNativeStyle | void =
resolveRNStyle || hook.resolveRNStyle;

if (nativeStyleResolver != null) {
const validAttributes =
nativeStyleEditorValidAttributes ||
hook.nativeStyleEditorValidAttributes ||
null;

setupNativeStyleEditor(bridge, agent, nativeStyleResolver, validAttributes);
}

return unsubscribeBackend;
}

0 comments on commit d012a32

Please sign in to comment.