forked from snxraven/p2ns
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:
@@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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
@@ -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 |
|
||||
|
||||
Reference in New Issue
Block a user