first commit
This commit is contained in:
@@ -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.
|
||||
@@ -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 host’s 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; you’d 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 connection’s data to the browser, and runs `core.replicate(protomux)` so the remote peer can replicate that core.
|
||||
|
||||
Storage for the host’s 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).
|
||||
Reference in New Issue
Block a user