Migrate plugin channels to protomux-rpc only

Replace dual message-channel + RPC setup with a single registerPluginProtocol
path, unified sdk.channels (register/request/event/broadcast), RPC keepalive
(__p2ns.ping/pong), and core request lifecycle on RPC open. Update
global.profile and example.plugin, admin Plugin RPC stats, docs, and
test:plugin-rpc. Breaking: upgrade all peers together; no legacy adapters.
This commit is contained in:
Raven Scott
2026-05-28 11:55:01 -04:00
parent ba44ef267d
commit 6ee20a3f68
18 changed files with 778 additions and 1651 deletions
+6 -9
View File
@@ -349,16 +349,13 @@ For advanced use cases, you can also use manual replication:
// Get replication stream
const replication = sdk.drives.replicate('my-drive', true); // true = is initiator
// In a plugin with peer channels, you can replicate over a channel
sdk.channels.createChannel('file-sync', {
encoding: 'binary',
onOpen: (peerId) => {
// Start replication when peer connects
// Register an RPC protocol and stream over the peer connection when RPC is ready
sdk.channels.register('file-sync', {
onPeerOpen: (peerId) => {
const replication = sdk.drives.replicate('my-drive', true);
const channel = sdk.channels.getChannel('file-sync');
const peerChannel = channel.peerChannels.get(peerId);
if (peerChannel) {
replication.pipe(peerChannel).pipe(replication);
const peerChannel = sdk.channels.getProtocol('file-sync')?.peerChannels?.get(peerId);
if (peerChannel?.conn) {
replication.pipe(peerChannel.conn).pipe(replication);
}
}
});
+33 -36
View File
@@ -1,64 +1,61 @@
# Plugin Channels Documentation
# Plugin RPC Documentation
## Overview
Plugin Channels allow plugins to create custom protomux channels for peer-to-peer communication. This enables real-time, direct communication between peers without relying on HTTP requests.
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**: Channels are automatically scoped to your plugin domain (e.g., `peer.chat-chat`)
- **Multiple Encoding Types**: Support for JSON, string, binary, and custom encodings
- **Auto-Reconnection**: Channels automatically reconnect when peers reconnect
- **Lifecycle Management**: Automatic cleanup on plugin shutdown
- **Message Routing**: Automatic routing of messages to your handlers
- **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 |
## Basic Usage
> **Note:** The SDK is automatically available to plugins via the `require()` path relative to your plugin's location. For plugins in `plugin-sites/`, use `require('../../includes/plugins/sdk')`.
> **Note:** The SDK is available via `require('../../includes/plugins/sdk')` from `plugin-sites/`.
### Creating a Channel
### Register a protocol
```javascript
const sdk = require('../../includes/plugins/sdk');
// Create a channel with JSON encoding
sdk.channels.createChannel('chat', {
encoding: 'json',
onMessage: (data, peerId, peer) => {
console.log(`Received from ${peerId}:`, data);
sdk.channels.register('chat', {
methods: {
'chat.message': async (data, { peerId }) => {
console.log(`Received from ${peerId}:`, data);
return null;
}
},
onOpen: (peerId, channel) => {
console.log(`Peer ${peerId} connected`);
},
onClose: (peerId, channel) => {
console.log(`Peer ${peerId} disconnected`);
}
onPeerOpen: (peerId) => console.log(`Peer ${peerId} RPC ready`),
onPeerClose: (peerId) => console.log(`Peer ${peerId} disconnected`)
});
```
### Sending Messages
### Request, event, broadcast
```javascript
// Send to a specific peer
const ok = sdk.channels.send('chat', peerId, {
type: 'message',
text: 'Hello!'
});
if (!ok) {
// channel unavailable/backpressure; retry later
}
// Broadcast to all connected peers
sdk.channels.broadcast('chat', {
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'
});
```
**Important behavior notes (Protomux 3):**
- `sdk.channels.send()` returns the direct result of `message.send(data)` and may be `false` when a channel is not writable.
- `sdk.channels.sendAsync()` waits for `channel.fullyOpened()` (with timeout) before sending, but still returns `false` if `message.send()` fails.
- Do not assume closed/unhealthy channels will queue outbound messages indefinitely; always check return values.
Use `sdk.channels.isRpcReady('chat', peerId)` before sending if the peer just connected.
### Getting Channel Information
+19 -68
View File
@@ -1662,84 +1662,35 @@ const allowed = sdk.security.checkPermission('add-domain');
// Returns: boolean (currently always true)
```
## Protomux Channels
## Plugin RPC (protomux-rpc)
Provides peer-to-peer communication via protomux channels. See [PLUGIN_CHANNELS.md](plugins/PLUGIN_CHANNELS.md) for detailed documentation.
Behavior note:
- `sdk.channels.send()` / `sendAsync()` return the underlying `message.send(data)` boolean (not just channel existence). `false` means send was not accepted and should be retried by caller logic.
Peer-to-peer plugin traffic uses **protomux-rpc only** (JSON). See [PLUGIN_CHANNELS.md](PLUGIN_CHANNELS.md).
### Methods
```javascript
// Create a protomux channel for this plugin
const success = sdk.channels.createChannel('chat', {
encoding: 'json', // 'json', 'string', 'binary', or custom encoding object
onMessage: (data, peerId, peer) => {
// Handle incoming messages
sdk.channels.register('chat', {
methods: {
'chat.message': async (data, { peerId }) => ({ ok: true })
},
onOpen: (peerId, channel) => {
// Channel opened with peer
},
onClose: (peerId, channel) => {
// Channel closed with peer
},
autoReconnect: true // default: true
});
// Returns: boolean
// Get channel info for a protocol
const channelInfo = sdk.channels.getChannel('chat');
// Returns: Channel info object or null
// List all channels for this plugin
const channels = sdk.channels.listChannels();
// Returns: Array of protocol names
// Close a channel
const success = sdk.channels.closeChannel('chat');
// Returns: boolean
// Send data to a specific peer
const success = sdk.channels.send('chat', peerId, { message: 'Hello' });
// Returns: boolean
// Send data to a specific peer (async version that waits for bidirectional channel opening)
const success = await sdk.channels.sendAsync('chat', peerId, { message: 'Hello' }, 5000);
// waitTimeout: number (default: 5000ms)
// Returns: Promise<boolean>
// Broadcast data to all connected peers
const count = sdk.channels.broadcast('chat', { message: 'Hello everyone' });
// Returns: number of peers message was sent to
// Get list of connected peers for a channel
const peers = sdk.channels.getConnectedPeers('chat');
// Returns: Array of peer IDs
// Check if a peer is connected to a channel
const isConnected = sdk.channels.isPeerConnected('chat', peerId);
// Returns: boolean
```
### RPC channels (required for new plugins)
Protomux RPC is always enabled. Use typed methods instead of colon-delimited strings:
```javascript
sdk.channels.createChannel('sync', { encoding: 'json' });
sdk.channels.rpc.register('sync', {
'sync.push': async (payload) => {
// handle request, return JSON-serializable result
return { ok: true, received: payload };
}
onPeerOpen: (peerId) => {},
onPeerClose: (peerId) => {},
autoReconnect: true
});
const result = await sdk.channels.rpc.request('sync', peerId, 'sync.push', { id: 1 });
sdk.channels.rpc.event('sync', peerId, 'sync.notify', { id: 1 });
await sdk.channels.request('chat', peerId, 'chat.ping', { ts: Date.now() });
sdk.channels.event('chat', peerId, 'chat.typing', { active: true });
sdk.channels.broadcast('chat', 'chat.message', { text: 'hi' });
sdk.channels.getProtocol('chat');
sdk.channels.listProtocols();
sdk.channels.unregister('chat');
sdk.channels.isRpcReady('chat', peerId);
sdk.channels.getConnectedPeers('chat');
```
Do not register method names starting with `__p2ns.` (reserved for keepalive).
Core network invite/consensus uses `p2ns.core-request-rpc` only (no `p2ns.core-invite` channel):
| Method | Direction | Payload |