321 lines
12 KiB
HTML
321 lines
12 KiB
HTML
<!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>
|