Skip to content

Commit 90c1fa5

Browse files
committed
feat[devtools]: add method for connecting backend with custom messaging protocol
1 parent bb0944f commit 90c1fa5

File tree

2 files changed

+105
-1
lines changed

2 files changed

+105
-1
lines changed

packages/react-devtools-core/README.md

+10
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,16 @@ if (process.env.NODE_ENV !== 'production') {
3636
| `useHttps` | `false` | Socket connection to frontend should use secure protocol (wss). |
3737
| `websocket` | | Custom `WebSocket` connection to frontend; overrides `host` and `port` settings. |
3838

39+
40+
### `connectWithCustomMessagingProtocol` options
41+
| Prop | Description |
42+
|-----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|
43+
| `onSubscribe` | Function, which receives listener (function, with a single argument) as an argument. Called when backend subscribes to messages from the other end (frontend). |
44+
| `onUnsubscribe` | Function, which receives listener (function) as an argument. Called when backend unsubscribes to messages from the other end (frontend). |
45+
| `onMessage` | Function, which receives 2 arguments: event (string) and payload (any). Called when backend emits a message, which should be sent to the frontend. |
46+
47+
Unlike `connectToDevTools`, `connectWithCustomMessagingProtocol` returns a callback, which can be used for unsubscribing the backend from the global DevTools hook.
48+
3949
# Frontend API
4050

4151
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.

packages/react-devtools-core/src/backend.js

+95-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ import {
2222
} from './cachedSettings';
2323

2424
import type {BackendBridge} from 'react-devtools-shared/src/bridge';
25-
import type {ComponentFilter} from 'react-devtools-shared/src/frontend/types';
25+
import type {
26+
ComponentFilter,
27+
Wall,
28+
} from 'react-devtools-shared/src/frontend/types';
2629
import type {DevToolsHook} from 'react-devtools-shared/src/backend/types';
2730
import type {ResolveNativeStyle} from 'react-devtools-shared/src/backend/NativeStyleEditor/setupNativeStyleEditor';
2831

@@ -310,3 +313,94 @@ export function connectToDevTools(options: ?ConnectOptions) {
310313
});
311314
}
312315
}
316+
317+
type ConnectWithCustomMessagingOptions = {
318+
onSubscribe: (cb: Function) => void,
319+
onUnsubscribe: (cb: Function) => void,
320+
onMessage: (event: string, payload: any) => void,
321+
settingsManager: ?DevToolsSettingsManager,
322+
nativeStyleEditorValidAttributes?: $ReadOnlyArray<string>,
323+
resolveRNStyle?: ResolveNativeStyle,
324+
};
325+
326+
export function connectWithCustomMessagingProtocol({
327+
onSubscribe,
328+
onUnsubscribe,
329+
onMessage,
330+
settingsManager,
331+
nativeStyleEditorValidAttributes,
332+
resolveRNStyle,
333+
}: ConnectWithCustomMessagingOptions): Function {
334+
if (hook == null) {
335+
// DevTools didn't get injected into this page (maybe b'c of the contentType).
336+
return;
337+
}
338+
339+
if (settingsManager != null) {
340+
try {
341+
initializeUsingCachedSettings(settingsManager);
342+
} catch (e) {
343+
// If we call a method on devToolsSettingsManager that throws, or if
344+
// is invalid data read out, don't throw and don't interrupt initialization
345+
console.error(e);
346+
}
347+
}
348+
349+
const wall: Wall = {
350+
listen(fn: Function) {
351+
onSubscribe(fn);
352+
353+
return () => {
354+
onUnsubscribe(fn);
355+
};
356+
},
357+
send(event: string, payload: any) {
358+
onMessage(event, payload);
359+
},
360+
};
361+
362+
const bridge: BackendBridge = new Bridge(wall);
363+
364+
bridge.addListener(
365+
'updateComponentFilters',
366+
(componentFilters: Array<ComponentFilter>) => {
367+
// Save filter changes in memory, in case DevTools is reloaded.
368+
// In that case, the renderer will already be using the updated values.
369+
// We'll lose these in between backend reloads but that can't be helped.
370+
savedComponentFilters = componentFilters;
371+
},
372+
);
373+
374+
if (settingsManager != null) {
375+
bridge.addListener('updateConsolePatchSettings', consolePatchSettings =>
376+
cacheConsolePatchSettings(settingsManager, consolePatchSettings),
377+
);
378+
}
379+
380+
if (window.__REACT_DEVTOOLS_COMPONENT_FILTERS__ == null) {
381+
bridge.send('overrideComponentFilters', savedComponentFilters);
382+
}
383+
384+
const agent = new Agent(bridge);
385+
agent.addListener('shutdown', () => {
386+
// If we received 'shutdown' from `agent`, we assume the `bridge` is already shutting down,
387+
// and that caused the 'shutdown' event on the `agent`, so we don't need to call `bridge.shutdown()` here.
388+
hook.emit('shutdown');
389+
});
390+
391+
const unsubscribeBackend = initBackend(hook, agent, window);
392+
393+
const nativeStyleResolver: ResolveNativeStyle | void =
394+
resolveRNStyle || hook.resolveRNStyle;
395+
396+
if (nativeStyleResolver != null) {
397+
const validAttributes =
398+
nativeStyleEditorValidAttributes ||
399+
hook.nativeStyleEditorValidAttributes ||
400+
null;
401+
402+
setupNativeStyleEditor(bridge, agent, nativeStyleResolver, validAttributes);
403+
}
404+
405+
return unsubscribeBackend;
406+
}

0 commit comments

Comments
 (0)