Updates
CI / Build & Test (push) Successful in 3m12s

This commit is contained in:
Raven Scott
2026-07-26 22:58:33 -04:00
parent 90b6033382
commit f4569b765e
21 changed files with 2152 additions and 220 deletions
+25 -6
View File
@@ -8,7 +8,15 @@ These are the main APIs your page uses. For host request types and event payload
`await BridgeSwarm.ready()` or `BridgeSwarm.ready().then(swarm => { ... })`.
**Default ready timeout:** If the extension option “Default ready timeout (ms)” is set to a value > 0, `ready()` will reject after that many milliseconds with a message like `"BridgeSwarm not available within N ms"` when the API has not appeared (e.g. extension not installed or injection failed). Use 0 in options to wait indefinitely (default).
- **`new BridgeSwarm(options)`** — Constructor options are passed to Hyperswarm on the host. Supported: **`appName`** (string), **`maxPeers`** (number, cap on peer connections per swarm). If the page omits `appName` or `maxPeers`, the extension **defaults** from the options page are used (default app name, default max peers). Default max peers 0 means no limit.
- **`new BridgeSwarm(options)`** — Constructor options are passed to Hyperswarm on the host. Supported: **`appName`** (string), **`maxPeers`** (number, cap on peer connections per swarm). If the page omits `appName` or `maxPeers`, the extension **defaults** from the options page are used (default app name, default max peers). Default max peers 0 means no limit. Firewall is **not** a JS function in options — use `swarm.setFirewall(...)` after construct.
- **`swarm.setFirewall({ mode, keys })`** — `mode`: `off` | `allowlist` | `denylist`; `keys` are public-key hex strings.
- **`swarm.ban(publicKeyHex, banned?)`** / **`peerInfo.ban(banned?)`** — Ban or unban a peer (also updates denylist).
- **`swarm.setAutoReplicate({ enabled, coreKeyHex?, resourceId? })`** — Opt-in auto Hypercore replication on new connections (host takes over those sockets).
- **`swarm.hrpcCall(connId, method, args?, { onChunk, timeoutMs })`** — Invoke host HRPC (`ping`, `notify`, `fetchStream`, `streamSum`, `duplex`).
- **`swarm.connections()`** — Returns an array of current connection objects (snapshot). Useful for broadcast; connection objects may later emit `end`. After `swarm.destroy()`, returns `[]`.
@@ -43,11 +51,17 @@ Used by the BridgeSwarm class. All payloads include `swarmId` (chosen by the cli
| Type | Payload | Response |
|------|---------|----------|
| `init` | `{ swarmId, options? }` — options passed to Hyperswarm (e.g. `appName`, `maxPeers`; extension defaults used when page omits them) | `{ ok }` |
| `init` | `{ swarmId, options? }` — options passed to Hyperswarm (`appName`, `maxPeers`; firewall is host-managed via `setFirewall`) | `{ ok }` |
| `join` | `{ swarmId, topic }` — topic as 32-byte hex string; optional `opts` | `{ ok }` or `{ ok: false, error }` |
| `leave` | `{ swarmId, topic }` — topic as 32-byte hex string | `{ ok }` |
| `swarmKey` | `{ swarmId }` | `{ ok, publicKey }` (hex) — used by `swarm.getPublicKey()` |
| `setFirewall` | `{ swarmId, mode, keys }``mode`: `off` \| `allowlist` \| `denylist` | `{ ok, mode, keys }` |
| `banPeer` | `{ swarmId, publicKeyHex, banned? }` | `{ ok, banned, publicKeyHex }` |
| `setAutoReplicate` | `{ swarmId, enabled, coreKeyHex?, resourceId? }` | `{ ok, enabled }` |
| `destroy` | `{ swarmId }` | `{ ok }` |
Page helpers: `swarm.setFirewall(...)`, `swarm.ban(publicKey)`, `swarm.setAutoReplicate(...)`, `peerInfo.ban()` on connection events.
---
## Connection
@@ -58,10 +72,13 @@ Connections are created when the hosts Hyperswarm emits a connection; the hos
| Event | Payload |
|-------|---------|
| `connection` | `{ connId, swarmId, peerInfo }` — peerInfo: `{ publicKey (hex), topics? (hex[]) }` |
| `connection` | `{ connId, swarmId, peerInfo, autoReplicated? }` — peerInfo: `{ publicKey (hex), topics? (hex[]) }`; page API adds `peerInfo.ban()` |
| `data` | `{ connId, swarmId, data }` — data is base64-encoded bytes |
| `end` | `{ connId, swarmId }` |
| `error` | `{ connId, swarmId, message }` |
| `hrpc-chunk` | `{ connId, swarmId, streamId, method, chunk }` |
| `hrpc-end` | `{ connId, swarmId, streamId, method, result? }` |
| `hrpc-error` | `{ connId, swarmId, streamId, method, message }` |
### Requests
@@ -78,11 +95,13 @@ After a connection is established, the browser can ask the host to “take over
| Type | Payload | Response |
|------|---------|----------|
| `attachReplication` | `{ connId, coreKeyHex? }` — if coreKeyHex omitted, host uses default core | `{ ok }` or `{ ok: false, error }` |
| `attachReplication` | `{ connId, coreKeyHex?, resourceId? }` | `{ ok }` or `{ ok: false, error }` |
| `attachHrpc` | `{ connId }` | `{ ok }` or `{ ok: false, error }` — returns immediately; channel pairs when both sides have enabled; first RPC may wait for channel (up to 12s). |
| `hrpcInvoke` | `{ connId, method, args? }` — only `method: 'ping'` supported from browser (unary) | `{ ok, result }` (e.g. `result: { pong }`) or `{ ok: false, error }` |
| `hrpcInvoke` | `{ connId, method, args?, streamId? }``ping`, `notify`, `fetchStream`, `streamSum`, `duplex` | Unary `{ ok, result }` or streaming `{ ok, streamId, streaming: true }` + events |
See [DATA-API.md](DATA-API.md) and [HRPC.md](HRPC.md) for details.
Prefer `swarm.hrpcCall(connId, method, args, { onChunk })` for streaming. See [HRPC.md](HRPC.md).
In-page [protomux-rpc](https://www.npmjs.com/package/protomux-rpc) remains optional DIY on top of `createProtomux`; BridgeSwarms supported RPC path is **host HRPC**.
---
+54 -81
View File
@@ -1,125 +1,98 @@
# Native Host Data API (Hypercore, Hyperbee, Hyperdrive, Autobase, Hyperdb)
The native host runs **Corestore**, **Hypercore**, **Hyperbee**, **Hyperdrive**, **Autobase**, and **Hyperdb**. The browser can call into these via `BridgeSwarm.request(type, payload [, options])`, which sends a JSON command to the host and returns the response. Optional `options.timeoutMs` aborts after that many milliseconds; if omitted, the extensions **default request timeout** (set in the extension options page) is used when it is > 0. For connection APIs (e.g. `conn.write(data)` returning a Promise), see [API-REFERENCE.md](API-REFERENCE.md).
The native host runs **Corestore**, **Hypercore**, **Hyperbee**, **Hyperdrive**, **Autobase**, and **Hyperdb**. The browser can call into these via `BridgeSwarm.request(type, payload [, options])`. Optional `options.timeoutMs` aborts after that many milliseconds.
Storage is under `BRIDGE_SWARM_STORAGE` or `./bridge-swarm-storage` (relative to the host process). All commands use default instances (one core, one bee, one drive, one autobase) unless noted.
Storage is under `BRIDGE_SWARM_STORAGE` or `./bridge-swarm-storage`. The host keeps **one Corestore** and default instances; you can also open **named** resources (namespaces / keys) and pass `resourceId` on subsequent ops.
## Usage
```javascript
// From any page with the BridgeSwarm extension loaded
const res = await BridgeSwarm.request('beeGet', { key: 'foo' });
if (res.ok) console.log(res.value);
else console.error(res.error);
```
## Named resources
| Type | Payload | Response |
|------|---------|----------|
| `coreOpen` | `{ name? }` or `{ keyHex }` or `{ namespace, name }` | `{ ok, resourceId, key, length, writable }` |
| `beeOpen` | `{ name?, namespace? }` | `{ ok, resourceId, key }` |
| `driveOpen` | `{ name?, namespace? }` | `{ ok, resourceId, key }` |
| `autobaseOpen` | `{ name?, namespace?, viewName? }` | `{ ok, resourceId, length }` |
| `hyperdbOpen` | `{ name?, namespace? }` | `{ ok, resourceId, key }` |
Pass `resourceId` on data commands to target that resource instead of the default.
## Commands
### Hypercore
| Type | Payload | Response |
|------|---------|----------|
| `coreInfo` | | `{ ok, key (hex), length, writable }` |
| `coreAppend` | `{ data }` or `{ base64 }` (base64 string) | `{ ok, length }` |
| `coreGet` | `{ index }` (number) | `{ ok, data }` (base64 block or null) |
| `coreInfo` | `{ resourceId? }` | `{ ok, key (hex), length, writable }` |
| `coreAppend` | `{ data\|base64, resourceId? }` | `{ ok, length }` |
| `coreGet` | `{ index, resourceId? }` | `{ ok, data }` (base64 or null) |
### Hyperbee (key/value B-tree)
### Hyperbee
| Type | Payload | Response |
|------|---------|----------|
| `beeGet` | `{ key }` (string) | `{ ok, key, value, seq }` or `{ ok, value: null }` |
| `beePut` | `{ key, value }` (strings) | `{ ok }` |
| `beeDel` | `{ key }` | `{ ok }` |
| `beeGet` | `{ key, resourceId? }` | `{ ok, key, value, seq }` or `{ ok, value: null }` |
| `beePut` | `{ key, value, resourceId? }` | `{ ok }` |
| `beeDel` | `{ key, resourceId? }` | `{ ok }` |
### Hyperdrive (file system)
### Hyperdrive
| Type | Payload | Response |
|------|---------|----------|
| `driveGet` | `{ path }` (default `'/'`) | `{ ok, data }` (base64 file content or null) |
| `drivePut` | `{ path, data }` or `{ path, base64 }` | `{ ok }` |
| `driveList` | `{ path }` (default `'/'`) | `{ ok, entries }` (array of `{ key, value }`) |
| `driveDel` | `{ path }` | `{ ok }` |
| `driveGet` | `{ path?, resourceId? }` | `{ ok, data }` (base64 or null) |
| `drivePut` | `{ path, data\|base64, resourceId? }` | `{ ok }` |
| `driveList` | `{ path?, resourceId? }` | `{ ok, entries }` |
| `driveDel` | `{ path, resourceId? }` | `{ ok }` |
### Autobase (multi-writer linearized log)
### Autobase
| Type | Payload | Response |
|------|---------|----------|
| `autobaseAppend` | `{ value }` or `{ data }` (value to append) | `{ ok, length }` |
| `autobaseViewGet` | `{ index }` (number) | `{ ok, data }` (base64 view block or null) |
| `autobaseInfo` | | `{ ok, length, signedLength }` |
| `autobaseAppend` | `{ value\|data, resourceId? }` | `{ ok, length }` |
| `autobaseViewGet` | `{ index, resourceId? }` | `{ ok, data }` |
| `autobaseInfo` | `{ resourceId? }` | `{ ok, length, signedLength }` |
### Hyperdb (schema-based P2P database)
### Hyperdb
The default Hyperdb instance uses a minimal definition with a single collection **`records`**. Each document has a string **`id`** (primary key) and a string **`value`**.
Hyperschema-generated definition (`npm run build:hyperdb`) with collections:
- `@bridgeswarm/records``{ id, value }` (strings)
- `@bridgeswarm/notes``{ id, title, body }` (strings)
| Type | Payload | Response |
|------|---------|----------|
| `hyperdbGet` | `{ collection, query }` — e.g. `query: { id: 'key' }` | `{ ok, doc }` (doc or null) |
| `hyperdbInsert` | `{ collection, doc }` — doc must have `id` and `value` (strings) | `{ ok }` |
| `hyperdbDelete` | `{ collection, query }` — e.g. `query: { id: 'key' }` | `{ ok }` |
| `hyperdbFindToArray` | `{ collectionOrIndex, query?, limit?, reverse? }` | `{ ok, docs }` (array of docs) |
| `hyperdbFlush` | | `{ ok }` |
| `hyperdbGet` | `{ collection, query, resourceId? }` | `{ ok, doc }` |
| `hyperdbInsert` | `{ collection, doc, resourceId? }` | `{ ok }` |
| `hyperdbDelete` | `{ collection, query, resourceId? }` | `{ ok }` |
| `hyperdbFindToArray` | `{ collectionOrIndex, query?, limit?, reverse?, resourceId? }` | `{ ok, docs }` |
| `hyperdbFlush` | `{ resourceId? }` | `{ ok }` |
### Connection attachment
These commands attach host-side behavior to a connection (identified by `connId` from a `connection` event). After attachment, that connection is no longer forwarded to the browser.
### Connection attachment & auto-replicate
| Type | Payload | Response |
|------|---------|----------|
| `attachReplication` | `{ connId, coreKeyHex? }` | `{ ok }`Attach Hypercore replication to the connection. If `coreKeyHex` is provided, the host uses that core; otherwise the default core. |
| `attachHrpc` | `{ connId }` | `{ ok }` — Enable HRPC on the connection (Protomux channel `bridgeswarm-hrpc`). Returns immediately; channel pairs when both sides have enabled; first ping may wait up to 12s for channel. See [HRPC.md](HRPC.md). |
| `hrpcInvoke` | `{ connId, method, args? }` — Call an HRPC method from the browser; only `method: 'ping'` is supported (unary). | `{ ok, result }` (e.g. `result: { pong }`) or `{ ok: false, error }` |
| `attachReplication` | `{ connId, coreKeyHex?, resourceId? }` | `{ ok }`Host takes over the socket for Hypercore replication (no longer forwarded to the page). |
| `setAutoReplicate` | `{ swarmId, enabled, coreKeyHex?, resourceId? }` | `{ ok, enabled }` — When enabled, every new connection auto-runs `attachReplication`. Those sockets are not usable for page chat/Protomux. Prefer `swarm.setAutoReplicate(...)`. |
| `attachHrpc` | `{ connId }` | `{ ok }` — See [HRPC.md](HRPC.md). |
| `hrpcInvoke` | `{ connId, method, args?, streamId? }` | Unary `{ ok, result }` or streaming `{ ok, streamId, streaming: true }` + `hrpc-chunk` / `hrpc-end` / `hrpc-error` events |
## Example: Hyperbee
## Peer control
```javascript
await BridgeSwarm.request('beePut', { key: 'greeting', value: 'hello' });
const r = await BridgeSwarm.request('beeGet', { key: 'greeting' });
console.log(r.value); // 'hello'
```
| Type | Payload | Response |
|------|---------|----------|
| `setFirewall` | `{ swarmId, mode: 'off'\|'allowlist'\|'denylist', keys: string[] }` | `{ ok, mode, keys }` |
| `banPeer` | `{ swarmId, publicKeyHex, banned? }` | `{ ok, banned, publicKeyHex }` |
## Example: Hyperdrive
```javascript
const text = new TextEncoder().encode('file content');
const base64 = btoa(String.fromCharCode(...text));
await BridgeSwarm.request('drivePut', { path: '/hello.txt', base64 });
const res = await BridgeSwarm.request('driveGet', { path: '/hello.txt' });
// res.data is base64; decode: atob(res.data) then to bytes
const list = await BridgeSwarm.request('driveList', { path: '/' });
console.log(list.entries); // [{ key: '/hello.txt', value: {...} }]
```
## Example: Autobase
```javascript
await BridgeSwarm.request('autobaseAppend', { value: 'event one' });
await BridgeSwarm.request('autobaseAppend', { value: 'event two' });
const info = await BridgeSwarm.request('autobaseInfo');
for (let i = 0; i < info.length; i++) {
const block = await BridgeSwarm.request('autobaseViewGet', { index: i });
console.log(block.data ? atob(block.data) : null);
}
```
## Example: Hyperdb
```javascript
const collection = 'records';
await BridgeSwarm.request('hyperdbInsert', { collection, doc: { id: 'foo', value: 'hello' } });
const r = await BridgeSwarm.request('hyperdbGet', { collection, query: { id: 'foo' } });
console.log(r.doc); // { id: 'foo', value: 'hello' }
const { docs } = await BridgeSwarm.request('hyperdbFindToArray', { collectionOrIndex: collection });
console.log(docs);
await BridgeSwarm.request('hyperdbDelete', { collection, query: { id: 'foo' } });
await BridgeSwarm.request('hyperdbFlush'); // flush pending writes
```
Prefer page helpers: `swarm.setFirewall(...)`, `swarm.ban(publicKey)`, `peerInfo.ban()`.
## See also
- [API-REFERENCE.md](API-REFERENCE.md) — Full list of all host request types and events.
- [ARCHITECTURE.md](ARCHITECTURE.md) — Message flow and components.
## Errors
If the host returns `{ ok: false, error: '...' }`, the promise still resolves. Check `response.ok` and use `response.error` when `ok` is false.
- [API-REFERENCE.md](API-REFERENCE.md)
- [HRPC.md](HRPC.md)
- [ARCHITECTURE.md](ARCHITECTURE.md)
+31 -55
View File
@@ -1,79 +1,55 @@
# HRPC on the Native Host
The native host can run **[hrpc](https://www.npmjs.com/package/hrpc)** over a connection: schema-defined, code-generated RPC with support for unary calls, request-streams, response-streams, duplex streams, and send-only commands. HRPC runs **only on the native host** over a Protomux channel; the browser does not run hrpc. From the browser you only send `attachHrpc` to enable it on a connection; the host then speaks the hrpc protocol with the remote peer (or with another host that has also attached hrpc).
The native host runs **[hrpc](https://www.npmjs.com/package/hrpc)** over a connection (schema-defined RPC on a Protomux channel). The browser does not run hrpc itself; it attaches HRPC on the host and invokes methods via `hrpcInvoke` / `swarm.hrpcCall`.
## Enabling HRPC
From the browser, after you have a connection (e.g. from `swarm.on('connection', (conn, peerInfo) => { ... })`), call:
```javascript
const res = await BridgeSwarm.request('attachHrpc', { connId: conn.connId });
if (res.ok) {
// Host now speaks hrpc on that connection (Protomux channel "bridgeswarm-hrpc")
}
await BridgeSwarm.request('attachHrpc', { connId: conn.connId });
```
- **Payload**: `{ connId }` — the connection ID from the `connection` event payload.
- **Response**: `{ ok: true }` or `{ ok: false, error: '...' }`. On success, that connection is no longer forwarded to the browser; the host uses it for the hrpc channel.
- **Handshake**: `attachHrpc` returns immediately (no blocking wait). The Protomux channel pairs when **both** sides have called `attachHrpc` on the same connection pair. When you call `hrpcInvoke` (e.g. ping), the host waits up to **12 seconds** for the channel to open if it is not yet open; if the other tab has also enabled HRPC, the channel opens and the RPC runs. Enable HRPC in both tabs (or on both ends of the connection); the first ping may briefly wait for the channel.
- **One connection per peer**: The host keeps only the first connection per peer per swarm; later connections from the same peer are dropped so both sides use the same connection pair for HRPC.
Both peers must attach HRPC on the same connection pair. The first invoke may wait up to **12s** for the channel to open.
## Protocol
- **Channel**: Protomux protocol name `bridgeswarm-hrpc`; one buffer-framed message type carries the RPC wire format (bare-rpc compatible).
- **Stream**: The host creates a duplex stream over that channel and instantiates the generated HRPC class; the remote peer can speak the same protocol to call the defined commands.
## Commands
The following commands are defined in the spec and generated into `spec/hrpc/`. They are implemented by the host in [native-host/host.js](../native-host/host.js).
## Commands (spec)
| Command | Mode | Request | Response |
|---------|------|---------|----------|
| `ping` | Unary | `{ value? }` (optional string) | `{ pong }` (string; echoes or `'pong'`) |
| `streamSum` | Request-stream → single response | Client streams chunks `{ n, label }` | `{ sum, count }` (sum of `n`, number of chunks) |
| `fetchStream` | Single request → response-stream | `{ count }` (number of chunks to stream) | Server streams `{ i, data }` chunks |
| `duplex` | Duplex (request- and response-streams) | Client streams `{ x, y }` | Server streams `{ result, n }` (echo/transform) |
| `notify` | Send-only | `{ event, payload }` | No response (host logs to stderr) |
| `ping` | Unary | `{ value? }` | `{ pong }` |
| `streamSum` | Request-stream → response | chunks `{ n, label }` (pass `args.chunks` from browser) | `{ sum, count }` on `hrpc-end` |
| `fetchStream` | Request → response-stream | `{ count }` | chunks `{ i, data }` via `hrpc-chunk` |
| `duplex` | Duplex | `args.chunks` `{ x, y }` | chunks `{ result, n }` via `hrpc-chunk` |
| `notify` | Send-only | `{ event, payload }` | none |
Schema and command registration live in [scripts/build-hrpc.js](../scripts/build-hrpc.js); generated code is written to `spec/hyperschema/` and `spec/hrpc/` (index.js, messages.js, hrpc.json).
Build: `npm run build:hrpc``spec/hrpc/` + `native-host/spec/hrpc/`.
## Building the spec
## Invoking from the browser
From the repo root:
```bash
npm run build:hrpc
```
This writes `spec/hyperschema/` and `spec/hrpc/`. The installer runs this step automatically, so after `./scripts/install.sh` the spec is already built.
To change the API (add commands or message types), edit [scripts/build-hrpc.js](../scripts/build-hrpc.js) (Hyperschema namespace + HRPCBuilder registration), then run `npm run build:hrpc` again.
## Handlers
Handlers are implemented in [native-host/host.js](../native-host/host.js): ping returns pong, streamSum accumulates request stream and returns sum/count, fetchStream writes chunks to the response stream, duplex reads from the request stream and writes to the response stream, notify logs to stderr. The host loads the generated HRPC class from `spec/hrpc/` and registers these handlers when `attachHrpc` runs.
## Invoking ping from the browser
The host supports `BridgeSwarm.request('hrpcInvoke', { connId, method: 'ping', args })` so the browser can call the unary `ping` RPC. The host uses the same HRPC instance (server) as a client to send the request to the peer; the peers host must have HRPC attached so its handler replies. Example:
### Unary / notify
```javascript
const res = await BridgeSwarm.request('hrpcInvoke', {
connId: conn.connId,
method: 'ping',
args: { value: 'hello' }
});
if (res.ok && res.result) console.log('Pong:', res.result.pong);
const res = await swarm.hrpcCall(conn.connId, 'ping', { value: 'hello' });
// or: BridgeSwarm.request('hrpcInvoke', { connId, method: 'ping', args })
```
Only `ping` is supported via `hrpcInvoke` (streaming commands would require a different bridge). See **examples/hrpc-demo/** for a full demo: open `hrpc-demo/index.html` in two tabs, join the same topic; HRPC is auto-enabled on each connection, then either tab can ping.
### Streaming
## If the spec is missing
```javascript
const res = await swarm.hrpcCall(conn.connId, 'fetchStream', { count: 3 }, {
onChunk: (chunk) => console.log(chunk),
timeoutMs: 20000,
});
// Host replies immediately with { streaming: true, streamId }, then emits
// hrpc-chunk / hrpc-end / hrpc-error events to the page.
```
If `spec/hrpc/` was not built or fails to load (e.g. missing dependencies), the host still starts. In that case `attachHrpc` returns an error such as `"hrpc not available: ..."` or `"hrpc spec not built (run: npm run build:hrpc)"`. Run `npm run build:hrpc` from the repo root (or re-run the installer), then restart the native host.
```javascript
await swarm.hrpcCall(conn.connId, 'streamSum', {
chunks: [{ n: 1, label: 'a' }, { n: 2, label: 'b' }],
});
```
## See also
- [DATA-API.md](DATA-API.md) — Connection attachment table (attachReplication, attachHrpc).
- [API-REFERENCE.md](API-REFERENCE.md) — Full list of host request types.
- [ARCHITECTURE.md](ARCHITECTURE.md) — Message flow and components.
- [DATA-API.md](DATA-API.md)
- [API-REFERENCE.md](API-REFERENCE.md)
- `examples/hrpc-demo/`
+1 -1
View File
@@ -131,4 +131,4 @@ Storage for the hosts corestore is under `BRIDGE_SWARM_STORAGE` or `./bridge-
- Each BridgeSwarm connection is already a **NoiseSecretStream** (framed, encrypted). The extension forwards one decrypted frame per chunk, so `wrapRawFrames(conn)` presents one chunk per frame to Protomux.
- Protomux pairs channels by `protocol` and optional `id`. Use the same `protocol` (and `id` if you set one) on both sides so the channel opens.
- For RPC-style usage, consider layering [protomux-rpc](https://www.npmjs.com/package/protomux-rpc) on top (you would need to add and bundle it separately if you want it in the browser).
- For RPC-style usage, BridgeSwarms supported path is **host HRPC** (`attachHrpc` + `swarm.hrpcCall` / `hrpcInvoke`). In-page [protomux-rpc](https://www.npmjs.com/package/protomux-rpc) remains optional DIY if you bundle it yourself on top of `createProtomux`.