5.6 KiB
Plugin RPC Documentation
Overview
Plugins communicate peer-to-peer using protomux-rpc only (JSON payloads). Each registered protocol gets one RPC mux per peer ({pluginDomain}-{protocol}-rpc). There is no separate protomux message channel.
Features
- Scoped protocol names: Wire name
{pluginDomain}-{protocol}-rpc - Named methods:
request,event, andbroadcast(protocol, method, value) - Reserved keepalive:
__p2ns.ping/__p2ns.pong(handled internally; do not register__p2ns.*methods) - Auto-reconnection: RPC sessions recreate when peers reconnect
- Lifecycle hooks:
onPeerOpen/onPeerClose
Breaking migration
Upgrade all nodes and plugins together. Mixed old (message channel + RPC) and new (RPC-only) peers will not interoperate on plugin protocols.
| Old | New |
|---|---|
createChannel('x', { onMessage }) |
register('x', { methods: { 'my.event': handler } }) |
broadcast('x', data) |
broadcast('x', 'my.event', data) |
channels.rpc.register |
channels.register with methods |
send('x', peer, data) |
event('x', peer, 'message', data) or a named method |
closeChannel('x') |
unregister('x') |
Basic usage
Note: The SDK is available via
require('../../includes/plugins/sdk')fromplugin-sites/.
Register a protocol
const sdk = require('../../includes/plugins/sdk');
sdk.channels.register('chat', {
methods: {
'chat.message': async (data, { peerId }) => {
sdk.log.debug('chat', `From ${peerId}: ${JSON.stringify(data)}`);
return null;
},
'chat.ping': async (_value, { peerId }) => ({ pong: true, peerId })
},
onPeerOpen: (peerId) => sdk.log.debug('chat', `RPC ready: ${peerId}`),
onPeerClose: (peerId) => sdk.log.debug('chat', `RPC closed: ${peerId}`),
autoReconnect: true
});
Request, event, broadcast
const result = await sdk.channels.request('chat', peerId, 'chat.ping', { ts: Date.now() });
sdk.channels.event('chat', peerId, 'chat.typing', { active: true });
const sentCount = sdk.channels.broadcast('chat', 'chat.message', {
type: 'announcement',
text: 'Server restarting in 5 minutes'
});
Use sdk.channels.isRpcReady('chat', peerId) before sending if the peer just connected.
Protocol information
const info = sdk.channels.getProtocol('chat'); // alias: getChannel()
const protocols = sdk.channels.listProtocols(); // alias: listChannels()
const peers = sdk.channels.getConnectedPeers('chat');
const ready = sdk.channels.isRpcReady('chat', peerId);
getProtocol() returns RPC-centric state: transport: 'rpc', peerChannels map with rpc handles per peer (no legacy message channel object).
Register options
| Option | Description |
|---|---|
methods |
Map of method name → async (value, { peerId, pluginDomain, protocol }) => result |
onPeerOpen |
Called when RPC is open for a peer |
onPeerClose |
Called when RPC closes for a peer |
autoReconnect |
Recreate RPC when peer reconnects (default: true) |
Method names must not start with __p2ns. (reserved for keepalive).
Complete example: chat plugin
const sdk = require('../../includes/plugins/sdk');
async function onInit() {
sdk.channels.register('chat', {
methods: {
'chat.message': async (data, { peerId }) => {
sdk.websocket.broadcast({ type: 'chat', from: peerId, ...data });
return null;
}
},
onPeerOpen: (peerId) => {
sdk.channels.event('chat', peerId, 'chat.sync', { since: Date.now() });
}
});
}
async function onShutdown() {
sdk.channels.unregister('chat');
}
module.exports = { handler, onInit, onShutdown };
Best practices
- Register in
onInitso RPC is ready before peers attach. - Use explicit method names (
domain.action) instead of opaque blobs. - Check
isRpcReadybeforerequeston freshly connected peers. - Unregister in
onShutdownto release handlers and peer state. - Return JSON-serializable values from request handlers; use
nullfor fire-and-forget handlers.
Protocol naming
- Plugin domain:
peer.chat - Protocol:
chat - Wire mux:
peer.chat-chat-rpc
Use the short protocol name in the SDK; the manager adds the domain and -rpc suffix.
Troubleshooting
| Issue | Checks |
|---|---|
| RPC not registered | PLUGIN_DOMAIN set; register() in onInit; no reserved method names |
| Peer not receiving | Both nodes upgraded; isRpcReady; method name matches on both sides |
request hangs |
RPC open timeout; peer disconnected; handler throws |
API reference
| Method | Description |
|---|---|
register(protocol, config) |
Register RPC protocol and handlers |
unregister(protocol) |
Tear down protocol |
request(protocol, peerId, method, value?, timeoutMs?) |
Request/response |
event(protocol, peerId, method, value?) |
Fire-and-forget |
broadcast(protocol, method, value) |
Fan-out event to all peers with open RPC |
getProtocol(protocol) |
Protocol + per-peer RPC state |
listProtocols() |
Registered protocol names |
getConnectedPeers(protocol) |
Peer IDs with attached RPC state |
isRpcReady(protocol, peerId) |
Whether RPC mux is open |
getPeerRpc(protocol, peerId) |
Underlying ProtomuxRPC instance |
Deprecated shims (createChannel, send, sendAsync, nested channels.rpc) may still exist but should not be used in new code.
See also
- Plugin SDK
- Plugin system
- example.plugin
- global.profile —
profile.updatebroadcast pattern