@kehto/shell 0.26.0 → 0.27.0

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 CHANGED
@@ -14,8 +14,10 @@ pnpm add @kehto/shell
14
14
 
15
15
  ## Published Napplet Compatibility
16
16
 
17
- `@kehto/shell` publishes against `@napplet/core` and `@napplet/nap`
18
- `>=0.32.0 <0.33.0`. The installed core 0.32.0 / nap 0.32.0 contracts follow
17
+ Peers admit `@napplet/core` `>=0.32.0 <0.38.0` and `@napplet/nap`
18
+ `>=0.32.0 <0.37.0`, so the nap 0.35.x line that ships the `llm` subpath (and the
19
+ core 0.36.x line nap 0.35.1 requires) installs without peer conflicts; development
20
+ and tests stay on the baseline 0.32.0 line. The installed core 0.32.0 / nap 0.32.0 contracts follow
19
21
  NAP-INTENT authority `5ac0490461ca6fec2f0d2e45b4835cf9bc08de24`, napplet/web#199
20
22
  source `3037200c932488f14f7f369b8583c39c9c16510a` / merge
21
23
  `b3f0007867eac109fa4917fac9c285d3b7cc6155`, and Version Packages #198 release
@@ -23,9 +25,9 @@ source `dc1d24153c759152b6ba31a6ec9bea967798f2df`.
23
25
 
24
26
  ## Overview
25
27
 
26
- `@kehto/shell` is the browser-specific integration layer for kehto. It wraps `@kehto/runtime` with the window/postMessage transport, localStorage persistence hooks, an audio manager, and the five canonical per-domain proxies defined by NIP-5D.
28
+ `@kehto/shell` is the browser-specific integration layer for kehto. It wraps `@kehto/runtime` with the NIP-5D port-bound transport (one window-level `napplet.hello`, then a dedicated `MessagePort`), localStorage persistence hooks, an audio manager, and the five canonical per-domain proxies defined by NIP-5D.
27
29
 
28
- The primary entry point is `createShellBridge()` — it owns the postMessage listener, the NAP-SHELL `shell.ready` / `shell.init` handshake, manifest verification, and every dispatch back into the runtime engine.
30
+ The primary entry point is `createShellBridge()` — it owns the `napplet.hello` listener and port binding, the NAP-SHELL `shell.ready` / `shell.init` handshake, manifest verification, and every dispatch back into the runtime engine.
29
31
 
30
32
  ACL persistence uses the browser's `localStorage` key `napplet:acl`. A failed
31
33
  write is intentionally reported through `bridge.runtime.aclState.persist()` or
@@ -101,6 +103,38 @@ remain bounded compatibility adapters and warn once per iframe with
101
103
  `KEHTO_COMPAT_INTENT_OBJECT_INVOKE`. Their removal conditions are tracked in
