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) 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 ### Your First P2P App
```javascript ```javascript
@@ -313,6 +321,7 @@ Run `npm run build:hrpc` to generate the HRPC spec.
### Build Commands ### Build Commands
```bash ```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:protomux # Rebuild Protomux bundle
npm run build:hrpc # Rebuild HRPC spec (+ mirror into native-host/spec) npm run build:hrpc # Rebuild HRPC spec (+ mirror into native-host/spec)
npm run build # host launcher + protomux + hrpc 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. 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/`) ## 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**. 1. Open http://127.0.0.1:4173/chat/
2. Click **Join** (default topic is `bridge-swarm-demo`). 2. Click **Join** (default topic `bridge-swarm-demo`).
3. Open the same file in another tab (or another window/device) and join the same topic. 3. Open the same URL in another tab and join.
4. Type a message and click **Send**; it appears in the other tab. 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/`) ## SDK Demo (`sdk-demo/`)
A complete example of all BridgeSwarm SDK features: Swarm lifecycle, raw messages, and Protomux. Open http://127.0.0.1:4173/sdk-demo/ in two tabs.
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.
## HRPC Demo (`hrpc-demo/`) ## 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/`) ## 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/`) ## 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. WebRTC media + BridgeSwarm signaling. Open http://127.0.0.1:4173/screenshare/ in two tabs.
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.
## Data API Demo (`data-demo/`) ## 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). ## Troubleshooting
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. 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 ## How to Run
1. Load the BridgeSwarm extension in your browser 1. Load the BridgeSwarm extension and native host.
2. Open `index.html` in your browser (File → Open or serve locally) 2. From the repo root: `npm run examples`
3. Enter your nickname and click "Join" 3. Open **http://127.0.0.1:4173/chat-advanced/** in two tabs.
4. Open the same page in another tab or browser to start chatting
Do **not** open via `file://` — browsers treat each local file as a unique security origin.
## Usage ## Usage
@@ -58,18 +59,3 @@ A feature-rich P2P chat application built on BridgeSwarm with real-time messagin
- Type in the message box - Type in the message box
- Press Enter or click Send - Press Enter or click Send
- Use **bold**, *italic*, and `code` formatting - 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> <title>BridgeSwarm Advanced Chat</title>
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/styles/atom-one-dark.min.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> </head>
<body> <body>
<div class="app-container"> <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 ## 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 ## 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 ## Usage
@@ -28,13 +36,7 @@ Open `index.html` in your browser (File → Open or drag the file in). For `file
## APIs and concepts ## APIs and concepts
- `BridgeSwarm.ready()` — Wait for the injected API. - `BridgeSwarm.ready()` — Wait for the injected API.
- `new BridgeSwarm({ appName })` — Create a swarm (app name `bridge-swarm-chat`). - `new BridgeSwarm({ appName })` — Create a swarm.
- `swarm.join(topic)` — Join the topic. - `swarm.join(topic)` / `swarm.leave(topic)` — Topic discovery.
- `swarm.on('connection', (conn, peerInfo))` — Handle new peers. - `swarm.on('connection', …)` — Peer connections.
- `conn.on('data', fn)` — Receive messages; decode with `TextDecoder`. - `conn.write(data)` / `conn.on('data', )` — Raw messages.
- `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.
+13
View File
@@ -5,6 +5,19 @@
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>BridgeSwarm Chat</title> <title>BridgeSwarm Chat</title>
<link rel="stylesheet" href="style.css"> <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> </head>
<body> <body>
<h1>BridgeSwarm Chat</h1> <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 ## 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 ## 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 ## Usage
+13
View File
@@ -5,6 +5,19 @@
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>BridgeSwarm Data API Demo</title> <title>BridgeSwarm Data API Demo</title>
<link rel="stylesheet" href="style.css"> <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> </head>
<body> <body>
<h1>BridgeSwarm Data API Demo</h1> <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 ## 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 ## 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 ## Usage
+13
View File
@@ -5,6 +5,19 @@
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>BridgeSwarm HRPC Demo</title> <title>BridgeSwarm HRPC Demo</title>
<link rel="stylesheet" href="style.css"> <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> </head>
<body> <body>
<h1>BridgeSwarm HRPC Demo</h1> <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 ## 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 ## 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 ## Usage
+13
View File
@@ -5,6 +5,19 @@
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>BridgeSwarm Screenshare</title> <title>BridgeSwarm Screenshare</title>
<link rel="stylesheet" href="style.css"> <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> </head>
<body> <body>
<h1>BridgeSwarm Screenshare</h1> <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 ## 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 ## 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 ## Usage
+13
View File
@@ -5,6 +5,19 @@
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>BridgeSwarm SDK Demo</title> <title>BridgeSwarm SDK Demo</title>
<link rel="stylesheet" href="style.css"> <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> </head>
<body> <body>
<h1>BridgeSwarm SDK Demo</h1> <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 ## 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 ## 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 ## Usage
+13
View File
@@ -5,6 +5,19 @@
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>BridgeSwarm Whiteboard</title> <title>BridgeSwarm Whiteboard</title>
<link rel="stylesheet" href="style.css"> <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> </head>
<body> <body>
<h1>BridgeSwarm Whiteboard</h1> <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 * available as a global before any other module runs (required for bare-pack
* standalone binaries). * 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. * 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 * IMPORTANT: Never write non-framed data to stdout — Chrome native messaging
* Bare runtime extracts them; the installer then ad-hoc signs those files. * owns stdout. All logs go to stderr (and optional log file).
*/ */
import 'bare-process/global'; import 'bare-process/global';
import './set-tmpdir.mjs';
import _messenger from './messenger.js'; import _messenger from './messenger.js';
import _host from './host.js'; import _host from './host.js';
@@ -21,6 +24,19 @@ function logErr(msg) {
} catch (_) {} } 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')) { if (process.argv.includes('--extract-addons')) {
(async () => { (async () => {
const addonPackages = [ const addonPackages = [
@@ -49,13 +65,8 @@ if (process.argv.includes('--extract-addons')) {
const input = process.stdin; const input = process.stdin;
const output = process.stdout; const output = process.stdout;
// On Windows, native messaging requires binary I/O (no CRLF translation). // Do NOT setEncoding('binary') — that makes 'data' emit strings and breaks
if (input.setRawMode) { // length-prefixed Buffer framing with Chrome's native messaging pipes.
input.setEncoding?.('binary');
}
if (output.setDefaultEncoding) {
output.setDefaultEncoding?.('binary');
}
const messenger = createMessenger({ const messenger = createMessenger({
input, input,
@@ -75,15 +86,27 @@ if (process.argv.includes('--extract-addons')) {
}); });
function shutdown() { function shutdown() {
try {
cleanup(); cleanup();
} catch (_) {}
try {
messenger.destroy(); messenger.destroy();
} catch (_) {}
process.exit(0); process.exit(0);
} }
process.on('exit', () => cleanup()); process.on('exit', () => {
try {
cleanup();
} catch (_) {}
});
process.on('SIGTERM', shutdown); process.on('SIGTERM', shutdown);
process.on('SIGINT', shutdown); process.on('SIGINT', shutdown);
// Chrome closes stdin when the port disconnects — exit cleanly.
input.on('end', shutdown);
input.on('close', shutdown);
logErr('ready'); logErr('ready');
} catch (err) { } catch (err) {
logErr(`startup error: ${err.message}`); logErr(`startup error: ${err.message}`);
+20 -8
View File
@@ -22,12 +22,25 @@ function createMessenger({ input, output, onMessage, onError }) {
function fail(err) { function fail(err) {
if (onError) onError(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) { function processChunk(chunk) {
if (!chunk || chunk.length === 0) return; const buf = toBuffer(chunk);
buffer = Buffer.concat([buffer, chunk]); if (!buf || buf.length === 0) return;
buffer = Buffer.concat([buffer, buf]);
while (buffer.length >= needed) { while (buffer.length >= needed) {
const slice = buffer.subarray(0, needed); const slice = buffer.subarray(0, needed);
@@ -61,7 +74,7 @@ function createMessenger({ input, output, onMessage, onError }) {
input.on('data', processChunk); input.on('data', processChunk);
input.on('error', fail); input.on('error', fail);
input.resume?.(); if (typeof input.resume === 'function') input.resume();
return { return {
/** /**
@@ -71,11 +84,10 @@ function createMessenger({ input, output, onMessage, onError }) {
send(msg) { send(msg) {
try { try {
const json = JSON.stringify(msg); const json = JSON.stringify(msg);
const buf = Buffer.from(json, 'utf8'); const body = Buffer.from(json, 'utf8');
const len = Buffer.allocUnsafe(4); const len = Buffer.allocUnsafe(4);
len.writeUInt32LE(buf.length, 0); len.writeUInt32LE(body.length, 0);
output.write(len); output.write(Buffer.concat([len, body]));
output.write(buf);
} catch (e) { } catch (e) {
fail(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:hrpc": "node scripts/build-hrpc.js",
"build": "npm run build:host && npm run build:protomux && npm run build:hrpc", "build": "npm run build:host && npm run build:protomux && npm run build:hrpc",
"pack": "node scripts/pack-extension.js", "pack": "node scripts/pack-extension.js",
"examples": "node scripts/serve-examples.js",
"build:dist": "node scripts/build-distributable.js", "build:dist": "node scripts/build-distributable.js",
"build:dist:all": "node scripts/build-distributable.js --all", "build:dist:all": "node scripts/build-distributable.js --all",
"build:dist:mac": "node scripts/build-distributable.js --host darwin-arm64 --host darwin-x64", "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" 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 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" 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" 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" chmod +x "$LAUNCHER"
echo " Extracting native addons (--extract-addons)..." 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 sleep 2
SIGNED=0 SIGNED=0
@@ -127,16 +129,19 @@ if [[ "$PLATFORM" == "darwin" ]]; then
done < <(find "$ADDON_TMPDIR" \( -name "*.bare" -o -name "*.dylib" \) -print0 2>/dev/null) done < <(find "$ADDON_TMPDIR" \( -name "*.bare" -o -name "*.dylib" \) -print0 2>/dev/null)
fi fi
echo " Signed ${SIGNED} native addons; main binary has library-validation disabled" 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 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")" HOST_DIR="$(dirname "$HOST_BIN")"
LAUNCHER="${HOST_DIR}/run-bridge-swarm-host.sh" 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" 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" chmod +x "$LAUNCHER"
HOST_BIN="$LAUNCHER"
fi 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" echo " Binary: $HOST_BIN"
# ── Extension downloads ──────────────────────────────────────────────────────── # ── 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, () => {});
}
});