@kehto/shell 0.14.1 → 0.15.1
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 +7 -3
- package/dist/index.d.ts +110 -35
- package/dist/index.js +108 -10
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
Browser adapter over @kehto/runtime — ShellBridge, domain proxies, keys-forwarder.
|
|
4
4
|
|
|
5
5
|
> **Alpha status:** Kehto is an early runtime implementation for a draft NIP-5D
|
|
6
|
-
> protocol. NAP contracts
|
|
7
|
-
>
|
|
6
|
+
> protocol. NAP contracts and injected-domain behavior are still draft; treat
|
|
7
|
+
> this package as current implementation guidance.
|
|
8
8
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
@@ -21,9 +21,11 @@ The primary entry point is `createShellBridge()` — it owns the postMessage lis
|
|
|
21
21
|
Current draft behaviors this package enforces:
|
|
22
22
|
|
|
23
23
|
- The shell does not inject a host-provided nostr object into napplets — NIP-5D explicitly forbids napplet-visible signing. Napplets call `relay.publish` / `relay.publishEncrypted` and the shell mediates the signing flow internally (NIP-44 default, NIP-04 opt-in for encrypted envelopes).
|
|
24
|
-
- `
|
|
24
|
+
- `injectNappletNamespacePrelude()` implements the current draft NIP-5D injected-domain bootstrap: hosts prepend a `window.napplet` prelude to `srcdoc` outside the verified artifact bytes, so available NAP domain objects exist before napplet-authored scripts run. Assigned namespaces are filtered back to the explicit bare-domain allowlist; the playground does not inject `shell`.
|
|
25
|
+
- Legacy `window.napplet.shell.supports()` compatibility is not the current NIP-5D availability primitive. New host and napplet availability checks should use injected `window.napplet.<domain>` presence and leave per-domain semantic checks to the matching NAP.
|
|
25
26
|
- Five optional per-domain proxies — `createIdentityProxy`, `createThemeProxy`, `createKeysProxy`, `createMediaProxy`, `createNotifyProxy` — can be composed between napplet and runtime to intercept or augment traffic per NAP. They are NOT wired by default (Kehto's runtime already owns dispatch for the currently supported domains); they exist as host-app composition seams.
|
|
26
27
|
- The keys-forwarder pumps host keydown events into `keys.forward` envelopes for napplets that hold the `keys:forward` capability.
|
|
28
|
+
- `buildShellCapabilities()` advertises `dm` when the host adapter provides `hooks.dm`, and disabled-domain overrides remove it from both `domains` and `naps`.
|
|
27
29
|
|
|
28
30
|
## Quick Start
|
|
29
31
|
|
|
@@ -67,6 +69,8 @@ const bridge = createShellBridge({
|
|
|
67
69
|
|
|
68
70
|
### Shell init
|
|
69
71
|
- `buildShellCapabilities` — construct the current draft `ShellCapabilities` payload emitted during the `shell.ready` / `shell.init` handshake
|
|
72
|
+
- `injectNappletNamespacePrelude` — insert a host-owned NIP-5D `window.napplet` domain-availability prelude into verified HTML before authored scripts
|
|
73
|
+
- `renderNappletNamespacePrelude` — render only the bootstrap `<script>` for hosts that already own HTML insertion
|
|
70
74
|
|
|
71
75
|
### Domain proxies (NIP-5D composition seams)
|
|
72
76
|
- `createIdentityProxy` — intercept `identity.getProfile/getFollows/...` traffic
|
package/dist/index.d.ts
CHANGED
|
@@ -260,8 +260,9 @@ interface UnroutedMessageInfo {
|
|
|
260
260
|
}
|
|
261
261
|
/**
|
|
262
262
|
* Static capability set sent to napplet iframes through the shell.ready /
|
|
263
|
-
* shell.init handshake.
|
|
264
|
-
*
|
|
263
|
+
* shell.init handshake. The current runtime availability primitive is injected
|
|
264
|
+
* `window.napplet.<domain>` presence; this shape remains for older consumers
|
|
265
|
+
* that still read shell.init capability metadata.
|
|
265
266
|
*
|
|
266
267
|
* Per canonical NIP-5D (https://github.com/nostr-protocol/nips/pull/2303/),
|
|
267
268
|
* supports() distinguishes two namespaces:
|
|
@@ -275,45 +276,39 @@ interface UnroutedMessageInfo {
|
|
|
275
276
|
* The two namespaces do not cross: a bare-name lookup never matches a sandbox
|
|
276
277
|
* entry and a `perm:`-prefixed lookup never matches a NAP entry.
|
|
277
278
|
*
|
|
278
|
-
* ##
|
|
279
|
+
* ## Compatibility NAP-SHELL shape
|
|
279
280
|
*
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
* `capabilities.{ domains: string[], protocols: Record<string, string[]> }`:
|
|
281
|
+
* Older shell consumers may read the structured
|
|
282
|
+
* `capabilities.{ domains: string[], protocols: Record<string, string[]> }`
|
|
283
|
+
* alongside the flat `naps` array:
|
|
284
284
|
*
|
|
285
285
|
* - `supports('relay')` → true iff `'relay' ∈ capabilities.domains`
|
|
286
286
|
* - `supports('inc','NAP-01')`→ true iff `capabilities.protocols['inc']`
|
|
287
287
|
* includes `'NAP-01'`
|
|
288
288
|
*
|
|
289
|
-
* `domains` and `protocols` are emitted as a
|
|
290
|
-
* `naps`/`sandbox` fields
|
|
291
|
-
* special `perm:` logic
|
|
292
|
-
* membership check, so
|
|
289
|
+
* `domains` and `protocols` are emitted as a superset alongside the
|
|
290
|
+
* `naps`/`sandbox` fields for back-compat. The compatibility helper has no
|
|
291
|
+
* special `perm:` logic: `supports('perm:popups')` is an ordinary bare-domain
|
|
292
|
+
* membership check, so Kehto's `perm:`-prefixed sandbox entries are folded into
|
|
293
293
|
* `domains` (empty by default, preserving the default-empty sandbox behavior).
|
|
294
294
|
*/
|
|
295
295
|
interface ShellCapabilities {
|
|
296
296
|
/**
|
|
297
|
-
*
|
|
297
|
+
* Compatibility NAP-SHELL domain list.
|
|
298
298
|
* Bare NAP domain names the shell offers — `'identity'`, `'storage'`,
|
|
299
299
|
* `'inc'`, `'theme'`, etc. — with the same conditional entries as `naps`
|
|
300
300
|
* (`relay`/`outbox` when a relay pool is wired, `upload`/`intent` under their
|
|
301
301
|
* hooks). Carries NO `inc:NAP-NN` protocol strings (those live in
|
|
302
302
|
* `protocols`). Any `perm:<x>` sandbox entries are appended here too, since
|
|
303
|
-
*
|
|
304
|
-
*
|
|
305
|
-
* Read by `createShellEnvironment(msg).capabilities.domains` →
|
|
306
|
-
* `makeSupports` for `supports('<domain>')` queries.
|
|
303
|
+
* legacy supports helpers resolve `supports('perm:<x>')` against this list.
|
|
307
304
|
*/
|
|
308
305
|
domains: string[];
|
|
309
306
|
/**
|
|
310
|
-
*
|
|
311
|
-
* `@napplet/shim >=0.13`). Maps a domain to the NAP-NN protocol IDs it
|
|
307
|
+
* Compatibility NAP-SHELL per-domain numbered protocols. Maps a domain to the NAP-NN protocol IDs it
|
|
312
308
|
* speaks — `{ inc: ['NAP-01','NAP-02','NAP-03','NAP-04','NAP-05','NAP-06'] }`
|
|
313
309
|
* (derived from `NAP_INC_PROTOCOLS` by stripping the `inc:` prefix).
|
|
314
310
|
*
|
|
315
|
-
*
|
|
316
|
-
* `makeSupports` for `supports('<domain>','NAP-NN')` queries.
|
|
311
|
+
* Legacy supports helpers resolve `supports('<domain>','NAP-NN')` against this map.
|
|
317
312
|
*/
|
|
318
313
|
protocols: Record<string, string[]>;
|
|
319
314
|
/**
|
|
@@ -588,6 +583,52 @@ interface ShellBridge {
|
|
|
588
583
|
}
|
|
589
584
|
declare function createShellBridge(hooks: ShellAdapter): ShellBridge;
|
|
590
585
|
|
|
586
|
+
/**
|
|
587
|
+
* Options for rendering the host-owned NIP-5D `window.napplet` namespace prelude.
|
|
588
|
+
*
|
|
589
|
+
* @example
|
|
590
|
+
* ```ts
|
|
591
|
+
* const options: NappletNamespacePreludeOptions = {
|
|
592
|
+
* domains: ['shell', 'relay', 'identity'],
|
|
593
|
+
* };
|
|
594
|
+
* ```
|
|
595
|
+
*/
|
|
596
|
+
interface NappletNamespacePreludeOptions {
|
|
597
|
+
/**
|
|
598
|
+
* Bare NAP domain names the shell exposes to this napplet.
|
|
599
|
+
*/
|
|
600
|
+
domains: readonly string[];
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Render the host-owned NIP-5D bootstrap that exposes available NAP domains
|
|
604
|
+
* under `window.napplet` before napplet artifact code runs.
|
|
605
|
+
*
|
|
606
|
+
* @param options - Domain availability to inject.
|
|
607
|
+
* @returns An inline script tag suitable for `srcdoc` prelude insertion.
|
|
608
|
+
* @example
|
|
609
|
+
* ```ts
|
|
610
|
+
* const prelude = renderNappletNamespacePrelude({
|
|
611
|
+
* domains: ['shell', 'relay', 'identity'],
|
|
612
|
+
* });
|
|
613
|
+
* ```
|
|
614
|
+
*/
|
|
615
|
+
declare function renderNappletNamespacePrelude(options: NappletNamespacePreludeOptions): string;
|
|
616
|
+
/**
|
|
617
|
+
* Insert the NIP-5D namespace prelude into HTML before authored scripts.
|
|
618
|
+
*
|
|
619
|
+
* @param html - Verified napplet artifact HTML.
|
|
620
|
+
* @param options - Domain availability to inject.
|
|
621
|
+
* @returns HTML with the prelude inside `<head>` when possible.
|
|
622
|
+
* @example
|
|
623
|
+
* ```ts
|
|
624
|
+
* const srcdoc = injectNappletNamespacePrelude(verifiedHtml, {
|
|
625
|
+
* domains: ['shell', 'relay', 'identity'],
|
|
626
|
+
* });
|
|
627
|
+
* iframe.srcdoc = srcdoc;
|
|
628
|
+
* ```
|
|
629
|
+
*/
|
|
630
|
+
declare function injectNappletNamespacePrelude(html: string, options: NappletNamespacePreludeOptions): string;
|
|
631
|
+
|
|
591
632
|
/**
|
|
592
633
|
* Bidirectional registry mapping Window references to windowId strings.
|
|
593
634
|
* Optionally stores NIP-5D identity metadata (dTag and aggregateHash) per window.
|
|
@@ -648,6 +689,13 @@ declare const originRegistry: {
|
|
|
648
689
|
dTag: string;
|
|
649
690
|
aggregateHash: string;
|
|
650
691
|
} | undefined;
|
|
692
|
+
/**
|
|
693
|
+
* Look up the monotonically increasing registration id for a Window.
|
|
694
|
+
*
|
|
695
|
+
* @param win - The Window reference to look up
|
|
696
|
+
* @returns The registration id, or undefined if the Window is not registered
|
|
697
|
+
*/
|
|
698
|
+
getRegistrationId(win: Window): number | undefined;
|
|
651
699
|
/** Clear all registrations. */
|
|
652
700
|
clear(): void;
|
|
653
701
|
};
|
|
@@ -1128,10 +1176,10 @@ declare function adaptHooks(shellHooks: ShellAdapter, deps: BrowserDeps): Runtim
|
|
|
1128
1176
|
/**
|
|
1129
1177
|
* Build the shell's static capability set from adapter configuration.
|
|
1130
1178
|
*
|
|
1131
|
-
* Returns the
|
|
1132
|
-
*
|
|
1133
|
-
* `
|
|
1134
|
-
*
|
|
1179
|
+
* Returns the compatibility NAP-SHELL `domains`/`protocols` shape alongside
|
|
1180
|
+
* the flat `naps` array and the `sandbox` array. Current availability is
|
|
1181
|
+
* injected `window.napplet.<domain>` presence; this payload remains for older
|
|
1182
|
+
* consumers that still inspect shell.init capabilities.
|
|
1135
1183
|
*
|
|
1136
1184
|
* ### naps array (NAP vocabulary)
|
|
1137
1185
|
* Bare domain `inc` + `inc:NAP-01..inc:NAP-06`.
|
|
@@ -1148,22 +1196,21 @@ declare function adaptHooks(shellHooks: ShellAdapter, deps: BrowserDeps): Runtim
|
|
|
1148
1196
|
* sandbox permissions from NAP-capability lookups; see the living NIP-5D at
|
|
1149
1197
|
* https://github.com/nostr-protocol/nips/pull/2303/
|
|
1150
1198
|
*
|
|
1151
|
-
* ### domains array + protocols map (
|
|
1152
|
-
* The structured shape
|
|
1153
|
-
* `@napplet/nap@0.12`'s `createShellEnvironment` + `makeSupports`:
|
|
1199
|
+
* ### domains array + protocols map (compatibility NAP-SHELL)
|
|
1200
|
+
* The structured shape older shell capability consumers read:
|
|
1154
1201
|
*
|
|
1155
1202
|
* - `domains` — bare NAP domain names (the `naps` set MINUS the `inc:NAP-NN`
|
|
1156
1203
|
* protocol strings) with the same conditional entries (relay/outbox under
|
|
1157
1204
|
* `hooks.relayPool`, upload/intent/link/common under their hooks). Any `perm:<x>`
|
|
1158
|
-
* sandbox entries are appended here too —
|
|
1205
|
+
* sandbox entries are appended here too — legacy supports helpers resolve
|
|
1159
1206
|
* `supports('perm:<x>')` as a plain `domains` membership check.
|
|
1160
1207
|
* - `protocols` — `{ inc: ['NAP-01'..'NAP-06'] }`, derived from
|
|
1161
1208
|
* `NAP_INC_PROTOCOLS` by stripping the `inc:` prefix.
|
|
1162
1209
|
*
|
|
1163
|
-
* Emitted as a
|
|
1210
|
+
* Emitted as a superset alongside `naps`/`sandbox` for back-compat.
|
|
1164
1211
|
*
|
|
1165
1212
|
* @param hooks - The ShellAdapter provided by the host app
|
|
1166
|
-
* @returns ShellCapabilities with domains/protocols (
|
|
1213
|
+
* @returns ShellCapabilities with domains/protocols (compatibility shape) plus
|
|
1167
1214
|
* naps (NAP vocab) and sandbox (perm:-prefixed) arrays
|
|
1168
1215
|
* @example
|
|
1169
1216
|
* ```ts
|
|
@@ -1751,8 +1798,9 @@ declare function createKeysForwarder(deps: KeysForwarderDeps): KeysForwarder;
|
|
|
1751
1798
|
* fields and the current upstream-compatible fields.
|
|
1752
1799
|
*
|
|
1753
1800
|
* Resource protocol:
|
|
1754
|
-
* Inbound: resource.bytes, resource.bytesMany, resource.cancel
|
|
1755
|
-
* Outbound: resource.
|
|
1801
|
+
* Inbound: resource.info, resource.bytes, resource.bytesMany, resource.cancel
|
|
1802
|
+
* Outbound: resource.info.result, resource.info.error,
|
|
1803
|
+
* resource.bytes.result, resource.bytes.error,
|
|
1756
1804
|
* resource.bytesMany.result, resource.bytesMany.error
|
|
1757
1805
|
*/
|
|
1758
1806
|
/**
|
|
@@ -1760,6 +1808,20 @@ declare function createKeysForwarder(deps: KeysForwarderDeps): KeysForwarder;
|
|
|
1760
1808
|
* error / cancel envelope.
|
|
1761
1809
|
*/
|
|
1762
1810
|
type ResourceRequestId = string;
|
|
1811
|
+
/** Advisory resource capability and policy limits disclosed by the runtime. */
|
|
1812
|
+
interface ResourceInfo {
|
|
1813
|
+
schemes: readonly {
|
|
1814
|
+
scheme: string;
|
|
1815
|
+
enabled: boolean;
|
|
1816
|
+
}[];
|
|
1817
|
+
maxBytes?: number;
|
|
1818
|
+
maxUrls?: number;
|
|
1819
|
+
}
|
|
1820
|
+
/** Inbound: napplet asks for advisory resource policy and scheme support. */
|
|
1821
|
+
interface ResourceInfoRequest {
|
|
1822
|
+
type: 'resource.info';
|
|
1823
|
+
id: ResourceRequestId;
|
|
1824
|
+
}
|
|
1763
1825
|
/**
|
|
1764
1826
|
* Inbound: napplet requests bytes from an origin. Shell consults
|
|
1765
1827
|
* getConnectGrants(dTag, aggregateHash) before proxying; ungranted origins
|
|
@@ -1867,13 +1929,26 @@ interface ResourceBytesManyError {
|
|
|
1867
1929
|
error: string;
|
|
1868
1930
|
message?: string;
|
|
1869
1931
|
}
|
|
1932
|
+
/** Outbound: advisory resource policy and scheme support. */
|
|
1933
|
+
interface ResourceInfoResult {
|
|
1934
|
+
type: 'resource.info.result';
|
|
1935
|
+
id: ResourceRequestId;
|
|
1936
|
+
info: ResourceInfo;
|
|
1937
|
+
}
|
|
1938
|
+
/** Outbound: resource info could not be resolved. */
|
|
1939
|
+
interface ResourceInfoError {
|
|
1940
|
+
type: 'resource.info.error';
|
|
1941
|
+
id: ResourceRequestId;
|
|
1942
|
+
error: string;
|
|
1943
|
+
message?: string;
|
|
1944
|
+
}
|
|
1870
1945
|
/**
|
|
1871
1946
|
* Union of all inbound resource wire messages (napplet -> shell).
|
|
1872
1947
|
*/
|
|
1873
|
-
type ResourceInbound = ResourceBytesRequest | ResourceBytesManyRequest | ResourceCancelRequest;
|
|
1948
|
+
type ResourceInbound = ResourceInfoRequest | ResourceBytesRequest | ResourceBytesManyRequest | ResourceCancelRequest;
|
|
1874
1949
|
/**
|
|
1875
1950
|
* Union of all outbound resource wire messages (shell -> napplet).
|
|
1876
1951
|
*/
|
|
1877
|
-
type ResourceOutbound = ResourceBytesResult | ResourceBytesError | ResourceBytesManyResult | ResourceBytesManyError;
|
|
1952
|
+
type ResourceOutbound = ResourceInfoResult | ResourceInfoError | ResourceBytesResult | ResourceBytesError | ResourceBytesManyResult | ResourceBytesManyError;
|
|
1878
1953
|
|
|
1879
|
-
export { type AclCheckEvent, type AclEntry, 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 KeysForwarder, type KeysForwarderDeps, type KeysForwarderOriginRegistry, type KeysForwarderSessionRegistry, type KeysProxy, type KeysProxyDeps, type LinkHooks, type ListsHooks, type ManifestCacheEntry, type MediaProxy, type MediaProxyDeps, type NotifyProxy, type NotifyProxyDeps, 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 ResourceOutbound, type ResourceRequestId, type SerialHooks, type ShellAdapter, type ShellBridge, type ShellCapabilities, type ThemeProxy, type ThemeProxyDeps, type UnroutedMessageInfo, type UploadBackendLike, type UploadHooks, type WebrtcHooks, type WindowManagerHooks, type WorkerRelayHooks, type WorkerRelayLike, adaptHooks, audioManager, buildShellCapabilities, createIdentityProxy, createKeysForwarder, createKeysProxy, createMediaProxy, createNotifyProxy, createShellBridge, createThemeProxy, manifestCache, nappKeyRegistry, originRegistry, sessionRegistry };
|
|
1954
|
+
export { type AclCheckEvent, type AclEntry, 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 KeysForwarder, type KeysForwarderDeps, type KeysForwarderOriginRegistry, type KeysForwarderSessionRegistry, type KeysProxy, type KeysProxyDeps, type LinkHooks, type ListsHooks, type ManifestCacheEntry, type MediaProxy, type MediaProxyDeps, type NappletNamespacePreludeOptions, type NotifyProxy, type NotifyProxyDeps, 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 ResourceInfo, type ResourceInfoError, type ResourceInfoRequest, type ResourceInfoResult, type ResourceOutbound, type ResourceRequestId, type SerialHooks, type ShellAdapter, type ShellBridge, type ShellCapabilities, type ThemeProxy, type ThemeProxyDeps, type UnroutedMessageInfo, type UploadBackendLike, type UploadHooks, type WebrtcHooks, type WindowManagerHooks, type WorkerRelayHooks, type WorkerRelayLike, adaptHooks, audioManager, buildShellCapabilities, createIdentityProxy, createKeysForwarder, createKeysProxy, createMediaProxy, createNotifyProxy, createShellBridge, createThemeProxy, injectNappletNamespacePrelude, manifestCache, nappKeyRegistry, originRegistry, renderNappletNamespacePrelude, sessionRegistry };
|
package/dist/index.js
CHANGED
|
@@ -318,6 +318,7 @@ function adaptHooks(shellHooks, deps) {
|
|
|
318
318
|
|
|
319
319
|
// src/origin-registry.ts
|
|
320
320
|
var registry = /* @__PURE__ */ new Map();
|
|
321
|
+
var nextRegistrationId = 0;
|
|
321
322
|
var originRegistry = {
|
|
322
323
|
/**
|
|
323
324
|
* Register a window reference with a windowId and optional identity metadata.
|
|
@@ -327,10 +328,16 @@ var originRegistry = {
|
|
|
327
328
|
* @param identity - Optional NIP-5D identity metadata (dTag and aggregateHash)
|
|
328
329
|
*/
|
|
329
330
|
register(win, windowId, identity) {
|
|
331
|
+
for (const [registeredWin, entry] of registry.entries()) {
|
|
332
|
+
if (registeredWin === win || entry.windowId === windowId) {
|
|
333
|
+
registry.delete(registeredWin);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
330
336
|
registry.set(win, {
|
|
331
337
|
windowId,
|
|
332
338
|
dTag: identity?.dTag,
|
|
333
|
-
aggregateHash: identity?.aggregateHash
|
|
339
|
+
aggregateHash: identity?.aggregateHash,
|
|
340
|
+
registrationId: ++nextRegistrationId
|
|
334
341
|
});
|
|
335
342
|
},
|
|
336
343
|
/**
|
|
@@ -385,6 +392,15 @@ var originRegistry = {
|
|
|
385
392
|
if (!entry?.dTag || !entry?.aggregateHash) return void 0;
|
|
386
393
|
return { dTag: entry.dTag, aggregateHash: entry.aggregateHash };
|
|
387
394
|
},
|
|
395
|
+
/**
|
|
396
|
+
* Look up the monotonically increasing registration id for a Window.
|
|
397
|
+
*
|
|
398
|
+
* @param win - The Window reference to look up
|
|
399
|
+
* @returns The registration id, or undefined if the Window is not registered
|
|
400
|
+
*/
|
|
401
|
+
getRegistrationId(win) {
|
|
402
|
+
return registry.get(win)?.registrationId;
|
|
403
|
+
},
|
|
388
404
|
/** Clear all registrations. */
|
|
389
405
|
clear() {
|
|
390
406
|
registry.clear();
|
|
@@ -1006,6 +1022,7 @@ function buildShellCapabilities(hooks) {
|
|
|
1006
1022
|
if (hooks.serial?.isAvailable()) domains.push("serial");
|
|
1007
1023
|
if (hooks.ble?.isAvailable()) domains.push("ble");
|
|
1008
1024
|
if (hooks.webrtc?.isAvailable()) domains.push("webrtc");
|
|
1025
|
+
if (hooks.dm) domains.push("dm");
|
|
1009
1026
|
const sandbox = [];
|
|
1010
1027
|
domains.push(...sandbox);
|
|
1011
1028
|
const protocols = {};
|
|
@@ -1022,6 +1039,7 @@ function buildShellCapabilities(hooks) {
|
|
|
1022
1039
|
if (hooks.serial?.isAvailable()) naps.push("serial");
|
|
1023
1040
|
if (hooks.ble?.isAvailable()) naps.push("ble");
|
|
1024
1041
|
if (hooks.webrtc?.isAvailable()) naps.push("webrtc");
|
|
1042
|
+
if (hooks.dm) naps.push("dm");
|
|
1025
1043
|
return applyCapabilityOverrides(
|
|
1026
1044
|
{ domains, protocols, naps, sandbox },
|
|
1027
1045
|
hooks.capabilities?.disabledDomains ?? []
|
|
@@ -1043,20 +1061,29 @@ function applyCapabilityOverrides(capabilities, disabledDomains) {
|
|
|
1043
1061
|
}
|
|
1044
1062
|
|
|
1045
1063
|
// src/shell-ready.ts
|
|
1046
|
-
var initSent = /* @__PURE__ */ new
|
|
1064
|
+
var initSent = /* @__PURE__ */ new WeakMap();
|
|
1047
1065
|
function handleShellReady({
|
|
1048
1066
|
hooks,
|
|
1049
1067
|
origin,
|
|
1050
1068
|
runtime,
|
|
1069
|
+
sourceRegistrationId,
|
|
1070
|
+
sourceWindow,
|
|
1051
1071
|
windowId
|
|
1052
1072
|
}) {
|
|
1053
|
-
registerNip5dSessionIfNeeded({
|
|
1054
|
-
|
|
1073
|
+
registerNip5dSessionIfNeeded({
|
|
1074
|
+
hooks,
|
|
1075
|
+
origin,
|
|
1076
|
+
runtime,
|
|
1077
|
+
sourceRegistrationId,
|
|
1078
|
+
sourceWindow,
|
|
1079
|
+
windowId
|
|
1080
|
+
});
|
|
1081
|
+
if (initSent.get(sourceWindow) === sourceRegistrationId) {
|
|
1055
1082
|
return;
|
|
1056
1083
|
}
|
|
1057
1084
|
const capabilities = buildShellCapabilities(hooks);
|
|
1058
|
-
postShellInit(
|
|
1059
|
-
initSent.
|
|
1085
|
+
postShellInit(sourceWindow, capabilities, Object.keys(hooks.services ?? {}));
|
|
1086
|
+
initSent.set(sourceWindow, sourceRegistrationId);
|
|
1060
1087
|
}
|
|
1061
1088
|
function registerNip5dSessionIfNeeded({
|
|
1062
1089
|
hooks,
|
|
@@ -1105,14 +1132,13 @@ function resolveNip5dIdentity(hooks, windowId) {
|
|
|
1105
1132
|
aggregateHash: originIdentity.aggregateHash
|
|
1106
1133
|
};
|
|
1107
1134
|
}
|
|
1108
|
-
function postShellInit(
|
|
1135
|
+
function postShellInit(win, capabilities, services) {
|
|
1109
1136
|
const initMsg = {
|
|
1110
1137
|
type: "shell.init",
|
|
1111
1138
|
capabilities,
|
|
1112
1139
|
services
|
|
1113
1140
|
};
|
|
1114
|
-
|
|
1115
|
-
if (win) win.postMessage(initMsg, "*");
|
|
1141
|
+
win.postMessage(initMsg, "*");
|
|
1116
1142
|
}
|
|
1117
1143
|
|
|
1118
1144
|
// src/shell-bridge.ts
|
|
@@ -1174,7 +1200,14 @@ function createShellBridge(hooks) {
|
|
|
1174
1200
|
const msg = event.data;
|
|
1175
1201
|
if (typeof msg !== "object" || msg === null || typeof msg.type !== "string") return;
|
|
1176
1202
|
if (msg.type === "shell.ready") {
|
|
1177
|
-
handleShellReady({
|
|
1203
|
+
handleShellReady({
|
|
1204
|
+
hooks,
|
|
1205
|
+
origin: event.origin,
|
|
1206
|
+
runtime,
|
|
1207
|
+
sourceRegistrationId: originRegistry.getRegistrationId(sourceWindow) ?? 0,
|
|
1208
|
+
sourceWindow,
|
|
1209
|
+
windowId
|
|
1210
|
+
});
|
|
1178
1211
|
return;
|
|
1179
1212
|
}
|
|
1180
1213
|
runtime.handleMessage(windowId, msg);
|
|
@@ -1203,6 +1236,69 @@ function createShellBridge(hooks) {
|
|
|
1203
1236
|
};
|
|
1204
1237
|
}
|
|
1205
1238
|
|
|
1239
|
+
// src/napplet-namespace.ts
|
|
1240
|
+
function uniqueBareDomains(domains) {
|
|
1241
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1242
|
+
const normalized = [];
|
|
1243
|
+
for (const domain of domains) {
|
|
1244
|
+
const value = domain.trim();
|
|
1245
|
+
if (!value || value.includes(":") || value.startsWith("perm:") || seen.has(value)) {
|
|
1246
|
+
continue;
|
|
1247
|
+
}
|
|
1248
|
+
seen.add(value);
|
|
1249
|
+
normalized.push(value);
|
|
1250
|
+
}
|
|
1251
|
+
return normalized;
|
|
1252
|
+
}
|
|
1253
|
+
function scriptJson(value) {
|
|
1254
|
+
return JSON.stringify(value).replace(/</g, "\\u003c");
|
|
1255
|
+
}
|
|
1256
|
+
function renderNappletNamespacePrelude(options) {
|
|
1257
|
+
const domains = uniqueBareDomains(options.domains);
|
|
1258
|
+
return `<script data-kehto-nip5d-injection>(${nappletNamespacePrelude.toString()})(${scriptJson(domains)});</script>`;
|
|
1259
|
+
}
|
|
1260
|
+
function injectNappletNamespacePrelude(html, options) {
|
|
1261
|
+
const prelude = renderNappletNamespacePrelude(options);
|
|
1262
|
+
const cspMeta = /(<meta\s+http-equiv=["']Content-Security-Policy["'][^>]*>)/i;
|
|
1263
|
+
if (cspMeta.test(html)) {
|
|
1264
|
+
return html.replace(cspMeta, `$1${prelude}`);
|
|
1265
|
+
}
|
|
1266
|
+
if (/<head[^>]*>/i.test(html)) {
|
|
1267
|
+
return html.replace(/<head[^>]*>/i, (open) => `${open}${prelude}`);
|
|
1268
|
+
}
|
|
1269
|
+
if (/<html[^>]*>/i.test(html)) {
|
|
1270
|
+
return html.replace(/<html[^>]*>/i, (open) => `${open}<head>${prelude}</head>`);
|
|
1271
|
+
}
|
|
1272
|
+
return `${prelude}${html}`;
|
|
1273
|
+
}
|
|
1274
|
+
function nappletNamespacePrelude(domains) {
|
|
1275
|
+
const target = window;
|
|
1276
|
+
function buildNappletNamespace(value) {
|
|
1277
|
+
const candidate = typeof value === "object" && value !== null ? value : {};
|
|
1278
|
+
const next = {};
|
|
1279
|
+
for (const domain of domains) {
|
|
1280
|
+
Object.defineProperty(next, domain, {
|
|
1281
|
+
value: Object.prototype.hasOwnProperty.call(candidate, domain) ? candidate[domain] : {},
|
|
1282
|
+
enumerable: true,
|
|
1283
|
+
configurable: true,
|
|
1284
|
+
writable: true
|
|
1285
|
+
});
|
|
1286
|
+
}
|
|
1287
|
+
return next;
|
|
1288
|
+
}
|
|
1289
|
+
let root = buildNappletNamespace(target.napplet);
|
|
1290
|
+
Object.defineProperty(target, "napplet", {
|
|
1291
|
+
get() {
|
|
1292
|
+
return root;
|
|
1293
|
+
},
|
|
1294
|
+
set(value) {
|
|
1295
|
+
root = buildNappletNamespace(value);
|
|
1296
|
+
},
|
|
1297
|
+
enumerable: false,
|
|
1298
|
+
configurable: true
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1206
1302
|
// src/index.ts
|
|
1207
1303
|
import { ALL_CAPABILITIES as ALL_CAPABILITIES2 } from "@kehto/runtime";
|
|
1208
1304
|
import { createEnforceGate, createNapEnforceGate, formatDenialReason } from "@kehto/runtime";
|
|
@@ -1290,9 +1386,11 @@ export {
|
|
|
1290
1386
|
createShellBridge,
|
|
1291
1387
|
createThemeProxy,
|
|
1292
1388
|
formatDenialReason,
|
|
1389
|
+
injectNappletNamespacePrelude,
|
|
1293
1390
|
manifestCache,
|
|
1294
1391
|
nappKeyRegistry,
|
|
1295
1392
|
originRegistry,
|
|
1393
|
+
renderNappletNamespacePrelude,
|
|
1296
1394
|
sessionRegistry
|
|
1297
1395
|
};
|
|
1298
1396
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/shell-bridge.ts","../src/hooks-adapter.ts","../src/origin-registry.ts","../src/session-registry.ts","../src/acl-store.ts","../src/manifest-cache.ts","../src/audio-manager.ts","../src/keys-forwarder.ts","../src/shell-init.ts","../src/shell-ready.ts","../src/index.ts","../src/topics.ts","../src/identity-proxy.ts","../src/theme-proxy.ts","../src/keys-proxy.ts","../src/media-proxy.ts","../src/notify-proxy.ts"],"sourcesContent":["\nimport { createRuntime, type ConsentRequest, type Runtime } from '@kehto/runtime';\nimport { adaptHooks } from './hooks-adapter.js';\nimport { originRegistry } from './origin-registry.js';\nimport { sessionRegistry, nappKeyRegistry } from './session-registry.js';\nimport { aclStore } from './acl-store.js';\nimport { manifestCache } from './manifest-cache.js';\nimport { audioManager } from './audio-manager.js';\nimport type { ShellAdapter, UnroutedMessageInfo } from './types.js';\nimport type { NappletMessage } from '@napplet/core';\nimport type { Theme } from '@napplet/nap/theme/types';\nimport { createKeysForwarder, type KeysForwarder } from './keys-forwarder.js';\nimport { handleShellReady } from './shell-ready.js';\n\n/**\n * Shell-side message bridge that handles NIP-5D communication with napplet iframes.\n *\n * The bridge acts as a browser adapter: it receives raw MessageEvents from\n * window.addEventListener('message', ...), extracts the source Window, resolves\n * it to a windowId via originRegistry, and delegates NIP-5D envelope messages\n * to the runtime engine. The shell.ready/shell.init capability handshake is\n * handled locally within the bridge and never forwarded to the runtime.\n *\n * @example\n * ```ts\n * import { createShellBridge } from '@kehto/shell';\n *\n * const bridge = createShellBridge(hooks);\n * window.addEventListener('message', bridge.handleMessage);\n * ```\n */\nexport interface ShellBridge {\n /**\n * Handle an incoming postMessage from a napplet iframe.\n *\n * Only NIP-5D envelope objects (plain objects with a `.type` string) are\n * accepted. NIP-01 arrays and all other message shapes are silently dropped\n * (clean break — no legacy array fallback).\n *\n * shell.ready messages are handled locally: the bridge responds with shell.init\n * containing the capability set and registered service list. All other envelopes\n * are delegated to the runtime's NAP domain dispatch.\n *\n * @param event - The raw MessageEvent from window.addEventListener('message', ...)\n * @example\n * ```ts\n * window.addEventListener('message', bridge.handleMessage);\n * ```\n */\n handleMessage(event: MessageEvent): void;\n\n /**\n * Inject a shell-originated event into subscription delivery. Under NIP-5D,\n * shell-originated events are forwarded to napplets as inc.event envelope\n * messages. The runtime's injectEvent() handles the per-session routing.\n *\n * v1.10 hard-removed the v1.8 soft-rename compatibility branch for the\n * old `auth:identity-changed` topic. Use the canonical `identity:changed`\n * topic for identity-change pushes.\n *\n * @param topic - The event topic tag value. Forwarded exactly once.\n * @param payload - The event content\n * @example\n * ```ts\n * bridge.injectEvent('identity:changed', { pubkey: userPubkey });\n * ```\n */\n injectEvent(topic: string, payload: unknown): void;\n\n /**\n * Destroy the bridge instance, cleaning up all internal state.\n * Persists manifest cache and clears all subscriptions, buffers, and registries.\n * Call when the shell is shutting down or the bridge is no longer needed.\n *\n * @example\n * ```ts\n * bridge.destroy();\n * ```\n */\n destroy(): void;\n\n /**\n * Register a handler for consent requests on destructive signing kinds.\n * Called when a napplet requests signing for kinds 0, 3, 5, or 10002.\n *\n * @param handler - Callback receiving the consent request with a resolve function\n * @example\n * ```ts\n * bridge.registerConsentHandler((request) => {\n * const allowed = confirm(`Allow signing kind ${request.event.kind}?`);\n * request.resolve(allowed);\n * });\n * ```\n */\n registerConsentHandler(handler: (request: ConsentRequest) => void): void;\n\n /**\n * Publish a theme update to every registered napplet.\n *\n * Posts a `theme.changed` envelope (shell → napplet push) to every\n * window currently tracked by the runtime's sessionRegistry, using\n * the browser-adapter originRegistry to resolve windowId → iframe.\n * Napplets whose window cannot be resolved (stale sessions) are\n * silently skipped; this method never throws.\n *\n * ACL is enforced BY THE RECIPIENT NAPPLET — the runtime's\n * `themeMap` in @kehto/acl assigns `recipientCap: 'theme:read'` for\n * `theme.changed`, and @napplet/shim drops pushes the napplet lacks\n * the capability for. Hosts should not self-filter here.\n *\n * @param theme - The new theme payload to broadcast.\n * @example\n * ```ts\n * bridge.publishTheme({\n * colors: { background: '#0a0a0a', text: '#e0e0e0', primary: '#7aa2f7' },\n * title: 'Dark',\n * });\n * ```\n */\n publishTheme(theme: Theme): void;\n\n /**\n * Publish the current shell-user identity to every loaded napplet.\n *\n * Posts an `identity.changed` envelope (shell → napplet push) with the\n * current user pubkey. An empty pubkey means no signer/user identity is\n * currently connected. This is distinct from NIP-5D napplet session identity,\n * which remains source-bound at iframe creation.\n *\n * @param pubkey - Current user's hex pubkey, or empty string when signed out.\n */\n publishIdentityChanged(pubkey: string): void;\n\n /**\n * Access the underlying runtime instance for advanced use cases.\n * Provides direct access to the runtime's sessionRegistry, aclState,\n * and manifestCache.\n */\n readonly runtime: Runtime;\n\n}\n\n/**\n * Create a ShellBridge instance with dependency injection via hooks.\n *\n * Internally creates a Runtime from @kehto/runtime and adapts the\n * browser-oriented ShellAdapter into environment-agnostic RuntimeAdapter.\n *\n * @param hooks - Host application provides relay pool, auth, config, etc.\n * @returns A ShellBridge instance ready to handle napplet messages\n * @example\n * ```ts\n * import { createShellBridge, type ShellAdapter } from '@kehto/shell';\n *\n * const hooks: ShellAdapter = {\n * relayPool: myRelayPoolHooks,\n * relayConfig: myRelayConfigHooks,\n * windowManager: myWindowManagerHooks,\n * auth: myAuthHooks,\n * config: myConfigHooks,\n * hotkeys: myHotkeyHooks,\n * workerRelay: myWorkerRelayHooks,\n * crypto: myCryptoHooks,\n * };\n * const bridge = createShellBridge(hooks);\n * ```\n */\n/**\n * Fire the optional {@link ShellAdapter.onUnroutedMessage} diagnostic hook for a\n * message the bridge is about to drop. Extracts the envelope `type` defensively\n * (the message-shape guard hasn't run yet at the drop points) and never throws —\n * a misbehaving host hook must not break message handling.\n */\nfunction reportUnrouted(\n hooks: ShellAdapter,\n event: MessageEvent,\n reason: UnroutedMessageInfo['reason'],\n): void {\n if (!hooks.onUnroutedMessage) return;\n const data = event.data as { type?: unknown } | null | undefined;\n const type = typeof data === 'object' && data !== null && typeof data.type === 'string'\n ? data.type\n : undefined;\n try {\n hooks.onUnroutedMessage({ type, origin: event.origin, reason });\n } catch {\n // Observability must never break routing — swallow host hook errors.\n }\n}\n\nexport function createShellBridge(hooks: ShellAdapter): ShellBridge {\n const runtimeHooks = adaptHooks(hooks, {\n originRegistry,\n manifestCache,\n aclStore,\n audioManager,\n nappKeyRegistry,\n });\n\n const runtime: Runtime = createRuntime(runtimeHooks);\n\n function broadcastToNapplets(envelope: NappletMessage): void {\n // Use originRegistry.getAllWindowIds() rather than sessionRegistry.getAllEntries()\n // because demo napplets share pubkey:'' — the byPubkey map only retains one entry\n // per pubkey key, so getAllEntries() would return only the last-registered napplet\n // when multiple napplets have the same (empty) pubkey. originRegistry is keyed by\n // Window reference so it has one entry per distinct iframe regardless of pubkey.\n const windowIds = originRegistry.getAllWindowIds();\n for (const windowId of windowIds) {\n const win = originRegistry.getIframeWindow(windowId);\n if (!win) continue;\n win.postMessage(envelope, '*');\n }\n }\n\n // Attach the host-keydown forwarder (Plan 12-11 / NAP-05 shell half).\n // Skips construction in DOM-less environments (SSR / early Node tests);\n // any failure is swallowed so a malformed DOM never blocks bridge creation.\n let keysForwarder: KeysForwarder | null = null;\n if (typeof window !== 'undefined') {\n try {\n keysForwarder = createKeysForwarder({\n originRegistry,\n sessionRegistry,\n hasKeysForwardCap: (pubkey: string) => {\n const entry = sessionRegistry.getEntry(pubkey);\n if (!entry) return false;\n const acl = aclStore.getEntry(entry.pubkey, entry.dTag, entry.aggregateHash);\n return acl?.capabilities.includes('keys:forward') ?? false;\n },\n });\n } catch {\n // DOM present but addEventListener failed — proceed without the forwarder.\n keysForwarder = null;\n }\n }\n\n return {\n handleMessage(event: MessageEvent): void {\n const sourceWindow = event.source as Window | null;\n if (!sourceWindow) {\n reportUnrouted(hooks, event, 'no-source-window');\n return;\n }\n const windowId = originRegistry.getWindowId(sourceWindow);\n if (!windowId) {\n reportUnrouted(hooks, event, 'unregistered-window');\n return;\n }\n const msg = event.data;\n\n // NIP-5D envelope-only guard (clean break — no legacy array support)\n if (typeof msg !== 'object' || msg === null || typeof msg.type !== 'string') return;\n\n // Handle shell.ready handshake locally (not forwarded to runtime)\n if (msg.type === 'shell.ready') {\n handleShellReady({ hooks, origin: event.origin, runtime, windowId });\n return;\n }\n\n // Delegate to runtime — runtime handles NAP domain dispatch\n runtime.handleMessage(windowId, msg);\n },\n\n injectEvent(topic: string, payload: unknown): void {\n runtime.injectEvent(topic, payload);\n },\n\n destroy(): void {\n keysForwarder?.destroy();\n runtime.destroy();\n },\n\n registerConsentHandler(handler: (request: ConsentRequest) => void): void {\n runtime.registerConsentHandler(handler);\n },\n\n publishTheme(theme: Theme): void {\n const envelope: NappletMessage = { type: 'theme.changed', theme } as NappletMessage;\n broadcastToNapplets(envelope);\n },\n\n publishIdentityChanged(pubkey: string): void {\n const envelope: NappletMessage = { type: 'identity.changed', pubkey } as NappletMessage;\n broadcastToNapplets(envelope);\n },\n\n get runtime() {\n return runtime;\n },\n };\n}\n","/**\n * hooks-adapter.ts — Converts ShellAdapter (browser-facing) to RuntimeAdapter (environment-agnostic).\n *\n * The adapter bridges the gap between the shell's browser-oriented ShellAdapter interfaces\n * (Window references, localStorage, postMessage) and the runtime's abstract RuntimeAdapter\n * (windowId strings, persistence interfaces, sendToNapplet callbacks).\n */\n\nimport type { NostrEvent, NostrFilter } from '@napplet/core';\nimport type {\n RuntimeAdapter,\n RelayPoolAdapter,\n CacheAdapter,\n AuthAdapter,\n Signer,\n ConfigAdapter,\n HotkeyAdapter,\n AclPersistence,\n ManifestPersistence,\n StatePersistence,\n CryptoAdapter,\n WindowManagerAdapter,\n RelayConfigAdapter,\n DmAdapter,\n SendToNapplet,\n RelaySubscriptionHandle,\n ShellSecretPersistence,\n GuidPersistence,\n} from '@kehto/runtime';\n\nfunction bytesToHex(bytes: Uint8Array): string {\n return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');\n}\n\nfunction hexToBytes(hex: string): Uint8Array {\n const bytes = new Uint8Array(hex.length / 2);\n for (let i = 0; i < hex.length; i += 2) {\n bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16);\n }\n return bytes;\n}\nimport type { ShellAdapter } from './types.js';\nimport type { originRegistry as OriginRegistryType } from './origin-registry.js';\nimport type { manifestCache as ManifestCacheType } from './manifest-cache.js';\nimport type { aclStore as AclStoreType } from './acl-store.js';\nimport type { audioManager as AudioManagerType } from './audio-manager.js';\nimport type { sessionRegistry as SessionRegistryType } from './session-registry.js';\n\n/**\n * Browser-specific singletons that the adapter bridges to the runtime.\n * These use browser APIs (Window, localStorage, postMessage, CustomEvent)\n * that the runtime cannot access directly.\n */\nexport interface BrowserDeps {\n originRegistry: typeof OriginRegistryType;\n manifestCache: typeof ManifestCacheType;\n aclStore: typeof AclStoreType;\n audioManager: typeof AudioManagerType;\n nappKeyRegistry: typeof SessionRegistryType;\n}\n\nfunction createSendToNapplet(originRegistry: BrowserDeps['originRegistry']): SendToNapplet {\n return (windowId, msg) => {\n const win = originRegistry.getIframeWindow(windowId);\n if (win) win.postMessage(msg, '*');\n };\n}\n\nfunction createRelayPoolAdapter(\n shellHooks: ShellAdapter,\n originRegistry: BrowserDeps['originRegistry'],\n): RelayPoolAdapter {\n return {\n subscribe(\n filters: NostrFilter[],\n callback: (item: NostrEvent | 'EOSE') => void,\n relayUrls?: string[],\n ): RelaySubscriptionHandle {\n const pool = shellHooks.relayPool.getRelayPool();\n if (!pool) return { unsubscribe() { /* no-op */ } };\n\n const urls = relayUrls ?? shellHooks.relayPool.selectRelayTier(filters);\n const sub = pool.subscription(urls, filters).subscribe((item) => {\n if (item === 'EOSE') callback('EOSE');\n else callback(item as NostrEvent);\n });\n return { unsubscribe: () => sub.unsubscribe() };\n },\n\n publish(event: NostrEvent): void {\n const pool = shellHooks.relayPool.getRelayPool();\n if (!pool) return;\n const relayUrls = shellHooks.relayPool.selectRelayTier([]);\n pool.publish(relayUrls, event);\n },\n\n selectRelayTier(filters: NostrFilter[]): string[] {\n return shellHooks.relayPool.selectRelayTier(filters);\n },\n\n trackSubscription(subKey: string, cleanup: () => void): void {\n shellHooks.relayPool.trackSubscription(subKey, cleanup);\n },\n\n untrackSubscription(subKey: string): void {\n shellHooks.relayPool.untrackSubscription(subKey);\n },\n\n openScopedRelay(\n windowId: string,\n relayUrl: string,\n subId: string,\n filters: NostrFilter[],\n _sendFn: SendToNapplet,\n ): void {\n const win = originRegistry.getIframeWindow(windowId);\n if (win) shellHooks.relayPool.openScopedRelay(windowId, relayUrl, subId, filters, win);\n },\n\n closeScopedRelay(windowId: string): void {\n shellHooks.relayPool.closeScopedRelay(windowId);\n },\n\n publishToScopedRelay(windowId: string, event: NostrEvent): boolean {\n return shellHooks.relayPool.publishToScopedRelay(windowId, event);\n },\n\n isAvailable(): boolean {\n return shellHooks.relayPool.getRelayPool() !== null;\n },\n };\n}\n\nfunction createCacheAdapter(shellHooks: ShellAdapter): CacheAdapter {\n return {\n async query(filters: NostrFilter[]): Promise<NostrEvent[]> {\n const workerRelay = shellHooks.workerRelay.getWorkerRelay();\n if (!workerRelay) return [];\n const subId = crypto.randomUUID();\n return workerRelay.query(['REQ', subId, ...filters]);\n },\n\n store(event: NostrEvent): void {\n const workerRelay = shellHooks.workerRelay.getWorkerRelay();\n if (!workerRelay) return;\n try { workerRelay.event(event)?.catch?.(() => { /* best-effort */ }); } catch { /* best-effort */ }\n },\n\n isAvailable(): boolean {\n return shellHooks.workerRelay.getWorkerRelay() !== null;\n },\n };\n}\n\nfunction createAuthAdapter(shellHooks: ShellAdapter): AuthAdapter {\n return {\n getUserPubkey(): string | null {\n return shellHooks.auth.getUserPubkey();\n },\n getSigner(): Signer | null {\n return shellHooks.auth.getSigner();\n },\n };\n}\n\nfunction createConfigAdapter(shellHooks: ShellAdapter): ConfigAdapter {\n return {\n getNappUpdateBehavior(): 'auto-grant' | 'banner' | 'silent-reprompt' {\n return shellHooks.config.getNappUpdateBehavior();\n },\n };\n}\n\nfunction createHotkeyAdapter(shellHooks: ShellAdapter): HotkeyAdapter {\n return {\n executeHotkeyFromForward(event): void {\n shellHooks.hotkeys.executeHotkeyFromForward(event);\n },\n };\n}\n\nfunction createCryptoAdapter(shellHooks: ShellAdapter): CryptoAdapter {\n return {\n async verifyEvent(event: NostrEvent): Promise<boolean> {\n return shellHooks.crypto.verifyEvent(event);\n },\n randomUUID(): string {\n return crypto.randomUUID();\n },\n randomBytes(length: number): Uint8Array {\n const bytes = new Uint8Array(length);\n crypto.getRandomValues(bytes);\n return bytes;\n },\n };\n}\n\nfunction createAclPersistence(): AclPersistence {\n return {\n persist(data: string): void {\n try { localStorage.setItem('napplet:acl', data); } catch { /* best-effort */ }\n },\n load(): string | null {\n try { return localStorage.getItem('napplet:acl'); } catch { return null; }\n },\n };\n}\n\nfunction createManifestPersistence(): ManifestPersistence {\n return {\n persist(data: string): void {\n try { localStorage.setItem('napplet:manifest-cache', data); } catch { /* best-effort */ }\n },\n load(): string | null {\n try { return localStorage.getItem('napplet:manifest-cache'); } catch { return null; }\n },\n };\n}\n\nfunction createStatePersistence(): StatePersistence {\n return {\n get(scopedKey: string): string | null {\n try { return localStorage.getItem(scopedKey); } catch { return null; }\n },\n set(scopedKey: string, value: string): boolean {\n try { localStorage.setItem(scopedKey, value); return true; } catch { return false; }\n },\n remove(scopedKey: string): void {\n try { localStorage.removeItem(scopedKey); } catch { /* best-effort */ }\n },\n clear(prefix: string): void {\n try {\n const keysToRemove: string[] = [];\n for (let i = 0; i < localStorage.length; i++) {\n const key = localStorage.key(i);\n if (key?.startsWith(prefix)) keysToRemove.push(key);\n }\n for (const key of keysToRemove) localStorage.removeItem(key);\n } catch { /* best-effort */ }\n },\n keys(prefix: string): string[] {\n try {\n const result: string[] = [];\n for (let i = 0; i < localStorage.length; i++) {\n const key = localStorage.key(i);\n if (key?.startsWith(prefix)) result.push(key);\n }\n return result;\n } catch { return []; }\n },\n calculateBytes(prefix: string, excludeKey?: string): number {\n try {\n let total = 0;\n for (let i = 0; i < localStorage.length; i++) {\n const key = localStorage.key(i);\n if (!key?.startsWith(prefix)) continue;\n if (excludeKey && key === excludeKey) continue;\n const value = localStorage.getItem(key) ?? '';\n total += new TextEncoder().encode(key + value).length;\n }\n return total;\n } catch { return 0; }\n },\n };\n}\n\nfunction createWindowManagerAdapter(shellHooks: ShellAdapter): WindowManagerAdapter {\n return {\n createWindow(options): string | null {\n return shellHooks.windowManager.createWindow(options);\n },\n };\n}\n\nfunction createRelayConfigAdapter(shellHooks: ShellAdapter): RelayConfigAdapter {\n return {\n addRelay(tier: string, url: string): void {\n shellHooks.relayConfig.addRelay(tier, url);\n },\n removeRelay(tier: string, url: string): void {\n shellHooks.relayConfig.removeRelay(tier, url);\n },\n getRelayConfig(): { discovery: string[]; super: string[]; outbox: string[] } {\n return shellHooks.relayConfig.getRelayConfig();\n },\n getNip66Suggestions(): unknown {\n return shellHooks.relayConfig.getNip66Suggestions();\n },\n };\n}\n\nfunction createShellSecretPersistence(): ShellSecretPersistence {\n return {\n get(): Uint8Array | null {\n try {\n const hex = localStorage.getItem('napplet-shell-secret');\n if (!hex) return null;\n return hexToBytes(hex);\n } catch { return null; }\n },\n set(secret: Uint8Array): void {\n try {\n localStorage.setItem('napplet-shell-secret', bytesToHex(secret));\n } catch { /* localStorage unavailable */ }\n },\n };\n}\n\nfunction createGuidPersistence(): GuidPersistence {\n return {\n get(windowId: string): string | null {\n try {\n return localStorage.getItem(`napplet-guid:${windowId}`);\n } catch { return null; }\n },\n set(windowId: string, guid: string): void {\n try {\n localStorage.setItem(`napplet-guid:${windowId}`, guid);\n } catch { /* localStorage unavailable */ }\n },\n remove(windowId: string): void {\n try {\n localStorage.removeItem(`napplet-guid:${windowId}`);\n } catch { /* localStorage unavailable */ }\n },\n };\n}\n\nfunction createDmAdapter(shellHooks: ShellAdapter): DmAdapter | undefined {\n return shellHooks.dm\n ? {\n sendDm(recipientPubkey: string, message: string) {\n return shellHooks.dm!.sendDm(recipientPubkey, message);\n },\n }\n : undefined;\n}\n\n/**\n * Convert ShellAdapter (browser-facing) into RuntimeAdapter (environment-agnostic).\n *\n * The adapter is the single translation layer between browser APIs and the\n * runtime's abstract interfaces. It:\n * - Converts Window references to windowId strings via originRegistry\n * - Wraps localStorage-backed singletons into persistence interfaces\n * - Translates relay pool API shapes (Observable → callback)\n *\n * @param shellHooks - The browser-oriented ShellAdapter provided by the host app\n * @param deps - Browser-specific singletons (originRegistry, aclStore, etc.)\n * @returns RuntimeAdapter suitable for createRuntime()\n *\n * @example\n * ```ts\n * const runtimeHooks = adaptHooks(shellHooks, {\n * originRegistry, manifestCache, aclStore, audioManager, nappKeyRegistry,\n * });\n * const runtime = createRuntime(runtimeHooks);\n * ```\n */\nexport function adaptHooks(shellHooks: ShellAdapter, deps: BrowserDeps): RuntimeAdapter {\n const { originRegistry } = deps;\n\n return {\n sendToNapplet: createSendToNapplet(originRegistry),\n relayPool: createRelayPoolAdapter(shellHooks, originRegistry),\n cache: createCacheAdapter(shellHooks),\n auth: createAuthAdapter(shellHooks),\n config: createConfigAdapter(shellHooks),\n hotkeys: createHotkeyAdapter(shellHooks),\n crypto: createCryptoAdapter(shellHooks),\n aclPersistence: createAclPersistence(),\n manifestPersistence: createManifestPersistence(),\n statePersistence: createStatePersistence(),\n windowManager: createWindowManagerAdapter(shellHooks),\n relayConfig: createRelayConfigAdapter(shellHooks),\n dm: createDmAdapter(shellHooks),\n shellSecretPersistence: createShellSecretPersistence(),\n guidPersistence: createGuidPersistence(),\n onAclCheck: shellHooks.onAclCheck,\n onHashMismatch: shellHooks.onHashMismatch,\n services: shellHooks.services,\n getConfigOverrides: shellHooks.getConfigOverrides,\n };\n}\n","\ninterface OriginEntry {\n windowId: string;\n dTag?: string;\n aggregateHash?: string;\n}\n\nconst registry = new Map<Window, OriginEntry>();\n\n/**\n * Bidirectional registry mapping Window references to windowId strings.\n * Optionally stores NIP-5D identity metadata (dTag and aggregateHash) per window.\n *\n * @example\n * ```ts\n * import { originRegistry } from '@kehto/shell';\n *\n * originRegistry.register(iframe.contentWindow, 'napp-1');\n * const id = originRegistry.getWindowId(iframe.contentWindow); // 'napp-1'\n * ```\n */\nexport const originRegistry = {\n /**\n * Register a window reference with a windowId and optional identity metadata.\n *\n * @param win - The iframe's contentWindow reference\n * @param windowId - The unique identifier for this napplet window\n * @param identity - Optional NIP-5D identity metadata (dTag and aggregateHash)\n */\n register(win: Window, windowId: string, identity?: { dTag: string; aggregateHash: string }): void {\n registry.set(win, {\n windowId,\n dTag: identity?.dTag,\n aggregateHash: identity?.aggregateHash,\n });\n },\n\n /**\n * Unregister a window by its windowId, removing the mapping.\n *\n * @param windowId - The window identifier to remove\n */\n unregister(windowId: string): void {\n for (const [win, entry] of registry.entries()) {\n if (entry.windowId === windowId) {\n registry.delete(win);\n }\n }\n },\n\n /**\n * Look up the windowId for a given Window reference.\n *\n * @param win - The Window reference (typically from event.source)\n * @returns The windowId string, or undefined if not registered\n */\n getWindowId(win: Window): string | undefined {\n return registry.get(win)?.windowId;\n },\n\n /**\n * Look up the Window reference for a given windowId.\n *\n * @param windowId - The window identifier to look up\n * @returns The Window reference, or null if not found\n */\n getIframeWindow(windowId: string): Window | null {\n for (const [win, entry] of registry.entries()) {\n if (entry.windowId === windowId) return win;\n }\n return null;\n },\n\n /**\n * Get all registered windowId strings.\n *\n * @returns Array of all registered window identifiers\n */\n getAllWindowIds(): string[] {\n return Array.from(registry.values()).map(entry => entry.windowId);\n },\n\n /**\n * Get identity metadata for a registered Window.\n *\n * @param win - The Window reference to look up\n * @returns Identity metadata, or undefined if not registered or no identity set\n */\n getIdentity(win: Window): { dTag: string; aggregateHash: string } | undefined {\n const entry = registry.get(win);\n if (!entry?.dTag || !entry?.aggregateHash) return undefined;\n return { dTag: entry.dTag, aggregateHash: entry.aggregateHash };\n },\n\n /** Clear all registrations. */\n clear(): void {\n registry.clear();\n },\n};\n","/**\n * SessionRegistry — windowId to verified napplet pubkey bidirectional mapping.\n *\n * After a successful AUTH handshake, the ShellBridge registers the napplet's\n * verified pubkey here. Both mappings are kept in sync.\n */\n\nimport type { PendingUpdate, SessionEntry } from '@kehto/runtime';\n\n/**\n * A pending napplet update — raised when a napplet reconnects with a different aggregateHash.\n * @example\n * ```ts\n * const update: PendingUpdate = {\n * windowId: 'win-1', pubkey: 'abc...', dTag: '3chat',\n * oldHash: 'aaa', newHash: 'bbb',\n * resolve: (action) => { if (action === 'accept') { // apply } },\n * };\n * ```\n */\nexport type { PendingUpdate } from '@kehto/runtime';\n\nconst byWindowId = new Map<string, string>();\nconst byPubkey = new Map<string, SessionEntry>();\nconst pendingUpdates = new Map<string, PendingUpdate>();\n\nlet _pendingVersion = 0;\n\n/**\n * Bidirectional registry mapping windowIds to verified napplet pubkeys.\n * Maintained by ShellBridge after successful AUTH handshakes.\n *\n * @example\n * ```ts\n * import { sessionRegistry } from '@kehto/shell';\n *\n * const pubkey = sessionRegistry.getPubkey('win-1');\n * const entry = pubkey ? sessionRegistry.getEntry(pubkey) : undefined;\n * ```\n */\nexport const sessionRegistry = {\n /**\n * Register a napplet entry, mapping windowId to pubkey and vice versa.\n *\n * @param windowId - The window identifier\n * @param entry - The verified napplet session entry from AUTH handshake\n */\n register(windowId: string, entry: SessionEntry): void {\n byWindowId.set(windowId, entry.pubkey);\n byPubkey.set(entry.pubkey, entry);\n },\n\n /**\n * Unregister a napplet by windowId, removing both mappings.\n *\n * @param windowId - The window identifier to remove\n */\n unregister(windowId: string): void {\n const pubkey = byWindowId.get(windowId);\n if (pubkey) {\n byPubkey.delete(pubkey);\n byWindowId.delete(windowId);\n }\n pendingUpdates.delete(windowId);\n },\n\n /**\n * Get the pubkey associated with a windowId.\n *\n * @param windowId - The window identifier\n * @returns The napplet's pubkey, or undefined if not registered\n */\n getPubkey(windowId: string): string | undefined {\n return byWindowId.get(windowId);\n },\n\n /**\n * Get the full entry for a napplet pubkey.\n *\n * @param pubkey - The napplet's pubkey\n * @returns The full SessionEntry, or undefined if not found\n */\n getEntry(pubkey: string): SessionEntry | undefined {\n return byPubkey.get(pubkey);\n },\n\n /**\n * Get the windowId for a napplet pubkey.\n *\n * @param pubkey - The napplet's pubkey\n * @returns The windowId, or undefined if not found\n */\n getWindowId(pubkey: string): string | undefined {\n return byPubkey.get(pubkey)?.windowId;\n },\n\n /**\n * Check if a windowId has a registered napplet.\n *\n * @param windowId - The window identifier\n * @returns True if the windowId has a registered napplet\n */\n isRegistered(windowId: string): boolean {\n return byWindowId.has(windowId);\n },\n\n /**\n * Get all registered napplet entries.\n *\n * @returns Array of all SessionEntry objects\n */\n getAllEntries(): SessionEntry[] {\n return Array.from(byPubkey.values());\n },\n\n /**\n * Set a pending update for a window (napplet reconnected with different hash).\n *\n * @param windowId - The window identifier\n * @param update - The pending update details with resolve callback\n */\n setPendingUpdate(windowId: string, update: PendingUpdate): void {\n pendingUpdates.set(windowId, update);\n _pendingVersion++;\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new CustomEvent('napplet:pending-update', { detail: { windowId } }));\n }\n },\n\n /**\n * Get a pending update for a window.\n *\n * @param windowId - The window identifier\n * @returns The pending update, or undefined if none\n */\n getPendingUpdate(windowId: string): PendingUpdate | undefined {\n return pendingUpdates.get(windowId);\n },\n\n /**\n * Clear a pending update for a window.\n *\n * @param windowId - The window identifier\n */\n clearPendingUpdate(windowId: string): void {\n pendingUpdates.delete(windowId);\n _pendingVersion++;\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new CustomEvent('napplet:pending-update', { detail: { windowId } }));\n }\n },\n\n /** Clear all registrations and pending updates. */\n clear(): void {\n byWindowId.clear();\n byPubkey.clear();\n pendingUpdates.clear();\n },\n};\n\n/** @deprecated Use sessionRegistry. Will be removed in v0.9.0. */\nexport const nappKeyRegistry = sessionRegistry;\n","\nimport { ALL_CAPABILITIES, type Capability } from '@kehto/runtime';\nimport { migrateAclState, type AclState } from '@kehto/acl';\nimport type { AclEntry } from './types.js';\n\nconst STORAGE_KEY = 'napplet:acl';\n\n/** Default state quota in bytes (512 KB) per napplet identity. */\nexport const DEFAULT_STATE_QUOTA = 512 * 1024;\n\ninterface InternalAclEntry {\n key: string;\n pubkey: string;\n dTag: string;\n aggregateHash: string;\n capabilities: Set<Capability>;\n blocked: boolean;\n stateQuota: number;\n}\n\nfunction aclKey(_pubkey: string, dTag: string, aggregateHash: string): string {\n return `${dTag}:${aggregateHash}`;\n}\n\nconst store = new Map<string, InternalAclEntry>();\n\n// Capability name to bit position mapping for bitfield conversion.\n// Plan 12-10: signer bit slots (32/64/128) reclaimed by the v1.2\n// identity:read / keys:bind / keys:forward caps. v1.1 persisted ACL\n// state with those bits is reinterpreted under the v1.2 surface —\n// acceptable because the v1.1 signer actions have no napplet-visible\n// surface in NIP-5D.\nconst CAP_BITS: Record<string, number> = {\n 'relay:read': 1, 'relay:write': 2, 'cache:read': 4, 'cache:write': 8,\n 'hotkey:forward': 16,\n 'identity:read': 32, 'keys:bind': 64, 'keys:forward': 128,\n 'state:read': 256, 'state:write': 512,\n 'media:control': 1024, 'notify:send': 2048, 'notify:channel': 4096,\n 'theme:read': 8192,\n // 65536 RETIRED — was identity:decrypt (removed as a spec violation); left as\n // a permanent gap, do NOT reuse this bit.\n};\n\nfunction capArrayToBitfield(caps: Capability[]): number {\n let bits = 0;\n for (const cap of caps) bits |= (CAP_BITS[cap] ?? 0);\n return bits;\n}\n\nfunction bitfieldToCapArray(bits: number): Capability[] {\n return (Object.entries(CAP_BITS)\n .filter(([, bit]) => (bits & bit) !== 0)\n .map(([name]) => name)) as Capability[];\n}\n\nfunction getOrCreate(pubkey: string, dTag: string, aggregateHash: string): InternalAclEntry {\n const key = aclKey(pubkey, dTag, aggregateHash);\n let entry = store.get(key);\n if (!entry) {\n entry = {\n key,\n pubkey,\n dTag,\n aggregateHash,\n capabilities: new Set(ALL_CAPABILITIES),\n blocked: false,\n stateQuota: DEFAULT_STATE_QUOTA,\n };\n store.set(key, entry);\n }\n return entry;\n}\n\n/**\n * ACL store — manages capability grants, revocations, and blocks for napp identities.\n * Persists to localStorage and uses a permissive default policy (all capabilities granted).\n *\n * @example\n * ```ts\n * import { aclStore } from '@kehto/shell';\n *\n * aclStore.grant(pubkey, dTag, hash, 'relay:read');\n * const allowed = aclStore.check(pubkey, dTag, hash, 'relay:read'); // true\n * ```\n */\nexport const aclStore = {\n /**\n * Check if a napp identity has a specific capability.\n * Returns true for unknown identities (permissive default).\n *\n * @param pubkey - The napp's pubkey\n * @param dTag - The napp's dTag\n * @param aggregateHash - The napp's build hash\n * @param capability - The capability to check\n * @returns True if the capability is granted and the napp is not blocked\n */\n check(pubkey: string, dTag: string, aggregateHash: string, capability: Capability): boolean {\n const key = aclKey(pubkey, dTag, aggregateHash);\n const entry = store.get(key);\n if (!entry) return true;\n if (entry.blocked) return false;\n return entry.capabilities.has(capability);\n },\n\n /**\n * Grant a capability to a napp identity.\n *\n * @param pubkey - The napp's pubkey\n * @param dTag - The napp's dTag\n * @param aggregateHash - The napp's build hash\n * @param capability - The capability to grant\n */\n grant(pubkey: string, dTag: string, aggregateHash: string, capability: Capability): void {\n getOrCreate(pubkey, dTag, aggregateHash).capabilities.add(capability);\n },\n\n /**\n * Revoke a capability from a napp identity.\n *\n * @param pubkey - The napp's pubkey\n * @param dTag - The napp's dTag\n * @param aggregateHash - The napp's build hash\n * @param capability - The capability to revoke\n */\n revoke(pubkey: string, dTag: string, aggregateHash: string, capability: Capability): void {\n getOrCreate(pubkey, dTag, aggregateHash).capabilities.delete(capability);\n },\n\n /**\n * Block a napp identity entirely (all capabilities denied).\n *\n * @param pubkey - The napp's pubkey\n * @param dTag - The napp's dTag\n * @param aggregateHash - The napp's build hash\n */\n block(pubkey: string, dTag: string, aggregateHash: string): void {\n getOrCreate(pubkey, dTag, aggregateHash).blocked = true;\n },\n\n /**\n * Unblock a napp identity.\n *\n * @param pubkey - The napp's pubkey\n * @param dTag - The napp's dTag\n * @param aggregateHash - The napp's build hash\n */\n unblock(pubkey: string, dTag: string, aggregateHash: string): void {\n getOrCreate(pubkey, dTag, aggregateHash).blocked = false;\n },\n\n /**\n * Check if a napp identity is blocked.\n *\n * @param pubkey - The napp's pubkey\n * @param dTag - The napp's dTag\n * @param aggregateHash - The napp's build hash\n * @returns True if the identity is blocked\n */\n isBlocked(pubkey: string, dTag: string, aggregateHash: string): boolean {\n const key = aclKey(pubkey, dTag, aggregateHash);\n return store.get(key)?.blocked ?? false;\n },\n\n /**\n * Get the external ACL entry for a napp identity.\n *\n * @param pubkey - The napp's pubkey\n * @param dTag - The napp's dTag\n * @param aggregateHash - The napp's build hash\n * @returns The ACL entry, or undefined if no explicit entry exists\n */\n getEntry(pubkey: string, dTag: string, aggregateHash: string): AclEntry | undefined {\n const key = aclKey(pubkey, dTag, aggregateHash);\n const internal = store.get(key);\n if (!internal) return undefined;\n return {\n pubkey: internal.pubkey,\n capabilities: Array.from(internal.capabilities),\n blocked: internal.blocked,\n stateQuota: internal.stateQuota,\n };\n },\n\n /**\n * Get all ACL entries.\n *\n * @returns Array of all ACL entries\n */\n getAllEntries(): AclEntry[] {\n return Array.from(store.values()).map(e => ({\n pubkey: e.pubkey,\n capabilities: Array.from(e.capabilities),\n blocked: e.blocked,\n stateQuota: e.stateQuota,\n }));\n },\n\n /** Persist the ACL store to localStorage. */\n persist(): void {\n try {\n const entries = Array.from(store.entries()).map(([key, val]) => [\n key,\n {\n pubkey: val.pubkey,\n dTag: val.dTag,\n aggregateHash: val.aggregateHash,\n capabilities: Array.from(val.capabilities),\n blocked: val.blocked,\n stateQuota: val.stateQuota,\n },\n ]);\n localStorage.setItem(STORAGE_KEY, JSON.stringify(entries));\n } catch {\n // localStorage unavailable\n }\n },\n\n /** Load the ACL store from localStorage. Migrates old 3-segment keys to 2-segment format. */\n load(): void {\n try {\n const raw = localStorage.getItem(STORAGE_KEY);\n if (!raw) return;\n let entries = JSON.parse(raw) as Array<\n [string, {\n pubkey: string;\n dTag?: string;\n aggregateHash?: string;\n capabilities: Capability[];\n blocked: boolean;\n stateQuota?: number;\n }]\n >;\n\n const hasOldKeys = entries.some(([key]) => key.split(':').length === 3);\n if (hasOldKeys) {\n const tempState: AclState = {\n defaultPolicy: 'permissive',\n entries: Object.fromEntries(\n entries.map(([key, val]) => [key, {\n caps: capArrayToBitfield(val.capabilities),\n blocked: val.blocked,\n quota: val.stateQuota ?? DEFAULT_STATE_QUOTA,\n }])\n ),\n };\n const migrated = migrateAclState(tempState);\n if (migrated !== tempState) {\n // Rebuild entries from migrated state\n entries = Object.entries(migrated.entries).map(([key, entry]) => {\n const parts = key.split(':');\n return [key, {\n pubkey: '',\n dTag: parts[0] ?? '',\n aggregateHash: parts[1] ?? '',\n capabilities: bitfieldToCapArray(entry.caps),\n blocked: entry.blocked,\n stateQuota: entry.quota,\n }] as [string, typeof entries[0][1]];\n });\n // Re-persist migrated data immediately\n localStorage.setItem(STORAGE_KEY, JSON.stringify(entries));\n }\n }\n\n store.clear();\n for (const [key, val] of entries) {\n if (val.dTag === undefined || val.aggregateHash === undefined) continue;\n store.set(key, {\n key,\n pubkey: val.pubkey,\n dTag: val.dTag,\n aggregateHash: val.aggregateHash,\n capabilities: new Set(val.capabilities),\n blocked: val.blocked,\n stateQuota: val.stateQuota ?? DEFAULT_STATE_QUOTA,\n });\n }\n } catch {\n /* Corrupted ACL data in localStorage — clear and use defaults */\n store.clear();\n }\n },\n\n /**\n * Get the state quota for a napp identity.\n *\n * @param pubkey - The napp's pubkey\n * @param dTag - The napp's dTag\n * @param aggregateHash - The napp's build hash\n * @returns The quota in bytes (defaults to DEFAULT_STATE_QUOTA)\n */\n getStateQuota(pubkey: string, dTag: string, aggregateHash: string): number {\n const key = aclKey(pubkey, dTag, aggregateHash);\n return store.get(key)?.stateQuota ?? DEFAULT_STATE_QUOTA;\n },\n\n /** Clear all ACL entries and remove from localStorage. */\n clear(): void {\n store.clear();\n try {\n localStorage.removeItem(STORAGE_KEY);\n } catch {\n /* localStorage unavailable — ACL clear is best-effort */\n }\n },\n};\n","/**\n * Manifest cache — persists verified NIP-5A aggregate hashes per napplet identity.\n */\n\n/**\n * A cached manifest entry for a verified napplet build.\n * @example\n * ```ts\n * const entry: ManifestCacheEntry = {\n * pubkey: 'abc123...', dTag: '3chat',\n * aggregateHash: 'deadbeef', verifiedAt: Date.now(),\n * };\n * ```\n */\nexport interface ManifestCacheEntry {\n pubkey: string;\n dTag: string;\n aggregateHash: string;\n verifiedAt: number;\n}\n\nconst STORAGE_KEY = 'napplet:manifest-cache';\nconst cache = new Map<string, ManifestCacheEntry>();\n\nfunction cacheKey(pubkey: string, dTag: string): string {\n return `${pubkey}:${dTag}`;\n}\n\n/**\n * Cache for verified napplet manifest entries. Persists to localStorage.\n * Used to detect napplet updates (aggregateHash changes) across sessions.\n *\n * @example\n * ```ts\n * import { manifestCache } from '@kehto/shell';\n *\n * manifestCache.set({ pubkey: 'abc...', dTag: 'chat', aggregateHash: 'dead', verifiedAt: Date.now() });\n * const entry = manifestCache.get('abc...', 'chat');\n * ```\n */\nexport const manifestCache = {\n /**\n * Get a cached manifest entry by pubkey and dTag.\n *\n * @param pubkey - The napp's pubkey\n * @param dTag - The napp's dTag\n * @returns The cached entry, or undefined if not found\n */\n get(pubkey: string, dTag: string): ManifestCacheEntry | undefined {\n return cache.get(cacheKey(pubkey, dTag));\n },\n\n /**\n * Set (upsert) a manifest cache entry and persist to localStorage.\n *\n * @param entry - The manifest entry to cache\n */\n set(entry: ManifestCacheEntry): void {\n cache.set(cacheKey(entry.pubkey, entry.dTag), entry);\n manifestCache.persist();\n },\n\n /**\n * Check if a specific hash is cached for a pubkey/dTag combination.\n *\n * @param pubkey - The napp's pubkey\n * @param dTag - The napp's dTag\n * @param hash - The aggregateHash to check\n * @returns True if the exact hash matches the cached entry\n */\n has(pubkey: string, dTag: string, hash: string): boolean {\n const entry = cache.get(cacheKey(pubkey, dTag));\n return !!entry && entry.aggregateHash === hash;\n },\n\n /**\n * Remove a cached entry for a pubkey/dTag and persist.\n *\n * @param pubkey - The napp's pubkey\n * @param dTag - The napp's dTag\n */\n remove(pubkey: string, dTag: string): void {\n cache.delete(cacheKey(pubkey, dTag));\n manifestCache.persist();\n },\n\n /** Load the cache from localStorage. */\n load(): void {\n try {\n const raw = localStorage.getItem(STORAGE_KEY);\n if (!raw) return;\n const entries = JSON.parse(raw) as Array<[string, ManifestCacheEntry]>;\n cache.clear();\n for (const [key, val] of entries) cache.set(key, val);\n } catch { /* Corrupted cache data — clear and start fresh */ cache.clear(); }\n },\n\n /** Persist the cache to localStorage. */\n persist(): void {\n try {\n localStorage.setItem(STORAGE_KEY, JSON.stringify(Array.from(cache.entries())));\n } catch { /* localStorage unavailable in sandboxed contexts — persist is best-effort */ }\n },\n\n /** Clear all cached entries and remove from localStorage. */\n clear(): void {\n cache.clear();\n try { localStorage.removeItem(STORAGE_KEY); } catch { /* localStorage unavailable — cleanup is best-effort */ }\n },\n};\n","/**\n * audio-manager.ts — Shell-side registry of active audio sources.\n *\n * Tracks which windows are producing audio. UI components read the registry\n * reactively via the version counter and CustomEvent pattern.\n */\n\nimport { originRegistry } from './origin-registry.js';\n\n/**\n * An active audio source registered by a napplet.\n * @example\n * ```ts\n * const source: AudioSource = {\n * windowId: 'win-1', nappletClass: 'music-player',\n * title: 'Now Playing', muted: false,\n * };\n * ```\n */\nexport interface AudioSource {\n windowId: string;\n nappletClass: string;\n title: string;\n muted: boolean;\n}\n\nconst sources = new Map<string, AudioSource>();\nlet version = 0;\n\nfunction bump(): void {\n version++;\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new CustomEvent('napplet:audio-changed'));\n }\n}\n\n/**\n * Registry of active audio sources across all napplet windows.\n * Emits 'napplet:audio-changed' CustomEvents when the registry changes.\n *\n * @example\n * ```ts\n * import { audioManager } from '@kehto/shell';\n *\n * audioManager.register('win-1', 'music', 'My Song');\n * audioManager.mute('win-1', true);\n * ```\n */\nexport const audioManager = {\n /**\n * Register a new audio source for a window.\n *\n * @param windowId - The window identifier\n * @param nappletClass - The napplet class/type (e.g., 'music-player')\n * @param title - Human-readable title for the audio source\n */\n register(windowId: string, nappletClass: string, title: string): void {\n sources.set(windowId, { windowId, nappletClass, title, muted: false });\n bump();\n },\n\n /**\n * Unregister an audio source for a window.\n *\n * @param windowId - The window identifier to remove\n */\n unregister(windowId: string): void {\n if (sources.delete(windowId)) bump();\n },\n\n /**\n * Update the state of an audio source (e.g., change title).\n *\n * @param windowId - The window identifier\n * @param update - Partial update with optional title\n */\n updateState(windowId: string, update: { title?: string }): void {\n const src = sources.get(windowId);\n if (!src) return;\n if (update.title !== undefined) src.title = update.title;\n bump();\n },\n\n /**\n * Mute or unmute an audio source and notify the napplet via postMessage.\n *\n * @param windowId - The window identifier\n * @param muted - True to mute, false to unmute\n * @example\n * ```ts\n * audioManager.mute('win-1', true); // mute\n * audioManager.mute('win-1', false); // unmute\n * ```\n */\n mute(windowId: string, muted: boolean): void {\n const src = sources.get(windowId);\n if (src) { src.muted = muted; bump(); }\n const iframeWindow = originRegistry.getIframeWindow(windowId);\n if (iframeWindow) {\n const muteEvent = {\n kind: 29000, // INC_PEER — inlined numeric after Phase 24 DRIFT-01 shim removal\n created_at: Math.floor(Date.now() / 1000),\n tags: [['t', 'napplet:audio-muted']],\n content: JSON.stringify({ muted }),\n pubkey: '__shell__',\n id: `audio-mute-${windowId}-${Date.now()}`,\n sig: '',\n };\n iframeWindow.postMessage(['EVENT', '__shell__', muteEvent], '*');\n }\n },\n\n /**\n * Check if a window has a registered audio source.\n *\n * @param windowId - The window identifier\n * @returns True if the window has an active audio source\n */\n has(windowId: string): boolean { return sources.has(windowId); },\n\n /**\n * Get the audio source for a window.\n *\n * @param windowId - The window identifier\n * @returns The AudioSource, or undefined if not found\n */\n get(windowId: string): AudioSource | undefined { return sources.get(windowId); },\n\n /**\n * Get a snapshot of all audio sources.\n *\n * @returns A new Map of all active audio sources\n */\n getSources(): Map<string, AudioSource> { return new Map(sources); },\n\n /** Current version counter (incremented on every change). */\n get version(): number { return version; },\n\n /** Number of active audio sources. */\n get count(): number { return sources.size; },\n\n /** Clear all audio sources and reset version counter. */\n clear(): void { sources.clear(); version = 0; },\n};\n","/**\n * keys-forwarder.ts — Shell-side host keydown listener that forwards events\n * to registered napplets as `keys.forward` envelopes (Plan 12-11, NAP-05\n * shell-side half).\n *\n * Per `@napplet/nap/keys`, `keys.forward` is fire-and-forget (no result\n * envelope, no correlation id). Field names follow the nap convention:\n * `{ ctrl, alt, shift, meta }` — NOT the DOM-style `ctrlKey`/etc.\n *\n * Capability gate: only forwards to napplets whose ACL grants the\n * `keys:forward` capability (per Plan 12-10 `resolveCapabilitiesNap` 'keys'\n * case). The caller wires the cap-lookup via the `hasKeysForwardCap` dep so\n * this module stays free of any direct ACL-store dependency.\n *\n * Lifecycle: `createShellBridge()` attaches a forwarder on construction and\n * detaches it inside `bridge.destroy()`.\n */\n\nimport type { NappletMessage } from '@napplet/core';\nimport type { SessionEntry } from './types.js';\n\n/**\n * Minimal origin-registry contract used by the forwarder — matches the\n * `@kehto/shell` singleton `originRegistry` and test doubles alike.\n */\nexport interface KeysForwarderOriginRegistry {\n /** Resolve a registered napplet windowId to its iframe Window, or null. */\n getIframeWindow(windowId: string): Window | null;\n}\n\n/**\n * Minimal session-registry contract used by the forwarder — matches the\n * `@kehto/shell` singleton `sessionRegistry` and test doubles alike.\n */\nexport interface KeysForwarderSessionRegistry {\n /** Return every registered napplet session entry. */\n getAllEntries(): SessionEntry[];\n}\n\n/**\n * Dependencies for `createKeysForwarder`.\n *\n * @example\n * ```ts\n * const forwarder = createKeysForwarder({\n * originRegistry,\n * sessionRegistry,\n * hasKeysForwardCap: (pubkey) =>\n * aclStore.getAclEntry(pubkey)?.capabilities.includes('keys:forward') ?? false,\n * });\n * ```\n */\nexport interface KeysForwarderDeps {\n /** Origin registry for resolving windowId → iframe Window. */\n originRegistry: KeysForwarderOriginRegistry;\n /** Session registry for enumerating napplets to forward to. */\n sessionRegistry: KeysForwarderSessionRegistry;\n /**\n * Capability check: returns true when the given napplet pubkey holds the\n * `keys:forward` capability. Called per keydown per registered napplet —\n * keep the implementation cheap.\n */\n hasKeysForwardCap(pubkey: string): boolean;\n /**\n * Optional EventTarget to attach to. Defaults to the global `window` when\n * running in a DOM environment. Passing a fresh `new EventTarget()` is\n * useful for unit tests.\n */\n target?: EventTarget;\n}\n\n/**\n * Handle returned by `createKeysForwarder`. Call `destroy()` to remove the\n * keydown listener (e.g. inside `bridge.destroy()`).\n */\nexport interface KeysForwarder {\n /** Detach the keydown listener and release resources. */\n destroy(): void;\n}\n\n/**\n * The `keys.forward` envelope shape emitted by this forwarder. Matches\n * `@napplet/nap/keys` `KeysForwardMessage`.\n */\ninterface KeysForwardEnvelope extends NappletMessage {\n type: 'keys.forward';\n key: string;\n code: string;\n ctrl: boolean;\n alt: boolean;\n shift: boolean;\n meta: boolean;\n}\n\n/**\n * Create a host-keydown forwarder that posts `keys.forward` envelopes to\n * every registered napplet granted the `keys:forward` capability.\n *\n * @param deps - Origin registry, session registry, cap checker, optional target\n * @returns A {@link KeysForwarder} — call `destroy()` to detach\n * @example\n * ```ts\n * // Inside createShellBridge():\n * const keysForwarder = createKeysForwarder({\n * originRegistry,\n * sessionRegistry,\n * hasKeysForwardCap: (pubkey) =>\n * aclStore.getAclEntry(pubkey)?.capabilities.includes('keys:forward') ?? false,\n * });\n * // ...\n * // Inside bridge.destroy():\n * keysForwarder.destroy();\n * ```\n */\nexport function createKeysForwarder(deps: KeysForwarderDeps): KeysForwarder {\n // Fallback to the global window when running in a DOM environment; when\n // neither a target nor a window is available (SSR / early Node tests),\n // create an isolated EventTarget so addEventListener never throws. The\n // resulting forwarder is effectively inert until the caller dispatches\n // keydowns on the chosen target.\n const target: EventTarget =\n deps.target ?? (typeof window !== 'undefined' ? window : new EventTarget());\n\n const listener = (ev: Event): void => {\n const ke = ev as Event & {\n key?: string; code?: string;\n ctrlKey?: boolean; altKey?: boolean;\n shiftKey?: boolean; metaKey?: boolean;\n };\n\n const entries = deps.sessionRegistry.getAllEntries();\n for (const entry of entries) {\n if (!deps.hasKeysForwardCap(entry.pubkey)) continue;\n const iframe = deps.originRegistry.getIframeWindow(entry.windowId);\n if (!iframe) continue;\n\n const envelope: KeysForwardEnvelope = {\n type: 'keys.forward',\n key: ke.key ?? '',\n code: ke.code ?? '',\n ctrl: ke.ctrlKey ?? false,\n alt: ke.altKey ?? false,\n shift: ke.shiftKey ?? false,\n meta: ke.metaKey ?? false,\n };\n iframe.postMessage(envelope, '*');\n }\n };\n\n target.addEventListener('keydown', listener);\n\n return {\n destroy(): void {\n target.removeEventListener('keydown', listener);\n },\n };\n}\n","\nimport type { ShellAdapter, ShellCapabilities } from './types.js';\n\n/**\n * NAP-vocabulary domain list (consumed by @napplet/shim >=0.9.0).\n *\n * Note: `relay` and `outbox` are NOT in this list — both are gated on\n * `hooks.relayPool` (NAP-OUTBOX routes over relays, so it is meaningless\n * without one) and prepended conditionally in buildShellCapabilities below.\n * `link` and `common` are also conditional because shells must wire those\n * service backends before advertising user-visible navigation/social actions.\n */\nconst NAP_DOMAINS = [\n 'identity', 'storage', 'inc', 'theme', 'keys', 'media', 'notify',\n 'config', 'resource', 'cvm',\n] as const;\n\n/**\n * NAP protocol IDs for the `naps` array: `inc:NAP-01..inc:NAP-06`.\n */\nconst NAP_INC_PROTOCOLS = [\n 'inc:NAP-01',\n 'inc:NAP-02',\n 'inc:NAP-03',\n 'inc:NAP-04',\n 'inc:NAP-05',\n 'inc:NAP-06',\n] as const;\n\n/**\n * Build the shell's static capability set from adapter configuration.\n *\n * Returns the conformant NAP-SHELL `domains`/`protocols` shape (consumed by\n * `@napplet/shim >=0.13` via `@napplet/nap@0.12`'s `createShellEnvironment` +\n * `makeSupports`) alongside the flat `naps` array (consumed by\n * `@napplet/shim >=0.9.0`) and the `sandbox` array.\n *\n * ### naps array (NAP vocabulary)\n * Bare domain `inc` + `inc:NAP-01..inc:NAP-06`.\n * Conditional: `relay`+`outbox` prepended when hooks.relayPool;\n * `upload` appended when hooks.upload;\n * `intent` appended when hooks.intent.isAvailable();\n * `link` appended when hooks.link.isAvailable();\n * `common` appended when hooks.common.isAvailable().\n *\n * Sandbox permissions are left empty by default — host apps may extend after\n * construction. Sandbox entries (and any host-app extensions) MUST use the\n * canonical `perm:<permission>` form — e.g. `'perm:popups'`, `'perm:modals'`,\n * `'perm:downloads'`. Napplets rely on the `perm:` prefix to distinguish\n * sandbox permissions from NAP-capability lookups; see the living NIP-5D at\n * https://github.com/nostr-protocol/nips/pull/2303/\n *\n * ### domains array + protocols map (conformant NAP-SHELL — @napplet/core@0.12)\n * The structured shape the released `@napplet/shim@0.13` actually reads via\n * `@napplet/nap@0.12`'s `createShellEnvironment` + `makeSupports`:\n *\n * - `domains` — bare NAP domain names (the `naps` set MINUS the `inc:NAP-NN`\n * protocol strings) with the same conditional entries (relay/outbox under\n * `hooks.relayPool`, upload/intent/link/common under their hooks). Any `perm:<x>`\n * sandbox entries are appended here too — the 0.13 shim resolves\n * `supports('perm:<x>')` as a plain `domains` membership check.\n * - `protocols` — `{ inc: ['NAP-01'..'NAP-06'] }`, derived from\n * `NAP_INC_PROTOCOLS` by stripping the `inc:` prefix.\n *\n * Emitted as a SUPERSET alongside `naps`/`sandbox` (TERM-05 back-compat).\n *\n * @param hooks - The ShellAdapter provided by the host app\n * @returns ShellCapabilities with domains/protocols (conformant 0.13 shape) plus\n * naps (NAP vocab) and sandbox (perm:-prefixed) arrays\n * @example\n * ```ts\n * const caps = buildShellCapabilities(hooks);\n * // caps.domains => ['relay','outbox','identity','storage','inc','theme','keys','media','notify','config','resource','cvm']\n * // (relay + outbox present when hooks.relayPool is provided; 'upload'/'intent'\n * // appended under their hooks; perm:<x> sandbox entries appended when extended)\n * // caps.protocols => { inc: ['NAP-01','NAP-02','NAP-03','NAP-04','NAP-05','NAP-06'] }\n * // caps.naps => ['relay','outbox','identity','storage','inc','theme','keys','media','notify','config','resource','cvm',\n * // 'inc:NAP-01','inc:NAP-02','inc:NAP-03','inc:NAP-04','inc:NAP-05','inc:NAP-06']\n * // (relay + outbox present when hooks.relayPool is provided; 'upload'\n * // appended when hooks.upload is provided; 'intent' appended when\n * // hooks.intent.isAvailable() is true)\n * // caps.sandbox => [] // host app may extend with 'perm:popups', etc.\n * ```\n */\nexport function buildShellCapabilities(hooks: ShellAdapter): ShellCapabilities {\n // domains — conformant NAP-SHELL bare domain list (@napplet/shim >=0.13).\n // Same membership/order as `naps` but WITHOUT the inc:NAP-NN protocol strings\n // (those move to `protocols`). Conditional entries use the same gates as naps.\n const domains: string[] = hooks.relayPool\n ? ['relay', 'outbox', ...NAP_DOMAINS]\n : [...NAP_DOMAINS];\n if (hooks.upload) domains.push('upload');\n if (hooks.intent?.isAvailable()) domains.push('intent');\n if (hooks.link?.isAvailable()) domains.push('link');\n if (hooks.common?.isAvailable()) domains.push('common');\n if (hooks.lists?.isAvailable()) domains.push('lists');\n if (hooks.serial?.isAvailable()) domains.push('serial');\n if (hooks.ble?.isAvailable()) domains.push('ble');\n if (hooks.webrtc?.isAvailable()) domains.push('webrtc');\n // Sandbox permissions are perm:<x>-prefixed and resolved by the 0.13 shim as\n // plain domains membership (no separate permission namespace). Empty by\n // default — fold any host-extended sandbox entries in alongside the domains.\n const sandbox: string[] = [];\n domains.push(...sandbox);\n\n // protocols — conformant NAP-SHELL per-domain numbered protocol map.\n // Derive { inc: ['NAP-01'..'NAP-06'] } by stripping the `inc:` prefix from\n // NAP_INC_PROTOCOLS so domains/protocols stay the single source of truth.\n const protocols: Record<string, string[]> = {};\n for (const entry of NAP_INC_PROTOCOLS) {\n const [domain, protocol] = entry.split(':') as [string, string];\n (protocols[domain] ??= []).push(protocol);\n }\n\n // naps — NAP vocabulary (consumed by @napplet/shim >=0.9.0)\n const naps: string[] = hooks.relayPool\n ? ['relay', 'outbox', ...NAP_DOMAINS, ...NAP_INC_PROTOCOLS]\n : [...NAP_DOMAINS, ...NAP_INC_PROTOCOLS];\n // NAP-UPLOAD: advertised only when the host wires an upload backend.\n if (hooks.upload) naps.push('upload');\n // NAP-INTENT: advertised only when the host wires an available intent dispatcher.\n if (hooks.intent?.isAvailable()) naps.push('intent');\n // NAP-LINK: advertised only when the host wires shell-mediated link opening.\n if (hooks.link?.isAvailable()) naps.push('link');\n // NAP-COMMON: advertised only when the host wires common social actions.\n if (hooks.common?.isAvailable()) naps.push('common');\n // NAP-LISTS: advertised only when the host wires list mutation helpers.\n if (hooks.lists?.isAvailable()) naps.push('lists');\n // NAP-SERIAL: advertised only when the host wires runtime-owned serial sessions.\n if (hooks.serial?.isAvailable()) naps.push('serial');\n // NAP-BLE: advertised only when the host wires runtime-owned BLE sessions.\n if (hooks.ble?.isAvailable()) naps.push('ble');\n // NAP-WEBRTC: advertised only when the host wires runtime-owned WebRTC sessions.\n if (hooks.webrtc?.isAvailable()) naps.push('webrtc');\n\n return applyCapabilityOverrides(\n { domains, protocols, naps, sandbox },\n hooks.capabilities?.disabledDomains ?? [],\n );\n}\n\nfunction applyCapabilityOverrides(\n capabilities: ShellCapabilities,\n disabledDomains: readonly string[],\n): ShellCapabilities {\n if (disabledDomains.length === 0) return capabilities;\n\n const disabled = new Set(disabledDomains);\n const protocols: Record<string, string[]> = {};\n for (const [domain, supportedProtocols] of Object.entries(capabilities.protocols)) {\n if (!disabled.has(domain)) protocols[domain] = supportedProtocols;\n }\n\n return {\n domains: capabilities.domains.filter((entry) => !disabled.has(entry)),\n protocols,\n naps: capabilities.naps.filter((entry) => !disabled.has(entry.split(':')[0] ?? entry)),\n sandbox: capabilities.sandbox,\n };\n}\n","import type { Runtime, SessionEntry } from '@kehto/runtime';\nimport type { NappletMessage } from '@napplet/core';\nimport { originRegistry } from './origin-registry.js';\nimport { buildShellCapabilities } from './shell-init.js';\nimport type { ShellAdapter, ShellCapabilities } from './types.js';\n\ninterface ShellReadyOptions {\n hooks: ShellAdapter;\n origin: string;\n runtime: Runtime;\n windowId: string;\n}\n\n/**\n * SHELL-01 (NAP-SHELL gap G1): tracks the windowIds for which `shell.init` has\n * already been posted, so a duplicate `shell.ready` from the same window is\n * idempotent and does NOT resend `shell.init`. Module-scoped because the\n * \"no NIP-5D identity\" path never registers a session entry, so the guard must\n * live independently of the session registry (and must not mutate the runtime\n * `SessionEntry` shape, which is owned by @kehto/runtime).\n */\nconst initSent = new Set<string>();\n\n/**\n * Test-only hook to clear the module-scoped {@link initSent} guard between\n * test cases. NOT part of the public API; prefixed with `__` and `ForTests` to\n * signal it must never be called by production code.\n *\n * @internal\n */\nexport function __resetInitSentForTests(): void {\n initSent.clear();\n}\n\nexport function handleShellReady({\n hooks,\n origin,\n runtime,\n windowId,\n}: ShellReadyOptions): void {\n registerNip5dSessionIfNeeded({ hooks, origin, runtime, windowId });\n\n // SHELL-01: exactly-once shell.init per windowId. registerNip5dSessionIfNeeded\n // is already idempotent (its own getEntryByWindowId early-return); this guard\n // governs ONLY the postShellInit call so a duplicate shell.ready does not\n // resend shell.init.\n if (initSent.has(windowId)) {\n return;\n }\n\n const capabilities = buildShellCapabilities(hooks);\n postShellInit(windowId, capabilities, Object.keys(hooks.services ?? {}));\n initSent.add(windowId);\n}\n\nfunction registerNip5dSessionIfNeeded({\n hooks,\n origin,\n runtime,\n windowId,\n}: ShellReadyOptions): void {\n // NIP-5D: register a source-identity session entry in runtime.sessionRegistry\n // if one does not already exist for this windowId. This wires the originRegistry\n // identity into the runtime so domain handlers (storage/state, inc, etc.) can\n // resolve the napplet via getEntryByWindowId(windowId).\n if (runtime.sessionRegistry.getEntryByWindowId(windowId)) {\n return;\n }\n\n const identity = resolveNip5dIdentity(hooks, windowId);\n if (!identity) {\n return;\n }\n\n const entry: SessionEntry = {\n pubkey: '',\n windowId,\n origin,\n type: 'nip5d',\n dTag: identity.dTag,\n aggregateHash: identity.aggregateHash,\n registeredAt: Date.now(),\n instanceId: crypto.randomUUID(),\n provenance: 'nip-5d',\n };\n runtime.sessionRegistry.register(windowId, entry);\n}\n\nfunction resolveNip5dIdentity(\n hooks: ShellAdapter,\n windowId: string,\n): { dTag: string; aggregateHash: string } | null {\n // Identity resolution order:\n // 1. hooks.onNip5dIframeCreate?.(windowId) — preferred.\n // 2. originRegistry.getIdentity(win) — fallback for hosts that register\n // identity directly via originRegistry.register(win, windowId, identity).\n const hookIdentity = hooks.onNip5dIframeCreate?.(windowId);\n if (hookIdentity !== null && hookIdentity !== undefined) {\n return {\n dTag: hookIdentity.dTag,\n aggregateHash: hookIdentity.aggregateHash,\n };\n }\n\n const win = originRegistry.getIframeWindow(windowId);\n if (!win) {\n return null;\n }\n\n const originIdentity = originRegistry.getIdentity(win);\n if (!originIdentity) {\n return null;\n }\n\n return {\n dTag: originIdentity.dTag,\n aggregateHash: originIdentity.aggregateHash,\n };\n}\n\nfunction postShellInit(\n windowId: string,\n capabilities: ShellCapabilities,\n services: string[],\n): void {\n const initMsg: NappletMessage & {\n capabilities: ShellCapabilities;\n services: string[];\n } = {\n type: 'shell.init',\n capabilities,\n services,\n };\n const win = originRegistry.getIframeWindow(windowId);\n if (win) win.postMessage(initMsg, '*');\n}\n","\n// Factory function — main entry point\nexport { createShellBridge } from './shell-bridge.js';\nexport type { ShellBridge } from './shell-bridge.js';\n\n// Hooks adapter — for advanced integrators who need to customize the adapter\nexport { adaptHooks } from './hooks-adapter.js';\nexport type { BrowserDeps } from './hooks-adapter.js';\n\n// Protocol types (re-exported from @napplet/core + @kehto/runtime).\n// Phase 24 DRIFT-01: former @napplet/core compatibility shim deleted; legacy\n// NIP-01 constants no longer re-exported. Shell consumers import Capability +\n// ALL_CAPABILITIES from @kehto/runtime (sourced from @kehto/acl/capabilities).\nexport type { NostrEvent, NostrFilter, NappletMessage } from '@napplet/core';\nexport type { Capability } from '@kehto/runtime';\nexport { ALL_CAPABILITIES } from '@kehto/runtime';\n\n// Types for host app integration (shell-specific)\nexport type {\n ShellAdapter,\n ShellCapabilities,\n RelayPoolHooks,\n RelayPoolLike,\n RelayConfigHooks,\n WindowManagerHooks,\n AuthHooks,\n ConfigHooks,\n HotkeyHooks,\n WorkerRelayHooks,\n WorkerRelayLike,\n CryptoHooks,\n DmHooks,\n UploadHooks,\n UploadBackendLike,\n IntentHooks,\n LinkHooks,\n CommonHooks,\n ListsHooks,\n SerialHooks,\n BleHooks,\n WebrtcHooks,\n CapabilityHooks,\n SessionEntry,\n NappKeyEntry, // @deprecated — use SessionEntry\n AclEntry,\n AclCheckEvent,\n UnroutedMessageInfo,\n ServiceDescriptor,\n ServiceHandler,\n ServiceRegistry,\n} from './types.js';\n\n// Shell initialization — capability construction for shell.ready / shell.init handshake\nexport { buildShellCapabilities } from './shell-init.js';\n\nexport { sessionRegistry, nappKeyRegistry } from './session-registry.js';\nexport type { PendingUpdate } from './session-registry.js';\n\n// Standalone utilities (usable without full shell)\nexport { originRegistry } from './origin-registry.js';\nexport { audioManager } from './audio-manager.js';\nexport type { AudioSource } from './audio-manager.js';\nexport { manifestCache } from './manifest-cache.js';\nexport type { ManifestCacheEntry } from './manifest-cache.js';\n\n// Enforcement gate (re-exported from @kehto/runtime for backwards compatibility)\nexport { createEnforceGate, createNapEnforceGate, formatDenialReason } from '@kehto/runtime';\nexport type { EnforceResult, EnforceConfig, NapEnforceConfig, IdentityResolver, AclChecker, NapMessage } from '@kehto/runtime';\n// ConsentRequest canonical definition re-exported from @kehto/runtime\nexport type { ConsentRequest } from '@kehto/runtime';\n\n// Topic constants for shell command routing\nexport { TOPICS } from './topics.js';\nexport type { TopicKey, TopicValue } from './topics.js';\n\nexport { createIdentityProxy } from './identity-proxy.js';\nexport type { IdentityProxy, IdentityProxyDeps, ProxyOriginRegistry } from './identity-proxy.js';\nexport { createThemeProxy } from './theme-proxy.js';\nexport type { ThemeProxy, ThemeProxyDeps } from './theme-proxy.js';\nexport { createKeysProxy } from './keys-proxy.js';\nexport type { KeysProxy, KeysProxyDeps } from './keys-proxy.js';\nexport { createMediaProxy } from './media-proxy.js';\nexport type { MediaProxy, MediaProxyDeps } from './media-proxy.js';\nexport { createNotifyProxy } from './notify-proxy.js';\nexport type { NotifyProxy, NotifyProxyDeps } from './notify-proxy.js';\n\nexport { createKeysForwarder } from './keys-forwarder.js';\nexport type {\n KeysForwarder,\n KeysForwarderDeps,\n KeysForwarderOriginRegistry,\n KeysForwarderSessionRegistry,\n} from './keys-forwarder.js';\n\nexport type {\n ResourceBytesRequest,\n ResourceBytesManyRequest,\n ResourceBytesManyItem,\n ResourceCancelRequest,\n ResourceBytesResult,\n ResourceBytesManyResult,\n ResourceBytesError,\n ResourceBytesManyError,\n ResourceErrorCode,\n ResourceRequestId,\n ResourceInbound,\n ResourceOutbound,\n} from './types/internal-resource.js';\n","/**\n * topics.ts — Re-exports topic constants from @napplet/core.\n *\n * Shell previously owned these constants. They now live in @napplet/core\n * so all packages share a single source of truth.\n */\nexport { TOPICS } from '@napplet/core';\nexport type { TopicKey, TopicValue } from '@napplet/core';\n","/**\n * identity-proxy.ts — Shell-side per-domain proxy for identity.* envelopes.\n *\n * Establishes the canonical proxy shape for @kehto/shell (Plan 12-11): each\n * per-domain proxy exposes a `dispatch` method that delegates napplet→shell\n * requests to the runtime and an `emit` method that posts shell→napplet\n * push envelopes through the origin registry.\n *\n * By default, `createShellBridge()` does NOT compose this proxy into its\n * dispatch path — the runtime already owns identity.* dispatch per Plan\n * 12-03 (see @kehto/services identity-service). This module exists as an\n * optional composition point for host apps that want to intercept or\n * augment identity dispatch (e.g. custom logging, sandboxed rewrites, test\n * doubles).\n *\n * The canonical proxy shape — dispatch + emit — is mirrored verbatim by\n * theme-proxy, keys-proxy, media-proxy, and notify-proxy. Storage today is\n * served by `@kehto/runtime` state-handler directly; a storage-proxy using\n * this shape can be added later if host apps need a composition seam.\n */\n\nimport type { Runtime } from '@kehto/runtime';\nimport type { NappletMessage } from '@napplet/core';\n\n/**\n * Minimal origin-registry contract used by per-domain proxies.\n *\n * Accepts the `@kehto/shell` singleton `originRegistry` as well as any test\n * double with a matching `getIframeWindow` method.\n */\nexport interface ProxyOriginRegistry {\n /** Resolve a registered napplet windowId to its iframe Window, or null. */\n getIframeWindow(windowId: string): Window | null;\n}\n\n/**\n * Dependencies for `createIdentityProxy`.\n *\n * @example\n * ```ts\n * const proxy = createIdentityProxy({\n * runtime: shellBridge.runtime,\n * originRegistry,\n * });\n * ```\n */\nexport interface IdentityProxyDeps {\n /** The runtime engine that owns identity.* dispatch (Plan 12-03). */\n runtime: Runtime;\n /** Origin registry for resolving windowId → iframe Window. */\n originRegistry: ProxyOriginRegistry;\n}\n\n/**\n * Per-domain proxy for `identity.*` envelopes.\n *\n * The canonical proxy shape: `dispatch` routes napplet→shell requests into\n * the runtime; `emit` pushes shell→napplet envelopes through the iframe's\n * Window.\n */\nexport interface IdentityProxy {\n /**\n * Route a napplet-originated identity.* envelope into the runtime.\n *\n * Delegation only — the runtime already owns identity.* dispatch after\n * Plan 12-03. Override by wrapping or replacing this method.\n *\n * @param windowId - The source napplet's windowId\n * @param envelope - The NIP-5D NappletMessage envelope\n */\n dispatch(windowId: string, envelope: NappletMessage): void;\n /**\n * Push a shell-initiated identity-domain envelope into a napplet iframe.\n *\n * No-op when the originRegistry cannot resolve the windowId (unknown or\n * unregistered napplet). Never throws.\n *\n * @param windowId - The target napplet's windowId\n * @param envelope - The NIP-5D NappletMessage envelope to deliver\n */\n emit(windowId: string, envelope: NappletMessage): void;\n}\n\n/**\n * Factory for the canonical identity-domain proxy.\n *\n * @param deps - Runtime + origin registry\n * @returns An {@link IdentityProxy} ready to route identity.* envelopes\n * @example\n * ```ts\n * import { createIdentityProxy, originRegistry, createShellBridge } from '@kehto/shell';\n *\n * const bridge = createShellBridge(hooks);\n * const identityProxy = createIdentityProxy({\n * runtime: bridge.runtime,\n * originRegistry,\n * });\n *\n * // Optional composition: intercept napplet->shell identity requests\n * const originalDispatch = identityProxy.dispatch;\n * identityProxy.dispatch = (windowId, envelope) => {\n * console.log('identity dispatch', windowId, envelope.type);\n * originalDispatch(windowId, envelope);\n * };\n * ```\n */\nexport function createIdentityProxy(deps: IdentityProxyDeps): IdentityProxy {\n return {\n dispatch(windowId: string, envelope: NappletMessage): void {\n deps.runtime.handleMessage(windowId, envelope);\n },\n emit(windowId: string, envelope: NappletMessage): void {\n const win = deps.originRegistry.getIframeWindow(windowId);\n if (win) win.postMessage(envelope, '*');\n },\n };\n}\n","/**\n * theme-proxy.ts — Shell-side per-domain proxy for theme.* envelopes.\n *\n * Establishes the shell-side shape that Phase 13 composes into. Phase 13 is\n * expected to add `theme-service.ts` (runtime) + the shell-side `theme.set`\n * API that emits `theme.changed` push envelopes to registered napplets;\n * this proxy is the canonical seam those pieces plug into.\n *\n * Shape mirrors identity-proxy (Plan 12-11):\n *\n * - `dispatch(windowId, envelope)` routes napplet→shell `theme.get` into\n * the runtime (where Phase 13's theme-service will answer).\n * - `emit(windowId, envelope)` posts shell→napplet `theme.changed`\n * envelopes through the origin registry.\n *\n * By default `createShellBridge()` does NOT compose this proxy into its\n * dispatch path — the runtime owns theme.* dispatch. This module is an\n * optional composition point for host apps or Phase 13 wiring.\n */\n\nimport type { Runtime } from '@kehto/runtime';\nimport type { NappletMessage } from '@napplet/core';\nimport type { ProxyOriginRegistry } from './identity-proxy.js';\n\n/**\n * Dependencies for `createThemeProxy`.\n *\n * @example\n * ```ts\n * const proxy = createThemeProxy({\n * runtime: shellBridge.runtime,\n * originRegistry,\n * });\n * ```\n */\nexport interface ThemeProxyDeps {\n /** The runtime engine that will own theme.* dispatch (Phase 13). */\n runtime: Runtime;\n /** Origin registry for resolving windowId → iframe Window. */\n originRegistry: ProxyOriginRegistry;\n}\n\n/**\n * Per-domain proxy for `theme.*` envelopes.\n *\n * Shape: `dispatch` routes napplet→shell requests into the runtime; `emit`\n * pushes shell→napplet envelopes through the iframe's Window.\n */\nexport interface ThemeProxy {\n /**\n * Route a napplet-originated theme.* envelope (e.g. `theme.get`) into\n * the runtime.\n *\n * @param windowId - The source napplet's windowId\n * @param envelope - The NIP-5D NappletMessage envelope\n */\n dispatch(windowId: string, envelope: NappletMessage): void;\n /**\n * Push a shell-initiated theme-domain envelope (e.g. `theme.changed`)\n * into a napplet iframe.\n *\n * No-op when the originRegistry cannot resolve the windowId (unknown or\n * unregistered napplet). Never throws.\n *\n * @param windowId - The target napplet's windowId\n * @param envelope - The NIP-5D NappletMessage envelope to deliver\n */\n emit(windowId: string, envelope: NappletMessage): void;\n}\n\n/**\n * Factory for the canonical theme-domain proxy.\n *\n * @param deps - Runtime + origin registry\n * @returns A {@link ThemeProxy} ready to route theme.* envelopes\n * @example\n * ```ts\n * import { createThemeProxy, originRegistry, createShellBridge } from '@kehto/shell';\n *\n * const bridge = createShellBridge(hooks);\n * const themeProxy = createThemeProxy({\n * runtime: bridge.runtime,\n * originRegistry,\n * });\n *\n * // Phase 13: broadcast theme.changed to every registered napplet\n * for (const entry of bridge.runtime.sessionRegistry.getAllEntries()) {\n * themeProxy.emit(entry.windowId, { type: 'theme.changed', theme: newTheme });\n * }\n * ```\n */\nexport function createThemeProxy(deps: ThemeProxyDeps): ThemeProxy {\n return {\n dispatch(windowId: string, envelope: NappletMessage): void {\n deps.runtime.handleMessage(windowId, envelope);\n },\n emit(windowId: string, envelope: NappletMessage): void {\n const win = deps.originRegistry.getIframeWindow(windowId);\n if (win) win.postMessage(envelope, '*');\n },\n };\n}\n","/**\n * keys-proxy.ts — Shell-side per-domain proxy for keys.* envelopes.\n *\n * Per Plan 12-05, the runtime already dispatches `keys.*` (forward,\n * registerAction, unregisterAction) to the keys-service. This proxy is the\n * shell-side composition point for host apps that want to observe or\n * inject keys envelopes — it pairs with `keys-forwarder.ts` (Plan 12-11)\n * which covers the shell→napplet `keys.forward` push path.\n *\n * Shape mirrors identity-proxy (Plan 12-11):\n *\n * - `dispatch(windowId, envelope)` routes napplet→shell keys requests\n * into the runtime.\n * - `emit(windowId, envelope)` posts shell→napplet pushes (`keys.action`,\n * `keys.bindings`, `keys.registerAction.result`) through the origin\n * registry.\n *\n * By default `createShellBridge()` does NOT compose this proxy into its\n * dispatch path — the runtime owns keys.* dispatch. This module is an\n * optional composition point for host apps (e.g. global hotkey UIs).\n */\n\nimport type { Runtime } from '@kehto/runtime';\nimport type { NappletMessage } from '@napplet/core';\nimport type { ProxyOriginRegistry } from './identity-proxy.js';\n\n/**\n * Dependencies for `createKeysProxy`.\n *\n * @example\n * ```ts\n * const proxy = createKeysProxy({\n * runtime: shellBridge.runtime,\n * originRegistry,\n * });\n * ```\n */\nexport interface KeysProxyDeps {\n /** The runtime engine that owns keys.* dispatch (Plan 12-05). */\n runtime: Runtime;\n /** Origin registry for resolving windowId → iframe Window. */\n originRegistry: ProxyOriginRegistry;\n}\n\n/**\n * Per-domain proxy for `keys.*` envelopes.\n *\n * Shape: `dispatch` routes napplet→shell requests into the runtime; `emit`\n * pushes shell→napplet envelopes through the iframe's Window.\n */\nexport interface KeysProxy {\n /**\n * Route a napplet-originated keys.* envelope (e.g. `keys.forward`,\n * `keys.registerAction`) into the runtime.\n *\n * @param windowId - The source napplet's windowId\n * @param envelope - The NIP-5D NappletMessage envelope\n */\n dispatch(windowId: string, envelope: NappletMessage): void;\n /**\n * Push a shell-initiated keys-domain envelope (e.g. `keys.action`,\n * `keys.bindings`) into a napplet iframe.\n *\n * Paired with `keys-forwarder.ts`: the forwarder targets the DOM\n * `keydown` → `keys.forward` path; this `emit` covers the complementary\n * host-initiated pushes (binding updates, action triggers).\n *\n * No-op when the originRegistry cannot resolve the windowId (unknown or\n * unregistered napplet). Never throws.\n *\n * @param windowId - The target napplet's windowId\n * @param envelope - The NIP-5D NappletMessage envelope to deliver\n */\n emit(windowId: string, envelope: NappletMessage): void;\n}\n\n/**\n * Factory for the canonical keys-domain proxy.\n *\n * @param deps - Runtime + origin registry\n * @returns A {@link KeysProxy} ready to route keys.* envelopes\n * @example\n * ```ts\n * import { createKeysProxy, originRegistry, createShellBridge } from '@kehto/shell';\n *\n * const bridge = createShellBridge(hooks);\n * const keysProxy = createKeysProxy({\n * runtime: bridge.runtime,\n * originRegistry,\n * });\n *\n * // Host-app-initiated action trigger:\n * keysProxy.emit('win-editor', { type: 'keys.action', actionId: 'editor.save' });\n * ```\n */\nexport function createKeysProxy(deps: KeysProxyDeps): KeysProxy {\n return {\n dispatch(windowId: string, envelope: NappletMessage): void {\n deps.runtime.handleMessage(windowId, envelope);\n },\n emit(windowId: string, envelope: NappletMessage): void {\n const win = deps.originRegistry.getIframeWindow(windowId);\n if (win) win.postMessage(envelope, '*');\n },\n };\n}\n","/**\n * media-proxy.ts — Shell-side per-domain proxy for media.* envelopes.\n *\n * Establishes the shell-side composition seam for `@napplet/nap/media`\n * session-control envelopes. Shape mirrors identity-proxy (Plan 12-11):\n *\n * - `dispatch(windowId, envelope)` routes napplet→shell media requests\n * (`media.session.create`, `media.session.update`, `media.session.destroy`,\n * `media.state`, `media.capabilities`) into the runtime (Plan 12-06).\n * - `emit(windowId, envelope)` posts shell→napplet pushes (`media.command`,\n * `media.controls`, `media.session.create.result`) through the origin\n * registry.\n *\n * By default `createShellBridge()` does NOT compose this proxy into its\n * dispatch path — the runtime owns media.* dispatch. This module is an\n * optional composition point for host apps (e.g. shell-rendered playback\n * UIs that want to send `media.command` pushes).\n */\n\nimport type { Runtime } from '@kehto/runtime';\nimport type { NappletMessage } from '@napplet/core';\nimport type { ProxyOriginRegistry } from './identity-proxy.js';\n\n/**\n * Dependencies for `createMediaProxy`.\n *\n * @example\n * ```ts\n * const proxy = createMediaProxy({\n * runtime: shellBridge.runtime,\n * originRegistry,\n * });\n * ```\n */\nexport interface MediaProxyDeps {\n /** The runtime engine that owns media.* dispatch (Plan 12-06). */\n runtime: Runtime;\n /** Origin registry for resolving windowId → iframe Window. */\n originRegistry: ProxyOriginRegistry;\n}\n\n/**\n * Per-domain proxy for `media.*` envelopes.\n *\n * Shape: `dispatch` routes napplet→shell requests into the runtime; `emit`\n * pushes shell→napplet envelopes through the iframe's Window.\n */\nexport interface MediaProxy {\n /**\n * Route a napplet-originated media.* envelope into the runtime.\n *\n * @param windowId - The source napplet's windowId\n * @param envelope - The NIP-5D NappletMessage envelope\n */\n dispatch(windowId: string, envelope: NappletMessage): void;\n /**\n * Push a shell-initiated media-domain envelope (e.g. `media.command`,\n * `media.controls`) into a napplet iframe.\n *\n * No-op when the originRegistry cannot resolve the windowId (unknown or\n * unregistered napplet). Never throws.\n *\n * @param windowId - The target napplet's windowId\n * @param envelope - The NIP-5D NappletMessage envelope to deliver\n */\n emit(windowId: string, envelope: NappletMessage): void;\n}\n\n/**\n * Factory for the canonical media-domain proxy.\n *\n * @param deps - Runtime + origin registry\n * @returns A {@link MediaProxy} ready to route media.* envelopes\n * @example\n * ```ts\n * import { createMediaProxy, originRegistry, createShellBridge } from '@kehto/shell';\n *\n * const bridge = createShellBridge(hooks);\n * const mediaProxy = createMediaProxy({\n * runtime: bridge.runtime,\n * originRegistry,\n * });\n *\n * // Shell-UI-initiated media command:\n * mediaProxy.emit('win-player', {\n * type: 'media.command',\n * sessionId: 's1',\n * action: 'seek',\n * value: 120,\n * });\n * ```\n */\nexport function createMediaProxy(deps: MediaProxyDeps): MediaProxy {\n return {\n dispatch(windowId: string, envelope: NappletMessage): void {\n deps.runtime.handleMessage(windowId, envelope);\n },\n emit(windowId: string, envelope: NappletMessage): void {\n const win = deps.originRegistry.getIframeWindow(windowId);\n if (win) win.postMessage(envelope, '*');\n },\n };\n}\n","/**\n * notify-proxy.ts — Shell-side per-domain proxy for notify.* envelopes.\n *\n * Establishes the shell-side composition seam for `@napplet/nap/notify`\n * notification envelopes. Shape mirrors identity-proxy (Plan 12-11):\n *\n * - `dispatch(windowId, envelope)` routes napplet→shell notify requests\n * (`notify.send`, `notify.dismiss`, `notify.badge`,\n * `notify.channel.register`, `notify.permission.request`) into the\n * runtime (Plan 12-07).\n * - `emit(windowId, envelope)` posts shell→napplet pushes\n * (`notify.send.result`, `notify.permission.result`, `notify.action`,\n * `notify.clicked`, `notify.dismissed`, `notify.controls`) through the\n * origin registry.\n *\n * By default `createShellBridge()` does NOT compose this proxy into its\n * dispatch path — the runtime owns notify.* dispatch. This module is an\n * optional composition point for host apps (e.g. custom notification UIs\n * that need to emit `notify.clicked` / `notify.action` pushes).\n */\n\nimport type { Runtime } from '@kehto/runtime';\nimport type { NappletMessage } from '@napplet/core';\nimport type { ProxyOriginRegistry } from './identity-proxy.js';\n\n/**\n * Dependencies for `createNotifyProxy`.\n *\n * @example\n * ```ts\n * const proxy = createNotifyProxy({\n * runtime: shellBridge.runtime,\n * originRegistry,\n * });\n * ```\n */\nexport interface NotifyProxyDeps {\n /** The runtime engine that owns notify.* dispatch (Plan 12-07). */\n runtime: Runtime;\n /** Origin registry for resolving windowId → iframe Window. */\n originRegistry: ProxyOriginRegistry;\n}\n\n/**\n * Per-domain proxy for `notify.*` envelopes.\n *\n * Shape: `dispatch` routes napplet→shell requests into the runtime; `emit`\n * pushes shell→napplet envelopes through the iframe's Window.\n */\nexport interface NotifyProxy {\n /**\n * Route a napplet-originated notify.* envelope into the runtime.\n *\n * @param windowId - The source napplet's windowId\n * @param envelope - The NIP-5D NappletMessage envelope\n */\n dispatch(windowId: string, envelope: NappletMessage): void;\n /**\n * Push a shell-initiated notify-domain envelope (e.g. `notify.action`,\n * `notify.clicked`) into a napplet iframe.\n *\n * No-op when the originRegistry cannot resolve the windowId (unknown or\n * unregistered napplet). Never throws.\n *\n * @param windowId - The target napplet's windowId\n * @param envelope - The NIP-5D NappletMessage envelope to deliver\n */\n emit(windowId: string, envelope: NappletMessage): void;\n}\n\n/**\n * Factory for the canonical notify-domain proxy.\n *\n * @param deps - Runtime + origin registry\n * @returns A {@link NotifyProxy} ready to route notify.* envelopes\n * @example\n * ```ts\n * import { createNotifyProxy, originRegistry, createShellBridge } from '@kehto/shell';\n *\n * const bridge = createShellBridge(hooks);\n * const notifyProxy = createNotifyProxy({\n * runtime: bridge.runtime,\n * originRegistry,\n * });\n *\n * // Shell-UI notifies napplet that user clicked its toast:\n * notifyProxy.emit('win-chat', {\n * type: 'notify.clicked',\n * notificationId: 'shell-42',\n * });\n * ```\n */\nexport function createNotifyProxy(deps: NotifyProxyDeps): NotifyProxy {\n return {\n dispatch(windowId: string, envelope: NappletMessage): void {\n deps.runtime.handleMessage(windowId, envelope);\n },\n emit(windowId: string, envelope: NappletMessage): void {\n const win = deps.originRegistry.getIframeWindow(windowId);\n if (win) win.postMessage(envelope, '*');\n },\n };\n}\n"],"mappings":"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","names":["originRegistry","STORAGE_KEY","ALL_CAPABILITIES"]}
|
|
1
|
+
{"version":3,"sources":["../src/shell-bridge.ts","../src/hooks-adapter.ts","../src/origin-registry.ts","../src/session-registry.ts","../src/acl-store.ts","../src/manifest-cache.ts","../src/audio-manager.ts","../src/keys-forwarder.ts","../src/shell-init.ts","../src/shell-ready.ts","../src/napplet-namespace.ts","../src/index.ts","../src/topics.ts","../src/identity-proxy.ts","../src/theme-proxy.ts","../src/keys-proxy.ts","../src/media-proxy.ts","../src/notify-proxy.ts"],"sourcesContent":["\nimport { createRuntime, type ConsentRequest, type Runtime } from '@kehto/runtime';\nimport { adaptHooks } from './hooks-adapter.js';\nimport { originRegistry } from './origin-registry.js';\nimport { sessionRegistry, nappKeyRegistry } from './session-registry.js';\nimport { aclStore } from './acl-store.js';\nimport { manifestCache } from './manifest-cache.js';\nimport { audioManager } from './audio-manager.js';\nimport type { ShellAdapter, UnroutedMessageInfo } from './types.js';\nimport type { NappletMessage } from '@napplet/core';\nimport type { Theme } from '@napplet/nap/theme/types';\nimport { createKeysForwarder, type KeysForwarder } from './keys-forwarder.js';\nimport { handleShellReady } from './shell-ready.js';\n\n/**\n * Shell-side message bridge that handles NIP-5D communication with napplet iframes.\n *\n * The bridge acts as a browser adapter: it receives raw MessageEvents from\n * window.addEventListener('message', ...), extracts the source Window, resolves\n * it to a windowId via originRegistry, and delegates NIP-5D envelope messages\n * to the runtime engine. The shell.ready/shell.init capability handshake is\n * handled locally within the bridge and never forwarded to the runtime.\n *\n * @example\n * ```ts\n * import { createShellBridge } from '@kehto/shell';\n *\n * const bridge = createShellBridge(hooks);\n * window.addEventListener('message', bridge.handleMessage);\n * ```\n */\nexport interface ShellBridge {\n /**\n * Handle an incoming postMessage from a napplet iframe.\n *\n * Only NIP-5D envelope objects (plain objects with a `.type` string) are\n * accepted. NIP-01 arrays and all other message shapes are silently dropped\n * (clean break — no legacy array fallback).\n *\n * shell.ready messages are handled locally: the bridge responds with shell.init\n * containing the capability set and registered service list. All other envelopes\n * are delegated to the runtime's NAP domain dispatch.\n *\n * @param event - The raw MessageEvent from window.addEventListener('message', ...)\n * @example\n * ```ts\n * window.addEventListener('message', bridge.handleMessage);\n * ```\n */\n handleMessage(event: MessageEvent): void;\n\n /**\n * Inject a shell-originated event into subscription delivery. Under NIP-5D,\n * shell-originated events are forwarded to napplets as inc.event envelope\n * messages. The runtime's injectEvent() handles the per-session routing.\n *\n * v1.10 hard-removed the v1.8 soft-rename compatibility branch for the\n * old `auth:identity-changed` topic. Use the canonical `identity:changed`\n * topic for identity-change pushes.\n *\n * @param topic - The event topic tag value. Forwarded exactly once.\n * @param payload - The event content\n * @example\n * ```ts\n * bridge.injectEvent('identity:changed', { pubkey: userPubkey });\n * ```\n */\n injectEvent(topic: string, payload: unknown): void;\n\n /**\n * Destroy the bridge instance, cleaning up all internal state.\n * Persists manifest cache and clears all subscriptions, buffers, and registries.\n * Call when the shell is shutting down or the bridge is no longer needed.\n *\n * @example\n * ```ts\n * bridge.destroy();\n * ```\n */\n destroy(): void;\n\n /**\n * Register a handler for consent requests on destructive signing kinds.\n * Called when a napplet requests signing for kinds 0, 3, 5, or 10002.\n *\n * @param handler - Callback receiving the consent request with a resolve function\n * @example\n * ```ts\n * bridge.registerConsentHandler((request) => {\n * const allowed = confirm(`Allow signing kind ${request.event.kind}?`);\n * request.resolve(allowed);\n * });\n * ```\n */\n registerConsentHandler(handler: (request: ConsentRequest) => void): void;\n\n /**\n * Publish a theme update to every registered napplet.\n *\n * Posts a `theme.changed` envelope (shell → napplet push) to every\n * window currently tracked by the runtime's sessionRegistry, using\n * the browser-adapter originRegistry to resolve windowId → iframe.\n * Napplets whose window cannot be resolved (stale sessions) are\n * silently skipped; this method never throws.\n *\n * ACL is enforced BY THE RECIPIENT NAPPLET — the runtime's\n * `themeMap` in @kehto/acl assigns `recipientCap: 'theme:read'` for\n * `theme.changed`, and @napplet/shim drops pushes the napplet lacks\n * the capability for. Hosts should not self-filter here.\n *\n * @param theme - The new theme payload to broadcast.\n * @example\n * ```ts\n * bridge.publishTheme({\n * colors: { background: '#0a0a0a', text: '#e0e0e0', primary: '#7aa2f7' },\n * title: 'Dark',\n * });\n * ```\n */\n publishTheme(theme: Theme): void;\n\n /**\n * Publish the current shell-user identity to every loaded napplet.\n *\n * Posts an `identity.changed` envelope (shell → napplet push) with the\n * current user pubkey. An empty pubkey means no signer/user identity is\n * currently connected. This is distinct from NIP-5D napplet session identity,\n * which remains source-bound at iframe creation.\n *\n * @param pubkey - Current user's hex pubkey, or empty string when signed out.\n */\n publishIdentityChanged(pubkey: string): void;\n\n /**\n * Access the underlying runtime instance for advanced use cases.\n * Provides direct access to the runtime's sessionRegistry, aclState,\n * and manifestCache.\n */\n readonly runtime: Runtime;\n\n}\n\n/**\n * Create a ShellBridge instance with dependency injection via hooks.\n *\n * Internally creates a Runtime from @kehto/runtime and adapts the\n * browser-oriented ShellAdapter into environment-agnostic RuntimeAdapter.\n *\n * @param hooks - Host application provides relay pool, auth, config, etc.\n * @returns A ShellBridge instance ready to handle napplet messages\n * @example\n * ```ts\n * import { createShellBridge, type ShellAdapter } from '@kehto/shell';\n *\n * const hooks: ShellAdapter = {\n * relayPool: myRelayPoolHooks,\n * relayConfig: myRelayConfigHooks,\n * windowManager: myWindowManagerHooks,\n * auth: myAuthHooks,\n * config: myConfigHooks,\n * hotkeys: myHotkeyHooks,\n * workerRelay: myWorkerRelayHooks,\n * crypto: myCryptoHooks,\n * };\n * const bridge = createShellBridge(hooks);\n * ```\n */\n/**\n * Fire the optional {@link ShellAdapter.onUnroutedMessage} diagnostic hook for a\n * message the bridge is about to drop. Extracts the envelope `type` defensively\n * (the message-shape guard hasn't run yet at the drop points) and never throws —\n * a misbehaving host hook must not break message handling.\n */\nfunction reportUnrouted(\n hooks: ShellAdapter,\n event: MessageEvent,\n reason: UnroutedMessageInfo['reason'],\n): void {\n if (!hooks.onUnroutedMessage) return;\n const data = event.data as { type?: unknown } | null | undefined;\n const type = typeof data === 'object' && data !== null && typeof data.type === 'string'\n ? data.type\n : undefined;\n try {\n hooks.onUnroutedMessage({ type, origin: event.origin, reason });\n } catch {\n // Observability must never break routing — swallow host hook errors.\n }\n}\n\nexport function createShellBridge(hooks: ShellAdapter): ShellBridge {\n const runtimeHooks = adaptHooks(hooks, {\n originRegistry,\n manifestCache,\n aclStore,\n audioManager,\n nappKeyRegistry,\n });\n\n const runtime: Runtime = createRuntime(runtimeHooks);\n\n function broadcastToNapplets(envelope: NappletMessage): void {\n // Use originRegistry.getAllWindowIds() rather than sessionRegistry.getAllEntries()\n // because demo napplets share pubkey:'' — the byPubkey map only retains one entry\n // per pubkey key, so getAllEntries() would return only the last-registered napplet\n // when multiple napplets have the same (empty) pubkey. originRegistry is keyed by\n // Window reference so it has one entry per distinct iframe regardless of pubkey.\n const windowIds = originRegistry.getAllWindowIds();\n for (const windowId of windowIds) {\n const win = originRegistry.getIframeWindow(windowId);\n if (!win) continue;\n win.postMessage(envelope, '*');\n }\n }\n\n // Attach the host-keydown forwarder (Plan 12-11 / NAP-05 shell half).\n // Skips construction in DOM-less environments (SSR / early Node tests);\n // any failure is swallowed so a malformed DOM never blocks bridge creation.\n let keysForwarder: KeysForwarder | null = null;\n if (typeof window !== 'undefined') {\n try {\n keysForwarder = createKeysForwarder({\n originRegistry,\n sessionRegistry,\n hasKeysForwardCap: (pubkey: string) => {\n const entry = sessionRegistry.getEntry(pubkey);\n if (!entry) return false;\n const acl = aclStore.getEntry(entry.pubkey, entry.dTag, entry.aggregateHash);\n return acl?.capabilities.includes('keys:forward') ?? false;\n },\n });\n } catch {\n // DOM present but addEventListener failed — proceed without the forwarder.\n keysForwarder = null;\n }\n }\n\n return {\n handleMessage(event: MessageEvent): void {\n const sourceWindow = event.source as Window | null;\n if (!sourceWindow) {\n reportUnrouted(hooks, event, 'no-source-window');\n return;\n }\n const windowId = originRegistry.getWindowId(sourceWindow);\n if (!windowId) {\n reportUnrouted(hooks, event, 'unregistered-window');\n return;\n }\n const msg = event.data;\n\n // NIP-5D envelope-only guard (clean break — no legacy array support)\n if (typeof msg !== 'object' || msg === null || typeof msg.type !== 'string') return;\n\n // Handle shell.ready handshake locally (not forwarded to runtime)\n if (msg.type === 'shell.ready') {\n handleShellReady({\n hooks,\n origin: event.origin,\n runtime,\n sourceRegistrationId: originRegistry.getRegistrationId(sourceWindow) ?? 0,\n sourceWindow,\n windowId,\n });\n return;\n }\n\n // Delegate to runtime — runtime handles NAP domain dispatch\n runtime.handleMessage(windowId, msg);\n },\n\n injectEvent(topic: string, payload: unknown): void {\n runtime.injectEvent(topic, payload);\n },\n\n destroy(): void {\n keysForwarder?.destroy();\n runtime.destroy();\n },\n\n registerConsentHandler(handler: (request: ConsentRequest) => void): void {\n runtime.registerConsentHandler(handler);\n },\n\n publishTheme(theme: Theme): void {\n const envelope: NappletMessage = { type: 'theme.changed', theme } as NappletMessage;\n broadcastToNapplets(envelope);\n },\n\n publishIdentityChanged(pubkey: string): void {\n const envelope: NappletMessage = { type: 'identity.changed', pubkey } as NappletMessage;\n broadcastToNapplets(envelope);\n },\n\n get runtime() {\n return runtime;\n },\n };\n}\n","/**\n * hooks-adapter.ts — Converts ShellAdapter (browser-facing) to RuntimeAdapter (environment-agnostic).\n *\n * The adapter bridges the gap between the shell's browser-oriented ShellAdapter interfaces\n * (Window references, localStorage, postMessage) and the runtime's abstract RuntimeAdapter\n * (windowId strings, persistence interfaces, sendToNapplet callbacks).\n */\n\nimport type { NostrEvent, NostrFilter } from '@napplet/core';\nimport type {\n RuntimeAdapter,\n RelayPoolAdapter,\n CacheAdapter,\n AuthAdapter,\n Signer,\n ConfigAdapter,\n HotkeyAdapter,\n AclPersistence,\n ManifestPersistence,\n StatePersistence,\n CryptoAdapter,\n WindowManagerAdapter,\n RelayConfigAdapter,\n DmAdapter,\n SendToNapplet,\n RelaySubscriptionHandle,\n ShellSecretPersistence,\n GuidPersistence,\n} from '@kehto/runtime';\n\nfunction bytesToHex(bytes: Uint8Array): string {\n return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');\n}\n\nfunction hexToBytes(hex: string): Uint8Array {\n const bytes = new Uint8Array(hex.length / 2);\n for (let i = 0; i < hex.length; i += 2) {\n bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16);\n }\n return bytes;\n}\nimport type { ShellAdapter } from './types.js';\nimport type { originRegistry as OriginRegistryType } from './origin-registry.js';\nimport type { manifestCache as ManifestCacheType } from './manifest-cache.js';\nimport type { aclStore as AclStoreType } from './acl-store.js';\nimport type { audioManager as AudioManagerType } from './audio-manager.js';\nimport type { sessionRegistry as SessionRegistryType } from './session-registry.js';\n\n/**\n * Browser-specific singletons that the adapter bridges to the runtime.\n * These use browser APIs (Window, localStorage, postMessage, CustomEvent)\n * that the runtime cannot access directly.\n */\nexport interface BrowserDeps {\n originRegistry: typeof OriginRegistryType;\n manifestCache: typeof ManifestCacheType;\n aclStore: typeof AclStoreType;\n audioManager: typeof AudioManagerType;\n nappKeyRegistry: typeof SessionRegistryType;\n}\n\nfunction createSendToNapplet(originRegistry: BrowserDeps['originRegistry']): SendToNapplet {\n return (windowId, msg) => {\n const win = originRegistry.getIframeWindow(windowId);\n if (win) win.postMessage(msg, '*');\n };\n}\n\nfunction createRelayPoolAdapter(\n shellHooks: ShellAdapter,\n originRegistry: BrowserDeps['originRegistry'],\n): RelayPoolAdapter {\n return {\n subscribe(\n filters: NostrFilter[],\n callback: (item: NostrEvent | 'EOSE') => void,\n relayUrls?: string[],\n ): RelaySubscriptionHandle {\n const pool = shellHooks.relayPool.getRelayPool();\n if (!pool) return { unsubscribe() { /* no-op */ } };\n\n const urls = relayUrls ?? shellHooks.relayPool.selectRelayTier(filters);\n const sub = pool.subscription(urls, filters).subscribe((item) => {\n if (item === 'EOSE') callback('EOSE');\n else callback(item as NostrEvent);\n });\n return { unsubscribe: () => sub.unsubscribe() };\n },\n\n publish(event: NostrEvent): void {\n const pool = shellHooks.relayPool.getRelayPool();\n if (!pool) return;\n const relayUrls = shellHooks.relayPool.selectRelayTier([]);\n pool.publish(relayUrls, event);\n },\n\n selectRelayTier(filters: NostrFilter[]): string[] {\n return shellHooks.relayPool.selectRelayTier(filters);\n },\n\n trackSubscription(subKey: string, cleanup: () => void): void {\n shellHooks.relayPool.trackSubscription(subKey, cleanup);\n },\n\n untrackSubscription(subKey: string): void {\n shellHooks.relayPool.untrackSubscription(subKey);\n },\n\n openScopedRelay(\n windowId: string,\n relayUrl: string,\n subId: string,\n filters: NostrFilter[],\n _sendFn: SendToNapplet,\n ): void {\n const win = originRegistry.getIframeWindow(windowId);\n if (win) shellHooks.relayPool.openScopedRelay(windowId, relayUrl, subId, filters, win);\n },\n\n closeScopedRelay(windowId: string): void {\n shellHooks.relayPool.closeScopedRelay(windowId);\n },\n\n publishToScopedRelay(windowId: string, event: NostrEvent): boolean {\n return shellHooks.relayPool.publishToScopedRelay(windowId, event);\n },\n\n isAvailable(): boolean {\n return shellHooks.relayPool.getRelayPool() !== null;\n },\n };\n}\n\nfunction createCacheAdapter(shellHooks: ShellAdapter): CacheAdapter {\n return {\n async query(filters: NostrFilter[]): Promise<NostrEvent[]> {\n const workerRelay = shellHooks.workerRelay.getWorkerRelay();\n if (!workerRelay) return [];\n const subId = crypto.randomUUID();\n return workerRelay.query(['REQ', subId, ...filters]);\n },\n\n store(event: NostrEvent): void {\n const workerRelay = shellHooks.workerRelay.getWorkerRelay();\n if (!workerRelay) return;\n try { workerRelay.event(event)?.catch?.(() => { /* best-effort */ }); } catch { /* best-effort */ }\n },\n\n isAvailable(): boolean {\n return shellHooks.workerRelay.getWorkerRelay() !== null;\n },\n };\n}\n\nfunction createAuthAdapter(shellHooks: ShellAdapter): AuthAdapter {\n return {\n getUserPubkey(): string | null {\n return shellHooks.auth.getUserPubkey();\n },\n getSigner(): Signer | null {\n return shellHooks.auth.getSigner();\n },\n };\n}\n\nfunction createConfigAdapter(shellHooks: ShellAdapter): ConfigAdapter {\n return {\n getNappUpdateBehavior(): 'auto-grant' | 'banner' | 'silent-reprompt' {\n return shellHooks.config.getNappUpdateBehavior();\n },\n };\n}\n\nfunction createHotkeyAdapter(shellHooks: ShellAdapter): HotkeyAdapter {\n return {\n executeHotkeyFromForward(event): void {\n shellHooks.hotkeys.executeHotkeyFromForward(event);\n },\n };\n}\n\nfunction createCryptoAdapter(shellHooks: ShellAdapter): CryptoAdapter {\n return {\n async verifyEvent(event: NostrEvent): Promise<boolean> {\n return shellHooks.crypto.verifyEvent(event);\n },\n randomUUID(): string {\n return crypto.randomUUID();\n },\n randomBytes(length: number): Uint8Array {\n const bytes = new Uint8Array(length);\n crypto.getRandomValues(bytes);\n return bytes;\n },\n };\n}\n\nfunction createAclPersistence(): AclPersistence {\n return {\n persist(data: string): void {\n try { localStorage.setItem('napplet:acl', data); } catch { /* best-effort */ }\n },\n load(): string | null {\n try { return localStorage.getItem('napplet:acl'); } catch { return null; }\n },\n };\n}\n\nfunction createManifestPersistence(): ManifestPersistence {\n return {\n persist(data: string): void {\n try { localStorage.setItem('napplet:manifest-cache', data); } catch { /* best-effort */ }\n },\n load(): string | null {\n try { return localStorage.getItem('napplet:manifest-cache'); } catch { return null; }\n },\n };\n}\n\nfunction createStatePersistence(): StatePersistence {\n return {\n get(scopedKey: string): string | null {\n try { return localStorage.getItem(scopedKey); } catch { return null; }\n },\n set(scopedKey: string, value: string): boolean {\n try { localStorage.setItem(scopedKey, value); return true; } catch { return false; }\n },\n remove(scopedKey: string): void {\n try { localStorage.removeItem(scopedKey); } catch { /* best-effort */ }\n },\n clear(prefix: string): void {\n try {\n const keysToRemove: string[] = [];\n for (let i = 0; i < localStorage.length; i++) {\n const key = localStorage.key(i);\n if (key?.startsWith(prefix)) keysToRemove.push(key);\n }\n for (const key of keysToRemove) localStorage.removeItem(key);\n } catch { /* best-effort */ }\n },\n keys(prefix: string): string[] {\n try {\n const result: string[] = [];\n for (let i = 0; i < localStorage.length; i++) {\n const key = localStorage.key(i);\n if (key?.startsWith(prefix)) result.push(key);\n }\n return result;\n } catch { return []; }\n },\n calculateBytes(prefix: string, excludeKey?: string): number {\n try {\n let total = 0;\n for (let i = 0; i < localStorage.length; i++) {\n const key = localStorage.key(i);\n if (!key?.startsWith(prefix)) continue;\n if (excludeKey && key === excludeKey) continue;\n const value = localStorage.getItem(key) ?? '';\n total += new TextEncoder().encode(key + value).length;\n }\n return total;\n } catch { return 0; }\n },\n };\n}\n\nfunction createWindowManagerAdapter(shellHooks: ShellAdapter): WindowManagerAdapter {\n return {\n createWindow(options): string | null {\n return shellHooks.windowManager.createWindow(options);\n },\n };\n}\n\nfunction createRelayConfigAdapter(shellHooks: ShellAdapter): RelayConfigAdapter {\n return {\n addRelay(tier: string, url: string): void {\n shellHooks.relayConfig.addRelay(tier, url);\n },\n removeRelay(tier: string, url: string): void {\n shellHooks.relayConfig.removeRelay(tier, url);\n },\n getRelayConfig(): { discovery: string[]; super: string[]; outbox: string[] } {\n return shellHooks.relayConfig.getRelayConfig();\n },\n getNip66Suggestions(): unknown {\n return shellHooks.relayConfig.getNip66Suggestions();\n },\n };\n}\n\nfunction createShellSecretPersistence(): ShellSecretPersistence {\n return {\n get(): Uint8Array | null {\n try {\n const hex = localStorage.getItem('napplet-shell-secret');\n if (!hex) return null;\n return hexToBytes(hex);\n } catch { return null; }\n },\n set(secret: Uint8Array): void {\n try {\n localStorage.setItem('napplet-shell-secret', bytesToHex(secret));\n } catch { /* localStorage unavailable */ }\n },\n };\n}\n\nfunction createGuidPersistence(): GuidPersistence {\n return {\n get(windowId: string): string | null {\n try {\n return localStorage.getItem(`napplet-guid:${windowId}`);\n } catch { return null; }\n },\n set(windowId: string, guid: string): void {\n try {\n localStorage.setItem(`napplet-guid:${windowId}`, guid);\n } catch { /* localStorage unavailable */ }\n },\n remove(windowId: string): void {\n try {\n localStorage.removeItem(`napplet-guid:${windowId}`);\n } catch { /* localStorage unavailable */ }\n },\n };\n}\n\nfunction createDmAdapter(shellHooks: ShellAdapter): DmAdapter | undefined {\n return shellHooks.dm\n ? {\n sendDm(recipientPubkey: string, message: string) {\n return shellHooks.dm!.sendDm(recipientPubkey, message);\n },\n }\n : undefined;\n}\n\n/**\n * Convert ShellAdapter (browser-facing) into RuntimeAdapter (environment-agnostic).\n *\n * The adapter is the single translation layer between browser APIs and the\n * runtime's abstract interfaces. It:\n * - Converts Window references to windowId strings via originRegistry\n * - Wraps localStorage-backed singletons into persistence interfaces\n * - Translates relay pool API shapes (Observable → callback)\n *\n * @param shellHooks - The browser-oriented ShellAdapter provided by the host app\n * @param deps - Browser-specific singletons (originRegistry, aclStore, etc.)\n * @returns RuntimeAdapter suitable for createRuntime()\n *\n * @example\n * ```ts\n * const runtimeHooks = adaptHooks(shellHooks, {\n * originRegistry, manifestCache, aclStore, audioManager, nappKeyRegistry,\n * });\n * const runtime = createRuntime(runtimeHooks);\n * ```\n */\nexport function adaptHooks(shellHooks: ShellAdapter, deps: BrowserDeps): RuntimeAdapter {\n const { originRegistry } = deps;\n\n return {\n sendToNapplet: createSendToNapplet(originRegistry),\n relayPool: createRelayPoolAdapter(shellHooks, originRegistry),\n cache: createCacheAdapter(shellHooks),\n auth: createAuthAdapter(shellHooks),\n config: createConfigAdapter(shellHooks),\n hotkeys: createHotkeyAdapter(shellHooks),\n crypto: createCryptoAdapter(shellHooks),\n aclPersistence: createAclPersistence(),\n manifestPersistence: createManifestPersistence(),\n statePersistence: createStatePersistence(),\n windowManager: createWindowManagerAdapter(shellHooks),\n relayConfig: createRelayConfigAdapter(shellHooks),\n dm: createDmAdapter(shellHooks),\n shellSecretPersistence: createShellSecretPersistence(),\n guidPersistence: createGuidPersistence(),\n onAclCheck: shellHooks.onAclCheck,\n onHashMismatch: shellHooks.onHashMismatch,\n services: shellHooks.services,\n getConfigOverrides: shellHooks.getConfigOverrides,\n };\n}\n","\ninterface OriginEntry {\n windowId: string;\n dTag?: string;\n aggregateHash?: string;\n registrationId: number;\n}\n\nconst registry = new Map<Window, OriginEntry>();\nlet nextRegistrationId = 0;\n\n/**\n * Bidirectional registry mapping Window references to windowId strings.\n * Optionally stores NIP-5D identity metadata (dTag and aggregateHash) per window.\n *\n * @example\n * ```ts\n * import { originRegistry } from '@kehto/shell';\n *\n * originRegistry.register(iframe.contentWindow, 'napp-1');\n * const id = originRegistry.getWindowId(iframe.contentWindow); // 'napp-1'\n * ```\n */\nexport const originRegistry = {\n /**\n * Register a window reference with a windowId and optional identity metadata.\n *\n * @param win - The iframe's contentWindow reference\n * @param windowId - The unique identifier for this napplet window\n * @param identity - Optional NIP-5D identity metadata (dTag and aggregateHash)\n */\n register(win: Window, windowId: string, identity?: { dTag: string; aggregateHash: string }): void {\n for (const [registeredWin, entry] of registry.entries()) {\n if (registeredWin === win || entry.windowId === windowId) {\n registry.delete(registeredWin);\n }\n }\n\n registry.set(win, {\n windowId,\n dTag: identity?.dTag,\n aggregateHash: identity?.aggregateHash,\n registrationId: ++nextRegistrationId,\n });\n },\n\n /**\n * Unregister a window by its windowId, removing the mapping.\n *\n * @param windowId - The window identifier to remove\n */\n unregister(windowId: string): void {\n for (const [win, entry] of registry.entries()) {\n if (entry.windowId === windowId) {\n registry.delete(win);\n }\n }\n },\n\n /**\n * Look up the windowId for a given Window reference.\n *\n * @param win - The Window reference (typically from event.source)\n * @returns The windowId string, or undefined if not registered\n */\n getWindowId(win: Window): string | undefined {\n return registry.get(win)?.windowId;\n },\n\n /**\n * Look up the Window reference for a given windowId.\n *\n * @param windowId - The window identifier to look up\n * @returns The Window reference, or null if not found\n */\n getIframeWindow(windowId: string): Window | null {\n for (const [win, entry] of registry.entries()) {\n if (entry.windowId === windowId) return win;\n }\n return null;\n },\n\n /**\n * Get all registered windowId strings.\n *\n * @returns Array of all registered window identifiers\n */\n getAllWindowIds(): string[] {\n return Array.from(registry.values()).map(entry => entry.windowId);\n },\n\n /**\n * Get identity metadata for a registered Window.\n *\n * @param win - The Window reference to look up\n * @returns Identity metadata, or undefined if not registered or no identity set\n */\n getIdentity(win: Window): { dTag: string; aggregateHash: string } | undefined {\n const entry = registry.get(win);\n if (!entry?.dTag || !entry?.aggregateHash) return undefined;\n return { dTag: entry.dTag, aggregateHash: entry.aggregateHash };\n },\n\n /**\n * Look up the monotonically increasing registration id for a Window.\n *\n * @param win - The Window reference to look up\n * @returns The registration id, or undefined if the Window is not registered\n */\n getRegistrationId(win: Window): number | undefined {\n return registry.get(win)?.registrationId;\n },\n\n /** Clear all registrations. */\n clear(): void {\n registry.clear();\n },\n};\n","/**\n * SessionRegistry — windowId to verified napplet pubkey bidirectional mapping.\n *\n * After a successful AUTH handshake, the ShellBridge registers the napplet's\n * verified pubkey here. Both mappings are kept in sync.\n */\n\nimport type { PendingUpdate, SessionEntry } from '@kehto/runtime';\n\n/**\n * A pending napplet update — raised when a napplet reconnects with a different aggregateHash.\n * @example\n * ```ts\n * const update: PendingUpdate = {\n * windowId: 'win-1', pubkey: 'abc...', dTag: '3chat',\n * oldHash: 'aaa', newHash: 'bbb',\n * resolve: (action) => { if (action === 'accept') { // apply } },\n * };\n * ```\n */\nexport type { PendingUpdate } from '@kehto/runtime';\n\nconst byWindowId = new Map<string, string>();\nconst byPubkey = new Map<string, SessionEntry>();\nconst pendingUpdates = new Map<string, PendingUpdate>();\n\nlet _pendingVersion = 0;\n\n/**\n * Bidirectional registry mapping windowIds to verified napplet pubkeys.\n * Maintained by ShellBridge after successful AUTH handshakes.\n *\n * @example\n * ```ts\n * import { sessionRegistry } from '@kehto/shell';\n *\n * const pubkey = sessionRegistry.getPubkey('win-1');\n * const entry = pubkey ? sessionRegistry.getEntry(pubkey) : undefined;\n * ```\n */\nexport const sessionRegistry = {\n /**\n * Register a napplet entry, mapping windowId to pubkey and vice versa.\n *\n * @param windowId - The window identifier\n * @param entry - The verified napplet session entry from AUTH handshake\n */\n register(windowId: string, entry: SessionEntry): void {\n byWindowId.set(windowId, entry.pubkey);\n byPubkey.set(entry.pubkey, entry);\n },\n\n /**\n * Unregister a napplet by windowId, removing both mappings.\n *\n * @param windowId - The window identifier to remove\n */\n unregister(windowId: string): void {\n const pubkey = byWindowId.get(windowId);\n if (pubkey) {\n byPubkey.delete(pubkey);\n byWindowId.delete(windowId);\n }\n pendingUpdates.delete(windowId);\n },\n\n /**\n * Get the pubkey associated with a windowId.\n *\n * @param windowId - The window identifier\n * @returns The napplet's pubkey, or undefined if not registered\n */\n getPubkey(windowId: string): string | undefined {\n return byWindowId.get(windowId);\n },\n\n /**\n * Get the full entry for a napplet pubkey.\n *\n * @param pubkey - The napplet's pubkey\n * @returns The full SessionEntry, or undefined if not found\n */\n getEntry(pubkey: string): SessionEntry | undefined {\n return byPubkey.get(pubkey);\n },\n\n /**\n * Get the windowId for a napplet pubkey.\n *\n * @param pubkey - The napplet's pubkey\n * @returns The windowId, or undefined if not found\n */\n getWindowId(pubkey: string): string | undefined {\n return byPubkey.get(pubkey)?.windowId;\n },\n\n /**\n * Check if a windowId has a registered napplet.\n *\n * @param windowId - The window identifier\n * @returns True if the windowId has a registered napplet\n */\n isRegistered(windowId: string): boolean {\n return byWindowId.has(windowId);\n },\n\n /**\n * Get all registered napplet entries.\n *\n * @returns Array of all SessionEntry objects\n */\n getAllEntries(): SessionEntry[] {\n return Array.from(byPubkey.values());\n },\n\n /**\n * Set a pending update for a window (napplet reconnected with different hash).\n *\n * @param windowId - The window identifier\n * @param update - The pending update details with resolve callback\n */\n setPendingUpdate(windowId: string, update: PendingUpdate): void {\n pendingUpdates.set(windowId, update);\n _pendingVersion++;\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new CustomEvent('napplet:pending-update', { detail: { windowId } }));\n }\n },\n\n /**\n * Get a pending update for a window.\n *\n * @param windowId - The window identifier\n * @returns The pending update, or undefined if none\n */\n getPendingUpdate(windowId: string): PendingUpdate | undefined {\n return pendingUpdates.get(windowId);\n },\n\n /**\n * Clear a pending update for a window.\n *\n * @param windowId - The window identifier\n */\n clearPendingUpdate(windowId: string): void {\n pendingUpdates.delete(windowId);\n _pendingVersion++;\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new CustomEvent('napplet:pending-update', { detail: { windowId } }));\n }\n },\n\n /** Clear all registrations and pending updates. */\n clear(): void {\n byWindowId.clear();\n byPubkey.clear();\n pendingUpdates.clear();\n },\n};\n\n/** @deprecated Use sessionRegistry. Will be removed in v0.9.0. */\nexport const nappKeyRegistry = sessionRegistry;\n","\nimport { ALL_CAPABILITIES, type Capability } from '@kehto/runtime';\nimport { migrateAclState, type AclState } from '@kehto/acl';\nimport type { AclEntry } from './types.js';\n\nconst STORAGE_KEY = 'napplet:acl';\n\n/** Default state quota in bytes (512 KB) per napplet identity. */\nexport const DEFAULT_STATE_QUOTA = 512 * 1024;\n\ninterface InternalAclEntry {\n key: string;\n pubkey: string;\n dTag: string;\n aggregateHash: string;\n capabilities: Set<Capability>;\n blocked: boolean;\n stateQuota: number;\n}\n\nfunction aclKey(_pubkey: string, dTag: string, aggregateHash: string): string {\n return `${dTag}:${aggregateHash}`;\n}\n\nconst store = new Map<string, InternalAclEntry>();\n\n// Capability name to bit position mapping for bitfield conversion.\n// Plan 12-10: signer bit slots (32/64/128) reclaimed by the v1.2\n// identity:read / keys:bind / keys:forward caps. v1.1 persisted ACL\n// state with those bits is reinterpreted under the v1.2 surface —\n// acceptable because the v1.1 signer actions have no napplet-visible\n// surface in NIP-5D.\nconst CAP_BITS: Record<string, number> = {\n 'relay:read': 1, 'relay:write': 2, 'cache:read': 4, 'cache:write': 8,\n 'hotkey:forward': 16,\n 'identity:read': 32, 'keys:bind': 64, 'keys:forward': 128,\n 'state:read': 256, 'state:write': 512,\n 'media:control': 1024, 'notify:send': 2048, 'notify:channel': 4096,\n 'theme:read': 8192,\n // 65536 RETIRED — was identity:decrypt (removed as a spec violation); left as\n // a permanent gap, do NOT reuse this bit.\n};\n\nfunction capArrayToBitfield(caps: Capability[]): number {\n let bits = 0;\n for (const cap of caps) bits |= (CAP_BITS[cap] ?? 0);\n return bits;\n}\n\nfunction bitfieldToCapArray(bits: number): Capability[] {\n return (Object.entries(CAP_BITS)\n .filter(([, bit]) => (bits & bit) !== 0)\n .map(([name]) => name)) as Capability[];\n}\n\nfunction getOrCreate(pubkey: string, dTag: string, aggregateHash: string): InternalAclEntry {\n const key = aclKey(pubkey, dTag, aggregateHash);\n let entry = store.get(key);\n if (!entry) {\n entry = {\n key,\n pubkey,\n dTag,\n aggregateHash,\n capabilities: new Set(ALL_CAPABILITIES),\n blocked: false,\n stateQuota: DEFAULT_STATE_QUOTA,\n };\n store.set(key, entry);\n }\n return entry;\n}\n\n/**\n * ACL store — manages capability grants, revocations, and blocks for napp identities.\n * Persists to localStorage and uses a permissive default policy (all capabilities granted).\n *\n * @example\n * ```ts\n * import { aclStore } from '@kehto/shell';\n *\n * aclStore.grant(pubkey, dTag, hash, 'relay:read');\n * const allowed = aclStore.check(pubkey, dTag, hash, 'relay:read'); // true\n * ```\n */\nexport const aclStore = {\n /**\n * Check if a napp identity has a specific capability.\n * Returns true for unknown identities (permissive default).\n *\n * @param pubkey - The napp's pubkey\n * @param dTag - The napp's dTag\n * @param aggregateHash - The napp's build hash\n * @param capability - The capability to check\n * @returns True if the capability is granted and the napp is not blocked\n */\n check(pubkey: string, dTag: string, aggregateHash: string, capability: Capability): boolean {\n const key = aclKey(pubkey, dTag, aggregateHash);\n const entry = store.get(key);\n if (!entry) return true;\n if (entry.blocked) return false;\n return entry.capabilities.has(capability);\n },\n\n /**\n * Grant a capability to a napp identity.\n *\n * @param pubkey - The napp's pubkey\n * @param dTag - The napp's dTag\n * @param aggregateHash - The napp's build hash\n * @param capability - The capability to grant\n */\n grant(pubkey: string, dTag: string, aggregateHash: string, capability: Capability): void {\n getOrCreate(pubkey, dTag, aggregateHash).capabilities.add(capability);\n },\n\n /**\n * Revoke a capability from a napp identity.\n *\n * @param pubkey - The napp's pubkey\n * @param dTag - The napp's dTag\n * @param aggregateHash - The napp's build hash\n * @param capability - The capability to revoke\n */\n revoke(pubkey: string, dTag: string, aggregateHash: string, capability: Capability): void {\n getOrCreate(pubkey, dTag, aggregateHash).capabilities.delete(capability);\n },\n\n /**\n * Block a napp identity entirely (all capabilities denied).\n *\n * @param pubkey - The napp's pubkey\n * @param dTag - The napp's dTag\n * @param aggregateHash - The napp's build hash\n */\n block(pubkey: string, dTag: string, aggregateHash: string): void {\n getOrCreate(pubkey, dTag, aggregateHash).blocked = true;\n },\n\n /**\n * Unblock a napp identity.\n *\n * @param pubkey - The napp's pubkey\n * @param dTag - The napp's dTag\n * @param aggregateHash - The napp's build hash\n */\n unblock(pubkey: string, dTag: string, aggregateHash: string): void {\n getOrCreate(pubkey, dTag, aggregateHash).blocked = false;\n },\n\n /**\n * Check if a napp identity is blocked.\n *\n * @param pubkey - The napp's pubkey\n * @param dTag - The napp's dTag\n * @param aggregateHash - The napp's build hash\n * @returns True if the identity is blocked\n */\n isBlocked(pubkey: string, dTag: string, aggregateHash: string): boolean {\n const key = aclKey(pubkey, dTag, aggregateHash);\n return store.get(key)?.blocked ?? false;\n },\n\n /**\n * Get the external ACL entry for a napp identity.\n *\n * @param pubkey - The napp's pubkey\n * @param dTag - The napp's dTag\n * @param aggregateHash - The napp's build hash\n * @returns The ACL entry, or undefined if no explicit entry exists\n */\n getEntry(pubkey: string, dTag: string, aggregateHash: string): AclEntry | undefined {\n const key = aclKey(pubkey, dTag, aggregateHash);\n const internal = store.get(key);\n if (!internal) return undefined;\n return {\n pubkey: internal.pubkey,\n capabilities: Array.from(internal.capabilities),\n blocked: internal.blocked,\n stateQuota: internal.stateQuota,\n };\n },\n\n /**\n * Get all ACL entries.\n *\n * @returns Array of all ACL entries\n */\n getAllEntries(): AclEntry[] {\n return Array.from(store.values()).map(e => ({\n pubkey: e.pubkey,\n capabilities: Array.from(e.capabilities),\n blocked: e.blocked,\n stateQuota: e.stateQuota,\n }));\n },\n\n /** Persist the ACL store to localStorage. */\n persist(): void {\n try {\n const entries = Array.from(store.entries()).map(([key, val]) => [\n key,\n {\n pubkey: val.pubkey,\n dTag: val.dTag,\n aggregateHash: val.aggregateHash,\n capabilities: Array.from(val.capabilities),\n blocked: val.blocked,\n stateQuota: val.stateQuota,\n },\n ]);\n localStorage.setItem(STORAGE_KEY, JSON.stringify(entries));\n } catch {\n // localStorage unavailable\n }\n },\n\n /** Load the ACL store from localStorage. Migrates old 3-segment keys to 2-segment format. */\n load(): void {\n try {\n const raw = localStorage.getItem(STORAGE_KEY);\n if (!raw) return;\n let entries = JSON.parse(raw) as Array<\n [string, {\n pubkey: string;\n dTag?: string;\n aggregateHash?: string;\n capabilities: Capability[];\n blocked: boolean;\n stateQuota?: number;\n }]\n >;\n\n const hasOldKeys = entries.some(([key]) => key.split(':').length === 3);\n if (hasOldKeys) {\n const tempState: AclState = {\n defaultPolicy: 'permissive',\n entries: Object.fromEntries(\n entries.map(([key, val]) => [key, {\n caps: capArrayToBitfield(val.capabilities),\n blocked: val.blocked,\n quota: val.stateQuota ?? DEFAULT_STATE_QUOTA,\n }])\n ),\n };\n const migrated = migrateAclState(tempState);\n if (migrated !== tempState) {\n // Rebuild entries from migrated state\n entries = Object.entries(migrated.entries).map(([key, entry]) => {\n const parts = key.split(':');\n return [key, {\n pubkey: '',\n dTag: parts[0] ?? '',\n aggregateHash: parts[1] ?? '',\n capabilities: bitfieldToCapArray(entry.caps),\n blocked: entry.blocked,\n stateQuota: entry.quota,\n }] as [string, typeof entries[0][1]];\n });\n // Re-persist migrated data immediately\n localStorage.setItem(STORAGE_KEY, JSON.stringify(entries));\n }\n }\n\n store.clear();\n for (const [key, val] of entries) {\n if (val.dTag === undefined || val.aggregateHash === undefined) continue;\n store.set(key, {\n key,\n pubkey: val.pubkey,\n dTag: val.dTag,\n aggregateHash: val.aggregateHash,\n capabilities: new Set(val.capabilities),\n blocked: val.blocked,\n stateQuota: val.stateQuota ?? DEFAULT_STATE_QUOTA,\n });\n }\n } catch {\n /* Corrupted ACL data in localStorage — clear and use defaults */\n store.clear();\n }\n },\n\n /**\n * Get the state quota for a napp identity.\n *\n * @param pubkey - The napp's pubkey\n * @param dTag - The napp's dTag\n * @param aggregateHash - The napp's build hash\n * @returns The quota in bytes (defaults to DEFAULT_STATE_QUOTA)\n */\n getStateQuota(pubkey: string, dTag: string, aggregateHash: string): number {\n const key = aclKey(pubkey, dTag, aggregateHash);\n return store.get(key)?.stateQuota ?? DEFAULT_STATE_QUOTA;\n },\n\n /** Clear all ACL entries and remove from localStorage. */\n clear(): void {\n store.clear();\n try {\n localStorage.removeItem(STORAGE_KEY);\n } catch {\n /* localStorage unavailable — ACL clear is best-effort */\n }\n },\n};\n","/**\n * Manifest cache — persists verified NIP-5A aggregate hashes per napplet identity.\n */\n\n/**\n * A cached manifest entry for a verified napplet build.\n * @example\n * ```ts\n * const entry: ManifestCacheEntry = {\n * pubkey: 'abc123...', dTag: '3chat',\n * aggregateHash: 'deadbeef', verifiedAt: Date.now(),\n * };\n * ```\n */\nexport interface ManifestCacheEntry {\n pubkey: string;\n dTag: string;\n aggregateHash: string;\n verifiedAt: number;\n}\n\nconst STORAGE_KEY = 'napplet:manifest-cache';\nconst cache = new Map<string, ManifestCacheEntry>();\n\nfunction cacheKey(pubkey: string, dTag: string): string {\n return `${pubkey}:${dTag}`;\n}\n\n/**\n * Cache for verified napplet manifest entries. Persists to localStorage.\n * Used to detect napplet updates (aggregateHash changes) across sessions.\n *\n * @example\n * ```ts\n * import { manifestCache } from '@kehto/shell';\n *\n * manifestCache.set({ pubkey: 'abc...', dTag: 'chat', aggregateHash: 'dead', verifiedAt: Date.now() });\n * const entry = manifestCache.get('abc...', 'chat');\n * ```\n */\nexport const manifestCache = {\n /**\n * Get a cached manifest entry by pubkey and dTag.\n *\n * @param pubkey - The napp's pubkey\n * @param dTag - The napp's dTag\n * @returns The cached entry, or undefined if not found\n */\n get(pubkey: string, dTag: string): ManifestCacheEntry | undefined {\n return cache.get(cacheKey(pubkey, dTag));\n },\n\n /**\n * Set (upsert) a manifest cache entry and persist to localStorage.\n *\n * @param entry - The manifest entry to cache\n */\n set(entry: ManifestCacheEntry): void {\n cache.set(cacheKey(entry.pubkey, entry.dTag), entry);\n manifestCache.persist();\n },\n\n /**\n * Check if a specific hash is cached for a pubkey/dTag combination.\n *\n * @param pubkey - The napp's pubkey\n * @param dTag - The napp's dTag\n * @param hash - The aggregateHash to check\n * @returns True if the exact hash matches the cached entry\n */\n has(pubkey: string, dTag: string, hash: string): boolean {\n const entry = cache.get(cacheKey(pubkey, dTag));\n return !!entry && entry.aggregateHash === hash;\n },\n\n /**\n * Remove a cached entry for a pubkey/dTag and persist.\n *\n * @param pubkey - The napp's pubkey\n * @param dTag - The napp's dTag\n */\n remove(pubkey: string, dTag: string): void {\n cache.delete(cacheKey(pubkey, dTag));\n manifestCache.persist();\n },\n\n /** Load the cache from localStorage. */\n load(): void {\n try {\n const raw = localStorage.getItem(STORAGE_KEY);\n if (!raw) return;\n const entries = JSON.parse(raw) as Array<[string, ManifestCacheEntry]>;\n cache.clear();\n for (const [key, val] of entries) cache.set(key, val);\n } catch { /* Corrupted cache data — clear and start fresh */ cache.clear(); }\n },\n\n /** Persist the cache to localStorage. */\n persist(): void {\n try {\n localStorage.setItem(STORAGE_KEY, JSON.stringify(Array.from(cache.entries())));\n } catch { /* localStorage unavailable in sandboxed contexts — persist is best-effort */ }\n },\n\n /** Clear all cached entries and remove from localStorage. */\n clear(): void {\n cache.clear();\n try { localStorage.removeItem(STORAGE_KEY); } catch { /* localStorage unavailable — cleanup is best-effort */ }\n },\n};\n","/**\n * audio-manager.ts — Shell-side registry of active audio sources.\n *\n * Tracks which windows are producing audio. UI components read the registry\n * reactively via the version counter and CustomEvent pattern.\n */\n\nimport { originRegistry } from './origin-registry.js';\n\n/**\n * An active audio source registered by a napplet.\n * @example\n * ```ts\n * const source: AudioSource = {\n * windowId: 'win-1', nappletClass: 'music-player',\n * title: 'Now Playing', muted: false,\n * };\n * ```\n */\nexport interface AudioSource {\n windowId: string;\n nappletClass: string;\n title: string;\n muted: boolean;\n}\n\nconst sources = new Map<string, AudioSource>();\nlet version = 0;\n\nfunction bump(): void {\n version++;\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new CustomEvent('napplet:audio-changed'));\n }\n}\n\n/**\n * Registry of active audio sources across all napplet windows.\n * Emits 'napplet:audio-changed' CustomEvents when the registry changes.\n *\n * @example\n * ```ts\n * import { audioManager } from '@kehto/shell';\n *\n * audioManager.register('win-1', 'music', 'My Song');\n * audioManager.mute('win-1', true);\n * ```\n */\nexport const audioManager = {\n /**\n * Register a new audio source for a window.\n *\n * @param windowId - The window identifier\n * @param nappletClass - The napplet class/type (e.g., 'music-player')\n * @param title - Human-readable title for the audio source\n */\n register(windowId: string, nappletClass: string, title: string): void {\n sources.set(windowId, { windowId, nappletClass, title, muted: false });\n bump();\n },\n\n /**\n * Unregister an audio source for a window.\n *\n * @param windowId - The window identifier to remove\n */\n unregister(windowId: string): void {\n if (sources.delete(windowId)) bump();\n },\n\n /**\n * Update the state of an audio source (e.g., change title).\n *\n * @param windowId - The window identifier\n * @param update - Partial update with optional title\n */\n updateState(windowId: string, update: { title?: string }): void {\n const src = sources.get(windowId);\n if (!src) return;\n if (update.title !== undefined) src.title = update.title;\n bump();\n },\n\n /**\n * Mute or unmute an audio source and notify the napplet via postMessage.\n *\n * @param windowId - The window identifier\n * @param muted - True to mute, false to unmute\n * @example\n * ```ts\n * audioManager.mute('win-1', true); // mute\n * audioManager.mute('win-1', false); // unmute\n * ```\n */\n mute(windowId: string, muted: boolean): void {\n const src = sources.get(windowId);\n if (src) { src.muted = muted; bump(); }\n const iframeWindow = originRegistry.getIframeWindow(windowId);\n if (iframeWindow) {\n const muteEvent = {\n kind: 29000, // INC_PEER — inlined numeric after Phase 24 DRIFT-01 shim removal\n created_at: Math.floor(Date.now() / 1000),\n tags: [['t', 'napplet:audio-muted']],\n content: JSON.stringify({ muted }),\n pubkey: '__shell__',\n id: `audio-mute-${windowId}-${Date.now()}`,\n sig: '',\n };\n iframeWindow.postMessage(['EVENT', '__shell__', muteEvent], '*');\n }\n },\n\n /**\n * Check if a window has a registered audio source.\n *\n * @param windowId - The window identifier\n * @returns True if the window has an active audio source\n */\n has(windowId: string): boolean { return sources.has(windowId); },\n\n /**\n * Get the audio source for a window.\n *\n * @param windowId - The window identifier\n * @returns The AudioSource, or undefined if not found\n */\n get(windowId: string): AudioSource | undefined { return sources.get(windowId); },\n\n /**\n * Get a snapshot of all audio sources.\n *\n * @returns A new Map of all active audio sources\n */\n getSources(): Map<string, AudioSource> { return new Map(sources); },\n\n /** Current version counter (incremented on every change). */\n get version(): number { return version; },\n\n /** Number of active audio sources. */\n get count(): number { return sources.size; },\n\n /** Clear all audio sources and reset version counter. */\n clear(): void { sources.clear(); version = 0; },\n};\n","/**\n * keys-forwarder.ts — Shell-side host keydown listener that forwards events\n * to registered napplets as `keys.forward` envelopes (Plan 12-11, NAP-05\n * shell-side half).\n *\n * Per `@napplet/nap/keys`, `keys.forward` is fire-and-forget (no result\n * envelope, no correlation id). Field names follow the nap convention:\n * `{ ctrl, alt, shift, meta }` — NOT the DOM-style `ctrlKey`/etc.\n *\n * Capability gate: only forwards to napplets whose ACL grants the\n * `keys:forward` capability (per Plan 12-10 `resolveCapabilitiesNap` 'keys'\n * case). The caller wires the cap-lookup via the `hasKeysForwardCap` dep so\n * this module stays free of any direct ACL-store dependency.\n *\n * Lifecycle: `createShellBridge()` attaches a forwarder on construction and\n * detaches it inside `bridge.destroy()`.\n */\n\nimport type { NappletMessage } from '@napplet/core';\nimport type { SessionEntry } from './types.js';\n\n/**\n * Minimal origin-registry contract used by the forwarder — matches the\n * `@kehto/shell` singleton `originRegistry` and test doubles alike.\n */\nexport interface KeysForwarderOriginRegistry {\n /** Resolve a registered napplet windowId to its iframe Window, or null. */\n getIframeWindow(windowId: string): Window | null;\n}\n\n/**\n * Minimal session-registry contract used by the forwarder — matches the\n * `@kehto/shell` singleton `sessionRegistry` and test doubles alike.\n */\nexport interface KeysForwarderSessionRegistry {\n /** Return every registered napplet session entry. */\n getAllEntries(): SessionEntry[];\n}\n\n/**\n * Dependencies for `createKeysForwarder`.\n *\n * @example\n * ```ts\n * const forwarder = createKeysForwarder({\n * originRegistry,\n * sessionRegistry,\n * hasKeysForwardCap: (pubkey) =>\n * aclStore.getAclEntry(pubkey)?.capabilities.includes('keys:forward') ?? false,\n * });\n * ```\n */\nexport interface KeysForwarderDeps {\n /** Origin registry for resolving windowId → iframe Window. */\n originRegistry: KeysForwarderOriginRegistry;\n /** Session registry for enumerating napplets to forward to. */\n sessionRegistry: KeysForwarderSessionRegistry;\n /**\n * Capability check: returns true when the given napplet pubkey holds the\n * `keys:forward` capability. Called per keydown per registered napplet —\n * keep the implementation cheap.\n */\n hasKeysForwardCap(pubkey: string): boolean;\n /**\n * Optional EventTarget to attach to. Defaults to the global `window` when\n * running in a DOM environment. Passing a fresh `new EventTarget()` is\n * useful for unit tests.\n */\n target?: EventTarget;\n}\n\n/**\n * Handle returned by `createKeysForwarder`. Call `destroy()` to remove the\n * keydown listener (e.g. inside `bridge.destroy()`).\n */\nexport interface KeysForwarder {\n /** Detach the keydown listener and release resources. */\n destroy(): void;\n}\n\n/**\n * The `keys.forward` envelope shape emitted by this forwarder. Matches\n * `@napplet/nap/keys` `KeysForwardMessage`.\n */\ninterface KeysForwardEnvelope extends NappletMessage {\n type: 'keys.forward';\n key: string;\n code: string;\n ctrl: boolean;\n alt: boolean;\n shift: boolean;\n meta: boolean;\n}\n\n/**\n * Create a host-keydown forwarder that posts `keys.forward` envelopes to\n * every registered napplet granted the `keys:forward` capability.\n *\n * @param deps - Origin registry, session registry, cap checker, optional target\n * @returns A {@link KeysForwarder} — call `destroy()` to detach\n * @example\n * ```ts\n * // Inside createShellBridge():\n * const keysForwarder = createKeysForwarder({\n * originRegistry,\n * sessionRegistry,\n * hasKeysForwardCap: (pubkey) =>\n * aclStore.getAclEntry(pubkey)?.capabilities.includes('keys:forward') ?? false,\n * });\n * // ...\n * // Inside bridge.destroy():\n * keysForwarder.destroy();\n * ```\n */\nexport function createKeysForwarder(deps: KeysForwarderDeps): KeysForwarder {\n // Fallback to the global window when running in a DOM environment; when\n // neither a target nor a window is available (SSR / early Node tests),\n // create an isolated EventTarget so addEventListener never throws. The\n // resulting forwarder is effectively inert until the caller dispatches\n // keydowns on the chosen target.\n const target: EventTarget =\n deps.target ?? (typeof window !== 'undefined' ? window : new EventTarget());\n\n const listener = (ev: Event): void => {\n const ke = ev as Event & {\n key?: string; code?: string;\n ctrlKey?: boolean; altKey?: boolean;\n shiftKey?: boolean; metaKey?: boolean;\n };\n\n const entries = deps.sessionRegistry.getAllEntries();\n for (const entry of entries) {\n if (!deps.hasKeysForwardCap(entry.pubkey)) continue;\n const iframe = deps.originRegistry.getIframeWindow(entry.windowId);\n if (!iframe) continue;\n\n const envelope: KeysForwardEnvelope = {\n type: 'keys.forward',\n key: ke.key ?? '',\n code: ke.code ?? '',\n ctrl: ke.ctrlKey ?? false,\n alt: ke.altKey ?? false,\n shift: ke.shiftKey ?? false,\n meta: ke.metaKey ?? false,\n };\n iframe.postMessage(envelope, '*');\n }\n };\n\n target.addEventListener('keydown', listener);\n\n return {\n destroy(): void {\n target.removeEventListener('keydown', listener);\n },\n };\n}\n","\nimport type { ShellAdapter, ShellCapabilities } from './types.js';\n\n/**\n * NAP-vocabulary domain list (consumed by @napplet/shim >=0.9.0).\n *\n * Note: `relay` and `outbox` are NOT in this list — both are gated on\n * `hooks.relayPool` (NAP-OUTBOX routes over relays, so it is meaningless\n * without one) and prepended conditionally in buildShellCapabilities below.\n * `link` and `common` are also conditional because shells must wire those\n * service backends before advertising user-visible navigation/social actions.\n * `dm` is conditional because shells must register a runtime-owned DM backend.\n */\nconst NAP_DOMAINS = [\n 'identity', 'storage', 'inc', 'theme', 'keys', 'media', 'notify',\n 'config', 'resource', 'cvm',\n] as const;\n\n/**\n * NAP protocol IDs for the `naps` array: `inc:NAP-01..inc:NAP-06`.\n */\nconst NAP_INC_PROTOCOLS = [\n 'inc:NAP-01',\n 'inc:NAP-02',\n 'inc:NAP-03',\n 'inc:NAP-04',\n 'inc:NAP-05',\n 'inc:NAP-06',\n] as const;\n\n/**\n * Build the shell's static capability set from adapter configuration.\n *\n * Returns the compatibility NAP-SHELL `domains`/`protocols` shape alongside\n * the flat `naps` array and the `sandbox` array. Current availability is\n * injected `window.napplet.<domain>` presence; this payload remains for older\n * consumers that still inspect shell.init capabilities.\n *\n * ### naps array (NAP vocabulary)\n * Bare domain `inc` + `inc:NAP-01..inc:NAP-06`.\n * Conditional: `relay`+`outbox` prepended when hooks.relayPool;\n * `upload` appended when hooks.upload;\n * `intent` appended when hooks.intent.isAvailable();\n * `link` appended when hooks.link.isAvailable();\n * `common` appended when hooks.common.isAvailable().\n *\n * Sandbox permissions are left empty by default — host apps may extend after\n * construction. Sandbox entries (and any host-app extensions) MUST use the\n * canonical `perm:<permission>` form — e.g. `'perm:popups'`, `'perm:modals'`,\n * `'perm:downloads'`. Napplets rely on the `perm:` prefix to distinguish\n * sandbox permissions from NAP-capability lookups; see the living NIP-5D at\n * https://github.com/nostr-protocol/nips/pull/2303/\n *\n * ### domains array + protocols map (compatibility NAP-SHELL)\n * The structured shape older shell capability consumers read:\n *\n * - `domains` — bare NAP domain names (the `naps` set MINUS the `inc:NAP-NN`\n * protocol strings) with the same conditional entries (relay/outbox under\n * `hooks.relayPool`, upload/intent/link/common under their hooks). Any `perm:<x>`\n * sandbox entries are appended here too — legacy supports helpers resolve\n * `supports('perm:<x>')` as a plain `domains` membership check.\n * - `protocols` — `{ inc: ['NAP-01'..'NAP-06'] }`, derived from\n * `NAP_INC_PROTOCOLS` by stripping the `inc:` prefix.\n *\n * Emitted as a superset alongside `naps`/`sandbox` for back-compat.\n *\n * @param hooks - The ShellAdapter provided by the host app\n * @returns ShellCapabilities with domains/protocols (compatibility shape) plus\n * naps (NAP vocab) and sandbox (perm:-prefixed) arrays\n * @example\n * ```ts\n * const caps = buildShellCapabilities(hooks);\n * // caps.domains => ['relay','outbox','identity','storage','inc','theme','keys','media','notify','config','resource','cvm']\n * // (relay + outbox present when hooks.relayPool is provided; 'upload'/'intent'\n * // appended under their hooks; perm:<x> sandbox entries appended when extended)\n * // caps.protocols => { inc: ['NAP-01','NAP-02','NAP-03','NAP-04','NAP-05','NAP-06'] }\n * // caps.naps => ['relay','outbox','identity','storage','inc','theme','keys','media','notify','config','resource','cvm',\n * // 'inc:NAP-01','inc:NAP-02','inc:NAP-03','inc:NAP-04','inc:NAP-05','inc:NAP-06']\n * // (relay + outbox present when hooks.relayPool is provided; 'upload'\n * // appended when hooks.upload is provided; 'intent' appended when\n * // hooks.intent.isAvailable() is true)\n * // caps.sandbox => [] // host app may extend with 'perm:popups', etc.\n * ```\n */\nexport function buildShellCapabilities(hooks: ShellAdapter): ShellCapabilities {\n // domains — compatibility NAP-SHELL bare domain list.\n // Same membership/order as `naps` but WITHOUT the inc:NAP-NN protocol strings\n // (those move to `protocols`). Conditional entries use the same gates as naps.\n const domains: string[] = hooks.relayPool\n ? ['relay', 'outbox', ...NAP_DOMAINS]\n : [...NAP_DOMAINS];\n if (hooks.upload) domains.push('upload');\n if (hooks.intent?.isAvailable()) domains.push('intent');\n if (hooks.link?.isAvailable()) domains.push('link');\n if (hooks.common?.isAvailable()) domains.push('common');\n if (hooks.lists?.isAvailable()) domains.push('lists');\n if (hooks.serial?.isAvailable()) domains.push('serial');\n if (hooks.ble?.isAvailable()) domains.push('ble');\n if (hooks.webrtc?.isAvailable()) domains.push('webrtc');\n if (hooks.dm) domains.push('dm');\n // Sandbox permissions are perm:<x>-prefixed and resolved by the 0.13 shim as\n // plain domains membership (no separate permission namespace). Empty by\n // default — fold any host-extended sandbox entries in alongside the domains.\n const sandbox: string[] = [];\n domains.push(...sandbox);\n\n // protocols — conformant NAP-SHELL per-domain numbered protocol map.\n // Derive { inc: ['NAP-01'..'NAP-06'] } by stripping the `inc:` prefix from\n // NAP_INC_PROTOCOLS so domains/protocols stay the single source of truth.\n const protocols: Record<string, string[]> = {};\n for (const entry of NAP_INC_PROTOCOLS) {\n const [domain, protocol] = entry.split(':') as [string, string];\n (protocols[domain] ??= []).push(protocol);\n }\n\n // naps — NAP vocabulary (consumed by @napplet/shim >=0.9.0)\n const naps: string[] = hooks.relayPool\n ? ['relay', 'outbox', ...NAP_DOMAINS, ...NAP_INC_PROTOCOLS]\n : [...NAP_DOMAINS, ...NAP_INC_PROTOCOLS];\n // NAP-UPLOAD: advertised only when the host wires an upload backend.\n if (hooks.upload) naps.push('upload');\n // NAP-INTENT: advertised only when the host wires an available intent dispatcher.\n if (hooks.intent?.isAvailable()) naps.push('intent');\n // NAP-LINK: advertised only when the host wires shell-mediated link opening.\n if (hooks.link?.isAvailable()) naps.push('link');\n // NAP-COMMON: advertised only when the host wires common social actions.\n if (hooks.common?.isAvailable()) naps.push('common');\n // NAP-LISTS: advertised only when the host wires list mutation helpers.\n if (hooks.lists?.isAvailable()) naps.push('lists');\n // NAP-SERIAL: advertised only when the host wires runtime-owned serial sessions.\n if (hooks.serial?.isAvailable()) naps.push('serial');\n // NAP-BLE: advertised only when the host wires runtime-owned BLE sessions.\n if (hooks.ble?.isAvailable()) naps.push('ble');\n // NAP-WEBRTC: advertised only when the host wires runtime-owned WebRTC sessions.\n if (hooks.webrtc?.isAvailable()) naps.push('webrtc');\n // NAP-DM: advertised only when the host wires a runtime-owned DM backend.\n if (hooks.dm) naps.push('dm');\n\n return applyCapabilityOverrides(\n { domains, protocols, naps, sandbox },\n hooks.capabilities?.disabledDomains ?? [],\n );\n}\n\nfunction applyCapabilityOverrides(\n capabilities: ShellCapabilities,\n disabledDomains: readonly string[],\n): ShellCapabilities {\n if (disabledDomains.length === 0) return capabilities;\n\n const disabled = new Set(disabledDomains);\n const protocols: Record<string, string[]> = {};\n for (const [domain, supportedProtocols] of Object.entries(capabilities.protocols)) {\n if (!disabled.has(domain)) protocols[domain] = supportedProtocols;\n }\n\n return {\n domains: capabilities.domains.filter((entry) => !disabled.has(entry)),\n protocols,\n naps: capabilities.naps.filter((entry) => !disabled.has(entry.split(':')[0] ?? entry)),\n sandbox: capabilities.sandbox,\n };\n}\n","import type { Runtime, SessionEntry } from '@kehto/runtime';\nimport type { NappletMessage } from '@napplet/core';\nimport { originRegistry } from './origin-registry.js';\nimport { buildShellCapabilities } from './shell-init.js';\nimport type { ShellAdapter, ShellCapabilities } from './types.js';\n\ninterface ShellReadyOptions {\n hooks: ShellAdapter;\n origin: string;\n runtime: Runtime;\n sourceRegistrationId: number;\n sourceWindow: Window;\n windowId: string;\n}\n\n/**\n * SHELL-01 (NAP-SHELL gap G1): tracks the source Window registration for which\n * `shell.init` has already been posted, so a duplicate `shell.ready` in the same\n * iframe lifecycle is idempotent and does NOT resend `shell.init`.\n * Module-scoped because the \"no NIP-5D identity\" path never registers a session\n * entry, so the guard must live independently of the session registry (and must\n * not mutate the runtime `SessionEntry` shape, which is owned by @kehto/runtime).\n */\nlet initSent = new WeakMap<Window, number>();\n\n/**\n * Test-only hook to clear the module-scoped {@link initSent} guard between\n * test cases. NOT part of the public API; prefixed with `__` and `ForTests` to\n * signal it must never be called by production code.\n *\n * @internal\n */\nexport function __resetInitSentForTests(): void {\n initSent = new WeakMap<Window, number>();\n}\n\nexport function handleShellReady({\n hooks,\n origin,\n runtime,\n sourceRegistrationId,\n sourceWindow,\n windowId,\n}: ShellReadyOptions): void {\n registerNip5dSessionIfNeeded({\n hooks,\n origin,\n runtime,\n sourceRegistrationId,\n sourceWindow,\n windowId,\n });\n\n // SHELL-01: exactly-once shell.init per registered Window lifecycle.\n // registerNip5dSessionIfNeeded is already idempotent (its own\n // getEntryByWindowId early-return); this guard governs ONLY postShellInit so a\n // duplicate shell.ready does not resend shell.init, while an iframe reload\n // under the same WindowProxy/windowId can boot after re-registration.\n if (initSent.get(sourceWindow) === sourceRegistrationId) {\n return;\n }\n\n const capabilities = buildShellCapabilities(hooks);\n postShellInit(sourceWindow, capabilities, Object.keys(hooks.services ?? {}));\n initSent.set(sourceWindow, sourceRegistrationId);\n}\n\nfunction registerNip5dSessionIfNeeded({\n hooks,\n origin,\n runtime,\n windowId,\n}: ShellReadyOptions): void {\n // NIP-5D: register a source-identity session entry in runtime.sessionRegistry\n // if one does not already exist for this windowId. This wires the originRegistry\n // identity into the runtime so domain handlers (storage/state, inc, etc.) can\n // resolve the napplet via getEntryByWindowId(windowId).\n if (runtime.sessionRegistry.getEntryByWindowId(windowId)) {\n return;\n }\n\n const identity = resolveNip5dIdentity(hooks, windowId);\n if (!identity) {\n return;\n }\n\n const entry: SessionEntry = {\n pubkey: '',\n windowId,\n origin,\n type: 'nip5d',\n dTag: identity.dTag,\n aggregateHash: identity.aggregateHash,\n registeredAt: Date.now(),\n instanceId: crypto.randomUUID(),\n provenance: 'nip-5d',\n };\n runtime.sessionRegistry.register(windowId, entry);\n}\n\nfunction resolveNip5dIdentity(\n hooks: ShellAdapter,\n windowId: string,\n): { dTag: string; aggregateHash: string } | null {\n // Identity resolution order:\n // 1. hooks.onNip5dIframeCreate?.(windowId) — preferred.\n // 2. originRegistry.getIdentity(win) — fallback for hosts that register\n // identity directly via originRegistry.register(win, windowId, identity).\n const hookIdentity = hooks.onNip5dIframeCreate?.(windowId);\n if (hookIdentity !== null && hookIdentity !== undefined) {\n return {\n dTag: hookIdentity.dTag,\n aggregateHash: hookIdentity.aggregateHash,\n };\n }\n\n const win = originRegistry.getIframeWindow(windowId);\n if (!win) {\n return null;\n }\n\n const originIdentity = originRegistry.getIdentity(win);\n if (!originIdentity) {\n return null;\n }\n\n return {\n dTag: originIdentity.dTag,\n aggregateHash: originIdentity.aggregateHash,\n };\n}\n\nfunction postShellInit(\n win: Window,\n capabilities: ShellCapabilities,\n services: string[],\n): void {\n const initMsg: NappletMessage & {\n capabilities: ShellCapabilities;\n services: string[];\n } = {\n type: 'shell.init',\n capabilities,\n services,\n };\n win.postMessage(initMsg, '*');\n}\n","/**\n * Options for rendering the host-owned NIP-5D `window.napplet` namespace prelude.\n *\n * @example\n * ```ts\n * const options: NappletNamespacePreludeOptions = {\n * domains: ['shell', 'relay', 'identity'],\n * };\n * ```\n */\nexport interface NappletNamespacePreludeOptions {\n /**\n * Bare NAP domain names the shell exposes to this napplet.\n */\n domains: readonly string[];\n}\n\nfunction uniqueBareDomains(domains: readonly string[]): string[] {\n const seen = new Set<string>();\n const normalized: string[] = [];\n for (const domain of domains) {\n const value = domain.trim();\n if (!value || value.includes(':') || value.startsWith('perm:') || seen.has(value)) {\n continue;\n }\n seen.add(value);\n normalized.push(value);\n }\n return normalized;\n}\n\nfunction scriptJson(value: unknown): string {\n return JSON.stringify(value).replace(/</g, '\\\\u003c');\n}\n\n/**\n * Render the host-owned NIP-5D bootstrap that exposes available NAP domains\n * under `window.napplet` before napplet artifact code runs.\n *\n * @param options - Domain availability to inject.\n * @returns An inline script tag suitable for `srcdoc` prelude insertion.\n * @example\n * ```ts\n * const prelude = renderNappletNamespacePrelude({\n * domains: ['shell', 'relay', 'identity'],\n * });\n * ```\n */\nexport function renderNappletNamespacePrelude(options: NappletNamespacePreludeOptions): string {\n const domains = uniqueBareDomains(options.domains);\n return `<script data-kehto-nip5d-injection>(${nappletNamespacePrelude.toString()})(${scriptJson(domains)});</script>`;\n}\n\n/**\n * Insert the NIP-5D namespace prelude into HTML before authored scripts.\n *\n * @param html - Verified napplet artifact HTML.\n * @param options - Domain availability to inject.\n * @returns HTML with the prelude inside `<head>` when possible.\n * @example\n * ```ts\n * const srcdoc = injectNappletNamespacePrelude(verifiedHtml, {\n * domains: ['shell', 'relay', 'identity'],\n * });\n * iframe.srcdoc = srcdoc;\n * ```\n */\nexport function injectNappletNamespacePrelude(\n html: string,\n options: NappletNamespacePreludeOptions,\n): string {\n const prelude = renderNappletNamespacePrelude(options);\n const cspMeta = /(<meta\\s+http-equiv=[\"']Content-Security-Policy[\"'][^>]*>)/i;\n if (cspMeta.test(html)) {\n return html.replace(cspMeta, `$1${prelude}`);\n }\n if (/<head[^>]*>/i.test(html)) {\n return html.replace(/<head[^>]*>/i, (open) => `${open}${prelude}`);\n }\n if (/<html[^>]*>/i.test(html)) {\n return html.replace(/<html[^>]*>/i, (open) => `${open}<head>${prelude}</head>`);\n }\n return `${prelude}${html}`;\n}\n\nfunction nappletNamespacePrelude(domains: string[]): void {\n const target = window as Window & { napplet?: Record<string, unknown> };\n\n function buildNappletNamespace(value: unknown): Record<string, unknown> {\n const candidate = typeof value === 'object' && value !== null\n ? value as Record<string, unknown>\n : {};\n const next: Record<string, unknown> = {};\n for (const domain of domains) {\n Object.defineProperty(next, domain, {\n value: Object.prototype.hasOwnProperty.call(candidate, domain) ? candidate[domain] : {},\n enumerable: true,\n configurable: true,\n writable: true,\n });\n }\n return next;\n }\n\n let root = buildNappletNamespace(target.napplet);\n Object.defineProperty(target, 'napplet', {\n get() {\n return root;\n },\n set(value) {\n root = buildNappletNamespace(value);\n },\n enumerable: false,\n configurable: true,\n });\n}\n","\n// Factory function — main entry point\nexport { createShellBridge } from './shell-bridge.js';\nexport type { ShellBridge } from './shell-bridge.js';\nexport {\n injectNappletNamespacePrelude,\n renderNappletNamespacePrelude,\n} from './napplet-namespace.js';\nexport type { NappletNamespacePreludeOptions } from './napplet-namespace.js';\n\n// Hooks adapter — for advanced integrators who need to customize the adapter\nexport { adaptHooks } from './hooks-adapter.js';\nexport type { BrowserDeps } from './hooks-adapter.js';\n\n// Protocol types (re-exported from @napplet/core + @kehto/runtime).\n// Phase 24 DRIFT-01: former @napplet/core compatibility shim deleted; legacy\n// NIP-01 constants no longer re-exported. Shell consumers import Capability +\n// ALL_CAPABILITIES from @kehto/runtime (sourced from @kehto/acl/capabilities).\nexport type { NostrEvent, NostrFilter, NappletMessage } from '@napplet/core';\nexport type { Capability } from '@kehto/runtime';\nexport { ALL_CAPABILITIES } from '@kehto/runtime';\n\n// Types for host app integration (shell-specific)\nexport type {\n ShellAdapter,\n ShellCapabilities,\n RelayPoolHooks,\n RelayPoolLike,\n RelayConfigHooks,\n WindowManagerHooks,\n AuthHooks,\n ConfigHooks,\n HotkeyHooks,\n WorkerRelayHooks,\n WorkerRelayLike,\n CryptoHooks,\n DmHooks,\n UploadHooks,\n UploadBackendLike,\n IntentHooks,\n LinkHooks,\n CommonHooks,\n ListsHooks,\n SerialHooks,\n BleHooks,\n WebrtcHooks,\n CapabilityHooks,\n SessionEntry,\n NappKeyEntry, // @deprecated — use SessionEntry\n AclEntry,\n AclCheckEvent,\n UnroutedMessageInfo,\n ServiceDescriptor,\n ServiceHandler,\n ServiceRegistry,\n} from './types.js';\n\n// Shell initialization — capability construction for shell.ready / shell.init handshake\nexport { buildShellCapabilities } from './shell-init.js';\n\nexport { sessionRegistry, nappKeyRegistry } from './session-registry.js';\nexport type { PendingUpdate } from './session-registry.js';\n\n// Standalone utilities (usable without full shell)\nexport { originRegistry } from './origin-registry.js';\nexport { audioManager } from './audio-manager.js';\nexport type { AudioSource } from './audio-manager.js';\nexport { manifestCache } from './manifest-cache.js';\nexport type { ManifestCacheEntry } from './manifest-cache.js';\n\n// Enforcement gate (re-exported from @kehto/runtime for backwards compatibility)\nexport { createEnforceGate, createNapEnforceGate, formatDenialReason } from '@kehto/runtime';\nexport type { EnforceResult, EnforceConfig, NapEnforceConfig, IdentityResolver, AclChecker, NapMessage } from '@kehto/runtime';\n// ConsentRequest canonical definition re-exported from @kehto/runtime\nexport type { ConsentRequest } from '@kehto/runtime';\n\n// Topic constants for shell command routing\nexport { TOPICS } from './topics.js';\nexport type { TopicKey, TopicValue } from './topics.js';\n\nexport { createIdentityProxy } from './identity-proxy.js';\nexport type { IdentityProxy, IdentityProxyDeps, ProxyOriginRegistry } from './identity-proxy.js';\nexport { createThemeProxy } from './theme-proxy.js';\nexport type { ThemeProxy, ThemeProxyDeps } from './theme-proxy.js';\nexport { createKeysProxy } from './keys-proxy.js';\nexport type { KeysProxy, KeysProxyDeps } from './keys-proxy.js';\nexport { createMediaProxy } from './media-proxy.js';\nexport type { MediaProxy, MediaProxyDeps } from './media-proxy.js';\nexport { createNotifyProxy } from './notify-proxy.js';\nexport type { NotifyProxy, NotifyProxyDeps } from './notify-proxy.js';\n\nexport { createKeysForwarder } from './keys-forwarder.js';\nexport type {\n KeysForwarder,\n KeysForwarderDeps,\n KeysForwarderOriginRegistry,\n KeysForwarderSessionRegistry,\n} from './keys-forwarder.js';\n\nexport type {\n ResourceBytesRequest,\n ResourceInfo,\n ResourceInfoError,\n ResourceInfoRequest,\n ResourceInfoResult,\n ResourceBytesManyRequest,\n ResourceBytesManyItem,\n ResourceCancelRequest,\n ResourceBytesResult,\n ResourceBytesManyResult,\n ResourceBytesError,\n ResourceBytesManyError,\n ResourceErrorCode,\n ResourceRequestId,\n ResourceInbound,\n ResourceOutbound,\n} from './types/internal-resource.js';\n","/**\n * topics.ts — Re-exports topic constants from @napplet/core.\n *\n * Shell previously owned these constants. They now live in @napplet/core\n * so all packages share a single source of truth.\n */\nexport { TOPICS } from '@napplet/core';\nexport type { TopicKey, TopicValue } from '@napplet/core';\n","/**\n * identity-proxy.ts — Shell-side per-domain proxy for identity.* envelopes.\n *\n * Establishes the canonical proxy shape for @kehto/shell (Plan 12-11): each\n * per-domain proxy exposes a `dispatch` method that delegates napplet→shell\n * requests to the runtime and an `emit` method that posts shell→napplet\n * push envelopes through the origin registry.\n *\n * By default, `createShellBridge()` does NOT compose this proxy into its\n * dispatch path — the runtime already owns identity.* dispatch per Plan\n * 12-03 (see @kehto/services identity-service). This module exists as an\n * optional composition point for host apps that want to intercept or\n * augment identity dispatch (e.g. custom logging, sandboxed rewrites, test\n * doubles).\n *\n * The canonical proxy shape — dispatch + emit — is mirrored verbatim by\n * theme-proxy, keys-proxy, media-proxy, and notify-proxy. Storage today is\n * served by `@kehto/runtime` state-handler directly; a storage-proxy using\n * this shape can be added later if host apps need a composition seam.\n */\n\nimport type { Runtime } from '@kehto/runtime';\nimport type { NappletMessage } from '@napplet/core';\n\n/**\n * Minimal origin-registry contract used by per-domain proxies.\n *\n * Accepts the `@kehto/shell` singleton `originRegistry` as well as any test\n * double with a matching `getIframeWindow` method.\n */\nexport interface ProxyOriginRegistry {\n /** Resolve a registered napplet windowId to its iframe Window, or null. */\n getIframeWindow(windowId: string): Window | null;\n}\n\n/**\n * Dependencies for `createIdentityProxy`.\n *\n * @example\n * ```ts\n * const proxy = createIdentityProxy({\n * runtime: shellBridge.runtime,\n * originRegistry,\n * });\n * ```\n */\nexport interface IdentityProxyDeps {\n /** The runtime engine that owns identity.* dispatch (Plan 12-03). */\n runtime: Runtime;\n /** Origin registry for resolving windowId → iframe Window. */\n originRegistry: ProxyOriginRegistry;\n}\n\n/**\n * Per-domain proxy for `identity.*` envelopes.\n *\n * The canonical proxy shape: `dispatch` routes napplet→shell requests into\n * the runtime; `emit` pushes shell→napplet envelopes through the iframe's\n * Window.\n */\nexport interface IdentityProxy {\n /**\n * Route a napplet-originated identity.* envelope into the runtime.\n *\n * Delegation only — the runtime already owns identity.* dispatch after\n * Plan 12-03. Override by wrapping or replacing this method.\n *\n * @param windowId - The source napplet's windowId\n * @param envelope - The NIP-5D NappletMessage envelope\n */\n dispatch(windowId: string, envelope: NappletMessage): void;\n /**\n * Push a shell-initiated identity-domain envelope into a napplet iframe.\n *\n * No-op when the originRegistry cannot resolve the windowId (unknown or\n * unregistered napplet). Never throws.\n *\n * @param windowId - The target napplet's windowId\n * @param envelope - The NIP-5D NappletMessage envelope to deliver\n */\n emit(windowId: string, envelope: NappletMessage): void;\n}\n\n/**\n * Factory for the canonical identity-domain proxy.\n *\n * @param deps - Runtime + origin registry\n * @returns An {@link IdentityProxy} ready to route identity.* envelopes\n * @example\n * ```ts\n * import { createIdentityProxy, originRegistry, createShellBridge } from '@kehto/shell';\n *\n * const bridge = createShellBridge(hooks);\n * const identityProxy = createIdentityProxy({\n * runtime: bridge.runtime,\n * originRegistry,\n * });\n *\n * // Optional composition: intercept napplet->shell identity requests\n * const originalDispatch = identityProxy.dispatch;\n * identityProxy.dispatch = (windowId, envelope) => {\n * console.log('identity dispatch', windowId, envelope.type);\n * originalDispatch(windowId, envelope);\n * };\n * ```\n */\nexport function createIdentityProxy(deps: IdentityProxyDeps): IdentityProxy {\n return {\n dispatch(windowId: string, envelope: NappletMessage): void {\n deps.runtime.handleMessage(windowId, envelope);\n },\n emit(windowId: string, envelope: NappletMessage): void {\n const win = deps.originRegistry.getIframeWindow(windowId);\n if (win) win.postMessage(envelope, '*');\n },\n };\n}\n","/**\n * theme-proxy.ts — Shell-side per-domain proxy for theme.* envelopes.\n *\n * Establishes the shell-side shape that Phase 13 composes into. Phase 13 is\n * expected to add `theme-service.ts` (runtime) + the shell-side `theme.set`\n * API that emits `theme.changed` push envelopes to registered napplets;\n * this proxy is the canonical seam those pieces plug into.\n *\n * Shape mirrors identity-proxy (Plan 12-11):\n *\n * - `dispatch(windowId, envelope)` routes napplet→shell `theme.get` into\n * the runtime (where Phase 13's theme-service will answer).\n * - `emit(windowId, envelope)` posts shell→napplet `theme.changed`\n * envelopes through the origin registry.\n *\n * By default `createShellBridge()` does NOT compose this proxy into its\n * dispatch path — the runtime owns theme.* dispatch. This module is an\n * optional composition point for host apps or Phase 13 wiring.\n */\n\nimport type { Runtime } from '@kehto/runtime';\nimport type { NappletMessage } from '@napplet/core';\nimport type { ProxyOriginRegistry } from './identity-proxy.js';\n\n/**\n * Dependencies for `createThemeProxy`.\n *\n * @example\n * ```ts\n * const proxy = createThemeProxy({\n * runtime: shellBridge.runtime,\n * originRegistry,\n * });\n * ```\n */\nexport interface ThemeProxyDeps {\n /** The runtime engine that will own theme.* dispatch (Phase 13). */\n runtime: Runtime;\n /** Origin registry for resolving windowId → iframe Window. */\n originRegistry: ProxyOriginRegistry;\n}\n\n/**\n * Per-domain proxy for `theme.*` envelopes.\n *\n * Shape: `dispatch` routes napplet→shell requests into the runtime; `emit`\n * pushes shell→napplet envelopes through the iframe's Window.\n */\nexport interface ThemeProxy {\n /**\n * Route a napplet-originated theme.* envelope (e.g. `theme.get`) into\n * the runtime.\n *\n * @param windowId - The source napplet's windowId\n * @param envelope - The NIP-5D NappletMessage envelope\n */\n dispatch(windowId: string, envelope: NappletMessage): void;\n /**\n * Push a shell-initiated theme-domain envelope (e.g. `theme.changed`)\n * into a napplet iframe.\n *\n * No-op when the originRegistry cannot resolve the windowId (unknown or\n * unregistered napplet). Never throws.\n *\n * @param windowId - The target napplet's windowId\n * @param envelope - The NIP-5D NappletMessage envelope to deliver\n */\n emit(windowId: string, envelope: NappletMessage): void;\n}\n\n/**\n * Factory for the canonical theme-domain proxy.\n *\n * @param deps - Runtime + origin registry\n * @returns A {@link ThemeProxy} ready to route theme.* envelopes\n * @example\n * ```ts\n * import { createThemeProxy, originRegistry, createShellBridge } from '@kehto/shell';\n *\n * const bridge = createShellBridge(hooks);\n * const themeProxy = createThemeProxy({\n * runtime: bridge.runtime,\n * originRegistry,\n * });\n *\n * // Phase 13: broadcast theme.changed to every registered napplet\n * for (const entry of bridge.runtime.sessionRegistry.getAllEntries()) {\n * themeProxy.emit(entry.windowId, { type: 'theme.changed', theme: newTheme });\n * }\n * ```\n */\nexport function createThemeProxy(deps: ThemeProxyDeps): ThemeProxy {\n return {\n dispatch(windowId: string, envelope: NappletMessage): void {\n deps.runtime.handleMessage(windowId, envelope);\n },\n emit(windowId: string, envelope: NappletMessage): void {\n const win = deps.originRegistry.getIframeWindow(windowId);\n if (win) win.postMessage(envelope, '*');\n },\n };\n}\n","/**\n * keys-proxy.ts — Shell-side per-domain proxy for keys.* envelopes.\n *\n * Per Plan 12-05, the runtime already dispatches `keys.*` (forward,\n * registerAction, unregisterAction) to the keys-service. This proxy is the\n * shell-side composition point for host apps that want to observe or\n * inject keys envelopes — it pairs with `keys-forwarder.ts` (Plan 12-11)\n * which covers the shell→napplet `keys.forward` push path.\n *\n * Shape mirrors identity-proxy (Plan 12-11):\n *\n * - `dispatch(windowId, envelope)` routes napplet→shell keys requests\n * into the runtime.\n * - `emit(windowId, envelope)` posts shell→napplet pushes (`keys.action`,\n * `keys.bindings`, `keys.registerAction.result`) through the origin\n * registry.\n *\n * By default `createShellBridge()` does NOT compose this proxy into its\n * dispatch path — the runtime owns keys.* dispatch. This module is an\n * optional composition point for host apps (e.g. global hotkey UIs).\n */\n\nimport type { Runtime } from '@kehto/runtime';\nimport type { NappletMessage } from '@napplet/core';\nimport type { ProxyOriginRegistry } from './identity-proxy.js';\n\n/**\n * Dependencies for `createKeysProxy`.\n *\n * @example\n * ```ts\n * const proxy = createKeysProxy({\n * runtime: shellBridge.runtime,\n * originRegistry,\n * });\n * ```\n */\nexport interface KeysProxyDeps {\n /** The runtime engine that owns keys.* dispatch (Plan 12-05). */\n runtime: Runtime;\n /** Origin registry for resolving windowId → iframe Window. */\n originRegistry: ProxyOriginRegistry;\n}\n\n/**\n * Per-domain proxy for `keys.*` envelopes.\n *\n * Shape: `dispatch` routes napplet→shell requests into the runtime; `emit`\n * pushes shell→napplet envelopes through the iframe's Window.\n */\nexport interface KeysProxy {\n /**\n * Route a napplet-originated keys.* envelope (e.g. `keys.forward`,\n * `keys.registerAction`) into the runtime.\n *\n * @param windowId - The source napplet's windowId\n * @param envelope - The NIP-5D NappletMessage envelope\n */\n dispatch(windowId: string, envelope: NappletMessage): void;\n /**\n * Push a shell-initiated keys-domain envelope (e.g. `keys.action`,\n * `keys.bindings`) into a napplet iframe.\n *\n * Paired with `keys-forwarder.ts`: the forwarder targets the DOM\n * `keydown` → `keys.forward` path; this `emit` covers the complementary\n * host-initiated pushes (binding updates, action triggers).\n *\n * No-op when the originRegistry cannot resolve the windowId (unknown or\n * unregistered napplet). Never throws.\n *\n * @param windowId - The target napplet's windowId\n * @param envelope - The NIP-5D NappletMessage envelope to deliver\n */\n emit(windowId: string, envelope: NappletMessage): void;\n}\n\n/**\n * Factory for the canonical keys-domain proxy.\n *\n * @param deps - Runtime + origin registry\n * @returns A {@link KeysProxy} ready to route keys.* envelopes\n * @example\n * ```ts\n * import { createKeysProxy, originRegistry, createShellBridge } from '@kehto/shell';\n *\n * const bridge = createShellBridge(hooks);\n * const keysProxy = createKeysProxy({\n * runtime: bridge.runtime,\n * originRegistry,\n * });\n *\n * // Host-app-initiated action trigger:\n * keysProxy.emit('win-editor', { type: 'keys.action', actionId: 'editor.save' });\n * ```\n */\nexport function createKeysProxy(deps: KeysProxyDeps): KeysProxy {\n return {\n dispatch(windowId: string, envelope: NappletMessage): void {\n deps.runtime.handleMessage(windowId, envelope);\n },\n emit(windowId: string, envelope: NappletMessage): void {\n const win = deps.originRegistry.getIframeWindow(windowId);\n if (win) win.postMessage(envelope, '*');\n },\n };\n}\n","/**\n * media-proxy.ts — Shell-side per-domain proxy for media.* envelopes.\n *\n * Establishes the shell-side composition seam for `@napplet/nap/media`\n * session-control envelopes. Shape mirrors identity-proxy (Plan 12-11):\n *\n * - `dispatch(windowId, envelope)` routes napplet→shell media requests\n * (`media.session.create`, `media.session.update`, `media.session.destroy`,\n * `media.state`, `media.capabilities`) into the runtime (Plan 12-06).\n * - `emit(windowId, envelope)` posts shell→napplet pushes (`media.command`,\n * `media.controls`, `media.session.create.result`) through the origin\n * registry.\n *\n * By default `createShellBridge()` does NOT compose this proxy into its\n * dispatch path — the runtime owns media.* dispatch. This module is an\n * optional composition point for host apps (e.g. shell-rendered playback\n * UIs that want to send `media.command` pushes).\n */\n\nimport type { Runtime } from '@kehto/runtime';\nimport type { NappletMessage } from '@napplet/core';\nimport type { ProxyOriginRegistry } from './identity-proxy.js';\n\n/**\n * Dependencies for `createMediaProxy`.\n *\n * @example\n * ```ts\n * const proxy = createMediaProxy({\n * runtime: shellBridge.runtime,\n * originRegistry,\n * });\n * ```\n */\nexport interface MediaProxyDeps {\n /** The runtime engine that owns media.* dispatch (Plan 12-06). */\n runtime: Runtime;\n /** Origin registry for resolving windowId → iframe Window. */\n originRegistry: ProxyOriginRegistry;\n}\n\n/**\n * Per-domain proxy for `media.*` envelopes.\n *\n * Shape: `dispatch` routes napplet→shell requests into the runtime; `emit`\n * pushes shell→napplet envelopes through the iframe's Window.\n */\nexport interface MediaProxy {\n /**\n * Route a napplet-originated media.* envelope into the runtime.\n *\n * @param windowId - The source napplet's windowId\n * @param envelope - The NIP-5D NappletMessage envelope\n */\n dispatch(windowId: string, envelope: NappletMessage): void;\n /**\n * Push a shell-initiated media-domain envelope (e.g. `media.command`,\n * `media.controls`) into a napplet iframe.\n *\n * No-op when the originRegistry cannot resolve the windowId (unknown or\n * unregistered napplet). Never throws.\n *\n * @param windowId - The target napplet's windowId\n * @param envelope - The NIP-5D NappletMessage envelope to deliver\n */\n emit(windowId: string, envelope: NappletMessage): void;\n}\n\n/**\n * Factory for the canonical media-domain proxy.\n *\n * @param deps - Runtime + origin registry\n * @returns A {@link MediaProxy} ready to route media.* envelopes\n * @example\n * ```ts\n * import { createMediaProxy, originRegistry, createShellBridge } from '@kehto/shell';\n *\n * const bridge = createShellBridge(hooks);\n * const mediaProxy = createMediaProxy({\n * runtime: bridge.runtime,\n * originRegistry,\n * });\n *\n * // Shell-UI-initiated media command:\n * mediaProxy.emit('win-player', {\n * type: 'media.command',\n * sessionId: 's1',\n * action: 'seek',\n * value: 120,\n * });\n * ```\n */\nexport function createMediaProxy(deps: MediaProxyDeps): MediaProxy {\n return {\n dispatch(windowId: string, envelope: NappletMessage): void {\n deps.runtime.handleMessage(windowId, envelope);\n },\n emit(windowId: string, envelope: NappletMessage): void {\n const win = deps.originRegistry.getIframeWindow(windowId);\n if (win) win.postMessage(envelope, '*');\n },\n };\n}\n","/**\n * notify-proxy.ts — Shell-side per-domain proxy for notify.* envelopes.\n *\n * Establishes the shell-side composition seam for `@napplet/nap/notify`\n * notification envelopes. Shape mirrors identity-proxy (Plan 12-11):\n *\n * - `dispatch(windowId, envelope)` routes napplet→shell notify requests\n * (`notify.send`, `notify.dismiss`, `notify.badge`,\n * `notify.channel.register`, `notify.permission.request`) into the\n * runtime (Plan 12-07).\n * - `emit(windowId, envelope)` posts shell→napplet pushes\n * (`notify.send.result`, `notify.permission.result`, `notify.action`,\n * `notify.clicked`, `notify.dismissed`, `notify.controls`) through the\n * origin registry.\n *\n * By default `createShellBridge()` does NOT compose this proxy into its\n * dispatch path — the runtime owns notify.* dispatch. This module is an\n * optional composition point for host apps (e.g. custom notification UIs\n * that need to emit `notify.clicked` / `notify.action` pushes).\n */\n\nimport type { Runtime } from '@kehto/runtime';\nimport type { NappletMessage } from '@napplet/core';\nimport type { ProxyOriginRegistry } from './identity-proxy.js';\n\n/**\n * Dependencies for `createNotifyProxy`.\n *\n * @example\n * ```ts\n * const proxy = createNotifyProxy({\n * runtime: shellBridge.runtime,\n * originRegistry,\n * });\n * ```\n */\nexport interface NotifyProxyDeps {\n /** The runtime engine that owns notify.* dispatch (Plan 12-07). */\n runtime: Runtime;\n /** Origin registry for resolving windowId → iframe Window. */\n originRegistry: ProxyOriginRegistry;\n}\n\n/**\n * Per-domain proxy for `notify.*` envelopes.\n *\n * Shape: `dispatch` routes napplet→shell requests into the runtime; `emit`\n * pushes shell→napplet envelopes through the iframe's Window.\n */\nexport interface NotifyProxy {\n /**\n * Route a napplet-originated notify.* envelope into the runtime.\n *\n * @param windowId - The source napplet's windowId\n * @param envelope - The NIP-5D NappletMessage envelope\n */\n dispatch(windowId: string, envelope: NappletMessage): void;\n /**\n * Push a shell-initiated notify-domain envelope (e.g. `notify.action`,\n * `notify.clicked`) into a napplet iframe.\n *\n * No-op when the originRegistry cannot resolve the windowId (unknown or\n * unregistered napplet). Never throws.\n *\n * @param windowId - The target napplet's windowId\n * @param envelope - The NIP-5D NappletMessage envelope to deliver\n */\n emit(windowId: string, envelope: NappletMessage): void;\n}\n\n/**\n * Factory for the canonical notify-domain proxy.\n *\n * @param deps - Runtime + origin registry\n * @returns A {@link NotifyProxy} ready to route notify.* envelopes\n * @example\n * ```ts\n * import { createNotifyProxy, originRegistry, createShellBridge } from '@kehto/shell';\n *\n * const bridge = createShellBridge(hooks);\n * const notifyProxy = createNotifyProxy({\n * runtime: bridge.runtime,\n * originRegistry,\n * });\n *\n * // Shell-UI notifies napplet that user clicked its toast:\n * notifyProxy.emit('win-chat', {\n * type: 'notify.clicked',\n * notificationId: 'shell-42',\n * });\n * ```\n */\nexport function createNotifyProxy(deps: NotifyProxyDeps): NotifyProxy {\n return {\n dispatch(windowId: string, envelope: NappletMessage): void {\n deps.runtime.handleMessage(windowId, envelope);\n },\n emit(windowId: string, envelope: NappletMessage): void {\n const win = deps.originRegistry.getIframeWindow(windowId);\n if (win) win.postMessage(envelope, '*');\n },\n };\n}\n"],"mappings":"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","names":["originRegistry","STORAGE_KEY","ALL_CAPABILITIES"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kehto/shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.1",
|
|
4
4
|
"description": "Shell runtime — relay bridge, ACL enforcement, signer proxy, storage proxy for hosting napplet iframe applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -20,18 +20,18 @@
|
|
|
20
20
|
"access": "public"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@kehto/
|
|
24
|
-
"@kehto/
|
|
23
|
+
"@kehto/runtime": "^0.16.0",
|
|
24
|
+
"@kehto/acl": "^0.14.1"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
|
-
"@napplet/core": ">=0.
|
|
28
|
-
"@napplet/nap": ">=0.
|
|
27
|
+
"@napplet/core": ">=0.23.0 <0.24.0",
|
|
28
|
+
"@napplet/nap": ">=0.23.0 <0.24.0",
|
|
29
29
|
"nostr-tools": ">=2.23.3 <3.0.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
|
-
"@napplet/core": ">=0.
|
|
33
|
-
"@napplet/nap": ">=0.
|
|
34
|
-
"@napplet/shim": "^0.
|
|
32
|
+
"@napplet/core": ">=0.23.0 <0.24.0",
|
|
33
|
+
"@napplet/nap": ">=0.23.0 <0.24.0",
|
|
34
|
+
"@napplet/shim": "^0.24.0",
|
|
35
35
|
"nostr-tools": "^2.23.3",
|
|
36
36
|
"tsup": "^8.5.0",
|
|
37
37
|
"typescript": "^5.9.3"
|