
Research
Two Malicious Rust Crates Impersonate Popular Logger to Steal Wallet Keys
Socket uncovers malicious Rust crates impersonating fast_log to steal Solana and Ethereum wallet keys from source code.
@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-client
v4 is expected in apps that use this SDK.
pluginId
Use 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) => NativeBus
Builds 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) => WebBus
Creates 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;
};
DevtoolsPlugin
import 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.
Research
Socket uncovers malicious Rust crates impersonating fast_log to steal Solana and Ethereum wallet keys from source code.
Research
A malicious package uses a QR code as steganography in an innovative technique.
Research
/Security News
Socket identified 80 fake candidates targeting engineering roles, including suspected North Korean operators, exposing the new reality of hiring as a security function.