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
+33 -12
View File
@@ -288,8 +288,9 @@ async function collectAdminStats() {
stats.holesailChildren = holesailChildren;
// Collect Peer Channels stats
const peerChannelsStats = collectPeerChannelsStats();
const peerChannelsStats = collectPluginRpcStats();
stats.peerChannels = peerChannelsStats;
stats.pluginRpc = peerChannelsStats;
// Collect HyperDB stats
const hyperdbStats = collectHyperDBStats();
@@ -327,13 +328,18 @@ function collectHistoricalMinutes(minutes) {
}
/**
* Collect comprehensive Peer Channels statistics
* Collect plugin protomux-rpc protocol statistics
*/
function collectPeerChannelsStats() {
function collectPluginRpcStats() {
const stats = {
schemaVersion: 1,
transport: 'rpc',
totalPlugins: 0,
totalProtocols: 0,
totalPeerConnections: 0,
totalMethods: 0,
rpcOpen: 0,
rpcAttached: 0,
openChannels: 0,
closedChannels: 0,
plugins: []
@@ -356,13 +362,18 @@ function collectPeerChannelsStats() {
for (const [protocol, channelInfo] of protocolMap.entries()) {
stats.totalProtocols++;
const handler = handlers?.get(protocol);
const methods = handler?.methods || [];
stats.totalMethods += methods.length;
const protocolInfo = {
name: protocol,
fullName: `${pluginDomain}-${protocol}`,
encoding: handler?.encoding || 'unknown',
fullName: `${pluginDomain}-${protocol}-rpc`,
transport: 'rpc',
methods,
autoReconnect: handler?.autoReconnect !== false,
peerCount: 0,
rpcOpenCount: 0,
rpcAttachedCount: 0,
openCount: 0,
closedCount: 0,
peers: []
@@ -373,11 +384,17 @@ function collectPeerChannelsStats() {
stats.totalPeerConnections += channelInfo.peerChannels.size;
for (const [peerId, peerChannel] of channelInfo.peerChannels.entries()) {
const isOpen = peerChannel.channel?.opened || false;
const isClosed = peerChannel.channel?.closed || false;
if (isOpen) {
const rpcAttached = !!peerChannel.rpc;
const rpcOpen = !!(peerChannel.rpc?.opened && !peerChannel.rpc?.closed);
if (rpcAttached) {
protocolInfo.rpcAttachedCount++;
stats.rpcAttached++;
}
if (rpcOpen) {
protocolInfo.rpcOpenCount++;
protocolInfo.openCount++;
stats.rpcOpen++;
stats.openChannels++;
} else {
protocolInfo.closedCount++;
@@ -387,7 +404,9 @@ function collectPeerChannelsStats() {
const peerInfo = {
peerId: peerId.substring(0, 16) + '...',
fullPeerId: peerId,
status: isClosed ? 'closed' : (isOpen ? 'open' : 'connecting'),
status: rpcOpen ? 'open' : (rpcAttached ? 'connecting' : 'closed'),
rpcReady: rpcOpen,
rpcAttached,
localOpened: peerChannel.localOpened || false,
remoteOpened: peerChannel.remoteOpened || false,
openedAt: peerChannel.openedAt || null,
@@ -409,12 +428,14 @@ function collectPeerChannelsStats() {
stats.plugins.push(pluginInfo);
}
} catch (err) {
logError('Admin', `Error collecting peer channels stats: ${err.message}`);
logError('Admin', `Error collecting plugin RPC stats: ${err.message}`);
}
return stats;
}
const collectPeerChannelsStats = collectPluginRpcStats;
/**
* Collect comprehensive HyperDB statistics
*/
+8 -7
View File
@@ -644,7 +644,7 @@
<p id="resources-holesails" class="text-2xl font-bold">-</p>
</div>
<div class="text-center">
<p class="text-sm theme-text-tertiary">Peer Channels</p>
<p class="text-sm theme-text-tertiary">Plugin RPC</p>
<p id="resources-channels" class="text-2xl font-bold">-</p>
</div>
</div>
@@ -693,9 +693,10 @@
<div id="core-recommendations" class="mt-4 hidden"></div>
</div>
<!-- Peer Channels Section -->
<!-- Plugin RPC Section -->
<div class="theme-table rounded-lg p-6 mb-6">
<h3 class="text-xl font-bold mb-4">Peer Channels</h3>
<h3 class="text-xl font-bold mb-2">Plugin RPC</h3>
<p class="text-sm theme-text-tertiary mb-4">protomux-rpc protocols registered by plugins</p>
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-4">
<div class="text-center">
<p class="text-sm theme-text-tertiary">Total Plugins</p>
@@ -706,16 +707,16 @@
<p id="peer-channels-total-protocols" class="text-2xl font-bold">-</p>
</div>
<div class="text-center">
<p class="text-sm theme-text-tertiary">Open Channels</p>
<p class="text-sm theme-text-tertiary">RPC Open</p>
<p id="peer-channels-open" class="text-2xl font-bold text-green-500">-</p>
</div>
<div class="text-center">
<p class="text-sm theme-text-tertiary">Closed/Connecting</p>
<p id="peer-channels-closed" class="text-2xl font-bold text-yellow-500">-</p>
<p class="text-sm theme-text-tertiary">Methods</p>
<p id="peer-channels-methods" class="text-2xl font-bold text-indigo-400">-</p>
</div>
</div>
<div id="peer-channels-details" class="space-y-4">
<p class="theme-text-tertiary text-center">Loading peer channels data...</p>
<p class="theme-text-tertiary text-center">Loading plugin RPC data...</p>
</div>
</div>
+24 -19
View File
@@ -92,34 +92,36 @@ function renderStats() {
});
}
// Render Peer Channels stats
function renderPeerChannelsStats(peerChannels) {
// Update summary cards
// Render Plugin RPC stats (stats.peerChannels / stats.pluginRpc)
function renderPluginRpcStats(peerChannels) {
const totalPluginsEl = document.getElementById('peer-channels-total-plugins');
const totalProtocolsEl = document.getElementById('peer-channels-total-protocols');
const openChannelsEl = document.getElementById('peer-channels-open');
const closedChannelsEl = document.getElementById('peer-channels-closed');
const methodsEl = document.getElementById('peer-channels-methods');
const detailsContainer = document.getElementById('peer-channels-details');
if (totalPluginsEl) totalPluginsEl.textContent = peerChannels?.totalPlugins || 0;
if (totalProtocolsEl) totalProtocolsEl.textContent = peerChannels?.totalProtocols || 0;
if (openChannelsEl) openChannelsEl.textContent = peerChannels?.openChannels || 0;
if (closedChannelsEl) closedChannelsEl.textContent = peerChannels?.closedChannels || 0;
if (openChannelsEl) openChannelsEl.textContent = peerChannels?.rpcOpen ?? peerChannels?.openChannels ?? 0;
if (methodsEl) methodsEl.textContent = peerChannels?.totalMethods ?? 0;
if (!detailsContainer) return;
if (!peerChannels || !peerChannels.plugins || peerChannels.plugins.length === 0) {
detailsContainer.innerHTML = '<p class="theme-text-tertiary text-center">No peer channels registered</p>';
detailsContainer.innerHTML = '<p class="theme-text-tertiary text-center">No plugin RPC protocols registered</p>';
return;
}
detailsContainer.innerHTML = peerChannels.plugins.map(plugin => {
const protocolsHtml = plugin.protocols.map(protocol => {
const statusColor = protocol.openCount > 0 ? 'bg-green-500' : 'bg-gray-500';
const statusColor = (protocol.rpcOpenCount ?? protocol.openCount) > 0 ? 'bg-green-500' : 'bg-gray-500';
const methodsLabel = (protocol.methods && protocol.methods.length)
? protocol.methods.join(', ')
: '(no plugin methods)';
const peersHtml = protocol.peers.length > 0 ? protocol.peers.map(peer => {
const peerStatusColor = peer.status === 'open' ? 'text-green-500' :
const peerStatusColor = peer.rpcReady || peer.status === 'open' ? 'text-green-500' :
peer.status === 'connecting' ? 'text-yellow-500' : 'text-red-500';
const peerStatusIcon = peer.status === 'open' ? 'fa-circle-check' :
const peerStatusIcon = peer.rpcReady || peer.status === 'open' ? 'fa-circle-check' :
peer.status === 'connecting' ? 'fa-spinner fa-spin' : 'fa-circle-xmark';
return `
<div class="flex items-center justify-between p-2 theme-glass rounded text-sm">
@@ -128,25 +130,25 @@ function renderPeerChannelsStats(peerChannels) {
<span class="font-mono">${peer.peerId}</span>
</div>
<div class="flex items-center gap-4 text-xs theme-text-secondary">
<span>Local: ${peer.localOpened ? '✓' : '✗'}</span>
<span>Remote: ${peer.remoteOpened ? '✓' : '✗'}</span>
${peer.reopenAttempts > 0 ? `<span class="text-yellow-500">Reopens: ${peer.reopenAttempts}</span>` : ''}
<span>RPC: ${peer.rpcReady ? '✓' : '✗'}</span>
<span>Conn: ${peer.connectionValid ? '✓' : '✗'}</span>
${peer.reopenAttempts > 0 ? `<span class="text-yellow-500">Reopens: ${peer.reopenAttempts}</span>` : ''}
</div>
</div>
`;
}).join('') : '<p class="text-sm theme-text-tertiary p-2">No peers connected</p>';
const openCount = protocol.rpcOpenCount ?? protocol.openCount ?? 0;
return `
<div class="mb-3">
<div class="flex items-center gap-2 mb-2">
<div class="flex items-center gap-2 mb-2 flex-wrap">
<span class="w-2 h-2 rounded-full ${statusColor}"></span>
<span class="font-semibold">${protocol.fullName}</span>
<span class="text-xs theme-text-secondary">(${protocol.encoding})</span>
<span class="text-xs theme-text-secondary font-mono">${methodsLabel}</span>
<span class="text-xs px-2 py-0.5 rounded ${protocol.autoReconnect ? 'bg-blue-500/20 text-blue-400' : 'bg-gray-500/20 text-gray-400'}">
${protocol.autoReconnect ? 'Auto-reconnect' : 'Manual'}
</span>
<span class="text-xs theme-text-secondary ml-auto">${protocol.openCount}/${protocol.peerCount} open</span>
<span class="text-xs theme-text-secondary ml-auto">${openCount}/${protocol.peerCount} RPC open</span>
</div>
<div class="space-y-1 ml-4">
${peersHtml}
@@ -644,9 +646,8 @@ function updateStatsDisplay(stats, historical) {
window.renderHolesailChildren(stats.holesailChildren || []);
}
// Peer Channels
if (stats.peerChannels) {
renderPeerChannelsStats(stats.peerChannels);
if (stats.peerChannels || stats.pluginRpc) {
renderPluginRpcStats(stats.pluginRpc || stats.peerChannels);
}
// HyperDB
@@ -1493,6 +1494,10 @@ window.requestStatsSnapshotViaWebSocket = requestStatsSnapshotViaWebSocket;
window.renderStats = renderStats;
window.updateStatsDisplay = updateStatsDisplay;
window.renderHolesailChildren = renderHolesailChildren;
function renderPeerChannelsStats(peerChannels) {
return renderPluginRpcStats(peerChannels);
}
window.renderPluginRpcStats = renderPluginRpcStats;
window.renderPeerChannelsStats = renderPeerChannelsStats;
window.renderCoreStats = renderCoreStats;
window.renderHyperDBStats = renderHyperDBStats;
File diff suppressed because it is too large Load Diff
+72 -29
View File
@@ -5,12 +5,17 @@
const c = require('compact-encoding');
const { logDebug, logError, logWarn } = require('../infrastructure/logger');
const { RESERVED, isReservedMethod, assertPluginMethodName } = require('./plugin-rpc-contract');
const RPC_METHODS_KEY = Symbol('rpcMethods');
const RESERVED_HANDLERS_KEY = Symbol('reservedRpcHandlers');
/** @type {Map<string, Map<string, Function>>} key: pluginDomain:protocol */
const methodRegistries = new Map();
/** @type {Map<string, Function>} keepalive callback per registry key */
const keepaliveHooks = new Map();
const jsonEncoding = {
preencode(state, value) {
c.string.preencode(state, JSON.stringify(value == null ? null : value));
@@ -37,51 +42,81 @@ function getOrCreateRegistry(pluginDomain, protocol) {
return methodRegistries.get(key);
}
/**
* Register RPC method handlers for a plugin channel.
* @param {string} pluginDomain
* @param {string} protocol
* @param {Object<string, Function>} methods - method name -> async handler(value) => result
*/
function registerRpcMethods(pluginDomain, protocol, methods) {
const registry = getOrCreateRegistry(pluginDomain, protocol);
for (const [method, handler] of Object.entries(methods)) {
for (const [method, handler] of Object.entries(methods || {})) {
assertPluginMethodName(method);
if (typeof handler !== 'function') {
throw new Error(`RPC handler for ${method} must be a function`);
}
registry.set(method, handler);
}
logDebug('ChannelRPC', `Registered ${Object.keys(methods).length} RPC method(s) on ${pluginDomain}-${protocol}`);
logDebug('ChannelRPC', `Registered ${Object.keys(methods || {}).length} RPC method(s) on ${pluginDomain}-${protocol}`);
}
function unregisterRpcMethods(pluginDomain, protocol) {
methodRegistries.delete(registryKey(pluginDomain, protocol));
keepaliveHooks.delete(registryKey(pluginDomain, protocol));
}
function setKeepaliveHook(pluginDomain, protocol, hook) {
const key = registryKey(pluginDomain, protocol);
if (hook) keepaliveHooks.set(key, hook);
else keepaliveHooks.delete(key);
}
function handleReservedRpc(method, value, { peerId, pluginDomain, protocol }) {
const hook = keepaliveHooks.get(registryKey(pluginDomain, protocol));
if (!hook) return null;
if (method === RESERVED.PING) {
hook.onPing(peerId);
return null;
}
if (method === RESERVED.PONG) {
hook.onPong(peerId, value);
return null;
}
return null;
}
function installReservedRpcHandlers(rpc, pluginDomain, protocol, peerId) {
if (!rpc) return;
if (!rpc[RESERVED_HANDLERS_KEY]) rpc[RESERVED_HANDLERS_KEY] = false;
if (rpc[RESERVED_HANDLERS_KEY]) return;
const respondNoReply = (method) => {
rpc.respond(method, { requestEncoding: jsonEncoding, responseEncoding: jsonEncoding }, async (value) => {
handleReservedRpc(method, value, { peerId, pluginDomain, protocol });
return null;
});
};
respondNoReply(RESERVED.PING);
respondNoReply(RESERVED.PONG);
rpc[RESERVED_HANDLERS_KEY] = true;
}
/**
* Attach registered methods to a peer's ProtomuxRPC instance.
* @param {import('protomux-rpc')} rpc
* @param {string} pluginDomain
* @param {string} protocol
*/
function applyRpcHandlersToPeer(rpc, pluginDomain, protocol, peerId) {
if (!rpc) return;
const registry = methodRegistries.get(registryKey(pluginDomain, protocol));
if (!registry || registry.size === 0) return;
const peerKey = `${peerId || 'unknown'}:${registryKey(pluginDomain, protocol)}`;
if (!rpc[RPC_METHODS_KEY]) rpc[RPC_METHODS_KEY] = new Set();
if (rpc[RPC_METHODS_KEY].has(peerKey)) return;
for (const [method, handler] of registry.entries()) {
rpc.respond(method, { requestEncoding: jsonEncoding, responseEncoding: jsonEncoding }, async (value) => {
try {
return await handler(value, { pluginDomain, protocol, peerId });
} catch (err) {
logError('ChannelRPC', `RPC ${method} failed: ${err.message}`);
throw err;
}
});
installReservedRpcHandlers(rpc, pluginDomain, protocol, peerId);
if (registry) {
for (const [method, handler] of registry.entries()) {
if (isReservedMethod(method)) continue;
rpc.respond(method, { requestEncoding: jsonEncoding, responseEncoding: jsonEncoding }, async (value) => {
try {
return await handler(value, { pluginDomain, protocol, peerId });
} catch (err) {
logError('ChannelRPC', `RPC ${method} failed: ${err.message}`);
throw err;
}
});
}
}
rpc[RPC_METHODS_KEY].add(peerKey);
}
@@ -103,9 +138,6 @@ async function waitForRpcOpen(rpc, timeoutMs = 5000) {
}
}
/**
* @returns {Promise<any>}
*/
async function rpcRequest(rpc, method, value = null, timeoutMs = 10000) {
if (!rpc) throw new Error('RPC channel not available');
await waitForRpcOpen(rpc, Math.min(timeoutMs, 5000));
@@ -133,13 +165,24 @@ function clearPeerRpcMarks(rpc) {
if (rpc && rpc[RPC_METHODS_KEY]) rpc[RPC_METHODS_KEY].clear();
}
function listRegisteredMethods(pluginDomain, protocol) {
const registry = methodRegistries.get(registryKey(pluginDomain, protocol));
if (!registry) return [];
return Array.from(registry.keys());
}
module.exports = {
jsonEncoding,
RESERVED,
registerRpcMethods,
unregisterRpcMethods,
setKeepaliveHook,
applyRpcHandlersToPeer,
installReservedRpcHandlers,
waitForRpcOpen,
rpcRequest,
rpcEvent,
clearPeerRpcMarks
clearPeerRpcMarks,
listRegisteredMethods,
isReservedMethod
};
+34
View File
@@ -0,0 +1,34 @@
/**
* Plugin protomux-rpc contract (reserved methods + schema version).
*/
const SCHEMA_VERSION = 1;
const RESERVED = {
PING: '__p2ns.ping',
PONG: '__p2ns.pong',
STATUS: '__p2ns.status'
};
const RESERVED_PREFIX = '__p2ns.';
function isReservedMethod(method) {
return typeof method === 'string' && method.startsWith(RESERVED_PREFIX);
}
function assertPluginMethodName(method) {
if (!method || typeof method !== 'string') {
throw new Error('RPC method name must be a non-empty string');
}
if (isReservedMethod(method)) {
throw new Error(`RPC method "${method}" uses reserved prefix ${RESERVED_PREFIX}`);
}
}
module.exports = {
SCHEMA_VERSION,
RESERVED,
RESERVED_PREFIX,
isReservedMethod,
assertPluginMethodName
};
+85 -137
View File
@@ -3504,19 +3504,47 @@ const sdk = {
},
/**
* Protomux Channels
* Provides peer-to-peer communication via protomux channels
* Plugin peer protocols (protomux-rpc only).
*/
channels: {
/**
* Get current plugin domain
* @returns {string|null} Plugin domain or null
*/
_getPluginDomain() {
return _getPluginDomain();
},
register(protocol, config = {}) {
const pluginDomain = this._getPluginDomain();
if (!pluginDomain) {
logError('PluginChannels', 'Cannot register protocol: PLUGIN_DOMAIN not set');
return false;
}
if (!protocol || typeof protocol !== 'string') {
logError('PluginChannels', 'Protocol name is required');
return false;
}
try {
require('./channel-manager').registerPluginProtocol(pluginDomain, protocol, config);
logInfo('PluginChannels', `RPC protocol ${pluginDomain}-${protocol} registered`);
return true;
} catch (err) {
logError('PluginChannels', `Protocol register failed: ${err.message}`);
return false;
}
},
unregister(protocol) {
const pluginDomain = this._getPluginDomain();
if (!pluginDomain) return false;
try {
require('./channel-manager').unregisterPluginProtocol(pluginDomain, protocol);
return true;
} catch (err) {
logError('PluginChannels', `Protocol unregister failed: ${err.message}`);
return false;
}
},
/**
* @deprecated Use channels.register()
* Create a protomux channel for this plugin
* @param {string} protocol - Protocol name (will be prefixed with plugin domain)
* @param {Object} options - Channel options
@@ -3539,22 +3567,19 @@ const sdk = {
return false;
}
try {
const channelManager = require('./channel-manager');
channelManager.registerPluginChannel(pluginDomain, protocol, {
encoding: options.encoding || 'string',
onMessage: options.onMessage || null,
onOpen: options.onOpen || null,
onClose: options.onClose || null,
autoReconnect: options.autoReconnect !== false
});
logInfo('PluginChannels', `Channel ${pluginDomain}-${protocol} created`);
return true;
} catch (err) {
logError('PluginChannels', `Error creating channel: ${err.message}`);
return false;
const methods = {};
if (typeof options.onMessage === 'function') {
methods.message = async (value, ctx) => {
options.onMessage(value, ctx.peerId);
return null;
};
}
return this.register(protocol, {
methods,
onPeerOpen: options.onOpen,
onPeerClose: options.onClose,
autoReconnect: options.autoReconnect
});
},
/**
@@ -3585,7 +3610,7 @@ const sdk = {
try {
const channelManager = require('./channel-manager');
return channelManager.listPluginChannels(pluginDomain);
return channelManager.listPluginProtocols(pluginDomain);
} catch (err) {
logError('PluginChannels', `Error listing channels: ${err.message}`);
return [];
@@ -3606,7 +3631,7 @@ const sdk = {
try {
const channelManager = require('./channel-manager');
channelManager.unregisterPluginChannel(pluginDomain, protocol);
channelManager.unregisterPluginProtocol(pluginDomain, protocol);
logInfo('PluginChannels', `Channel ${pluginDomain}-${protocol} closed`);
return true;
} catch (err) {
@@ -3623,75 +3648,42 @@ const sdk = {
* @returns {boolean} Success status
*/
send(protocol, peerId, data) {
const pluginDomain = this._getPluginDomain();
if (!pluginDomain) {
logError('PluginChannels', 'Cannot send message: PLUGIN_DOMAIN not set');
return false;
}
if (!peerId || typeof peerId !== 'string') {
logError('PluginChannels', 'Peer ID is required and must be a string');
return false;
}
try {
const channelManager = require('./channel-manager');
return channelManager.sendToPeer(pluginDomain, protocol, peerId, data);
} catch (err) {
logError('PluginChannels', `Error sending message: ${err.message}`);
return false;
}
return this.event(protocol, peerId, 'message', data);
},
/**
* Send data to a specific peer (async version that waits for bidirectional channel opening)
* @param {string} protocol - Protocol name
* @param {string} peerId - Target peer ID
* @param {any} data - Data to send (will be encoded according to channel encoding)
* @param {number} waitTimeout - Timeout to wait for channel to open (default: 5000ms)
* @returns {Promise<boolean>} Success status
*/
async sendAsync(protocol, peerId, data, waitTimeout = 5000) {
const pluginDomain = this._getPluginDomain();
if (!pluginDomain) {
logError('PluginChannels', 'Cannot send message: PLUGIN_DOMAIN not set');
return false;
}
if (!peerId || typeof peerId !== 'string') {
logError('PluginChannels', 'Peer ID is required and must be a string');
return false;
}
try {
const channelManager = require('./channel-manager');
return await channelManager.sendToPeerAsync(pluginDomain, protocol, peerId, data, waitTimeout);
} catch (err) {
logError('PluginChannels', `Error sending message: ${err.message}`);
return false;
if (!pluginDomain) return false;
const channelManager = require('./channel-manager');
const peerChannel = channelManager.getChannelInfo(pluginDomain, protocol)?.peerChannels?.get(peerId);
if (peerChannel?.rpc) {
await channelManager.waitForBidirectionalOpen(peerChannel, waitTimeout);
}
return this.event(protocol, peerId, 'message', data);
},
/**
* Broadcast data to all connected peers
* @param {string} protocol - Protocol name
* @param {any} data - Data to broadcast (will be encoded according to channel encoding)
* @returns {number} Number of peers message was sent to
*/
broadcast(protocol, data) {
broadcast(protocol, method, value) {
const pluginDomain = this._getPluginDomain();
if (!pluginDomain) {
logError('PluginChannels', 'Cannot broadcast message: PLUGIN_DOMAIN not set');
return 0;
}
if (!pluginDomain) return 0;
return require('./channel-manager').broadcastRpcEvent(pluginDomain, protocol, method, value);
},
try {
const channelManager = require('./channel-manager');
return channelManager.broadcastToPeers(pluginDomain, protocol, data);
} catch (err) {
logError('PluginChannels', `Error broadcasting message: ${err.message}`);
return 0;
}
async request(protocol, peerId, method, value = null, timeoutMs = 10000) {
const pluginDomain = this._getPluginDomain();
if (!pluginDomain) throw new Error('PLUGIN_DOMAIN not set');
return require('./channel-manager').rpcRequest(pluginDomain, protocol, peerId, method, value, timeoutMs);
},
event(protocol, peerId, method, value = null) {
const pluginDomain = this._getPluginDomain();
if (!pluginDomain) return false;
return require('./channel-manager').rpcEvent(pluginDomain, protocol, peerId, method, value);
},
isRpcReady(protocol, peerId) {
const pluginDomain = this._getPluginDomain();
if (!pluginDomain) return false;
return require('./channel-manager').isRpcReady(pluginDomain, protocol, peerId);
},
/**
@@ -3723,62 +3715,18 @@ const sdk = {
return channelInfo.peerChannels.has(peerId);
},
/**
* RPC-first channel API (protomux-rpc, JSON payloads).
*/
rpc: {
/**
* Register RPC method handlers for a protocol.
* @param {string} protocol
* @param {Object<string, Function>} methods - method name -> async (value) => result
*/
register(protocol, methods) {
const pluginDomain = sdk.channels._getPluginDomain();
if (!pluginDomain) {
logError('PluginChannels', 'Cannot register RPC: PLUGIN_DOMAIN not set');
return false;
}
try {
const channelManager = require('./channel-manager');
channelManager.registerRpcMethods(pluginDomain, protocol, methods);
return true;
} catch (err) {
logError('PluginChannels', `RPC register failed: ${err.message}`);
return false;
}
},
getPeerRpc(protocol, peerId) {
const pluginDomain = this._getPluginDomain();
if (!pluginDomain) return null;
return require('./channel-manager').getPeerRpc(pluginDomain, protocol, peerId);
},
/**
* @param {string} protocol
* @param {string} peerId
* @param {string} method
* @param {any} value
* @param {number} timeoutMs
* @returns {Promise<any>}
*/
async request(protocol, peerId, method, value = null, timeoutMs = 10000) {
const pluginDomain = sdk.channels._getPluginDomain();
if (!pluginDomain) throw new Error('PLUGIN_DOMAIN not set');
const channelManager = require('./channel-manager');
return channelManager.rpcRequest(pluginDomain, protocol, peerId, method, value, timeoutMs);
},
listProtocols() {
return this.listChannels();
},
/**
* Fire-and-forget RPC event (no response).
*/
event(protocol, peerId, method, value = null) {
const pluginDomain = sdk.channels._getPluginDomain();
if (!pluginDomain) return false;
const channelManager = require('./channel-manager');
return channelManager.rpcEvent(pluginDomain, protocol, peerId, method, value);
},
getPeerRpc(protocol, peerId) {
const pluginDomain = sdk.channels._getPluginDomain();
if (!pluginDomain) return null;
const channelManager = require('./channel-manager');
return channelManager.getPeerRpc(pluginDomain, protocol, peerId);
}
getProtocol(protocol) {
return this.getChannel(protocol);
}
},