Files
BridgeSwarm/examples/sdk-demo.html
T
2026-02-12 03:27:05 -05:00

321 lines
12 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>BridgeSwarm SDK Demo</title>
<style>
* { box-sizing: border-box; }
body {
font-family: system-ui, -apple-system, sans-serif;
max-width: 640px;
margin: 0 auto;
padding: 1rem;
background: #1a1b26;
color: #c0caf5;
min-height: 100vh;
}
h1 { font-size: 1.25rem; margin: 0 0 0.5rem; color: #7aa2f7; }
h2 { font-size: 0.95rem; margin: 1rem 0 0.5rem; color: #bb9af7; font-weight: 600; }
p { margin: 0 0 0.5rem; font-size: 0.9rem; color: #a9b1d6; }
.row { display: flex; gap: 0.5rem; margin-bottom: 0.5rem; align-items: center; flex-wrap: wrap; }
input[type="text"] {
flex: 1;
min-width: 120px;
padding: 0.5rem 0.75rem;
border: 1px solid #3b4261;
border-radius: 6px;
background: #24283b;
color: #c0caf5;
font-size: 0.9rem;
}
input::placeholder { color: #565f89; }
button {
padding: 0.5rem 0.75rem;
border: none;
border-radius: 6px;
font-size: 0.85rem;
cursor: pointer;
font-weight: 500;
}
button.primary { background: #7aa2f7; color: #1a1b26; }
button.primary:hover { background: #89b4fa; }
button.primary:disabled { opacity: 0.5; cursor: not-allowed; }
button.secondary { background: #3b4261; color: #c0caf5; }
button.secondary:hover { background: #414868; }
button.danger { background: #f7768e; color: #1a1b26; }
button.danger:hover { background: #ff9db5; }
.status { font-size: 0.85rem; margin-bottom: 0.5rem; }
.status.ok { color: #9ece6a; }
.status.error { color: #f7768e; }
.status.warn { color: #e0af68; }
.log {
background: #24283b;
border: 1px solid #3b4261;
border-radius: 6px;
padding: 0.6rem;
height: 200px;
overflow-y: auto;
font-family: ui-monospace, monospace;
font-size: 0.75rem;
line-height: 1.35;
margin-top: 0.5rem;
}
.log .peer { color: #bb9af7; }
.log .raw { color: #9ece6a; }
.log .protomux { color: #7dcfff; }
.log .sys { color: #7aa2f7; }
.log .err { color: #f7768e; }
.section { margin-bottom: 1rem; }
.badge { font-size: 0.7rem; padding: 0.15rem 0.4rem; border-radius: 4px; background: #3b4261; color: #a9b1d6; }
</style>
</head>
<body>
<h1>BridgeSwarm SDK Demo</h1>
<p>This page demonstrates all SDK features: swarm, connections, raw messages, and Protomux. Open in two tabs (or two devices) and join the same topic.</p>
<div class="section">
<h2>1. Swarm <span class="badge">join / leave / destroy</span></h2>
<p id="status" class="status">Waiting for extension…</p>
<div class="row">
<input type="text" id="topic" placeholder="Topic (32 bytes or string)" value="bridge-swarm-sdk-demo">
<button class="primary" id="btnJoin">Join topic</button>
<button class="danger" id="btnLeave" disabled>Leave & destroy</button>
</div>
</div>
<div class="section">
<h2>2. Connections <span class="badge">peerInfo, connection events</span></h2>
<p id="peers">Peers: 0</p>
<p class="status ok" id="connStatus" style="display:none;">On connection you get (conn, peerInfo). peerInfo: publicKey (hex), topics (hex[]).</p>
</div>
<div class="section">
<h2>3. Raw messages <span class="badge">conn.write / on('data')</span></h2>
<div class="row">
<input type="text" id="rawMsg" placeholder="Send raw bytes (text)…" disabled>
<button class="secondary" id="btnRawSend" disabled>Send raw</button>
</div>
</div>
<div class="section">
<h2>4. Protomux <span class="badge">createProtomux, channel, c.string / c.binary</span></h2>
<div class="row">
<input type="text" id="protoMsg" placeholder="Send via Protomux (string)…" disabled>
<button class="secondary" id="btnProtoSend" disabled>Send Protomux</button>
</div>
<p class="status ok" id="protoStatus" style="display:none;">Channel protocol: sdk-demo/v1. Binary messages supported too.</p>
</div>
<div class="section">
<h2>Log</h2>
<div class="log" id="log"></div>
</div>
<script>
(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 connStatusEl = document.getElementById('connStatus');
const rawMsgEl = document.getElementById('rawMsg');
const btnRawSend = document.getElementById('btnRawSend');
const protoMsgEl = document.getElementById('protoMsg');
const btnProtoSend = document.getElementById('btnProtoSend');
const protoStatusEl = document.getElementById('protoStatus');
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 waitForExtension(cb) {
if (typeof window.BridgeSwarm !== 'undefined') {
cb();
return;
}
setStatus('Waiting for extension…', 'warn');
let attempts = 0;
const t = setInterval(function () {
attempts++;
if (typeof window.BridgeSwarm !== 'undefined') {
clearInterval(t);
cb();
return;
}
if (attempts >= 50) {
clearInterval(t);
setStatus('BridgeSwarm extension not detected. Install it and reload.', 'error');
}
}, 100);
}
waitForExtension(function () {
setStatus('Ready. Join a topic to start.');
});
let swarm = null;
const connectionEntries = [];
function updatePeers() {
peersEl.textContent = 'Peers: ' + connectionEntries.length;
if (connectionEntries.length > 0) connStatusEl.style.display = '';
}
function enableConnected() {
rawMsgEl.disabled = false;
btnRawSend.disabled = false;
protoMsgEl.disabled = false;
btnProtoSend.disabled = false;
}
function disableConnected() {
rawMsgEl.disabled = true;
btnRawSend.disabled = true;
protoMsgEl.disabled = true;
btnProtoSend.disabled = true;
}
btnJoin.addEventListener('click', async function () {
if (typeof window.BridgeSwarm === 'undefined') {
setStatus('Extension not ready.', 'error');
return;
}
const topic = topicEl.value.trim() || 'sdk-demo';
if (swarm) return;
try {
setStatus('Joining "' + topic + '"…');
swarm = new window.BridgeSwarm({ appName: 'bridge-swarm-sdk-demo' });
swarm.on('connection', function (conn, peerInfo) {
const keyShort = (peerInfo.publicKey || '').slice(0, 16) + '…';
log('Connection: peer ' + keyShort + ', topics: ' + (peerInfo.topics ? peerInfo.topics.length : 0), 'peer');
conn.on('end', function () {
const i = connectionEntries.findIndex(function (e) { return e.conn === conn; });
if (i !== -1) connectionEntries.splice(i, 1);
updatePeers();
if (connectionEntries.length === 0) disableConnected();
log('Peer left', 'sys');
});
conn.on('error', function (err) { log('Peer error: ' + err.message, 'err'); });
// Raw messages
conn.on('data', function (data) {
const text = new TextDecoder().decode(data);
log('Raw received: ' + text, 'raw');
});
const entry = { conn, peerInfo, mux: null, channel: null };
connectionEntries.push(entry);
updatePeers();
enableConnected();
// Protomux: createProtomux(conn), createChannel, addMessage (c.string, c.binary), open(), msg.send()
const mux = swarm.createProtomux(conn);
if (mux && window.BridgeSwarmProtomux) {
entry.mux = mux;
const c = window.BridgeSwarmProtomux.c;
const ch = mux.createChannel({
protocol: 'sdk-demo/v1',
onopen: function () { log('Protomux channel opened', 'protomux'); protoStatusEl.style.display = ''; },
onclose: function () { log('Protomux channel closed', 'protomux'); }
});
entry.stringMsg = ch.addMessage({
encoding: c.string,
onmessage: function (m) { log('Protomux (string): ' + m, 'protomux'); }
});
ch.addMessage({
encoding: c.binary,
onmessage: function (buf) {
const str = new TextDecoder().decode(buf);
log('Protomux (binary): ' + str, 'protomux');
}
});
ch.open();
entry.channel = ch;
} else {
log('Protomux not available (ensure protomux-bundle.js is loaded)', 'err');
}
});
await swarm.join(topic);
setStatus('Joined "' + topic + '". Connect another tab to try raw and Protomux messages.');
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() || 'sdk-demo';
await swarm.leave(topic);
await swarm.destroy();
connectionEntries.length = 0;
updatePeers();
disableConnected();
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');
}
});
function sendRaw() {
const text = rawMsgEl.value.trim();
if (!text || !swarm || connectionEntries.length === 0) return;
const data = new TextEncoder().encode(text);
connectionEntries.forEach(function (e) {
try { e.conn.write(data); } catch (_) {}
});
log('Sent raw: ' + text, 'raw');
rawMsgEl.value = '';
}
function sendProtomux() {
const text = protoMsgEl.value.trim();
if (!text || connectionEntries.length === 0) return;
let sent = 0;
connectionEntries.forEach(function (e) {
if (e.stringMsg) {
try {
e.stringMsg.send(text);
sent++;
} catch (_) {}
}
});
if (sent > 0) {
log('Sent via Protomux: ' + text, 'protomux');
protoMsgEl.value = '';
} else {
log('No Protomux channel ready', 'err');
}
}
btnRawSend.addEventListener('click', sendRaw);
rawMsgEl.addEventListener('keydown', function (e) { if (e.key === 'Enter') sendRaw(); });
btnProtoSend.addEventListener('click', sendProtomux);
protoMsgEl.addEventListener('keydown', function (e) { if (e.key === 'Enter') sendProtomux(); });
})();
</script>
</body>
</html>