first commit

This commit is contained in:
Raven Scott
2026-02-12 03:27:05 -05:00
commit 90c7a4910e
38 changed files with 7466 additions and 0 deletions
+116
View File
@@ -0,0 +1,116 @@
# Native Host Data API (Hypercore, Hyperbee, Hyperdrive, Autobase)
The native host runs **Corestore**, **Hypercore**, **Hyperbee**, **Hyperdrive**, and **Autobase**. The browser can call into these via `BridgeSwarm.request(type, payload)`, which sends a JSON command to the host and returns the response.
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.
## 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);
```
## 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) |
### Hyperbee (key/value B-tree)
| Type | Payload | Response |
|------|---------|----------|
| `beeGet` | `{ key }` (string) | `{ ok, key, value, seq }` or `{ ok, value: null }` |
| `beePut` | `{ key, value }` (strings) | `{ ok }` |
| `beeDel` | `{ key }` | `{ ok }` |
### Hyperdrive (file system)
| 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 }` |
### Autobase (multi-writer linearized log)
| 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 }` |
### Hyperdb (schema-based P2P database)
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`**.
| 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 }` |
### Replication
| Type | Payload | Response |
|------|---------|----------|
| `attachReplication` | `{ connId, coreKeyHex? }` | `{ ok }` — Attach core replication to connection; that connection stops being forwarded to the browser. |
## Example: Hyperbee
```javascript
await BridgeSwarm.request('beePut', { key: 'greeting', value: 'hello' });
const r = await BridgeSwarm.request('beeGet', { key: 'greeting' });
console.log(r.value); // 'hello'
```
## 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
```
## Errors
If the host returns `{ ok: false, error: '...' }`, the promise still resolves. Check `response.ok` and use `response.error` when `ok` is false.
+128
View File
@@ -0,0 +1,128 @@
# Using Protomux and compact-encoding in the Browser
BridgeSwarm injects a browser bundle of **Protomux**, **compact-encoding** (`c`), and **b4a** so you can run message-oriented protocols over P2P connections.
## Setup
1. Install the extension and native host (see main [README](../README.md)).
2. Ensure the Protomux bundle is built: from repo root run `npm run build:protomux` (output: `extension/protomux-bundle.js`).
3. The extension injects `api.js`, `framed-stream.js`, and `protomux-bundle.js` into the page in order. No extra script tags are needed.
## Globals
| Global | Description |
|--------|-------------|
| `window.BridgeSwarm` | Main API: `new BridgeSwarm(opts)`, `swarm.join(topic)`, `swarm.on('connection', ...)`, `swarm.createProtomux(conn)` |
| `window.BridgeSwarmFramedStream` | `wrapRawFrames(conn)` — wraps a connection so it presents a stream that preserves message boundaries for Protomux |
| `window.BridgeSwarmProtomux` | `{ Protomux, c, b4a }` — Protomux class, compact-encoding helpers, b4a buffer utils |
## createProtomux(conn)
Given a connection from `swarm.on('connection', (conn, peerInfo) => { ... })`, call:
```js
const mux = swarm.createProtomux(conn);
```
This wraps the connection in a framed stream and returns a new `Protomux` instance. If the Protomux bundle or framed stream script is not loaded, `createProtomux` returns `null` and logs a warning.
## compact-encoding (c)
Use `window.BridgeSwarmProtomux.c` for message encodings. Common encodings:
- `c.string` — UTF-8 string
- `c.binary` / `c.raw` — raw buffer
- `c.uint` — unsigned integer
- `c.bool` — boolean
- `c.json` — JSON (if available in the bundle)
Example:
```js
const { Protomux, c, b4a } = window.BridgeSwarmProtomux;
const channel = mux.createChannel({
protocol: 'my-app/v1',
onopen() {},
onclose() {}
});
const stringMsg = channel.addMessage({
encoding: c.string,
onmessage(value) { console.log('Got string:', value); }
});
const binaryMsg = channel.addMessage({
encoding: c.binary,
onmessage(buf) { console.log('Got buffer:', buf); }
});
channel.open();
stringMsg.send('hello');
binaryMsg.send(b4a.from('data'));
```
## b4a
`window.BridgeSwarmProtomux.b4a` provides buffer helpers compatible with Node-style `Buffer` usage:
- `b4a.from(str, encoding)` — create from string (e.g. `'utf8'`)
- `b4a.toString(buf, encoding)` — buffer to string
- `b4a.allocUnsafe(n)` — allocate (browser may use `Uint8Array`)
Use these when encoding/decoding binary message payloads.
## Full example
```html
<!DOCTYPE html>
<html>
<body>
<button id="join">Join topic</button>
<pre id="log"></pre>
<script>
const log = (msg) => { document.getElementById('log').textContent += msg + '\n'; };
const swarm = new BridgeSwarm({ appName: 'protomux-example' });
document.getElementById('join').onclick = async () => {
await swarm.join('protomux-demo-topic');
log('Joined. Open this page in another tab and join the same topic.');
};
swarm.on('connection', (conn, peerInfo) => {
log('Peer connected: ' + peerInfo.publicKey.slice(0, 16) + '...');
const mux = swarm.createProtomux(conn);
if (!mux) { log('Protomux not available'); return; }
const ch = mux.createChannel({
protocol: 'chat',
onopen() { log('Channel opened'); },
onclose() { log('Channel closed'); }
});
ch.addMessage({
encoding: window.BridgeSwarmProtomux.c.string,
onmessage(m) { log('Peer said: ' + m); }
});
ch.open();
window.sendChat = (text) => ch.messages[0].send(text);
});
</script>
</body>
</html>
```
## Native host: Hypercore replication (advanced)
The native host can attach **Hypercore replication** to a connection so that the hosts core replicates with the remote peer over that connection. When you do this, that connection is no longer forwarded to the browser (the host “takes over” it for the Hypercore protocol).
From the browser you would need to send a message to the native host (the current extension API does not expose this; youd extend the extension to send an `attachReplication` command). The host supports:
- **attachReplication** — payload: `{ connId, coreKeyHex? }`. Uses the connection identified by `connId`. If `coreKeyHex` is provided, the host looks up that core from its corestore; otherwise it uses a default core (name `'default'`). The host creates a Protomux from the socket, stops forwarding that connections data to the browser, and runs `core.replicate(protomux)` so the remote peer can replicate that core.
Storage for the hosts corestore is under `BRIDGE_SWARM_STORAGE` or `./bridge-swarm-storage` relative to the host process.
## Notes
- 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).