fixes
CI / Build & Test (push) Successful in 3m0s

This commit is contained in:
Raven Scott
2026-07-26 22:19:31 -04:00
parent ad41323e27
commit 8c95a97662
23 changed files with 557 additions and 93 deletions
+9
View File
@@ -82,6 +82,14 @@ npm run pack # extension zip + xpi
npm run build:dist:package # all-platform host zips (needs bare-build; best on CI)
```
### Try the examples
```bash
npm run examples
```
Opens **http://127.0.0.1:4173/** (do not use `file://` — browsers treat each local file as a unique origin). Pick a demo and open it in two tabs.
### Your First P2P App
```javascript
@@ -313,6 +321,7 @@ Run `npm run build:hrpc` to generate the HRPC spec.
### Build Commands
```bash
npm run examples # Serve demos at http://127.0.0.1:4173/ (not file://)
npm run build:protomux # Rebuild Protomux bundle
npm run build:hrpc # Rebuild HRPC spec (+ mirror into native-host/spec)
npm run build # host launcher + protomux + hrpc
+43 -28
View File
@@ -2,53 +2,68 @@
Open these in your browser **after** installing the BridgeSwarm extension and native host.
Each example lives in its own directory with separated concerns: **index.html** (structure), **style.css** (styles), **app.js** (logic). Open the directorys **index.html** (e.g. `chat/index.html` or run a local server and go to `http://localhost:3000/chat/`). Each subdirectory has its own **README.md** with detailed usage, APIs, and file roles.
**Do not open examples via `file://`.** Modern Chrome/Firefox treat each local file as a unique opaque origin, which breaks scripts, styles, and extension injection (you may see: *Unsafe attempt to load URL file://… 'file:' URLs are treated as unique security origins*).
## Run the examples server
From the BridgeSwarm repo root:
```bash
npm run examples
```
This serves `examples/` at **http://127.0.0.1:4173/** and opens the landing page. Use two browser tabs for P2P demos.
| Demo | URL |
|------|-----|
| Landing | http://127.0.0.1:4173/ |
| Chat | http://127.0.0.1:4173/chat/ |
| Advanced Chat | http://127.0.0.1:4173/chat-advanced/ |
| SDK Demo | http://127.0.0.1:4173/sdk-demo/ |
| HRPC Demo | http://127.0.0.1:4173/hrpc-demo/ |
| Whiteboard | http://127.0.0.1:4173/whiteboard/ |
| Screenshare | http://127.0.0.1:4173/screenshare/ |
| Data API | http://127.0.0.1:4173/data-demo/ |
Each subdirectory has its own **README.md** with APIs and file roles. Layout per demo: **index.html**, **style.css**, **app.js**.
## Chat (`chat/`)
A minimal P2P chat: join a topic, see peers, send and receive messages.
Minimal P2P chat: join a topic, see peers, send and receive messages.
1. Open `chat/index.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.
1. Open http://127.0.0.1:4173/chat/
2. Click **Join** (default topic `bridge-swarm-demo`).
3. Open the same URL in another tab and join.
4. Send a message; it appears in the other tab.
## Advanced Chat (`chat-advanced/`)
Rooms, presence, markdown, emoji, and file sharing. Open http://127.0.0.1:4173/chat-advanced/ in two tabs.
## SDK Demo (`sdk-demo/`)
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/index.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.
Swarm lifecycle, raw messages, and Protomux. Open http://127.0.0.1:4173/sdk-demo/ in two tabs.
## HRPC Demo (`hrpc-demo/`)
Demonstrates **HRPC** on a connection: HRPC is **auto-enabled** on each connection. Open the page in two tabs, join the same topic; when a peer connects, the host enables HRPC and either tab can use “Ping peer” to get a pong. No manual enable step. See [../docs/HRPC.md](../docs/HRPC.md).
HRPC ping/streams over connections. Open http://127.0.0.1:4173/hrpc-demo/ in two tabs. See [../docs/HRPC.md](../docs/HRPC.md).
## Whiteboard (`whiteboard/`)
Collaborative P2P whiteboard: join a topic, draw on the canvas, and strokes sync to all peers in real time. Open `whiteboard/index.html` in two tabs (or two devices), join the same topic, then draw; strokes and “Clear board” are broadcast to everyone. Uses raw `conn.write()` / `conn.on('data')` with JSON stroke and clear messages.
Collaborative drawing. Open http://127.0.0.1:4173/whiteboard/ in two tabs.
## Screenshare (`screenshare/`)
P2P screen sharing using **WebRTC** for the media stream and **BridgeSwarm for signaling** (no separate signaling server). SDP offers/answers and ICE candidates are exchanged over the BridgeSwarm connection.
1. Open `screenshare/index.html` in two tabs (or two windows).
2. **Tab 1 (sharer):** Click **Join**, then **Share my screen** and pick a screen or window to share.
3. **Tab 2 (viewer):** Join the same topic; the shared screen appears in the video area.
Signaling uses BridgeSwarm; the actual video flows via the browser's WebRTC (RTCPeerConnection). No public STUN or TURN is used — fully enclosed P2P; ICE uses only host and local-network candidates. The sharer can start sharing before any viewer joins; viewers see the stream when they join the topic.
WebRTC media + BridgeSwarm signaling. Open http://127.0.0.1:4173/screenshare/ in two tabs.
## Data API Demo (`data-demo/`)
Uses the native hosts **Hypercore**, **Hyperbee**, **Hyperdrive**, and **Autobase** via `BridgeSwarm.request(type, payload)`.
Hypercore / Hyperbee / Hyperdrive / Autobase / Hyperdb via `BridgeSwarm.request`. Open http://127.0.0.1:4173/data-demo/. See [../docs/DATA-API.md](../docs/DATA-API.md).
1. Open `data-demo/index.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.
## Troubleshooting
See [../docs/DATA-API.md](../docs/DATA-API.md) for the full command reference.
If the page says the extension is not detected:
**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 the extensions **Options** and ensure “Do not inject on file:// URLs” is *off*, then in `chrome://extensions` (or the browsers extension page) 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/`.
1. Wait a moment for content-script injection, then retry.
2. Confirm the BridgeSwarm extension and native host are installed.
3. Confirm you are on `http://127.0.0.1:4173/…`, not `file://`.
+5 -19
View File
@@ -42,10 +42,11 @@ A feature-rich P2P chat application built on BridgeSwarm with real-time messagin
## How to Run
1. Load the BridgeSwarm extension in your browser
2. Open `index.html` in your browser (File → Open or serve locally)
3. Enter your nickname and click "Join"
4. Open the same page in another tab or browser to start chatting
1. Load the BridgeSwarm extension and native host.
2. From the repo root: `npm run examples`
3. Open **http://127.0.0.1:4173/chat-advanced/** in two tabs.
Do **not** open via `file://` — browsers treat each local file as a unique security origin.
## Usage
@@ -58,18 +59,3 @@ A feature-rich P2P chat application built on BridgeSwarm with real-time messagin
- Type in the message box
- Press Enter or click Send
- Use **bold**, *italic*, and `code` formatting
### Rooms
- Click "+" next to Rooms to create a new room
- Click a room to switch to it
### Files
- Click the 📎 button to share a file
## Technical Details
- Uses BridgeSwarm API for P2P networking
- Each peer maintains connections to other peers
- Messages are broadcast to all connected peers
- Presence is shared via periodic broadcasts
- Unique public key identifies each user/tab
+13
View File
@@ -6,6 +6,19 @@
<title>BridgeSwarm Advanced Chat</title>
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/styles/atom-one-dark.min.css">
<script>
if (location.protocol === 'file:') {
document.documentElement.innerHTML =
'<body style="margin:0;font:15px/1.5 Segoe UI,system-ui,sans-serif;background:#0f1419;color:#e7ecf3">' +
'<div style="max-width:36rem;margin:10vh auto;padding:24px;background:#1a2332;border:1px solid #2a3548;border-radius:14px">' +
'<h1 style="margin:0 0 10px;font-size:1.25rem">Open this demo over HTTP</h1>' +
'<p style="color:#8b9bb4">Browsers treat each <code>file://</code> URL as a unique security origin, so local demos break.</p>' +
'<p>From the BridgeSwarm repo root run:</p>' +
'<pre style="padding:12px;background:#0f1419;border-radius:10px;color:#2dd4bf">npm run examples</pre>' +
'<p style="color:#8b9bb4">Then open <code>http://127.0.0.1:4173/</code></p>' +
'</div></body>';
}
</script>
</head>
<body>
<div class="app-container">
+14 -12
View File
@@ -6,11 +6,19 @@ Minimal P2P chat: join a topic, see peer count, and send and receive text messag
## Prerequisites
Install the [BridgeSwarm extension and native host](../../README.md#install-one-command) before opening this example.
Install the [BridgeSwarm extension and native host](../../README.md#easy-install-recommended) before opening this example.
## How to run
Open `index.html` in your browser (File → Open or drag the file in). For `file://` URLs, ensure the extension has **Allow access to file URLs** enabled, or run a local server (e.g. `npx serve .` from the `examples` folder) and open `http://localhost:3000/chat/`.
From the repo root:
```bash
npm run examples
```
Then open **http://127.0.0.1:4173/chat/** (use two tabs for P2P).
Do **not** open `index.html` via `file://` — browsers treat each local file as a unique origin and the demo will fail.
## Usage
@@ -28,13 +36,7 @@ Open `index.html` in your browser (File → Open or drag the file in). For `file
## APIs and concepts
- `BridgeSwarm.ready()` — Wait for the injected API.
- `new BridgeSwarm({ appName })` — Create a swarm (app name `bridge-swarm-chat`).
- `swarm.join(topic)` — Join the topic.
- `swarm.on('connection', (conn, peerInfo))` — Handle new peers.
- `conn.on('data', fn)` — Receive messages; decode with `TextDecoder`.
- `conn.write(data)` — Send bytes (e.g. `TextEncoder().encode(text)`).
- `swarm.leave(topic)`, `swarm.destroy()` — Leave and tear down.
## See also
- [API reference](../../docs/API-REFERENCE.md) — Page API and host request types.
- `new BridgeSwarm({ appName })` — Create a swarm.
- `swarm.join(topic)` / `swarm.leave(topic)` — Topic discovery.
- `swarm.on('connection', …)` — Peer connections.
- `conn.write(data)` / `conn.on('data', )` — Raw messages.
+13
View File
@@ -5,6 +5,19 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>BridgeSwarm Chat</title>
<link rel="stylesheet" href="style.css">
<script>
if (location.protocol === 'file:') {
document.documentElement.innerHTML =
'<body style="margin:0;font:15px/1.5 Segoe UI,system-ui,sans-serif;background:#0f1419;color:#e7ecf3">' +
'<div style="max-width:36rem;margin:10vh auto;padding:24px;background:#1a2332;border:1px solid #2a3548;border-radius:14px">' +
'<h1 style="margin:0 0 10px;font-size:1.25rem">Open this demo over HTTP</h1>' +
'<p style="color:#8b9bb4">Browsers treat each <code>file://</code> URL as a unique security origin, so local demos break.</p>' +
'<p>From the BridgeSwarm repo root run:</p>' +
'<pre style="padding:12px;background:#0f1419;border-radius:10px;color:#2dd4bf">npm run examples</pre>' +
'<p style="color:#8b9bb4">Then open <code>http://127.0.0.1:4173/</code></p>' +
'</div></body>';
}
</script>
</head>
<body>
<h1>BridgeSwarm Chat</h1>
+6 -2
View File
@@ -6,11 +6,15 @@ Uses the native host Data API via `BridgeSwarm.request(type, payload)`: Hypercor
## Prerequisites
Install the [BridgeSwarm extension and native host](../../README.md#install-one-command) before opening this example.
Install the [BridgeSwarm extension and native host](../../README.md#easy-install-recommended) before opening this example.
## How to run
Open `index.html` in your browser (File → Open or drag the file in). For `file://` URLs, ensure the extension has **Allow access to file URLs** enabled, or run a local server (e.g. `npx serve .` from the `examples` folder) and open `http://localhost:3000/data-demo/`.
```bash
npm run examples
```
Open **http://127.0.0.1:4173/data-demo/**. Do not use `file://`.
## Usage
+13
View File
@@ -5,6 +5,19 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>BridgeSwarm Data API Demo</title>
<link rel="stylesheet" href="style.css">
<script>
if (location.protocol === 'file:') {
document.documentElement.innerHTML =
'<body style="margin:0;font:15px/1.5 Segoe UI,system-ui,sans-serif;background:#0f1419;color:#e7ecf3">' +
'<div style="max-width:36rem;margin:10vh auto;padding:24px;background:#1a2332;border:1px solid #2a3548;border-radius:14px">' +
'<h1 style="margin:0 0 10px;font-size:1.25rem">Open this demo over HTTP</h1>' +
'<p style="color:#8b9bb4">Browsers treat each <code>file://</code> URL as a unique security origin, so local demos break.</p>' +
'<p>From the BridgeSwarm repo root run:</p>' +
'<pre style="padding:12px;background:#0f1419;border-radius:10px;color:#2dd4bf">npm run examples</pre>' +
'<p style="color:#8b9bb4">Then open <code>http://127.0.0.1:4173/</code></p>' +
'</div></body>';
}
</script>
</head>
<body>
<h1>BridgeSwarm Data API Demo</h1>
+6 -2
View File
@@ -6,11 +6,15 @@ HRPC on a connection: HRPC is auto-enabled when a peer connects. Either tab can
## Prerequisites
Install the [BridgeSwarm extension and native host](../../README.md#install-one-command) before opening this example.
Install the [BridgeSwarm extension and native host](../../README.md#easy-install-recommended) before opening this example.
## How to run
Open `index.html` in your browser (File → Open or drag the file in). For file:// URLs, ensure the extension has **Allow access to file URLs** enabled, or run a local server (e.g. `npx serve .` from the `examples` folder) and open `http://localhost:3000/hrpc-demo/`.
```bash
npm run examples
```
Open **http://127.0.0.1:4173/hrpc-demo/** (two tabs for P2P). Do not use `file://`.
## Usage
+13
View File
@@ -5,6 +5,19 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>BridgeSwarm HRPC Demo</title>
<link rel="stylesheet" href="style.css">
<script>
if (location.protocol === 'file:') {
document.documentElement.innerHTML =
'<body style="margin:0;font:15px/1.5 Segoe UI,system-ui,sans-serif;background:#0f1419;color:#e7ecf3">' +
'<div style="max-width:36rem;margin:10vh auto;padding:24px;background:#1a2332;border:1px solid #2a3548;border-radius:14px">' +
'<h1 style="margin:0 0 10px;font-size:1.25rem">Open this demo over HTTP</h1>' +
'<p style="color:#8b9bb4">Browsers treat each <code>file://</code> URL as a unique security origin, so local demos break.</p>' +
'<p>From the BridgeSwarm repo root run:</p>' +
'<pre style="padding:12px;background:#0f1419;border-radius:10px;color:#2dd4bf">npm run examples</pre>' +
'<p style="color:#8b9bb4">Then open <code>http://127.0.0.1:4173/</code></p>' +
'</div></body>';
}
</script>
</head>
<body>
<h1>BridgeSwarm HRPC Demo</h1>
+160
View File
@@ -0,0 +1,160 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BridgeSwarm Examples</title>
<style>
:root {
--bg: #0f1419;
--panel: #1a2332;
--text: #e7ecf3;
--muted: #8b9bb4;
--accent: #3d8bfd;
--accent-2: #2dd4bf;
--border: #2a3548;
}
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
font-family: "Segoe UI", system-ui, sans-serif;
background:
radial-gradient(1200px 600px at 10% -10%, rgba(61, 139, 253, 0.18), transparent 55%),
radial-gradient(900px 500px at 90% 0%, rgba(45, 212, 191, 0.12), transparent 50%),
var(--bg);
color: var(--text);
line-height: 1.5;
}
main {
max-width: 880px;
margin: 0 auto;
padding: 48px 24px 80px;
}
h1 {
margin: 0 0 8px;
font-size: 2rem;
letter-spacing: -0.03em;
}
.lede {
color: var(--muted);
margin: 0 0 28px;
max-width: 54ch;
}
.warn {
border: 1px solid rgba(251, 191, 36, 0.35);
background: rgba(251, 191, 36, 0.08);
color: #fde68a;
padding: 12px 14px;
border-radius: 10px;
margin-bottom: 28px;
font-size: 0.95rem;
}
.warn code { color: #fff7c2; }
.grid {
display: grid;
gap: 14px;
}
a.card {
display: block;
text-decoration: none;
color: inherit;
background: var(--panel);
border: 1px solid var(--border);
border-radius: 14px;
padding: 18px 18px 16px;
transition: border-color 0.15s ease, transform 0.15s ease;
}
a.card:hover {
border-color: rgba(61, 139, 253, 0.7);
transform: translateY(-1px);
}
a.card h2 {
margin: 0 0 6px;
font-size: 1.15rem;
}
a.card p {
margin: 0;
color: var(--muted);
font-size: 0.95rem;
}
.path {
display: inline-block;
margin-top: 10px;
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 0.8rem;
color: var(--accent-2);
}
footer {
margin-top: 36px;
color: var(--muted);
font-size: 0.9rem;
}
footer code {
color: #cbd5e1;
}
</style>
</head>
<body>
<main>
<h1>BridgeSwarm Examples</h1>
<p class="lede">
Interactive demos for Hyperswarm in the browser. Install the BridgeSwarm extension
and native host first, then open any demo below in two tabs to try P2P.
</p>
<div class="warn" id="fileWarn" hidden>
You opened this page via <code>file://</code>. Modern browsers treat each local file as a
unique security origin, so demos break. Run <code>npm run examples</code> from the repo
and use <code>http://127.0.0.1:4173/</code> instead.
</div>
<div class="grid">
<a class="card" href="./chat/">
<h2>Chat</h2>
<p>Minimal P2P chat over a topic — join, see peers, send messages.</p>
<span class="path">/chat/</span>
</a>
<a class="card" href="./chat-advanced/">
<h2>Advanced Chat</h2>
<p>Rooms, presence, markdown, emoji, and file sharing.</p>
<span class="path">/chat-advanced/</span>
</a>
<a class="card" href="./sdk-demo/">
<h2>SDK Demo</h2>
<p>Swarm lifecycle, raw messages, and Protomux channels.</p>
<span class="path">/sdk-demo/</span>
</a>
<a class="card" href="./hrpc-demo/">
<h2>HRPC Demo</h2>
<p>Typed RPC (ping / streams) over Hyperswarm connections.</p>
<span class="path">/hrpc-demo/</span>
</a>
<a class="card" href="./whiteboard/">
<h2>Whiteboard</h2>
<p>Collaborative drawing synced peer-to-peer in real time.</p>
<span class="path">/whiteboard/</span>
</a>
<a class="card" href="./screenshare/">
<h2>Screenshare</h2>
<p>WebRTC media with BridgeSwarm used only for signaling.</p>
<span class="path">/screenshare/</span>
</a>
<a class="card" href="./data-demo/">
<h2>Data API Demo</h2>
<p>Hypercore, Hyperbee, Hyperdrive, Autobase, and Hyperdb from the page.</p>
<span class="path">/data-demo/</span>
</a>
</div>
<footer>
Start the server with <code>npm run examples</code> from the BridgeSwarm repo root.
</footer>
</main>
<script>
if (location.protocol === 'file:') {
document.getElementById('fileWarn').hidden = false;
}
</script>
</body>
</html>
+6 -2
View File
@@ -6,11 +6,15 @@ P2P screen sharing: BridgeSwarm is used only for **signaling** (SDP offer/answer
## Prerequisites
Install the [BridgeSwarm extension and native host](../../README.md#install-one-command) before opening this example.
Install the [BridgeSwarm extension and native host](../../README.md#easy-install-recommended) before opening this example.
## How to run
Open `index.html` in your browser (File → Open or drag the file in). For `file://` URLs, ensure the extension has **Allow access to file URLs** enabled, or run a local server (e.g. `npx serve .` from the `examples` folder) and open `http://localhost:3000/screenshare/`.
```bash
npm run examples
```
Open **http://127.0.0.1:4173/screenshare/** (two tabs for P2P). Do not use `file://`.
## Usage
+13
View File
@@ -5,6 +5,19 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>BridgeSwarm Screenshare</title>
<link rel="stylesheet" href="style.css">
<script>
if (location.protocol === 'file:') {
document.documentElement.innerHTML =
'<body style="margin:0;font:15px/1.5 Segoe UI,system-ui,sans-serif;background:#0f1419;color:#e7ecf3">' +
'<div style="max-width:36rem;margin:10vh auto;padding:24px;background:#1a2332;border:1px solid #2a3548;border-radius:14px">' +
'<h1 style="margin:0 0 10px;font-size:1.25rem">Open this demo over HTTP</h1>' +
'<p style="color:#8b9bb4">Browsers treat each <code>file://</code> URL as a unique security origin, so local demos break.</p>' +
'<p>From the BridgeSwarm repo root run:</p>' +
'<pre style="padding:12px;background:#0f1419;border-radius:10px;color:#2dd4bf">npm run examples</pre>' +
'<p style="color:#8b9bb4">Then open <code>http://127.0.0.1:4173/</code></p>' +
'</div></body>';
}
</script>
</head>
<body>
<h1>BridgeSwarm Screenshare</h1>
+6 -2
View File
@@ -6,11 +6,15 @@ Demonstrates all core BridgeSwarm SDK features in one page: swarm lifecycle, con
## Prerequisites
Install the [BridgeSwarm extension and native host](../../README.md#install-one-command) before opening this example.
Install the [BridgeSwarm extension and native host](../../README.md#easy-install-recommended) before opening this example.
## How to run
Open `index.html` in your browser (File → Open or drag the file in). For file:// URLs, ensure the extension has **Allow access to file URLs** enabled, or run a local server (e.g. `npx serve .` from the `examples` folder) and open `http://localhost:3000/sdk-demo/`.
```bash
npm run examples
```
Open **http://127.0.0.1:4173/sdk-demo/** (two tabs for P2P). Do not use `file://`.
## Usage
+13
View File
@@ -5,6 +5,19 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>BridgeSwarm SDK Demo</title>
<link rel="stylesheet" href="style.css">
<script>
if (location.protocol === 'file:') {
document.documentElement.innerHTML =
'<body style="margin:0;font:15px/1.5 Segoe UI,system-ui,sans-serif;background:#0f1419;color:#e7ecf3">' +
'<div style="max-width:36rem;margin:10vh auto;padding:24px;background:#1a2332;border:1px solid #2a3548;border-radius:14px">' +
'<h1 style="margin:0 0 10px;font-size:1.25rem">Open this demo over HTTP</h1>' +
'<p style="color:#8b9bb4">Browsers treat each <code>file://</code> URL as a unique security origin, so local demos break.</p>' +
'<p>From the BridgeSwarm repo root run:</p>' +
'<pre style="padding:12px;background:#0f1419;border-radius:10px;color:#2dd4bf">npm run examples</pre>' +
'<p style="color:#8b9bb4">Then open <code>http://127.0.0.1:4173/</code></p>' +
'</div></body>';
}
</script>
</head>
<body>
<h1>BridgeSwarm SDK Demo</h1>
+6 -2
View File
@@ -6,11 +6,15 @@ Collaborative P2P whiteboard: join a topic, draw on a canvas, and strokes and
## Prerequisites
Install the [BridgeSwarm extension and native host](../../README.md#install-one-command) before opening this example.
Install the [BridgeSwarm extension and native host](../../README.md#easy-install-recommended) before opening this example.
## How to run
Open `index.html` in your browser (File → Open or drag the file in). For `file://` URLs, ensure the extension has **Allow access to file URLs** enabled, or run a local server (e.g. `npx serve .` from the `examples` folder) and open `http://localhost:3000/whiteboard/`.
```bash
npm run examples
```
Open **http://127.0.0.1:4173/whiteboard/** (two tabs for P2P). Do not use `file://`.
## Usage
+13
View File
@@ -5,6 +5,19 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>BridgeSwarm Whiteboard</title>
<link rel="stylesheet" href="style.css">
<script>
if (location.protocol === 'file:') {
document.documentElement.innerHTML =
'<body style="margin:0;font:15px/1.5 Segoe UI,system-ui,sans-serif;background:#0f1419;color:#e7ecf3">' +
'<div style="max-width:36rem;margin:10vh auto;padding:24px;background:#1a2332;border:1px solid #2a3548;border-radius:14px">' +
'<h1 style="margin:0 0 10px;font-size:1.25rem">Open this demo over HTTP</h1>' +
'<p style="color:#8b9bb4">Browsers treat each <code>file://</code> URL as a unique security origin, so local demos break.</p>' +
'<p>From the BridgeSwarm repo root run:</p>' +
'<pre style="padding:12px;background:#0f1419;border-radius:10px;color:#2dd4bf">npm run examples</pre>' +
'<p style="color:#8b9bb4">Then open <code>http://127.0.0.1:4173/</code></p>' +
'</div></body>';
}
</script>
</head>
<body>
<h1>BridgeSwarm Whiteboard</h1>
+33 -10
View File
@@ -5,13 +5,16 @@
* available as a global before any other module runs (required for bare-pack
* standalone binaries).
*
* set-tmpdir must run before loading native addons (macOS codesign).
*
* All local imports are static so bare-pack can pre-resolve the full module graph.
*
* macOS install: when run with --extract-addons, loads native addons so the
* Bare runtime extracts them; the installer then ad-hoc signs those files.
* IMPORTANT: Never write non-framed data to stdout — Chrome native messaging
* owns stdout. All logs go to stderr (and optional log file).
*/
import 'bare-process/global';
import './set-tmpdir.mjs';
import _messenger from './messenger.js';
import _host from './host.js';
@@ -21,6 +24,19 @@ function logErr(msg) {
} catch (_) {}
}
// Keep stdout clean for the native messaging framing protocol.
const _stderrWrite = (...args) => {
try {
const line = args.map((a) => (typeof a === 'string' ? a : String(a))).join(' ') + '\n';
process.stderr.write(line);
} catch (_) {}
};
console.log = _stderrWrite;
console.info = _stderrWrite;
console.warn = _stderrWrite;
console.error = _stderrWrite;
console.debug = _stderrWrite;
if (process.argv.includes('--extract-addons')) {
(async () => {
const addonPackages = [
@@ -49,13 +65,8 @@ if (process.argv.includes('--extract-addons')) {
const input = process.stdin;
const output = process.stdout;
// On Windows, native messaging requires binary I/O (no CRLF translation).
if (input.setRawMode) {
input.setEncoding?.('binary');
}
if (output.setDefaultEncoding) {
output.setDefaultEncoding?.('binary');
}
// Do NOT setEncoding('binary') — that makes 'data' emit strings and breaks
// length-prefixed Buffer framing with Chrome's native messaging pipes.
const messenger = createMessenger({
input,
@@ -75,15 +86,27 @@ if (process.argv.includes('--extract-addons')) {
});
function shutdown() {
try {
cleanup();
} catch (_) {}
try {
messenger.destroy();
} catch (_) {}
process.exit(0);
}
process.on('exit', () => cleanup());
process.on('exit', () => {
try {
cleanup();
} catch (_) {}
});
process.on('SIGTERM', shutdown);
process.on('SIGINT', shutdown);
// Chrome closes stdin when the port disconnects — exit cleanly.
input.on('end', shutdown);
input.on('close', shutdown);
logErr('ready');
} catch (err) {
logErr(`startup error: ${err.message}`);
+20 -8
View File
@@ -22,12 +22,25 @@ function createMessenger({ input, output, onMessage, onError }) {
function fail(err) {
if (onError) onError(err);
else console.error(err);
else {
try {
process.stderr.write(String(err && err.stack ? err.stack : err) + '\n');
} catch (_) {}
}
}
function toBuffer(chunk) {
if (!chunk) return null;
if (Buffer.isBuffer(chunk)) return chunk;
if (chunk instanceof Uint8Array) return Buffer.from(chunk);
if (typeof chunk === 'string') return Buffer.from(chunk, 'latin1');
return Buffer.from(chunk);
}
function processChunk(chunk) {
if (!chunk || chunk.length === 0) return;
buffer = Buffer.concat([buffer, chunk]);
const buf = toBuffer(chunk);
if (!buf || buf.length === 0) return;
buffer = Buffer.concat([buffer, buf]);
while (buffer.length >= needed) {
const slice = buffer.subarray(0, needed);
@@ -61,7 +74,7 @@ function createMessenger({ input, output, onMessage, onError }) {
input.on('data', processChunk);
input.on('error', fail);
input.resume?.();
if (typeof input.resume === 'function') input.resume();
return {
/**
@@ -71,11 +84,10 @@ function createMessenger({ input, output, onMessage, onError }) {
send(msg) {
try {
const json = JSON.stringify(msg);
const buf = Buffer.from(json, 'utf8');
const body = Buffer.from(json, 'utf8');
const len = Buffer.allocUnsafe(4);
len.writeUInt32LE(buf.length, 0);
output.write(len);
output.write(buf);
len.writeUInt32LE(body.length, 0);
output.write(Buffer.concat([len, body]));
} catch (e) {
fail(e);
}
+18
View File
@@ -0,0 +1,18 @@
/**
* Pin macOS TMPDIR (signed addons) and default Corestore path next to the
* executable. Chrome often launches native hosts with cwd=/ and no env.
* Must run before any module that loads native addons / opens storage.
*/
import path from 'bare-path';
if (typeof process.execPath === 'string') {
try {
const dir = path.dirname(process.execPath);
if (process.platform === 'darwin') {
process.env.TMPDIR = path.join(dir, 'tmp');
}
if (!process.env.BRIDGE_SWARM_STORAGE) {
process.env.BRIDGE_SWARM_STORAGE = path.join(dir, 'bridge-swarm-storage');
}
} catch (_) {}
}
+1
View File
@@ -11,6 +11,7 @@
"build:hrpc": "node scripts/build-hrpc.js",
"build": "npm run build:host && npm run build:protomux && npm run build:hrpc",
"pack": "node scripts/pack-extension.js",
"examples": "node scripts/serve-examples.js",
"build:dist": "node scripts/build-distributable.js",
"build:dist:all": "node scripts/build-distributable.js --all",
"build:dist:mac": "node scripts/build-distributable.js --host darwin-arm64 --host darwin-x64",
+9 -4
View File
@@ -111,12 +111,14 @@ if [[ "$PLATFORM" == "darwin" ]]; then
printf '%s\n' '<?xml version="1.0" encoding="UTF-8"?>' '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">' '<plist version="1.0"><dict><key>com.apple.security.cs.disable-library-validation</key><true/></dict></plist>' > "$ENTITLEMENTS_PLIST"
codesign --force --sign - --entitlements "$ENTITLEMENTS_PLIST" "$HOST_BIN" 2>/dev/null || true
# Optional helper launcher (manual runs). Native messaging uses the Mach-O
# binary directly — set-tmpdir.mjs inside the binary pins TMPDIR for signed addons.
LAUNCHER="${HOST_DIR}/run-bridge-swarm-host.sh"
printf '%s\n' '#!/bin/bash' 'DIR="$(cd "$(dirname "$0")" && pwd)"' 'export TMPDIR="${DIR}/tmp"' 'export BRIDGE_SWARM_STORAGE="${BRIDGE_SWARM_STORAGE:-${DIR}/bridge-swarm-storage}"' 'exec "${DIR}/bridge-swarm-host" "$@"' > "$LAUNCHER"
chmod +x "$LAUNCHER"
echo " Extracting native addons (--extract-addons)..."
"$LAUNCHER" --extract-addons 2>/dev/null || true
TMPDIR="$ADDON_TMPDIR" "$HOST_BIN" --extract-addons 2>/dev/null || true
sleep 2
SIGNED=0
@@ -127,16 +129,19 @@ if [[ "$PLATFORM" == "darwin" ]]; then
done < <(find "$ADDON_TMPDIR" \( -name "*.bare" -o -name "*.dylib" \) -print0 2>/dev/null)
fi
echo " Signed ${SIGNED} native addons; main binary has library-validation disabled"
HOST_BIN="$LAUNCHER"
# Keep HOST_BIN as the Mach-O binary for Chrome native messaging
else
# Linux launcher sets storage path next to the binary
# Linux: binary path is fine; set-tmpdir is macOS-only
HOST_DIR="$(dirname "$HOST_BIN")"
LAUNCHER="${HOST_DIR}/run-bridge-swarm-host.sh"
printf '%s\n' '#!/bin/bash' 'DIR="$(cd "$(dirname "$0")" && pwd)"' 'export BRIDGE_SWARM_STORAGE="${BRIDGE_SWARM_STORAGE:-${DIR}/bridge-swarm-storage}"' 'exec "${DIR}/bridge-swarm-host" "$@"' > "$LAUNCHER"
chmod +x "$LAUNCHER"
HOST_BIN="$LAUNCHER"
fi
# Default storage next to install when launched by the browser
# (bare host also honors BRIDGE_SWARM_STORAGE if set by a wrapper)
export BRIDGE_SWARM_STORAGE="${BRIDGE_SWARM_STORAGE:-${INSTALL_DIR}/bridge-swarm-storage}"
echo " Binary: $HOST_BIN"
# ── Extension downloads ────────────────────────────────────────────────────────
+122
View File
@@ -0,0 +1,122 @@
#!/usr/bin/env node
/**
* Serve BridgeSwarm examples over http://localhost so they share one origin.
* Modern Chrome/Firefox treat each file:// URL as a unique opaque origin, which
* breaks local HTML demos (scripts, styles, extension injection).
*
* Usage: npm run examples
* node scripts/serve-examples.js [port]
*/
'use strict';
const http = require('http');
const fs = require('fs');
const path = require('path');
const { exec } = require('child_process');
const ROOT = path.resolve(__dirname, '..', 'examples');
const PORT = Number(process.argv[2]) || Number(process.env.PORT) || 4173;
const HOST = process.env.HOST || '127.0.0.1';
const MIME = {
'.html': 'text/html; charset=utf-8',
'.js': 'text/javascript; charset=utf-8',
'.mjs': 'text/javascript; charset=utf-8',
'.css': 'text/css; charset=utf-8',
'.json': 'application/json; charset=utf-8',
'.svg': 'image/svg+xml',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.gif': 'image/gif',
'.webp': 'image/webp',
'.ico': 'image/x-icon',
'.woff': 'font/woff',
'.woff2': 'font/woff2',
'.map': 'application/json',
'.txt': 'text/plain; charset=utf-8',
'.md': 'text/markdown; charset=utf-8',
};
function safeJoin(root, urlPath) {
const decoded = decodeURIComponent((urlPath || '/').split('?')[0]);
const cleaned = path.normalize(decoded).replace(/^(\.\.[/\\])+/, '');
const full = path.join(root, cleaned);
if (!full.startsWith(root)) return null;
return full;
}
function send(res, status, body, headers = {}) {
res.writeHead(status, {
'Cache-Control': 'no-store',
...headers,
});
res.end(body);
}
function contentType(filePath) {
return MIME[path.extname(filePath).toLowerCase()] || 'application/octet-stream';
}
const server = http.createServer((req, res) => {
let urlPath = req.url || '/';
if (urlPath === '/') urlPath = '/index.html';
const filePath = safeJoin(ROOT, urlPath);
if (!filePath) {
send(res, 403, 'Forbidden');
return;
}
fs.stat(filePath, (err, st) => {
if (!err && st.isDirectory()) {
const indexPath = path.join(filePath, 'index.html');
fs.readFile(indexPath, (err2, data) => {
if (err2) {
send(res, 404, 'Not found');
return;
}
send(res, 200, data, { 'Content-Type': 'text/html; charset=utf-8' });
});
return;
}
fs.readFile(filePath, (err2, data) => {
if (err2) {
send(res, 404, 'Not found');
return;
}
send(res, 200, data, { 'Content-Type': contentType(filePath) });
});
});
});
server.listen(PORT, HOST, () => {
const base = `http://${HOST}:${PORT}`;
console.log('');
console.log('BridgeSwarm examples');
console.log('====================');
console.log(`Serving ${ROOT}`);
console.log(`Open: ${base}/`);
console.log('');
console.log('Demos:');
console.log(` ${base}/chat/`);
console.log(` ${base}/chat-advanced/`);
console.log(` ${base}/sdk-demo/`);
console.log(` ${base}/hrpc-demo/`);
console.log(` ${base}/whiteboard/`);
console.log(` ${base}/screenshare/`);
console.log(` ${base}/data-demo/`);
console.log('');
console.log('Do not open examples via file:// — Chrome treats each file as a unique origin.');
console.log('Press Ctrl+C to stop.');
console.log('');
const openUrl = `${base}/`;
if (process.env.BRIDGESWARM_NO_OPEN !== '1') {
const platform = process.platform;
const cmd =
platform === 'darwin' ? `open "${openUrl}"` : platform === 'win32' ? `start "" "${openUrl}"` : `xdg-open "${openUrl}"`;
exec(cmd, () => {});
}
});