102
104
  [compatibility](https://github.com/kehto/web/blob/main/docs/compatibility.md).
103
105
 
106
+ ### NIP-5D port-bound transport
107
+
108
+ NIP-5D ([nostr-protocol/nips#2303 at `51ef0cd`](https://github.com/nostr-protocol/nips/blob/51ef0cd33a5694784306f3260d90b1872545fdee/5D.md))
109
+ binds napplet identity to a `MessagePort`, not the iframe `Window`. A `Window`
110
+ survives same-frame navigation, but the port lives only in the verified document:
111
+
112
+ - The injected prelude opens a `MessageChannel` before napplet scripts run,
113
+ keeps one port private, and sends the other once in a window-level
114
+ `{ type: 'napplet.hello' }` (`NAPPLET_HELLO`). Every NAP message then travels
115
+ over that port in both directions.
116
+ - `ShellBridge.handleMessage` accepts the hello only from the `contentWindow` of
117
+ an iframe registered in `originRegistry`, carrying exactly one port. It binds
118
+ the registered `(dTag, aggregateHash)` to the port. Shell senders use
119
+ `originRegistry.postToNapplet(windowId, message)` or `getNappletTarget(windowId)`,
120
+ never `iframeWindow.postMessage`.
121
+ - A second hello, any other window-level message, a hello without exactly one
122
+ port, a second iframe `load`, or the port closing revokes the napplet. The
123
+ bridge closes the port, revokes its sessions, subscriptions and environment,
124
+ and calls `ShellAdapter.onNappletRevoked({ windowId, reason, detail })`. The
125
+ host must remove that frame. `ShellBridge.revokeNapplet(windowId)` revokes on
126
+ demand.
127
+ - `ShellBridge.observeFrame(frame, windowId)` watches for the second load. Call
128
+ it after the frame is in the DOM and before assigning `srcdoc`.
129
+ - `originRegistry.register()` throws for a `Window` that already handed over (or
130
+ lost) its port. Create a fresh iframe for every reload or navigation.
131
+ - `ShellAdapter.onNappletMessage(windowId, message)` observes each port envelope
132
+ before dispatch, for logging or readiness tracking.
133
+
134
+ Napplets hosted with this prelude must not also import `@napplet/shim`, because
135
+ the shim sends a second hello. The retired window-level transport is recorded as
136
+ `NIP5D_WINDOW_TRANSPORT_RETIRED` in the [compatibility register](https://github.com/kehto/web/blob/main/docs/compatibility.md).
137
+
104
138
  ## Quick Start
105
139
 
106
140
  ```ts
@@ -123,13 +157,16 @@ bridge.runtime.registerService(
123
157
  ## Public API
124
158
 
125
159
  ### Bridge factory
126
- - `createShellBridge` — primary entry point; returns a `ShellBridge` (exposed `runtime`, `shell.ready`, lifecycle hooks)
160
+ - `createShellBridge` — primary entry point; returns a `ShellBridge` (exposed `runtime`, `shell.ready`, lifecycle hooks, `observeFrame`, `revokeNapplet`)
161
+ - `NAPPLET_HELLO` — the NIP-5D port handoff message type
127
162
  - `ShellBridge` — interface type for the returned bridge
128
163
 
129
164
  ### Hooks adapter
130
165
  - `adaptHooks` — convert a `ShellAdapter` + `BrowserDeps` into the canonical `RuntimeAdapter` hook bag consumed by `@kehto/runtime`
131
166
 
132
167
  ### Diagnostics
168
+ - `ShellAdapter.onNappletRevoked?(revocation)` — called once when the bridge revokes a NIP-5D binding (`NappletRevocation`: `{ windowId, reason, detail }`, `reason` one of `NappletRevocationReason`). The host must remove the frame.
169
+ - `ShellAdapter.onNappletMessage?(windowId, message)` — observe-only; called for each NAP envelope received on a bound port before dispatch.
133
170
  - `ShellAdapter.onUnroutedMessage?(info)` — optional observe-only hook fired when `ShellBridge.handleMessage` drops an incoming postMessage it can't route to a registered napplet window. `info` is an `UnroutedMessageInfo` (`{ type?, origin, reason }`) where `reason` is `'no-source-window'` or `'unregistered-window'`. The message is still dropped — this exists so otherwise-silent drops (e.g. an intent/`srcdoc` iframe whose `contentWindow` was never registered in `originRegistry`, or was swapped by a reload) are diagnosable instead of vanishing. Host can `console.warn` inside its own hook; the bridge adds no console output and swallows hook errors so routing is never broken.
134
171
 
135
172
  ```ts
@@ -218,14 +255,15 @@ payload.
218
255
  ### Session / origin registry
219
256
  - `sessionRegistry` — canonical windowId ↔ verified-napplet registry singleton
220
257
  - `nappKeyRegistry` — deprecated alias for `sessionRegistry`
221
- - `originRegistry` — origin-to-windowId map used by proxies and bridge broadcasts
258
+ - `originRegistry` — iframe `Window` → windowId/identity map; also holds each window's bound NIP-5D port (`bindPort`, `getPort`, `postToNapplet`, `getNappletTarget`, `isSpent`)
259
+ - `NappletMessageTarget` — port-backed outbound target passed to `RelayPoolHooks.openScopedRelay`
222
260
  - `PendingUpdate` — type for pending aggregate-hash change prompts
223
261
 
224
262
  Environment snapshots belong to one source registration. To retire a window,
225
263
  call `bridge.runtime.destroyWindow(windowId)`,
226
264
  `bridge.runtime.sessionRegistry.unregister(windowId)`, and
227
265
  `originRegistry.unregister(windowId)`. When registering a replacement under the
228
- same window ID, provide its snapshot with `originRegistry.setEnvironment()` or
266
+ same window ID (always a fresh iframe — a spent `Window` cannot be re-registered), provide its snapshot with `originRegistry.setEnvironment()` or
229
267
  let the bridge call `resolveShellEnvironment()` for current host defaults. A
230
268
  retired registration's cached domains and service names are discarded even when
231
269
  the runtime session was already removed. This applies before any domain dispatch,
@@ -303,11 +341,11 @@ for outer URL encoding, review, routing, and verified launcher behavior.
303
341
 
304
342
  ## GAMEPAD integration
305
343
 
306
- This non-normative guidance defers to the living [NAP-GAMEPAD](https://github.com/napplet/naps/pull/108). Services export `createGamepadService` and `browserGamepadOptions`; shell exports `denyNappletControllerAccess`. Paja and the playground register one host reader and deny native gamepad/HID/USB paths before frame execution. Source-bound subscriptions receive immediate full snapshots; only the visible host's input owner gets live controls. Focus loss supplies neutral controls and timestamp zero; unsubscribe, window destruction, and service removal release polling. Non-browser or denied native readers report the proposal's unavailable reasons. See [Controller input](https://github.com/kehto/web/blob/main/docs/gamepad.md).
344
+ This non-normative guidance defers to the living [NAP-GAMEPAD](https://github.com/napplet/naps/pull/108). Services export `createGamepadService` and `browserGamepadOptions`; shell exports `denyNappletControllerAccess`. Paja registers one host reader and deny native gamepad/HID/USB paths before frame execution. Source-bound subscriptions receive immediate full snapshots; only the visible host's input owner gets live controls. Focus loss supplies neutral controls and timestamp zero; unsubscribe, window destruction, and service removal release polling. Non-browser or denied native readers report the proposal's unavailable reasons. See [Controller input](https://github.com/kehto/web/blob/main/docs/gamepad.md).
307
345
 
308
346
  ## LLM integration
309
347
 
310
- This non-normative guidance defers to the living [NAP-LLM](https://github.com/napplet/naps/pull/134) (checked at `3e10939`). Services export `createLlmService`, `LlmServiceError`, the provider mappers and `LLM_PROVIDER_PRESETS`; shell exports `getShellDomainState` and the `LlmHooks` adapter field; acl exports `llm:generate`. The runtime routes `llm.*` to one registered service and answers every sanctioned request with exactly one `llm.<op>.result`, shaping missing-service and ACL failures as `{ code: 'unsupported' }`. Hosts implement `LlmConnectionHost`; the service enforces schema, handle ownership, consent before submission, duplicate ids, cancellation and deadline races, ordered deltas and safe errors. Advertisement stays binary on the wire while hosts report a third `unconfigured` state until the user adds a connection. See [Language models](https://github.com/kehto/web/blob/main/docs/llm.md).
348
+ This non-normative guidance defers to the living [NAP-LLM](https://github.com/napplet/naps/pull/134) (checked at `3e10939`). Services export `createLlmService`, `LlmServiceError`, the provider mappers and `LLM_PROVIDER_PRESETS`; shell exports `getShellDomainState` and the `LlmHooks` adapter field; acl exports `llm:generate`. The runtime routes `llm.*` to one registered service and answers every sanctioned request with exactly one `llm.<op>.result`, shaping missing-service and ACL failures as `{ code: 'unsupported' }`. Hosts implement `LlmConnectionHost`; the service enforces schema, handle ownership, a per-window live-handle bound (`maxHandlesPerWindow`), consent before submission, duplicate ids, cancellation and deadline races, ordered deltas and safe errors, and passes `select`/`authorize` a `{ signal }` that withdraws stale prompts. `createHttpLlmProvider({ stream: true })` streams provider output into `llm.delta`. Advertisement stays binary on the wire while hosts report a third `unconfigured` state until the user adds a connection. See [Language models](https://github.com/kehto/web/blob/main/docs/llm.md).
311
349
 
312
350
  ## UUID support on HTTP origins
313
351
 
package/dist/index.d.ts CHANGED
@@ -6,14 +6,30 @@ import { Theme } from '@napplet/nap/theme/types';
6
6
  import { ResourceBytesItem, ResourceBytesRequest as ResourceBytesRequest$1, ResourceInfo } from '@napplet/nap/resource/types';
7
7
  export { ResourceInfo } from '@napplet/nap/resource/types';
8
8
 
9
+ /**
10
+ * Minimal outbound target for one napplet. NIP-5D carries every NAP message
11
+ * over the port bound by `napplet.hello`, so this never wraps the iframe
12
+ * `Window`.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * originRegistry.getNappletTarget('napp-1')?.postMessage({ type: 'relay.eose', subId });
17
+ * ```
18
+ */
19
+ interface NappletMessageTarget {
20
+ /** Post one NAP envelope over the bound port. Dropped when no port is bound. */
21
+ postMessage(message: unknown): void;
22
+ }
9
23
  /** NIP-5D identity metadata associated with a registered iframe window. */
10
24
  interface OriginIdentity {
11
25
  readonly dTag: string;
12
26
  readonly aggregateHash: string;
13
27
  }
14
28
  /**
15
- * Bidirectional registry mapping Window references to windowId strings.
16
- * Optionally stores NIP-5D identity metadata (dTag and aggregateHash) per window.
29
+ * Registry mapping each shell-created iframe Window to its windowId, verified
30
+ * NIP-5D identity, and the `MessagePort` bound by its single `napplet.hello`.
31
+ * The Window is only used to authenticate the hello; all NAP traffic uses the
32
+ * bound port (NIP-5D, nostr-protocol/nips#2303).
17
33
  *
18
34
  * @example
19
35
  * ```ts
@@ -26,6 +42,11 @@ interface OriginIdentity {
26
42
  interface OriginRegistry {
27
43
  register(win: Window, windowId: string, identity?: OriginIdentity): void;
28
44
  unregister(windowId: string): void;
45
+ bindPort(windowId: string, port: MessagePort): boolean;
46
+ getPort(windowId: string): MessagePort | undefined;
47
+ postToNapplet(windowId: string, message: unknown): boolean;
48
+ getNappletTarget(windowId: string): NappletMessageTarget | null;
49
+ isSpent(win: Window): boolean;
29
50
  getWindowId(win: Window): string | undefined;
30
51
  getIframeWindow(windowId: string): Window | null;
31
52
  getAllWindowIds(): string[];
@@ -73,7 +94,7 @@ interface RelayPoolHooks {
73
94
  /** Untrack and clean up a subscription. */
74
95
  untrackSubscription(subKey: string): void;
75
96
  /** Open a scoped relay connection (NIP-29 groups). */
76
- openScopedRelay(windowId: string, relayUrl: string, subId: string, filters: NostrFilter[], sourceWindow: Window): void;
97
+ openScopedRelay(windowId: string, relayUrl: string, subId: string, filters: NostrFilter[], target: NappletMessageTarget): void;
77
98
  /** Close a scoped relay connection. */
78
99
  closeScopedRelay(windowId: string): void;
79
100
  /**
@@ -348,6 +369,33 @@ interface UnroutedMessageInfo {
348
369
  */
349
370
  reason: 'no-source-window' | 'unregistered-window';
350
371
  }
372
+ /**
373
+ * Why a NIP-5D napplet binding was revoked.
374
+ * - `window-message` — a window-level message other than the one `napplet.hello`.
375
+ * - `second-hello` — a second `napplet.hello` from the same iframe.
376
+ * - `invalid-hello` — a `napplet.hello` without exactly one transferred port.
377
+ * - `second-load` — the iframe loaded a second document (navigation or reload).
378
+ * - `port-closed` — the bound port closed or failed to deserialize a message.
379
+ * - `host` — the host revoked the binding itself.
380
+ */
381
+ type NappletRevocationReason = 'window-message' | 'second-hello' | 'invalid-hello' | 'second-load' | 'port-closed' | 'host';
382
+ /**
383
+ * Passed to {@link ShellAdapter.onNappletRevoked} after the shell closed the
384
+ * port and revoked the identity binding, sessions and grants of one frame.
385
+ *
386
+ * @example
387
+ * ```ts
388
+ * hooks.onNappletRevoked = ({ windowId }) => frames.get(windowId)?.remove();
389
+ * ```
390
+ */
391
+ interface NappletRevocation {
392
+ /** The revoked window. Its identity can never be re-bound to the same iframe. */
393
+ readonly windowId: string;
394
+ /** What the shell observed. */
395
+ readonly reason: NappletRevocationReason;
396
+ /** Human-readable detail for diagnostics. Never includes message payloads. */
397
+ readonly detail: string;
398
+ }
351
399
  /**
352
400
  * Static capability set sent to napplet iframes through the shell.ready /
353
401
  * shell.init handshake. Optional runtime domain presence is injected before
@@ -461,6 +509,24 @@ interface ShellAdapter {
461
509
  * (the FEED-02 / hyprgate#21 class of "a napplet's messages vanish" bug).
462
510
  */
463
511
  onUnroutedMessage?: (info: UnroutedMessageInfo) => void;
512
+ /**
513
+ * Called once when the shell revokes a NIP-5D napplet binding: a second
514
+ * `napplet.hello`, any other window-level message, an invalid hello, a
515
+ * second iframe `load` (see `ShellBridge.observeFrame`), the port closing,
516
+ * or `ShellBridge.revokeNapplet`. The shell has already closed the port and
517
+ * revoked the identity binding, sessions and grants. NIP-5D requires the
518
+ * host to remove the iframe here; it MAY create a fresh iframe from the
519
+ * verified bytes but MUST NOT reuse this one.
520
+ */
521
+ onNappletRevoked?: (revocation: NappletRevocation) => void;
522
+ /**
523
+ * Observe-only: called with each NAP envelope received on a napplet's bound
524
+ * NIP-5D port, before the shell dispatches it. NIP-5D moves NAP traffic off
525
+ * window-level `message` events, so hosts that logged or tracked readiness
526
+ * from those events use this hook instead. It cannot change or drop the
527
+ * message; host hook errors are swallowed.
528
+ */
529
+ onNappletMessage?: (windowId: string, message: NappletMessage) => void;
464
530
  /** Called when aggregate hash verification fails (computed != declared). */
465
531
  onHashMismatch?: (dTag: string, claimed: string, computed: string) => void;
466
532
  /**
@@ -497,14 +563,36 @@ interface ShellAdapter {
497
563
  getConfigOverrides?(): RuntimeConfigOverrides;
498
564
  }
499
565
 
566
+ /**
567
+ * NIP-5D port-bound transport for ShellBridge (nostr-protocol/nips#2303 at
568
+ * 51ef0cd33a5694784306f3260d90b1872545fdee, packaged by napplet/web#302).
569
+ *
570
+ * A napplet's injected bootstrap transfers one MessagePort in a single
571
+ * window-level `napplet.hello`. The shell binds the registered identity to
572
+ * that port, dispatches NAP only from it, and revokes the binding on any
573
+ * evidence that the verified document was replaced.
574
+ *
575
+ * @module
576
+ */
577
+
578
+ /** The NIP-5D port handoff message type (nostr-protocol/nips#2303). */
579
+ declare const NAPPLET_HELLO = "napplet.hello";
580
+
500
581
  /**
501
582
  * Shell-side message bridge that handles NIP-5D communication with napplet iframes.
502
583
  *
503
- * The bridge acts as a browser adapter: it receives raw MessageEvents from
504
- * window.addEventListener('message', ...), extracts the source Window, resolves
505
- * it to a windowId via originRegistry, and delegates NIP-5D envelope messages
506
- * to the runtime engine. The shell.ready/shell.init capability handshake is
507
- * handled locally within the bridge and never forwarded to the runtime.
584
+ * NIP-5D binds napplet identity to a `MessagePort`, not to the iframe Window:
585
+ * the injected bootstrap transfers one port to the shell in a single
586
+ * window-level `napplet.hello`. The bridge accepts that hello only from the
587
+ * `contentWindow` of a registered iframe carrying exactly one port, binds the
588
+ * frame's verified `(dTag, aggregateHash)` to the port, and then processes NAP
589
+ * envelopes only from that port and replies only over it. A second hello, any
590
+ * other window-level message, an invalid hello, a second iframe `load`
591
+ * ({@link ShellBridge.observeFrame}) or the port closing revokes the binding,
592
+ * its sessions and grants, and asks the host to remove the frame through
593
+ * `ShellAdapter.onNappletRevoked`. The shell.ready/shell.init capability
594
+ * handshake is handled locally within the bridge and never forwarded to the
595
+ * runtime.
508
596
  *
509
597
  * @example
510
598
  * ```ts
@@ -516,15 +604,19 @@ interface ShellAdapter {
516
604
  */
517
605
  interface ShellBridge {
518
606
  /**
519
- * Handle an incoming postMessage from a napplet iframe.
607
+ * Handle a window-level message from a napplet iframe.
520
608
  *
521
- * Only NIP-5D envelope objects (plain objects with a `.type` string) are
522
- * accepted. NIP-01 arrays and all other message shapes are silently dropped
523
- * (clean break — no legacy array fallback).
609
+ * The only window-level message a napplet may send is its single
610
+ * `napplet.hello` carrying exactly one `MessagePort`. The bridge binds that
611
+ * port and receives every later NAP envelope from it. Any other window-level
612
+ * message from a registered iframe, a second hello, or a hello without
613
+ * exactly one port revokes the frame. Messages from unregistered sources are
614
+ * dropped.
524
615
  *
525
- * shell.ready messages are handled locally: the bridge responds with shell.init
526
- * containing the capability set and registered service list. All other envelopes
527
- * are delegated to the runtime's NAP domain dispatch.
616
+ * On the bound port, only NIP-5D envelope objects (plain objects with a
617
+ * `.type` string) are accepted. shell.ready is handled locally (the bridge
618
+ * answers with shell.init over the port); all other envelopes are delegated
619
+ * to the runtime's NAP domain dispatch.
528
620
  *
529
621
  * @param event - The raw MessageEvent from window.addEventListener('message', ...)
530
622
  * @example
@@ -605,6 +697,36 @@ interface ShellBridge {
605
697
  * @param pubkey - Current user's hex pubkey, or empty string when signed out.
606
698
  */
607
699
  publishIdentityChanged(pubkey: string): void;
700
+ /**
701
+ * Watch a napplet iframe for a second `load`, which NIP-5D treats as
702
+ * evidence that the verified document was replaced. Call after the frame is
703
+ * in the DOM and before assigning its `srcdoc`/`src`.
704
+ *
705
+ * @param frame - The shell-created napplet iframe
706
+ * @param windowId - The windowId registered for this frame
707
+ * @returns A function that stops watching
708
+ * @example
709
+ * ```ts
710
+ * document.body.append(frame);
711
+ * originRegistry.register(frame.contentWindow!, windowId, identity);
712
+ * const stop = bridge.observeFrame(frame, windowId);
713
+ * frame.srcdoc = verifiedSrcdoc;
714
+ * ```
715
+ */
716
+ observeFrame(frame: Pick<HTMLIFrameElement, 'addEventListener' | 'removeEventListener'>, windowId: string): () => void;
717
+ /**
718
+ * Revoke one napplet binding: close its port, unregister it, destroy its
719
+ * sessions and subscriptions, and call `ShellAdapter.onNappletRevoked` so
720
+ * the host removes the frame. No-op for unknown windows.
721
+ *
722
+ * @param windowId - The window to revoke
723
+ * @param reason - Why the binding is revoked (defaults to `host`)
724
+ * @example
725
+ * ```ts
726
+ * bridge.revokeNapplet(windowId);
727
+ * ```
728
+ */
729
+ revokeNapplet(windowId: string, reason?: NappletRevocationReason): void;
608
730
  /**
609
731
  * Access the underlying runtime instance for advanced use cases.
610
732
  * Provides direct access to the runtime's sessionRegistry, aclState,
@@ -612,6 +734,31 @@ interface ShellBridge {
612
734
  */
613
735
  readonly runtime: Runtime;
614
736
  }
737
+ /**
738
+ * Create a ShellBridge instance with dependency injection via hooks.
739
+ *
740
+ * Internally creates a Runtime from @kehto/runtime and adapts the
741
+ * browser-oriented ShellAdapter into environment-agnostic RuntimeAdapter.
742
+ *
743
+ * @param hooks - Host application provides relay pool, auth, config, etc.
744
+ * @returns A ShellBridge instance ready to handle napplet messages
745
+ * @example
746
+ * ```ts
747
+ * import { createShellBridge, type ShellAdapter } from '@kehto/shell';
748
+ *
749
+ * const hooks: ShellAdapter = {
750
+ * relayPool: myRelayPoolHooks,
751
+ * relayConfig: myRelayConfigHooks,
752
+ * windowManager: myWindowManagerHooks,
753
+ * auth: myAuthHooks,
754
+ * config: myConfigHooks,
755
+ * hotkeys: myHotkeyHooks,
756
+ * workerRelay: myWorkerRelayHooks,
757
+ * crypto: myCryptoHooks,
758
+ * };
759
+ * const bridge = createShellBridge(hooks);
760
+ * ```
761
+ */
615
762
  declare function createShellBridge(hooks: ShellAdapter): ShellBridge;
616
763
 
617
764
  /**
@@ -1054,11 +1201,12 @@ declare function getShellDomainState(hooks: ShellAdapter, domain: string): Shell
1054
1201
  * Minimal origin-registry contract used by per-domain proxies.
1055
1202
  *
1056
1203
  * Accepts the `@kehto/shell` singleton `originRegistry` as well as any test
1057
- * double with a matching `getIframeWindow` method.
1204
+ * double with a matching `postToNapplet` method. NIP-5D carries every NAP
1205
+ * message over the port bound by `napplet.hello`, never the iframe Window.
1058
1206
  */
1059
1207
  interface ProxyOriginRegistry {
1060
- /** Resolve a registered napplet windowId to its iframe Window, or null. */
1061
- getIframeWindow(windowId: string): Window | null;
1208
+ /** Send one envelope over the window's bound NIP-5D port; `false` when unbound. */
1209
+ postToNapplet(windowId: string, message: unknown): boolean;
1062
1210
  }
1063
1211
  /**
1064
1212
  * Dependencies for `createIdentityProxy`.
@@ -1074,7 +1222,7 @@ interface ProxyOriginRegistry {
1074
1222
  interface IdentityProxyDeps {
1075
1223
  /** The runtime engine that owns identity.* dispatch (Plan 12-03). */
1076
1224
  runtime: Runtime;
1077
- /** Origin registry for resolving windowId → iframe Window. */
1225
+ /** Origin registry that delivers envelopes over each window's bound port. */
1078
1226
  originRegistry: ProxyOriginRegistry;
1079
1227
  }
1080
1228
  /**
@@ -1616,4 +1764,4 @@ type ResourceInbound = ResourceInfoRequest | ResourceBytesRequest | ResourceByte
1616
1764
  */
1617
1765
  type ResourceOutbound = ResourceInfoResult | ResourceInfoError | ResourceBytesResult | ResourceBytesError | ResourceBytesManyResult | ResourceBytesManyError;
1618
1766
 
1619
- export { type AclCheckEvent, type AclEntry, type AclStore, type AudioManager, type AudioSource, type AuthHooks, type BleHooks, type BrowserDeps, type CapabilityHooks, type CommonHooks, type ConfigHooks, type CryptoHooks, type DmHooks, type HotkeyHooks, type IdentityProxy, type IdentityProxyDeps, type IntentHooks, type KeysProxy, type KeysProxyDeps, type LinkHooks, type ListsHooks, type LlmHooks, type ManifestCache, type ManifestCacheEntry, type MediaProxy, type MediaProxyDeps, type NappletCspDirective, type NappletCspDirectives, type NappletCspOptions, type NappletNamespacePreludeOptions, type NappletSrcdocOptions, type NormalizedIntentUri, type NotifyProxy, type NotifyProxyDeps, type OriginIdentity, type OriginRegistry, type ProxyOriginRegistry, type RelayConfigHooks, type RelayPoolHooks, type RelayPoolLike, type ResourceBytesError, type ResourceBytesManyError, type ResourceBytesManyItem, type ResourceBytesManyRequest, type ResourceBytesManyResult, type ResourceBytesRequest, type ResourceBytesResult, type ResourceCancelRequest, type ResourceErrorCode, type ResourceInbound, type ResourceInfoError, type ResourceInfoRequest, type ResourceInfoResult, type ResourceOutbound, type ResourceRequestId, type SerialHooks, type SessionRegistry, type ShellAdapter, type ShellBridge, type ShellCapabilities, type ShellDomainState, type ShellEnvironment, type ThemeProxy, type ThemeProxyDeps, type UnroutedMessageInfo, type UploadBackendLike, type UploadHooks, type WebrtcHooks, type WindowManagerHooks, type WorkerRelayHooks, type WorkerRelayLike, adaptHooks, audioManager, buildNappletCsp, buildShellCapabilities, createIdentityProxy, createKeysProxy, createMediaProxy, createNotifyProxy, createShellBridge, createThemeProxy, denyNappletControllerAccess, getShellDomainState, injectNappletCsp, injectNappletNamespacePrelude, manifestCache, nappKeyRegistry, normalizeIntentUri, originRegistry, prepareNappletSrcdoc, renderNappletCspMeta, renderNappletNamespacePrelude, resolveShellEnvironment, sessionRegistry };
1767
+ export { type AclCheckEvent, type AclEntry, type AclStore, type AudioManager, type AudioSource, type AuthHooks, type BleHooks, type BrowserDeps, type CapabilityHooks, type CommonHooks, type ConfigHooks, type CryptoHooks, type DmHooks, type HotkeyHooks, type IdentityProxy, type IdentityProxyDeps, type IntentHooks, type KeysProxy, type KeysProxyDeps, type LinkHooks, type ListsHooks, type LlmHooks, type ManifestCache, type ManifestCacheEntry, type MediaProxy, type MediaProxyDeps, NAPPLET_HELLO, type NappletCspDirective, type NappletCspDirectives, type NappletCspOptions, type NappletMessageTarget, type NappletNamespacePreludeOptions, type NappletRevocation, type NappletRevocationReason, type NappletSrcdocOptions, type NormalizedIntentUri, type NotifyProxy, type NotifyProxyDeps, type OriginIdentity, type OriginRegistry, type ProxyOriginRegistry, type RelayConfigHooks, type RelayPoolHooks, type RelayPoolLike, type ResourceBytesError, type ResourceBytesManyError, type ResourceBytesManyItem, type ResourceBytesManyRequest, type ResourceBytesManyResult, type ResourceBytesRequest, type ResourceBytesResult, type ResourceCancelRequest, type ResourceErrorCode, type ResourceInbound, type ResourceInfoError, type ResourceInfoRequest, type ResourceInfoResult, type ResourceOutbound, type ResourceRequestId, type SerialHooks, type SessionRegistry, type ShellAdapter, type ShellBridge, type ShellCapabilities, type ShellDomainState, type ShellEnvironment, type ThemeProxy, type ThemeProxyDeps, type UnroutedMessageInfo, type UploadBackendLike, type UploadHooks, type WebrtcHooks, type WindowManagerHooks, type WorkerRelayHooks, type WorkerRelayLike, adaptHooks, audioManager, buildNappletCsp, buildShellCapabilities, createIdentityProxy, createKeysProxy, createMediaProxy, createNotifyProxy, createShellBridge, createThemeProxy, denyNappletControllerAccess, getShellDomainState, injectNappletCsp, injectNappletNamespacePrelude, manifestCache, nappKeyRegistry, normalizeIntentUri, originRegistry, prepareNappletSrcdoc, renderNappletCspMeta, renderNappletNamespacePrelude, resolveShellEnvironment, sessionRegistry };