Files
p2ns/docs/plugins/PLUGIN_CHANNELS.md
T
2026-05-28 12:39:38 -04:00

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, and broadcast(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') from plugin-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

  1. Register in onInit so RPC is ready before peers attach.
  2. Use explicit method names (domain.action) instead of opaque blobs.
  3. Check isRpcReady before request on freshly connected peers.
  4. Unregister in onShutdown to release handlers and peer state.
  5. Return JSON-serializable values from request handlers; use null for 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