first commit

This commit is contained in:
Raven Scott
2026-02-12 03:27:05 -05:00
commit 90c7a4910e
38 changed files with 7466 additions and 0 deletions
+34
View File
@@ -0,0 +1,34 @@
# Examples
Open these in your browser **after** installing the BridgeSwarm extension and native host.
## Chat (`chat.html`)
A minimal P2P chat: join a topic, see peers, send and receive messages.
1. Open `chat.html` in Chrome (or Edge/Firefox) — e.g. drag the file into the browser or use **File → Open**.
2. Click **Join** (default topic is `bridge-swarm-demo`).
3. Open the same file in another tab (or another window/device) and join the same topic.
4. Type a message and click **Send**; it appears in the other tab.
## SDK Demo (`sdk-demo.html`)
A complete example of all BridgeSwarm SDK features:
1. **Swarm** — Join/leave topic, destroy swarm.
2. **Connections** — Connection events, `peerInfo` (publicKey, topics).
3. **Raw messages**`conn.write(data)` and `conn.on('data')` for raw bytes.
4. **Protomux**`swarm.createProtomux(conn)`, create a channel with protocol `sdk-demo/v1`, add string and binary messages via `compact-encoding` (`c.string`, `c.binary`), `channel.open()`, and `msg.send()`.
Open `sdk-demo.html` in two tabs (or two devices), join the same topic, then try sending raw messages and Protomux messages. The log shows which path each message used.
## Data API Demo (`data-demo.html`)
Uses the native hosts **Hypercore**, **Hyperbee**, **Hyperdrive**, and **Autobase** via `BridgeSwarm.request(type, payload)`.
1. Open `data-demo.html` in the browser (with the extension installed).
2. Use the buttons to get core info, append to the core, put/get/del in Hyperbee, put/get/list/delete files in Hyperdrive, and append/read the Autobase linearized view.
See [../docs/DATA-API.md](../docs/DATA-API.md) for the full command reference.
**Note:** If the page says the extension is not detected: (1) The page waits a few seconds for the extension to inject—try again after a moment. (2) For `file://` URLs, open `chrome://extensions`, find BridgeSwarm, and enable **Allow access to file URLs**. (3) Or run a local server (e.g. `npx serve .` in the `examples` folder) and open `http://localhost:3000/chat.html`.
+216
View File
@@ -0,0 +1,216 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>BridgeSwarm Chat</title>
<style>
* { box-sizing: border-box; }
body {
font-family: system-ui, -apple-system, sans-serif;
max-width: 560px;
margin: 0 auto;
padding: 1rem;
background: #1a1b26;
color: #c0caf5;
min-height: 100vh;
}
h1 { font-size: 1.25rem; margin: 0 0 1rem; color: #7aa2f7; }
.row { display: flex; gap: 0.5rem; margin-bottom: 0.75rem; align-items: center; }
input[type="text"] {
flex: 1;
padding: 0.5rem 0.75rem;
border: 1px solid #3b4261;
border-radius: 6px;
background: #24283b;
color: #c0caf5;
font-size: 0.95rem;
}
input::placeholder { color: #565f89; }
button {
padding: 0.5rem 1rem;
border: none;
border-radius: 6px;
font-size: 0.9rem;
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.danger { background: #f7768e; color: #1a1b26; }
button.danger:hover { background: #ff9db5; }
.status {
font-size: 0.85rem;
color: #9ece6a;
margin-bottom: 0.5rem;
}
.status.error { color: #f7768e; }
.log {
background: #24283b;
border: 1px solid #3b4261;
border-radius: 6px;
padding: 0.75rem;
height: 220px;
overflow-y: auto;
font-family: ui-monospace, monospace;
font-size: 0.8rem;
line-height: 1.4;
margin-top: 1rem;
}
.log .peer { color: #bb9af7; }
.log .msg { color: #9ece6a; }
.log .sys { color: #7aa2f7; }
.log .err { color: #f7768e; }
.peers { font-size: 0.85rem; color: #a9b1d6; margin-bottom: 0.5rem; }
</style>
</head>
<body>
<h1>BridgeSwarm Chat</h1>
<p class="status" id="status">Load the extension, then join a topic. Open this page in another tab or device to chat.</p>
<div class="row">
<input type="text" id="topic" placeholder="Topic (e.g. my-chat-room)" value="bridge-swarm-demo">
<button class="primary" id="btnJoin">Join</button>
<button class="danger" id="btnLeave" disabled>Leave</button>
</div>
<div class="peers" id="peers">Peers: 0</div>
<div class="row">
<input type="text" id="message" placeholder="Type a message..." disabled>
<button class="primary" id="btnSend" disabled>Send</button>
</div>
<div class="log" id="log"></div>
<script>
(function () {
const statusEl = document.getElementById('status');
const topicEl = document.getElementById('topic');
const messageEl = document.getElementById('message');
const btnJoin = document.getElementById('btnJoin');
const btnLeave = document.getElementById('btnLeave');
const btnSend = document.getElementById('btnSend');
const peersEl = document.getElementById('peers');
const logEl = document.getElementById('log');
function setStatus(msg, isError) {
statusEl.textContent = msg;
statusEl.className = 'status' + (isError ? ' error' : '');
}
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;
}
// Extension injects api.js async; wait for it to set window.BridgeSwarm
function waitForExtension(cb) {
if (typeof window.BridgeSwarm !== 'undefined') {
cb();
return;
}
setStatus('Waiting for extension…');
let attempts = 0;
const maxAttempts = 50; // ~5 seconds
const t = setInterval(() => {
attempts++;
if (typeof window.BridgeSwarm !== 'undefined') {
clearInterval(t);
cb();
return;
}
if (attempts >= maxAttempts) {
clearInterval(t);
setStatus('BridgeSwarm extension not detected. Install the extension and reload, or wait a moment and refresh.', true);
}
}, 100);
}
waitForExtension(() => {
setStatus('Load the extension, then join a topic. Open this page in another tab or device to chat.');
});
let swarm = null;
const connections = [];
function updatePeers() {
peersEl.textContent = 'Peers: ' + connections.length;
}
btnJoin.addEventListener('click', async () => {
if (typeof window.BridgeSwarm === 'undefined') {
setStatus('Extension not ready yet. Wait a moment and try again.', true);
return;
}
const topic = topicEl.value.trim() || 'default-topic';
if (swarm) return;
try {
setStatus('Joining topic "' + topic + '"...');
swarm = new window.BridgeSwarm({ appName: 'bridge-swarm-chat' });
swarm.on('connection', (conn, peerInfo) => {
connections.push({ conn, peerInfo });
updatePeers();
log('Peer connected: ' + (peerInfo.publicKey || '').slice(0, 16) + '…', 'peer');
conn.on('data', (data) => {
const text = new TextDecoder().decode(data);
log('Received: ' + text, 'msg');
});
conn.on('end', () => {
const i = connections.findIndex(c => c.conn === conn);
if (i !== -1) connections.splice(i, 1);
updatePeers();
log('Peer left', 'sys');
});
conn.on('error', (err) => log('Peer error: ' + err.message, 'err'));
});
await swarm.join(topic);
setStatus('Joined "' + topic + '". Send a message or open this page in another tab.');
btnJoin.disabled = true;
btnLeave.disabled = false;
messageEl.disabled = false;
btnSend.disabled = false;
log('Joined topic: ' + topic, 'sys');
} catch (err) {
setStatus('Error: ' + err.message, true);
log('Error: ' + err.message, 'err');
}
});
btnLeave.addEventListener('click', async () => {
if (!swarm) return;
try {
const topic = topicEl.value.trim() || 'default-topic';
await swarm.leave(topic);
await swarm.destroy();
connections.length = 0;
updatePeers();
swarm = null;
setStatus('Left topic. You can join again.');
btnJoin.disabled = false;
btnLeave.disabled = true;
messageEl.disabled = true;
btnSend.disabled = true;
log('Left topic', 'sys');
} catch (err) {
log('Error: ' + err.message, 'err');
}
});
function sendMessage() {
const text = messageEl.value.trim();
if (!text || !swarm || connections.length === 0) return;
const data = new TextEncoder().encode(text);
connections.forEach(({ conn }) => {
try { conn.write(data); } catch (e) {}
});
log('Sent: ' + text, 'msg');
messageEl.value = '';
}
btnSend.addEventListener('click', sendMessage);
messageEl.addEventListener('keydown', (e) => { if (e.key === 'Enter') sendMessage(); });
})();
</script>
</body>
</html>
+311
View File
@@ -0,0 +1,311 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>BridgeSwarm Data API 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: 100px;
padding: 0.5rem 0.75rem;
border: 1px solid #3b4261;
border-radius: 6px;
background: #24283b;
color: #c0caf5;
font-size: 0.9rem;
}
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.secondary { background: #3b4261; color: #c0caf5; }
.status { font-size: 0.85rem; margin-bottom: 0.5rem; }
.status.ok { color: #9ece6a; }
.status.error { color: #f7768e; }
.log {
background: #24283b;
border: 1px solid #3b4261;
border-radius: 6px;
padding: 0.6rem;
height: 220px;
overflow-y: auto;
font-family: ui-monospace, monospace;
font-size: 0.75rem;
line-height: 1.35;
margin-top: 0.5rem;
}
.log .sys { color: #7aa2f7; }
.log .err { color: #f7768e; }
.log .val { color: #9ece6a; }
.section { margin-bottom: 1rem; }
</style>
</head>
<body>
<h1>BridgeSwarm Data API Demo</h1>
<p>Uses <code>BridgeSwarm.request(type, payload)</code> to call Hypercore, Hyperbee, Hyperdrive, Autobase, and Hyperdb in the native host.</p>
<div class="section">
<h2>Hypercore</h2>
<div class="row">
<button class="primary" id="coreInfo">Core info</button>
<input type="text" id="coreAppend" placeholder="Data to append (text)">
<button class="secondary" id="coreAppendBtn">Append</button>
</div>
</div>
<div class="section">
<h2>Hyperbee (key/value)</h2>
<div class="row">
<input type="text" id="beeKey" placeholder="Key">
<input type="text" id="beeValue" placeholder="Value">
<button class="primary" id="beePut">Put</button>
<button class="secondary" id="beeGet">Get</button>
<button class="secondary" id="beeDel">Del</button>
</div>
</div>
<div class="section">
<h2>Hyperdrive (files)</h2>
<div class="row">
<input type="text" id="drivePath" placeholder="Path (e.g. /foo.txt)" value="/readme.txt">
<input type="text" id="driveContent" placeholder="Content to write">
<button class="primary" id="drivePut">Put</button>
<button class="secondary" id="driveGet">Get</button>
<button class="secondary" id="driveList">List /</button>
</div>
</div>
<div class="section">
<h2>Autobase (log)</h2>
<div class="row">
<input type="text" id="autobaseValue" placeholder="Value to append">
<button class="primary" id="autobaseAppend">Append</button>
<button class="secondary" id="autobaseInfo">Info</button>
<button class="secondary" id="autobaseView">Read view (0..4)</button>
</div>
</div>
<div class="section">
<h2>Hyperdb (records)</h2>
<div class="row">
<input type="text" id="hyperdbId" placeholder="Id">
<input type="text" id="hyperdbValue" placeholder="Value">
<button class="primary" id="hyperdbPut">Put</button>
<button class="secondary" id="hyperdbGet">Get</button>
<button class="secondary" id="hyperdbDel">Del</button>
<button class="secondary" id="hyperdbFind">Find all</button>
<button class="secondary" id="hyperdbFlush">Flush</button>
</div>
</div>
<div class="section">
<h2>Log</h2>
<div class="log" id="log"></div>
</div>
<script>
(function () {
const logEl = document.getElementById('log');
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 waitForBridge(cb) {
if (typeof window.BridgeSwarm !== 'undefined' && typeof window.BridgeSwarm.request === 'function') {
cb();
return;
}
let n = 0;
const t = setInterval(function () {
n++;
if (typeof window.BridgeSwarm !== 'undefined' && typeof window.BridgeSwarm.request === 'function') {
clearInterval(t);
cb();
} else if (n >= 50) {
clearInterval(t);
log('BridgeSwarm.request not available. Install extension and reload.', 'err');
}
}, 100);
}
function request(type, payload) {
return window.BridgeSwarm.request(type, payload || {}).then(function (r) {
if (!r.ok) throw new Error(r.error || 'Request failed');
return r;
});
}
waitForBridge(function () {
log('Data API ready. Use the buttons above.');
document.getElementById('coreInfo').onclick = async function () {
try {
const r = await request('coreInfo');
log('Core: key=' + (r.key || '').slice(0, 16) + '… length=' + r.length + ' writable=' + r.writable, 'val');
} catch (e) { log(e.message, 'err'); }
};
document.getElementById('coreAppendBtn').onclick = async function () {
const data = document.getElementById('coreAppend').value;
if (!data) return;
try {
const base64 = btoa(unescape(encodeURIComponent(data)));
await request('coreAppend', { base64 });
log('Appended ' + data.length + ' bytes', 'val');
} catch (e) { log(e.message, 'err'); }
};
document.getElementById('beePut').onclick = async function () {
const key = document.getElementById('beeKey').value;
const value = document.getElementById('beeValue').value;
if (!key) return;
try {
await request('beePut', { key, value });
log('bee.put("' + key + '", "' + value + '")', 'val');
} catch (e) { log(e.message, 'err'); }
};
document.getElementById('beeGet').onclick = async function () {
const key = document.getElementById('beeKey').value;
if (!key) return;
try {
const r = await request('beeGet', { key });
log('bee.get("' + key + '") => ' + (r.value === null ? 'null' : JSON.stringify(r.value)), 'val');
if (r.value !== null) document.getElementById('beeValue').value = r.value;
} catch (e) { log(e.message, 'err'); }
};
document.getElementById('beeDel').onclick = async function () {
const key = document.getElementById('beeKey').value;
if (!key) return;
try {
await request('beeDel', { key });
log('bee.del("' + key + '")', 'val');
} catch (e) { log(e.message, 'err'); }
};
document.getElementById('drivePut').onclick = async function () {
const path = document.getElementById('drivePath').value || '/file.txt';
const content = document.getElementById('driveContent').value;
try {
const base64 = btoa(unescape(encodeURIComponent(content || '')));
await request('drivePut', { path, base64 });
log('drive.put("' + path + '")', 'val');
} catch (e) { log(e.message, 'err'); }
};
document.getElementById('driveGet').onclick = async function () {
const path = document.getElementById('drivePath').value || '/';
try {
const r = await request('driveGet', { path });
const text = r.data ? decodeURIComponent(escape(atob(r.data))) : '(empty/null)';
log('drive.get("' + path + '") => ' + text.slice(0, 80) + (text.length > 80 ? '…' : ''), 'val');
document.getElementById('driveContent').value = r.data ? decodeURIComponent(escape(atob(r.data))) : '';
} catch (e) { log(e.message, 'err'); }
};
document.getElementById('driveList').onclick = async function () {
try {
const r = await request('driveList', { path: '/' });
const keys = (r.entries || []).map(function (e) { return e.key; });
log('drive.list("/") => ' + keys.join(', ') || '(none)', 'val');
} catch (e) { log(e.message, 'err'); }
};
document.getElementById('autobaseAppend').onclick = async function () {
const value = document.getElementById('autobaseValue').value;
try {
await request('autobaseAppend', { value: value || '(empty)' });
log('autobase.append("' + (value || '(empty)') + '")', 'val');
} catch (e) { log(e.message, 'err'); }
};
document.getElementById('autobaseInfo').onclick = async function () {
try {
const r = await request('autobaseInfo');
log('Autobase length=' + r.length + ' signedLength=' + r.signedLength, 'val');
} catch (e) { log(e.message, 'err'); }
};
document.getElementById('autobaseView').onclick = async function () {
try {
const info = await request('autobaseInfo');
const len = Math.min(info.length, 5);
for (let i = 0; i < len; i++) {
const r = await request('autobaseViewGet', { index: i });
const text = r.data ? decodeURIComponent(escape(atob(r.data))) : null;
log('view[' + i + '] = ' + (text || 'null'), 'val');
}
if (info.length === 0) log('view is empty', 'val');
} catch (e) { log(e.message, 'err'); }
};
const collection = 'records';
document.getElementById('hyperdbPut').onclick = async function () {
const id = document.getElementById('hyperdbId').value;
const value = document.getElementById('hyperdbValue').value;
if (!id) return;
try {
await request('hyperdbInsert', { collection, doc: { id, value: value || '' } });
log('hyperdb.insert(records, { id: "' + id + '", value: "' + (value || '') + '" })', 'val');
} catch (e) { log(e.message, 'err'); }
};
document.getElementById('hyperdbGet').onclick = async function () {
const id = document.getElementById('hyperdbId').value;
if (!id) return;
try {
const r = await request('hyperdbGet', { collection, query: { id } });
log('hyperdb.get(records, { id: "' + id + '" }) => ' + (r.doc === null ? 'null' : JSON.stringify(r.doc)), 'val');
if (r.doc) document.getElementById('hyperdbValue').value = r.doc.value || '';
} catch (e) { log(e.message, 'err'); }
};
document.getElementById('hyperdbDel').onclick = async function () {
const id = document.getElementById('hyperdbId').value;
if (!id) return;
try {
await request('hyperdbDelete', { collection, query: { id } });
log('hyperdb.delete(records, { id: "' + id + '" })', 'val');
} catch (e) { log(e.message, 'err'); }
};
document.getElementById('hyperdbFind').onclick = async function () {
try {
const r = await request('hyperdbFindToArray', { collectionOrIndex: collection });
const docs = r.docs || [];
log('hyperdb.find(records) => ' + docs.length + ' doc(s) ' + JSON.stringify(docs), 'val');
} catch (e) { log(e.message, 'err'); }
};
document.getElementById('hyperdbFlush').onclick = async function () {
try {
await request('hyperdbFlush');
log('hyperdb.flush()', 'val');
} catch (e) { log(e.message, 'err'); }
};
});
})();
</script>
</body>
</html>
+320
View File
@@ -0,0 +1,320 @@
<!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>