@kehto/services 0.16.1 → 0.16.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -15
- package/dist/cvm-nostr-transport.d.ts +2 -0
- package/dist/cvm-nostr-transport.js.map +1 -1
- package/dist/index.d.ts +103 -95
- package/dist/index.js +649 -286
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ Current draft posture:
|
|
|
23
23
|
- The v1.1 signer service is deleted outright. Its responsibilities split into two: read-only identity lookups go through `createIdentityService` (`getPublicKey`, `getRelays`, `getProfile`, `getFollows`, `getList`, `getZaps`, `getMutes`, `getBlocked`, `getBadges`); signing happens inside the shell as part of `relay.publish` / `relay.publishEncrypted` and is never exposed to napplets.
|
|
24
24
|
- `createKeysService` and `createMediaService` ship real reference backends as of v1.4 (see the dedicated sections below). `createKeysService` attaches a document-level `keydown` listener by default and delivers `keys.action` push envelopes to registered napplets; `createMediaService` mirrors session metadata and playback state to `navigator.mediaSession` and emits `media.command` push envelopes on OS transport events. Both accept a host-bridge option (`HostKeysBridge` / `HostMediaBridge`) so Electron / Tauri / native shells can swap in OS-level backends without re-implementing the wire-protocol bookkeeping.
|
|
25
25
|
- `createNotifyService` (NIP-5D `notify.*` NAP) coexists with the legacy `createNotificationService` (inc-emit `notifications:*` channel). Both may be registered simultaneously while hosts migrate.
|
|
26
|
-
- `createOutboxService` supports `outbox.getEvent` from draft NAP-OUTBOX. Single-event lookups run through shell-owned relay routing and only return events whose ID matches the request.
|
|
26
|
+
- `createOutboxService` supports `outbox.getEvent` from draft NAP-OUTBOX. Single-event lookups run through shell-owned relay routing and only return events whose ID matches the request. The draft wire contract keeps `outbox.query` one-shot and `outbox.subscribe` streaming; Kehto's concrete `createRelayPoolOutboxRouter` additionally exposes host-side `queryStream()` so verified query events can arrive before asynchronous NIP-65 discovery completes.
|
|
27
27
|
- `createResourceService` supports `resource.bytesMany` from draft NAP-RESOURCE. Bulk requests return ordered per-URL items and keep per-URL failures local while preserving legacy single-fetch fields for existing Kehto callers.
|
|
28
28
|
- `createUploadService` supports `upload.info` from draft NAP-UPLOAD. Hosts may expose configured rails, return URL forms, maximum bytes, and MIME type policy without requiring napplets to start an upload.
|
|
29
29
|
- `createResourceService` supports `resource.info` and `resource.bytesMany` from draft NAP-RESOURCE. `resource.info` returns advisory schemes and coarse limits; bulk requests return ordered per-URL items and keep per-URL failures local while preserving legacy single-fetch fields for existing Kehto callers.
|
|
@@ -137,6 +137,22 @@ runtime.registerService(
|
|
|
137
137
|
);
|
|
138
138
|
```
|
|
139
139
|
|
|
140
|
+
## Outbox Streaming Queries
|
|
141
|
+
|
|
142
|
+
NAP-OUTBOX is still an open upstream draft ([`napplet/naps` PR #32](https://github.com/napplet/naps/pull/32)). Its wire `outbox.query.result` remains one aggregate response. Hosts using Kehto's concrete router can also consume the same bounded read incrementally without changing the napplet wire protocol:
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
const stream = router.queryStream(
|
|
146
|
+
[{ authors: [author], kinds: [1] }],
|
|
147
|
+
{ timeoutMs: 3000 },
|
|
148
|
+
{ event: (result) => render(result.event) },
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
const aggregate = await stream.result;
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
When relay-list loading is asynchronous, the router opens validated relay hints or fallback relays immediately, attaches discovered author write relays to the same deduplicating collector, and applies one deadline to discovery and collection. `query()` remains the compatibility aggregate over this stream. `options.limit` applies to that aggregate; the stream receives every verified unique arrival before completion. `subscribe()` uses the same immediate seed fanout before adding discovered relays.
|
|
155
|
+
|
|
140
156
|
## Keys Service
|
|
141
157
|
|
|
142
158
|
Reference keyboard / chord backend for the `keys.*` NIP-5D NAP. By default attaches a single `document`-level `keydown` listener that matches incoming events against registered chord subscriptions and delivers a `keys.action` push envelope back to the owning napplet. Implement the [`HostKeysBridge`](#hostkeysbridge-interface) interface to swap in OS-level backends (Electron `globalShortcut`, Tauri `GlobalShortcut`).
|
|
@@ -155,10 +171,10 @@ export function createKeysService(options?: KeysServiceOptions): ServiceHandler
|
|
|
155
171
|
|
|
156
172
|
| Field | Type | Description |
|
|
157
173
|
|-------|------|-------------|
|
|
158
|
-
| `onForward` | `(event: { key, code, ctrlKey, altKey, shiftKey, metaKey }) => void` | Called on `keys.forward` envelopes
|
|
174
|
+
| `onForward` | `(event: { key, code, ctrlKey, altKey, shiftKey, metaKey }) => void` | Called on `keys.forward` envelopes and on document/host-bridge keydowns handled by the reference backend. DOM-shape payload (the service translates from the wire-format `{ ctrl, alt, shift, meta }` before invoking this callback). Forwarded keys do not dispatch `keys.action`; active napplets suppress bound keys locally from `keys.bindings` before forwarding. |
|
|
159
175
|
| `listenerTarget` | `EventTarget` | Defaults to `document`. Pass a fresh `new EventTarget()` in unit tests to isolate the listener. Ignored when `hostBridge` is provided. |
|
|
160
176
|
| `hostBridge` | `HostKeysBridge` | Pluggable OS-bridge. When provided, the service delegates `keys.registerAction` to `bridge.subscribe(chord, cb)` and the default document listener is NOT attached. |
|
|
161
|
-
| `reservedChords` | `ReadonlyArray<string>` | Optional set of shell-reserved chords (wire-format strings like `'Ctrl+Shift+K'`, `'Cmd+P'`).
|
|
177
|
+
| `reservedChords` | `ReadonlyArray<string>` | Optional set of shell-reserved chords (wire-format strings like `'Ctrl+Shift+K'`, `'Cmd+P'`). Reserved chords are not assigned as napplet action bindings. Document keydowns that match a reserved chord call `onForward` but do not dispatch `keys.action`. Precedence: **reserved > registered**. Normalized once at construction via the same parser used for `action.defaultKey`. See [Reserved Chords](#reserved-chords). |
|
|
162
178
|
|
|
163
179
|
### HostKeysBridge interface
|
|
164
180
|
|
|
@@ -242,7 +258,7 @@ runtime.registerService('keys', createKeysService({ hostBridge: electronBridge }
|
|
|
242
258
|
|
|
243
259
|
Plug a `HostKeysBridge` when the default document listener is insufficient: Electron or Tauri apps that need to register OS-level global hotkeys (chords delivered even when the host window is not focused), native shells that route chords through a platform-specific hotkey manager (macOS Carbon, Linux X11 grab, Windows RegisterHotKey), or test harnesses that inject synthetic events through a controlled `EventTarget`. The bridge owns subscription lifecycle; the service retains per-window bookkeeping (so `onWindowDestroyed` cleanup stays identical across paths).
|
|
244
260
|
|
|
245
|
-
A napplet drives this end to end via `@napplet/sdk` — `keys.registerAction` to claim a chord and `keys.onAction` to receive dispatches against the real backend.
|
|
261
|
+
A napplet drives this end to end via `@napplet/sdk` — `keys.registerAction` to claim a chord and `keys.onAction` to receive dispatches against the real backend. After a successful bound registration, the service pushes a complete `keys.bindings` list for that napplet/window, using entries shaped as `{ actionId, key }`. It pushes the complete list again after `keys.unregisterAction` removes a binding, including an empty list when no bindings remain. Injected shims use that list to suppress locally-bound keydowns before forwarding.
|
|
246
262
|
|
|
247
263
|
### Reserved Chords
|
|
248
264
|
|
|
@@ -255,7 +271,7 @@ const keys = createKeysService({
|
|
|
255
271
|
reservedChords: [
|
|
256
272
|
'Ctrl+Alt+T', // launcher
|
|
257
273
|
'Super+Space', // workspace switch
|
|
258
|
-
'Ctrl+Shift+
|
|
274
|
+
'Ctrl+Shift+Y', // shell palette
|
|
259
275
|
],
|
|
260
276
|
onForward: (event) => {
|
|
261
277
|
// The shell's WM dispatcher — fires for reserved chords regardless of
|
|
@@ -267,10 +283,11 @@ const keys = createKeysService({
|
|
|
267
283
|
runtime.registerService('keys', keys);
|
|
268
284
|
```
|
|
269
285
|
|
|
270
|
-
**Precedence contract: reserved >
|
|
286
|
+
**Precedence contract: reserved > binding.** When a napplet registers an action whose `defaultKey` matches `reservedChords`, the service acknowledges the action with `keys.registerAction.result` but leaves `binding` undefined and does not add the chord to `keys.bindings`. That prevents the active napplet from suppressing a shell-owned chord locally.
|
|
271
287
|
|
|
272
|
-
- If
|
|
273
|
-
- If
|
|
288
|
+
- If a document keydown matches a reserved chord: `onForward` fires exactly once and no `keys.action` envelope is dispatched.
|
|
289
|
+
- If a document keydown matches a non-reserved registered binding: `onForward` fires and every napplet whose registered action matches receives a `keys.action` envelope via its captured `send` handle.
|
|
290
|
+
- If a napplet sends `keys.forward`: `onForward` fires and the service does not dispatch `keys.action`. Active napplets are expected to suppress bound keys locally from `keys.bindings` before forwarding.
|
|
274
291
|
|
|
275
292
|
Reserved chords are normalized at service construction via the same parser used for `action.defaultKey`, so `'Ctrl+Shift+K'`, `'Control+shift+k'`, and `'ctrl+Shift+K'` all match the same chord. Modifier aliases (`Cmd` / `Command` / `Win` / `Super` → meta; `Control` → ctrl; `Option` → alt) are recognized case-insensitively.
|
|
276
293
|
|
|
@@ -288,17 +305,16 @@ const keys = createKeysService({
|
|
|
288
305
|
});
|
|
289
306
|
runtime.registerService('keys', keys);
|
|
290
307
|
|
|
291
|
-
// Napplet-side (hotkey-chord napplet, for example):
|
|
308
|
+
// Napplet-side (hotkey-chord napplet, for example): free to request
|
|
292
309
|
// `Ctrl+Shift+K` via keys.registerAction. If Ctrl+Shift+K is NOT in the shell's
|
|
293
|
-
// reservedChords, the napplet receives
|
|
294
|
-
//
|
|
295
|
-
//
|
|
296
|
-
// chord — the shell is authoritative.
|
|
310
|
+
// reservedChords, the napplet receives a binding and can handle it locally while
|
|
311
|
+
// active. If the shell reserves that chord, the registration result is unbound
|
|
312
|
+
// and no suppress-list entry is pushed.
|
|
297
313
|
```
|
|
298
314
|
|
|
299
315
|
**Dynamic reservation is out of scope for v1.6.** If a downstream shell needs runtime updates to the reserved set (e.g. "reservation depends on which workspace is active"), open an issue referencing `HostKeysBridge.reserveAbsolute(chords)` — the deferred extension shape. Until then, `reservedChords` is static at service construction.
|
|
300
316
|
|
|
301
|
-
**OS-level global hotkeys remain a separate concern.** `reservedChords` operates at the service layer
|
|
317
|
+
**OS-level global hotkeys remain a separate concern.** `reservedChords` operates at the service layer. For OS-level reservation (chord fires even when the host window is unfocused), implement [`HostKeysBridge.registerGlobalHotkey`](#hostkeysbridge-interface) in your bridge — reserved chords and global hotkeys compose orthogonally.
|
|
302
318
|
|
|
303
319
|
## Media Service
|
|
304
320
|
|
|
@@ -453,7 +469,7 @@ Each factory returns a `ServiceHandler` registrable via `runtime.registerService
|
|
|
453
469
|
- `createCoordinatedRelay` — composite service that bundles relay-pool + cache with read-through behavior.
|
|
454
470
|
|
|
455
471
|
### Keys NAP
|
|
456
|
-
- `createKeysService` — `keys.registerAction` / `keys.unregisterAction` / `keys.forward` + `keys.action` push envelopes (`keys:forward`). Document-level chord listener by default; implement the `HostKeysBridge` interface to swap in Electron / Tauri / OS-level backends. See [Keys Service](#keys-service) for the full contract.
|
|
472
|
+
- `createKeysService` — `keys.registerAction` / `keys.unregisterAction` / `keys.forward` + `keys.bindings` / `keys.action` push envelopes (`keys:forward`). Document-level chord listener by default; implement the `HostKeysBridge` interface to swap in Electron / Tauri / OS-level backends. See [Keys Service](#keys-service) for the full contract.
|
|
457
473
|
|
|
458
474
|
### Media NAP
|
|
459
475
|
- `createMediaService` — owner-aware `media.session.create` / `update` / `destroy` / `media.state` / `media.capabilities` + `media.command` push envelopes (`media:control`). Napplet-owned sessions mirror to `navigator.mediaSession` by default; shell-owned creates are rejected until a host playback/fetch bridge is supplied. Implement the `HostMediaBridge` interface to swap in native backends. See [Media Service](#media-service) for the full contract.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cvm-nostr-transport.ts"],"sourcesContent":["/**\n * cvm-nostr-transport.ts — concrete ContextVM transport for NAP-CVM.\n *\n * Implements {@link CvmTransport} over Nostr, exactly as validated against live\n * ContextVM servers (e.g. Relatr):\n *\n * - MCP JSON-RPC messages ride in kind-25910 event `content`.\n * - Requests are CEP-4 gift-wrapped: the inner kind-25910 event is signed with\n * the shell's ephemeral client key, NIP-44-encrypted to the server, and\n * placed in a kind-21059 (ephemeral) / 1059 (regular) wrap signed by a fresh\n * random key, `p`-tagged to the server. Responses arrive the same way,\n * `p`-tagged to the client, and are correlated by the inner JSON-RPC `id`.\n * - Discovery reads kind-11316 (server) + kind-11317 (tools) announcements.\n *\n * Shipped on a separate entry (`@kehto/services/cvm-nostr-transport`) so the\n * `nostr-tools` dependency stays out of the core `@kehto/services` bundle.\n *\n * The client key is ephemeral and shell-owned: napplets never see keys, relay\n * sockets, or NIP-44 material (NAP-CVM §Security).\n *\n * @example\n * ```ts\n * import { createNostrCvmTransport } from '@kehto/services/cvm-nostr-transport';\n * const transport = createNostrCvmTransport({\n * defaultRelays: ['wss://relay.contextvm.org', 'wss://relay2.contextvm.org'],\n * });\n * ```\n */\n\nimport { SimplePool } from 'nostr-tools/pool';\nimport { finalizeEvent, generateSecretKey, getPublicKey } from 'nostr-tools/pure';\nimport * as nip44 from 'nostr-tools/nip44';\nimport type { Event as NostrToolsEvent, Filter as NostrToolsFilter } from 'nostr-tools';\n\nimport type { CvmTransport } from './cvm-service.js';\nimport type {\n CvmDiscoverQuery,\n CvmRequestOptions,\n CvmServer,\n CvmServerRef,\n McpMessage,\n} from './cvm-types.js';\n\n/** ContextVM unified transport event kind. */\nconst KIND_CVM = 25910;\n/** CEP-4 gift-wrap kinds: ephemeral (CEP-19) and regular. */\nconst KIND_GIFT_WRAP_EPHEMERAL = 21059;\nconst KIND_GIFT_WRAP_REGULAR = 1059;\n/** CEP-6 announcement kinds. */\nconst KIND_ANNOUNCE_SERVER = 11316;\nconst KIND_ANNOUNCE_TOOLS = 11317;\n\nconst DEFAULT_TIMEOUT_MS = 30_000;\nconst DEFAULT_DISCOVER_TIMEOUT_MS = 6_000;\nconst MCP_PROTOCOL_VERSION = '2025-11-25';\nconst SEEN_WRAP_LIMIT = 512;\n\n/** Minimal signed Nostr event. */\nexport interface NostrEventLike {\n id: string;\n pubkey: string;\n created_at: number;\n kind: number;\n tags: string[][];\n content: string;\n sig: string;\n}\n\n/** A Nostr REQ filter (subset). */\nexport interface NostrFilterLike {\n kinds?: number[];\n authors?: string[];\n limit?: number;\n ['#p']?: string[];\n [key: string]: unknown;\n}\n\n/** Subscription handle returned by the relay pool. */\nexport interface CvmSubCloser {\n close(): void;\n}\n\n/**\n * Minimal relay-pool surface used by this transport — structurally satisfied\n * by `nostr-tools` `SimplePool`. Injectable for testing.\n */\nexport interface CvmRelayPool {\n subscribe(\n relays: string[],\n filter: NostrFilterLike,\n params: { onevent?: (event: NostrEventLike) => void; oneose?: () => void },\n ): CvmSubCloser;\n publish(relays: string[], event: NostrEventLike): unknown;\n}\n\n/** Options for {@link createNostrCvmTransport}. */\nexport interface NostrCvmTransportOptions {\n /** Relays used when a server reference carries no relay hints. */\n defaultRelays?: string[];\n /** Default per-request timeout in milliseconds. */\n timeoutMs?: number;\n /** Whether to CEP-4 gift-wrap requests. Default true (most servers require it). */\n encrypt?: boolean;\n /** Use ephemeral (kind 21059) gift wraps when encrypting. Default true. */\n ephemeralWrap?: boolean;\n /** Relay pool to use. Defaults to a fresh `nostr-tools` `SimplePool`. */\n pool?: CvmRelayPool;\n /** Client secret key (32 bytes). Defaults to a generated ephemeral key. */\n clientSecretKey?: Uint8Array;\n /** Client info advertised during MCP `initialize`. */\n clientInfo?: { name: string; version: string };\n}\n\ntype EventHandler = (server: CvmServerRef, message: McpMessage) => void;\n\ninterface PendingRequest {\n resolve(message: McpMessage): void;\n reject(error: Error): void;\n timer: ReturnType<typeof setTimeout>;\n /** The caller's original JSON-RPC id, restored on the response. */\n originalId: string | number | undefined;\n}\n\ninterface ServerSession {\n relays: string[];\n initialized: boolean;\n initializing: Promise<void> | null;\n}\n\nlet correlationCounter = 0;\nfunction nextCorrelationId(): string {\n correlationCounter += 1;\n return `cvm-${correlationCounter}-${getPublicKey(generateSecretKey()).slice(0, 8)}`;\n}\n\nfunction randomizedPastTimestamp(): number {\n // NIP-59: randomize within the past two days to reduce timing metadata.\n const jitter = Math.floor(Math.random() * 172_800);\n return Math.floor(Date.now() / 1000) - jitter;\n}\n\nfunction tagValue(tags: string[][], name: string): string | undefined {\n return tags.find((tag) => tag[0] === name)?.[1];\n}\n\n/** Adapt a `nostr-tools` SimplePool to the {@link CvmRelayPool} surface. */\nfunction simplePoolAdapter(sp: SimplePool): CvmRelayPool {\n return {\n subscribe(relays, filter, params) {\n return sp.subscribe(relays, filter as NostrToolsFilter, {\n onevent: params.onevent,\n oneose: params.oneose,\n });\n },\n publish(relays, event) {\n return sp.publish(relays, event as NostrToolsEvent);\n },\n };\n}\n\n/**\n * Create a Nostr-backed ContextVM transport.\n *\n * @param options - Relay set, timeouts, encryption mode, and optional injected\n * pool/keys (the injected pool + key make the transport deterministic in tests).\n * @returns A {@link CvmTransport} plus a `dispose()` to tear down subscriptions.\n */\nexport function createNostrCvmTransport(\n options: NostrCvmTransportOptions = {},\n): CvmTransport & { dispose(): void } {\n const defaultRelays = options.defaultRelays ?? [];\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const encrypt = options.encrypt ?? true;\n const wrapKind = options.ephemeralWrap === false ? KIND_GIFT_WRAP_REGULAR : KIND_GIFT_WRAP_EPHEMERAL;\n const pool: CvmRelayPool = options.pool ?? simplePoolAdapter(new SimplePool());\n const clientSecretKey = options.clientSecretKey ?? generateSecretKey();\n const clientPubkey = getPublicKey(clientSecretKey);\n const clientInfo = options.clientInfo ?? { name: 'kehto-cvm', version: '1.0.0' };\n\n const sessions = new Map<string, ServerSession>();\n const pending = new Map<string, PendingRequest>();\n const eventHandlers = new Set<EventHandler>();\n const relayRefcount = new Map<string, number>();\n const seenWraps = new Set<string>();\n let inbound: CvmSubCloser | null = null;\n let subscribedRelays = '';\n\n function resolveRelays(server: CvmServerRef): string[] {\n const relays = server.relays && server.relays.length > 0 ? server.relays : defaultRelays;\n if (relays.length === 0) throw new Error('server not found');\n return [...new Set(relays)];\n }\n\n function holdRelays(relays: string[]): void {\n for (const url of relays) relayRefcount.set(url, (relayRefcount.get(url) ?? 0) + 1);\n refreshSubscription();\n }\n\n function releaseRelays(relays: string[]): void {\n for (const url of relays) {\n const count = (relayRefcount.get(url) ?? 0) - 1;\n if (count <= 0) relayRefcount.delete(url);\n else relayRefcount.set(url, count);\n }\n refreshSubscription();\n }\n\n function refreshSubscription(): void {\n const relays = [...relayRefcount.keys()].sort();\n const key = relays.join(',');\n if (key === subscribedRelays) return;\n inbound?.close();\n subscribedRelays = key;\n inbound = relays.length === 0\n ? null\n : pool.subscribe(\n relays,\n { kinds: encrypt ? [KIND_GIFT_WRAP_REGULAR, KIND_GIFT_WRAP_EPHEMERAL] : [KIND_CVM], ['#p']: [clientPubkey] },\n { onevent: handleInbound },\n );\n }\n\n function rememberWrap(id: string): boolean {\n if (seenWraps.has(id)) return false;\n seenWraps.add(id);\n if (seenWraps.size > SEEN_WRAP_LIMIT) {\n const oldest = seenWraps.values().next().value;\n if (oldest !== undefined) seenWraps.delete(oldest);\n }\n return true;\n }\n\n function handleInbound(event: NostrEventLike): void {\n if (!rememberWrap(event.id)) return;\n let serverPubkey: string;\n let mcp: McpMessage;\n try {\n if (encrypt) {\n const conversationKey = nip44.getConversationKey(clientSecretKey, event.pubkey);\n const inner = JSON.parse(nip44.decrypt(event.content, conversationKey)) as NostrEventLike;\n serverPubkey = inner.pubkey;\n mcp = JSON.parse(inner.content) as McpMessage;\n } else {\n serverPubkey = event.pubkey;\n mcp = JSON.parse(event.content) as McpMessage;\n }\n } catch {\n return; // not addressed to us / undecryptable / malformed — ignore.\n }\n\n const id = mcp.id;\n if (id != null && pending.has(String(id))) {\n const entry = pending.get(String(id))!;\n pending.delete(String(id));\n clearTimeout(entry.timer);\n entry.resolve({ ...mcp, id: entry.originalId });\n return;\n }\n // Uncorrelated server message (notification) → fan out as a CVM event.\n if (mcp.method !== undefined && sessions.has(serverPubkey)) {\n const server: CvmServerRef = { pubkey: serverPubkey };\n for (const handler of eventHandlers) handler(server, mcp);\n }\n }\n\n function publishMcp(server: CvmServerRef, relays: string[], message: McpMessage): void {\n const inner = finalizeEvent(\n { kind: KIND_CVM, created_at: Math.floor(Date.now() / 1000), tags: [['p', server.pubkey]], content: JSON.stringify(message) },\n clientSecretKey,\n ) as NostrEventLike;\n if (!encrypt) {\n pool.publish(relays, inner);\n return;\n }\n const wrapSecretKey = generateSecretKey();\n const conversationKey = nip44.getConversationKey(wrapSecretKey, server.pubkey);\n // Ephemeral wraps (kind 21059) are not stored; relays reject backdated\n // ephemeral events as \"expired\", so they MUST carry a current timestamp.\n // Regular wraps (kind 1059) are backdated per NIP-59 to blur timing metadata.\n const createdAt =\n wrapKind === KIND_GIFT_WRAP_EPHEMERAL ? Math.floor(Date.now() / 1000) : randomizedPastTimestamp();\n const wrap = finalizeEvent(\n {\n kind: wrapKind,\n created_at: createdAt,\n tags: [['p', server.pubkey]],\n content: nip44.encrypt(JSON.stringify(inner), conversationKey),\n },\n wrapSecretKey,\n ) as NostrEventLike;\n pool.publish(relays, wrap);\n }\n\n function sendCorrelated(\n server: CvmServerRef,\n relays: string[],\n message: McpMessage,\n timeout: number,\n ): Promise<McpMessage> {\n const correlationId = nextCorrelationId();\n const originalId = message.id;\n const outgoing: McpMessage = { ...message, id: correlationId };\n return new Promise<McpMessage>((resolve, reject) => {\n const timer = setTimeout(() => {\n pending.delete(correlationId);\n reject(new Error('relay timeout'));\n }, timeout);\n pending.set(correlationId, { resolve, reject, timer, originalId });\n try {\n publishMcp(server, relays, outgoing);\n } catch (err) {\n pending.delete(correlationId);\n clearTimeout(timer);\n reject(err instanceof Error ? err : new Error('publish failed'));\n }\n });\n }\n\n function getSession(server: CvmServerRef): ServerSession {\n let session = sessions.get(server.pubkey);\n if (!session) {\n const relays = resolveRelays(server);\n session = { relays, initialized: false, initializing: null };\n sessions.set(server.pubkey, session);\n holdRelays(relays);\n }\n return session;\n }\n\n async function ensureInitialized(server: CvmServerRef, session: ServerSession, timeout: number): Promise<void> {\n if (session.initialized) return;\n if (session.initializing) return session.initializing;\n session.initializing = (async () => {\n try {\n await sendCorrelated(\n server,\n session.relays,\n {\n jsonrpc: '2.0',\n id: 'init',\n method: 'initialize',\n params: { protocolVersion: MCP_PROTOCOL_VERSION, capabilities: {}, clientInfo },\n },\n timeout,\n );\n // notifications/initialized completes the handshake; no response expected.\n publishMcp(server, session.relays, { jsonrpc: '2.0', method: 'notifications/initialized' });\n session.initialized = true;\n } catch {\n throw new Error('initialization failed');\n } finally {\n session.initializing = null;\n }\n })();\n return session.initializing;\n }\n\n return {\n async discover(query?: CvmDiscoverQuery): Promise<CvmServer[]> {\n const relays = query?.relays && query.relays.length > 0 ? query.relays : defaultRelays;\n if (relays.length === 0) return [];\n const announces = new Map<string, NostrEventLike>();\n const toolLists = new Map<string, NostrEventLike>();\n await new Promise<void>((resolve) => {\n const sub = pool.subscribe(\n relays,\n { kinds: [KIND_ANNOUNCE_SERVER, KIND_ANNOUNCE_TOOLS], limit: query?.limit ? query.limit * 4 : 100 },\n {\n onevent(event) {\n if (event.kind === KIND_ANNOUNCE_SERVER) announces.set(event.pubkey, event);\n else if (event.kind === KIND_ANNOUNCE_TOOLS) toolLists.set(event.pubkey, event);\n },\n oneose() { resolve(); },\n },\n );\n setTimeout(() => { sub.close(); resolve(); }, DEFAULT_DISCOVER_TIMEOUT_MS);\n });\n\n const servers: CvmServer[] = [];\n for (const [pubkey, event] of announces) {\n const name = tagValue(event.tags, 'name');\n const description = tagValue(event.tags, 'about');\n const server: CvmServer = {\n pubkey,\n relays: [...relays],\n ...(name ? { name } : {}),\n ...(description ? { description } : {}),\n paymentRequired: false,\n };\n const tools = toolLists.get(pubkey);\n if (tools) {\n const names = tools.tags.filter((tag) => tag[0] === 'i' && typeof tag[2] === 'string').map((tag) => tag[2]);\n if (names.length > 0) server.capabilities = names;\n }\n servers.push(server);\n }\n\n const search = query?.search?.toLowerCase();\n const filtered = search\n ? servers.filter((s) => `${s.name ?? ''} ${s.description ?? ''}`.toLowerCase().includes(search))\n : servers;\n return query?.limit ? filtered.slice(0, query.limit) : filtered;\n },\n\n async request(server: CvmServerRef, message: McpMessage, requestOptions?: CvmRequestOptions): Promise<McpMessage> {\n const session = getSession(server);\n const timeout = requestOptions?.timeoutMs ?? timeoutMs;\n if (requestOptions?.initialize) await ensureInitialized(server, session, timeout);\n return sendCorrelated(server, session.relays, message, timeout);\n },\n\n async close(server: CvmServerRef): Promise<void> {\n const session = sessions.get(server.pubkey);\n if (!session) return;\n sessions.delete(server.pubkey);\n releaseRelays(session.relays);\n },\n\n onEvent(handler: EventHandler): { close(): void } {\n eventHandlers.add(handler);\n return {\n close() {\n eventHandlers.delete(handler);\n },\n };\n },\n\n dispose(): void {\n inbound?.close();\n inbound = null;\n for (const entry of pending.values()) {\n clearTimeout(entry.timer);\n entry.reject(new Error('transport disposed'));\n }\n pending.clear();\n sessions.clear();\n relayRefcount.clear();\n eventHandlers.clear();\n subscribedRelays = '';\n },\n };\n}\n"],"mappings":";AA6BA,SAAS,kBAAkB;AAC3B,SAAS,eAAe,mBAAmB,oBAAoB;AAC/D,YAAY,WAAW;AAavB,IAAM,WAAW;AAEjB,IAAM,2BAA2B;AACjC,IAAM,yBAAyB;AAE/B,IAAM,uBAAuB;AAC7B,IAAM,sBAAsB;AAE5B,IAAM,qBAAqB;AAC3B,IAAM,8BAA8B;AACpC,IAAM,uBAAuB;AAC7B,IAAM,kBAAkB;AA0ExB,IAAI,qBAAqB;AACzB,SAAS,oBAA4B;AACnC,wBAAsB;AACtB,SAAO,OAAO,kBAAkB,IAAI,aAAa,kBAAkB,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC;AACnF;AAEA,SAAS,0BAAkC;AAEzC,QAAM,SAAS,KAAK,MAAM,KAAK,OAAO,IAAI,MAAO;AACjD,SAAO,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI,IAAI;AACzC;AAEA,SAAS,SAAS,MAAkB,MAAkC;AACpE,SAAO,KAAK,KAAK,CAAC,QAAQ,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC;AAChD;AAGA,SAAS,kBAAkB,IAA8B;AACvD,SAAO;AAAA,IACL,UAAU,QAAQ,QAAQ,QAAQ;AAChC,aAAO,GAAG,UAAU,QAAQ,QAA4B;AAAA,QACtD,SAAS,OAAO;AAAA,QAChB,QAAQ,OAAO;AAAA,MACjB,CAAC;AAAA,IACH;AAAA,IACA,QAAQ,QAAQ,OAAO;AACrB,aAAO,GAAG,QAAQ,QAAQ,KAAwB;AAAA,IACpD;AAAA,EACF;AACF;AASO,SAAS,wBACd,UAAoC,CAAC,GACD;AACpC,QAAM,gBAAgB,QAAQ,iBAAiB,CAAC;AAChD,QAAM,YAAY,QAAQ,aAAa;AACvC,QAAMA,WAAU,QAAQ,WAAW;AACnC,QAAM,WAAW,QAAQ,kBAAkB,QAAQ,yBAAyB;AAC5E,QAAM,OAAqB,QAAQ,QAAQ,kBAAkB,IAAI,WAAW,CAAC;AAC7E,QAAM,kBAAkB,QAAQ,mBAAmB,kBAAkB;AACrE,QAAM,eAAe,aAAa,eAAe;AACjD,QAAM,aAAa,QAAQ,cAAc,EAAE,MAAM,aAAa,SAAS,QAAQ;AAE/E,QAAM,WAAW,oBAAI,IAA2B;AAChD,QAAM,UAAU,oBAAI,IAA4B;AAChD,QAAM,gBAAgB,oBAAI,IAAkB;AAC5C,QAAM,gBAAgB,oBAAI,IAAoB;AAC9C,QAAM,YAAY,oBAAI,IAAY;AAClC,MAAI,UAA+B;AACnC,MAAI,mBAAmB;AAEvB,WAAS,cAAc,QAAgC;AACrD,UAAM,SAAS,OAAO,UAAU,OAAO,OAAO,SAAS,IAAI,OAAO,SAAS;AAC3E,QAAI,OAAO,WAAW,EAAG,OAAM,IAAI,MAAM,kBAAkB;AAC3D,WAAO,CAAC,GAAG,IAAI,IAAI,MAAM,CAAC;AAAA,EAC5B;AAEA,WAAS,WAAW,QAAwB;AAC1C,eAAW,OAAO,OAAQ,eAAc,IAAI,MAAM,cAAc,IAAI,GAAG,KAAK,KAAK,CAAC;AAClF,wBAAoB;AAAA,EACtB;AAEA,WAAS,cAAc,QAAwB;AAC7C,eAAW,OAAO,QAAQ;AACxB,YAAM,SAAS,cAAc,IAAI,GAAG,KAAK,KAAK;AAC9C,UAAI,SAAS,EAAG,eAAc,OAAO,GAAG;AAAA,UACnC,eAAc,IAAI,KAAK,KAAK;AAAA,IACnC;AACA,wBAAoB;AAAA,EACtB;AAEA,WAAS,sBAA4B;AACnC,UAAM,SAAS,CAAC,GAAG,cAAc,KAAK,CAAC,EAAE,KAAK;AAC9C,UAAM,MAAM,OAAO,KAAK,GAAG;AAC3B,QAAI,QAAQ,iBAAkB;AAC9B,aAAS,MAAM;AACf,uBAAmB;AACnB,cAAU,OAAO,WAAW,IACxB,OACA,KAAK;AAAA,MACH;AAAA,MACA,EAAE,OAAOA,WAAU,CAAC,wBAAwB,wBAAwB,IAAI,CAAC,QAAQ,GAAG,CAAC,IAAI,GAAG,CAAC,YAAY,EAAE;AAAA,MAC3G,EAAE,SAAS,cAAc;AAAA,IAC3B;AAAA,EACN;AAEA,WAAS,aAAa,IAAqB;AACzC,QAAI,UAAU,IAAI,EAAE,EAAG,QAAO;AAC9B,cAAU,IAAI,EAAE;AAChB,QAAI,UAAU,OAAO,iBAAiB;AACpC,YAAM,SAAS,UAAU,OAAO,EAAE,KAAK,EAAE;AACzC,UAAI,WAAW,OAAW,WAAU,OAAO,MAAM;AAAA,IACnD;AACA,WAAO;AAAA,EACT;AAEA,WAAS,cAAc,OAA6B;AAClD,QAAI,CAAC,aAAa,MAAM,EAAE,EAAG;AAC7B,QAAI;AACJ,QAAI;AACJ,QAAI;AACF,UAAIA,UAAS;AACX,cAAM,kBAAwB,yBAAmB,iBAAiB,MAAM,MAAM;AAC9E,cAAM,QAAQ,KAAK,MAAY,cAAQ,MAAM,SAAS,eAAe,CAAC;AACtE,uBAAe,MAAM;AACrB,cAAM,KAAK,MAAM,MAAM,OAAO;AAAA,MAChC,OAAO;AACL,uBAAe,MAAM;AACrB,cAAM,KAAK,MAAM,MAAM,OAAO;AAAA,MAChC;AAAA,IACF,QAAQ;AACN;AAAA,IACF;AAEA,UAAM,KAAK,IAAI;AACf,QAAI,MAAM,QAAQ,QAAQ,IAAI,OAAO,EAAE,CAAC,GAAG;AACzC,YAAM,QAAQ,QAAQ,IAAI,OAAO,EAAE,CAAC;AACpC,cAAQ,OAAO,OAAO,EAAE,CAAC;AACzB,mBAAa,MAAM,KAAK;AACxB,YAAM,QAAQ,EAAE,GAAG,KAAK,IAAI,MAAM,WAAW,CAAC;AAC9C;AAAA,IACF;AAEA,QAAI,IAAI,WAAW,UAAa,SAAS,IAAI,YAAY,GAAG;AAC1D,YAAM,SAAuB,EAAE,QAAQ,aAAa;AACpD,iBAAW,WAAW,cAAe,SAAQ,QAAQ,GAAG;AAAA,IAC1D;AAAA,EACF;AAEA,WAAS,WAAW,QAAsB,QAAkB,SAA2B;AACrF,UAAM,QAAQ;AAAA,MACZ,EAAE,MAAM,UAAU,YAAY,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI,GAAG,MAAM,CAAC,CAAC,KAAK,OAAO,MAAM,CAAC,GAAG,SAAS,KAAK,UAAU,OAAO,EAAE;AAAA,MAC5H;AAAA,IACF;AACA,QAAI,CAACA,UAAS;AACZ,WAAK,QAAQ,QAAQ,KAAK;AAC1B;AAAA,IACF;AACA,UAAM,gBAAgB,kBAAkB;AACxC,UAAM,kBAAwB,yBAAmB,eAAe,OAAO,MAAM;AAI7E,UAAM,YACJ,aAAa,2BAA2B,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI,IAAI,wBAAwB;AAClG,UAAM,OAAO;AAAA,MACX;AAAA,QACE,MAAM;AAAA,QACN,YAAY;AAAA,QACZ,MAAM,CAAC,CAAC,KAAK,OAAO,MAAM,CAAC;AAAA,QAC3B,SAAe,cAAQ,KAAK,UAAU,KAAK,GAAG,eAAe;AAAA,MAC/D;AAAA,MACA;AAAA,IACF;AACA,SAAK,QAAQ,QAAQ,IAAI;AAAA,EAC3B;AAEA,WAAS,eACP,QACA,QACA,SACA,SACqB;AACrB,UAAM,gBAAgB,kBAAkB;AACxC,UAAM,aAAa,QAAQ;AAC3B,UAAM,WAAuB,EAAE,GAAG,SAAS,IAAI,cAAc;AAC7D,WAAO,IAAI,QAAoB,CAAC,SAAS,WAAW;AAClD,YAAM,QAAQ,WAAW,MAAM;AAC7B,gBAAQ,OAAO,aAAa;AAC5B,eAAO,IAAI,MAAM,eAAe,CAAC;AAAA,MACnC,GAAG,OAAO;AACV,cAAQ,IAAI,eAAe,EAAE,SAAS,QAAQ,OAAO,WAAW,CAAC;AACjE,UAAI;AACF,mBAAW,QAAQ,QAAQ,QAAQ;AAAA,MACrC,SAAS,KAAK;AACZ,gBAAQ,OAAO,aAAa;AAC5B,qBAAa,KAAK;AAClB,eAAO,eAAe,QAAQ,MAAM,IAAI,MAAM,gBAAgB,CAAC;AAAA,MACjE;AAAA,IACF,CAAC;AAAA,EACH;AAEA,WAAS,WAAW,QAAqC;AACvD,QAAI,UAAU,SAAS,IAAI,OAAO,MAAM;AACxC,QAAI,CAAC,SAAS;AACZ,YAAM,SAAS,cAAc,MAAM;AACnC,gBAAU,EAAE,QAAQ,aAAa,OAAO,cAAc,KAAK;AAC3D,eAAS,IAAI,OAAO,QAAQ,OAAO;AACnC,iBAAW,MAAM;AAAA,IACnB;AACA,WAAO;AAAA,EACT;AAEA,iBAAe,kBAAkB,QAAsB,SAAwB,SAAgC;AAC7G,QAAI,QAAQ,YAAa;AACzB,QAAI,QAAQ,aAAc,QAAO,QAAQ;AACzC,YAAQ,gBAAgB,YAAY;AAClC,UAAI;AACF,cAAM;AAAA,UACJ;AAAA,UACA,QAAQ;AAAA,UACR;AAAA,YACE,SAAS;AAAA,YACT,IAAI;AAAA,YACJ,QAAQ;AAAA,YACR,QAAQ,EAAE,iBAAiB,sBAAsB,cAAc,CAAC,GAAG,WAAW;AAAA,UAChF;AAAA,UACA;AAAA,QACF;AAEA,mBAAW,QAAQ,QAAQ,QAAQ,EAAE,SAAS,OAAO,QAAQ,4BAA4B,CAAC;AAC1F,gBAAQ,cAAc;AAAA,MACxB,QAAQ;AACN,cAAM,IAAI,MAAM,uBAAuB;AAAA,MACzC,UAAE;AACA,gBAAQ,eAAe;AAAA,MACzB;AAAA,IACF,GAAG;AACH,WAAO,QAAQ;AAAA,EACjB;AAEA,SAAO;AAAA,IACL,MAAM,SAAS,OAAgD;AAC7D,YAAM,SAAS,OAAO,UAAU,MAAM,OAAO,SAAS,IAAI,MAAM,SAAS;AACzE,UAAI,OAAO,WAAW,EAAG,QAAO,CAAC;AACjC,YAAM,YAAY,oBAAI,IAA4B;AAClD,YAAM,YAAY,oBAAI,IAA4B;AAClD,YAAM,IAAI,QAAc,CAAC,YAAY;AACnC,cAAM,MAAM,KAAK;AAAA,UACf;AAAA,UACA,EAAE,OAAO,CAAC,sBAAsB,mBAAmB,GAAG,OAAO,OAAO,QAAQ,MAAM,QAAQ,IAAI,IAAI;AAAA,UAClG;AAAA,YACE,QAAQ,OAAO;AACb,kBAAI,MAAM,SAAS,qBAAsB,WAAU,IAAI,MAAM,QAAQ,KAAK;AAAA,uBACjE,MAAM,SAAS,oBAAqB,WAAU,IAAI,MAAM,QAAQ,KAAK;AAAA,YAChF;AAAA,YACA,SAAS;AAAE,sBAAQ;AAAA,YAAG;AAAA,UACxB;AAAA,QACF;AACA,mBAAW,MAAM;AAAE,cAAI,MAAM;AAAG,kBAAQ;AAAA,QAAG,GAAG,2BAA2B;AAAA,MAC3E,CAAC;AAED,YAAM,UAAuB,CAAC;AAC9B,iBAAW,CAAC,QAAQ,KAAK,KAAK,WAAW;AACvC,cAAM,OAAO,SAAS,MAAM,MAAM,MAAM;AACxC,cAAM,cAAc,SAAS,MAAM,MAAM,OAAO;AAChD,cAAM,SAAoB;AAAA,UACxB;AAAA,UACA,QAAQ,CAAC,GAAG,MAAM;AAAA,UAClB,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;AAAA,UACvB,GAAI,cAAc,EAAE,YAAY,IAAI,CAAC;AAAA,UACrC,iBAAiB;AAAA,QACnB;AACA,cAAM,QAAQ,UAAU,IAAI,MAAM;AAClC,YAAI,OAAO;AACT,gBAAM,QAAQ,MAAM,KAAK,OAAO,CAAC,QAAQ,IAAI,CAAC,MAAM,OAAO,OAAO,IAAI,CAAC,MAAM,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC;AAC1G,cAAI,MAAM,SAAS,EAAG,QAAO,eAAe;AAAA,QAC9C;AACA,gBAAQ,KAAK,MAAM;AAAA,MACrB;AAEA,YAAM,SAAS,OAAO,QAAQ,YAAY;AAC1C,YAAM,WAAW,SACb,QAAQ,OAAO,CAAC,MAAM,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,eAAe,EAAE,GAAG,YAAY,EAAE,SAAS,MAAM,CAAC,IAC7F;AACJ,aAAO,OAAO,QAAQ,SAAS,MAAM,GAAG,MAAM,KAAK,IAAI;AAAA,IACzD;AAAA,IAEA,MAAM,QAAQ,QAAsB,SAAqB,gBAAyD;AAChH,YAAM,UAAU,WAAW,MAAM;AACjC,YAAM,UAAU,gBAAgB,aAAa;AAC7C,UAAI,gBAAgB,WAAY,OAAM,kBAAkB,QAAQ,SAAS,OAAO;AAChF,aAAO,eAAe,QAAQ,QAAQ,QAAQ,SAAS,OAAO;AAAA,IAChE;AAAA,IAEA,MAAM,MAAM,QAAqC;AAC/C,YAAM,UAAU,SAAS,IAAI,OAAO,MAAM;AAC1C,UAAI,CAAC,QAAS;AACd,eAAS,OAAO,OAAO,MAAM;AAC7B,oBAAc,QAAQ,MAAM;AAAA,IAC9B;AAAA,IAEA,QAAQ,SAA0C;AAChD,oBAAc,IAAI,OAAO;AACzB,aAAO;AAAA,QACL,QAAQ;AACN,wBAAc,OAAO,OAAO;AAAA,QAC9B;AAAA,MACF;AAAA,IACF;AAAA,IAEA,UAAgB;AACd,eAAS,MAAM;AACf,gBAAU;AACV,iBAAW,SAAS,QAAQ,OAAO,GAAG;AACpC,qBAAa,MAAM,KAAK;AACxB,cAAM,OAAO,IAAI,MAAM,oBAAoB,CAAC;AAAA,MAC9C;AACA,cAAQ,MAAM;AACd,eAAS,MAAM;AACf,oBAAc,MAAM;AACpB,oBAAc,MAAM;AACpB,yBAAmB;AAAA,IACrB;AAAA,EACF;AACF;","names":["encrypt"]}
|
|
1
|
+
{"version":3,"sources":["../src/cvm-nostr-transport.ts"],"sourcesContent":["/**\n * cvm-nostr-transport.ts — concrete ContextVM transport for NAP-CVM.\n *\n * Implements {@link CvmTransport} over Nostr, exactly as validated against live\n * ContextVM servers (e.g. Relatr):\n *\n * - MCP JSON-RPC messages ride in kind-25910 event `content`.\n * - Requests are CEP-4 gift-wrapped: the inner kind-25910 event is signed with\n * the shell's ephemeral client key, NIP-44-encrypted to the server, and\n * placed in a kind-21059 (ephemeral) / 1059 (regular) wrap signed by a fresh\n * random key, `p`-tagged to the server. Responses arrive the same way,\n * `p`-tagged to the client, and are correlated by the inner JSON-RPC `id`.\n * - Discovery reads kind-11316 (server) + kind-11317 (tools) announcements.\n *\n * Shipped on a separate entry (`@kehto/services/cvm-nostr-transport`) so the\n * `nostr-tools` dependency stays out of the core `@kehto/services` bundle.\n *\n * The client key is ephemeral and shell-owned: napplets never see keys, relay\n * sockets, or NIP-44 material (NAP-CVM §Security).\n *\n * @example\n * ```ts\n * import { createNostrCvmTransport } from '@kehto/services/cvm-nostr-transport';\n * const transport = createNostrCvmTransport({\n * defaultRelays: ['wss://relay.contextvm.org', 'wss://relay2.contextvm.org'],\n * });\n * ```\n *\n * @module\n */\n\nimport { SimplePool } from 'nostr-tools/pool';\nimport { finalizeEvent, generateSecretKey, getPublicKey } from 'nostr-tools/pure';\nimport * as nip44 from 'nostr-tools/nip44';\nimport type { Event as NostrToolsEvent, Filter as NostrToolsFilter } from 'nostr-tools';\n\nimport type { CvmTransport } from './cvm-service.js';\nimport type {\n CvmDiscoverQuery,\n CvmRequestOptions,\n CvmServer,\n CvmServerRef,\n McpMessage,\n} from './cvm-types.js';\n\n/** ContextVM unified transport event kind. */\nconst KIND_CVM = 25910;\n/** CEP-4 gift-wrap kinds: ephemeral (CEP-19) and regular. */\nconst KIND_GIFT_WRAP_EPHEMERAL = 21059;\nconst KIND_GIFT_WRAP_REGULAR = 1059;\n/** CEP-6 announcement kinds. */\nconst KIND_ANNOUNCE_SERVER = 11316;\nconst KIND_ANNOUNCE_TOOLS = 11317;\n\nconst DEFAULT_TIMEOUT_MS = 30_000;\nconst DEFAULT_DISCOVER_TIMEOUT_MS = 6_000;\nconst MCP_PROTOCOL_VERSION = '2025-11-25';\nconst SEEN_WRAP_LIMIT = 512;\n\n/** Minimal signed Nostr event. */\nexport interface NostrEventLike {\n id: string;\n pubkey: string;\n created_at: number;\n kind: number;\n tags: string[][];\n content: string;\n sig: string;\n}\n\n/** A Nostr REQ filter (subset). */\nexport interface NostrFilterLike {\n kinds?: number[];\n authors?: string[];\n limit?: number;\n ['#p']?: string[];\n [key: string]: unknown;\n}\n\n/** Subscription handle returned by the relay pool. */\nexport interface CvmSubCloser {\n close(): void;\n}\n\n/**\n * Minimal relay-pool surface used by this transport — structurally satisfied\n * by `nostr-tools` `SimplePool`. Injectable for testing.\n */\nexport interface CvmRelayPool {\n subscribe(\n relays: string[],\n filter: NostrFilterLike,\n params: { onevent?: (event: NostrEventLike) => void; oneose?: () => void },\n ): CvmSubCloser;\n publish(relays: string[], event: NostrEventLike): unknown;\n}\n\n/** Options for {@link createNostrCvmTransport}. */\nexport interface NostrCvmTransportOptions {\n /** Relays used when a server reference carries no relay hints. */\n defaultRelays?: string[];\n /** Default per-request timeout in milliseconds. */\n timeoutMs?: number;\n /** Whether to CEP-4 gift-wrap requests. Default true (most servers require it). */\n encrypt?: boolean;\n /** Use ephemeral (kind 21059) gift wraps when encrypting. Default true. */\n ephemeralWrap?: boolean;\n /** Relay pool to use. Defaults to a fresh `nostr-tools` `SimplePool`. */\n pool?: CvmRelayPool;\n /** Client secret key (32 bytes). Defaults to a generated ephemeral key. */\n clientSecretKey?: Uint8Array;\n /** Client info advertised during MCP `initialize`. */\n clientInfo?: { name: string; version: string };\n}\n\ntype EventHandler = (server: CvmServerRef, message: McpMessage) => void;\n\ninterface PendingRequest {\n resolve(message: McpMessage): void;\n reject(error: Error): void;\n timer: ReturnType<typeof setTimeout>;\n /** The caller's original JSON-RPC id, restored on the response. */\n originalId: string | number | undefined;\n}\n\ninterface ServerSession {\n relays: string[];\n initialized: boolean;\n initializing: Promise<void> | null;\n}\n\nlet correlationCounter = 0;\nfunction nextCorrelationId(): string {\n correlationCounter += 1;\n return `cvm-${correlationCounter}-${getPublicKey(generateSecretKey()).slice(0, 8)}`;\n}\n\nfunction randomizedPastTimestamp(): number {\n // NIP-59: randomize within the past two days to reduce timing metadata.\n const jitter = Math.floor(Math.random() * 172_800);\n return Math.floor(Date.now() / 1000) - jitter;\n}\n\nfunction tagValue(tags: string[][], name: string): string | undefined {\n return tags.find((tag) => tag[0] === name)?.[1];\n}\n\n/** Adapt a `nostr-tools` SimplePool to the {@link CvmRelayPool} surface. */\nfunction simplePoolAdapter(sp: SimplePool): CvmRelayPool {\n return {\n subscribe(relays, filter, params) {\n return sp.subscribe(relays, filter as NostrToolsFilter, {\n onevent: params.onevent,\n oneose: params.oneose,\n });\n },\n publish(relays, event) {\n return sp.publish(relays, event as NostrToolsEvent);\n },\n };\n}\n\n/**\n * Create a Nostr-backed ContextVM transport.\n *\n * @param options - Relay set, timeouts, encryption mode, and optional injected\n * pool/keys (the injected pool + key make the transport deterministic in tests).\n * @returns A {@link CvmTransport} plus a `dispose()` to tear down subscriptions.\n */\nexport function createNostrCvmTransport(\n options: NostrCvmTransportOptions = {},\n): CvmTransport & { dispose(): void } {\n const defaultRelays = options.defaultRelays ?? [];\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const encrypt = options.encrypt ?? true;\n const wrapKind = options.ephemeralWrap === false ? KIND_GIFT_WRAP_REGULAR : KIND_GIFT_WRAP_EPHEMERAL;\n const pool: CvmRelayPool = options.pool ?? simplePoolAdapter(new SimplePool());\n const clientSecretKey = options.clientSecretKey ?? generateSecretKey();\n const clientPubkey = getPublicKey(clientSecretKey);\n const clientInfo = options.clientInfo ?? { name: 'kehto-cvm', version: '1.0.0' };\n\n const sessions = new Map<string, ServerSession>();\n const pending = new Map<string, PendingRequest>();\n const eventHandlers = new Set<EventHandler>();\n const relayRefcount = new Map<string, number>();\n const seenWraps = new Set<string>();\n let inbound: CvmSubCloser | null = null;\n let subscribedRelays = '';\n\n function resolveRelays(server: CvmServerRef): string[] {\n const relays = server.relays && server.relays.length > 0 ? server.relays : defaultRelays;\n if (relays.length === 0) throw new Error('server not found');\n return [...new Set(relays)];\n }\n\n function holdRelays(relays: string[]): void {\n for (const url of relays) relayRefcount.set(url, (relayRefcount.get(url) ?? 0) + 1);\n refreshSubscription();\n }\n\n function releaseRelays(relays: string[]): void {\n for (const url of relays) {\n const count = (relayRefcount.get(url) ?? 0) - 1;\n if (count <= 0) relayRefcount.delete(url);\n else relayRefcount.set(url, count);\n }\n refreshSubscription();\n }\n\n function refreshSubscription(): void {\n const relays = [...relayRefcount.keys()].sort();\n const key = relays.join(',');\n if (key === subscribedRelays) return;\n inbound?.close();\n subscribedRelays = key;\n inbound = relays.length === 0\n ? null\n : pool.subscribe(\n relays,\n { kinds: encrypt ? [KIND_GIFT_WRAP_REGULAR, KIND_GIFT_WRAP_EPHEMERAL] : [KIND_CVM], ['#p']: [clientPubkey] },\n { onevent: handleInbound },\n );\n }\n\n function rememberWrap(id: string): boolean {\n if (seenWraps.has(id)) return false;\n seenWraps.add(id);\n if (seenWraps.size > SEEN_WRAP_LIMIT) {\n const oldest = seenWraps.values().next().value;\n if (oldest !== undefined) seenWraps.delete(oldest);\n }\n return true;\n }\n\n function handleInbound(event: NostrEventLike): void {\n if (!rememberWrap(event.id)) return;\n let serverPubkey: string;\n let mcp: McpMessage;\n try {\n if (encrypt) {\n const conversationKey = nip44.getConversationKey(clientSecretKey, event.pubkey);\n const inner = JSON.parse(nip44.decrypt(event.content, conversationKey)) as NostrEventLike;\n serverPubkey = inner.pubkey;\n mcp = JSON.parse(inner.content) as McpMessage;\n } else {\n serverPubkey = event.pubkey;\n mcp = JSON.parse(event.content) as McpMessage;\n }\n } catch {\n return; // not addressed to us / undecryptable / malformed — ignore.\n }\n\n const id = mcp.id;\n if (id != null && pending.has(String(id))) {\n const entry = pending.get(String(id))!;\n pending.delete(String(id));\n clearTimeout(entry.timer);\n entry.resolve({ ...mcp, id: entry.originalId });\n return;\n }\n // Uncorrelated server message (notification) → fan out as a CVM event.\n if (mcp.method !== undefined && sessions.has(serverPubkey)) {\n const server: CvmServerRef = { pubkey: serverPubkey };\n for (const handler of eventHandlers) handler(server, mcp);\n }\n }\n\n function publishMcp(server: CvmServerRef, relays: string[], message: McpMessage): void {\n const inner = finalizeEvent(\n { kind: KIND_CVM, created_at: Math.floor(Date.now() / 1000), tags: [['p', server.pubkey]], content: JSON.stringify(message) },\n clientSecretKey,\n ) as NostrEventLike;\n if (!encrypt) {\n pool.publish(relays, inner);\n return;\n }\n const wrapSecretKey = generateSecretKey();\n const conversationKey = nip44.getConversationKey(wrapSecretKey, server.pubkey);\n // Ephemeral wraps (kind 21059) are not stored; relays reject backdated\n // ephemeral events as \"expired\", so they MUST carry a current timestamp.\n // Regular wraps (kind 1059) are backdated per NIP-59 to blur timing metadata.\n const createdAt =\n wrapKind === KIND_GIFT_WRAP_EPHEMERAL ? Math.floor(Date.now() / 1000) : randomizedPastTimestamp();\n const wrap = finalizeEvent(\n {\n kind: wrapKind,\n created_at: createdAt,\n tags: [['p', server.pubkey]],\n content: nip44.encrypt(JSON.stringify(inner), conversationKey),\n },\n wrapSecretKey,\n ) as NostrEventLike;\n pool.publish(relays, wrap);\n }\n\n function sendCorrelated(\n server: CvmServerRef,\n relays: string[],\n message: McpMessage,\n timeout: number,\n ): Promise<McpMessage> {\n const correlationId = nextCorrelationId();\n const originalId = message.id;\n const outgoing: McpMessage = { ...message, id: correlationId };\n return new Promise<McpMessage>((resolve, reject) => {\n const timer = setTimeout(() => {\n pending.delete(correlationId);\n reject(new Error('relay timeout'));\n }, timeout);\n pending.set(correlationId, { resolve, reject, timer, originalId });\n try {\n publishMcp(server, relays, outgoing);\n } catch (err) {\n pending.delete(correlationId);\n clearTimeout(timer);\n reject(err instanceof Error ? err : new Error('publish failed'));\n }\n });\n }\n\n function getSession(server: CvmServerRef): ServerSession {\n let session = sessions.get(server.pubkey);\n if (!session) {\n const relays = resolveRelays(server);\n session = { relays, initialized: false, initializing: null };\n sessions.set(server.pubkey, session);\n holdRelays(relays);\n }\n return session;\n }\n\n async function ensureInitialized(server: CvmServerRef, session: ServerSession, timeout: number): Promise<void> {\n if (session.initialized) return;\n if (session.initializing) return session.initializing;\n session.initializing = (async () => {\n try {\n await sendCorrelated(\n server,\n session.relays,\n {\n jsonrpc: '2.0',\n id: 'init',\n method: 'initialize',\n params: { protocolVersion: MCP_PROTOCOL_VERSION, capabilities: {}, clientInfo },\n },\n timeout,\n );\n // notifications/initialized completes the handshake; no response expected.\n publishMcp(server, session.relays, { jsonrpc: '2.0', method: 'notifications/initialized' });\n session.initialized = true;\n } catch {\n throw new Error('initialization failed');\n } finally {\n session.initializing = null;\n }\n })();\n return session.initializing;\n }\n\n return {\n async discover(query?: CvmDiscoverQuery): Promise<CvmServer[]> {\n const relays = query?.relays && query.relays.length > 0 ? query.relays : defaultRelays;\n if (relays.length === 0) return [];\n const announces = new Map<string, NostrEventLike>();\n const toolLists = new Map<string, NostrEventLike>();\n await new Promise<void>((resolve) => {\n const sub = pool.subscribe(\n relays,\n { kinds: [KIND_ANNOUNCE_SERVER, KIND_ANNOUNCE_TOOLS], limit: query?.limit ? query.limit * 4 : 100 },\n {\n onevent(event) {\n if (event.kind === KIND_ANNOUNCE_SERVER) announces.set(event.pubkey, event);\n else if (event.kind === KIND_ANNOUNCE_TOOLS) toolLists.set(event.pubkey, event);\n },\n oneose() { resolve(); },\n },\n );\n setTimeout(() => { sub.close(); resolve(); }, DEFAULT_DISCOVER_TIMEOUT_MS);\n });\n\n const servers: CvmServer[] = [];\n for (const [pubkey, event] of announces) {\n const name = tagValue(event.tags, 'name');\n const description = tagValue(event.tags, 'about');\n const server: CvmServer = {\n pubkey,\n relays: [...relays],\n ...(name ? { name } : {}),\n ...(description ? { description } : {}),\n paymentRequired: false,\n };\n const tools = toolLists.get(pubkey);\n if (tools) {\n const names = tools.tags.filter((tag) => tag[0] === 'i' && typeof tag[2] === 'string').map((tag) => tag[2]);\n if (names.length > 0) server.capabilities = names;\n }\n servers.push(server);\n }\n\n const search = query?.search?.toLowerCase();\n const filtered = search\n ? servers.filter((s) => `${s.name ?? ''} ${s.description ?? ''}`.toLowerCase().includes(search))\n : servers;\n return query?.limit ? filtered.slice(0, query.limit) : filtered;\n },\n\n async request(server: CvmServerRef, message: McpMessage, requestOptions?: CvmRequestOptions): Promise<McpMessage> {\n const session = getSession(server);\n const timeout = requestOptions?.timeoutMs ?? timeoutMs;\n if (requestOptions?.initialize) await ensureInitialized(server, session, timeout);\n return sendCorrelated(server, session.relays, message, timeout);\n },\n\n async close(server: CvmServerRef): Promise<void> {\n const session = sessions.get(server.pubkey);\n if (!session) return;\n sessions.delete(server.pubkey);\n releaseRelays(session.relays);\n },\n\n onEvent(handler: EventHandler): { close(): void } {\n eventHandlers.add(handler);\n return {\n close() {\n eventHandlers.delete(handler);\n },\n };\n },\n\n dispose(): void {\n inbound?.close();\n inbound = null;\n for (const entry of pending.values()) {\n clearTimeout(entry.timer);\n entry.reject(new Error('transport disposed'));\n }\n pending.clear();\n sessions.clear();\n relayRefcount.clear();\n eventHandlers.clear();\n subscribedRelays = '';\n },\n };\n}\n"],"mappings":"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","names":["encrypt"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -577,29 +577,30 @@ declare function createCoordinatedRelay(options: CoordinatedRelayOptions): Servi
|
|
|
577
577
|
*
|
|
578
578
|
* Handles the 3 napplet -> shell request types from @napplet/nap/keys:
|
|
579
579
|
* - keys.forward -> invokes options.onForward (hotkey passthrough, fire-and-forget)
|
|
580
|
-
* - keys.registerAction -> parses action.defaultKey into a chord
|
|
581
|
-
*
|
|
582
|
-
* tracks windowId ownership so onWindowDestroyed
|
|
583
|
-
* unsubscribe,
|
|
584
|
-
*
|
|
580
|
+
* - keys.registerAction -> parses action.defaultKey into a normalized chord binding,
|
|
581
|
+
* stores bound subscriptions in an in-memory registry keyed
|
|
582
|
+
* by actionId, tracks windowId ownership so onWindowDestroyed
|
|
583
|
+
* can auto-unsubscribe, echoes { actionId, binding } as .result
|
|
584
|
+
* when a binding is assigned, and pushes the window's complete
|
|
585
|
+
* keys.bindings list
|
|
586
|
+
* - keys.unregisterAction -> removes the subscription and pushes the window's
|
|
587
|
+
* updated keys.bindings list
|
|
585
588
|
*
|
|
586
589
|
* Real listener: on service construction the handler attaches a single
|
|
587
590
|
* `keydown` listener to `options.listenerTarget` (default: `document`). Each
|
|
588
591
|
* keydown is matched against the chord-subscription registry; matches invoke
|
|
589
|
-
* `options.onForward` with the DOM-shape payload
|
|
592
|
+
* `options.onForward` with the DOM-shape payload and push a canonical
|
|
590
593
|
* `keys.action` envelope back to the owning napplet via the per-window `send`
|
|
591
594
|
* callback captured at `keys.registerAction` time. Subscriptions persist
|
|
592
595
|
* across messages; `onWindowDestroyed(windowId)` drops all subscriptions owned
|
|
593
596
|
* by the destroyed window as well as its cached `send` handle.
|
|
594
597
|
*
|
|
595
|
-
* On each document keydown matching a registered action, the service
|
|
596
|
-
*
|
|
597
|
-
*
|
|
598
|
-
*
|
|
599
|
-
*
|
|
600
|
-
*
|
|
601
|
-
* base shape unchanged, downstream SDKs that only read `{ type, actionId }`
|
|
602
|
-
* ignore `chord` silently).
|
|
598
|
+
* On each document keydown matching a registered action, the service emits a
|
|
599
|
+
* `keys.action` envelope to the action's owning napplet via the per-window
|
|
600
|
+
* `send` callback — this is the canonical @napplet/nap/keys surface the SDK's
|
|
601
|
+
* `keys.onAction(...)` helper consumes. Forwarded keydowns do not trigger
|
|
602
|
+
* `keys.action`; the active napplet suppresses bound keys locally from the
|
|
603
|
+
* `keys.bindings` list before forwarding.
|
|
603
604
|
*
|
|
604
605
|
* Field-name translation: @napplet/nap/keys uses the compact
|
|
605
606
|
* { ctrl, alt, shift, meta } form on the wire; the shell's HotkeyHooks
|
|
@@ -607,9 +608,9 @@ declare function createCoordinatedRelay(options: CoordinatedRelayOptions): Servi
|
|
|
607
608
|
* { ctrlKey, altKey, shiftKey, metaKey } form. This service performs the
|
|
608
609
|
* translation so callers of `onForward` see the DOM shape.
|
|
609
610
|
*
|
|
610
|
-
* Shell -> napplet push envelopes `keys.bindings`
|
|
611
|
-
*
|
|
612
|
-
*
|
|
611
|
+
* Shell -> napplet push envelopes `keys.bindings` are emitted here whenever
|
|
612
|
+
* the registered bindings for a window change. The injected shim consumes
|
|
613
|
+
* that complete list to suppress locally-bound keydowns before forwarding.
|
|
613
614
|
*/
|
|
614
615
|
|
|
615
616
|
/**
|
|
@@ -708,8 +709,8 @@ interface HostKeysBridge {
|
|
|
708
709
|
*/
|
|
709
710
|
interface KeysServiceOptions {
|
|
710
711
|
/**
|
|
711
|
-
* Called on `keys.forward` (napplet-forwarded chord)
|
|
712
|
-
*
|
|
712
|
+
* Called on `keys.forward` (napplet-forwarded chord) and on document keydown
|
|
713
|
+
* handled by the reference listener. Receives the DOM-style field names
|
|
713
714
|
* (ctrlKey/altKey/shiftKey/metaKey) to match the shell's HotkeyHooks
|
|
714
715
|
* contract. The service translates from the wire shape
|
|
715
716
|
* ({ ctrl, alt, shift, meta }) before invoking this callback.
|
|
@@ -727,7 +728,7 @@ interface KeysServiceOptions {
|
|
|
727
728
|
* global `document` when running in a DOM environment, else an isolated
|
|
728
729
|
* `new EventTarget()` (SSR / Node-test safe). Passing a fresh
|
|
729
730
|
* `new EventTarget()` is useful for unit tests. Mirrors the pattern used
|
|
730
|
-
* by
|
|
731
|
+
* by the shell and service test harnesses.
|
|
731
732
|
*
|
|
732
733
|
* Ignored when `hostBridge` is provided — the bridge owns subscription
|
|
733
734
|
* lifecycle and no document listener is attached.
|
|
@@ -743,13 +744,9 @@ interface KeysServiceOptions {
|
|
|
743
744
|
hostBridge?: HostKeysBridge;
|
|
744
745
|
/**
|
|
745
746
|
* Optional set of shell-reserved chords. Strings in the `@napplet/nap/keys`
|
|
746
|
-
* wire format (e.g. `'Ctrl+Shift+K'`, `'Cmd+P'`).
|
|
747
|
-
*
|
|
748
|
-
*
|
|
749
|
-
* (or the `hostBridge`-registered handler) but suppresses the
|
|
750
|
-
* `keys.action` push to any napplet that registered the same chord via
|
|
751
|
-
* `keys.registerAction`. Precedence: reserved > registered. The shell
|
|
752
|
-
* WANTS the forward — that is why it reserved the chord.
|
|
747
|
+
* wire format (e.g. `'Ctrl+Shift+K'`, `'Cmd+P'`). Reserved chords are not
|
|
748
|
+
* assigned as napplet action bindings; document keydowns that match them
|
|
749
|
+
* invoke `onForward` but do not push `keys.action`.
|
|
753
750
|
*
|
|
754
751
|
* Normalized once at service construction via the same chord parser used
|
|
755
752
|
* for `action.defaultKey` — `'Ctrl+K'` / `'Control+k'` / `'ctrl+K'` all
|
|
@@ -771,7 +768,7 @@ interface KeysServiceOptions {
|
|
|
771
768
|
*
|
|
772
769
|
* Attaches a single `keydown` listener to `options.listenerTarget`
|
|
773
770
|
* (default `document`). Matching chord subscriptions invoke `options.onForward`
|
|
774
|
-
* with a DOM-shape payload
|
|
771
|
+
* with a DOM-shape payload and push a `keys.action` envelope back to the
|
|
775
772
|
* owning napplet via the per-window `send` callback captured at
|
|
776
773
|
* `keys.registerAction` time. Returns a `ServiceHandler` augmented with a
|
|
777
774
|
* `destroy()` method that detaches the listener and clears all registries.
|
|
@@ -1678,10 +1675,12 @@ interface OutboxSubscribeOptions extends OutboxQueryOptions {
|
|
|
1678
1675
|
}
|
|
1679
1676
|
/** Options for an outbox publish. */
|
|
1680
1677
|
interface OutboxPublishOptions {
|
|
1681
|
-
/**
|
|
1678
|
+
/** Explicit relay URL fanout candidates, subject to shell validation. */
|
|
1682
1679
|
relays?: string[];
|
|
1683
|
-
/**
|
|
1684
|
-
|
|
1680
|
+
/** Include the shell user's NIP-65 write relays. Defaults to true. */
|
|
1681
|
+
toOutbox?: boolean;
|
|
1682
|
+
/** Recipient pubkeys whose NIP-65 read relays are required fanout targets. */
|
|
1683
|
+
toInboxes?: string[];
|
|
1685
1684
|
}
|
|
1686
1685
|
/** A read/write target for relay-plan resolution. */
|
|
1687
1686
|
interface OutboxTarget {
|
|
@@ -1739,6 +1738,18 @@ interface OutboxSubscriptionSink {
|
|
|
1739
1738
|
/** Signal that the subscription was closed upstream; `reason` is optional. */
|
|
1740
1739
|
closed(reason?: string): void;
|
|
1741
1740
|
}
|
|
1741
|
+
/** Sink a bounded outbox query streams verified events through as they arrive. */
|
|
1742
|
+
interface OutboxQueryStreamSink {
|
|
1743
|
+
/** Deliver one deduplicated, signature-validated event result. */
|
|
1744
|
+
event(result: RelayEventResult): void;
|
|
1745
|
+
}
|
|
1746
|
+
/** Handle for a bounded streaming query and its compatibility aggregation. */
|
|
1747
|
+
interface OutboxQueryStream {
|
|
1748
|
+
/** Aggregated result, settled on all-relay EOSE or the overall deadline. */
|
|
1749
|
+
readonly result: Promise<OutboxResult>;
|
|
1750
|
+
/** Stop relay reads and settle {@link result} as incomplete. */
|
|
1751
|
+
close(): void;
|
|
1752
|
+
}
|
|
1742
1753
|
/** Handle to a router-owned subscription. */
|
|
1743
1754
|
interface OutboxRouterSubscription {
|
|
1744
1755
|
/** Stop the subscription and release its relay connections. */
|
|
@@ -1761,6 +1772,16 @@ interface OutboxRouter {
|
|
|
1761
1772
|
/** Return the relay plan the shell would use for a read/write target. */
|
|
1762
1773
|
resolveRelays(target: OutboxTarget): Promise<OutboxRelayPlan>;
|
|
1763
1774
|
}
|
|
1775
|
+
/** Outbox router with an incremental surface for bounded queries. */
|
|
1776
|
+
interface StreamingOutboxRouter extends OutboxRouter {
|
|
1777
|
+
/**
|
|
1778
|
+
* Start policy/fallback reads immediately, stream verified events through
|
|
1779
|
+
* `sink`, and attach author relays when concurrent discovery resolves.
|
|
1780
|
+
* `options.limit` applies to the final aggregate; the sink receives every
|
|
1781
|
+
* verified unique arrival until EOSE, close, or the overall deadline.
|
|
1782
|
+
*/
|
|
1783
|
+
queryStream(filters: NostrFilter[], options: OutboxQueryOptions | undefined, sink: OutboxQueryStreamSink): OutboxQueryStream;
|
|
1784
|
+
}
|
|
1764
1785
|
/** Options for {@link createOutboxService}. */
|
|
1765
1786
|
interface OutboxServiceOptions {
|
|
1766
1787
|
/** The outbox router the shell uses to reach relays. Required. */
|
|
@@ -1776,7 +1797,52 @@ interface OutboxServiceOptions {
|
|
|
1776
1797
|
declare function createOutboxService(options: OutboxServiceOptions): ServiceHandler;
|
|
1777
1798
|
|
|
1778
1799
|
/**
|
|
1779
|
-
*
|
|
1800
|
+
* Relay-pool contracts for the concrete outbox router.
|
|
1801
|
+
*
|
|
1802
|
+
* @module
|
|
1803
|
+
*/
|
|
1804
|
+
|
|
1805
|
+
/** A NIP-65 relay list for a single pubkey. */
|
|
1806
|
+
interface RelayListEntry {
|
|
1807
|
+
/** Relays the author reads from (their inbox). */
|
|
1808
|
+
read: string[];
|
|
1809
|
+
/** Relays the author writes to (where their events land). */
|
|
1810
|
+
write: string[];
|
|
1811
|
+
}
|
|
1812
|
+
/**
|
|
1813
|
+
* Relay pool contract the router drives. Implementors adapt their pool library
|
|
1814
|
+
* while the router retains relay selection and event attribution.
|
|
1815
|
+
*/
|
|
1816
|
+
interface OutboxRelayPool {
|
|
1817
|
+
/** Subscribe to `filters` on exactly `relayUrls`. */
|
|
1818
|
+
subscribe(filters: NostrFilter[], relayUrls: string[], callback: (item: NostrEvent | 'EOSE') => void): {
|
|
1819
|
+
unsubscribe(): void;
|
|
1820
|
+
};
|
|
1821
|
+
/** Publish `event` to `relayUrls`, optionally returning per-relay outcomes. */
|
|
1822
|
+
publish(event: NostrEvent, relayUrls: string[]): Promise<Record<string, boolean>> | Record<string, boolean> | void;
|
|
1823
|
+
/** Whether the relay pool can handle requests. */
|
|
1824
|
+
isAvailable(): boolean;
|
|
1825
|
+
}
|
|
1826
|
+
/** Options for the relay-pool-backed outbox router. */
|
|
1827
|
+
interface RelayPoolOutboxRouterOptions {
|
|
1828
|
+
/** Relay pool the router subscribes and publishes through. */
|
|
1829
|
+
relayPool: OutboxRelayPool;
|
|
1830
|
+
/** Resolve NIP-65 relay lists for the requested pubkeys. */
|
|
1831
|
+
loadRelayLists(pubkeys: string[]): Promise<Map<string, RelayListEntry>> | Map<string, RelayListEntry>;
|
|
1832
|
+
/** Relays used when hints and NIP-65 data are unavailable. */
|
|
1833
|
+
fallbackRelays: string[];
|
|
1834
|
+
/** Sign a template before publish. Omission denies publishing. */
|
|
1835
|
+
signEvent?(template: EventTemplate): Promise<NostrEvent>;
|
|
1836
|
+
/** Validate an event before delivery. Host pools may pre-verify. */
|
|
1837
|
+
verifyEvent?(event: NostrEvent): Promise<boolean> | boolean;
|
|
1838
|
+
/** Gate every relay URL, including caller hints. */
|
|
1839
|
+
isRelayAllowed?(url: string): boolean;
|
|
1840
|
+
/** Default bounded-query and initial-discovery timeout in milliseconds. */
|
|
1841
|
+
defaultTimeoutMs?: number;
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
/**
|
|
1845
|
+
* relay-pool-outbox-router.ts — concrete {@link StreamingOutboxRouter} backed by a relay pool.
|
|
1780
1846
|
*
|
|
1781
1847
|
* Implements the outbox-model routing that NAP-OUTBOX centralizes so napplets
|
|
1782
1848
|
* don't each reinvent it: derive authors, resolve their NIP-65 relays, fan a
|
|
@@ -1812,74 +1878,16 @@ declare function createOutboxService(options: OutboxServiceOptions): ServiceHand
|
|
|
1812
1878
|
* @packageDocumentation
|
|
1813
1879
|
*/
|
|
1814
1880
|
|
|
1815
|
-
/** A NIP-65 relay list for a single pubkey. */
|
|
1816
|
-
interface RelayListEntry {
|
|
1817
|
-
/** Relays the author reads from (their inbox). */
|
|
1818
|
-
read: string[];
|
|
1819
|
-
/** Relays the author writes to (where their events land). */
|
|
1820
|
-
write: string[];
|
|
1821
|
-
}
|
|
1822
|
-
/**
|
|
1823
|
-
* Relay pool contract the router drives. Implementors adapt their pool library
|
|
1824
|
-
* (nostr-tools SimplePool, applesauce-relay, etc.). Unlike the lower-level
|
|
1825
|
-
* relay NAP pool, both methods take an explicit relay-URL set so the router
|
|
1826
|
-
* controls outbox routing and can attribute events to the relay they arrived on.
|
|
1827
|
-
*/
|
|
1828
|
-
interface OutboxRelayPool {
|
|
1829
|
-
/**
|
|
1830
|
-
* Subscribe to `filters` on exactly `relayUrls`. The callback receives each
|
|
1831
|
-
* matching event or the literal `'EOSE'` once stored events are exhausted.
|
|
1832
|
-
* Returns a handle to cancel the subscription.
|
|
1833
|
-
*/
|
|
1834
|
-
subscribe(filters: NostrFilter[], relayUrls: string[], callback: (item: NostrEvent | 'EOSE') => void): {
|
|
1835
|
-
unsubscribe(): void;
|
|
1836
|
-
};
|
|
1837
|
-
/**
|
|
1838
|
-
* Publish `event` to `relayUrls`. May return a per-relay success map; a
|
|
1839
|
-
* `void`/missing return is treated as optimistic success on every target.
|
|
1840
|
-
*/
|
|
1841
|
-
publish(event: NostrEvent, relayUrls: string[]): Promise<Record<string, boolean>> | Record<string, boolean> | void;
|
|
1842
|
-
/** Whether the relay pool is connected and able to handle requests. */
|
|
1843
|
-
isAvailable(): boolean;
|
|
1844
|
-
}
|
|
1845
|
-
/** Options for {@link createRelayPoolOutboxRouter}. */
|
|
1846
|
-
interface RelayPoolOutboxRouterOptions {
|
|
1847
|
-
/** Relay pool the router subscribes/publishes through. Required. */
|
|
1848
|
-
relayPool: OutboxRelayPool;
|
|
1849
|
-
/**
|
|
1850
|
-
* Resolve NIP-65 relay lists for a set of pubkeys. Pubkeys with no known
|
|
1851
|
-
* list are simply omitted from the returned map (they become `missingAuthors`).
|
|
1852
|
-
*/
|
|
1853
|
-
loadRelayLists(pubkeys: string[]): Promise<Map<string, RelayListEntry>> | Map<string, RelayListEntry>;
|
|
1854
|
-
/** Relays to fall back to when NIP-65 data is absent, stale, or empty. Required. */
|
|
1855
|
-
fallbackRelays: string[];
|
|
1856
|
-
/**
|
|
1857
|
-
* Sign a template before publish (shell-mediated; napplets never sign). When
|
|
1858
|
-
* omitted, `publish` resolves with `{ ok: false, error: 'publish denied' }`.
|
|
1859
|
-
*/
|
|
1860
|
-
signEvent?(template: EventTemplate): Promise<NostrEvent>;
|
|
1861
|
-
/**
|
|
1862
|
-
* Validate an event signature before delivering it to a napplet. May be sync
|
|
1863
|
-
* or async. Defaults to accepting every event (host pools often pre-verify).
|
|
1864
|
-
*/
|
|
1865
|
-
verifyEvent?(event: NostrEvent): Promise<boolean> | boolean;
|
|
1866
|
-
/**
|
|
1867
|
-
* Gate relay URLs (e.g. block private-network hosts). Defaults to allowing
|
|
1868
|
-
* only `ws://` / `wss://` URLs — `options.relays` hints pass through this too.
|
|
1869
|
-
*/
|
|
1870
|
-
isRelayAllowed?(url: string): boolean;
|
|
1871
|
-
/** Default query timeout when `options.timeoutMs` is unset. Default 4000ms. */
|
|
1872
|
-
defaultTimeoutMs?: number;
|
|
1873
|
-
}
|
|
1874
1881
|
/**
|
|
1875
|
-
* Create a relay-pool-backed {@link
|
|
1882
|
+
* Create a relay-pool-backed {@link StreamingOutboxRouter}.
|
|
1876
1883
|
*
|
|
1877
1884
|
* @param options - Relay pool, NIP-65 loader, fallback relays, and optional
|
|
1878
1885
|
* signer / verifier / relay gate / timeout.
|
|
1879
|
-
* @returns
|
|
1886
|
+
* @returns A {@link StreamingOutboxRouter} for direct incremental reads and
|
|
1887
|
+
* compatibility use through {@link createOutboxService}.
|
|
1880
1888
|
* @throws If `relayPool`, `loadRelayLists`, or `fallbackRelays` are missing.
|
|
1881
1889
|
*/
|
|
1882
|
-
declare function createRelayPoolOutboxRouter(options: RelayPoolOutboxRouterOptions):
|
|
1890
|
+
declare function createRelayPoolOutboxRouter(options: RelayPoolOutboxRouterOptions): StreamingOutboxRouter;
|
|
1883
1891
|
|
|
1884
1892
|
/**
|
|
1885
1893
|
* upload-service.ts — NAP-UPLOAD (shell-mediated file/blob upload) reference service.
|
|
@@ -3001,4 +3009,4 @@ declare function createCordnRelayCoordinatorClient(options: CordnRelayCoordinato
|
|
|
3001
3009
|
/** Create a Cordn/ContextVM-backed NAP-DM adapter. */
|
|
3002
3010
|
declare function createCordnDmAdapter(options: CordnDmAdapterOptions): DmAdapter;
|
|
3003
3011
|
|
|
3004
|
-
export { type AudioServiceOptions, type AudioSource, type BleServiceContext, type BleServiceOptions, type CacheServiceOptions, type CatalogIntentResolver, type CatalogIntentResolverOptions, type CommonServiceContext, type CommonServiceOptions, type ConfigSchemaValidation, type ConfigService, type ConfigServiceOptions, type CoordinatedRelayOptions, type CordnCodecResult, type CordnCoordinatorClient, type CordnCoordinatorSubscription, type CordnDmAdapterOptions, type CordnDmClient, type CordnGroupMessage, type CordnRelayCoordinatorOptions, type CountRequest, type CountResult, type CountServiceOptions, type DmAdapter, type DmConversation, type DmConversationPage, type DmConversationQuery, type DmHexPubkey, DmMemoryStore, type DmMessage, type DmMessagePage, type DmMessageQuery, type DmMessageStatus, type DmOk, type DmPeer, type DmRelayPool, type DmSendRequest, type DmSendResult, type DmService, type DmServiceOptions, type DmStatus, type DmSubscribeRequest, type DmSubscription, type DmTimestamp, type HostCacheBridge, type HostKeyEvent, type HostKeysBridge, type HostMediaBridge, type HttpUploaderOptions, type HttpUploaderRails, type IdentityServiceOptions, type IntentArchetypeSupport, type IntentAvailability, type IntentBehavior, type IntentCandidate, type IntentCatalogEntry, type IntentHandlerPreference, type IntentOpenParams, type IntentRequest, type IntentResolver, type IntentResolverContext, type IntentResult, type IntentServiceOptions, type IntentWindowController, type KeysServiceOptions, type LinkOpenContext, type LinkServiceOptions, type ListsServiceContext, type ListsServiceOptions, type ManifestArchetypeInput, type MaybePromise, type MediaMetadataLike, type MediaPlaybackOwner, type MediaServiceOptions, type MediaSessionCreateOptions, type MediaSessionTarget, type MediaSourceRef, type NdrDmAdapterOptions, type NdrRelayTransport, type NdrRelayTransportOptions, type NdrRumorLike, type NdrRuntimeLike, type Nip17DmAdapterOptions, type NostrTag, type Notification, type NotificationServiceOptions, type NotifyServiceOptions, type OutboxEventOptions, type OutboxEventResult, type OutboxPublishOptions, type OutboxPublishResult, type OutboxQueryOptions, type OutboxRelayPlan, type OutboxRelayPool, type OutboxResult, type OutboxRouter, type OutboxRouterSubscription, type OutboxServiceOptions, type OutboxSubscribeOptions, type OutboxSubscriptionSink, type OutboxTarget, type RailServerConfig, type RelayListEntry, type RelayPoolOutboxRouterOptions, type RelayPoolServiceOptions, type ResourceInfo, type ResourceInfoContext, type ResourceInfoProvider, type ResourceSchemeInfo, type ResourceService, type ResourceServiceOptions, type SerialServiceContext, type SerialServiceOptions, type SignEvent, type ThemeService, type ThemeServiceOptions, type UploadDimensions, type UploadInfo, type UploadInfoContext, type UploadInfoProvider, type UploadRail, type UploadRailInfo, type UploadRequest, type UploadResult, type UploadServiceOptions, type UploadState, type UploadStatus, type Uploader, type UploaderContext, type WebrtcServiceContext, type WebrtcServiceOptions, createAudioService, createBleService, createBrowserMediaBridge, createCacheService, createCatalogIntentResolver, createCommonService, createConfigService, createCoordinatedRelay, createCordnDmAdapter, createCordnRelayCoordinatorClient, createCountService, createDmService, createHttpUploader, createIdentityService, createIntentService, createKeysService, createLinkService, createListsService, createMediaService, createNdrDmAdapter, createNdrRelayTransport, createNip17DmAdapter, createNotificationService, createNotifyService, createOutboxService, createRelayPoolOutboxRouter, createRelayPoolService, createResourceService, createSerialService, createThemeService, createUploadService, createWebrtcService, manifestToIntentCatalogEntry };
|
|
3012
|
+
export { type AudioServiceOptions, type AudioSource, type BleServiceContext, type BleServiceOptions, type CacheServiceOptions, type CatalogIntentResolver, type CatalogIntentResolverOptions, type CommonServiceContext, type CommonServiceOptions, type ConfigSchemaValidation, type ConfigService, type ConfigServiceOptions, type CoordinatedRelayOptions, type CordnCodecResult, type CordnCoordinatorClient, type CordnCoordinatorSubscription, type CordnDmAdapterOptions, type CordnDmClient, type CordnGroupMessage, type CordnRelayCoordinatorOptions, type CountRequest, type CountResult, type CountServiceOptions, type DmAdapter, type DmConversation, type DmConversationPage, type DmConversationQuery, type DmHexPubkey, DmMemoryStore, type DmMessage, type DmMessagePage, type DmMessageQuery, type DmMessageStatus, type DmOk, type DmPeer, type DmRelayPool, type DmSendRequest, type DmSendResult, type DmService, type DmServiceOptions, type DmStatus, type DmSubscribeRequest, type DmSubscription, type DmTimestamp, type HostCacheBridge, type HostKeyEvent, type HostKeysBridge, type HostMediaBridge, type HttpUploaderOptions, type HttpUploaderRails, type IdentityServiceOptions, type IntentArchetypeSupport, type IntentAvailability, type IntentBehavior, type IntentCandidate, type IntentCatalogEntry, type IntentHandlerPreference, type IntentOpenParams, type IntentRequest, type IntentResolver, type IntentResolverContext, type IntentResult, type IntentServiceOptions, type IntentWindowController, type KeysServiceOptions, type LinkOpenContext, type LinkServiceOptions, type ListsServiceContext, type ListsServiceOptions, type ManifestArchetypeInput, type MaybePromise, type MediaMetadataLike, type MediaPlaybackOwner, type MediaServiceOptions, type MediaSessionCreateOptions, type MediaSessionTarget, type MediaSourceRef, type NdrDmAdapterOptions, type NdrRelayTransport, type NdrRelayTransportOptions, type NdrRumorLike, type NdrRuntimeLike, type Nip17DmAdapterOptions, type NostrTag, type Notification, type NotificationServiceOptions, type NotifyServiceOptions, type OutboxEventOptions, type OutboxEventResult, type OutboxPublishOptions, type OutboxPublishResult, type OutboxQueryOptions, type OutboxQueryStream, type OutboxQueryStreamSink, type OutboxRelayPlan, type OutboxRelayPool, type OutboxResult, type OutboxRouter, type OutboxRouterSubscription, type OutboxServiceOptions, type OutboxSubscribeOptions, type OutboxSubscriptionSink, type OutboxTarget, type RailServerConfig, type RelayListEntry, type RelayPoolOutboxRouterOptions, type RelayPoolServiceOptions, type ResourceInfo, type ResourceInfoContext, type ResourceInfoProvider, type ResourceSchemeInfo, type ResourceService, type ResourceServiceOptions, type SerialServiceContext, type SerialServiceOptions, type SignEvent, type StreamingOutboxRouter, type ThemeService, type ThemeServiceOptions, type UploadDimensions, type UploadInfo, type UploadInfoContext, type UploadInfoProvider, type UploadRail, type UploadRailInfo, type UploadRequest, type UploadResult, type UploadServiceOptions, type UploadState, type UploadStatus, type Uploader, type UploaderContext, type WebrtcServiceContext, type WebrtcServiceOptions, createAudioService, createBleService, createBrowserMediaBridge, createCacheService, createCatalogIntentResolver, createCommonService, createConfigService, createCoordinatedRelay, createCordnDmAdapter, createCordnRelayCoordinatorClient, createCountService, createDmService, createHttpUploader, createIdentityService, createIntentService, createKeysService, createLinkService, createListsService, createMediaService, createNdrDmAdapter, createNdrRelayTransport, createNip17DmAdapter, createNotificationService, createNotifyService, createOutboxService, createRelayPoolOutboxRouter, createRelayPoolService, createResourceService, createSerialService, createThemeService, createUploadService, createWebrtcService, manifestToIntentCatalogEntry };
|