(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 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; } (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(function () { setStatus('Ready. Join a topic, then open another tab and join the same topic.'); }).catch(function () { setStatus('BridgeSwarm extension not detected.', 'error'); }); })(0); connSelect.addEventListener('change', function () { currentConnId = connSelect.value || null; }); let swarm = null; let currentConnId = null; let hrpcEnabled = false; const connectionEntries = []; 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'); }); } 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 { setStatus('Joining "' + topic + '"…'); swarm = new window.BridgeSwarm({ appName: 'bridge-swarm-hrpc-demo' }); 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); 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'); } 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; 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; }); })();