
Product
Introducing Webhook Events for Alert Changes
Add real-time Socket webhook events to your workflows to automatically receive software supply chain alert changes in real time.
@rn-devtools/plugin-sdk
Advanced tools
Tools and utilities for building rn-devtools plugins on both the dashboard (web) and the device (native) sides.
# with yarn
yarn add -D @rn-devtools/plugin-sdk
# or with npm
npm i -D @rn-devtools/plugin-sdk
Peer dependency:
socket.io-clientv4 is expected in apps that use this SDK.
pluginIdUse a stable string, e.g. "rn-devtools.demo.counter".
Create a client bound to your pluginId, the active socket, and the current deviceId.
import type { Socket } from 'socket.io-client';
import { createNativePluginClient } from '@rn-devtools/plugin-sdk';
// somewhere in your native runtime
function makeNativeBus(socket: Socket, deviceId: string) {
const pluginId = 'rn-devtools.demo.counter';
const bus = createNativePluginClient(pluginId, socket, deviceId);
// send an event up to the dashboard
bus.sendMessage('counter:value', { value: 42 });
// listen for commands coming down from the dashboard
const unsubscribe = bus.addMessageListener<{ step: number }>('counter:inc', ({ step }) => {
// increment your native counter by `step`
});
// later, on cleanup
return () => unsubscribe();
}
Create a client for the same pluginId. Provide a function that returns the currently selected device id.
import * as React from 'react';
import { createWebPluginClient } from '@rn-devtools/plugin-sdk';
export function CounterPanel({ currentDeviceId }: { currentDeviceId?: string }) {
const pluginId = 'rn-devtools.demo.counter';
const bus = React.useMemo(
() => createWebPluginClient(pluginId, () => currentDeviceId),
[pluginId, currentDeviceId]
);
React.useEffect(() => {
// listen for values coming from the device
const unsub = bus.addMessageListener<{ value: number }>('counter:value', (payload, { deviceId }) => {
console.log('value from', deviceId, payload.value);
});
return () => unsub();
}, [bus]);
return (
<button onClick={() => bus.sendMessage('counter:inc', { step: 1 })}>
Increment on device
</button>
);
}
Dashboard (web) Device (native)
------------------------------------ ------------------------------------
sendMessage(event, payload, deviceId) → socket.emit("plugin:down", { pluginId, deviceId, event, payload })
↑
socket.on("plugin:up", handler) ← sendMessage(event, payload)
socket.emit("plugin:up", { pluginId, deviceId, event, payload, timestamp })
On dashboard init, a singleton socket is created and says hello:
socket = io('/', { transports: ['websocket', 'polling'] })socket.emit('devtools:hello', { role: 'dashboard' })createNativePluginClient(pluginId, socket, deviceId) => NativeBusBuilds a message bus bound to a specific device and plugin.
export type NativeBus = {
sendMessage: (event: string, payload?: unknown) => void;
addMessageListener: <T = unknown>(event: string, cb: (payload: T) => void) => () => void;
};
sendMessage(event, payload?) → emits plugin:up with { pluginId, deviceId, event, payload, timestamp }.addMessageListener(event, cb) → listens to plugin:down and filters by { pluginId, deviceId, event }. Returns an unsubscribe function.createWebPluginClient(pluginId, getDeviceId) => WebBusCreates or reuses a singleton socket and builds a bus scoped to your plugin.
export type WebBus = {
socket: Socket; // shared across plugins
sendMessage: (event: string, payload?: unknown, deviceId?: string) => void;
addMessageListener: <T = unknown>(
event: string,
cb: (payload: T, meta: { deviceId: string }) => void,
) => () => void;
};
sendMessage(event, payload?, deviceId?) → emits plugin:down to the resolved device id
(deviceId ?? getDeviceId()); no-op if no device id is available.addMessageListener(event, cb) → listens to plugin:up for your pluginId and event.
The callback receives { payload, meta: { deviceId } }. Returns an unsubscribe function.Note: The dashboard socket is a singleton. All plugins share the same
io('/')connection.
export type Device = {
id: string;
deviceId: string;
deviceName: string;
isConnected: boolean;
platform?: string;
};
export type PluginProps = {
targetDevice: Device; // the currently focused device
allDevices: Device[]; // every known device
isDashboardConnected: boolean;
};
export type PluginMsg = {
pluginId: string;
deviceId?: string;
event: string;
payload?: Record<string, unknown>;
timestamp?: number; // set by native on plugin:up
};
export type DevtoolsPlugin = {
id: string; // your pluginId
title: string; // display name
Icon: React.FC<{ className?: string }>;
mount: React.ComponentType<PluginProps>; // the main React view of your plugin
};
export type NativeHookProps = {
socket: Socket;
deviceId: string;
};
DevtoolsPluginimport type { DevtoolsPlugin, PluginProps } from '@rn-devtools/plugin-sdk';
import { createWebPluginClient } from '@rn-devtools/plugin-sdk';
function Panel(props: PluginProps) {
const bus = React.useMemo(
() => createWebPluginClient('rn-devtools.demo.counter', () => props.targetDevice?.deviceId),
[props.targetDevice?.deviceId]
);
// ...render UI and talk to native via `bus`
return null;
}
export const CounterPlugin: DevtoolsPlugin = {
id: 'rn-devtools.demo.counter',
title: 'Counter',
Icon: (p) => <svg {...p} viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/></svg>,
mount: Panel,
};
plugin:up with { pluginId, deviceId, event, payload, timestamp }plugin:down with { pluginId, deviceId, event, payload }devtools:hello with { role: 'dashboard' } emitted once per sessionaddMessageListener in useEffect cleanup or component teardownThe dashboard client ignores sends if it cannot resolve a device id. Make sure getDeviceId() is stable and returns a value when your UI is active.
The native client filters by exact pluginId, deviceId, and event. Make sure they match on both ends.
timestamp is added on native plugin:up messages; the dashboard does not set it.
If you want to support broadcasts from native (i.e., deviceId omitted), confirm your filtering logic. A common pattern is:
// Accept messages for the current device *or* broadcasts
if (msg.deviceId !== undefined && msg.deviceId !== current) return;
addMessageListener<T>() to get typed payloads per event.sendMessage/addMessageListener to constrain event names.FAQs
Plugin SDK for rn-devtools
We found that @rn-devtools/plugin-sdk demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Product
Add real-time Socket webhook events to your workflows to automatically receive software supply chain alert changes in real time.

Security News
ENISA has become a CVE Program Root, giving the EU a central authority for coordinating vulnerability reporting, disclosure, and cross-border response.

Product
Socket now scans OpenVSX extensions, giving teams early detection of risky behaviors, hidden capabilities, and supply chain threats in developer tools.