Files
BridgeSwarm/examples/hrpc-demo/app.js
T
Raven Scott f71b01bdb2
CI / Build & Test (push) Successful in 8m7s
Update examples
2026-07-27 02:28:23 -04:00

353 lines
16 KiB
JavaScript

(function () {
const statusEl = document.getElementById('status');
const topicEl = document.getElementById('topic');
const btnJoin = document.getElementById('btnJoin');
const btnLeave = document.getElementById('btnLeave');
const peersEl = document.getElementById('peers');
const publicKeyEl = document.getElementById('publicKey');
const connSelectRow = document.getElementById('connSelectRow');
const connSelect = document.getElementById('connSelect');
const btnEnableHrpc = document.getElementById('btnEnableHrpc');
const hrpcStatusEl = document.getElementById('hrpcStatus');
const pingRow = document.getElementById('pingRow');
const pingValueEl = document.getElementById('pingValue');
const btnPing = document.getElementById('btnPing');
const btnFetchStream = document.getElementById('btnFetchStream');
const btnStreamSum = document.getElementById('btnStreamSum');
const pongResultEl = document.getElementById('pongResult');
const logEl = document.getElementById('log');
function setStatus(msg, className) {
statusEl.textContent = msg;
statusEl.className = 'status ' + (className || 'ok');
}
function log(msg, type) {
const line = document.createElement('div');
line.className = type || 'sys';
line.textContent = '[' + new Date().toLocaleTimeString() + '] ' + msg;
logEl.appendChild(line);
logEl.scrollTop = logEl.scrollHeight;
}
let swarm = null;
let currentConnId = null;
let hrpcEnabled = false;
const connectionEntries = [];
const session = BridgeSwarmExamples.sessionStore('hrpc');
connSelect.addEventListener('change', function () {
currentConnId = connSelect.value || null;
});
function updatePeers() {
peersEl.textContent = 'Peers: ' + connectionEntries.length;
btnEnableHrpc.disabled = !swarm || connectionEntries.length === 0;
if (connectionEntries.length === 0) {
connSelectRow.style.display = 'none';
connSelect.innerHTML = '';
currentConnId = null;
hrpcStatusEl.style.display = 'none';
pingRow.style.display = 'none';
pongResultEl.style.display = 'none';
return;
}
connSelectRow.style.display = 'flex';
const sel = connSelect.value;
connSelect.innerHTML = '';
const hasAnyHrpc = connectionEntries.some(function (e) { return e.hrpcEnabled; });
hrpcStatusEl.style.display = '';
if (hasAnyHrpc) {
pingRow.style.display = 'flex';
hrpcStatusEl.textContent = 'HRPC enabled on ' + connectionEntries.filter(function (e) { return e.hrpcEnabled; }).length + ' connection(s). You can ping.';
hrpcStatusEl.className = 'status ok';
} else {
pingRow.style.display = 'none';
hrpcStatusEl.textContent = 'Enabling HRPC on connection(s)… (wait up to 30s for other tab)';
hrpcStatusEl.className = 'status';
}
connectionEntries.forEach(function (e, i) {
const keyShort = (e.peerInfo.publicKey || '').slice(0, 16) + '\u2026';
const opt = document.createElement('option');
opt.value = e.connId;
opt.textContent = 'Peer ' + (i + 1) + ' (' + keyShort + ')' + (e.hrpcEnabled ? ' \u2713' : '');
if (e.connId === currentConnId || (!currentConnId && i === 0)) opt.selected = true;
connSelect.appendChild(opt);
});
currentConnId = connSelect.value || (connectionEntries[0] && connectionEntries[0].connId) || null;
}
function enableHrpcForConn(connId) {
if (typeof window.BridgeSwarm.request !== 'function') return;
window.BridgeSwarm.request('attachHrpc', { connId }).then(function (res) {
const entry = connectionEntries.find(function (e) { return e.connId === connId; });
if (!entry) return;
if (res && res.ok) {
entry.hrpcEnabled = true;
log('HRPC enabled on connection ' + (connectionEntries.indexOf(entry) + 1), 'hrpc');
updatePeers();
} else {
log('HRPC failed for connection: ' + (res && res.error ? res.error : 'unknown'), 'err');
if (res && res.error && res.error.indexOf('Connection not found') !== -1) {
const idx = connectionEntries.findIndex(function (e) { return e.connId === connId; });
if (idx !== -1) connectionEntries.splice(idx, 1);
if (currentConnId === connId) currentConnId = connectionEntries[0] ? connectionEntries[0].connId : null;
updatePeers();
}
}
}).catch(function (err) {
log('HRPC error: ' + err.message, 'err');
});
}
async function joinTopic(topic, opts) {
if (swarm) return;
opts = opts || {};
setStatus('Joining "' + topic + '"…');
swarm = new window.BridgeSwarm(session.swarmOptions('bridge-swarm-hrpc-demo', topic, opts));
swarm.on('connection', function (conn, peerInfo) {
const peerKey = peerInfo.publicKey || '';
const keyShort = peerKey.slice(0, 16) + '…';
const existingIdx = connectionEntries.findIndex(function (e) { return (e.peerInfo.publicKey || '') === peerKey; });
if (existingIdx !== -1) {
const old = connectionEntries[existingIdx];
connectionEntries.splice(existingIdx, 1);
try { old.conn.destroy(); } catch (_) {}
log('Replaced connection for peer ' + keyShort, 'sys');
}
log('Connection: peer ' + keyShort, 'peer');
connectionEntries.push({ conn, connId: conn.connId, peerInfo, hrpcEnabled: false });
updatePeers();
if (!currentConnId) currentConnId = conn.connId;
enableHrpcForConn(conn.connId);
conn.on('end', function () {
const i = connectionEntries.findIndex(function (e) { return e.conn === conn; });
if (i !== -1) connectionEntries.splice(i, 1);
if (currentConnId === conn.connId) currentConnId = connectionEntries[0] ? connectionEntries[0].connId : null;
updatePeers();
log('Peer left', 'sys');
});
conn.on('error', function (err) { log('Peer error: ' + err.message, 'err'); });
});
await swarm.join(topic);
await session.resumeConnections(swarm);
session.markJoined(swarm, topic);
const pubKey = await swarm.getPublicKey();
publicKeyEl.textContent = 'Your public key: ' + pubKey;
setStatus('Joined "' + topic + '". Open another tab and join the same topic; HRPC enables automatically.', 'ok');
btnJoin.disabled = true;
btnLeave.disabled = false;
log('Joined topic: ' + topic, 'sys');
}
(function initWhenReady(attempts) {
if (attempts >= 50) {
setStatus('BridgeSwarm extension not detected. Install it and reload.', 'error');
return;
}
if (typeof window.BridgeSwarm === 'undefined') {
setStatus('Waiting for extension…', 'warn');
setTimeout(function () { initWhenReady(attempts + 1); }, 100);
return;
}
window.BridgeSwarm.ready().then(async function () {
var sess = session.load();
if (sess && sess.active && sess.topic) {
topicEl.value = sess.topic;
setStatus('Restoring session…');
try {
await joinTopic(sess.topic, { swarmId: sess.swarmId });
} catch (err) {
session.clear();
setStatus(err.message || 'Could not restore session', 'error');
log('Error: ' + (err.message || err), 'err');
}
return;
}
setStatus('Ready. Join a topic, then open another tab and join the same topic.');
}).catch(function () {
setStatus('BridgeSwarm extension not detected.', 'error');
});
})(0);
btnJoin.addEventListener('click', async function () {
if (typeof window.BridgeSwarm === 'undefined') {
setStatus('Extension not ready.', 'error');
return;
}
const topic = topicEl.value.trim() || 'hrpc-demo';
if (swarm) return;
try {
await joinTopic(topic);
} catch (err) {
setStatus('Error: ' + err.message, 'error');
log('Error: ' + err.message, 'err');
}
});
btnLeave.addEventListener('click', async function () {
if (!swarm) return;
try {
const topic = topicEl.value.trim() || 'hrpc-demo';
await swarm.leave(topic);
await swarm.destroy();
connectionEntries.length = 0;
currentConnId = null;
hrpcEnabled = false;
pingRow.style.display = 'none';
hrpcStatusEl.style.display = 'none';
pongResultEl.style.display = 'none';
updatePeers();
btnEnableHrpc.disabled = true;
swarm = null;
session.clear();
setStatus('Left topic. You can join again.');
btnJoin.disabled = false;
btnLeave.disabled = true;
log('Left topic and destroyed swarm', 'sys');
} catch (err) {
log('Error: ' + err.message, 'err');
}
});
btnEnableHrpc.addEventListener('click', async function () {
const connId = currentConnId || (connectionEntries[0] && connectionEntries[0].connId);
if (!connId || typeof window.BridgeSwarm.request !== 'function') return;
const entry = connectionEntries.find(function (e) { return e.connId === connId; });
try {
btnEnableHrpc.disabled = true;
hrpcStatusEl.style.display = '';
hrpcStatusEl.textContent = 'Enabling HRPC… (wait up to 30s for other tab)';
hrpcStatusEl.className = 'status';
log('Retrying HRPC on connection…', 'hrpc');
const res = await window.BridgeSwarm.request('attachHrpc', { connId });
if (res && res.ok) {
if (entry) entry.hrpcEnabled = true;
hrpcEnabled = true;
hrpcStatusEl.textContent = 'HRPC enabled. You can ping the peer.';
hrpcStatusEl.className = 'status ok';
pingRow.style.display = 'flex';
log('HRPC enabled', 'hrpc');
updatePeers();
} else {
const errMsg = res && res.error ? res.error : 'Failed to enable HRPC';
if (errMsg.indexOf('Connection not found') !== -1) {
const idx = connectionEntries.findIndex(function (e) { return e.connId === connId; });
if (idx !== -1) connectionEntries.splice(idx, 1);
if (currentConnId === connId) currentConnId = connectionEntries[0] ? connectionEntries[0].connId : null;
updatePeers();
hrpcStatusEl.style.display = '';
hrpcStatusEl.textContent = 'Connection not found (removed). Select another or refresh.';
hrpcStatusEl.className = 'status error';
log('attachHrpc: ' + errMsg, 'err');
} else {
hrpcStatusEl.style.display = '';
hrpcStatusEl.textContent = errMsg;
hrpcStatusEl.className = 'status error';
log('attachHrpc failed: ' + errMsg, 'err');
}
}
} catch (err) {
hrpcStatusEl.style.display = '';
hrpcStatusEl.textContent = 'Error: ' + err.message;
hrpcStatusEl.className = 'status error';
log('Error: ' + err.message, 'err');
}
btnEnableHrpc.disabled = !swarm || connectionEntries.length === 0;
});
btnPing.addEventListener('click', async function () {
const hrpcEntry = connectionEntries.find(function (e) { return e.hrpcEnabled && (e.connId === currentConnId || !currentConnId); }) || connectionEntries.find(function (e) { return e.hrpcEnabled; });
const connId = hrpcEntry ? hrpcEntry.connId : (currentConnId || (connectionEntries[0] && connectionEntries[0].connId));
if (!connId || typeof window.BridgeSwarm.request !== 'function') return;
const value = pingValueEl.value.trim();
const args = value ? { value: value } : {};
btnPing.disabled = true;
pongResultEl.style.display = 'none';
log('Ping peer… (wait up to 15s)', 'hrpc');
if (!hrpcEntry) log('No HRPC-enabled connection selected; trying anyway.', 'sys');
const requestPromise = window.BridgeSwarm.request('hrpcInvoke', {
connId: connId,
method: 'ping',
args: args
});
const timeoutMs = 16000;
const timeoutPromise = new Promise(function (_, reject) {
setTimeout(function () {
reject(new Error('Request timed out. Ensure the other tab enabled HRPC and only two tabs are in the topic.'));
}, timeoutMs);
});
try {
const res = await Promise.race([requestPromise, timeoutPromise]);
if (res && res.ok && res.result) {
const pong = res.result.pong != null ? res.result.pong : JSON.stringify(res.result);
pongResultEl.style.display = '';
pongResultEl.textContent = 'Pong: ' + pong;
pongResultEl.className = 'status ok';
log('Pong: ' + pong, 'hrpc');
} else {
const errMsg = res && res.error ? res.error : 'No response';
pongResultEl.style.display = '';
pongResultEl.textContent = errMsg;
pongResultEl.className = 'status error';
log('Ping failed: ' + errMsg, 'err');
if (errMsg.indexOf('Connection not found') !== -1 || errMsg.indexOf('HRPC not attached') !== -1) {
const idx = connectionEntries.findIndex(function (e) { return e.connId === connId; });
if (idx !== -1) connectionEntries.splice(idx, 1);
if (currentConnId === connId) currentConnId = connectionEntries[0] ? connectionEntries[0].connId : null;
updatePeers();
}
}
} catch (err) {
pongResultEl.style.display = '';
pongResultEl.textContent = 'Error: ' + err.message;
pongResultEl.className = 'status error';
log('Error: ' + err.message, 'err');
if (err.message.indexOf('Connection not found') !== -1 || err.message.indexOf('HRPC not attached') !== -1) {
const idx = connectionEntries.findIndex(function (e) { return e.connId === connId; });
if (idx !== -1) connectionEntries.splice(idx, 1);
if (currentConnId === connId) currentConnId = connectionEntries[0] ? connectionEntries[0].connId : null;
updatePeers();
}
}
btnPing.disabled = false;
});
async function hrpcStreamingDemo(method, args) {
const hrpcEntry = connectionEntries.find(function (e) { return e.hrpcEnabled && (e.connId === currentConnId || !currentConnId); }) || connectionEntries.find(function (e) { return e.hrpcEnabled; });
if (!swarm || !hrpcEntry) {
log('Need an HRPC-enabled connection', 'err');
return;
}
try {
const chunks = [];
const res = await swarm.hrpcCall(hrpcEntry.connId, method, args, {
onChunk: function (chunk) {
chunks.push(chunk);
log(method + ' chunk: ' + JSON.stringify(chunk), 'hrpc');
},
timeoutMs: 20000,
});
log(method + ' done: ' + JSON.stringify(res.result != null ? res.result : { chunks: chunks.length }), 'hrpc');
pongResultEl.style.display = '';
pongResultEl.textContent = method + ': ' + JSON.stringify(res.result != null ? res.result : chunks);
pongResultEl.className = 'status ok';
} catch (err) {
log(method + ' error: ' + err.message, 'err');
}
}
if (btnFetchStream) {
btnFetchStream.addEventListener('click', function () {
hrpcStreamingDemo('fetchStream', { count: 3 });
});
}
if (btnStreamSum) {
btnStreamSum.addEventListener('click', function () {
hrpcStreamingDemo('streamSum', { chunks: [{ n: 1, label: 'a' }, { n: 2, label: 'b' }, { n: 3, label: 'c' }] });
});
}
})();