@@ -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
@@ -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 directory’s **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 host’s **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 extension’s **Options** and ensure “Do not inject on file:// URLs” is *off*, then in `chrome://extensions` (or the browser’s 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://`.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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,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
|
||||
|
||||
|
||||
@@ -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,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
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,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
|
||||
|
||||
|
||||
@@ -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,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
|
||||
|
||||
|
||||
@@ -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,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
|
||||
|
||||
|
||||
@@ -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
@@ -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}`);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 (_) {}
|
||||
}
|
||||
@@ -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
@@ -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 ────────────────────────────────────────────────────────
|
||||
|
||||
Executable
+122
@@ -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, () => {});
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user