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:
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
+339
-1297
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
};
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user