first commit
This commit is contained in:
@@ -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 host’s **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`.
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user