Update peer dep from @rayhanadev/iroh to @alkdev/iroh. Document platform strategy: Linux native + WASM fallback, Windows/macOS deferred. Fix outdated single-import reference in iroh-transport.md to split spoke/hub imports. Resolve binding stability and NAPI/Deno R&D items as covered by the fork path.
77 lines
3.7 KiB
Markdown
77 lines
3.7 KiB
Markdown
---
|
|
status: draft
|
|
last_updated: 2026-05-08
|
|
---
|
|
|
|
# Iroh Spoke Event Target
|
|
|
|
**Import**: `@alkdev/pubsub/event-target-iroh-spoke`
|
|
**Peer dep**: `@alkdev/iroh` (optional, NAPI-RS native addon — pending fork of iroh-ts)
|
|
**Status**: Deferred. Pending fork of iroh-ts with Linux + WASM platform targets.
|
|
|
|
P2P QUIC event target for the spoke (client) side. The spoke initiates the connection and opens the bidirectional stream.
|
|
|
|
## `createIrohSpokeEventTarget`
|
|
|
|
```ts
|
|
async function createIrohSpokeEventTarget<TEvent extends TypedEvent>(
|
|
args: CreateIrohSpokeEventTargetArgs,
|
|
): Promise<TypedEventTarget<TEvent>>;
|
|
```
|
|
|
|
### `CreateIrohSpokeEventTargetArgs`
|
|
|
|
| Field | Type | Required | Description |
|
|
|-------|------|----------|-------------|
|
|
| `endpoint` | `Endpoint` | Yes | iroh endpoint (created with `Endpoint.create()`) |
|
|
| `hubNodeId` | `string` \| `NodeId` | Yes | The hub's public key (Ed25519) |
|
|
| `alpn` | `string` | No | Application-layer protocol. Default: `"alkpubsub/1"` |
|
|
|
|
## Protocol
|
|
|
|
Single bidirectional QUIC stream per connection. Length-prefixed JSON messages:
|
|
|
|
```
|
|
[4 bytes: length N][N bytes: JSON payload]
|
|
```
|
|
|
|
The JSON payload is the `EventEnvelope`:
|
|
|
|
```json
|
|
{ "type": "call.responded", "id": "uuid-123", "payload": { "output": 42 } }
|
|
```
|
|
|
|
## Connection Flow
|
|
|
|
1. Spoke creates `Endpoint`
|
|
2. Spoke calls `endpoint.connect(hubNodeId, alpn)` → `Connection`
|
|
3. Spoke calls `connection.openBi()` → `SendStream` + `RecvStream`
|
|
4. Spoke wraps streams in `IrohSpokeEventTarget`
|
|
5. On disconnect: `RecvStream.readExact()` throws, spoke must reconnect
|
|
|
|
## Subscription Forwarding
|
|
|
|
Same pattern as [WebSocket Client](websocket-client.md): when `addEventListener` is called for the first listener on a topic, the spoke sends a `__subscribe` control event to the hub. When `removeEventListener` removes the last listener for a topic, the spoke sends `__unsubscribe`. Reference counting ensures `__subscribe` is sent only on the first `addEventListener` for a topic, and `__unsubscribe` only when the last `removeEventListener` for that topic is called. This enables the hub's topic-based fan-out (see [ADR-003](../decisions/003-subscription-control-protocol.md)).
|
|
|
|
Control events use the existing `EventEnvelope` format:
|
|
|
|
```json
|
|
{ "type": "__subscribe", "id": "", "payload": { "topic": "message.sent:conv-123" } }
|
|
{ "type": "__unsubscribe", "id": "", "payload": { "topic": "message.sent:conv-123" } }
|
|
```
|
|
|
|
## Key Properties
|
|
|
|
- **NAT traversal** — spoke dials hub by `NodeId`, no public IP needed
|
|
- **Cryptographic identity** — `Connection.remoteNodeId()` verifies the hub
|
|
- **Bidirectional** — `dispatchEvent` writes to `SendStream`, `addEventListener` reads from `RecvStream`
|
|
- **Per-connection** — one event target per QUIC connection
|
|
- **Subscription forwarding** — `addEventListener`/`removeEventListener` send `__subscribe`/`__unsubscribe` control events to the hub (see [ADR-003](../decisions/003-subscription-control-protocol.md))
|
|
|
|
## R&D Needed
|
|
|
|
1. **Fork of iroh-ts** — `@rayhanadev/iroh` has one author and no tests. We plan to fork as `@alkdev/iroh` with Linux (NAPI-RS) + WASM platform targets. Windows and macOS native builds are deferred — cross-compilation from Linux is possible with NAPI but tricky, and macOS licensing is dubious for CI. Initial release: Linux native + WASM fallback. Adding other platforms is a future accessibility concern.
|
|
2. **Stream multiplexing** — multiple `openBi()` streams on one connection vs. single stream with multiplexed events. Single stream + JSON framing is simpler.
|
|
3. **Reconnection** — `RecvStream.readExact()` throws on connection close. Need to propagate this to listeners and support reconnect.
|
|
|
|
See [../iroh-transport.md](../iroh-transport.md) for full protocol details, identity, and comparison with WebSocket. |