@kehto/paja 0.2.0 → 0.3.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/dist/browser-host.js +1603 -43
- package/dist/browser-host.js.map +1 -1
- package/dist/{chunk-BM6ROSMJ.js → chunk-5HWERUMK.js} +26 -2
- package/dist/chunk-5HWERUMK.js.map +1 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.js +1 -1
- package/dist/{options-D3xZz-aS.d.ts → options-D4tneY-c.d.ts} +1 -1
- package/package.json +20 -20
- package/dist/chunk-BM6ROSMJ.js.map +0 -1
package/dist/browser-host.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../acl/src/capabilities.ts","../../acl/src/types.ts","../../acl/src/check.ts","../../acl/src/mutations.ts","../../acl/src/migrate.ts","../../acl/src/resolve.ts","../../firewall/src/evaluate.ts","../../firewall/src/defaults.ts","../../firewall/src/config.ts","../../../node_modules/.pnpm/@napplet+core@0.13.0/node_modules/@napplet/core/src/envelope.ts","../../../node_modules/.pnpm/@napplet+core@0.13.0/node_modules/@napplet/core/src/dispatch.ts","../../../node_modules/.pnpm/@napplet+core@0.13.0/node_modules/@napplet/core/src/topics.ts","../../runtime/src/enforce.ts","../../runtime/src/session-registry.ts","../../runtime/src/acl-state.ts","../../runtime/src/firewall-state.ts","../../runtime/src/manifest-cache.ts","../../runtime/src/replay.ts","../../runtime/src/event-buffer.ts","../../runtime/src/runtime.ts","../../runtime/src/service-dispatch.ts","../../runtime/src/relay-handler.ts","../../runtime/src/identity-handler.ts","../../runtime/src/inc-handler.ts","../../runtime/src/state-handler.ts","../../runtime/src/domain-handlers.ts","../../runtime/src/index.ts","../../shell/src/shell-bridge.ts","../../shell/src/hooks-adapter.ts","../../shell/src/origin-registry.ts","../../shell/src/session-registry.ts","../../shell/src/acl-store.ts","../../shell/src/manifest-cache.ts","../../shell/src/audio-manager.ts","../../shell/src/keys-forwarder.ts","../../shell/src/shell-init.ts","../../shell/src/shell-ready.ts","../../shell/src/index.ts","../../shell/src/topics.ts","../../shell/src/identity-proxy.ts","../../shell/src/theme-proxy.ts","../../shell/src/keys-proxy.ts","../../shell/src/media-proxy.ts","../../shell/src/notify-proxy.ts","../../services/src/audio-service.ts","../../services/src/notification-service.ts","../../services/src/identity-service.ts","../../services/src/relay-pool-service.ts","../../services/src/cache-service.ts","../../services/src/coordinated-relay.ts","../../services/src/keys-service.ts","../../services/src/browser-media-bridge.ts","../../services/src/media-service.ts","../../services/src/notify-service.ts","../../services/src/theme-service.ts","../../services/src/config-service.ts","../../services/src/resource-service.ts","../../services/src/outbox-service.ts","../../services/src/relay-pool-outbox-router.ts","../../services/src/upload-service.ts","../../services/src/http-uploader.ts","../../services/src/intent-service.ts","../../services/src/catalog-intent-resolver.ts","../../services/src/manifest-intent-catalog.ts","../../services/src/cvm-service.ts","../src/simulation.ts","../src/browser-host.ts"],"sourcesContent":["\n/**\n * All capability strings recognized by @kehto/acl.\n *\n * Ordering: v1.1 surface first (relay/cache/hotkey/state), then the\n * v1.2 additions for the seven naps + theme. The v1.1 `sign:event`,\n * `sign:nip04`, `sign:nip44` strings were intentionally removed — no\n * napplet-visible signing exists in canonical NIP-5D; signing flows\n * through shell-internal `relay.publishEncrypted` instead.\n */\nexport const ALL_CAPABILITIES = [\n // v1.1 kept:\n 'relay:read', 'relay:write',\n 'cache:read', 'cache:write',\n 'hotkey:forward',\n 'state:read', 'state:write',\n // v1.2 additions (seven naps + theme):\n 'identity:read',\n 'keys:bind', 'keys:forward',\n 'media:control',\n 'notify:send', 'notify:channel',\n 'theme:read',\n // v1.7 Phase 39 — NAP-CONFIG reference service (9th domain):\n 'config:read',\n // v1.7 Phase 40 — NAP-RESOURCE reference service (10th domain):\n 'resource:fetch',\n // NAP-CVM — ContextVM bridge (11th domain): call MCP-over-Nostr servers.\n 'cvm:call',\n // NAP-OUTBOX — outbox-aware relay routing (12th domain): read = query/\n // subscribe/resolveRelays/close; write = publish (shell-signed fanout).\n 'outbox:read', 'outbox:write',\n // NAP-UPLOAD — shell-mediated file/blob upload (13th domain): a single write\n // cap gates the network-egress + identity-linking upload op; status queries\n // ride the same grant (a napplet only inspects its own uploads).\n 'upload:write',\n // NAP-INTENT — archetype intent dispatch (14th domain): read = available/\n // handlers introspection (and receipt of shell pushes); write = invoke (the\n // focus-stealing cross-napplet dispatch op).\n 'intent:read', 'intent:write',\n] as const;\n\n/** Union of every capability string in ALL_CAPABILITIES. */\nexport type Capability = typeof ALL_CAPABILITIES[number];\n\n/** identity.getProfile/getFollows/getList/getZaps/getMutes/getBlocked/getBadges */\nexport const CAP_IDENTITY_READ = 'identity:read' as const;\n/** keys.registerAction / keys.unregisterAction / keys.bindings */\nexport const CAP_KEYS_BIND = 'keys:bind' as const;\n/** keys.forward / keys.action */\nexport const CAP_KEYS_FORWARD = 'keys:forward' as const;\n/** media.* (all actions) */\nexport const CAP_MEDIA_CONTROL = 'media:control' as const;\n/** notify.send / notify.dismiss / notify.badge / notify.action / notify.clicked / notify.dismissed / notify.controls / notify.send.result */\nexport const CAP_NOTIFY_SEND = 'notify:send' as const;\n/** notify.channel.register / notify.permission.request / notify.permission.result */\nexport const CAP_NOTIFY_CHANNEL = 'notify:channel' as const;\n/** theme.get / theme.changed */\nexport const CAP_THEME_READ = 'theme:read' as const;\n/** config.get / config.subscribe / config.unsubscribe / config.registerSchema / config.openSettings */\nexport const CAP_CONFIG_READ = 'config:read' as const;\n/** resource.bytes / resource.cancel (inbound) + resource.bytes.result / resource.bytes.error (outbound) */\nexport const CAP_RESOURCE_FETCH = 'resource:fetch' as const;\n/** cvm.discover / cvm.request / cvm.close (inbound) + cvm.*.result / cvm.event (outbound) */\nexport const CAP_CVM_CALL = 'cvm:call' as const;\n/** outbox.query / outbox.subscribe / outbox.close / outbox.resolveRelays (read-side outbox access) */\nexport const CAP_OUTBOX_READ = 'outbox:read' as const;\n/** outbox.publish (shell-signed, outbox-aware publish fanout) */\nexport const CAP_OUTBOX_WRITE = 'outbox:write' as const;\n/** upload.upload / upload.status (shell-mediated file/blob upload + status query) */\nexport const CAP_UPLOAD_WRITE = 'upload:write' as const;\n/** intent.available / intent.handlers (read-side archetype introspection + push receipt) */\nexport const CAP_INTENT_READ = 'intent:read' as const;\n/** intent.invoke (focus-stealing cross-napplet archetype dispatch) */\nexport const CAP_INTENT_WRITE = 'intent:write' as const;\n","/**\n * @kehto/acl — Type definitions and capability bit constants.\n *\n * All types use Readonly<> to enforce immutability at the type level.\n * Capability constants are bitfield values for fast check/grant/revoke.\n */\n\n/** relay:read — subscribe to relay events */\nexport const CAP_RELAY_READ = 1 << 0; // 1\n/** relay:write — publish events to relays */\nexport const CAP_RELAY_WRITE = 1 << 1; // 2\n/** cache:read — read from local cache */\nexport const CAP_CACHE_READ = 1 << 2; // 4\n/** cache:write — write to local cache */\nexport const CAP_CACHE_WRITE = 1 << 3; // 8\n/** hotkey:forward — forward keyboard shortcuts to shell */\nexport const CAP_HOTKEY_FORWARD = 1 << 4; // 16\n/** sign:event — request event signing */\nexport const CAP_SIGN_EVENT = 1 << 5; // 32\n/** sign:nip04 — request NIP-04 encrypt/decrypt */\nexport const CAP_SIGN_NIP04 = 1 << 6; // 64\n/** sign:nip44 — request NIP-44 encrypt/decrypt */\nexport const CAP_SIGN_NIP44 = 1 << 7; // 128\n/** state:read — read napplet-scoped state */\nexport const CAP_STATE_READ = 1 << 8; // 256\n/** state:write — write napplet-scoped state */\nexport const CAP_STATE_WRITE = 1 << 9; // 512\n\n/** All capabilities granted (bits 0-9 set) */\nexport const CAP_ALL = (1 << 10) - 1; // 1023\n\n/** No capabilities granted */\nexport const CAP_NONE = 0;\n\n/**\n * Napplet identity — composite key for ACL lookups.\n *\n * Under NIP-5D v0.1.0, identity is assigned from the NIP-5A manifest\n * at iframe creation time. The pubkey field is no longer used.\n *\n * @param pubkey - (deprecated) Ephemeral AUTH keypair pubkey. Ignored by toKey().\n * @param dTag - Derived tag (deterministic from napp type)\n * @param hash - Aggregate hash of napplet build artifacts\n */\nexport interface Identity {\n /** @deprecated NIP-5D: AUTH keypair no longer exists. Pass '' or omit entirely.\n * Kept as optional for backward compatibility during data migration. */\n readonly pubkey?: string;\n readonly dTag: string;\n readonly hash: string;\n}\n\n/**\n * A single ACL entry for one napplet identity.\n *\n * @param caps - Bitfield of granted capabilities (use CAP_* constants)\n * @param blocked - Orthogonal block flag; when true, all checks fail regardless of caps\n * @param quota - State storage quota in bytes\n */\nexport interface AclEntry {\n readonly caps: number;\n readonly blocked: boolean;\n readonly quota: number;\n}\n\n/**\n * Complete ACL state — immutable data structure.\n *\n * All mutations return a new AclState; the original is never modified.\n *\n * @param defaultPolicy - 'permissive' grants all caps to unknown identities;\n * 'restrictive' denies all caps to unknown identities\n * @param entries - Map from composite key ('dTag:hash') to AclEntry\n */\nexport interface AclState {\n readonly defaultPolicy: 'permissive' | 'restrictive';\n readonly entries: Readonly<Record<string, AclEntry>>;\n}\n\n/** Default state storage quota in bytes (512 KB) */\nexport const DEFAULT_QUOTA = 512 * 1024;\n","/**\n * @kehto/acl — Pure check function.\n *\n * Determines whether an identity has a specific capability.\n * No side effects, no I/O, no mutations.\n */\n\nimport type { AclState, Identity } from './types.js';\n\n/**\n * Compute composite key from identity fields.\n *\n * Under NIP-5D v0.1.0, the key is 'dTag:hash' (pubkey is ignored).\n *\n * @param identity - Napplet identity\n * @returns Composite key string 'dTag:hash'\n *\n * @example\n * ```ts\n * toKey({ dTag: 'chat', hash: 'ff00' })\n * // => 'chat:ff00'\n * ```\n */\nexport function toKey(identity: Identity): string {\n return `${identity.dTag}:${identity.hash}`;\n}\n\n/**\n * Check whether an identity has a specific capability.\n *\n * Decision logic:\n * 1. If identity has no entry: return based on defaultPolicy\n * - 'permissive' → true (all caps granted to unknown identities)\n * - 'restrictive' → false (all caps denied to unknown identities)\n * 2. If identity is blocked: return false (blocked overrides all caps)\n * 3. Otherwise: return (entry.caps & cap) !== 0\n *\n * @param state - Current ACL state (immutable)\n * @param identity - Napplet identity to check\n * @param cap - Capability bit constant (e.g., CAP_RELAY_READ)\n * @returns true if the identity has the capability, false otherwise\n *\n * @example\n * ```ts\n * import { check, createState, grant } from '@kehto/acl';\n * import { CAP_RELAY_READ } from '@kehto/acl';\n *\n * const state = createState('restrictive');\n * const id = { dTag: 'chat', hash: 'ff00' };\n *\n * check(state, id, CAP_RELAY_READ); // false (restrictive, no entry)\n *\n * const state2 = grant(state, id, CAP_RELAY_READ);\n * check(state2, id, CAP_RELAY_READ); // true\n * ```\n */\nexport function check(state: AclState, identity: Identity, cap: number): boolean {\n const key = toKey(identity);\n const entry = state.entries[key];\n if (!entry) {\n return state.defaultPolicy === 'permissive';\n }\n if (entry.blocked) {\n return false;\n }\n return (entry.caps & cap) !== 0;\n}\n","/**\n * @kehto/acl — Pure state mutation functions.\n *\n * Every function takes an AclState and returns a NEW AclState.\n * The original state is never modified. No side effects, no I/O.\n */\n\nimport { CAP_ALL, DEFAULT_QUOTA, type AclEntry, type AclState, type Identity } from './types.js';\nimport { toKey } from './check.js';\n\n/**\n * Create a new ACL state with the given default policy.\n *\n * @param policy - 'permissive' grants all caps to unknown identities;\n * 'restrictive' denies all caps to unknown identities.\n * Defaults to 'permissive'.\n * @returns A new empty AclState\n *\n * @example\n * ```ts\n * const state = createState('restrictive');\n * // { defaultPolicy: 'restrictive', entries: {} }\n * ```\n */\nexport function createState(policy: 'permissive' | 'restrictive' = 'permissive'): AclState {\n return { defaultPolicy: policy, entries: {} };\n}\n\n/**\n * Get the entry for an identity, or a default entry based on policy.\n * Internal helper — not exported.\n */\nfunction getEntry(state: AclState, key: string): AclEntry {\n const existing = state.entries[key];\n if (existing) return existing;\n // Default entry: all caps if permissive, no caps if restrictive\n return {\n caps: state.defaultPolicy === 'permissive' ? CAP_ALL : 0,\n blocked: false,\n quota: DEFAULT_QUOTA,\n };\n}\n\n/**\n * Grant a capability to an identity.\n *\n * If the identity has no entry, one is created with default caps plus the granted cap.\n * Returns a new AclState — the original is not modified.\n *\n * @param state - Current ACL state\n * @param identity - Napplet identity\n * @param cap - Capability bit constant to grant (e.g., CAP_RELAY_READ)\n * @returns New AclState with the capability granted\n *\n * @example\n * ```ts\n * const state2 = grant(state, id, CAP_RELAY_READ);\n * check(state2, id, CAP_RELAY_READ); // true\n * ```\n */\nexport function grant(state: AclState, identity: Identity, cap: number): AclState {\n const key = toKey(identity);\n const entry = getEntry(state, key);\n return {\n ...state,\n entries: {\n ...state.entries,\n [key]: { ...entry, caps: entry.caps | cap },\n },\n };\n}\n\n/**\n * Revoke a capability from an identity.\n *\n * If the identity has no entry, one is created with default caps minus the revoked cap.\n * Returns a new AclState — the original is not modified.\n *\n * @param state - Current ACL state\n * @param identity - Napplet identity\n * @param cap - Capability bit constant to revoke (e.g., CAP_RELAY_WRITE)\n * @returns New AclState with the capability revoked\n *\n * @example\n * ```ts\n * const state2 = revoke(state, id, CAP_RELAY_WRITE);\n * check(state2, id, CAP_RELAY_WRITE); // false\n * ```\n */\nexport function revoke(state: AclState, identity: Identity, cap: number): AclState {\n const key = toKey(identity);\n const entry = getEntry(state, key);\n return {\n ...state,\n entries: {\n ...state.entries,\n [key]: { ...entry, caps: entry.caps & ~cap },\n },\n };\n}\n\n/**\n * Block an identity.\n *\n * A blocked identity fails all capability checks regardless of granted caps.\n * The caps bitfield is preserved — unblocking restores previous capabilities.\n * Returns a new AclState — the original is not modified.\n *\n * @param state - Current ACL state\n * @param identity - Napplet identity to block\n * @returns New AclState with the identity blocked\n *\n * @example\n * ```ts\n * const state2 = block(state, id);\n * check(state2, id, CAP_RELAY_READ); // false (blocked)\n * ```\n */\nexport function block(state: AclState, identity: Identity): AclState {\n const key = toKey(identity);\n const entry = getEntry(state, key);\n return {\n ...state,\n entries: {\n ...state.entries,\n [key]: { ...entry, blocked: true },\n },\n };\n}\n\n/**\n * Unblock an identity.\n *\n * Restores capability checks to use the caps bitfield.\n * Returns a new AclState — the original is not modified.\n *\n * @param state - Current ACL state\n * @param identity - Napplet identity to unblock\n * @returns New AclState with the identity unblocked\n *\n * @example\n * ```ts\n * const state2 = unblock(state, id);\n * check(state2, id, CAP_RELAY_READ); // true (if cap was granted)\n * ```\n */\nexport function unblock(state: AclState, identity: Identity): AclState {\n const key = toKey(identity);\n const entry = getEntry(state, key);\n return {\n ...state,\n entries: {\n ...state.entries,\n [key]: { ...entry, blocked: false },\n },\n };\n}\n\n/**\n * Set the state storage quota for an identity.\n *\n * @param state - Current ACL state\n * @param identity - Napplet identity\n * @param bytes - Quota in bytes\n * @returns New AclState with the quota set\n *\n * @example\n * ```ts\n * const state2 = setQuota(state, id, 1024 * 1024); // 1 MB\n * getQuota(state2, id); // 1048576\n * ```\n */\nexport function setQuota(state: AclState, identity: Identity, bytes: number): AclState {\n const key = toKey(identity);\n const entry = getEntry(state, key);\n return {\n ...state,\n entries: {\n ...state.entries,\n [key]: { ...entry, quota: bytes },\n },\n };\n}\n\n/**\n * Get the state storage quota for an identity.\n *\n * Returns DEFAULT_QUOTA (512 KB) if no entry exists.\n *\n * @param state - Current ACL state\n * @param identity - Napplet identity\n * @returns Quota in bytes\n *\n * @example\n * ```ts\n * getQuota(state, id); // 524288 (default 512 KB)\n * ```\n */\nexport function getQuota(state: AclState, identity: Identity): number {\n const key = toKey(identity);\n const entry = state.entries[key];\n return entry?.quota ?? DEFAULT_QUOTA;\n}\n\n/**\n * Serialize ACL state to a JSON string.\n *\n * Pure function — no I/O. The persistence adapter in @kehto/shell\n * uses this to write state to localStorage or other backends.\n *\n * @param state - ACL state to serialize\n * @returns JSON string representation\n *\n * @example\n * ```ts\n * const json = serialize(state);\n * localStorage.setItem('napplet:acl', json);\n * ```\n */\nexport function serialize(state: AclState): string {\n return JSON.stringify(state);\n}\n\n/**\n * Deserialize ACL state from a JSON string.\n *\n * Pure function — no I/O. Returns a valid AclState or a fresh\n * permissive state if the input is invalid.\n *\n * @param json - JSON string to parse\n * @returns Parsed AclState, or fresh permissive state on parse failure\n *\n * @example\n * ```ts\n * const json = localStorage.getItem('napplet:acl') ?? '';\n * const state = deserialize(json);\n * ```\n */\nexport function deserialize(json: string): AclState {\n try {\n const parsed = JSON.parse(json);\n if (\n typeof parsed === 'object' &&\n parsed !== null &&\n (parsed.defaultPolicy === 'permissive' || parsed.defaultPolicy === 'restrictive') &&\n typeof parsed.entries === 'object' &&\n parsed.entries !== null\n ) {\n const entries: Record<string, AclEntry> = {};\n for (const [key, value] of Object.entries(parsed.entries)) {\n const entry = value as Record<string, unknown>;\n if (\n typeof entry.caps === 'number' &&\n typeof entry.blocked === 'boolean' &&\n typeof entry.quota === 'number'\n ) {\n entries[key] = {\n caps: entry.caps,\n blocked: entry.blocked,\n quota: entry.quota,\n };\n }\n }\n return { defaultPolicy: parsed.defaultPolicy, entries };\n }\n } catch {\n // Invalid JSON — fall through to default\n }\n return createState('permissive');\n}\n","/**\n * @kehto/acl — ACL state migration utility.\n *\n * Provides a pure function to migrate persisted ACL state from the old\n * 3-segment composite key format (pubkey:dTag:hash) to the new 2-segment\n * format (dTag:hash) introduced in NIP-5D v0.1.0.\n *\n * No I/O, no side effects. Pure function: takes AclState, returns AclState.\n */\n\nimport type { AclState, AclEntry } from './types.js';\n\n/**\n * Migrate ACL state from old 3-segment key format to new 2-segment key format.\n *\n * Converts entries stored under 'pubkey:dTag:hash' keys to 'dTag:hash' keys.\n * If two old entries map to the same dTag:hash, merges them conservatively:\n * - caps: OR of both bitfields (never removes a granted capability)\n * - blocked: OR of both flags (blocks if either source was blocked)\n * - quota: MAX of both values (keeps the higher allocation)\n *\n * Idempotent: if no 3-segment keys are found, returns the original state\n * unchanged (same object reference).\n *\n * @param state - Current ACL state (may contain old-format entries)\n * @returns Migrated AclState with only 2-segment keys, or the original\n * state unchanged if no migration was needed\n *\n * @example\n * ```ts\n * const oldState = deserialize(localStorage.getItem('napplet:acl') ?? '');\n * const newState = migrateAclState(oldState);\n * if (newState !== oldState) {\n * // Migration occurred — persist the new format\n * localStorage.setItem('napplet:acl', serialize(newState));\n * }\n * ```\n */\nexport function migrateAclState(state: AclState): AclState {\n const newEntries: Record<string, AclEntry> = {};\n let migrated = false;\n\n for (const [key, entry] of Object.entries(state.entries)) {\n const parts = key.split(':');\n if (parts.length === 3) {\n // Old format: pubkey:dTag:hash -> dTag:hash\n const newKey = `${parts[1]}:${parts[2]}`;\n const existing = newEntries[newKey];\n if (existing) {\n // Merge: union caps, preserve block, max quota\n newEntries[newKey] = {\n caps: existing.caps | entry.caps,\n blocked: existing.blocked || entry.blocked,\n quota: Math.max(existing.quota, entry.quota),\n };\n } else {\n newEntries[newKey] = entry;\n }\n migrated = true;\n } else {\n // Already new format or other key — merge if collision with a previously migrated entry\n const existing = newEntries[key];\n if (existing) {\n // Collision: old-format entry was processed first under the same key — merge\n newEntries[key] = {\n caps: existing.caps | entry.caps,\n blocked: existing.blocked || entry.blocked,\n quota: Math.max(existing.quota, entry.quota),\n };\n } else {\n newEntries[key] = entry;\n }\n }\n }\n\n if (!migrated) return state; // No old entries found — return original unchanged\n\n return { defaultPolicy: state.defaultPolicy, entries: newEntries };\n}\n","/**\n * @kehto/acl — NAP/NAP domain capability resolution (8 canonical + config,\n * resource, cvm, outbox, upload, intent).\n *\n * Maps NAP message types (e.g., 'relay.subscribe', 'identity.getProfile') to\n * the capability strings required by sender and recipient. This is the\n * canonical source for \"which capability does this NAP operation require?\"\n * in the @kehto/acl package.\n *\n * Canonical NIP-5D 8 domains: identity, keys, media, notify, relay,\n * storage, inc, theme. Extended in v1.7 with: config (Phase 39, 9th domain),\n * resource (Phase 40, 10th domain). The v1.1 `signer` domain is REMOVED —\n * getPublicKey/getRelays migrated to `identity`; signEvent/nip04/nip44 have\n * no napplet-visible surface (shell handles encryption inside\n * `relay.publishEncrypted`).\n *\n * Zero dependencies. No imports from @napplet/core or any external package.\n *\n * @see packages/acl/src/capabilities.ts for cap string constants + ALL_CAPABILITIES.\n * @see docs/ACL-MIGRATION.md section 2 — Capability Constant to NAP Domain Mapping.\n */\n\n/**\n * Minimal message shape used for capability resolution.\n *\n * Compatible with NappletMessage from @napplet/core, but defined here\n * independently to maintain @kehto/acl's zero-dependency constraint.\n *\n * @param type - NAP message type, e.g. 'relay.subscribe', 'identity.getProfile'\n */\nexport interface NapMessage {\n readonly type: string;\n}\n\n/**\n * Result of resolving what capabilities a NAP message requires.\n *\n * | Field | Description |\n * |----------------|----------------------------------------------------------------|\n * | `senderCap` | Capability the sender must have, or null if no check needed |\n * | `recipientCap` | Capability the recipient must have, or null if no check needed |\n *\n * @param senderCap - Capability the sender must have, or null if no ACL gate required\n * @param recipientCap - Capability the recipient must have, or null if no recipient check\n */\nexport interface CapabilityResolution {\n readonly senderCap: string | null;\n readonly recipientCap: string | null;\n}\n\n/**\n * `relay.*` — split publish / publishEncrypted / read actions.\n *\n * - `publish` → sender `relay:write`, recipient `relay:read`.\n * - `publishEncrypted` → sender `relay:write`, recipient `null` (the shell\n * handles encryption internally; no napplet-visible recipient ACL check).\n * - `subscribe` / `query` / `close` (and `.result` / `event` / `eose` /\n * `closed` / `publish.result` / `publishEncrypted.result`) → sender\n * `relay:read`, recipient `null`.\n */\nfunction relayMap(action: string): CapabilityResolution {\n if (action === 'publish') return { senderCap: 'relay:write', recipientCap: 'relay:read' };\n if (action === 'publishEncrypted') return { senderCap: 'relay:write', recipientCap: null };\n return { senderCap: 'relay:read', recipientCap: null };\n}\n\n/**\n * `identity.*` — split shell-public reads from gated profile reads.\n *\n * Identity is strictly read-only per NAP-IDENTITY: napplets cannot sign,\n * encrypt, or decrypt (encryption is delegated via `relay.publishEncrypted`).\n *\n * - `getPublicKey` / `getRelays` → `null`/`null` (shell-public info).\n * - `getProfile` / `getFollows` / `getList` / `getZaps` / `getMutes` /\n * `getBlocked` / `getBadges` (and any other identity read) → sender\n * `identity:read`, recipient `null`.\n */\nfunction identityMap(action: string): CapabilityResolution {\n if (action === 'getPublicKey' || action === 'getRelays') {\n return { senderCap: null, recipientCap: null };\n }\n return { senderCap: 'identity:read', recipientCap: null };\n}\n\n/**\n * `keys.*` — split forwarding from binding lifecycle.\n *\n * - `forward` / `action` → `keys:forward`.\n * - `registerAction` / `unregisterAction` / `bindings` → `keys:bind`.\n */\nfunction keysMap(action: string): CapabilityResolution {\n if (action === 'forward' || action === 'action') {\n return { senderCap: 'keys:forward', recipientCap: null };\n }\n return { senderCap: 'keys:bind', recipientCap: null };\n}\n\n/**\n * `notify.*` — split channel/permission registration from send/interaction.\n *\n * - `channel.register` / `permission.request` / `permission.result` → `notify:channel`.\n * - `send` / `dismiss` / `badge` / `send.result` / `action` / `clicked` /\n * `dismissed` / `controls` (and any other notify action) → `notify:send`.\n */\nfunction notifyMap(action: string): CapabilityResolution {\n if (\n action === 'channel.register' ||\n action === 'permission.request' ||\n action === 'permission.result'\n ) {\n return { senderCap: 'notify:channel', recipientCap: null };\n }\n return { senderCap: 'notify:send', recipientCap: null };\n}\n\n/**\n * `storage.*` — narrowed to the canonical 4 actions (get/keys/set/remove).\n *\n * - `get` / `keys` → `state:read`.\n * - `set` / `remove` → `state:write`.\n * - anything else (incl. the removed `clear`) → `null`/`null`. The runtime\n * storage handler rejects non-canonical actions before ACL resolution so\n * napplets see the explicit rejection rather than a misleading cap denial.\n */\nfunction storageMap(action: string): CapabilityResolution {\n if (action === 'get' || action === 'keys') return { senderCap: 'state:read', recipientCap: null };\n if (action === 'set' || action === 'remove') return { senderCap: 'state:write', recipientCap: null };\n return { senderCap: null, recipientCap: null };\n}\n\n/**\n * `inc.*` — topic + channel sub-protocol.\n *\n * - Write actions (`emit`, `channel.emit`, `channel.broadcast`) → sender\n * `relay:write`, recipient `relay:read`. Semantically equivalent to relay\n * publish: point-to-point or fan-out writes gate on relay-write at wire\n * level even though channel membership ACL is enforced at `channel.open`.\n * - Read / control actions (`subscribe`, `unsubscribe`, `channel.open`,\n * `channel.list`, `channel.close`) → sender\n * `relay:read`, recipient `null`. Channel open-time ACL semantics: the\n * caller must already hold `relay:read`, and channel membership is\n * recorded by the inc handler.\n */\nfunction incMap(action: string): CapabilityResolution {\n if (action === 'emit' || action === 'channel.emit' || action === 'channel.broadcast') {\n return { senderCap: 'relay:write', recipientCap: 'relay:read' };\n }\n return { senderCap: 'relay:read', recipientCap: null };\n}\n\n/**\n * `config.*` — NAP-CONFIG reference service (v1.7 Phase 39 / 9th NAP domain).\n *\n * Asymmetric protocol: napplet reads, shell writes. ALL napplet-originated\n * config messages require `config:read`. Shell→napplet pushes\n * (`config.values`, `config.registerSchema.result`, `config.schemaError`)\n * are gated by the recipient's `config:read` cap.\n *\n * Anti-overlap: NAP-STORAGE remains the general key-value surface\n * (`state:read`/`state:write`). NAP-CONFIG is shell-managed per-napplet\n * configuration only — see CONFIG-04 scope boundary docs.\n */\nfunction configMap(action: string): CapabilityResolution {\n // Shell-originated pushes: recipient gate (napplet must hold config:read to see them).\n if (action === 'values' || action === 'registerSchema.result' || action === 'schemaError') {\n return { senderCap: null, recipientCap: 'config:read' };\n }\n // Napplet-originated requests: sender gate.\n return { senderCap: 'config:read', recipientCap: null };\n}\n\n/**\n * `resource.*` — NAP-RESOURCE authenticated fetch proxy (v1.7 Phase 40 / 10th NAP domain).\n *\n * Asymmetric protocol: napplet initiates fetch requests, shell proxies and responds.\n *\n * - `bytes` / `cancel` (napplet → shell requests) →\n * sender `resource:fetch`, recipient `null`. The napplet must hold\n * `resource:fetch` to issue a bytes request or cancel one.\n * - `bytes.result` / `bytes.error` (shell → napplet pushes) →\n * sender `null`, recipient `resource:fetch`. The napplet must hold\n * `resource:fetch` to receive the result/error push.\n * - Unknown resource.* actions → sender `resource:fetch`, recipient `null`\n * (default sender gate: napplet must hold resource:fetch to send anything\n * in the resource domain).\n */\nfunction resourceMap(action: string): CapabilityResolution {\n // Shell-originated pushes: recipient gate (napplet must hold resource:fetch to see them).\n if (action === 'bytes.result' || action === 'bytes.error') {\n return { senderCap: null, recipientCap: 'resource:fetch' };\n }\n // Napplet-originated requests: sender gate (bytes, cancel, and any unknown).\n return { senderCap: 'resource:fetch', recipientCap: null };\n}\n\n/**\n * `cvm.*` — NAP-CVM ContextVM bridge. Single `cvm:call` cap gates the domain.\n *\n * - `discover` / `request` / `close` (napplet → shell requests) →\n * sender `cvm:call`, recipient `null`. The napplet must hold `cvm:call`\n * to query servers or send MCP messages.\n * - `discover.result` / `request.result` / `close.result` / `event`\n * (shell → napplet pushes) → sender `null`, recipient `cvm:call`. The push\n * is gated against the receiving napplet's cap so a napplet without\n * `cvm:call` never sees CVM results or server-pushed events.\n * - Unknown `cvm.*` actions → sender `cvm:call` (default sender gate).\n */\nfunction cvmMap(action: string): CapabilityResolution {\n // Shell-originated pushes: recipient gate.\n if (action === 'event' || action.endsWith('.result') || action.endsWith('.error')) {\n return { senderCap: null, recipientCap: 'cvm:call' };\n }\n // Napplet-originated requests: sender gate (discover, request, close, unknown).\n return { senderCap: 'cvm:call', recipientCap: null };\n}\n\n/**\n * `outbox.*` — NAP-OUTBOX outbox-aware relay routing (12th NAP domain).\n *\n * Split read/write like the `relay` domain, but with dedicated caps so a shell\n * can grant outbox routing independently of raw relay access:\n *\n * - `publish` (napplet → shell) → sender `outbox:write`,\n * recipient `null`. The shell signs and fans the event out to the relevant\n * write relays; there is no napplet-visible recipient ACL check.\n * - `query` / `subscribe` / `close` / `resolveRelays` (and any other\n * napplet-originated request) → sender `outbox:read`,\n * recipient `null`.\n * - `event` / `eose` / `closed` / `*.result` / `*.error` (shell → napplet\n * pushes) → sender `null`,\n * recipient `outbox:read`. The push is gated against the receiving napplet's\n * cap so a napplet without `outbox:read` never sees results or streamed\n * events.\n */\nfunction outboxMap(action: string): CapabilityResolution {\n // Shell-originated pushes: recipient gate (napplet must hold outbox:read to see them).\n if (\n action === 'event' ||\n action === 'eose' ||\n action === 'closed' ||\n action.endsWith('.result') ||\n action.endsWith('.error')\n ) {\n return { senderCap: null, recipientCap: 'outbox:read' };\n }\n // Publish is the write op.\n if (action === 'publish') return { senderCap: 'outbox:write', recipientCap: null };\n // Napplet-originated reads: query, subscribe, close, resolveRelays, unknown.\n return { senderCap: 'outbox:read', recipientCap: null };\n}\n\n/**\n * `upload.*` — NAP-UPLOAD shell-mediated file/blob upload (13th NAP domain).\n *\n * A single `upload:write` cap gates the domain — uploading is the sensitive op\n * (network egress + identity-linking), and `status` only inspects the requesting\n * napplet's own uploads, so it rides the same grant rather than a separate read\n * cap.\n *\n * - `upload` / `status` (and any other napplet-originated request) → sender\n * `upload:write`, recipient `null`.\n * - `upload.result` / `status.result` / `status.changed` / `*.error` (shell →\n * napplet pushes) → sender `null`, recipient `upload:write`. The push is gated\n * against the receiving napplet's cap so a napplet without `upload:write`\n * never sees results or progress updates.\n */\nfunction uploadMap(action: string): CapabilityResolution {\n // Shell-originated pushes: recipient gate.\n if (\n action === 'status.changed' ||\n action.endsWith('.result') ||\n action.endsWith('.error')\n ) {\n return { senderCap: null, recipientCap: 'upload:write' };\n }\n // Napplet-originated requests: sender gate (upload, status, unknown).\n return { senderCap: 'upload:write', recipientCap: null };\n}\n\n/**\n * `intent.*` — NAP-INTENT archetype intent dispatch (14th NAP domain).\n *\n * Split read/write so a read-only class can introspect available handlers but\n * cannot dispatch (mirrors `outbox` / `relay`, where the write op is the\n * sensitive one — here `invoke` is a focus-stealing cross-napplet navigation):\n *\n * - `invoke` (napplet → shell) → sender `intent:write`,\n * recipient `null`. The shell resolves the archetype to a handler, creates or\n * focuses its window, and delivers the payload.\n * - `available` / `handlers` (and any other napplet-originated request) →\n * sender `intent:read`, recipient `null`. Read-side catalog introspection.\n * - `changed` / `*.result` / `*.error` (shell → napplet pushes) →\n * sender `null`, recipient `intent:read`. The push is gated against the\n * receiving napplet's cap so a napplet without `intent:read` never sees\n * availability updates or invoke results.\n */\nfunction intentMap(action: string): CapabilityResolution {\n // Shell-originated pushes: recipient gate (napplet must hold intent:read to see them).\n if (action === 'changed' || action.endsWith('.result') || action.endsWith('.error')) {\n return { senderCap: null, recipientCap: 'intent:read' };\n }\n // Invoke is the write op (cross-napplet dispatch / focus-steal).\n if (action === 'invoke') return { senderCap: 'intent:write', recipientCap: null };\n // Napplet-originated reads: available, handlers, unknown.\n return { senderCap: 'intent:read', recipientCap: null };\n}\n\n/**\n * `theme.*` — napplet read gate vs shell-initiated push.\n *\n * - `get` / `get.result` (and any other napplet-originated query) →\n * sender `theme:read`, recipient `null`.\n * - `changed` (shell → napplet push) →\n * sender `null`, recipient `theme:read`. The push is gated against the\n * receiving napplet's cap so a napplet without `theme:read` never sees\n * the update.\n *\n * Note: theme's runtime/service wiring lands in Phase 13. The ACL gate is\n * defined here in Phase 12 so the cap surface is canonical ahead of the\n * runtime work.\n */\nfunction themeMap(action: string): CapabilityResolution {\n if (action === 'changed') return { senderCap: null, recipientCap: 'theme:read' };\n return { senderCap: 'theme:read', recipientCap: null };\n}\n\n/**\n * Resolve the capabilities required by a NAP message.\n *\n * Splits `msg.type` on '.' to obtain `[domain, action]`, then dispatches to\n * a per-domain mapper. Unknown domains return `null/null` (silently ignored).\n *\n * **NAP domain mapping table (8 canonical domains):**\n *\n * | Domain | Action(s) | senderCap | recipientCap |\n * |------------|--------------------------------------------------------------|-----------------|---------------|\n * | `relay` | `subscribe`, `query`, `close`, results/pushes | `relay:read` | `null` |\n * | `relay` | `publish` | `relay:write` | `relay:read` |\n * | `relay` | `publishEncrypted` | `relay:write` | `null` |\n * | `identity` | `getPublicKey`, `getRelays` | `null` | `null` |\n * | `identity` | `getProfile/getFollows/getList/getZaps/getMutes/...` | `identity:read` | `null` |\n * | `keys` | `forward`, `action` | `keys:forward` | `null` |\n * | `keys` | `registerAction`, `unregisterAction`, `bindings` | `keys:bind` | `null` |\n * | `media` | any | `media:control` | `null` |\n * | `notify` | `channel.register`, `permission.request`, `permission.result` | `notify:channel`| `null` |\n * | `notify` | `send`, `dismiss`, `badge`, `clicked`, `action`, ... | `notify:send` | `null` |\n * | `storage` | `get`, `keys` | `state:read` | `null` |\n * | `storage` | `set`, `remove` | `state:write` | `null` |\n * | `storage` | any other (incl. removed `clear`) | `null` | `null` |\n * | `inc` | `emit`, `channel.emit`, `channel.broadcast` | `relay:write` | `relay:read` |\n * | `inc` | `subscribe`, `unsubscribe`, `channel.open/list/close` | `relay:read` | `null` |\n * | `theme` | `get`, `get.result` | `theme:read` | `null` |\n * | `theme` | `changed` (shell → napplet push) | `null` | `theme:read` |\n * | `config` | `get`, `subscribe`, `unsubscribe`, `registerSchema`, `openSettings` | `config:read` | `null` |\n * | `config` | `values`, `registerSchema.result`, `schemaError` (shell → napplet pushes) | `null` | `config:read` |\n * | `resource` | `bytes`, `cancel` (napplet → shell requests) | `resource:fetch`| `null` |\n * | `resource` | `bytes.result`, `bytes.error` (shell → napplet pushes) | `null` | `resource:fetch` |\n * | `intent` | `invoke` (napplet → shell) | `intent:write` | `null` |\n * | `intent` | `available`, `handlers` (napplet → shell) | `intent:read` | `null` |\n * | `intent` | `changed`, `*.result`, `*.error` (shell → napplet pushes) | `null` | `intent:read` |\n * | unknown | any | `null` | `null` |\n *\n * The `signer` domain is REMOVED — signer messages fall through to the\n * default null/null branch. `getPublicKey`/`getRelays` migrated to\n * `identity`; napplet-visible signing does not exist in NIP-5D (shell\n * signs internally for `relay.publishEncrypted`).\n *\n * @param msg - Message with a `type` field in NAP format (e.g., 'relay.subscribe')\n * @returns CapabilityResolution with senderCap and recipientCap (each may be null)\n *\n * @example\n * ```ts\n * resolveCapabilitiesNap({ type: 'relay.subscribe' })\n * // => { senderCap: 'relay:read', recipientCap: null }\n *\n * resolveCapabilitiesNap({ type: 'relay.publishEncrypted' })\n * // => { senderCap: 'relay:write', recipientCap: null }\n *\n * resolveCapabilitiesNap({ type: 'identity.getProfile' })\n * // => { senderCap: 'identity:read', recipientCap: null }\n *\n * resolveCapabilitiesNap({ type: 'keys.forward' })\n * // => { senderCap: 'keys:forward', recipientCap: null }\n *\n * resolveCapabilitiesNap({ type: 'inc.channel.broadcast' })\n * // => { senderCap: 'relay:write', recipientCap: 'relay:read' }\n *\n * resolveCapabilitiesNap({ type: 'theme.changed' })\n * // => { senderCap: null, recipientCap: 'theme:read' }\n *\n * resolveCapabilitiesNap({ type: 'signer.signEvent' })\n * // => { senderCap: null, recipientCap: null } // domain removed\n * ```\n */\nexport function resolveCapabilitiesNap(msg: NapMessage): CapabilityResolution {\n const dotIdx = msg.type.indexOf('.');\n if (dotIdx === -1) return { senderCap: null, recipientCap: null };\n const domain = msg.type.slice(0, dotIdx);\n const action = msg.type.slice(dotIdx + 1);\n\n switch (domain) {\n case 'relay': return relayMap(action);\n case 'identity': return identityMap(action);\n case 'keys': return keysMap(action);\n case 'media': return { senderCap: 'media:control', recipientCap: null };\n case 'notify': return notifyMap(action);\n case 'storage': return storageMap(action);\n case 'inc': return incMap(action);\n case 'theme': return themeMap(action);\n case 'config': return configMap(action);\n case 'resource': return resourceMap(action); // Phase 40 (RESOURCE-02)\n case 'cvm': return cvmMap(action); // NAP-CVM ContextVM bridge\n case 'outbox': return outboxMap(action); // NAP-OUTBOX outbox-aware relay routing\n case 'upload': return uploadMap(action); // NAP-UPLOAD shell-mediated file/blob upload\n case 'intent': return intentMap(action); // NAP-INTENT archetype intent dispatch\n default: return { senderCap: null, recipientCap: null };\n }\n}\n","/**\n * @kehto/firewall — Pure evaluate function.\n *\n * This module is PURE, SIDE-EFFECT-FREE, and NEVER reads a wall clock.\n * `observation.now` is the only time source — no wall-clock reads, no I/O, no mutations.\n *\n * Designed for deterministic access control decisions that could be compiled to\n * WASM without modification (the WASM-ready boundary).\n */\n\nimport type {\n Observation,\n FirewallConfig,\n FirewallState,\n Bucket,\n BurstCounter,\n EvaluateResult,\n Action,\n Decision,\n} from './types.js';\n\n/**\n * Compute the token-bucket key from napplet dTag and operation class.\n *\n * Key shape: `${napplet}:${opClass}` — deliberately dTag-only (version-agnostic).\n *\n * DIVERGENCE FROM @kehto/acl: acl uses `dTag:hash` to distinguish napplet\n * versions. The firewall intentionally omits the hash so rate budgets are shared\n * across ALL versions of the same napplet dTag. This is the correct behavior for\n * a behavioral abuse control: we want to track the napplet identity over time,\n * not its specific loaded version.\n *\n * @param napplet - Napplet dTag (version-agnostic identity key)\n * @param opClass - Operation class string (e.g. 'relay:write', 'outbox:publish')\n * @returns Composite key string `napplet:opClass`\n *\n * @example\n * ```ts\n * toKey('chat', 'relay:write')\n * // => 'chat:relay:write'\n * ```\n */\nexport function toKey(napplet: string, opClass: string): string {\n return `${napplet}:${opClass}`;\n}\n\n/**\n * Map a rule Action to the caller-facing Decision.\n *\n * - `'flag'` → `'pass'` (caller dispatches + emits audit event)\n * - `'block'` → `'reject'` (caller drops the operation)\n * - `'ignore'` → `'pass'` (caller dispatches silently)\n */\nfunction actionToDecision(action: Action): Decision {\n if (action === 'block') return 'reject';\n return 'pass'; // 'flag' and 'ignore' both pass\n}\n\n/**\n * Evaluate a single firewall observation and return the access decision.\n *\n * PURE: no wall-clock reads (no system time APIs), no I/O, no mutation.\n * All time comes from `observation.now`. The original `config` and `state`\n * are NEVER modified — every `newState` is returned via immutable spread.\n *\n * ## Precedence order (A1 — POLICY-03, first-match-wins, most→least specific)\n *\n * 1. **Per-napplet policy** (`allow` / `deny` / `ask`) — hard override for the dTag.\n * `allow` → pass (bypass everything); `deny` → reject (block); `ask` → prompt.\n * Policy returns do NOT advance any counters; newState = input state.\n *\n * 2. **Init-burst guard** — if `observation.initElapsedMs` is defined and less than\n * `config.burstGuard.windowMs`, the burst counter for this napplet is advanced.\n * If the count exceeds `config.burstGuard.maxOps`, the burst action fires\n * (default `block`). The advanced burst counter is returned in newState.\n *\n * 3. **Content matchers** — `config.matchers` are evaluated in order; the FIRST\n * matcher whose declared conditions (opClass, kinds, size, focus, msSinceFocusGain)\n * ALL hold fires its action. Matchers do NOT advance the token bucket.\n *\n * 4. **Per-napplet × op-class rate limit** (`config.napplets[napplet].rateLimits[opClass]`)\n * with ruleId `'rate:opclass'`.\n *\n * 5. **Per-napplet global rate fallback** (`config.napplets[napplet].globalRate`)\n * for op-classes with no specific rateLimits entry. ruleId `'rate:global'`.\n *\n * 6. **Global default rate** (`config.defaultRate`) — applied when no napplet-specific\n * rule exists. ruleId `'rate:default'`.\n *\n * ## Unfocused multiplier (A2 — FOCUS-02)\n *\n * When `observation.focused === false`, the effective bucket capacity is tightened:\n * `effectiveCapacity = limit.capacity * config.unfocusedMultiplier`.\n * Refill rate is derived as `effectiveCapacity / windowMs` so the drip also tightens\n * proportionally. The bucket KEY stays stable (`napplet:opClass`, no focus suffix).\n * Because the multiplier is always `> 0`, an unfocused napplet's budget is reduced\n * but never zero — **focus alone NEVER hard-blocks**.\n *\n * @param config - Immutable firewall configuration\n * @param state - Current ephemeral counter state (never mutated)\n * @param observation - Normalized observation (the sole input surface — CORE-02)\n * @returns Decision result with updated counter state\n *\n * @example\n * ```ts\n * import { evaluate, defaultConfig, createState } from '@kehto/firewall';\n *\n * const config = defaultConfig();\n * const state = createState();\n * const obs = {\n * napplet: 'chat',\n * opClass: 'relay:write',\n * focused: true,\n * now: injectedTimestamp, // caller supplies time; evaluate() never reads a clock\n * };\n *\n * const result = evaluate(config, state, obs);\n * // result.decision === 'pass'\n * // result.newState has an updated token bucket for 'chat:relay:write'\n * ```\n */\nexport function evaluate(\n config: FirewallConfig,\n state: FirewallState,\n observation: Observation,\n): EvaluateResult {\n const { napplet, opClass, now } = observation;\n\n const nappletRules = config.napplets[napplet];\n const policy = nappletRules?.policy;\n\n if (policy === 'allow') {\n return {\n decision: 'pass',\n action: 'ignore',\n ruleId: 'policy:allow',\n reason: `napplet ${napplet} has allow policy — bypassing all checks`,\n newState: state,\n };\n }\n\n if (policy === 'deny') {\n return {\n decision: 'reject',\n action: 'block',\n ruleId: 'policy:deny',\n reason: `napplet ${napplet} has deny policy — always rejected`,\n newState: state,\n };\n }\n\n if (policy === 'ask') {\n return {\n decision: 'prompt',\n action: 'block',\n ruleId: 'policy:ask',\n reason: `napplet ${napplet} has ask policy — prompting for consent`,\n newState: state,\n };\n }\n\n const { initElapsedMs } = observation;\n\n if (initElapsedMs !== undefined && initElapsedMs < config.burstGuard.windowMs) {\n // Advance the burst counter for this napplet\n const existingBurst: BurstCounter | undefined = state.bursts[napplet];\n const newBurst: BurstCounter = {\n count: (existingBurst?.count ?? 0) + 1,\n windowStart: existingBurst?.windowStart ?? now,\n };\n\n const newBursts = { ...state.bursts, [napplet]: newBurst };\n\n if (newBurst.count > config.burstGuard.maxOps) {\n const burstAction = config.burstGuard.action;\n return {\n decision: actionToDecision(burstAction),\n action: burstAction,\n ruleId: 'burst',\n reason: `napplet ${napplet} exceeded init-burst limit (${newBurst.count} > ${config.burstGuard.maxOps} ops within ${config.burstGuard.windowMs}ms)`,\n newState: { ...state, bursts: newBursts },\n };\n }\n\n // Burst count advanced but not exceeded — continue to next tier with updated bursts\n // We update state to carry the burst counter forward\n state = { ...state, bursts: newBursts };\n }\n\n for (const matcher of config.matchers) {\n if (matcher.opClass !== undefined && matcher.opClass !== opClass) continue;\n\n // Check kinds condition (observation.kind must be in the set)\n if (matcher.kinds !== undefined) {\n if (observation.kind === undefined) continue;\n if (!matcher.kinds.includes(observation.kind)) continue;\n }\n\n if (matcher.minSize !== undefined) {\n if (observation.size === undefined) continue;\n if (observation.size < matcher.minSize) continue;\n }\n\n if (matcher.focused !== undefined && matcher.focused !== observation.focused) continue;\n\n if (matcher.maxMsSinceFocusGain !== undefined) {\n if (observation.msSinceFocusGain === undefined) continue;\n if (observation.msSinceFocusGain > matcher.maxMsSinceFocusGain) continue;\n }\n\n // All conditions satisfied — this matcher fires\n const matcherAction = matcher.action;\n return {\n decision: actionToDecision(matcherAction),\n action: matcherAction,\n ruleId: `matcher:${matcher.id}`,\n reason: `content matcher '${matcher.id}' fired`,\n newState: state,\n };\n }\n\n // Resolve the applicable RateLimit (precedence: op-class > global > default)\n let rateLimit = config.defaultRate;\n let rateLimitRuleId = 'rate:default';\n\n if (nappletRules) {\n const opClassLimit = nappletRules.rateLimits[opClass];\n if (opClassLimit) {\n rateLimit = opClassLimit;\n rateLimitRuleId = 'rate:opclass';\n } else if (nappletRules.globalRate) {\n rateLimit = nappletRules.globalRate;\n rateLimitRuleId = 'rate:global';\n }\n }\n\n // Apply focus multiplier to capacity (A2 — never zeroes the budget)\n const effectiveCapacity = observation.focused\n ? rateLimit.capacity\n : rateLimit.capacity * config.unfocusedMultiplier;\n\n // Refill rate derived from effective capacity (drip tightens proportionally with focus)\n const refillRatePerMs = effectiveCapacity / rateLimit.windowMs;\n\n // Lazy-init bucket: absent key means fresh napplet starting full at `now`\n const bucketKey = toKey(napplet, opClass);\n const existingBucket: Bucket | undefined = state.buckets[bucketKey];\n\n // RESEARCH Pattern 2 token-bucket math:\n // lazy `lastRefill || now` init — a fresh key starts FULL at effectiveCapacity at now\n const lastRefill = existingBucket?.lastRefill ?? now;\n const initialTokens = existingBucket?.tokens ?? effectiveCapacity;\n\n // Clamp negative clock skew to 0 (T-80-03 mitigation)\n const elapsed = Math.max(0, now - lastRefill);\n\n // Refill tokens up to effectiveCapacity, keep fractional tokens\n const tokens = Math.min(effectiveCapacity, initialTokens + elapsed * refillRatePerMs);\n\n if (tokens >= 1) {\n // Within budget — spend one token and pass\n // ruleId encodes the resolution path so callers and tests can observe which tier resolved\n const nextBucket: Bucket = { tokens: tokens - 1, lastRefill: now };\n return {\n decision: 'pass',\n action: 'ignore',\n ruleId: rateLimitRuleId,\n reason: `within budget (${rateLimitRuleId})`,\n newState: {\n ...state,\n buckets: { ...state.buckets, [bucketKey]: nextBucket },\n },\n };\n } else {\n // Exceeded budget — apply the rule's exceed-action; still update bucket (no token spent)\n const nextBucket: Bucket = { tokens, lastRefill: now };\n const exceedAction = rateLimit.action;\n return {\n decision: actionToDecision(exceedAction),\n action: exceedAction,\n ruleId: rateLimitRuleId,\n reason: `rate limit exceeded (${rateLimitRuleId}): ${tokens.toFixed(4)} tokens available, need 1`,\n newState: {\n ...state,\n buckets: { ...state.buckets, [bucketKey]: nextBucket },\n },\n };\n }\n}\n","\nimport type { Action, FirewallConfig, FirewallState } from './types.js';\n\n/**\n * Default exceed-action for rate limits: `flag` (pass + audit).\n *\n * Conservative, allow-and-audit default — operations are never silently blocked\n * on first deployment. The shell hears about violations via audit events without\n * disrupting the napplet experience (CORE-04).\n *\n * @example\n * ```ts\n * const limit: RateLimit = { capacity: 60, windowMs: 60_000, action: DEFAULT_EXCEED_ACTION };\n * ```\n */\nexport const DEFAULT_EXCEED_ACTION: Action = 'flag';\n\n/**\n * Default action for the init-burst guard: `block`.\n *\n * The burst guard is the one documented exception to the conservative `flag`\n * default. A napplet that fires more than `DEFAULT_BURST_MAX_OPS` operations\n * within its initialization window is almost certainly misbehaving and should\n * be stopped immediately (BURST-02).\n *\n * @example\n * ```ts\n * const guard: BurstGuard = { windowMs: DEFAULT_BURST_WINDOW_MS, maxOps: DEFAULT_BURST_MAX_OPS, action: DEFAULT_BURST_ACTION };\n * ```\n */\nexport const DEFAULT_BURST_ACTION: Action = 'block';\n\n/**\n * Fractional capacity multiplier applied to unfocused napplets: `0.25`.\n *\n * Scales the effective token-bucket capacity for napplets that are not the\n * currently focused window. Chosen at 1/4 so background napplets can still\n * make legitimate low-rate requests while a sustained high-rate attack from a\n * background napplet is throttled quickly.\n *\n * MUST remain strictly greater than 0 — focus alone must NEVER hard-block a\n * napplet (FOCUS-02 invariant). A value of 0 would be equivalent to an\n * unconditional deny for all unfocused napplets.\n *\n * @example\n * ```ts\n * const effectiveCapacity = limit.capacity * (obs.focused ? 1 : DEFAULT_UNFOCUSED_MULTIPLIER);\n * ```\n */\nexport const DEFAULT_UNFOCUSED_MULTIPLIER = 0.25;\n\n/**\n * Default token-bucket capacity: 60 operations per window.\n *\n * 60 ops/minute is generous for typical napplet relay interactions (reading\n * profiles, publishing notes) while providing a clear ceiling against rapid\n * automated relay flooding. Conservative without being restrictive for\n * well-behaved napplets (CORE-04).\n *\n * @example\n * ```ts\n * const limit: RateLimit = { capacity: DEFAULT_RATE_CAPACITY, windowMs: DEFAULT_RATE_WINDOW_MS, action: DEFAULT_EXCEED_ACTION };\n * ```\n */\nexport const DEFAULT_RATE_CAPACITY = 60;\n\n/**\n * Default token-bucket window: 60 000 ms (1 minute).\n *\n * A 1-minute rolling window pairs with `DEFAULT_RATE_CAPACITY` (60 ops) to\n * give each napplet 1 op/second sustained throughput — sufficient for relay\n * reads, publish flows, and intent invocations under normal usage (CORE-04).\n *\n * @example\n * ```ts\n * const refillRatePerMs = DEFAULT_RATE_CAPACITY / DEFAULT_RATE_WINDOW_MS; // 0.001 ops/ms\n * ```\n */\nexport const DEFAULT_RATE_WINDOW_MS = 60_000;\n\n/**\n * Default init-burst guard window: 3 000 ms (3 seconds).\n *\n * The initialization window covers the first few seconds after a napplet loads.\n * Legitimate napplets bootstrap with a small number of setup requests; a napplet\n * that fires many ops in the first 3 seconds is exhibiting burst-attack behavior\n * (BURST-01, BURST-02).\n *\n * @example\n * ```ts\n * const guard: BurstGuard = { windowMs: DEFAULT_BURST_WINDOW_MS, maxOps: DEFAULT_BURST_MAX_OPS, action: DEFAULT_BURST_ACTION };\n * ```\n */\nexport const DEFAULT_BURST_WINDOW_MS = 3_000;\n\n/**\n * Default init-burst guard operation cap: 20 operations.\n *\n * 20 ops within the first 3 seconds is more than enough for any legitimate\n * initialization sequence (subscribe, fetch profile, query relays). Exceeding\n * this indicates automated request flooding and triggers the `block` action\n * (BURST-02). Value chosen to tolerate brief SDK setup bursts while stopping\n * malicious behavior.\n *\n * @example\n * ```ts\n * const guard: BurstGuard = { windowMs: DEFAULT_BURST_WINDOW_MS, maxOps: DEFAULT_BURST_MAX_OPS, action: DEFAULT_BURST_ACTION };\n * ```\n */\nexport const DEFAULT_BURST_MAX_OPS = 20;\n\n/**\n * Assemble the built-in default FirewallConfig.\n *\n * Returns a fresh config applying conservative rate/burst limits to every\n * napplet out of the box. No per-napplet rules or content matchers are\n * pre-configured — those are added via the mutation functions in config.ts.\n *\n * The exceed-action default is `flag` (CORE-04 — allow + audit). The\n * init-burst guard default is `block` (BURST-02 — the documented exception).\n *\n * @returns A new FirewallConfig with conservative built-in limits\n *\n * @example\n * ```ts\n * const config = defaultConfig();\n * // config.defaultRate.action === 'flag'\n * // config.burstGuard.action === 'block'\n * // config.unfocusedMultiplier === 0.25\n * ```\n */\nexport function defaultConfig(): FirewallConfig {\n return {\n napplets: {},\n matchers: [],\n burstGuard: {\n windowMs: DEFAULT_BURST_WINDOW_MS,\n maxOps: DEFAULT_BURST_MAX_OPS,\n action: DEFAULT_BURST_ACTION,\n },\n defaultRate: {\n capacity: DEFAULT_RATE_CAPACITY,\n windowMs: DEFAULT_RATE_WINDOW_MS,\n action: DEFAULT_EXCEED_ACTION,\n },\n unfocusedMultiplier: DEFAULT_UNFOCUSED_MULTIPLIER,\n };\n}\n\n/**\n * Create an empty FirewallState with no token-bucket or burst counters.\n *\n * Returns a fresh ephemeral counter state. Counter state is never persisted\n * (Phase 81 concern) — it is reset on reload and rebuilt as observations arrive.\n *\n * Mirrors the `createState()` factory pattern from @kehto/acl/mutations.ts.\n *\n * @returns A new FirewallState with empty buckets and bursts maps\n *\n * @example\n * ```ts\n * const state = createState();\n * // { buckets: {}, bursts: {} }\n * ```\n */\nexport function createState(): FirewallState {\n return { buckets: {}, bursts: {} };\n}\n","/**\n * @kehto/firewall — Pure config mutation functions and serialization.\n *\n * Every mutation function takes a FirewallConfig and returns a NEW FirewallConfig.\n * The original config is never modified. No side effects, no I/O.\n *\n * Mirrors the role of @kehto/acl's mutations.ts: immutable spread-return mutations\n * (grant/revoke pattern), plain JSON.stringify serialize, and defensive deserialize\n * with shape validation and defaultConfig() fallback (V5 input validation — T-80-01).\n */\n\nimport type {\n FirewallConfig,\n NappletRules,\n RateLimit,\n ContentMatcher,\n NappletPolicy,\n Action,\n} from './types.js';\nimport { defaultConfig } from './defaults.js';\n\n/**\n * Return the existing NappletRules for `napplet`, or a fresh empty entry.\n * Internal helper — not exported. Mirrors acl's `getEntry` (mutations.ts:33-42).\n */\nfunction getNapplet(config: FirewallConfig, napplet: string): NappletRules {\n const existing = config.napplets[napplet];\n if (existing) return existing;\n return { rateLimits: {} };\n}\n\n/**\n * Set the hard policy posture for a specific napplet (dTag).\n *\n * Returns a new FirewallConfig with `napplets[napplet].policy` set to `policy`.\n * If the napplet has no existing entry, a fresh entry is created.\n * The original config is never modified.\n *\n * @param config - Current firewall config\n * @param napplet - Napplet dTag to configure\n * @param policy - Policy posture ('allow' | 'deny' | 'ask')\n * @returns New FirewallConfig with the policy set\n *\n * @example\n * ```ts\n * const cfg2 = setPolicy(cfg, 'chat', 'deny');\n * // cfg2.napplets['chat'].policy === 'deny'\n * // cfg is unchanged\n * ```\n */\nexport function setPolicy(\n config: FirewallConfig,\n napplet: string,\n policy: NappletPolicy,\n): FirewallConfig {\n const entry = getNapplet(config, napplet);\n return {\n ...config,\n napplets: {\n ...config.napplets,\n [napplet]: { ...entry, policy },\n },\n };\n}\n\n/**\n * Set a per-napplet, per-opClass token-bucket rate limit.\n *\n * Returns a new FirewallConfig with `napplets[napplet].rateLimits[opClass]`\n * set to `limit`. If the napplet has no existing entry, a fresh entry is created.\n * The original config is never modified.\n *\n * @param config - Current firewall config\n * @param napplet - Napplet dTag to configure\n * @param opClass - Operation class string (e.g. 'relay:write', 'outbox:write')\n * @param limit - Token-bucket rate limit to apply\n * @returns New FirewallConfig with the rate limit set\n *\n * @example\n * ```ts\n * const cfg2 = setRateLimit(cfg, 'chat', 'relay:write', { capacity: 10, windowMs: 5000, action: 'block' });\n * // cfg2.napplets['chat'].rateLimits['relay:write'].capacity === 10\n * ```\n */\nexport function setRateLimit(\n config: FirewallConfig,\n napplet: string,\n opClass: string,\n limit: RateLimit,\n): FirewallConfig {\n const entry = getNapplet(config, napplet);\n return {\n ...config,\n napplets: {\n ...config.napplets,\n [napplet]: {\n ...entry,\n rateLimits: { ...entry.rateLimits, [opClass]: limit },\n },\n },\n };\n}\n\n/**\n * Set a per-napplet global fallback rate limit (RATE-03).\n *\n * The global rate is applied to all op-classes for this napplet that lack a\n * specific `rateLimits` entry. This provides a single budget covering all\n * unlisted operations rather than relying solely on the config-wide `defaultRate`.\n *\n * Returns a new FirewallConfig with `napplets[napplet].globalRate` set.\n * The original config is never modified.\n *\n * @param config - Current firewall config\n * @param napplet - Napplet dTag to configure\n * @param limit - Global fallback rate limit for this napplet\n * @returns New FirewallConfig with the global rate set\n *\n * @example\n * ```ts\n * const cfg2 = setGlobalRate(cfg, 'chat', { capacity: 30, windowMs: 30000, action: 'flag' });\n * // cfg2.napplets['chat'].globalRate is set; other napplets unaffected\n * ```\n */\nexport function setGlobalRate(\n config: FirewallConfig,\n napplet: string,\n limit: RateLimit,\n): FirewallConfig {\n const entry = getNapplet(config, napplet);\n return {\n ...config,\n napplets: {\n ...config.napplets,\n [napplet]: { ...entry, globalRate: limit },\n },\n };\n}\n\n/**\n * Append a content matcher to the firewall config.\n *\n * Matchers are evaluated in order; the first match wins (POLICY-03). Returns a\n * new FirewallConfig with `matcher` appended to the end of `config.matchers`.\n * The original config is never modified.\n *\n * @param config - Current firewall config\n * @param matcher - Content matcher to append\n * @returns New FirewallConfig with the matcher appended\n *\n * @example\n * ```ts\n * const cfg2 = addMatcher(cfg, { id: 'delete-spam', opClass: 'relay:write', kinds: [5], action: 'block' });\n * // cfg2.matchers.length === cfg.matchers.length + 1\n * ```\n */\nexport function addMatcher(config: FirewallConfig, matcher: ContentMatcher): FirewallConfig {\n return { ...config, matchers: [...config.matchers, matcher] };\n}\n\n/**\n * Serialize a FirewallConfig to a JSON string.\n *\n * Pure function — no I/O. The persistence adapter in @kehto/shell (Phase 81)\n * uses this to write config to localStorage or other backends.\n *\n * @param config - Firewall config to serialize\n * @returns JSON string representation\n *\n * @example\n * ```ts\n * const json = serialize(config);\n * localStorage.setItem('kehto:firewall', json);\n * ```\n */\nexport function serialize(config: FirewallConfig): string {\n return JSON.stringify(config);\n}\n\nconst VALID_ACTIONS: Action[] = ['flag', 'block', 'ignore'];\n\nfunction isValidAction(v: unknown): v is Action {\n return VALID_ACTIONS.includes(v as Action);\n}\n\nfunction isValidRateLimit(v: unknown): v is RateLimit {\n if (typeof v !== 'object' || v === null) return false;\n const r = v as Record<string, unknown>;\n return (\n typeof r['capacity'] === 'number' &&\n isFinite(r['capacity'] as number) &&\n typeof r['windowMs'] === 'number' &&\n isFinite(r['windowMs'] as number) &&\n isValidAction(r['action'])\n );\n}\n\nfunction isValidBurstGuard(v: unknown): boolean {\n if (typeof v !== 'object' || v === null) return false;\n const b = v as Record<string, unknown>;\n return (\n typeof b['windowMs'] === 'number' &&\n isFinite(b['windowMs'] as number) &&\n typeof b['maxOps'] === 'number' &&\n isFinite(b['maxOps'] as number) &&\n isValidAction(b['action'])\n );\n}\n\nfunction isValidContentMatcher(v: unknown): v is ContentMatcher {\n if (typeof v !== 'object' || v === null) return false;\n const m = v as Record<string, unknown>;\n if (typeof m['id'] !== 'string') return false;\n if (!isValidAction(m['action'])) return false;\n // Optional fields — if present, validate types\n if ('opClass' in m && typeof m['opClass'] !== 'string') return false;\n if ('minSize' in m && typeof m['minSize'] !== 'number') return false;\n if ('focused' in m && typeof m['focused'] !== 'boolean') return false;\n if ('maxMsSinceFocusGain' in m && typeof m['maxMsSinceFocusGain'] !== 'number') return false;\n if ('kinds' in m) {\n if (!Array.isArray(m['kinds'])) return false;\n if (!(m['kinds'] as unknown[]).every((k) => typeof k === 'number')) return false;\n }\n return true;\n}\n\nfunction isValidNappletRules(v: unknown): v is NappletRules {\n if (typeof v !== 'object' || v === null) return false;\n const n = v as Record<string, unknown>;\n // rateLimits must be an object\n if (typeof n['rateLimits'] !== 'object' || n['rateLimits'] === null) return false;\n for (const limit of Object.values(n['rateLimits'] as Record<string, unknown>)) {\n if (!isValidRateLimit(limit)) return false;\n }\n // Optional policy\n if ('policy' in n) {\n const p = n['policy'];\n if (p !== 'allow' && p !== 'deny' && p !== 'ask') return false;\n }\n // Optional globalRate\n if ('globalRate' in n && !isValidRateLimit(n['globalRate'])) return false;\n return true;\n}\n\n/**\n * Deserialize a FirewallConfig from a JSON string.\n *\n * Defensive parse: tries JSON.parse, then shape-validates every top-level and\n * nested field (napplets map, matchers array, burstGuard, defaultRate, and\n * unfocusedMultiplier). Rebuilds a validated config from the parsed data.\n *\n * On ANY failure (invalid JSON, missing fields, wrong types, invalid action\n * values, malformed nested structures) falls through to `defaultConfig()`.\n * This function NEVER throws.\n *\n * Security control for T-80-01 (Tampering — persisted config string):\n * the config string is the only untrusted input to @kehto/firewall. Poisoned\n * or malformed strings always produce a safe, valid default config.\n *\n * @param json - JSON string to parse (may be untrusted)\n * @returns Parsed and validated FirewallConfig, or defaultConfig() on any failure\n *\n * @example\n * ```ts\n * const json = localStorage.getItem('kehto:firewall') ?? '';\n * const config = deserialize(json);\n * // config is always a valid FirewallConfig — never throws\n * ```\n */\nexport function deserialize(json: string): FirewallConfig {\n try {\n const parsed = JSON.parse(json);\n\n // Top-level shape check\n if (\n typeof parsed !== 'object' ||\n parsed === null ||\n typeof parsed.napplets !== 'object' ||\n parsed.napplets === null ||\n !Array.isArray(parsed.matchers) ||\n !isValidBurstGuard(parsed.burstGuard) ||\n !isValidRateLimit(parsed.defaultRate) ||\n typeof parsed.unfocusedMultiplier !== 'number' ||\n !isFinite(parsed.unfocusedMultiplier)\n ) {\n return defaultConfig();\n }\n\n const napplets: Record<string, NappletRules> = {};\n for (const [key, value] of Object.entries(parsed.napplets as Record<string, unknown>)) {\n if (!isValidNappletRules(value)) return defaultConfig();\n // The type guard above narrows `value` to NappletRules.\n const raw = value;\n const rateLimits: Record<string, RateLimit> = {};\n for (const [opClass, limit] of Object.entries(raw.rateLimits)) {\n rateLimits[opClass] = limit;\n }\n const entry: NappletRules = { rateLimits };\n const withPolicy: NappletRules = raw.policy !== undefined\n ? { ...entry, policy: raw.policy }\n : entry;\n const withGlobalRate: NappletRules = raw.globalRate !== undefined\n ? { ...withPolicy, globalRate: raw.globalRate }\n : withPolicy;\n napplets[key] = withGlobalRate;\n }\n\n const matchers: ContentMatcher[] = [];\n for (const item of parsed.matchers as unknown[]) {\n if (!isValidContentMatcher(item)) return defaultConfig();\n matchers.push(item as ContentMatcher);\n }\n\n // Rebuild burstGuard and defaultRate from validated parsed data\n const bg = parsed.burstGuard as Record<string, unknown>;\n const dr = parsed.defaultRate as Record<string, unknown>;\n\n return {\n napplets,\n matchers,\n burstGuard: {\n windowMs: bg['windowMs'] as number,\n maxOps: bg['maxOps'] as number,\n action: bg['action'] as Action,\n },\n defaultRate: {\n capacity: dr['capacity'] as number,\n windowMs: dr['windowMs'] as number,\n action: dr['action'] as Action,\n },\n unfocusedMultiplier: parsed.unfocusedMultiplier as number,\n };\n } catch {\n // Invalid JSON — fall through to default\n }\n return defaultConfig();\n}\n","/**\n * @napplet/core -- JSON envelope types for the napplet-shell wire protocol.\n *\n * Defines the base types for the JSON envelope wire format introduced\n * in NIP-5D v4. All messages between napplet and shell use a `type`\n * field as a discriminant in `domain.action` format.\n *\n * @example\n * ```ts\n * import type { NappletMessage, NapDomain, NapProtocolId, ShellSupports } from '@napplet/core';\n * import { NAP_DOMAINS } from '@napplet/core';\n * ```\n *\n * @packageDocumentation\n */\n\n/**\n * Base interface for all JSON envelope messages exchanged between\n * napplet and shell. The `type` field is a string discriminant\n * in `domain.action` format (e.g., `\"relay.subscribe\"`, `\"storage.get\"`).\n *\n * Concrete message types extend this interface with domain-specific payload fields.\n *\n * @example\n * ```ts\n * const msg: NappletMessage = { type: 'relay.subscribe' };\n *\n * // Concrete message with payload:\n * interface RelaySubscribe extends NappletMessage {\n * type: 'relay.subscribe';\n * filters: NostrFilter[];\n * }\n * ```\n */\nexport interface NappletMessage {\n /** Message type discriminant in \"domain.action\" format (e.g., \"relay.subscribe\", \"storage.get\") */\n type: string;\n}\n\n/**\n * String literal union of the fourteen NAP (Nostr Applet Protocol) domains.\n * Each domain corresponds to a capability namespace that a shell may support.\n *\n * | Domain | Scope |\n * |------------|----------------------------------------------------|\n * | `relay` | NIP-01 relay proxy (subscribe, publish) |\n * | `identity` | Read-only user identity queries |\n * | `storage` | Scoped key-value storage proxy |\n * | `inc` | Inter-napplet communication (INC peer bus) |\n * | `theme` | Theme tokens and appearance settings |\n * | `keys` | Keyboard forwarding and action keybindings |\n * | `media` | Media session control and playback |\n * | `notify` | Shell-rendered notifications |\n * | `config` | Per-napplet declarative configuration |\n * | `resource` | Byte-fetching primitive (URL → Blob) |\n *\n * @example\n * ```ts\n * const domain: NapDomain = 'relay';\n * const isValid = NAP_DOMAINS.includes(domain); // true\n * ```\n */\nexport type NapDomain = 'relay' | 'identity' | 'storage' | 'inc' | 'theme' | 'keys' | 'media' | 'notify' | 'config' | 'resource' | 'cvm' | 'outbox' | 'upload' | 'intent';\n\n/**\n * Runtime-accessible constant array of all NAP domain names.\n * Useful for iteration, validation, and capability enumeration.\n *\n * @example\n * ```ts\n * for (const domain of NAP_DOMAINS) {\n * console.log(`Checking support for: ${domain}`);\n * }\n * ```\n */\nexport const NAP_DOMAINS: readonly NapDomain[] = ['relay', 'identity', 'storage', 'inc', 'theme', 'keys', 'media', 'notify', 'config', 'resource', 'cvm', 'outbox', 'upload', 'intent'] as const;\n\n/**\n * Namespaced capability string for {@link ShellSupports.supports}.\n *\n * Accepts NAP capability prefixes plus bare domain shorthand:\n *\n * | Prefix | Example | Meaning |\n * |---------|---------------------|--------------------------------|\n * | `nap:` | `'nap:relay'` | Shell implements the relay NAP |\n * | `perm:` | `'perm:popups'` | Shell grants popup permission |\n * | *(bare)*| `'relay'` | Shorthand for `'nap:relay'` |\n *\n * Bare strings are valid only for NAP domains. Permissions MUST use the\n * `perm:` prefix.\n *\n * @example\n * ```ts\n * const cap: NamespacedCapability = 'nap:relay';\n * const bare: NamespacedCapability = 'relay'; // shorthand OK\n * const perm: NamespacedCapability = 'perm:popups';\n * ```\n */\nexport type NamespacedCapability =\n | NapDomain\n | `nap:${NapDomain}`\n | `perm:${string}`;\n\n/**\n * Numbered NAP protocol identifier for napplet-to-napplet message semantics.\n *\n * NAP-WORD interfaces are discovered with the first `supports()` argument.\n * NAP-NN message protocols are negotiated with the optional second argument.\n *\n * @example\n * ```ts\n * const protocol: NapProtocolId = 'NAP-01';\n * window.napplet.shell.supports('inc', protocol);\n * ```\n */\nexport type NapProtocolId = `NAP-${number}`;\n\n/** Numbered protocol identifier accepted by shell.supports(). */\nexport type ProtocolId = NapProtocolId;\n\n/**\n * Interface for the shell capability query API.\n * Allows napplets to check whether the shell supports a NAP domain,\n * a permission, or a numbered NAP protocol at runtime.\n *\n * @example\n * ```ts\n * // NAP domain queries (bare shorthand or prefixed):\n * shell.supports('relay'); // shorthand for 'nap:relay'\n * shell.supports('nap:storage'); // explicit NAP prefix\n *\n * // Permission queries:\n * shell.supports('perm:popups'); // popup permission\n *\n * // Numbered protocol queries over an interface:\n * shell.supports('inc', 'NAP-01');\n * ```\n */\nexport interface ShellSupports {\n /** Check whether the shell supports a NAP capability, permission, or numbered protocol. */\n supports(capability: NamespacedCapability, protocol?: ProtocolId): boolean;\n}\n","/**\n * @napplet/core -- NAP registration and message dispatch infrastructure.\n *\n * Provides a NAP-agnostic mechanism for NAP modules (relay, identity, storage, inc)\n * to register their domain string and a message handler function. Inbound messages\n * are dispatched to the correct NAP handler based on the domain prefix extracted\n * from `message.type` (the part before the first `.`).\n *\n * Use the {@link createDispatch} factory for isolated registries (testing,\n * multi-instance), or the module-level singleton exports ({@link registerNap},\n * {@link dispatch}, {@link getRegisteredDomains}) for the common single-registry case.\n *\n * @example\n * ```ts\n * import { registerNap, dispatch } from '@napplet/core';\n *\n * // NAP module registers its domain:\n * registerNap('relay', (msg) => {\n * console.log('relay handler received:', msg.type);\n * });\n *\n * // Dispatch routes by domain prefix:\n * dispatch({ type: 'relay.subscribe' }); // => true, calls relay handler\n * dispatch({ type: 'identity.getPublicKey' }); // => false, no identity handler\n * ```\n *\n * @packageDocumentation\n */\n\nimport type { NappletMessage } from './envelope.js';\n\n/**\n * Callback that a NAP module provides to handle messages in its domain.\n *\n * @param message - The envelope message whose `type` matched this handler's domain.\n *\n * @example\n * ```ts\n * const handler: NapHandler = (msg) => {\n * console.log('Received:', msg.type);\n * };\n * ```\n */\nexport type NapHandler = (message: NappletMessage) => void;\n\n/**\n * Shape returned by {@link createDispatch}. Contains the three dispatch\n * operations backed by a shared, isolated handler registry.\n */\nexport interface NapDispatch {\n /** Register a NAP domain handler. Throws if the domain is already registered. */\n registerNap: (domain: string, handler: NapHandler) => void;\n /** Dispatch a message to the handler matching its domain prefix. Returns `true` if handled. */\n dispatch: (message: NappletMessage) => boolean;\n /** Return all currently registered domain strings. */\n getRegisteredDomains: () => string[];\n}\n\n/**\n * Create an isolated NAP dispatch registry.\n *\n * Each call returns a fresh `{ registerNap, dispatch, getRegisteredDomains }`\n * backed by its own `Map<string, NapHandler>`. Use this factory for\n * testability or when multiple independent dispatch registries are needed.\n *\n * @returns A fresh dispatch instance with its own handler map.\n *\n * @example\n * ```ts\n * import { createDispatch } from '@napplet/core';\n *\n * const { registerNap, dispatch } = createDispatch();\n * registerNap('relay', handleRelayMessage);\n * dispatch({ type: 'relay.subscribe' }); // true\n * ```\n */\nexport function createDispatch(): NapDispatch {\n const handlers = new Map<string, NapHandler>();\n\n /**\n * Register a handler for the given NAP domain.\n *\n * @param domain - The domain string (e.g., `'relay'`, `'identity'`).\n * @param handler - Callback invoked for messages in this domain.\n * @throws {Error} If the domain is already registered.\n *\n * @example\n * ```ts\n * registerNap('identity', (msg) => { /* handle identity.* messages *\\/ });\n * ```\n */\n function registerNap(domain: string, handler: NapHandler): void {\n if (handlers.has(domain)) {\n throw new Error(`NAP domain \"${domain}\" is already registered`);\n }\n handlers.set(domain, handler);\n }\n\n /**\n * Dispatch a message to the handler matching its domain prefix.\n *\n * The domain is extracted from `message.type` by splitting on the first `.`.\n * If the domain portion is empty, has no `.`, or no handler is registered,\n * the function returns `false` without throwing.\n *\n * @param message - The envelope message to dispatch.\n * @returns `true` if a handler was found and called, `false` otherwise.\n *\n * @example\n * ```ts\n * dispatch({ type: 'relay.subscribe' }); // true (if relay handler exists)\n * dispatch({ type: 'unknown.action' }); // false\n * dispatch({ type: 'malformed' }); // false (no dot)\n * ```\n */\n function dispatch(message: NappletMessage): boolean {\n const dotIndex = message.type.indexOf('.');\n if (dotIndex <= 0) return false;\n\n const domain = message.type.slice(0, dotIndex);\n const handler = handlers.get(domain);\n if (!handler) return false;\n\n handler(message);\n return true;\n }\n\n /**\n * Return all currently registered domain strings.\n *\n * @returns Array of domain strings in registration order.\n *\n * @example\n * ```ts\n * getRegisteredDomains(); // ['relay', 'identity']\n * ```\n */\n function getRegisteredDomains(): string[] {\n return Array.from(handlers.keys());\n }\n\n return { registerNap, dispatch, getRegisteredDomains };\n}\n\nconst _default = createDispatch();\n\n/**\n * Register a handler for the given NAP domain on the default registry.\n *\n * @param domain - The domain string (e.g., `'relay'`, `'identity'`).\n * @param handler - Callback invoked for messages in this domain.\n * @throws {Error} If the domain is already registered.\n *\n * @example\n * ```ts\n * import { registerNap } from '@napplet/core';\n * registerNap('relay', (msg) => console.log(msg));\n * ```\n */\nexport const registerNap: NapDispatch['registerNap'] = _default.registerNap;\n\n/**\n * Dispatch a message on the default registry.\n *\n * @param message - The envelope message to dispatch.\n * @returns `true` if a handler was found and called, `false` otherwise.\n *\n * @example\n * ```ts\n * import { dispatch } from '@napplet/core';\n * dispatch({ type: 'relay.subscribe' }); // true if relay handler registered\n * ```\n */\nexport const dispatch: NapDispatch['dispatch'] = _default.dispatch;\n\n/**\n * Return all registered domain strings from the default registry.\n *\n * @returns Array of domain strings.\n *\n * @example\n * ```ts\n * import { getRegisteredDomains } from '@napplet/core';\n * getRegisteredDomains(); // ['relay', 'identity']\n * ```\n */\nexport const getRegisteredDomains: NapDispatch['getRegisteredDomains'] = _default.getRegisteredDomains;\n","/**\n * @napplet/core — Topic constants for the napplet INC-PEER event bus.\n *\n * These constants define the topic strings used in INC-PEER\n * events for shell commands, state operations, audio,\n * and UI coordination.\n */\n\n/**\n * Built-in topic constants for the napplet shell INC-PEER protocol.\n *\n * @example\n * ```ts\n * import { TOPICS } from '@napplet/core';\n *\n * // Open a profile view via INC-PEER\n * shim.publish({ kind: 29003, tags: [['t', TOPICS.PROFILE_OPEN]], content: '{}' });\n * ```\n *\n * ## Topic Prefix Conventions\n *\n * Topic strings follow a prefix convention that signals message direction:\n *\n * | Prefix | Direction | Meaning |\n * |--------|-----------|---------|\n * | `shell:*` | napplet → shell | Commands sent by a napplet to the shell |\n * | `napplet:*` | shell → napplet | Responses/notifications sent by shell to napplet |\n * | `{service}:*` | bidirectional | Service-scoped messages; direction is per-topic |\n *\n * Examples of `{service}:*` prefixes: `auth:*`, `stream:*`, `profile:*`,\n * `wm:*`, `keybinds:*`, `chat:*`, `audio:*`.\n */\nexport const TOPICS = {\n STREAM_CHANNEL_SWITCH: 'stream:channel-switch',\n STREAM_CURRENT_CONTEXT_GET: 'stream:current-context-get',\n STREAM_CURRENT_CONTEXT: 'stream:current-context',\n\n PROFILE_OPEN: 'profile:open',\n\n KEYBINDS_GET: 'keybinds:get-all',\n KEYBINDS_ALL: 'keybinds:all',\n KEYBINDS_UPDATE: 'keybinds:update',\n KEYBINDS_RESET: 'keybinds:reset',\n KEYBINDS_CAPTURE_START: 'keybinds:capture-start',\n KEYBINDS_CAPTURE_END: 'keybinds:capture-end',\n\n WM_FOCUSED_WINDOW_CHANGED: 'wm:focused-window-changed',\n\n CHAT_OPEN_DM: 'chat:open-dm',\n\n} as const;\n\n/** Key type for the TOPICS constant object. */\nexport type TopicKey = keyof typeof TOPICS;\n\n/** Value type for the TOPICS constant object. */\nexport type TopicValue = (typeof TOPICS)[TopicKey];\n","\nimport type { Capability } from '@kehto/acl/capabilities';\nimport type { NapMessage } from '@kehto/acl';\nimport type { AclCheckEvent } from './types.js';\n\n// Re-export NAP capability resolution for consumers who import through enforce.ts\nexport { resolveCapabilitiesNap } from '@kehto/acl';\nexport type { NapMessage } from '@kehto/acl';\n\n/**\n * Result of an enforcement check.\n *\n * @param allowed - Whether the capability check passed\n * @param capability - The capability that was checked (human-readable string)\n * @param reason - Why the decision was reached (v1.7 CLASS-03 / D7). Always set on return.\n */\nexport interface EnforceResult {\n allowed: boolean;\n capability: Capability;\n /**\n * Why the decision was reached. Always set on the return path.\n * Distinct from AclCheckEvent.reason (which is optional for backwards compat).\n */\n reason: 'allowed' | 'capability-missing';\n}\n\n/**\n * Identity lookup function type — resolves a pubkey to its full identity.\n * Provided by sessionRegistry at runtime.\n */\nexport type IdentityResolver = (pubkey: string) => { dTag: string; aggregateHash: string } | undefined;\n\n/**\n * ACL check function type — performs the actual capability check.\n * Provided by @kehto/acl's check() at runtime, or by the legacy aclStore.check().\n */\nexport type AclChecker = (pubkey: string, dTag: string, aggregateHash: string, capability: Capability) => boolean;\n\n/**\n * Enforcement gate configuration.\n *\n * @param checkAcl - The ACL check function (wraps @kehto/acl or legacy aclStore)\n * @param resolveIdentity - Maps pubkey to full identity (dTag, aggregateHash)\n * @param onAclCheck - Optional audit callback. Called on every enforce() check\n * with the identity, capability, and decision.\n */\nexport interface EnforceConfig {\n checkAcl: AclChecker;\n resolveIdentity: IdentityResolver;\n onAclCheck?: (event: AclCheckEvent) => void;\n}\n\n/**\n * Create an enforcement gate with the given configuration.\n *\n * Returns a function that checks a single capability for a given pubkey.\n * Every call is logged to the audit callback.\n *\n * @param config - Enforcement configuration with ACL checker, identity resolver, and audit hooks\n * @returns An enforce function that checks capabilities and logs decisions\n *\n * @example\n * ```ts\n * const gate = createEnforceGate({\n * checkAcl: aclStore.check,\n * resolveIdentity: (pk) => nappKeyRegistry.getEntry(pk),\n * onAclCheck: hooks.onAclCheck,\n * });\n * const result = gate('abc123...', 'relay:write');\n * // result.allowed === true | false\n * ```\n */\nexport function createEnforceGate(config: EnforceConfig): (pubkey: string, capability: Capability, message?: unknown[]) => EnforceResult {\n const { checkAcl, resolveIdentity, onAclCheck } = config;\n\n return function enforce(pubkey: string, capability: Capability, message?: unknown[]): EnforceResult {\n const entry = resolveIdentity(pubkey);\n const dTag = entry?.dTag ?? '';\n const aggregateHash = entry?.aggregateHash ?? '';\n\n const allowed = checkAcl(pubkey, dTag, aggregateHash, capability);\n\n // Audit logging — every check, both allows and denials\n const identity = { pubkey, dTag, hash: aggregateHash };\n const decision = allowed ? 'allow' as const : 'deny' as const;\n const reason = allowed ? 'allowed' as const : 'capability-missing' as const;\n\n if (onAclCheck) {\n onAclCheck({ identity, capability, decision, message, reason });\n }\n\n return { allowed, capability, reason };\n };\n}\n\n/**\n * Enforcement gate configuration for NIP-5D NAP handlers.\n * Uses windowId for identity resolution instead of pubkey (which is '' in NIP-5D sessions).\n *\n * @param checkAcl - The ACL check function\n * @param resolveIdentityByWindowId - Maps windowId to identity (dTag, aggregateHash).\n * @param onAclCheck - Optional audit callback, called on every enforceNap() check\n */\nexport interface NapEnforceConfig {\n checkAcl: AclChecker;\n resolveIdentityByWindowId: (windowId: string) => { dTag: string; aggregateHash: string } | undefined;\n onAclCheck?: (event: AclCheckEvent) => void;\n}\n\n/**\n * Create an enforcement gate for NIP-5D NAP message handlers.\n *\n * Unlike createEnforceGate (which resolves identity by pubkey), this factory\n * resolves identity by windowId — necessary for NIP-5D sessions where pubkey is ''.\n *\n * @param config - NAP enforcement configuration\n * @returns An enforceNap function that resolves identity by windowId and\n * delegates to the ACL check.\n *\n * @example\n * ```ts\n * const gate = createNapEnforceGate({\n * checkAcl: aclStore.check,\n * resolveIdentityByWindowId: (wid) => sessionRegistry.getEntryByWindowId(wid),\n * onAclCheck: hooks.onAclCheck,\n * });\n * const result = gate('win-1', 'relay:write', { type: 'relay.publish' });\n * // result.allowed === true | false\n * ```\n */\nexport function createNapEnforceGate(config: NapEnforceConfig): (windowId: string, capability: Capability, message?: NapMessage) => EnforceResult {\n const { checkAcl, resolveIdentityByWindowId, onAclCheck } = config;\n\n return function enforceNap(windowId: string, capability: Capability, message?: NapMessage): EnforceResult {\n const entry = resolveIdentityByWindowId(windowId);\n const dTag = entry?.dTag ?? '';\n const aggregateHash = entry?.aggregateHash ?? '';\n\n // NIP-5D: pass empty string for pubkey - toKey() ignores it (uses dTag:hash).\n const allowed = checkAcl('', dTag, aggregateHash, capability);\n\n const identity = { pubkey: '', dTag, hash: aggregateHash };\n const decision = allowed ? 'allow' as const : 'deny' as const;\n const reason = allowed ? 'allowed' as const : 'capability-missing' as const;\n\n if (onAclCheck) {\n onAclCheck({ identity, capability, decision, message, reason });\n }\n\n return { allowed, capability, reason };\n };\n}\n\n/**\n * Format a denial reason string with the standard 'denied:' prefix.\n *\n * @param capability - The denied capability name\n * @returns Formatted denial string, e.g., 'denied: relay:write'\n *\n * @example\n * ```ts\n * formatDenialReason('relay:write')\n * // => 'denied: relay:write'\n * ```\n */\nexport function formatDenialReason(capability: Capability): string {\n return `denied: ${capability}`;\n}\n","\nimport type { SessionEntry, PendingUpdate, PendingUpdateNotifier } from './types.js';\n\n/**\n * Bidirectional registry mapping windowIds to verified napplet pubkeys.\n * Maintained by the runtime after successful AUTH handshakes.\n *\n * @example\n * ```ts\n * const registry = createSessionRegistry();\n * registry.register('win-1', entry);\n * const pubkey = registry.getPubkey('win-1');\n * ```\n */\nexport interface SessionRegistry {\n /** Register a napplet entry, mapping windowId to pubkey and vice versa. */\n register(windowId: string, entry: SessionEntry): void;\n /** Unregister a napplet by windowId, removing both mappings. */\n unregister(windowId: string): void;\n /** Get the pubkey associated with a windowId. */\n getPubkey(windowId: string): string | undefined;\n /** Get the full entry for a napplet pubkey. */\n getEntry(pubkey: string): SessionEntry | undefined;\n /** Get the windowId for a napplet pubkey. */\n getWindowId(pubkey: string): string | undefined;\n /** Check if a windowId has a registered napplet. */\n isRegistered(windowId: string): boolean;\n /** Get all registered napplet entries. */\n getAllEntries(): SessionEntry[];\n /** Get the instance GUID for a window. */\n getInstanceId(windowId: string): string | undefined;\n /** Set a pending update for a window (napplet reconnected with different hash). */\n setPendingUpdate(windowId: string, update: PendingUpdate): void;\n /** Get a pending update for a window. */\n getPendingUpdate(windowId: string): PendingUpdate | undefined;\n /** Clear a pending update for a window. */\n clearPendingUpdate(windowId: string): void;\n /**\n * Get the full entry for a napplet by windowId directly.\n * NIP-5D: Required for sessions where pubkey is '' (identity established via originRegistry).\n * Unlike getEntry(pubkey), this works when pubkey is empty.\n */\n getEntryByWindowId(windowId: string): SessionEntry | undefined;\n /** Clear all registrations and pending updates. */\n clear(): void;\n}\n\n/** @deprecated Use SessionRegistry. Will be removed in v0.9.0. */\nexport type NappKeyRegistry = SessionRegistry;\n\n/**\n * Create a new SessionRegistry instance.\n *\n * @param notifier - Optional callback invoked when pending updates change\n * @returns A SessionRegistry instance\n *\n * @example\n * ```ts\n * const registry = createSessionRegistry((windowId) => {\n * console.log('Pending update changed for', windowId);\n * });\n * ```\n */\nexport function createSessionRegistry(notifier?: PendingUpdateNotifier): SessionRegistry {\n const byWindowId = new Map<string, string>();\n const byPubkey = new Map<string, SessionEntry>();\n const byWindowIdEntry = new Map<string, SessionEntry>();\n const pendingUpdates = new Map<string, PendingUpdate>();\n\n return {\n register(windowId: string, entry: SessionEntry): void {\n byWindowId.set(windowId, entry.pubkey);\n byPubkey.set(entry.pubkey, entry);\n byWindowIdEntry.set(windowId, entry);\n },\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 byWindowIdEntry.delete(windowId);\n pendingUpdates.delete(windowId);\n },\n\n getPubkey(windowId: string): string | undefined {\n return byWindowId.get(windowId);\n },\n\n getEntry(pubkey: string): SessionEntry | undefined {\n return byPubkey.get(pubkey);\n },\n\n getWindowId(pubkey: string): string | undefined {\n return byPubkey.get(pubkey)?.windowId;\n },\n\n isRegistered(windowId: string): boolean {\n return byWindowId.has(windowId);\n },\n\n getAllEntries(): SessionEntry[] {\n return Array.from(byPubkey.values());\n },\n\n getInstanceId(windowId: string): string | undefined {\n const pubkey = byWindowId.get(windowId);\n if (!pubkey) return undefined;\n return byPubkey.get(pubkey)?.instanceId;\n },\n\n getEntryByWindowId(windowId: string): SessionEntry | undefined {\n return byWindowIdEntry.get(windowId);\n },\n\n setPendingUpdate(windowId: string, update: PendingUpdate): void {\n pendingUpdates.set(windowId, update);\n notifier?.(windowId);\n },\n\n getPendingUpdate(windowId: string): PendingUpdate | undefined {\n return pendingUpdates.get(windowId);\n },\n\n clearPendingUpdate(windowId: string): void {\n pendingUpdates.delete(windowId);\n notifier?.(windowId);\n },\n\n clear(): void {\n byWindowId.clear();\n byPubkey.clear();\n byWindowIdEntry.clear();\n pendingUpdates.clear();\n },\n };\n}\n\n/** @deprecated Use createSessionRegistry. Will be removed in v0.9.0. */\nexport const createNappKeyRegistry = createSessionRegistry;\n","/**\n * acl-state.ts — ACL state container with persistence hooks.\n *\n * Wraps @kehto/acl's pure functions with persistence via\n * AclPersistence. No localStorage or DOM references.\n */\n\nimport type { Capability } from '@kehto/acl/capabilities';\nimport type { AclState, Identity } from '@kehto/acl';\nimport {\n createState, check, grant, revoke, block, unblock,\n serialize, deserialize, getQuota,\n CAP_RELAY_READ, CAP_RELAY_WRITE, CAP_CACHE_READ, CAP_CACHE_WRITE,\n CAP_HOTKEY_FORWARD,\n CAP_STATE_READ, CAP_STATE_WRITE,\n toKey,\n} from '@kehto/acl';\nimport type { AclPersistence, AclEntryExternal } from './types.js';\n\nconst CAP_IDENTITY_READ = 1 << 5; // 32 (reclaimed from CAP_SIGN_EVENT)\nconst CAP_KEYS_BIND = 1 << 6; // 64 (reclaimed from CAP_SIGN_NIP04)\nconst CAP_KEYS_FORWARD = 1 << 7; // 128 (reclaimed from CAP_SIGN_NIP44)\nconst CAP_MEDIA_CONTROL = 1 << 10; // 1024\nconst CAP_NOTIFY_SEND = 1 << 11; // 2048\nconst CAP_NOTIFY_CHANNEL = 1 << 12; // 4096\nconst CAP_THEME_READ = 1 << 13; // 8192\nconst CAP_CONFIG_READ = 1 << 14; // 16384 (v1.7 Phase 39 NAP-CONFIG)\nconst CAP_RESOURCE_FETCH = 1 << 15; // 32768 (v1.7 Phase 40 NAP-RESOURCE)\n// 1 << 16 (65536) RETIRED — was identity:decrypt (v1.8); removed as a spec\n// violation. Left as a permanent gap; do NOT reuse this bit (persisted ACL\n// grants are bitfields — reassigning it would silently re-grant old state).\nconst CAP_CVM_CALL = 1 << 17; // 131072 (NAP-CVM ContextVM bridge)\nconst CAP_OUTBOX_READ = 1 << 18; // 262144 (NAP-OUTBOX read-side routing)\nconst CAP_OUTBOX_WRITE = 1 << 19; // 524288 (NAP-OUTBOX shell-signed publish)\nconst CAP_UPLOAD_WRITE = 1 << 20; // 1048576 (NAP-UPLOAD shell-mediated upload)\nconst CAP_INTENT_READ = 1 << 21; // 2097152 (NAP-INTENT archetype introspection)\nconst CAP_INTENT_WRITE = 1 << 22; // 4194304 (NAP-INTENT cross-napplet dispatch)\n\nconst CAP_MAP: Record<Capability, number> = {\n 'relay:read': CAP_RELAY_READ,\n 'relay:write': CAP_RELAY_WRITE,\n 'cache:read': CAP_CACHE_READ,\n 'cache:write': CAP_CACHE_WRITE,\n 'hotkey:forward': CAP_HOTKEY_FORWARD,\n 'state:read': CAP_STATE_READ,\n 'state:write': CAP_STATE_WRITE,\n 'identity:read': CAP_IDENTITY_READ,\n 'keys:bind': CAP_KEYS_BIND,\n 'keys:forward': CAP_KEYS_FORWARD,\n 'media:control': CAP_MEDIA_CONTROL,\n 'notify:send': CAP_NOTIFY_SEND,\n 'notify:channel': CAP_NOTIFY_CHANNEL,\n 'theme:read': CAP_THEME_READ,\n 'config:read': CAP_CONFIG_READ,\n 'resource:fetch': CAP_RESOURCE_FETCH,\n 'cvm:call': CAP_CVM_CALL,\n 'outbox:read': CAP_OUTBOX_READ,\n 'outbox:write': CAP_OUTBOX_WRITE,\n 'upload:write': CAP_UPLOAD_WRITE,\n 'intent:read': CAP_INTENT_READ,\n 'intent:write': CAP_INTENT_WRITE,\n};\n\nconst RUNTIME_CAP_ALL = Object.values(CAP_MAP).reduce((bits, bit) => bits | bit, 0);\n\nfunction capToBit(cap: Capability): number {\n return CAP_MAP[cap] ?? 0;\n}\n\n/** Convert a bitfield to an array of capability strings. */\nfunction bitsToCapabilities(bits: number): Capability[] {\n const result: Capability[] = [];\n for (const [cap, bit] of Object.entries(CAP_MAP)) {\n if (bits & bit) result.push(cap as Capability);\n }\n return result;\n}\n\nfunction toIdentity(pubkey: string, dTag: string, hash: string): Identity {\n return { pubkey, dTag, hash };\n}\n\n/**\n * ACL state container — wraps @kehto/acl's pure functions with\n * persistence and a convenient imperative API.\n *\n * @example\n * ```ts\n * const aclState = createAclState(persistence);\n * aclState.load();\n * const allowed = aclState.check(pubkey, dTag, hash, 'relay:read');\n * ```\n */\nexport interface AclStateContainer {\n check(pubkey: string, dTag: string, aggregateHash: string, capability: Capability): boolean;\n grant(pubkey: string, dTag: string, aggregateHash: string, capability: Capability): void;\n revoke(pubkey: string, dTag: string, aggregateHash: string, capability: Capability): void;\n block(pubkey: string, dTag: string, aggregateHash: string): void;\n unblock(pubkey: string, dTag: string, aggregateHash: string): void;\n isBlocked(pubkey: string, dTag: string, aggregateHash: string): boolean;\n getEntry(pubkey: string, dTag: string, aggregateHash: string): AclEntryExternal | undefined;\n getAllEntries(): AclEntryExternal[];\n getStateQuota(pubkey: string, dTag: string, aggregateHash: string): number;\n persist(): void;\n load(): void;\n clear(): void;\n}\n\n/**\n * Create an ACL state container backed by @kehto/acl and persisted\n * via the given persistence hooks.\n *\n * @param persistence - Storage backend for ACL state\n * @param defaultPolicy - Default ACL policy for unknown identities\n * @returns An AclStateContainer instance\n *\n * @example\n * ```ts\n * const aclState = createAclState(persistence, 'permissive');\n * aclState.load();\n * ```\n */\nexport function createAclState(\n persistence: AclPersistence,\n defaultPolicy: 'permissive' | 'restrictive' = 'permissive',\n): AclStateContainer {\n let state: AclState = createState(defaultPolicy);\n\n function ensureRuntimeDefaultEntry(id: Identity): void {\n if (state.defaultPolicy !== 'permissive') return;\n if (state.entries[toKey(id)]) return;\n state = grant(state, id, RUNTIME_CAP_ALL);\n }\n\n return {\n check(pubkey: string, dTag: string, aggregateHash: string, capability: Capability): boolean {\n const id = toIdentity(pubkey, dTag, aggregateHash);\n return check(state, id, capToBit(capability));\n },\n\n grant(pubkey: string, dTag: string, aggregateHash: string, capability: Capability): void {\n const id = toIdentity(pubkey, dTag, aggregateHash);\n ensureRuntimeDefaultEntry(id);\n state = grant(state, id, capToBit(capability));\n },\n\n revoke(pubkey: string, dTag: string, aggregateHash: string, capability: Capability): void {\n const id = toIdentity(pubkey, dTag, aggregateHash);\n ensureRuntimeDefaultEntry(id);\n state = revoke(state, id, capToBit(capability));\n },\n\n block(pubkey: string, dTag: string, aggregateHash: string): void {\n const id = toIdentity(pubkey, dTag, aggregateHash);\n ensureRuntimeDefaultEntry(id);\n state = block(state, id);\n },\n\n unblock(pubkey: string, dTag: string, aggregateHash: string): void {\n const id = toIdentity(pubkey, dTag, aggregateHash);\n ensureRuntimeDefaultEntry(id);\n state = unblock(state, id);\n },\n\n isBlocked(pubkey: string, dTag: string, aggregateHash: string): boolean {\n const id = toIdentity(pubkey, dTag, aggregateHash);\n // A blocked identity fails all checks — check with all runtime caps.\n // If blocked, check returns false even for all caps\n return !check(state, id, RUNTIME_CAP_ALL) && this.getEntry(pubkey, dTag, aggregateHash)?.blocked === true;\n },\n\n getEntry(pubkey: string, dTag: string, aggregateHash: string): AclEntryExternal | undefined {\n const id = toIdentity(pubkey, dTag, aggregateHash);\n const key = `${id.dTag}:${id.hash}`;\n const entry = state.entries[key];\n if (!entry) return undefined;\n return {\n pubkey: pubkey || '',\n capabilities: bitsToCapabilities(entry.caps),\n blocked: entry.blocked,\n stateQuota: entry.quota,\n };\n },\n\n getAllEntries(): AclEntryExternal[] {\n return Object.entries(state.entries).map(([, entry]) => {\n return {\n pubkey: '',\n capabilities: bitsToCapabilities(entry.caps),\n blocked: entry.blocked,\n stateQuota: entry.quota,\n };\n });\n },\n\n getStateQuota(pubkey: string, dTag: string, aggregateHash: string): number {\n const id = toIdentity(pubkey, dTag, aggregateHash);\n return getQuota(state, id);\n },\n\n persist(): void {\n try {\n persistence.persist(serialize(state));\n } catch { /* persistence is best-effort */ }\n },\n\n load(): void {\n try {\n const raw = persistence.load();\n if (!raw) return;\n state = deserialize(raw);\n } catch {\n state = createState(defaultPolicy);\n }\n },\n\n clear(): void {\n state = createState(defaultPolicy);\n try { persistence.persist(''); } catch { /* best-effort */ }\n },\n };\n}\n","/**\n * firewall-state.ts — Firewall state container with persistence hooks.\n *\n * Wraps @kehto/firewall's pure functions with persistence via\n * FirewallPersistence. No localStorage or DOM references.\n *\n * Two independent `let`-bound cells:\n * - `config` — immutable FirewallConfig; persisted on each mutation.\n * - `counters` — ephemeral FirewallState (token-buckets + burst counters);\n * in-memory only, reset on reload (RUNTIME-03).\n *\n * CRITICAL: `evaluate` reassigns `counters = result.newState` on every call.\n * Without this, token buckets never advance and flood events never escalate\n * from 'flag' to 'block'.\n */\n\nimport type {\n Observation,\n FirewallConfig,\n FirewallState,\n NappletPolicy,\n RateLimit,\n ContentMatcher,\n EvaluateResult,\n} from '@kehto/firewall';\nimport {\n evaluate,\n defaultConfig,\n createState,\n serialize,\n deserialize,\n setPolicy,\n setRateLimit,\n setGlobalRate,\n addMatcher,\n} from '@kehto/firewall';\nimport type { FirewallPersistence } from './types.js';\n\n/**\n * Stateful firewall container — wraps @kehto/firewall's pure functions\n * with persistence and a convenient imperative API.\n *\n * Mirrors AclStateContainer from acl-state.ts in structure and naming.\n *\n * @example\n * ```ts\n * const firewall = createFirewallState(persistence);\n * firewall.load();\n * const result = firewall.evaluate({ napplet: 'chat', opClass: 'relay:write', focused: true, now: Date.now() });\n * ```\n */\nexport interface FirewallStateContainer {\n /**\n * Evaluate an observation against the current firewall config and counters.\n * CRITICAL: advances the in-memory counter state on each call.\n *\n * @param observation - Normalized observation extracted from the napplet message envelope.\n * @returns The full EvaluateResult (decision, action, ruleId, reason, newState).\n */\n evaluate(observation: Observation): EvaluateResult;\n /**\n * Set a per-napplet policy override (allow / deny / ask).\n *\n * @param napplet - The napplet dTag (version-agnostic identity key).\n * @param policy - Hard policy override for this napplet.\n */\n setPolicy(napplet: string, policy: NappletPolicy): void;\n /**\n * Set a per-(napplet, opClass) token-bucket rate limit.\n *\n * @param napplet - The napplet dTag.\n * @param opClass - The operation class string.\n * @param limit - The rate limit to apply.\n */\n setRateLimit(napplet: string, opClass: string, limit: RateLimit): void;\n /**\n * Set a global rate limit applied to all op-classes that have no specific entry.\n *\n * @param napplet - The napplet dTag.\n * @param limit - The global fallback rate limit.\n */\n setGlobalRate(napplet: string, limit: RateLimit): void;\n /**\n * Add a content matcher to the firewall config.\n *\n * @param matcher - The content matcher to append.\n */\n addMatcher(matcher: ContentMatcher): void;\n /** Return the current firewall config. */\n getConfig(): FirewallConfig;\n /** Persist the current firewall config via the persistence hook. Best-effort. */\n persist(): void;\n /** Load previously persisted firewall config. Counters are NOT restored. */\n load(): void;\n /** Reset config to defaultConfig() and counters to createState(), then persist empty. */\n clear(): void;\n}\n\n/**\n * Create a firewall state container backed by @kehto/firewall and optionally\n * persisted via the given persistence hooks.\n *\n * When `persistence` is absent (or undefined), firewall config is in-memory\n * only and resets on container recreation. This is safe for hosts that do not\n * need durable per-napplet policies.\n *\n * @param persistence - Optional storage backend for firewall config.\n * @returns A FirewallStateContainer instance.\n *\n * @example\n * ```ts\n * const firewall = createFirewallState(persistence);\n * firewall.load();\n * firewall.setPolicy('chat', 'allow');\n * firewall.persist();\n * ```\n */\nexport function createFirewallState(\n persistence?: FirewallPersistence,\n): FirewallStateContainer {\n let config: FirewallConfig = defaultConfig();\n let counters: FirewallState = createState();\n\n return {\n evaluate(observation: Observation): EvaluateResult {\n const result = evaluate(config, counters, observation);\n counters = result.newState; // CRITICAL: advance ephemeral counter state\n return result;\n },\n\n setPolicy(napplet: string, policy: NappletPolicy): void {\n config = setPolicy(config, napplet, policy);\n },\n\n setRateLimit(napplet: string, opClass: string, limit: RateLimit): void {\n config = setRateLimit(config, napplet, opClass, limit);\n },\n\n setGlobalRate(napplet: string, limit: RateLimit): void {\n config = setGlobalRate(config, napplet, limit);\n },\n\n addMatcher(matcher: ContentMatcher): void {\n config = addMatcher(config, matcher);\n },\n\n getConfig(): FirewallConfig {\n return config;\n },\n\n persist(): void {\n try {\n persistence?.persist(serialize(config));\n } catch { /* persistence is best-effort */ }\n },\n\n load(): void {\n try {\n const raw = persistence?.load() ?? null;\n if (!raw) return;\n config = deserialize(raw);\n // counters are deliberately NOT restored (RUNTIME-03: ephemeral)\n } catch {\n config = defaultConfig();\n }\n },\n\n clear(): void {\n config = defaultConfig();\n counters = createState();\n try { persistence?.persist(''); } catch { /* best-effort */ }\n },\n };\n}\n","/**\n * manifest-cache.ts — Manifest cache with persistence hooks.\n *\n * Caches NIP-5A manifest data (aggregate hashes) per napplet identity.\n * Delegates storage to ManifestPersistence — no localStorage.\n */\n\nimport type { ManifestPersistence, ManifestCacheEntry, VerificationCacheEntry } from './types.js';\n\n/**\n * Cache for verified napplet manifest entries.\n * Used to detect napplet updates (aggregateHash changes) across sessions.\n *\n * @example\n * ```ts\n * const cache = createManifestCache(persistence);\n * cache.load();\n * cache.set({ pubkey: 'abc...', dTag: 'chat', aggregateHash: 'dead', verifiedAt: Date.now() });\n * ```\n */\nexport interface ManifestCache {\n /** Get a cached manifest entry by pubkey and dTag. */\n get(pubkey: string, dTag: string): ManifestCacheEntry | undefined;\n /** Set (upsert) a manifest cache entry and persist. */\n set(entry: ManifestCacheEntry): void;\n /** Check if a specific hash is cached for a pubkey/dTag combination. */\n has(pubkey: string, dTag: string, hash: string): boolean;\n /** Get the requires list for a cached manifest, or empty array if not found. */\n getRequires(pubkey: string, dTag: string): string[];\n /** Remove a cached entry for a pubkey/dTag and persist. */\n remove(pubkey: string, dTag: string): void;\n /** Load the cache from persistence. */\n load(): void;\n /** Persist the cache to storage. */\n persist(): void;\n /** Clear all cached entries. */\n clear(): void;\n\n /** Get a cached verification result by manifest event ID. */\n getVerification(eventId: string): VerificationCacheEntry | undefined;\n\n /** Cache a verification result keyed by manifest event ID. */\n setVerification(eventId: string, result: VerificationCacheEntry): void;\n\n /** Check if a manifest event ID has been verified. */\n hasVerification(eventId: string): boolean;\n\n /** Clear all verification cache entries. */\n clearVerifications(): void;\n}\n\n/**\n * Create a manifest cache backed by the given persistence hooks.\n *\n * @param persistence - Storage backend for manifest data\n * @returns A ManifestCache instance\n *\n * @example\n * ```ts\n * import { createManifestCache } from '@kehto/runtime';\n *\n * const cache = createManifestCache(manifestPersistence);\n * cache.load();\n * cache.set({ pubkey: 'abc...', dTag: 'chat', aggregateHash: 'dead', verifiedAt: Date.now() });\n * cache.has('abc...', 'chat', 'dead'); // true\n * ```\n */\nexport function createManifestCache(persistence: ManifestPersistence): ManifestCache {\n const cache = new Map<string, ManifestCacheEntry>();\n const verificationCache = new Map<string, VerificationCacheEntry>();\n\n function cacheKey(pubkey: string, dTag: string): string {\n return `${pubkey}:${dTag}`;\n }\n\n const self: ManifestCache = {\n get(pubkey: string, dTag: string): ManifestCacheEntry | undefined {\n return cache.get(cacheKey(pubkey, dTag));\n },\n\n set(entry: ManifestCacheEntry): void {\n cache.set(cacheKey(entry.pubkey, entry.dTag), entry);\n self.persist();\n },\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 getRequires(pubkey: string, dTag: string): string[] {\n const entry = cache.get(cacheKey(pubkey, dTag));\n return entry?.requires ?? [];\n },\n\n remove(pubkey: string, dTag: string): void {\n cache.delete(cacheKey(pubkey, dTag));\n self.persist();\n },\n\n load(): void {\n try {\n const raw = persistence.load();\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 { cache.clear(); }\n },\n\n persist(): void {\n try {\n persistence.persist(JSON.stringify(Array.from(cache.entries())));\n } catch { /* persistence is best-effort */ }\n },\n\n clear(): void {\n cache.clear();\n verificationCache.clear();\n try { persistence.persist(''); } catch { /* best-effort */ }\n },\n\n getVerification(eventId: string): VerificationCacheEntry | undefined {\n return verificationCache.get(eventId);\n },\n\n setVerification(eventId: string, result: VerificationCacheEntry): void {\n verificationCache.set(eventId, result);\n // Verification cache is in-memory only — no persistence needed\n // since manifest event IDs are immutable and re-fetching is cheap\n },\n\n hasVerification(eventId: string): boolean {\n return verificationCache.has(eventId);\n },\n\n clearVerifications(): void {\n verificationCache.clear();\n },\n };\n\n return self;\n}\n","/**\n * replay.ts — Replay detection module.\n *\n * Tracks seen event IDs and validates timestamps to prevent\n * duplicate event processing and replay attacks.\n */\n\nimport type { NostrEvent } from '@napplet/core';\n\n/**\n * Replay detection window in seconds — events older than this are rejected.\n * Relocated inline from the former @napplet/core compatibility shim\n * (DRIFT-CORE-06, Phase 24). Numeric value preserved unchanged from the shim\n * to hold behavioral parity with the Phase 23 test baseline.\n */\nconst REPLAY_WINDOW_SECONDS = 30;\n\n/**\n * Replay detection engine. Tracks seen event IDs and validates timestamps.\n *\n * @example\n * ```ts\n * const detector = createReplayDetector();\n * const reason = detector.check(event);\n * if (reason !== null) { // reject event }\n * ```\n */\nexport interface ReplayDetector {\n /**\n * Check if an event should be rejected as a replay.\n * Returns null if event is valid, or a string reason if it should be rejected.\n */\n check(event: NostrEvent): string | null;\n\n /** Clear all tracked event IDs. */\n clear(): void;\n}\n\n/**\n * Create a replay detector that rejects duplicate events and events\n * with timestamps outside the replay window.\n *\n * @param getReplayWindow - Optional getter for a dynamic replay window override.\n * When provided, its return value is used instead of the module-level constant.\n * Called on every check, so changes take effect immediately.\n * @returns A ReplayDetector instance\n *\n * @example\n * ```ts\n * import { createReplayDetector } from '@kehto/runtime';\n *\n * const detector = createReplayDetector();\n * const reason = detector.check(event);\n * if (reason !== null) {\n * // Reject — duplicate, stale, or future-dated\n * }\n * ```\n */\nexport function createReplayDetector(getReplayWindow?: () => number | undefined): ReplayDetector {\n const seenEventIds = new Map<string, number>();\n\n return {\n check(event: NostrEvent): string | null {\n const replayWindow = getReplayWindow?.() ?? REPLAY_WINDOW_SECONDS;\n const now = Math.floor(Date.now() / 1000);\n if (now - event.created_at > replayWindow) return 'invalid: event created_at too old';\n if (event.created_at - now > 10) return 'invalid: event created_at in the future';\n if (seenEventIds.has(event.id)) return 'duplicate: already processed';\n seenEventIds.set(event.id, now);\n for (const [id, timestamp] of seenEventIds) {\n if (now - timestamp > replayWindow) seenEventIds.delete(id);\n }\n return null;\n },\n\n clear(): void {\n seenEventIds.clear();\n },\n };\n}\n","/**\n * event-buffer.ts — Ring buffer and subscription delivery engine.\n *\n * Buffers events in a fixed-size ring buffer and delivers matching\n * events to subscribed napplets via the abstract sendToNapplet transport.\n */\n\nimport type { NostrEvent, NostrFilter } from '@napplet/core';\nimport type { Capability } from '@kehto/acl/capabilities';\nimport type { SendToNapplet } from './types.js';\nimport type { SessionRegistry } from './session-registry.js';\nimport type { EnforceResult } from './enforce.js';\n\n/** Default ring buffer size. */\nexport const RING_BUFFER_SIZE = 100;\n\n/** Subscription entry — tracks a subscription for a specific napplet window. */\nexport interface SubscriptionEntry {\n windowId: string;\n filters: NostrFilter[];\n}\n\n/**\n * Check if an event matches a single NIP-01 filter.\n * Pure function — no side effects.\n *\n * @param event - The event to check\n * @param filter - The filter to match against\n * @returns True if the event matches the filter\n *\n * @example\n * ```ts\n * import { matchesFilter } from '@kehto/runtime';\n *\n * matchesFilter(event, { kinds: [1], authors: ['abc'] });\n * // true if event.kind === 1 and event.pubkey starts with 'abc'\n * ```\n */\nexport function matchesFilter(event: NostrEvent, filter: NostrFilter): boolean {\n if (filter.ids !== undefined && !filter.ids.some((id) => event.id.startsWith(id))) return false;\n if (filter.authors !== undefined && !filter.authors.some((a) => event.pubkey.startsWith(a))) return false;\n if (filter.kinds !== undefined && !filter.kinds.includes(event.kind)) return false;\n if (filter.since !== undefined && event.created_at < filter.since) return false;\n if (filter.until !== undefined && event.created_at > filter.until) return false;\n for (const [key, values] of Object.entries(filter)) {\n if (!key.startsWith('#') || values === undefined) continue;\n const tagName = key.slice(1);\n const tagValues = values as string[];\n const eventTagValues = event.tags.filter((t) => t[0] === tagName).map((t) => t[1]);\n if (!tagValues.some((v) => eventTagValues.includes(v))) return false;\n }\n return true;\n}\n\n/**\n * Check if an event matches any filter in a list.\n * Returns true for empty filter lists.\n *\n * @param event - The event to check\n * @param filters - The filters to match against\n * @returns True if the event matches any filter (or if filters is empty)\n *\n * @example\n * ```ts\n * import { matchesAnyFilter } from '@kehto/runtime';\n *\n * matchesAnyFilter(event, [{ kinds: [1] }, { kinds: [7] }]);\n * // true if event is kind 1 or kind 7\n * ```\n */\nexport function matchesAnyFilter(event: NostrEvent, filters: NostrFilter[]): boolean {\n if (filters.length === 0) return true;\n return filters.some((filter) => matchesFilter(event, filter));\n}\n\n/** Event buffer and subscription delivery engine. */\nexport interface EventBuffer {\n /** Add an event to the ring buffer and deliver to matching subscriptions. */\n bufferAndDeliver(event: NostrEvent, senderId: string | null): void;\n\n /** Deliver an event to matching subscriptions without buffering. */\n deliverToSubscriptions(event: NostrEvent, senderId: string | null): void;\n\n /** Get the current subscription map (for REQ handler to register subs). */\n getSubscriptions(): Map<string, SubscriptionEntry>;\n\n /** Get buffered events (for REQ replay). */\n getBufferedEvents(): readonly NostrEvent[];\n\n /** Clear the buffer. */\n clear(): void;\n}\n\n/**\n * Create an event buffer with subscription delivery.\n *\n * @param sendToNapplet - Transport function to send messages to napplets\n * @param sessionRegistry - Identity registry for looking up napplet pubkeys\n * @param enforce - Enforcement function for checking relay:read on recipients\n * @param subscriptions - Shared subscription map (owned by the runtime)\n * @param getBufferSize - Optional getter for a dynamic buffer size override.\n * When provided, its return value is used instead of RING_BUFFER_SIZE.\n * Called on every bufferAndDeliver, so changes take effect immediately.\n * @returns An EventBuffer instance\n *\n * @example\n * ```ts\n * import { createEventBuffer } from '@kehto/runtime';\n *\n * const buffer = createEventBuffer(sendToNapplet, sessionRegistry, enforce, subscriptions);\n * buffer.bufferAndDeliver(event, senderWindowId);\n * ```\n */\nexport function createEventBuffer(\n sendToNapplet: SendToNapplet,\n sessionRegistry: SessionRegistry,\n enforce: (pubkey: string, capability: Capability, message?: unknown[]) => EnforceResult,\n subscriptions: Map<string, SubscriptionEntry>,\n getBufferSize?: () => number,\n): EventBuffer {\n const buffer: NostrEvent[] = [];\n\n function deliverToSubscriptions(event: NostrEvent, senderId: string | null): void {\n const pTag = event.tags?.find((t) => t[0] === 'p');\n const targetPubkey = pTag?.[1];\n\n for (const [subKey, sub] of subscriptions) {\n if (senderId !== null && sub.windowId === senderId) continue;\n\n const recipientPubkey = sessionRegistry.getPubkey(sub.windowId);\n if (recipientPubkey) {\n const recipientResult = enforce(recipientPubkey, 'relay:read', ['EVENT', event]);\n if (!recipientResult.allowed) continue;\n }\n\n if (targetPubkey) {\n const subPubkey = recipientPubkey;\n if (subPubkey !== targetPubkey) continue;\n }\n\n if (!matchesAnyFilter(event, sub.filters)) continue;\n\n const prefix = `${sub.windowId}:`;\n if (!subKey.startsWith(prefix)) continue;\n const subId = subKey.slice(prefix.length);\n\n sendToNapplet(sub.windowId, ['EVENT', subId, event]);\n }\n }\n\n return {\n bufferAndDeliver(event: NostrEvent, senderId: string | null): void {\n const maxSize = getBufferSize?.() ?? RING_BUFFER_SIZE;\n if (buffer.length >= maxSize) buffer.shift();\n buffer.push(event);\n deliverToSubscriptions(event, senderId);\n },\n\n deliverToSubscriptions,\n\n getSubscriptions(): Map<string, SubscriptionEntry> { return subscriptions; },\n\n getBufferedEvents(): readonly NostrEvent[] { return buffer; },\n\n clear(): void {\n buffer.length = 0;\n },\n };\n}\n","\nimport { createDispatch, type NappletMessage, type NostrEvent, type NapHandler } from '@napplet/core';\nimport type { Capability } from '@kehto/acl/capabilities';\nimport type { Observation } from '@kehto/firewall';\n\nimport type {\n RuntimeAdapter, ConsentHandler, FirewallEvent,\n ServiceHandler, ServiceRegistry, ServiceInfo,\n} from './types.js';\nimport { notifyServiceWindowDestroyed } from './service-dispatch.js';\nimport { createSessionRegistry, type SessionRegistry } from './session-registry.js';\nimport { createAclState, type AclStateContainer } from './acl-state.js';\nimport { createFirewallState, type FirewallStateContainer } from './firewall-state.js';\nimport { createManifestCache, type ManifestCache } from './manifest-cache.js';\nimport { createReplayDetector, type ReplayDetector } from './replay.js';\nimport { createEventBuffer, RING_BUFFER_SIZE, type EventBuffer, type SubscriptionEntry } from './event-buffer.js';\nimport { createEnforceGate, createNapEnforceGate, resolveCapabilitiesNap, formatDenialReason } from './enforce.js';\nimport { createRelayHandler } from './relay-handler.js';\nimport { createIdentityHandler } from './identity-handler.js';\nimport { createIncRuntime, type IncRuntime } from './inc-handler.js';\nimport { createRuntimeDomainHandlers, type RuntimeDomainHandlers } from './domain-handlers.js';\n\n/**\n * The napplet protocol engine — handles NIP-5D NAP domain dispatch,\n * ACL enforcement, subscription lifecycle.\n *\n * @example\n * ```ts\n * import { createRuntime } from '@kehto/runtime';\n *\n * const runtime = createRuntime(hooks);\n * runtime.handleMessage('window-1', { type: 'relay.req', id: 'sub1', filters: [{ kinds: [1] }] });\n * ```\n */\nexport interface Runtime {\n /**\n * Handle an incoming NIP-5D NappletMessage envelope from a napplet.\n * The caller is responsible for identifying the source (windowId).\n * Legacy NIP-01 arrays are silently dropped (clean break — no dual-mode).\n *\n * @param windowId - The identifier of the napplet that sent the message\n * @param msg - The raw message (NappletMessage envelopes are processed; other types dropped)\n */\n handleMessage(windowId: string, msg: unknown): void;\n\n /**\n * Inject a shell-originated event into subscription delivery.\n *\n * @param topic - The event topic tag value\n * @param payload - The event content\n */\n injectEvent(topic: string, payload: unknown): void;\n\n /** Destroy the runtime, persisting state and clearing all internal state. */\n destroy(): void;\n\n /** Register a handler for consent requests on destructive signing kinds. */\n registerConsentHandler(handler: ConsentHandler): void;\n\n /**\n * Register a service handler dynamically after runtime creation.\n * If a handler is already registered for this name, it is replaced.\n *\n * @param name - Service name (e.g., 'audio', 'notifications')\n * @param handler - The service handler implementation\n */\n registerService(name: string, handler: ServiceHandler): void;\n\n /**\n * Unregister a service handler by name. No-op if the name is not registered.\n *\n * @param name - Service name to remove\n */\n unregisterService(name: string): void;\n\n /**\n * Clean up all state associated with a napplet window.\n * Removes subscriptions, pending state, and notifies service handlers.\n *\n * @param windowId - The window to clean up\n */\n destroyWindow(windowId: string): void;\n\n /** Access the identity registry (for shell adapter to read napplet session state). */\n readonly sessionRegistry: SessionRegistry;\n\n /** Access the ACL state container. */\n readonly aclState: AclStateContainer;\n\n /** Access the firewall state container (for tests to pre-set policy/rules). */\n readonly firewallState: FirewallStateContainer;\n\n /** Access the manifest cache. */\n readonly manifestCache: ManifestCache;\n}\n\ntype RuntimeNapHandlers = RuntimeDomainHandlers & {\n relay: (windowId: string, msg: NappletMessage) => void;\n identity: (windowId: string, msg: NappletMessage) => void;\n inc: (windowId: string, msg: NappletMessage) => void;\n};\n\ntype RuntimeInstanceContext = {\n hooks: RuntimeAdapter;\n serviceRegistry: ServiceRegistry;\n registeredServices: Map<string, ServiceInfo>;\n replayDetector: ReplayDetector;\n subscriptions: Map<string, SubscriptionEntry>;\n eventBuffer: EventBuffer;\n incRuntime: IncRuntime;\n sessionRegistry: SessionRegistry;\n aclState: AclStateContainer;\n firewallState: FirewallStateContainer;\n manifestCache: ManifestCache;\n consentHandlerRef: { current: ConsentHandler | null };\n handleMessage: Runtime['handleMessage'];\n};\n\nfunction createRegisteredServices(serviceRegistry: ServiceRegistry): Map<string, ServiceInfo> {\n const registeredServices = new Map<string, ServiceInfo>();\n for (const [name, handler] of Object.entries(serviceRegistry)) {\n registeredServices.set(name, {\n name: handler.descriptor.name,\n version: handler.descriptor.version,\n description: handler.descriptor.description,\n });\n }\n return registeredServices;\n}\n\nfunction createNapEnvelopeDispatcher(handlers: RuntimeNapHandlers): (windowId: string, envelope: NappletMessage) => void {\n let currentWindowId: string | null = null;\n const napDispatch = createDispatch();\n const adapt = (handler: (windowId: string, msg: NappletMessage) => void): NapHandler => (msg) => {\n if (currentWindowId !== null) handler(currentWindowId, msg);\n };\n\n napDispatch.registerNap('relay', adapt(handlers.relay));\n napDispatch.registerNap('identity', adapt(handlers.identity));\n napDispatch.registerNap('keys', adapt(handlers.keys));\n napDispatch.registerNap('media', adapt(handlers.media));\n napDispatch.registerNap('notify', adapt(handlers.notify));\n napDispatch.registerNap('storage', adapt(handlers.storage));\n napDispatch.registerNap('inc', adapt(handlers.inc));\n napDispatch.registerNap('theme', adapt(handlers.theme));\n napDispatch.registerNap('config', adapt(handlers.config));\n napDispatch.registerNap('resource', adapt(handlers.resource));\n napDispatch.registerNap('cvm', adapt(handlers.cvm));\n napDispatch.registerNap('outbox', adapt(handlers.outbox));\n napDispatch.registerNap('upload', adapt(handlers.upload));\n napDispatch.registerNap('intent', adapt(handlers.intent));\n\n return (windowId, envelope) => {\n currentWindowId = windowId;\n try {\n napDispatch.dispatch(envelope);\n } finally {\n currentWindowId = null;\n }\n };\n}\n\n/**\n * Compute the UTF-8 byte length of a string without TextEncoder (ES2022-safe).\n * Mirrors the same helper in state-handler.ts to avoid cross-file import.\n */\nfunction utf8ByteLength(str: string): number {\n let bytes = 0;\n for (let i = 0; i < str.length; i++) {\n const c = str.charCodeAt(i);\n if (c < 0x80) bytes += 1;\n else if (c < 0x800) bytes += 2;\n else if (c < 0xd800 || c >= 0xe000) bytes += 3;\n else { i++; bytes += 4; } // surrogate pair\n }\n return bytes;\n}\n\n/**\n * Extract kind and byte-size from a message envelope (publish-style ops only).\n * Guards against malformed envelopes — returns undefined fields on any failure.\n *\n * @param envelope - Incoming NappletMessage envelope.\n * @returns `{ kind?, size? }` — both optional; undefined when not a publish-style op.\n */\nfunction extractKindSize(envelope: NappletMessage): { kind?: number; size?: number } {\n const ev = (envelope as NappletMessage & { event?: unknown }).event;\n if (typeof ev !== 'object' || ev === null) return {};\n const kind = typeof (ev as { kind?: unknown }).kind === 'number'\n ? (ev as { kind: number }).kind\n : undefined;\n let size: number | undefined;\n try {\n size = utf8ByteLength(JSON.stringify(ev));\n } catch { /* malformed event — size stays undefined */ }\n return { kind, size };\n}\n\n/**\n * Build a normalized Observation from a message envelope and runtime context.\n * The runtime owns the clock (Date.now()); the pure engine never reads it.\n * Focus is sourced exclusively via getFocusContext (FOCUS-01) — never napplet-self-reported.\n * When getFocusContext is absent, defaults to `{ focused: true }` (safe relax-only default).\n *\n * @param envelope - Incoming NappletMessage envelope.\n * @param windowId - Source napplet window identifier.\n * @param senderCap - Resolved sender capability string (or null).\n * @param sessionRegistry - Used to resolve dTag and registeredAt for the window.\n * @param getFocusContext - Optional shell-supplied focus query hook.\n * @returns A complete Observation ready for evaluate().\n */\nfunction buildObservation(\n envelope: NappletMessage,\n windowId: string,\n senderCap: string | null,\n sessionRegistry: SessionRegistry,\n getFocusContext?: RuntimeAdapter['getFocusContext'],\n): Observation {\n const now = Date.now();\n const entry = sessionRegistry.getEntryByWindowId(windowId);\n const napplet = entry?.dTag ?? '';\n const initElapsedMs = now - (entry?.registeredAt ?? now);\n const focus = getFocusContext?.(windowId) ?? { focused: true };\n const opClass = senderCap ?? envelope.type;\n const { kind, size } = extractKindSize(envelope);\n return { napplet, opClass, kind, size, initElapsedMs, focused: focus.focused, msSinceFocusGain: focus.msSinceFocusGain, now };\n}\n\n/** Configuration for createFirewallGate. */\ninterface FirewallGateConfig {\n firewallState: FirewallStateContainer;\n sessionRegistry: SessionRegistry;\n hooks: RuntimeAdapter;\n fireConsent: (windowId: string, napplet: string) => void;\n}\n\n/**\n * Create the firewall gate closure to inject into createMessageHandler.\n * Returns 'dispatch' to allow the message through or 'drop' to reject it.\n *\n * Decision→action mapping (RUNTIME-01, RUNTIME-04, POLICY-02):\n * - reject → error envelope + drop (no dispatch)\n * - prompt → error envelope + fireConsent + drop (ask path)\n * - pass + flag → onFirewallEvent audit + dispatch\n * - pass (ignore/allow) → dispatch (no audit)\n *\n * @param config - Gate configuration (firewallState, sessionRegistry, hooks, fireConsent).\n * @returns A gate function `(windowId, envelope, senderCap) => 'dispatch' | 'drop'`.\n */\nfunction createFirewallGate(config: FirewallGateConfig): (windowId: string, envelope: NappletMessage, senderCap: string | null) => 'dispatch' | 'drop' {\n const { firewallState, sessionRegistry, hooks, fireConsent } = config;\n\n return function firewallGate(windowId: string, envelope: NappletMessage, senderCap: string | null): 'dispatch' | 'drop' {\n const obs = buildObservation(envelope, windowId, senderCap, sessionRegistry, hooks.getFocusContext);\n const result = firewallState.evaluate(obs);\n const { decision, action, ruleId, reason } = result;\n const napplet = obs.napplet;\n const opClass = obs.opClass;\n\n if (decision === 'reject' || decision === 'prompt') {\n // Mirror the ACL denial envelope shaping (runtime.ts ACL path):\n // storage envelopes → `.result`; all others → `.error` (T-81-03: no internals leaked)\n const id = (envelope as NappletMessage & { id?: string }).id ?? '';\n const isStorageEnvelope = envelope.type.startsWith('storage.');\n const type = isStorageEnvelope ? `${envelope.type}.result` : `${envelope.type}.error`;\n hooks.sendToNapplet(windowId, { type, id, error: `firewall: ${reason}` } as NappletMessage);\n\n hooks.onFirewallEvent?.({ windowId, napplet, opClass, decision, action, ruleId, reason, message: envelope } as FirewallEvent);\n\n if (decision === 'prompt') {\n // POLICY-02: reject current message AND fire async consent prompt\n fireConsent(windowId, napplet);\n }\n return 'drop';\n }\n\n // decision === 'pass'\n if (action === 'flag') {\n // RUNTIME-04: flag → audit + dispatch (message is NOT dropped)\n hooks.onFirewallEvent?.({ windowId, napplet, opClass, decision, action, ruleId, reason, message: envelope } as FirewallEvent);\n }\n return 'dispatch';\n };\n}\n\nfunction createMessageHandler(\n hooks: RuntimeAdapter,\n enforceNap: ReturnType<typeof createNapEnforceGate>,\n dispatchNapEnvelope: (windowId: string, envelope: NappletMessage) => void,\n firewallGate: (windowId: string, envelope: NappletMessage, senderCap: string | null) => 'dispatch' | 'drop',\n): Runtime['handleMessage'] {\n return (windowId: string, msg: unknown): void => {\n if (typeof msg !== 'object' || msg === null || !('type' in msg)) return;\n const envelope = msg as NappletMessage;\n const dotIdx = envelope.type.indexOf('.');\n if (dotIdx === -1) return;\n\n const caps = resolveCapabilitiesNap(envelope);\n if (caps.senderCap) {\n const result = enforceNap(windowId, caps.senderCap as Capability, envelope);\n if (!result.allowed) {\n const id = (envelope as NappletMessage & { id?: string }).id ?? '';\n const isStorageEnvelope = envelope.type.startsWith('storage.');\n const error = formatDenialReason(result.capability);\n const type = isStorageEnvelope ? `${envelope.type}.result` : `${envelope.type}.error`;\n hooks.sendToNapplet(windowId, { type, id, error } as NappletMessage);\n return;\n }\n }\n\n const verdict = firewallGate(windowId, envelope, caps.senderCap);\n if (verdict === 'drop') return;\n\n dispatchNapEnvelope(windowId, envelope);\n };\n}\n\nfunction createInjectedEvent(hooks: RuntimeAdapter, topic: string, payload: unknown): NostrEvent {\n const uuid = hooks.crypto.randomUUID().replace(/-/g, '').slice(0, 64).padEnd(64, '0');\n return {\n id: uuid,\n pubkey: '0'.repeat(64),\n created_at: Math.floor(Date.now() / 1000),\n kind: 29000,\n tags: [['t', topic]],\n content: JSON.stringify(payload),\n sig: '0'.repeat(128),\n };\n}\n\nfunction createRuntimeInstance(context: RuntimeInstanceContext): Runtime {\n const {\n aclState, firewallState, eventBuffer, hooks, incRuntime, manifestCache, registeredServices,\n replayDetector, serviceRegistry, sessionRegistry, subscriptions, consentHandlerRef,\n } = context;\n const undeclaredServiceConsents = new Set<string>();\n\n return {\n handleMessage: context.handleMessage,\n injectEvent(topic: string, payload: unknown): void {\n eventBuffer.bufferAndDeliver(createInjectedEvent(hooks, topic, payload), null);\n },\n destroy(): void {\n manifestCache.persist();\n aclState.persist();\n firewallState.persist();\n replayDetector.clear();\n subscriptions.clear();\n incRuntime.clear();\n eventBuffer.clear();\n registeredServices.clear();\n undeclaredServiceConsents.clear();\n },\n registerConsentHandler(handler: ConsentHandler): void {\n consentHandlerRef.current = handler;\n },\n registerService(name: string, handler: ServiceHandler): void {\n serviceRegistry[name] = handler;\n registeredServices.set(name, {\n name: handler.descriptor.name,\n version: handler.descriptor.version,\n description: handler.descriptor.description,\n });\n },\n unregisterService(name: string): void {\n delete serviceRegistry[name];\n registeredServices.delete(name);\n },\n destroyWindow(windowId: string): void {\n for (const [key] of subscriptions) {\n if (key.startsWith(`${windowId}:`)) {\n subscriptions.delete(key);\n hooks.relayPool?.untrackSubscription(key);\n }\n }\n incRuntime.destroyWindow(windowId);\n notifyServiceWindowDestroyed(windowId, serviceRegistry);\n },\n get sessionRegistry() { return sessionRegistry; },\n get aclState() { return aclState; },\n get firewallState() { return firewallState; },\n get manifestCache() { return manifestCache; },\n };\n}\n\n/**\n * Create a runtime instance with dependency injection via hooks.\n *\n * @param hooks - Host application provides relay pool, auth, config, etc.\n * @returns A Runtime instance ready to handle napplet messages\n *\n * @example\n * ```ts\n * const runtime = createRuntime(hooks);\n * // On incoming message from napplet:\n * runtime.handleMessage(windowId, { type: 'relay.req', id: 'sub1', filters: [] });\n * ```\n */\nexport function createRuntime(hooks: RuntimeAdapter): Runtime {\n const subscriptions = new Map<string, SubscriptionEntry>();\n const serviceRegistry: ServiceRegistry = { ...hooks.services };\n const registeredServices = createRegisteredServices(serviceRegistry);\n const sessionRegistry = createSessionRegistry(hooks.onPendingUpdate);\n const aclState = createAclState(hooks.aclPersistence);\n const firewallState = createFirewallState(hooks.firewallPersistence);\n const manifestCache = createManifestCache(hooks.manifestPersistence);\n const replayDetector = createReplayDetector(\n hooks.getConfigOverrides\n ? () => hooks.getConfigOverrides!().replayWindowSeconds\n : undefined,\n );\n\n // Shared consent handler ref — lifted to createRuntime scope so the firewall gate\n // can fire it. The gate is built here (outer scope) but registerConsentHandler is\n // called on the runtime instance (inner scope); the ref bridges both closures.\n const consentHandlerRef: { current: ConsentHandler | null } = { current: null };\n\n // fireConsent: invoked by the gate on 'prompt' decisions (POLICY-02 ask path).\n // On resolution the user's choice is persisted as a per-napplet policy so subsequent\n // messages are NOT re-prompted (T-81-04: bounded consent spam prevention).\n const fireConsent = (windowId: string, napplet: string): void => {\n const handler = consentHandlerRef.current;\n if (!handler) return;\n handler({\n type: 'firewall-policy',\n windowId,\n napplet,\n pubkey: '',\n resolve: (allowed: boolean): void => {\n firewallState.setPolicy(napplet, allowed ? 'allow' : 'deny');\n firewallState.persist();\n },\n });\n };\n\n const enforce = createEnforceGate({\n checkAcl: (pubkey, dTag, aggregateHash, capability) =>\n aclState.check(pubkey, dTag, aggregateHash, capability),\n resolveIdentity: (pubkey) => {\n const entry = sessionRegistry.getEntry(pubkey);\n return entry ? { dTag: entry.dTag, aggregateHash: entry.aggregateHash } : undefined;\n },\n onAclCheck: hooks.onAclCheck,\n });\n\n const enforceNap = createNapEnforceGate({\n checkAcl: (pubkey, dTag, aggregateHash, capability) =>\n aclState.check(pubkey, dTag, aggregateHash, capability),\n resolveIdentityByWindowId: (windowId) => {\n const entry = sessionRegistry.getEntryByWindowId(windowId);\n return entry\n ? { dTag: entry.dTag, aggregateHash: entry.aggregateHash }\n : undefined;\n },\n onAclCheck: hooks.onAclCheck,\n });\n\n const firewallGate = createFirewallGate({ firewallState, sessionRegistry, hooks, fireConsent });\n\n const eventBuffer = createEventBuffer(\n hooks.sendToNapplet,\n sessionRegistry,\n enforce,\n subscriptions,\n hooks.getConfigOverrides\n ? () => hooks.getConfigOverrides!().ringBufferSize ?? RING_BUFFER_SIZE\n : undefined,\n );\n\n aclState.load();\n firewallState.load();\n manifestCache.load();\n\n const incRuntime = createIncRuntime(hooks, sessionRegistry);\n const domainHandlers = createRuntimeDomainHandlers({ hooks, serviceRegistry, sessionRegistry, aclState });\n const dispatchNapEnvelope = createNapEnvelopeDispatcher({\n relay: createRelayHandler({ hooks, serviceRegistry, subscriptions, eventBuffer, replayDetector }),\n identity: createIdentityHandler({ hooks, serviceRegistry }),\n inc: incRuntime.handleMessage,\n ...domainHandlers,\n });\n const handleMessage = createMessageHandler(hooks, enforceNap, dispatchNapEnvelope, firewallGate);\n\n return createRuntimeInstance({\n hooks,\n serviceRegistry,\n registeredServices,\n replayDetector,\n subscriptions,\n eventBuffer,\n incRuntime,\n sessionRegistry,\n aclState,\n firewallState,\n manifestCache,\n consentHandlerRef,\n handleMessage,\n });\n}\n","\nimport type { NappletMessage } from '@napplet/core';\nimport type { ServiceRegistry, SendToNapplet } from './types.js';\n\n/**\n * Route a NappletMessage envelope to the matching service handler.\n *\n * NAP-domain services are routed by the domain prefix of message.type\n * (e.g., 'signer.signEvent' -> 'signer' service).\n *\n * INC-routed services receive inc.emit messages and are routed by the\n * topic prefix before ':' (e.g., topic 'audio:play' -> 'audio' service).\n *\n * Returns true if a service handled the message, false otherwise.\n *\n * @param windowId - The napplet window that sent the message\n * @param message - The NappletMessage envelope to route\n * @param services - The service registry to look up handlers\n * @param sendToNapplet - Callback to send messages back to the napplet\n * @returns true if a service handled the message, false if no matching service\n *\n * @example\n * ```ts\n * const handled = routeServiceMessage(windowId, msg, services, sendToNapplet);\n * if (!handled) { // fallback handling }\n * ```\n */\nexport function routeServiceMessage(\n windowId: string,\n message: NappletMessage,\n services: ServiceRegistry,\n sendToNapplet: SendToNapplet,\n): boolean {\n // NAP-domain services: signer.*, relay.*, storage.* route by type prefix\n const domain = message.type.split('.')[0];\n const handler = services[domain];\n if (handler) {\n handler.handleMessage(windowId, message, (msg) => sendToNapplet(windowId, msg));\n return true;\n }\n\n // INC-routed services: audio and notifications receive inc.emit with topic prefix\n const incMessage = message as NappletMessage & { topic?: unknown };\n if (message.type === 'inc.emit' && typeof incMessage.topic === 'string') {\n const prefix = incMessage.topic.split(':')[0];\n const incHandler = services[prefix];\n if (incHandler) {\n incHandler.handleMessage(windowId, message, (msg) => sendToNapplet(windowId, msg));\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Notify all registered service handlers that a napplet window was destroyed.\n * Calls onWindowDestroyed() on every handler that implements it.\n * Errors in individual handlers are caught and silently ignored to prevent\n * one service's cleanup failure from blocking others.\n *\n * @param windowId - The destroyed napplet's window identifier\n * @param services - The service registry containing all handlers\n *\n * @example\n * ```ts\n * notifyServiceWindowDestroyed('window-1', services);\n * ```\n */\nexport function notifyServiceWindowDestroyed(\n windowId: string,\n services: ServiceRegistry,\n): void {\n for (const handler of Object.values(services)) {\n try {\n handler.onWindowDestroyed?.(windowId);\n } catch {\n /* Service cleanup is best-effort — don't let one service block others */\n }\n }\n}\n","import type { NappletMessage, NostrEvent, NostrFilter } from '@napplet/core';\nimport type { RelayMessage } from '@napplet/nap/relay/types';\n\nimport { matchesAnyFilter, type EventBuffer, type SubscriptionEntry } from './event-buffer.js';\nimport type { ReplayDetector } from './replay.js';\nimport type { RuntimeAdapter, ServiceHandler, ServiceRegistry } from './types.js';\n\ndeclare function setTimeout(callback: () => void, ms: number): unknown;\ndeclare function clearTimeout(id: unknown): void;\n\ntype RuntimeRelayMessage = RelayMessage & {\n subId?: string;\n filters?: NostrFilter[];\n event?: NostrEvent;\n id?: string;\n relay?: string;\n};\n\ntype RelayHandlerContext = {\n hooks: RuntimeAdapter;\n serviceRegistry: ServiceRegistry;\n subscriptions: Map<string, SubscriptionEntry>;\n eventBuffer: EventBuffer;\n replayDetector: ReplayDetector;\n};\n\nexport type RelayHandler = (windowId: string, msg: NappletMessage) => void;\n\nexport function createRelayHandler(context: RelayHandlerContext): RelayHandler {\n return function handleRelayMessage(windowId: string, msg: NappletMessage): void {\n const m = msg as RuntimeRelayMessage;\n const dotIdx = msg.type.indexOf('.');\n const action = msg.type.slice(dotIdx + 1);\n\n switch (action) {\n case 'subscribe':\n handleRelaySubscribe(context, windowId, msg, m);\n return;\n case 'close':\n handleRelayClose(context, windowId, msg, m);\n return;\n case 'publish':\n handleRelayPublish(context, windowId, msg, m);\n return;\n case 'publishEncrypted':\n handleRelayPublishEncrypted(context, windowId, msg);\n return;\n case 'query':\n handleRelayQuery(context, windowId, m);\n return;\n default:\n return;\n }\n };\n}\n\nfunction relayServiceFrom(context: RelayHandlerContext): ServiceHandler | undefined {\n return context.serviceRegistry['relay'] ?? context.serviceRegistry['relay-pool'];\n}\n\nfunction isShellKindQuery(filters: NostrFilter[]): boolean {\n return filters.length > 0 && filters.every((filter) => filter.kinds?.every((kind) => kind >= 29000 && kind < 30000));\n}\n\nfunction handleRelaySubscribe(\n context: RelayHandlerContext,\n windowId: string,\n msg: NappletMessage,\n m: RuntimeRelayMessage,\n): void {\n const { eventBuffer, hooks, serviceRegistry, subscriptions } = context;\n const subId = m.subId ?? '';\n const filters = m.filters ?? [];\n if (!subId) return;\n\n const subKey = `${windowId}:${subId}`;\n subscriptions.set(subKey, { windowId, filters });\n\n const seenIds = new Set<string>();\n function deliver(event: NostrEvent): void {\n if (seenIds.has(event.id)) return;\n seenIds.add(event.id);\n if (subscriptions.has(subKey)) {\n hooks.sendToNapplet(windowId, { type: 'relay.event', subId, event } as NappletMessage);\n }\n }\n\n for (const bufferedEvent of eventBuffer.getBufferedEvents()) {\n if (matchesAnyFilter(bufferedEvent, filters)) deliver(bufferedEvent);\n }\n\n const isShellKind = isShellKindQuery(filters);\n const relayService = relayServiceFrom(context);\n const cacheService = !serviceRegistry['relay'] ? serviceRegistry['cache'] : undefined;\n\n if (!isShellKind && relayService) {\n relayService.handleMessage(windowId, msg, (resp: NappletMessage) => {\n if (!subscriptions.has(subKey)) return;\n hooks.sendToNapplet(windowId, resp);\n });\n if (cacheService) {\n cacheService.handleMessage(windowId, msg, (resp: NappletMessage) => {\n if (!subscriptions.has(subKey)) return;\n hooks.sendToNapplet(windowId, resp);\n });\n }\n return;\n }\n\n const relayHint = typeof m.relay === 'string' && m.relay.length > 0 ? m.relay : undefined;\n deliverFromRuntimeBackends(context, windowId, subId, subKey, filters, isShellKind, deliver, relayHint);\n}\n\nfunction deliverFromRuntimeBackends(\n context: RelayHandlerContext,\n windowId: string,\n subId: string,\n subKey: string,\n filters: NostrFilter[],\n isShellKind: boolean,\n deliver: (event: NostrEvent) => void,\n relayHint?: string,\n): void {\n const { hooks } = context;\n const cache = hooks.cache;\n\n if (cache?.isAvailable() && !isShellKind) {\n cache.query(filters)\n .then((cachedEvents) => {\n for (const event of cachedEvents) deliver(event);\n })\n .catch(() => {});\n }\n\n const pool = hooks.relayPool;\n if (!pool?.isAvailable() && !isShellKind) {\n hooks.sendToNapplet(windowId, { type: 'relay.eose', subId } as NappletMessage);\n return;\n }\n if (!pool?.isAvailable() || isShellKind) return;\n\n const relayUrls = relayHint ? [relayHint] : pool.selectRelayTier(filters);\n let eoseSent = false;\n const eoseFallbackTimer = setTimeout(() => {\n if (!eoseSent) {\n eoseSent = true;\n hooks.sendToNapplet(windowId, { type: 'relay.eose', subId } as NappletMessage);\n }\n }, 15_000);\n\n const subscription = pool.subscribe(filters, (item) => {\n if (item === 'EOSE') {\n clearTimeout(eoseFallbackTimer);\n if (!eoseSent) {\n eoseSent = true;\n hooks.sendToNapplet(windowId, { type: 'relay.eose', subId } as NappletMessage);\n }\n return;\n }\n deliver(item as NostrEvent);\n if (cache?.isAvailable() && !isShellKind) {\n try { cache.store(item as NostrEvent); } catch { return; }\n }\n }, relayUrls);\n\n pool.trackSubscription(subKey, () => {\n clearTimeout(eoseFallbackTimer);\n subscription.unsubscribe();\n });\n}\n\nfunction handleRelayClose(\n context: RelayHandlerContext,\n windowId: string,\n msg: NappletMessage,\n m: RuntimeRelayMessage,\n): void {\n const { hooks, subscriptions } = context;\n const subId = m.subId ?? '';\n if (!subId) return;\n const subKey = `${windowId}:${subId}`;\n subscriptions.delete(subKey);\n\n const relayService = relayServiceFrom(context);\n if (relayService) relayService.handleMessage(windowId, msg, () => {});\n hooks.relayPool?.untrackSubscription(subKey);\n hooks.sendToNapplet(windowId, { type: 'relay.closed', subId, message: '' } as NappletMessage);\n}\n\nfunction handleRelayPublish(\n context: RelayHandlerContext,\n windowId: string,\n msg: NappletMessage,\n m: RuntimeRelayMessage,\n): void {\n const { eventBuffer, hooks, replayDetector } = context;\n const event = m.event;\n const id = m.id ?? '';\n if (!event || typeof event !== 'object') {\n hooks.sendToNapplet(windowId, { type: 'relay.publish.error', id, error: 'invalid event' } as NappletMessage);\n return;\n }\n\n const replayResult = replayDetector.check(event);\n if (replayResult !== null) {\n hooks.sendToNapplet(windowId, { type: 'relay.publish.result', id, accepted: false, message: replayResult } as NappletMessage);\n return;\n }\n\n const relayService = relayServiceFrom(context);\n if (relayService) {\n relayService.handleMessage(windowId, msg, (resp: NappletMessage) => hooks.sendToNapplet(windowId, resp));\n } else if (hooks.relayPool?.isAvailable()) {\n hooks.relayPool.publish(event);\n hooks.sendToNapplet(windowId, { type: 'relay.publish.result', id, accepted: true } as NappletMessage);\n } else {\n hooks.sendToNapplet(windowId, { type: 'relay.publish.result', id, accepted: false, message: 'no relay pool available' } as NappletMessage);\n }\n\n eventBuffer.bufferAndDeliver(event, windowId);\n}\n\nfunction handleRelayPublishEncrypted(\n context: RelayHandlerContext,\n windowId: string,\n msg: NappletMessage,\n): void {\n const { hooks } = context;\n const id = (msg as RuntimeRelayMessage).id ?? '';\n const eventTemplate = (msg as RuntimeRelayMessage).event;\n const peMsg = msg as NappletMessage & { recipient?: string; encryption?: string };\n const recipient = peMsg.recipient ?? '';\n const encryption = (peMsg.encryption ?? 'nip44') as 'nip04' | 'nip44' | string;\n\n const replyPe = (ok: boolean, extra: Record<string, unknown> = {}) => {\n hooks.sendToNapplet(windowId, { type: 'relay.publishEncrypted.result', id, ok, ...extra } as NappletMessage);\n };\n\n if (!recipient) { replyPe(false, { error: 'missing recipient' }); return; }\n if (encryption !== 'nip44' && encryption !== 'nip04') {\n replyPe(false, { error: `unsupported encryption scheme: ${encryption}` });\n return;\n }\n const peSigner = hooks.auth.getSigner();\n if (!peSigner) { replyPe(false, { error: 'no signer configured' }); return; }\n if (!eventTemplate || typeof eventTemplate !== 'object') {\n replyPe(false, { error: 'invalid event template' });\n return;\n }\n\n publishEncrypted(context, windowId, id, recipient, encryption, eventTemplate, replyPe);\n}\n\nfunction publishEncrypted(\n context: RelayHandlerContext,\n windowId: string,\n id: string,\n recipient: string,\n encryption: 'nip04' | 'nip44' | string,\n eventTemplate: NostrEvent,\n replyPe: (ok: boolean, extra?: Record<string, unknown>) => void,\n): void {\n const { eventBuffer, hooks } = context;\n const peSigner = hooks.auth.getSigner();\n if (!peSigner) return;\n\n (async (): Promise<void> => {\n try {\n const plaintext = String((eventTemplate as { content?: unknown }).content ?? '');\n const ciphertext: string = encryption === 'nip44'\n ? (await peSigner.nip44?.encrypt(recipient, plaintext)) ?? ''\n : (await peSigner.nip04?.encrypt(recipient, plaintext)) ?? '';\n const eventWithCiphertext = { ...(eventTemplate as object), content: ciphertext } as NostrEvent;\n const signed = await peSigner.signEvent?.(eventWithCiphertext);\n if (!signed) { replyPe(false, { error: 'signEvent returned null' }); return; }\n\n publishSignedEncrypted(context, windowId, id, signed, replyPe);\n try { eventBuffer.bufferAndDeliver(signed, windowId); } catch { return; }\n } catch (err) {\n replyPe(false, { error: (err as Error)?.message ?? 'encryption failed' });\n }\n })();\n}\n\nfunction publishSignedEncrypted(\n context: RelayHandlerContext,\n windowId: string,\n id: string,\n signed: NostrEvent,\n replyPe: (ok: boolean, extra?: Record<string, unknown>) => void,\n): void {\n const { hooks } = context;\n const relayService = relayServiceFrom(context);\n if (!relayService) {\n if (hooks.relayPool?.isAvailable()) {\n hooks.relayPool.publish(signed);\n replyPe(true, { event: signed, eventId: signed.id });\n } else {\n replyPe(false, { error: 'no relay pool available' });\n }\n return;\n }\n\n const publishMsg = { type: 'relay.publish', id, event: signed } as NappletMessage;\n let replied = false;\n relayService.handleMessage(windowId, publishMsg, (resp: NappletMessage) => {\n if (replied) return;\n const r = resp as NappletMessage & {\n ok?: boolean; accepted?: boolean; eventId?: string;\n message?: string; error?: string;\n };\n if (typeof r.type !== 'string' || !r.type.startsWith('relay.publish')) return;\n const okVal = r.ok ?? r.accepted ?? false;\n replied = true;\n const publishResult = { event: signed, eventId: signed.id } as {\n event: NostrEvent;\n eventId: string;\n error?: string;\n };\n if (!okVal) publishResult.error = r.error ?? r.message ?? 'publish failed';\n replyPe(okVal, publishResult);\n });\n\n if (!replied) {\n replied = true;\n replyPe(true, { event: signed, eventId: signed.id });\n }\n}\n\nfunction handleRelayQuery(\n context: RelayHandlerContext,\n windowId: string,\n m: RuntimeRelayMessage,\n): void {\n const id = m.id ?? '';\n const filters = m.filters ?? [];\n let count = 0;\n for (const event of context.eventBuffer.getBufferedEvents()) {\n if (matchesAnyFilter(event, filters)) count++;\n }\n context.hooks.sendToNapplet(windowId, { type: 'relay.query.result', id, count } as NappletMessage);\n}\n","import type { NappletMessage } from '@napplet/core';\n\nimport type { RuntimeAdapter, ServiceRegistry } from './types.js';\n\ntype IdentityHandlerContext = {\n hooks: RuntimeAdapter;\n serviceRegistry: ServiceRegistry;\n};\n\nexport type IdentityHandler = (windowId: string, msg: NappletMessage) => void;\n\nexport function createIdentityHandler(context: IdentityHandlerContext): IdentityHandler {\n return function handleIdentityMessage(windowId: string, msg: NappletMessage): void {\n const { hooks, serviceRegistry } = context;\n const identityService = serviceRegistry['identity'];\n if (identityService) {\n identityService.handleMessage(windowId, msg, (resp: NappletMessage) => hooks.sendToNapplet(windowId, resp));\n return;\n }\n\n const id = (msg as NappletMessage & { id?: string }).id ?? '';\n const action = msg.type.slice('identity.'.length);\n const signer = hooks.auth.getSigner();\n const sendError = (error: string) => {\n hooks.sendToNapplet(windowId, { type: `${msg.type}.error`, id, error } as NappletMessage);\n };\n const sendResult = (payload: Record<string, unknown>) => {\n hooks.sendToNapplet(windowId, { type: `${msg.type}.result`, id, ...payload } as NappletMessage);\n };\n\n switch (action) {\n case 'getPublicKey':\n if (!signer) { sendResult({ pubkey: '' }); return; }\n Promise.resolve(signer.getPublicKey?.())\n .then((pubkey) => sendResult({ pubkey: pubkey ?? '' }))\n .catch((err: unknown) => sendError((err as Error)?.message ?? 'getPublicKey failed'));\n return;\n case 'getRelays':\n if (!signer) { sendError('no signer configured'); return; }\n Promise.resolve(signer.getRelays?.() ?? {})\n .then((relays) => sendResult({ relays }))\n .catch((err: unknown) => sendError((err as Error)?.message ?? 'getRelays failed'));\n return;\n case 'getProfile': sendResult({ profile: null }); return;\n case 'getFollows': sendResult({ pubkeys: [] }); return;\n case 'getList': sendResult({ entries: [] }); return;\n case 'getZaps': sendResult({ zaps: [] }); return;\n case 'getMutes': sendResult({ pubkeys: [] }); return;\n case 'getBlocked': sendResult({ pubkeys: [] }); return;\n case 'getBadges': sendResult({ badges: [] }); return;\n default:\n sendError(`Unknown identity action: ${action}`);\n }\n };\n}\n","import type { NappletMessage } from '@napplet/core';\nimport type { IncMessage } from '@napplet/nap/inc/types';\n\nimport type { SessionRegistry } from './session-registry.js';\nimport type { RuntimeAdapter } from './types.js';\n\ninterface IncChannel {\n channelId: string;\n peerA: string;\n peerB: string;\n}\n\ntype RuntimeIncMessage = IncMessage & {\n id?: string;\n topic?: string;\n payload?: unknown;\n target?: string;\n channelId?: string;\n};\n\ntype IncState = {\n subscriptions: Map<string, Set<string>>;\n channels: Map<string, IncChannel>;\n channelsByWindow: Map<string, Set<string>>;\n};\n\nexport interface IncRuntime {\n handleMessage(windowId: string, msg: NappletMessage): void;\n destroyWindow(windowId: string): void;\n clear(): void;\n}\n\nexport function createIncRuntime(hooks: RuntimeAdapter, sessionRegistry: SessionRegistry): IncRuntime {\n const state: IncState = {\n subscriptions: new Map(),\n channels: new Map(),\n channelsByWindow: new Map(),\n };\n\n return {\n handleMessage(windowId: string, msg: NappletMessage): void {\n handleIncMessage(state, hooks, sessionRegistry, windowId, msg);\n },\n destroyWindow(windowId: string): void {\n removeWindowChannels(state, hooks, windowId);\n removeWindowSubscriptions(state, windowId);\n },\n clear(): void {\n state.subscriptions.clear();\n state.channels.clear();\n state.channelsByWindow.clear();\n },\n };\n}\n\nfunction addChannel(state: IncState, channelId: string, peerA: string, peerB: string): void {\n state.channels.set(channelId, { channelId, peerA, peerB });\n for (const windowId of [peerA, peerB]) {\n let set = state.channelsByWindow.get(windowId);\n if (!set) {\n set = new Set();\n state.channelsByWindow.set(windowId, set);\n }\n set.add(channelId);\n }\n}\n\nfunction removeChannel(state: IncState, channelId: string): void {\n const channel = state.channels.get(channelId);\n if (!channel) return;\n state.channels.delete(channelId);\n for (const windowId of [channel.peerA, channel.peerB]) {\n const set = state.channelsByWindow.get(windowId);\n if (!set) continue;\n set.delete(channelId);\n if (set.size === 0) state.channelsByWindow.delete(windowId);\n }\n}\n\nfunction peerOf(state: IncState, channelId: string, self: string): string | null {\n const channel = state.channels.get(channelId);\n if (!channel) return null;\n if (channel.peerA === self) return channel.peerB;\n if (channel.peerB === self) return channel.peerA;\n return null;\n}\n\nfunction resolveTarget(sessionRegistry: SessionRegistry, target: string): string | null {\n if (sessionRegistry.getEntryByWindowId(target)) return target;\n const entries = sessionRegistry.getAllEntries();\n const byPubkey = entries.find((entry) => entry.pubkey === target);\n return byPubkey?.windowId ?? null;\n}\n\nfunction handleIncMessage(\n state: IncState,\n hooks: RuntimeAdapter,\n sessionRegistry: SessionRegistry,\n windowId: string,\n msg: NappletMessage,\n): void {\n const m = msg as RuntimeIncMessage;\n const dotIdx = msg.type.indexOf('.');\n const action = msg.type.slice(dotIdx + 1);\n\n switch (action) {\n case 'emit': handleEmit(state, hooks, windowId, m); return;\n case 'subscribe': handleSubscribe(state, hooks, windowId, m); return;\n case 'unsubscribe': handleUnsubscribe(state, windowId, m); return;\n case 'channel.open': handleChannelOpen(state, hooks, sessionRegistry, windowId, m); return;\n case 'channel.emit': handleChannelEmit(state, hooks, windowId, m); return;\n case 'channel.broadcast': handleChannelBroadcast(state, hooks, windowId, m); return;\n case 'channel.list': handleChannelList(state, hooks, windowId, m); return;\n case 'channel.close': handleChannelClose(state, hooks, windowId, m); return;\n default: return;\n }\n}\n\nfunction handleEmit(\n state: IncState,\n hooks: RuntimeAdapter,\n windowId: string,\n m: RuntimeIncMessage,\n): void {\n const topic = m.topic ?? '';\n if (!topic) return;\n const subscribers = state.subscriptions.get(topic);\n if (!subscribers) return;\n for (const subscriberWindowId of subscribers) {\n if (subscriberWindowId !== windowId) {\n hooks.sendToNapplet(subscriberWindowId, { type: 'inc.event', topic, payload: m.payload, sender: windowId } as NappletMessage);\n }\n }\n}\n\nfunction handleSubscribe(\n state: IncState,\n hooks: RuntimeAdapter,\n windowId: string,\n m: RuntimeIncMessage,\n): void {\n const id = m.id ?? '';\n const topic = m.topic ?? '';\n if (!topic) {\n hooks.sendToNapplet(windowId, { type: 'inc.subscribe.result', id, error: 'missing topic' } as NappletMessage);\n return;\n }\n let subscriptions = state.subscriptions.get(topic);\n if (!subscriptions) {\n subscriptions = new Set();\n state.subscriptions.set(topic, subscriptions);\n }\n subscriptions.add(windowId);\n hooks.sendToNapplet(windowId, { type: 'inc.subscribe.result', id } as NappletMessage);\n}\n\nfunction handleUnsubscribe(state: IncState, windowId: string, m: RuntimeIncMessage): void {\n const topic = m.topic ?? '';\n if (!topic) return;\n const subscriptions = state.subscriptions.get(topic);\n if (!subscriptions) return;\n subscriptions.delete(windowId);\n if (subscriptions.size === 0) state.subscriptions.delete(topic);\n}\n\nfunction handleChannelOpen(\n state: IncState,\n hooks: RuntimeAdapter,\n sessionRegistry: SessionRegistry,\n windowId: string,\n m: RuntimeIncMessage,\n): void {\n const id = m.id ?? '';\n const peerWindow = resolveTarget(sessionRegistry, m.target ?? '');\n if (!peerWindow) {\n hooks.sendToNapplet(windowId, { type: 'inc.channel.open.result', id, error: 'target not found' } as NappletMessage);\n return;\n }\n const channelId = hooks.crypto.randomUUID().replace(/-/g, '').slice(0, 32);\n addChannel(state, channelId, windowId, peerWindow);\n hooks.sendToNapplet(windowId, { type: 'inc.channel.open.result', id, channelId, peer: peerWindow } as NappletMessage);\n}\n\nfunction handleChannelEmit(\n state: IncState,\n hooks: RuntimeAdapter,\n windowId: string,\n m: RuntimeIncMessage,\n): void {\n const peer = peerOf(state, m.channelId ?? '', windowId);\n if (peer) {\n hooks.sendToNapplet(peer, { type: 'inc.channel.event', channelId: m.channelId ?? '', sender: windowId, payload: m.payload } as NappletMessage);\n }\n}\n\nfunction handleChannelBroadcast(\n state: IncState,\n hooks: RuntimeAdapter,\n windowId: string,\n m: RuntimeIncMessage,\n): void {\n const channels = state.channelsByWindow.get(windowId);\n if (!channels) return;\n for (const channelId of channels) {\n const peer = peerOf(state, channelId, windowId);\n if (peer) {\n hooks.sendToNapplet(peer, { type: 'inc.channel.event', channelId, sender: windowId, payload: m.payload } as NappletMessage);\n }\n }\n}\n\nfunction handleChannelList(\n state: IncState,\n hooks: RuntimeAdapter,\n windowId: string,\n m: RuntimeIncMessage,\n): void {\n const channels = [];\n const set = state.channelsByWindow.get(windowId);\n if (set) {\n for (const channelId of set) {\n const peer = peerOf(state, channelId, windowId);\n if (peer) channels.push({ id: channelId, peer });\n }\n }\n hooks.sendToNapplet(windowId, { type: 'inc.channel.list.result', id: m.id ?? '', channels } as NappletMessage);\n}\n\nfunction handleChannelClose(\n state: IncState,\n hooks: RuntimeAdapter,\n windowId: string,\n m: RuntimeIncMessage,\n): void {\n const channelId = m.channelId ?? '';\n const peer = peerOf(state, channelId, windowId);\n if (!peer) return;\n hooks.sendToNapplet(windowId, { type: 'inc.channel.closed', channelId } as NappletMessage);\n hooks.sendToNapplet(peer, { type: 'inc.channel.closed', channelId } as NappletMessage);\n removeChannel(state, channelId);\n}\n\nfunction removeWindowSubscriptions(state: IncState, windowId: string): void {\n for (const [topic, subscriptions] of state.subscriptions) {\n subscriptions.delete(windowId);\n if (subscriptions.size === 0) state.subscriptions.delete(topic);\n }\n}\n\nfunction removeWindowChannels(state: IncState, hooks: RuntimeAdapter, windowId: string): void {\n const channelIds = state.channelsByWindow.get(windowId);\n if (!channelIds) return;\n for (const channelId of Array.from(channelIds)) {\n const peer = peerOf(state, channelId, windowId);\n if (peer) {\n hooks.sendToNapplet(peer, { type: 'inc.channel.closed', channelId } as NappletMessage);\n }\n removeChannel(state, channelId);\n }\n}\n","/**\n * state-handler.ts — Storage NAP request handler using persistence hooks.\n *\n * Handles napplet storage operations (get, set, remove, keys) via the\n * canonical `@napplet/nap/storage` NIP-5D envelope surface. Delegates\n * storage to StatePersistence. No localStorage, no legacy NIP-01 dispatch.\n *\n * Per-instance scope (NAP-STORAGE, napplet/naps#3): every request carries an\n * optional `scope: \"shared\" | \"instance\"` (default `\"shared\"`). `\"instance\"`\n * folds a stable per-window discriminator (`@i/<windowId>:`) into the key so\n * that multiple open windows of the *same* napplet keep isolated, independently\n * persisted state. Instancing is a property of the data (per call), not the\n * napplet — there is no napplet-wide mode. The napplet never sees or names an\n * instance identifier; it sets `scope` and trusts the shell's Unique + Stable\n * guarantees. `\"shared\"` (or absent) addresses the napplet-wide namespace,\n * byte-identical to the historical behavior.\n */\n\nimport type { NappletMessage } from '@napplet/core';\nimport type { StorageMessage } from '@napplet/nap/storage/types';\nimport type { SendToNapplet, StatePersistence } from './types.js';\nimport type { SessionRegistry } from './session-registry.js';\nimport type { AclStateContainer } from './acl-state.js';\n\n/** Canonical NAP-STORAGE scope values. `scope` absent ⇔ `\"shared\"`. */\ntype StorageScope = 'shared' | 'instance';\n\n/**\n * Reserved key segment that marks the per-instance sub-namespace inside a\n * napplet's `(dTag, aggregateHash)` bucket. Server-side only — napplets never\n * see it. Used for `scope: \"instance\"` requests (NAP-STORAGE per-instance scope).\n */\nconst INSTANCE_MARKER = '@i/';\n\n/**\n * Build the per-window instance sub-namespace segment: `@i/<windowId>:`.\n *\n * The discriminator is derived from the runtime's `windowId`, which the shell\n * keeps stable across reload / workspace restore, so per-instance storage\n * persists across sessions (the spec's \"Stable\" guarantee). Two distinct\n * windows of the same napplet get distinct segments (the \"Unique\" guarantee).\n * This format is internal and never exposed to the napplet.\n */\nfunction instanceSegment(windowId: string): string {\n return `${INSTANCE_MARKER}${windowId}:`;\n}\n\n/**\n * Build the storage key for a napplet user key.\n *\n * For `scope: \"shared\"` (default) this is byte-identical to the historical key.\n * For `scope: \"instance\"` the runtime folds in the opaque per-window segment —\n * transparently, with no napplet involvement (the napplet only sets the wire\n * field; it never sees the resulting key shape).\n */\nfunction scopedKey(\n dTag: string,\n aggregateHash: string,\n userKey: string,\n instance = false,\n windowId = '',\n): string {\n const segment = instance ? instanceSegment(windowId) : '';\n return `napplet-state:${dTag}:${aggregateHash}:${segment}${userKey}`;\n}\n\n/** Build a legacy scoped key that includes pubkey (for migration reads). */\nfunction legacyScopedKey(pubkey: string, dTag: string, aggregateHash: string, userKey: string): string {\n return `napplet-state:${pubkey}:${dTag}:${aggregateHash}:${userKey}`;\n}\n\n/** Compute byte length of a UTF-8 string without TextEncoder (ES2022-safe). */\nfunction byteLength(str: string): number {\n let bytes = 0;\n for (let i = 0; i < str.length; i++) {\n const c = str.charCodeAt(i);\n if (c < 0x80) bytes += 1;\n else if (c < 0x800) bytes += 2;\n else if (c < 0xd800 || c >= 0xe000) bytes += 3;\n else { i++; bytes += 4; } // surrogate pair\n }\n return bytes;\n}\n\n/**\n * Handle a NIP-5D NAP storage message from a napplet.\n * Routes to the canonical four `@napplet/nap/storage` actions:\n * - `storage.get` → `storage.get.result` `{ value: string | null }`\n * - `storage.set` → `storage.set.result` `{ ok: boolean }` (canonical only checks `error`)\n * - `storage.remove` → `storage.remove.result` `{ ok: boolean }`\n * - `storage.keys` → `storage.keys.result` `{ keys: string[] }`\n *\n * `storage.clear` is NOT in the canonical `@napplet/nap/storage` union (it was a\n * kehto unilateral extension); attempts produce a `storage.clear.result` envelope\n * with an `error` field set. Internal lifecycle cleanup still uses the\n * `cleanupNappState()` helper below — it is not napplet-reachable.\n *\n * **Per-instance scope (NAP-STORAGE / napplet/naps#3):** each request MAY carry\n * `scope: \"shared\" | \"instance\"` (default `\"shared\"`). `\"instance\"` addresses a\n * per-window sub-namespace keyed by `windowId`, so multiple windows of the same\n * napplet keep isolated state; `\"shared\"` (or absent) addresses the napplet-wide\n * namespace, byte-identical to historical behavior. An unrecognized `scope` value\n * is an invalid request and produces a `.result` envelope with `error` set.\n *\n * **Deviation note (Phase 15 to decide):** Set/remove results emit both `ok`\n * (legacy compat) and an `error` field on failure. Canonical `@napplet/nap/storage`\n * only specifies the optional `error`; napplets check `!result.error` for success.\n * Emitting `ok` preserves backward compatibility with existing in-tree callers.\n * Phase 15 (v1.2 release prep) decides whether to drop `ok` pre-release.\n *\n * @param windowId - The window identifier of the requesting napplet\n * @param msg - The NappletMessage containing the storage request\n * @param sendToNapplet - Transport function to send responses\n * @param sessionRegistry - Identity registry for looking up napplet session identity\n * @param aclState - ACL state for quota checks\n * @param statePersistence - State storage backend\n *\n * @example\n * ```ts\n * import { handleStorageNap } from '@kehto/runtime';\n *\n * handleStorageNap(windowId, { type: 'storage.get', id: 'q1', key: 'draft' },\n * sendToNapplet, sessionRegistry, aclState, statePersistence);\n * ```\n */\nexport function handleStorageNap(\n windowId: string,\n msg: NappletMessage,\n sendToNapplet: SendToNapplet,\n sessionRegistry: SessionRegistry,\n aclState: AclStateContainer,\n statePersistence: StatePersistence,\n): void {\n const m = msg as StorageMessage & {\n id?: string;\n key?: string;\n value?: string;\n scope?: string;\n };\n const id = m.id ?? '';\n const action = msg.type.split('.')[1];\n\n function sendResult(payload: Record<string, unknown>): void {\n sendToNapplet(windowId, { type: `${msg.type}.result`, id, ...payload } as NappletMessage);\n }\n function sendErrorNap(error: string): void {\n sendToNapplet(windowId, { type: `${msg.type}.result`, id, error } as NappletMessage);\n }\n\n // Identity lookup via windowId (NIP-5D path)\n const entry = sessionRegistry.getEntryByWindowId(windowId);\n if (!entry) { sendErrorNap('not registered'); return; }\n\n const { dTag, aggregateHash, pubkey } = entry;\n const prefix = `napplet-state:${dTag}:${aggregateHash}:`;\n const legacyPrefix = pubkey ? `napplet-state:${pubkey}:${dTag}:${aggregateHash}:` : '';\n\n // NAP-STORAGE per-call scope. Absent ⇔ \"shared\" (byte-identical to history).\n // An unrecognized value is an invalid request — the shell MUST return an error.\n if (m.scope !== undefined && m.scope !== 'shared' && m.scope !== 'instance') {\n sendErrorNap(`invalid scope: ${String(m.scope)} (expected \"shared\" or \"instance\")`);\n return;\n }\n const scope: StorageScope = m.scope === 'instance' ? 'instance' : 'shared';\n const isInstance = scope === 'instance';\n // The per-window sub-namespace for this request, when instance-scoped.\n const instancePrefix = `${prefix}${instanceSegment(windowId)}`;\n const keyFor = (userKey: string): string =>\n scopedKey(dTag, aggregateHash, userKey, isInstance, windowId);\n\n switch (action) {\n case 'get': {\n const key = m.key as string;\n if (!key) { sendErrorNap('missing key'); return; }\n // Instance scope addresses a fresh per-window namespace with no legacy data —\n // read only the instance key. Shared scope keeps the triple-read migration.\n if (isInstance) {\n sendResult({ value: statePersistence.get(keyFor(key)) });\n break;\n }\n const newKey = scopedKey(dTag, aggregateHash, key);\n // Triple-read for migration: new format, legacy-with-pubkey, old prefix\n let result = statePersistence.get(newKey);\n if (result === null && pubkey) {\n result = statePersistence.get(legacyScopedKey(pubkey, dTag, aggregateHash, key));\n }\n if (result === null && pubkey) {\n result = statePersistence.get(`napp-state:${pubkey}:${dTag}:${aggregateHash}:${key}`);\n }\n // Canonical @napplet/nap/storage: `value: string | null` — null ⇔ missing.\n sendResult({ value: result });\n break;\n }\n case 'set': {\n const key = m.key as string;\n const value = (m.value as string) ?? '';\n if (!key) { sendErrorNap('missing key'); return; }\n const quota = aclState.getStateQuota(pubkey ?? '', dTag, aggregateHash);\n const sk = keyFor(key);\n const newWriteBytes = byteLength(sk + value);\n // Quota is per napplet identity: `prefix` spans both the shared namespace and\n // every per-instance sub-key, so instance writes draw from the same budget.\n const existingBytes = statePersistence.calculateBytes(prefix, key);\n if (existingBytes + newWriteBytes > quota) {\n sendErrorNap(`quota exceeded: napplet state limit is ${quota} bytes`);\n return;\n }\n const success = statePersistence.set(sk, value);\n sendResult({ ok: success });\n break;\n }\n case 'remove': {\n const key = m.key as string;\n if (!key) { sendErrorNap('missing key'); return; }\n statePersistence.remove(keyFor(key));\n // legacyPrefix exists only while `pubkey` is non-empty (legacy AUTH sessions).\n // Suppress \"unused binding\" warnings: we intentionally retain the computation\n // so future migration lands at call sites, not here.\n void legacyPrefix;\n sendResult({ ok: true });\n break;\n }\n case 'clear': {\n // storage.clear was a kehto unilateral extension; it is NOT in the canonical\n // @napplet/nap/storage union. Napplets hitting this branch receive a\n // storage.clear.result envelope with the error field set. Internal lifecycle\n // cleanup uses cleanupNappState() below (not napplet-reachable).\n sendErrorNap('storage.clear is not in @napplet/nap/storage; action not supported');\n break;\n }\n case 'keys': {\n // Instance scope sees only this window's sub-namespace.\n if (isInstance) {\n const instKeys = statePersistence.keys(instancePrefix);\n const instanceKeySet = new Set<string>();\n for (const k of instKeys) {\n instanceKeySet.add(k.startsWith(instancePrefix) ? k.slice(instancePrefix.length) : k);\n }\n sendResult({ keys: Array.from(instanceKeySet) });\n break;\n }\n const newKeys = statePersistence.keys(prefix);\n const legacyKeys = legacyPrefix ? statePersistence.keys(legacyPrefix) : [];\n const userKeySet = new Set<string>();\n // Shared scope excludes per-instance sub-keys (reserved `@i/` marker) so an\n // instance-scoped napplet's per-window data never leaks into a shared listing.\n for (const k of newKeys) {\n if (!k.startsWith(prefix)) { userKeySet.add(k); continue; }\n const userKey = k.slice(prefix.length);\n if (userKey.startsWith(INSTANCE_MARKER)) continue;\n userKeySet.add(userKey);\n }\n for (const k of legacyKeys) userKeySet.add(k.startsWith(legacyPrefix) ? k.slice(legacyPrefix.length) : k);\n sendResult({ keys: Array.from(userKeySet) });\n break;\n }\n default:\n sendErrorNap(`unknown storage action: ${action}`);\n break;\n }\n}\n\n/**\n * Remove all state entries for a napplet identity.\n * Clears both new-format and legacy-format keys for completeness.\n * Used during napplet cleanup when a window is closed.\n *\n * @param pubkey - The napplet's pubkey (needed for legacy key cleanup)\n * @param dTag - The napplet's dTag\n * @param aggregateHash - The napplet's build hash\n * @param statePersistence - State storage backend\n *\n * @example\n * ```ts\n * import { cleanupNappState } from '@kehto/runtime';\n *\n * cleanupNappState(pubkey, dTag, aggregateHash, statePersistence);\n * ```\n */\nexport function cleanupNappState(\n pubkey: string,\n dTag: string,\n aggregateHash: string,\n statePersistence: StatePersistence,\n): void {\n // Clear new format\n const prefix = `napplet-state:${dTag}:${aggregateHash}:`;\n statePersistence.clear(prefix);\n // Clear legacy format (includes pubkey)\n const legacyPrefix = `napplet-state:${pubkey}:${dTag}:${aggregateHash}:`;\n statePersistence.clear(legacyPrefix);\n}\n","import type { NappletMessage } from '@napplet/core';\n\nimport type { AclStateContainer } from './acl-state.js';\nimport type { SessionRegistry } from './session-registry.js';\nimport { handleStorageNap } from './state-handler.js';\nimport type { RuntimeAdapter, ServiceRegistry } from './types.js';\n\ntype DomainHandler = (windowId: string, msg: NappletMessage) => void;\n\ntype RuntimeDomainContext = {\n hooks: RuntimeAdapter;\n serviceRegistry: ServiceRegistry;\n sessionRegistry: SessionRegistry;\n aclState: AclStateContainer;\n};\n\nexport type RuntimeDomainHandlers = {\n storage: DomainHandler;\n media: DomainHandler;\n keys: DomainHandler;\n notify: DomainHandler;\n theme: DomainHandler;\n config: DomainHandler;\n resource: DomainHandler;\n cvm: DomainHandler;\n outbox: DomainHandler;\n upload: DomainHandler;\n intent: DomainHandler;\n};\n\nconst THEME_FALLBACK_DEFAULT = {\n colors: { background: '#0a0a0a', text: '#e0e0e0', primary: '#7aa2f7' },\n} as const;\n\nexport function createRuntimeDomainHandlers(context: RuntimeDomainContext): RuntimeDomainHandlers {\n return {\n storage: (windowId, msg) => handleStorageMessage(context, windowId, msg),\n media: (windowId, msg) => handleMediaMessage(context, windowId, msg),\n keys: (windowId, msg) => handleKeysMessage(context, windowId, msg),\n notify: (windowId, msg) => handleNotifyMessage(context, windowId, msg),\n theme: (windowId, msg) => handleThemeMessage(context, windowId, msg),\n config: (windowId, msg) => handleServiceOnlyMessage(context, 'config', windowId, msg),\n resource: (windowId, msg) => handleServiceOnlyMessage(context, 'resource', windowId, msg),\n cvm: (windowId, msg) => handleServiceOnlyMessage(context, 'cvm', windowId, msg),\n outbox: (windowId, msg) => handleServiceOnlyMessage(context, 'outbox', windowId, msg),\n upload: (windowId, msg) => handleServiceOnlyMessage(context, 'upload', windowId, msg),\n intent: (windowId, msg) => handleServiceOnlyMessage(context, 'intent', windowId, msg),\n };\n}\n\nfunction handleStorageMessage(context: RuntimeDomainContext, windowId: string, msg: NappletMessage): void {\n const { aclState, hooks, sessionRegistry } = context;\n handleStorageNap(windowId, msg, hooks.sendToNapplet, sessionRegistry, aclState, hooks.statePersistence);\n}\n\nfunction handleMediaMessage(context: RuntimeDomainContext, windowId: string, msg: NappletMessage): void {\n const { hooks, serviceRegistry } = context;\n const mediaService = serviceRegistry['media'];\n if (mediaService) {\n mediaService.handleMessage(windowId, msg, (resp: NappletMessage) => hooks.sendToNapplet(windowId, resp));\n return;\n }\n if (msg.type === 'media.session.create') {\n const m = msg as NappletMessage & { id?: string; owner?: string; sessionId?: string };\n if (m.owner !== 'napplet' && m.owner !== 'shell') {\n hooks.sendToNapplet(windowId, {\n type: 'media.session.create.result',\n id: m.id ?? '',\n error: 'missing owner',\n } as NappletMessage);\n return;\n }\n if (m.owner === 'shell') {\n hooks.sendToNapplet(windowId, {\n type: 'media.session.create.result',\n id: m.id ?? '',\n owner: 'shell',\n error: 'unsupported owner mode',\n } as NappletMessage);\n return;\n }\n hooks.sendToNapplet(windowId, {\n type: 'media.session.create.result',\n id: m.id ?? '',\n sessionId: m.sessionId ?? '',\n owner: m.owner,\n } as NappletMessage);\n }\n}\n\nfunction handleKeysMessage(context: RuntimeDomainContext, windowId: string, msg: NappletMessage): void {\n const { hooks, serviceRegistry } = context;\n const keysService = serviceRegistry['keys'];\n if (keysService) {\n keysService.handleMessage(windowId, msg, (resp: NappletMessage) => hooks.sendToNapplet(windowId, resp));\n return;\n }\n if (msg.type === 'keys.forward') {\n forwardHotkey(hooks, msg);\n return;\n }\n if (msg.type === 'keys.registerAction') sendRegisterActionResult(hooks, windowId, msg);\n}\n\nfunction forwardHotkey(hooks: RuntimeAdapter, msg: NappletMessage): void {\n const m = msg as NappletMessage & {\n key?: string; code?: string;\n ctrl?: boolean; alt?: boolean; shift?: boolean; meta?: boolean;\n };\n hooks.hotkeys.executeHotkeyFromForward({\n key: m.key ?? '',\n code: m.code ?? '',\n ctrlKey: !!m.ctrl,\n altKey: !!m.alt,\n shiftKey: !!m.shift,\n metaKey: !!m.meta,\n });\n}\n\nfunction sendRegisterActionResult(hooks: RuntimeAdapter, windowId: string, msg: NappletMessage): void {\n const m = msg as NappletMessage & {\n id?: string; action?: { id: string; defaultKey?: string };\n };\n hooks.sendToNapplet(windowId, {\n type: 'keys.registerAction.result',\n id: m.id ?? '',\n actionId: m.action?.id ?? '',\n ...(m.action?.defaultKey ? { binding: m.action.defaultKey } : {}),\n } as NappletMessage);\n}\n\nfunction handleNotifyMessage(context: RuntimeDomainContext, windowId: string, msg: NappletMessage): void {\n const { hooks, serviceRegistry } = context;\n const notifyService = serviceRegistry['notify'];\n if (notifyService) {\n notifyService.handleMessage(windowId, msg, (resp: NappletMessage) => hooks.sendToNapplet(windowId, resp));\n return;\n }\n if (msg.type === 'notify.send') {\n const m = msg as NappletMessage & { id?: string };\n hooks.sendToNapplet(windowId, { type: 'notify.send.result', id: m.id ?? '', notificationId: `shell-${Date.now()}` } as NappletMessage);\n } else if (msg.type === 'notify.permission.request') {\n const m = msg as NappletMessage & { id?: string };\n hooks.sendToNapplet(windowId, { type: 'notify.permission.result', id: m.id ?? '', granted: true } as NappletMessage);\n }\n}\n\nfunction handleThemeMessage(context: RuntimeDomainContext, windowId: string, msg: NappletMessage): void {\n const { hooks, serviceRegistry } = context;\n const themeService = serviceRegistry['theme'];\n if (themeService) {\n themeService.handleMessage(windowId, msg, (resp: NappletMessage) => hooks.sendToNapplet(windowId, resp));\n return;\n }\n if (msg.type === 'theme.get') {\n const m = msg as NappletMessage & { id?: string };\n hooks.sendToNapplet(windowId, {\n type: 'theme.get.result',\n id: m.id ?? '',\n theme: THEME_FALLBACK_DEFAULT,\n } as NappletMessage);\n }\n}\n\nfunction handleServiceOnlyMessage(\n context: RuntimeDomainContext,\n name: 'config' | 'resource' | 'cvm' | 'outbox' | 'upload' | 'intent',\n windowId: string,\n msg: NappletMessage,\n): void {\n const service = context.serviceRegistry[name];\n if (!service) return;\n service.handleMessage(windowId, msg, (resp: NappletMessage) => context.hooks.sendToNapplet(windowId, resp));\n}\n","// @kehto/runtime — Browser-agnostic protocol engine for the napplet protocol.\n\nexport type {\n RuntimeAdapter,\n SendToNapplet,\n RelayPoolAdapter,\n RelaySubscriptionHandle,\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 ConsentRequest,\n ConsentHandler,\n SessionEntry,\n NappKeyEntry, // @deprecated — use SessionEntry\n PendingUpdate,\n PendingUpdateNotifier,\n ManifestCacheEntry,\n AclEntryExternal,\n AclCheckEvent,\n FirewallPersistence,\n FirewallEvent,\n ServiceHandler,\n ServiceRegistry,\n CompatibilityReport,\n ServiceInfo,\n ShellSecretPersistence,\n GuidPersistence,\n HashVerifierAdapter,\n VerificationCacheEntry,\n RuntimeConfigOverrides,\n NappletMessage,\n} from './types.js';\n\nexport { createEnforceGate, createNapEnforceGate, resolveCapabilitiesNap, formatDenialReason } from './enforce.js';\nexport type { EnforceResult, EnforceConfig, NapEnforceConfig, IdentityResolver, AclChecker, NapMessage } from './enforce.js';\n\nexport { createSessionRegistry, createNappKeyRegistry } from './session-registry.js';\nexport type { SessionRegistry, NappKeyRegistry } from './session-registry.js';\n\nexport { createAclState } from './acl-state.js';\nexport type { AclStateContainer } from './acl-state.js';\n\nexport { createFirewallState } from './firewall-state.js';\nexport type { FirewallStateContainer } from './firewall-state.js';\n\nexport { createManifestCache } from './manifest-cache.js';\nexport type { ManifestCache } from './manifest-cache.js';\n\nexport { createReplayDetector } from './replay.js';\nexport type { ReplayDetector } from './replay.js';\n\nexport { createEventBuffer, matchesFilter, matchesAnyFilter, RING_BUFFER_SIZE } from './event-buffer.js';\nexport type { EventBuffer, SubscriptionEntry } from './event-buffer.js';\n\nexport { createRuntime } from './runtime.js';\nexport type { Runtime } from './runtime.js';\n\nexport { handleStorageNap, cleanupNappState } from './state-handler.js';\n\nexport { routeServiceMessage, notifyServiceWindowDestroyed } from './service-dispatch.js';\n\nexport type { Capability } from '@kehto/acl/capabilities';\nexport { ALL_CAPABILITIES } from '@kehto/acl/capabilities';\nexport type { ServiceDescriptor } from './types.js';\n","\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 */\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 *\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 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 // 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\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 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 ResourceCancelRequest,\n ResourceBytesResult,\n ResourceBytesError,\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","/**\n * audio-service.ts — Audio source registry as a ServiceHandler.\n *\n * Tracks which napplet windows are producing audio. Shell hosts wire this\n * into the runtime via registerService('audio', createAudioService(opts)).\n * Browser-agnostic — no DOM, no window, no postMessage.\n */\n\nimport type { NappletMessage } from '@napplet/core';\n// DRIFT-CORE-06 — Phase 11-deviation: ServiceDescriptor dropped from @napplet/core\n// v0.2.0+ (napplet phase-81). Re-exported from @kehto/runtime (canonical home after Phase 24 DRIFT-01).\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\nimport type { AudioSource, AudioServiceOptions } from './types.js';\n\n/** Audio service version — follows semver. */\nconst AUDIO_SERVICE_VERSION = '1.0.0';\n\n/**\n * Create an audio service handler.\n *\n * The audio service is a state registry that tracks active audio sources\n * per napplet window. Napplets announce audio state via `audio:*` topic\n * events; the service tracks sources and can relay mute commands back.\n *\n * @param options - Optional configuration (onChange callback for UI updates)\n * @returns A ServiceHandler to register with the runtime\n *\n * @example\n * ```ts\n * import { createAudioService } from '@kehto/services';\n *\n * const audio = createAudioService({\n * onChange: (sources) => {\n * // Update UI with current audio sources\n * for (const [windowId, source] of sources) {\n * console.log(`${source.title} (${source.muted ? 'muted' : 'playing'})`);\n * }\n * },\n * });\n *\n * runtime.registerService('audio', audio);\n * ```\n */\nexport function createAudioService(options?: AudioServiceOptions): ServiceHandler {\n const sources = new Map<string, AudioSource>();\n const onChange = options?.onChange;\n\n function notify(): void {\n onChange?.(new Map(sources));\n }\n\n const descriptor: ServiceDescriptor = {\n name: 'audio',\n version: AUDIO_SERVICE_VERSION,\n description: 'Audio source registry — tracks active audio sources per napplet window',\n };\n\n return {\n descriptor,\n\n handleMessage(windowId: string, message: NappletMessage, send: (msg: NappletMessage) => void): void {\n if (message.type !== 'inc.emit') return;\n const incMessage = message as NappletMessage & { topic?: unknown; payload?: unknown };\n const topic = typeof incMessage.topic === 'string' ? incMessage.topic : undefined;\n if (!topic?.startsWith('audio:')) return;\n\n const action = topic.slice(6); // 'audio:'.length === 6\n const payload = incMessage.payload && typeof incMessage.payload === 'object'\n ? incMessage.payload as Record<string, unknown>\n : {};\n\n switch (action) {\n case 'register': {\n const nappletClass = typeof payload.nappletClass === 'string' ? payload.nappletClass : '';\n const title = typeof payload.title === 'string' ? payload.title : '';\n sources.set(windowId, { windowId, nappletClass, title, muted: false });\n notify();\n break;\n }\n\n case 'unregister': {\n if (sources.delete(windowId)) {\n notify();\n }\n break;\n }\n\n case 'state-changed': {\n const source = sources.get(windowId);\n if (!source) return;\n if (typeof payload.title === 'string') {\n source.title = payload.title;\n }\n notify();\n break;\n }\n\n case 'mute': {\n const targetWindowId = typeof payload.windowId === 'string'\n ? payload.windowId\n : windowId;\n const muted = payload.muted === true;\n\n const source = sources.get(targetWindowId);\n if (source) {\n source.muted = muted;\n notify();\n }\n\n send({ type: 'inc.event', topic: 'napplet:audio-muted', payload: { muted } } as NappletMessage);\n break;\n }\n\n default:\n // Unknown audio action — ignore\n break;\n }\n },\n\n onWindowDestroyed(windowId: string): void {\n if (sources.delete(windowId)) {\n notify();\n }\n },\n };\n}\n","\nimport type { NappletMessage } from '@napplet/core';\n// DRIFT-CORE-06 — Phase 11-deviation: ServiceDescriptor dropped from @napplet/core\n// v0.2.0+ (napplet phase-81). Re-exported from @kehto/runtime (canonical home after Phase 24 DRIFT-01).\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\nimport type { Notification, NotificationServiceOptions } from './types.js';\n\n/** Notification service version — follows semver. */\nconst NOTIFICATION_SERVICE_VERSION = '1.0.0';\n\n/** Default maximum notifications per window. */\nconst DEFAULT_MAX_PER_WINDOW = 100;\n\n/** Counter for generating unique notification IDs. */\nlet idCounter = 0;\n\n/**\n * Generate a unique notification ID.\n */\nfunction generateId(): string {\n idCounter++;\n return `notif-${Date.now()}-${idCounter}`;\n}\n\ninterface NotificationStore {\n notifications: Map<string, Notification[]>;\n onChange?: (notifications: Notification[]) => void;\n maxPerWindow: number;\n}\n\nfunction getAllNotifications(store: NotificationStore): Notification[] {\n const all: Notification[] = [];\n for (const windowNotifs of store.notifications.values()) {\n all.push(...windowNotifs);\n }\n return all;\n}\n\nfunction notify(store: NotificationStore): void {\n store.onChange?.(getAllNotifications(store));\n}\n\nfunction getWindowNotifications(store: NotificationStore, windowId: string): Notification[] {\n let list = store.notifications.get(windowId);\n if (!list) {\n list = [];\n store.notifications.set(windowId, list);\n }\n return list;\n}\n\nfunction enforceLimit(store: NotificationStore, list: Notification[]): void {\n while (list.length > store.maxPerWindow) {\n list.shift();\n }\n}\n\nfunction findById(store: NotificationStore, id: string): [string, Notification, number] | undefined {\n for (const [windowId, list] of store.notifications) {\n const index = list.findIndex((n) => n.id === id);\n if (index !== -1) {\n return [windowId, list[index], index];\n }\n }\n return undefined;\n}\n\nfunction createNotification(\n store: NotificationStore,\n windowId: string,\n title: string,\n body: string,\n): Notification {\n const notification: Notification = {\n id: generateId(),\n windowId,\n title,\n body,\n read: false,\n createdAt: Math.floor(Date.now() / 1000),\n };\n const list = getWindowNotifications(store, windowId);\n list.push(notification);\n enforceLimit(store, list);\n notify(store);\n return notification;\n}\n\nfunction dismissNotification(store: NotificationStore, id: string): void {\n const found = findById(store, id);\n if (!found) return;\n const [foundWindowId, , index] = found;\n const list = store.notifications.get(foundWindowId);\n if (!list) return;\n list.splice(index, 1);\n if (list.length === 0) store.notifications.delete(foundWindowId);\n notify(store);\n}\n\nfunction markNotificationRead(store: NotificationStore, id: string): void {\n const found = findById(store, id);\n if (!found) return;\n const [, notification] = found;\n if (!notification.read) {\n notification.read = true;\n notify(store);\n }\n}\n\nfunction handleNotifyEnvelope(\n store: NotificationStore,\n windowId: string,\n action: string,\n msg: NappletMessage & Record<string, unknown>,\n send: (msg: NappletMessage) => void,\n): void {\n switch (action) {\n case 'create': {\n const title = typeof msg.title === 'string' ? msg.title : '';\n const body = typeof msg.body === 'string' ? msg.body : '';\n const notification = createNotification(store, windowId, title, body);\n send({ type: 'notify.created', id: notification.id } as NappletMessage);\n break;\n }\n\n case 'dismiss': {\n const notifId = typeof msg.notificationId === 'string' ? msg.notificationId : '';\n if (notifId) dismissNotification(store, notifId);\n break;\n }\n\n case 'read': {\n const notifId = typeof msg.notificationId === 'string' ? msg.notificationId : '';\n if (notifId) markNotificationRead(store, notifId);\n break;\n }\n\n case 'list': {\n const windowNotifs = store.notifications.get(windowId) ?? [];\n send({ type: 'notify.listed', notifications: windowNotifs } as NappletMessage);\n break;\n }\n\n default:\n break;\n }\n}\n\nfunction handleIncNotification(\n store: NotificationStore,\n windowId: string,\n action: string,\n payload: Record<string, unknown>,\n send: (msg: NappletMessage) => void,\n): void {\n switch (action) {\n case 'create': {\n const title = typeof payload.title === 'string' ? payload.title : '';\n const body = typeof payload.body === 'string' ? payload.body : '';\n const notification = createNotification(store, windowId, title, body);\n send({ type: 'inc.event', topic: 'notifications:created', payload: { id: notification.id } } as NappletMessage);\n break;\n }\n\n case 'dismiss': {\n const id = typeof payload.id === 'string' ? payload.id : '';\n if (id) dismissNotification(store, id);\n break;\n }\n\n case 'read': {\n const id = typeof payload.id === 'string' ? payload.id : '';\n if (id) markNotificationRead(store, id);\n break;\n }\n\n case 'list': {\n const windowNotifs = store.notifications.get(windowId) ?? [];\n send({ type: 'inc.event', topic: 'notifications:listed', payload: { notifications: windowNotifs } } as NappletMessage);\n break;\n }\n\n default:\n break;\n }\n}\n\n/**\n * Create a notification service handler.\n *\n * The notification service is a state registry that tracks notifications\n * per napplet window. Napplets create and manage notifications via\n * `notifications:*` topic events; the shell host controls presentation\n * via the onChange callback.\n *\n * @param options - Optional configuration (onChange callback, maxPerWindow limit)\n * @returns A ServiceHandler to register with the runtime\n *\n * @example\n * ```ts\n * import { createNotificationService } from '@kehto/services';\n *\n * const notifications = createNotificationService({\n * onChange: (list) => {\n * const unread = list.filter(n => !n.read);\n * updateBadge(unread.length);\n * },\n * maxPerWindow: 50,\n * });\n *\n * runtime.registerService('notifications', notifications);\n * ```\n */\nexport function createNotificationService(options?: NotificationServiceOptions): ServiceHandler {\n const store: NotificationStore = {\n notifications: new Map<string, Notification[]>(),\n onChange: options?.onChange,\n maxPerWindow: options?.maxPerWindow ?? DEFAULT_MAX_PER_WINDOW,\n };\n\n const descriptor: ServiceDescriptor = {\n name: 'notifications',\n version: NOTIFICATION_SERVICE_VERSION,\n description: 'Notification state registry — tracks notifications per napplet window',\n };\n\n return {\n descriptor,\n\n handleMessage(windowId: string, message: NappletMessage, send: (msg: NappletMessage) => void): void {\n const msg = message as NappletMessage & Record<string, unknown>;\n\n if (message.type.startsWith('notify.')) {\n handleNotifyEnvelope(store, windowId, message.type.slice(7), msg, send);\n return;\n }\n\n if (message.type !== 'inc.emit') return;\n const topic = msg.topic as string | undefined;\n if (!topic?.startsWith('notifications:')) return;\n\n const payload = ((msg.payload ?? {}) as Record<string, unknown>);\n handleIncNotification(store, windowId, topic.slice(14), payload, send);\n },\n\n onWindowDestroyed(windowId: string): void {\n if (store.notifications.delete(windowId)) {\n notify(store);\n }\n },\n };\n}\n","/**\n * identity-service.ts — NIP-5D identity nap reference service.\n *\n * MIGRATION from signer-service (v1.1 -> v1.2):\n * - signer.getPublicKey -> identity.getPublicKey (same shell state)\n * - signer.getRelays -> identity.getRelays (same shell state)\n * - signer.signEvent -> DELETED (no napplet-visible path; shell signs\n * internally inside relay.publish)\n * - signer.nip04.encrypt/decrypt -> DELETED\n * - signer.nip44.encrypt/decrypt -> DELETED (shell encrypts internally\n * inside relay.publishEncrypted)\n *\n * Identity is strictly read-only per NAP-IDENTITY: napplets learn *about* the\n * user but cannot act *as* the user — no signing, encryption, or decryption.\n * (An `identity.decrypt` capability was briefly added in v1.8 and removed as a\n * spec violation; decryption belongs to the runtime, inline over the wire.)\n *\n * Handles 9 identity.* request types from @napplet/nap/identity. getPublicKey\n * and getRelays return real values sourced from hooks.auth.getSigner(); the\n * remaining read-only queries can be backed by optional host providers. When a\n * provider is absent, the service keeps returning spec-shaped empty results.\n */\n\nimport type { NappletMessage } from '@napplet/core';\nimport type { ServiceHandler, Signer } from '@kehto/runtime';\nimport type {\n IdentityGetBadgesMessage,\n IdentityGetBlockedMessage,\n IdentityGetFollowsMessage,\n IdentityGetListMessage,\n IdentityGetMutesMessage,\n IdentityGetProfileMessage,\n IdentityGetPublicKeyResultMessage,\n IdentityGetRelaysResultMessage,\n IdentityGetProfileResultMessage,\n IdentityGetFollowsResultMessage,\n IdentityGetListResultMessage,\n IdentityGetZapsResultMessage,\n IdentityGetZapsMessage,\n IdentityGetMutesResultMessage,\n IdentityGetBlockedResultMessage,\n IdentityGetBadgesResultMessage,\n RelayPermission,\n} from '@napplet/nap/identity/types';\n\n/** Identity service version — follows semver. */\nconst IDENTITY_SERVICE_VERSION = '1.0.0';\n\n/** A value that may be returned synchronously or through a Promise. */\nexport type MaybePromise<T> = T | Promise<T>;\n\ntype IdentityProviderResult = NappletMessage & { error?: string };\ntype SendIdentityMessage = (msg: NappletMessage) => void;\n\nfunction sendProviderError<T extends IdentityProviderResult>(\n send: SendIdentityMessage,\n result: T,\n fallback: string,\n err: unknown,\n): void {\n send({\n ...result,\n error: (err as Error)?.message ?? fallback,\n });\n}\n\nasync function getCurrentPubkey(options: IdentityServiceOptions): Promise<string> {\n const currentSigner = options.getSigner();\n if (!currentSigner?.getPublicKey) return '';\n try {\n return (await currentSigner.getPublicKey()) ?? '';\n } catch {\n return '';\n }\n}\n\nfunction sendOptionalProviderResult<T extends IdentityProviderResult>(\n options: IdentityServiceOptions,\n send: SendIdentityMessage,\n fallbackResult: T,\n errorFallback: string,\n buildResult?: (pubkey: string) => MaybePromise<T>,\n): void {\n if (!buildResult) {\n send(fallbackResult);\n return;\n }\n\n Promise.resolve(getCurrentPubkey(options))\n .then((pubkey) => buildResult(pubkey))\n .then((result) => send(result))\n .catch((err: unknown) => sendProviderError(send, fallbackResult, errorFallback, err));\n}\n\n/**\n * Options for creating the identity service.\n *\n * @example\n * ```ts\n * const identityService = createIdentityService({\n * getSigner: () => window.nostr ?? null,\n * });\n * runtime.registerService('identity', identityService);\n * ```\n */\nexport interface IdentityServiceOptions {\n /**\n * Return the NIP-07-compatible signer (or null) used to resolve\n * identity.getPublicKey / identity.getRelays. Called on every request —\n * availability can change dynamically.\n */\n getSigner: () => Signer | null;\n\n /**\n * Optional host-backed profile lookup for the current user.\n *\n * Kehto does not query relays itself; hosts that already maintain kind-0\n * metadata can provide it here without replacing the whole identity service.\n *\n * @param pubkey - Current signer pubkey, or \"\" when no signer is connected.\n * @param message - Original identity.getProfile request envelope.\n * @returns Profile metadata, or null when unavailable.\n */\n getProfile?: (\n pubkey: string,\n message: IdentityGetProfileMessage,\n ) => MaybePromise<IdentityGetProfileResultMessage['profile']>;\n\n /**\n * Optional host-backed follow-list lookup for the current user.\n *\n * @param pubkey - Current signer pubkey, or \"\" when no signer is connected.\n * @param message - Original identity.getFollows request envelope.\n * @returns Hex-encoded followed pubkeys.\n */\n getFollows?: (\n pubkey: string,\n message: IdentityGetFollowsMessage,\n ) => MaybePromise<IdentityGetFollowsResultMessage['pubkeys']>;\n\n /**\n * Optional host-backed categorized-list lookup for the current user.\n *\n * @param listType - Requested list category from the wire envelope.\n * @param pubkey - Current signer pubkey, or \"\" when no signer is connected.\n * @param message - Original identity.getList request envelope.\n * @returns List entries.\n */\n getList?: (\n listType: string,\n pubkey: string,\n message: IdentityGetListMessage,\n ) => MaybePromise<IdentityGetListResultMessage['entries']>;\n\n /**\n * Optional host-backed zap receipt lookup for the current user.\n *\n * @param pubkey - Current signer pubkey, or \"\" when no signer is connected.\n * @param message - Original identity.getZaps request envelope.\n * @returns Zap receipts.\n */\n getZaps?: (\n pubkey: string,\n message: IdentityGetZapsMessage,\n ) => MaybePromise<IdentityGetZapsResultMessage['zaps']>;\n\n /**\n * Optional host-backed mute-list lookup for the current user.\n *\n * @param pubkey - Current signer pubkey, or \"\" when no signer is connected.\n * @param message - Original identity.getMutes request envelope.\n * @returns Hex-encoded muted pubkeys.\n */\n getMutes?: (\n pubkey: string,\n message: IdentityGetMutesMessage,\n ) => MaybePromise<IdentityGetMutesResultMessage['pubkeys']>;\n\n /**\n * Optional host-backed block-list lookup for the current user.\n *\n * @param pubkey - Current signer pubkey, or \"\" when no signer is connected.\n * @param message - Original identity.getBlocked request envelope.\n * @returns Hex-encoded blocked pubkeys.\n */\n getBlocked?: (\n pubkey: string,\n message: IdentityGetBlockedMessage,\n ) => MaybePromise<IdentityGetBlockedResultMessage['pubkeys']>;\n\n /**\n * Optional host-backed badge lookup for the current user.\n *\n * @param pubkey - Current signer pubkey, or \"\" when no signer is connected.\n * @param message - Original identity.getBadges request envelope.\n * @returns Badges awarded to the user.\n */\n getBadges?: (\n pubkey: string,\n message: IdentityGetBadgesMessage,\n ) => MaybePromise<IdentityGetBadgesResultMessage['badges']>;\n}\n\nfunction sendIdentityError(\n send: SendIdentityMessage,\n id: string,\n typeBase: string,\n error: string,\n): void {\n send({ type: `${typeBase}.error`, id, error } as NappletMessage);\n}\n\nfunction sendSignerError(\n send: SendIdentityMessage,\n id: string,\n typeBase: string,\n fallback: string,\n err: unknown,\n): void {\n sendIdentityError(send, id, typeBase, (err as Error)?.message ?? fallback);\n}\n\nfunction handleGetPublicKey(options: IdentityServiceOptions, id: string, send: SendIdentityMessage): void {\n const signer = options.getSigner();\n if (!signer) {\n const result: IdentityGetPublicKeyResultMessage = {\n type: 'identity.getPublicKey.result',\n id,\n pubkey: '',\n };\n send(result);\n return;\n }\n\n Promise.resolve(signer.getPublicKey?.())\n .then((pubkey) => {\n const result: IdentityGetPublicKeyResultMessage = {\n type: 'identity.getPublicKey.result',\n id,\n pubkey: (pubkey as string) ?? '',\n };\n send(result);\n })\n .catch((err: unknown) => sendSignerError(send, id, 'identity.getPublicKey', 'getPublicKey failed', err));\n}\n\nfunction handleGetRelays(options: IdentityServiceOptions, id: string, send: SendIdentityMessage): void {\n const signer = options.getSigner();\n if (!signer) {\n sendIdentityError(send, id, 'identity.getRelays', 'no signer configured');\n return;\n }\n\n Promise.resolve(signer.getRelays?.() ?? {})\n .then((relays) => {\n const result: IdentityGetRelaysResultMessage = {\n type: 'identity.getRelays.result',\n id,\n relays: relays as Record<string, RelayPermission>,\n };\n send(result);\n })\n .catch((err: unknown) => sendSignerError(send, id, 'identity.getRelays', 'getRelays failed', err));\n}\n\nfunction handleReadProvider(\n options: IdentityServiceOptions,\n id: string,\n message: NappletMessage,\n send: SendIdentityMessage,\n): boolean {\n switch (message.type) {\n case 'identity.getProfile':\n sendOptionalProviderResult(\n options,\n send,\n { type: 'identity.getProfile.result', id, profile: null },\n 'getProfile failed',\n options.getProfile\n ? async (pubkey): Promise<IdentityGetProfileResultMessage> => ({\n type: 'identity.getProfile.result',\n id,\n profile: await options.getProfile!(pubkey, message as IdentityGetProfileMessage),\n })\n : undefined,\n );\n return true;\n\n case 'identity.getFollows':\n sendOptionalProviderResult(\n options,\n send,\n { type: 'identity.getFollows.result', id, pubkeys: [] },\n 'getFollows failed',\n options.getFollows\n ? async (pubkey): Promise<IdentityGetFollowsResultMessage> => ({\n type: 'identity.getFollows.result',\n id,\n pubkeys: await options.getFollows!(pubkey, message as IdentityGetFollowsMessage),\n })\n : undefined,\n );\n return true;\n\n case 'identity.getList':\n handleGetList(options, id, message as IdentityGetListMessage, send);\n return true;\n\n case 'identity.getZaps':\n sendOptionalProviderResult(\n options,\n send,\n { type: 'identity.getZaps.result', id, zaps: [] },\n 'getZaps failed',\n options.getZaps\n ? async (pubkey): Promise<IdentityGetZapsResultMessage> => ({\n type: 'identity.getZaps.result',\n id,\n zaps: await options.getZaps!(pubkey, message as IdentityGetZapsMessage),\n })\n : undefined,\n );\n return true;\n\n case 'identity.getMutes':\n sendOptionalProviderResult(\n options,\n send,\n { type: 'identity.getMutes.result', id, pubkeys: [] },\n 'getMutes failed',\n options.getMutes\n ? async (pubkey): Promise<IdentityGetMutesResultMessage> => ({\n type: 'identity.getMutes.result',\n id,\n pubkeys: await options.getMutes!(pubkey, message as IdentityGetMutesMessage),\n })\n : undefined,\n );\n return true;\n\n case 'identity.getBlocked':\n sendOptionalProviderResult(\n options,\n send,\n { type: 'identity.getBlocked.result', id, pubkeys: [] },\n 'getBlocked failed',\n options.getBlocked\n ? async (pubkey): Promise<IdentityGetBlockedResultMessage> => ({\n type: 'identity.getBlocked.result',\n id,\n pubkeys: await options.getBlocked!(pubkey, message as IdentityGetBlockedMessage),\n })\n : undefined,\n );\n return true;\n\n case 'identity.getBadges':\n sendOptionalProviderResult(\n options,\n send,\n { type: 'identity.getBadges.result', id, badges: [] },\n 'getBadges failed',\n options.getBadges\n ? async (pubkey): Promise<IdentityGetBadgesResultMessage> => ({\n type: 'identity.getBadges.result',\n id,\n badges: await options.getBadges!(pubkey, message as IdentityGetBadgesMessage),\n })\n : undefined,\n );\n return true;\n\n default:\n return false;\n }\n}\n\nfunction handleGetList(\n options: IdentityServiceOptions,\n id: string,\n message: IdentityGetListMessage,\n send: SendIdentityMessage,\n): void {\n sendOptionalProviderResult(\n options,\n send,\n { type: 'identity.getList.result', id, entries: [] },\n 'getList failed',\n options.getList\n ? async (pubkey): Promise<IdentityGetListResultMessage> => ({\n type: 'identity.getList.result',\n id,\n entries: await options.getList!(message.listType, pubkey, message),\n })\n : undefined,\n );\n}\n\nfunction handleIdentityServiceMessage(\n options: IdentityServiceOptions,\n message: NappletMessage,\n send: SendIdentityMessage,\n): void {\n const id = (message as NappletMessage & { id?: string }).id ?? '';\n\n switch (message.type) {\n case 'identity.getPublicKey':\n // Per NIP-5D, no signer resolves to the empty-pubkey sentinel, not an error.\n handleGetPublicKey(options, id, send);\n return;\n\n case 'identity.getRelays':\n handleGetRelays(options, id, send);\n return;\n\n default:\n if (!handleReadProvider(options, id, message, send)) {\n sendIdentityError(send, id, message.type, `Unknown identity method: ${message.type}`);\n }\n }\n}\n\n/**\n * Create an identity service that handles NIP-5D identity.* envelope messages.\n *\n * Supports the 9 read-only identity.* request types from @napplet/nap/identity.\n * The two nostr-info queries (getPublicKey, getRelays) resolve through the\n * caller-supplied signer; the remaining read-only queries resolve through\n * optional host providers or return default/empty payloads with spec-correct\n * envelope shapes so napplets always receive a result envelope.\n *\n * @param options - Identity service configuration (getSigner)\n * @returns A ServiceHandler ready for runtime.registerService('identity', handler)\n *\n * @example\n * ```ts\n * import { createIdentityService } from '@kehto/services';\n *\n * const identity = createIdentityService({\n * getSigner: () => mySignerAdapter,\n * });\n * runtime.registerService('identity', identity);\n * ```\n */\nexport function createIdentityService(options: IdentityServiceOptions): ServiceHandler {\n return {\n descriptor: {\n name: 'identity',\n version: IDENTITY_SERVICE_VERSION,\n description: 'NIP-5D identity NAP reference handler (9 read-only identity queries)',\n },\n\n handleMessage(\n _windowId: string,\n message: NappletMessage,\n send: (msg: NappletMessage) => void,\n ): void {\n handleIdentityServiceMessage(options, message, send);\n },\n\n // Identity service has no per-window state to clean up.\n onWindowDestroyed(_windowId: string): void {\n /* no-op */\n },\n };\n}\n","/**\n * relay-pool-service.ts — Relay pool as a ServiceHandler.\n *\n * Wraps an existing relay pool implementation (subscribe, publish,\n * selectRelayTier, isAvailable) as a ServiceHandler that receives\n * relay NAP envelope messages and manages subscription lifecycle.\n *\n * Handles: relay.subscribe, relay.close, relay.publish, relay.publishEncrypted.\n *\n * Note on `relay.publishEncrypted`: the canonical napplet→shell path routes\n * through @kehto/runtime handleRelayMessage, which performs shell-internal\n * NIP-44/NIP-04 encryption via the signer, then synthesizes a relay.publish\n * envelope handed to this service. The publishEncrypted branch here is a\n * fallback for alternate wirings; by the time the service sees the\n * envelope, content MUST already be ciphertext (the service never encrypts\n * or decrypts).\n */\n\nimport type { NostrEvent, NostrFilter, NappletMessage } from '@napplet/core';\nimport type { ServiceHandler } from '@kehto/runtime';\n\n// Timer globals available in all JS runtimes\ndeclare function setTimeout(callback: () => void, ms: number): unknown;\ndeclare function clearTimeout(id: unknown): void;\n\n/** EOSE fallback timeout in milliseconds. */\nconst EOSE_FALLBACK_MS = 15_000;\n\n/**\n * Options for creating a relay pool service.\n *\n * @example\n * ```ts\n * const relayPoolService = createRelayPoolService({\n * subscribe: (filters, cb, urls) => myPool.subscribe(filters, cb, urls),\n * publish: (event) => myPool.publish(event),\n * selectRelayTier: (filters) => myPool.selectRelays(filters),\n * isAvailable: () => myPool.connected,\n * });\n * ```\n */\nexport interface RelayPoolServiceOptions {\n /**\n * Subscribe to events matching filters. Returns handle with unsubscribe().\n *\n * @param filters - NIP-01 filter objects\n * @param callback - Receives matching events or 'EOSE'\n * @param relayUrls - Optional relay URL hints\n * @returns Handle to cancel the subscription\n */\n subscribe(\n filters: NostrFilter[],\n callback: (item: NostrEvent | 'EOSE') => void,\n relayUrls?: string[],\n ): { unsubscribe(): void };\n\n /**\n * Publish an event to relays.\n *\n * @param event - The event to publish\n */\n publish(event: NostrEvent): void;\n\n /**\n * Select relay URLs appropriate for the given filters.\n *\n * @param filters - NIP-01 filter objects\n * @returns Array of relay URLs\n */\n selectRelayTier(filters: NostrFilter[]): string[];\n\n /**\n * Whether the relay pool is available and connected.\n *\n * @returns true if the relay pool can handle requests\n */\n isAvailable(): boolean;\n}\n\n/** Internal subscription tracking entry. */\ninterface TrackedSubscription {\n handle: { unsubscribe(): void };\n eoseTimer: unknown;\n}\n\ntype RelayServiceMessage = NappletMessage & {\n subId?: unknown;\n filters?: unknown;\n event?: unknown;\n relay?: unknown;\n};\n\n/**\n * Create a relay pool service that wraps an existing relay pool\n * implementation as a ServiceHandler.\n *\n * Handles relay.subscribe, relay.close, and relay.publish envelopes.\n * Tracks subscriptions per windowId:subId for lifecycle management.\n * Sets a 15-second EOSE fallback timer on each subscription.\n *\n * @param options - Relay pool implementation to wrap\n * @returns A ServiceHandler ready for runtime.registerService('relay', handler)\n *\n * @example\n * ```ts\n * import { createRelayPoolService } from '@kehto/services';\n *\n * const pool = createRelayPoolService({\n * subscribe: (f, cb, urls) => applesauce.subscribe(f, cb, urls),\n * publish: (e) => applesauce.publish(e),\n * selectRelayTier: (f) => applesauce.getRelays(f),\n * isAvailable: () => applesauce.connected,\n * });\n * runtime.registerService('relay', pool);\n * ```\n */\nexport function createRelayPoolService(options: RelayPoolServiceOptions): ServiceHandler {\n const tracked = new Map<string, TrackedSubscription>();\n\n return {\n descriptor: {\n name: 'relay-pool',\n version: '1.0.0',\n description: 'Relay pool subscription and publishing',\n },\n\n handleMessage(windowId: string, message: NappletMessage, send: (msg: NappletMessage) => void): void {\n const relayMessage = message as RelayServiceMessage;\n if (message.type === 'relay.subscribe') {\n const subId = relayMessage.subId;\n if (typeof subId !== 'string') return;\n const filters = Array.isArray(relayMessage.filters)\n ? relayMessage.filters as NostrFilter[]\n : [];\n const subKey = `${windowId}:${subId}`;\n\n // Cancel existing subscription for this key if any\n const existing = tracked.get(subKey);\n if (existing) {\n existing.handle.unsubscribe();\n clearTimeout(existing.eoseTimer);\n tracked.delete(subKey);\n }\n\n if (!options.isAvailable()) {\n send({ type: 'relay.eose', subId } as NappletMessage);\n return;\n }\n\n const relayHint = typeof relayMessage.relay === 'string' && relayMessage.relay.length > 0\n ? relayMessage.relay\n : undefined;\n const relayUrls = relayHint ? [relayHint] : options.selectRelayTier(filters);\n let eoseSent = false;\n\n const eoseTimer = setTimeout(() => {\n if (!eoseSent) {\n eoseSent = true;\n send({ type: 'relay.eose', subId } as NappletMessage);\n }\n }, EOSE_FALLBACK_MS);\n\n const handle = options.subscribe(filters, (item) => {\n if (item === 'EOSE') {\n clearTimeout(eoseTimer);\n if (!eoseSent) {\n eoseSent = true;\n send({ type: 'relay.eose', subId } as NappletMessage);\n }\n return;\n }\n send({ type: 'relay.event', subId, event: item } as NappletMessage);\n }, relayUrls);\n\n tracked.set(subKey, { handle, eoseTimer });\n return;\n }\n\n if (message.type === 'relay.close') {\n const subId = relayMessage.subId;\n if (typeof subId !== 'string') return;\n const subKey = `${windowId}:${subId}`;\n const entry = tracked.get(subKey);\n if (entry) {\n entry.handle.unsubscribe();\n clearTimeout(entry.eoseTimer);\n tracked.delete(subKey);\n }\n return;\n }\n\n if (message.type === 'relay.publish') {\n const event = relayMessage.event as NostrEvent | undefined;\n if (event && typeof event === 'object' && options.isAvailable()) {\n options.publish(event);\n }\n return;\n }\n\n if (message.type === 'relay.publishEncrypted') {\n const event = relayMessage.event as NostrEvent | undefined;\n if (event && typeof event === 'object' && options.isAvailable()) {\n options.publish(event);\n }\n return;\n }\n },\n\n onWindowDestroyed(windowId: string): void {\n const prefix = `${windowId}:`;\n for (const [key, entry] of tracked) {\n if (key.startsWith(prefix)) {\n entry.handle.unsubscribe();\n clearTimeout(entry.eoseTimer);\n tracked.delete(key);\n }\n }\n },\n };\n}\n","\nimport type { NostrEvent, NostrFilter, NappletMessage } from '@napplet/core';\nimport type { ServiceHandler } from '@kehto/runtime';\n\ntype RelayServiceMessage = NappletMessage & {\n subId?: unknown;\n filters?: unknown;\n event?: unknown;\n};\n\n/**\n * Options for creating a cache service.\n *\n * @example\n * ```ts\n * const cacheService = createCacheService({\n * query: (filters) => myIndexedDB.query(filters),\n * store: (event) => myIndexedDB.store(event),\n * isAvailable: () => true,\n * });\n * ```\n */\nexport interface CacheServiceOptions {\n /**\n * Query cached events matching the given filters.\n *\n * @param filters - NIP-01 filter objects\n * @returns Promise resolving to matching cached events\n */\n query(filters: NostrFilter[]): Promise<NostrEvent[]>;\n\n /**\n * Store an event in cache. Best-effort, may silently fail.\n *\n * @param event - The event to store\n */\n store(event: NostrEvent): void;\n\n /**\n * Whether the cache is available.\n *\n * @returns true if the cache can handle requests\n */\n isAvailable(): boolean;\n}\n\n/**\n * @kehto/services cross-package naming-parity alias for {@link CacheServiceOptions}.\n *\n * `HostCacheBridge` matches the v1.4 `HostKeysBridge` / `HostMediaBridge`\n * convention — it is a pure type alias for `CacheServiceOptions`, NOT a new\n * type. Existing consumers of `CacheServiceOptions` continue to work\n * unchanged; new consumers may prefer `HostCacheBridge` for consistency\n * with the other Host*Bridge names in `@kehto/services`.\n *\n * Anti-feature note (PITFALLS.md M-02): `CacheServiceOptions` MUST remain\n * the primary export. This alias is additive; do not rename or delete\n * `CacheServiceOptions` when other Host*Bridge names eventually\n * stabilize.\n *\n * @example\n * ```ts\n * import type { HostCacheBridge } from '@kehto/services';\n * const cache: HostCacheBridge = {\n * query: (filters) => myIndexedDB.query(filters),\n * store: (event) => myIndexedDB.store(event),\n * isAvailable: () => true,\n * };\n * ```\n */\nexport type HostCacheBridge = CacheServiceOptions;\n\n/**\n * Create a cache service that wraps an existing cache implementation\n * as a ServiceHandler.\n *\n * Cache relay.subscribe subscriptions are one-shot — they query, deliver\n * results, send EOSE, and are done. No long-lived subscription tracking needed.\n * Cache query failures are best-effort: EOSE is sent even on failure.\n *\n * @param options - Cache implementation to wrap\n * @returns A ServiceHandler ready for runtime.registerService('cache', handler)\n *\n * @example\n * ```ts\n * import { createCacheService } from '@kehto/services';\n *\n * const cache = createCacheService({\n * query: (f) => workerRelay.query(f),\n * store: (e) => workerRelay.store(e),\n * isAvailable: () => workerRelay.ready,\n * });\n * runtime.registerService('cache', cache);\n * ```\n */\nexport function createCacheService(options: CacheServiceOptions): ServiceHandler {\n return {\n descriptor: {\n name: 'cache',\n version: '1.0.0',\n description: 'Local event cache (IndexedDB, worker relay, etc.)',\n },\n\n handleMessage(_windowId: string, message: NappletMessage, send: (msg: NappletMessage) => void): void {\n const relayMessage = message as RelayServiceMessage;\n if (message.type === 'relay.subscribe') {\n const subId = relayMessage.subId;\n if (typeof subId !== 'string') return;\n const filters = Array.isArray(relayMessage.filters)\n ? relayMessage.filters as NostrFilter[]\n : [];\n\n if (!options.isAvailable()) {\n send({ type: 'relay.eose', subId } as NappletMessage);\n return;\n }\n\n options\n .query(filters)\n .then((events) => {\n for (const event of events) {\n send({ type: 'relay.event', subId, event } as NappletMessage);\n }\n send({ type: 'relay.eose', subId } as NappletMessage);\n })\n .catch(() => {\n // Cache query is best-effort — send EOSE even on failure\n send({ type: 'relay.eose', subId } as NappletMessage);\n });\n return;\n }\n\n if (message.type === 'relay.publish') {\n const event = relayMessage.event as NostrEvent | undefined;\n if (event && typeof event === 'object' && options.isAvailable()) {\n try {\n options.store(event);\n } catch {\n /* Cache write is best-effort */\n }\n }\n return;\n }\n },\n\n // Cache has no per-window state to clean up\n onWindowDestroyed(_windowId: string): void {\n /* no-op */\n },\n };\n}\n","\nimport type { NostrEvent, NostrFilter, NappletMessage } from '@napplet/core';\nimport type { ServiceHandler } from '@kehto/runtime';\nimport type { RelayPoolServiceOptions } from './relay-pool-service.js';\nimport type { CacheServiceOptions } from './cache-service.js';\n\n// Timer globals available in all JS runtimes\ndeclare function setTimeout(callback: () => void, ms: number): unknown;\ndeclare function clearTimeout(id: unknown): void;\n\n/** Default EOSE fallback timeout. */\nconst DEFAULT_EOSE_TIMEOUT_MS = 15_000;\n\n/**\n * Options for creating a coordinated relay service.\n *\n * @example\n * ```ts\n * const relay = createCoordinatedRelay({\n * relayPool: {\n * subscribe: (f, cb, urls) => pool.subscribe(f, cb, urls),\n * publish: (e) => pool.publish(e),\n * selectRelayTier: (f) => pool.selectRelays(f),\n * isAvailable: () => pool.connected,\n * },\n * cache: {\n * query: (f) => db.query(f),\n * store: (e) => db.store(e),\n * isAvailable: () => db.ready,\n * },\n * });\n * runtime.registerService('relay', relay);\n * ```\n */\nexport interface CoordinatedRelayOptions {\n /**\n * Relay pool implementation.\n * Uses the same interface as RelayPoolServiceOptions.\n */\n relayPool: RelayPoolServiceOptions;\n\n /**\n * Local cache implementation.\n * Uses the same interface as CacheServiceOptions.\n */\n cache: CacheServiceOptions;\n\n /**\n * EOSE fallback timeout in milliseconds.\n * Sent if relay pool doesn't respond within this time.\n * Default: 15000 (15 seconds).\n */\n eoseTimeoutMs?: number;\n}\n\n/** Internal state for a tracked subscription. */\ninterface TrackedSub {\n seenIds: Set<string>;\n cacheEose: boolean;\n relayEose: boolean;\n eoseSent: boolean;\n eoseTimer: unknown;\n relayHandle: { unsubscribe(): void } | null;\n}\n\ntype RelayServiceMessage = NappletMessage & {\n subId?: unknown;\n filters?: unknown;\n event?: unknown;\n relay?: unknown;\n};\n\n/**\n * Create a coordinated relay service that combines relay pool and cache\n * into a single ServiceHandler with dedup and unified EOSE.\n *\n * On relay.subscribe: queries cache first, then subscribes to relay pool.\n * Events are deduplicated by ID. EOSE is sent after both sources complete.\n * On relay.publish: publishes to relay pool and stores in cache.\n * On relay.close: cancels relay pool subscription.\n *\n * @param options - Relay pool and cache implementations to coordinate\n * @returns A ServiceHandler ready for runtime.registerService('relay', handler)\n *\n * @example\n * ```ts\n * import { createCoordinatedRelay } from '@kehto/services';\n *\n * const relay = createCoordinatedRelay({ relayPool: myPool, cache: myCache });\n * runtime.registerService('relay', relay);\n * ```\n */\nexport function createCoordinatedRelay(options: CoordinatedRelayOptions): ServiceHandler {\n const timeoutMs = options.eoseTimeoutMs ?? DEFAULT_EOSE_TIMEOUT_MS;\n const subs = new Map<string, TrackedSub>();\n\n function maybeSendEose(subKey: string, subId: string, send: (msg: NappletMessage) => void): void {\n const sub = subs.get(subKey);\n if (!sub || sub.eoseSent) return;\n if (sub.cacheEose && sub.relayEose) {\n sub.eoseSent = true;\n clearTimeout(sub.eoseTimer);\n send({ type: 'relay.eose', subId } as NappletMessage);\n }\n }\n\n return {\n descriptor: {\n name: 'relay',\n version: '1.0.0',\n description: 'Coordinated relay pool + cache with dedup and unified EOSE',\n },\n\n handleMessage(windowId: string, message: NappletMessage, send: (msg: NappletMessage) => void): void {\n const relayMessage = message as RelayServiceMessage;\n if (message.type === 'relay.subscribe') {\n const subId = relayMessage.subId;\n if (typeof subId !== 'string') return;\n const filters = Array.isArray(relayMessage.filters)\n ? relayMessage.filters as NostrFilter[]\n : [];\n const subKey = `${windowId}:${subId}`;\n\n // Cancel existing subscription for this key\n const existing = subs.get(subKey);\n if (existing) {\n existing.relayHandle?.unsubscribe();\n clearTimeout(existing.eoseTimer);\n subs.delete(subKey);\n }\n\n const cacheAvailable = options.cache.isAvailable();\n const relayAvailable = options.relayPool.isAvailable();\n\n // Neither source available — send EOSE immediately\n if (!cacheAvailable && !relayAvailable) {\n send({ type: 'relay.eose', subId } as NappletMessage);\n return;\n }\n\n const tracked: TrackedSub = {\n seenIds: new Set(),\n cacheEose: !cacheAvailable, // mark done if cache not available\n relayEose: !relayAvailable, // mark done if relay not available\n eoseSent: false,\n eoseTimer: null as unknown,\n relayHandle: null,\n };\n subs.set(subKey, tracked);\n\n function deliver(event: NostrEvent): void {\n if (tracked.seenIds.has(event.id)) return;\n tracked.seenIds.add(event.id);\n if (subs.has(subKey)) send({ type: 'relay.event', subId, event } as NappletMessage);\n }\n\n // Query cache (async)\n if (cacheAvailable) {\n options.cache\n .query(filters)\n .then((events) => {\n for (const event of events) deliver(event);\n tracked.cacheEose = true;\n maybeSendEose(subKey, subId, send);\n })\n .catch(() => {\n // Cache query is best-effort\n tracked.cacheEose = true;\n maybeSendEose(subKey, subId, send);\n });\n }\n\n // Subscribe to relay pool\n if (relayAvailable) {\n tracked.eoseTimer = setTimeout(() => {\n if (!tracked.eoseSent) {\n tracked.relayEose = true;\n maybeSendEose(subKey, subId, send);\n }\n }, timeoutMs);\n\n const relayHint = typeof relayMessage.relay === 'string' && relayMessage.relay.length > 0\n ? relayMessage.relay\n : undefined;\n const relayUrls = relayHint ? [relayHint] : options.relayPool.selectRelayTier(filters);\n tracked.relayHandle = options.relayPool.subscribe(filters, (item) => {\n if (item === 'EOSE') {\n clearTimeout(tracked.eoseTimer);\n tracked.relayEose = true;\n maybeSendEose(subKey, subId, send);\n return;\n }\n deliver(item);\n // Store relay events in cache\n if (cacheAvailable) {\n try { options.cache.store(item); } catch { /* best-effort */ }\n }\n }, relayUrls);\n }\n return;\n }\n\n if (message.type === 'relay.close') {\n const subId = relayMessage.subId;\n if (typeof subId !== 'string') return;\n const subKey = `${windowId}:${subId}`;\n const entry = subs.get(subKey);\n if (entry) {\n entry.relayHandle?.unsubscribe();\n clearTimeout(entry.eoseTimer);\n subs.delete(subKey);\n }\n return;\n }\n\n if (message.type === 'relay.publish') {\n const event = relayMessage.event as NostrEvent | undefined;\n if (!event || typeof event !== 'object') return;\n // Publish to relay pool\n if (options.relayPool.isAvailable()) {\n options.relayPool.publish(event);\n }\n if (options.cache.isAvailable()) {\n try { options.cache.store(event); } catch { /* best-effort */ }\n }\n return;\n }\n },\n\n onWindowDestroyed(windowId: string): void {\n const prefix = `${windowId}:`;\n for (const [key, entry] of subs) {\n if (key.startsWith(prefix)) {\n entry.relayHandle?.unsubscribe();\n clearTimeout(entry.eoseTimer);\n subs.delete(key);\n }\n }\n },\n };\n}\n","/**\n * keys-service.ts — NIP-5D keys NAP reference document-level listener implementation.\n *\n * Handles the 3 napplet -> shell request types from @napplet/nap/keys:\n * - keys.forward -> invokes options.onForward (hotkey passthrough, fire-and-forget)\n * - keys.registerAction -> parses action.defaultKey into a chord spec, stores the\n * subscription in an in-memory registry keyed by actionId,\n * tracks windowId ownership so onWindowDestroyed can auto-\n * unsubscribe, and echoes { actionId, binding } as .result\n * - keys.unregisterAction -> removes the subscription; fire-and-forget (no envelope)\n *\n * Real listener: on service construction the handler attaches a single\n * `keydown` listener to `options.listenerTarget` (default: `document`). Each\n * keydown is matched against the chord-subscription registry; matches invoke\n * `options.onForward` with the DOM-shape payload AND push a canonical\n * `keys.action` envelope back to the owning napplet via the per-window `send`\n * callback captured at `keys.registerAction` time. Subscriptions persist\n * across messages; `onWindowDestroyed(windowId)` drops all subscriptions owned\n * by the destroyed window as well as its cached `send` handle.\n *\n * On each document keydown matching a registered action, the service\n * additionally emits a `keys.action` envelope to the action's owning napplet\n * via the per-window `send` callback — this is the canonical @napplet/nap/keys\n * surface the SDK's `keys.onAction(...)` helper consumes. The shape is a\n * superset of `KeysActionMessage`: `{ type, actionId, chord }` where `chord`\n * is the parsed `{ ctrl, alt, shift, meta, key }` struct (extension field;\n * base shape unchanged, downstream SDKs that only read `{ type, actionId }`\n * ignore `chord` silently).\n *\n * Field-name translation: @napplet/nap/keys uses the compact\n * { ctrl, alt, shift, meta } form on the wire; the shell's HotkeyHooks\n * (packages/shell/src/types.ts) expects the DOM-compatible\n * { ctrlKey, altKey, shiftKey, metaKey } form. This service performs the\n * translation so callers of `onForward` see the DOM shape.\n *\n * Shell -> napplet push envelopes `keys.bindings` remain the shell-side keys\n * forwarder's responsibility (DRIFT-SHELL-06, tracked under Plan 12-11 /\n * future phase); `keys.action` is emitted here per Plan 26-01.\n */\n\nimport type { NappletMessage } from '@napplet/core';\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\nimport type {\n KeysForwardMessage,\n KeysRegisterActionMessage,\n KeysRegisterActionResultMessage,\n KeysActionMessage,\n} from '@napplet/nap/keys/types';\n\n/**\n * Minimal structural subset of the DOM `KeyboardEvent` exposed to\n * `HostKeysBridge` subscribe callbacks. DOM `KeyboardEvent` satisfies this\n * structurally with no adapter needed. OS-bridge impls (Electron, Tauri —\n * out of v1.4 scope) synthesize this from native key events.\n */\nexport interface HostKeyEvent {\n key: string;\n code: string;\n ctrlKey: boolean;\n altKey: boolean;\n shiftKey: boolean;\n metaKey: boolean;\n /** True for OS autorepeat; the service filters these by default. */\n repeat?: boolean;\n}\n\n/**\n * Host-bridge contract for pluggable keyboard backends.\n *\n * The browser reference implementation (the default {@link createKeysService}\n * behaviour when `hostBridge` is omitted) registers a `document`-level keydown\n * listener and satisfies this interface structurally — it exposes\n * `subscribe(chord, callback) => unsubscribe` semantics but omits the two\n * OS-level optional fields (browsers cannot register global hotkeys without\n * privileged APIs).\n *\n * Host apps (Electron, Tauri) implement this interface in their own code and\n * pass it via `createKeysService({ hostBridge: myBridge })` — the service\n * then delegates subscription lifecycle to the bridge and remains browser-free.\n *\n * Reference implementations for Electron / Tauri are explicitly out of v1.4\n * scope and live in host-app examples / follow-up milestones (see\n * REQUIREMENTS.md \"Future Requirements\").\n *\n * @example\n * ```ts\n * // Host-app pseudocode (Electron main-process relay):\n * const electronBridge: HostKeysBridge = {\n * subscribe(chord, cb) {\n * const handle = globalShortcut.register(chord, () => cb({ key: '', code: '', ctrlKey: false, altKey: false, shiftKey: false, metaKey: false }));\n * return () => globalShortcut.unregister(chord);\n * },\n * registerGlobalHotkey: (chord) => globalShortcut.register(chord, () => {}),\n * onGlobalHotkey: (cb) => globalHotkeyBridge.on('global-hotkey', (_, chord) => cb(chord)),\n * };\n *\n * const keys = createKeysService({ hostBridge: electronBridge });\n * runtime.registerService('keys', keys);\n * ```\n */\nexport interface HostKeysBridge {\n /**\n * Subscribe a callback to a chord. Returns an unsubscribe handle.\n *\n * Implementations MUST:\n * - invoke `callback` exactly once per matching chord event (implementations\n * are responsible for any OS-autorepeat filtering)\n * - invoke `callback` synchronously during the event delivery\n * - accept the string chord format documented by @napplet/nap/keys\n * (e.g. `'Ctrl+Shift+K'`, `'Cmd+P'`)\n */\n subscribe(chord: string, callback: (event: KeyboardEvent | HostKeyEvent) => void): () => void;\n\n /**\n * Optional: register an OS-level global hotkey (works even when the host\n * window is not focused). Returns true on success, false if the chord\n * cannot be registered (e.g. already claimed by another app).\n *\n * Omitted by the browser reference implementation — browsers cannot\n * register OS-level global hotkeys without privileged APIs. Electron\n * (`globalShortcut`) and Tauri (`GlobalShortcut`) provide this.\n */\n registerGlobalHotkey?(chord: string): boolean;\n\n /**\n * Optional: subscribe to OS-level global hotkey events (regardless of\n * focus). Returns an unsubscribe handle.\n *\n * Omitted by the browser reference implementation. See\n * {@link HostKeysBridge.registerGlobalHotkey}.\n */\n onGlobalHotkey?(callback: (chord: string) => void): () => void;\n}\n\n/** Keys service version — follows semver. */\nconst KEYS_SERVICE_VERSION = '1.2.0';\n\n/**\n * Parsed chord struct — internal, never on the wire. The napplet-facing API\n * (and the `action.defaultKey` field) is a string like `\"Ctrl+Shift+K\"`;\n * `parseChord` lowers those strings into this struct for efficient matching\n * against `KeyboardEvent` modifier flags.\n */\ninterface ChordSpec {\n ctrl: boolean;\n alt: boolean;\n shift: boolean;\n meta: boolean;\n /** Normalized uppercase single character or DOM key name (e.g. 'K', 'Enter'). */\n key: string;\n}\n\n/** Registry entry — maps a registered actionId back to its owning window + chord. */\ninterface ActionEntry {\n chord: ChordSpec;\n /** Original chord string, preserved for the .result `binding` field. */\n chordString: string;\n windowId: string;\n}\n\n/**\n * Options for creating a keys service via createKeysService().\n *\n * @example\n * ```ts\n * const keys = createKeysService({\n * onForward: (event) => {\n * // event has DOM-compatible field names: ctrlKey, altKey, etc.\n * hotkeyDispatcher.dispatch(event);\n * },\n * });\n * ```\n */\nexport interface KeysServiceOptions {\n /**\n * Called on `keys.forward` (napplet-forwarded chord) AND on document keydown\n * matching a registered action. Receives the DOM-style field names\n * (ctrlKey/altKey/shiftKey/metaKey) to match the shell's HotkeyHooks\n * contract. The service translates from the wire shape\n * ({ ctrl, alt, shift, meta }) before invoking this callback.\n */\n onForward?: (event: {\n key: string;\n code: string;\n ctrlKey: boolean;\n altKey: boolean;\n shiftKey: boolean;\n metaKey: boolean;\n }) => void;\n /**\n * EventTarget to attach the default keydown listener to. Defaults to the\n * global `document` when running in a DOM environment, else an isolated\n * `new EventTarget()` (SSR / Node-test safe). Passing a fresh\n * `new EventTarget()` is useful for unit tests. Mirrors the pattern used\n * by `@kehto/shell`'s `createKeysForwarder`.\n *\n * Ignored when `hostBridge` is provided — the bridge owns subscription\n * lifecycle and no document listener is attached.\n */\n listenerTarget?: EventTarget;\n /**\n * Optional pluggable backend for chord subscription. When provided, the\n * service delegates `keys.registerAction` → `bridge.subscribe(chord, cb)`\n * and stores the returned unsubscribe handle keyed on `actionId`. The\n * default document-listener path is NOT attached when `hostBridge` is\n * provided — the bridge is authoritative. See {@link HostKeysBridge}.\n */\n hostBridge?: HostKeysBridge;\n /**\n * Optional set of shell-reserved chords. Strings in the `@napplet/nap/keys`\n * wire format (e.g. `'Ctrl+Shift+K'`, `'Cmd+P'`). When a napplet sends\n * `keys.forward` with a chord matching this set — or when a document\n * keydown matches a reserved chord — the service invokes `onForward`\n * (or the `hostBridge`-registered handler) but suppresses the\n * `keys.action` push to any napplet that registered the same chord via\n * `keys.registerAction`. Precedence: reserved > registered. The shell\n * WANTS the forward — that is why it reserved the chord.\n *\n * Normalized once at service construction via the same chord parser used\n * for `action.defaultKey` — `'Ctrl+K'` / `'Control+k'` / `'ctrl+K'` all\n * match. Static; no runtime mutation. For dynamic reservation see the\n * deferred `HostKeysBridge.reserveAbsolute(chords)` extension.\n *\n * @example\n * ```ts\n * const keys = createKeysService({\n * reservedChords: ['Ctrl+Alt+T', 'Super+Space'],\n * onForward: (event) => wm.dispatch(event),\n * });\n * ```\n */\n reservedChords?: ReadonlyArray<string>;\n}\n\n/** Modifier-token aliases accepted by `parseChord` (case-insensitive). */\nconst MODIFIER_ALIASES: Record<string, keyof Pick<ChordSpec, 'ctrl' | 'alt' | 'shift' | 'meta'>> = {\n ctrl: 'ctrl',\n control: 'ctrl',\n alt: 'alt',\n option: 'alt',\n shift: 'shift',\n meta: 'meta',\n cmd: 'meta',\n command: 'meta',\n win: 'meta',\n super: 'meta',\n};\n\n/**\n * Parse a chord string into a `ChordSpec`. Modifier tokens are case-insensitive\n * and recognize common aliases (Cmd/Command/Win/Super → meta, Control → ctrl,\n * Option → alt). Single-character keys are normalized to uppercase so chord\n * matching is case-insensitive; multi-character DOM key names (`Enter`,\n * `ArrowUp`, `F4`) preserve their original casing.\n *\n * Examples:\n * parseChord('Ctrl+Shift+K') → { ctrl: true, alt: false, shift: true, meta: false, key: 'K' }\n * parseChord('ctrl+s') → { ctrl: true, alt: false, shift: false, meta: false, key: 'S' }\n * parseChord('Cmd+P') → { ctrl: false, alt: false, shift: false, meta: true, key: 'P' }\n * parseChord('K') → { ctrl: false, alt: false, shift: false, meta: false, key: 'K' }\n * parseChord('Ctrl++') → { ctrl: true, alt: false, shift: false, meta: false, key: '+' }\n *\n * @throws Error('empty chord') when the input is the empty string.\n * @throws Error(`unknown modifier: ${tok}`) when a non-final token isn't a recognized modifier.\n * @throws Error(`empty key in chord: ${chord}`) when the final token is empty/whitespace.\n */\nfunction parseChord(chord: string): ChordSpec {\n if (chord.length === 0) throw new Error('empty chord');\n const parts = chord.split('+');\n const out: ChordSpec = { ctrl: false, alt: false, shift: false, meta: false, key: '' };\n // The final token is always the key — even if it is literally '+' (chord like 'Ctrl++').\n // All preceding tokens must be modifiers.\n for (let i = 0; i < parts.length - 1; i++) {\n const tok = parts[i].trim().toLowerCase();\n if (tok.length === 0) continue; // tolerate stray whitespace\n const slot = MODIFIER_ALIASES[tok];\n if (!slot) throw new Error(`unknown modifier: ${parts[i]}`);\n out[slot] = true;\n }\n const keyTok = parts[parts.length - 1].trim();\n if (keyTok.length === 0) throw new Error(`empty key in chord: ${chord}`);\n // Single characters normalize to uppercase for case-insensitive comparison;\n // multi-character DOM key names (Enter, ArrowUp, F4) preserve their original casing.\n out.key = keyTok.length === 1 ? keyTok.toUpperCase() : keyTok;\n return out;\n}\n\n/**\n * Create a keys service handler.\n *\n * Attaches a single `keydown` listener to `options.listenerTarget`\n * (default `document`). Matching chord subscriptions invoke `options.onForward`\n * with a DOM-shape payload AND push a `keys.action` envelope back to the\n * owning napplet via the per-window `send` callback captured at\n * `keys.registerAction` time. Returns a `ServiceHandler` augmented with a\n * `destroy()` method that detaches the listener and clears all registries.\n *\n * @param options - Optional configuration (onForward callback, listenerTarget)\n * @returns A ServiceHandler (with `destroy()`) to register with the runtime\n *\n * @example\n * ```ts\n * import { createKeysService } from '@kehto/services';\n *\n * const keys = createKeysService({\n * onForward: (event) => shellHotkeyDispatcher.execute(event),\n * });\n *\n * runtime.registerService('keys', keys);\n * // Later, on shell teardown:\n * keys.destroy();\n * ```\n */\nexport function createKeysService(\n options: KeysServiceOptions = {},\n): ServiceHandler & { destroy(): void } {\n const descriptor: ServiceDescriptor = {\n name: 'keys',\n version: KEYS_SERVICE_VERSION,\n description: options.hostBridge\n ? 'NIP-5D keys NAP reference handler (host-bridge delegated)'\n : 'NIP-5D keys NAP reference handler (document-level chord listener)',\n };\n\n function chordSpecKey(spec: {\n ctrl: boolean;\n alt: boolean;\n shift: boolean;\n meta: boolean;\n key: string;\n }): string {\n return `${spec.ctrl}|${spec.alt}|${spec.shift}|${spec.meta}|${spec.key}`;\n }\n const reservedChordKeys: Set<string> = new Set();\n if (options.reservedChords) {\n for (const chordStr of options.reservedChords) {\n // parseChord throws on malformed input — let it bubble up at construction\n // so misconfigured shells fail loudly at boot, not silently at runtime.\n reservedChordKeys.add(chordSpecKey(parseChord(chordStr)));\n }\n }\n // Canonicalize a wire-shape keys.forward payload into the same key.\n function forwardKey(m: {\n key: string;\n ctrl: boolean;\n alt: boolean;\n shift: boolean;\n meta: boolean;\n }): string {\n const k = m.key.length === 1 ? m.key.toUpperCase() : m.key;\n return `${m.ctrl}|${m.alt}|${m.shift}|${m.meta}|${k}`;\n }\n function forwardPayload(m: KeysForwardMessage): Omit<HostKeyEvent, 'repeat'> {\n return {\n key: m.key,\n code: m.code,\n ctrlKey: m.ctrl,\n altKey: m.alt,\n shiftKey: m.shift,\n metaKey: m.meta,\n };\n }\n // Canonicalize a DOM KeyboardEvent into the same key (for Branch B keydown listener).\n function eventKey(ev: KeyboardEvent): string {\n const k = ev.key.length === 1 ? ev.key.toUpperCase() : ev.key;\n return `${ev.ctrlKey}|${ev.altKey}|${ev.shiftKey}|${ev.metaKey}|${k}`;\n }\n\n if (options.hostBridge) {\n const bridge = options.hostBridge;\n // windowId → Set<actionId> — parallels Branch B for scoped cleanup.\n const bridgeWindowActions = new Map<string, Set<string>>();\n // actionId → unsubscribe handle returned from bridge.subscribe.\n const unsubscribeHandles = new Map<string, () => void>();\n\n return {\n descriptor,\n\n handleMessage(\n windowId: string,\n message: NappletMessage,\n send: (msg: NappletMessage) => void,\n ): void {\n switch (message.type) {\n case 'keys.forward': {\n // Legacy napplet-forwarded path still works identically — preserves wire contract.\n // Phase 33 / KEYS-04-05: reserved chords take precedence. The Branch-A\n // handler never dispatches keys.action to napplets on forward (bridge\n // owns chord → napplet routing via its own subscribe callback), so\n // reservation here is observationally identical to the base case —\n // but we compute and check the reservation explicitly to document\n // the contract for future edits and keep both branches uniform.\n const m = message as KeysForwardMessage;\n const reserved = reservedChordKeys.has(forwardKey(m));\n options.onForward?.(forwardPayload(m));\n if (reserved) {\n return;\n }\n return;\n }\n\n case 'keys.registerAction': {\n const m = message as KeysRegisterActionMessage;\n if (m.action.defaultKey) {\n try {\n const unsubscribe = bridge.subscribe(m.action.defaultKey, (ev) => {\n // Normalize either KeyboardEvent or HostKeyEvent to the DOM-shape onForward payload.\n const e = ev as KeyboardEvent | HostKeyEvent;\n // Bridges may not filter autorepeat — we do, matching Branch B semantics.\n if ('repeat' in e && e.repeat) return;\n options.onForward?.({\n key: e.key,\n code: e.code,\n ctrlKey: e.ctrlKey,\n altKey: e.altKey,\n shiftKey: e.shiftKey,\n metaKey: e.metaKey,\n });\n // Canonical shell→napplet push: emit keys.action to the owning\n // napplet. Shape matches Branch B (superset of KeysActionMessage);\n // the `chord` extension is omitted here because bridges deliver\n // pre-parsed chord events without the internal ChordSpec struct.\n const payload: KeysActionMessage = {\n type: 'keys.action',\n actionId: m.action.id,\n };\n send(payload as NappletMessage);\n });\n unsubscribeHandles.set(m.action.id, unsubscribe);\n if (!bridgeWindowActions.has(windowId)) bridgeWindowActions.set(windowId, new Set());\n bridgeWindowActions.get(windowId)!.add(m.action.id);\n } catch (err) {\n const id = m.id ?? '';\n send({\n type: 'keys.registerAction.error',\n id,\n error: `bridge subscribe failed: ${(err as Error).message}`,\n } as NappletMessage);\n return;\n }\n }\n const result: KeysRegisterActionResultMessage = {\n type: 'keys.registerAction.result',\n id: m.id,\n actionId: m.action.id,\n ...(m.action.defaultKey ? { binding: m.action.defaultKey } : {}),\n };\n send(result as NappletMessage);\n return;\n }\n\n case 'keys.unregisterAction': {\n const m = message as NappletMessage & { actionId?: string };\n if (m.actionId) {\n const unsubscribe = unsubscribeHandles.get(m.actionId);\n if (unsubscribe) {\n try {\n unsubscribe();\n } catch {\n /* best-effort */\n }\n unsubscribeHandles.delete(m.actionId);\n // Prune the bridgeWindowActions entry that owns this actionId.\n for (const [wid, set] of bridgeWindowActions.entries()) {\n if (set.delete(m.actionId) && set.size === 0) bridgeWindowActions.delete(wid);\n }\n }\n }\n return;\n }\n\n default: {\n const id = (message as NappletMessage & { id?: string }).id ?? '';\n send({\n type: `${message.type}.error`,\n id,\n error: `Unknown keys method: ${message.type}`,\n } as NappletMessage);\n return;\n }\n }\n },\n\n onWindowDestroyed(windowId: string): void {\n const actions = bridgeWindowActions.get(windowId);\n if (!actions) return;\n for (const actionId of actions) {\n const unsubscribe = unsubscribeHandles.get(actionId);\n if (unsubscribe) {\n try {\n unsubscribe();\n } catch {\n /* best-effort */\n }\n }\n unsubscribeHandles.delete(actionId);\n }\n bridgeWindowActions.delete(windowId);\n },\n\n destroy(): void {\n for (const unsubscribe of unsubscribeHandles.values()) {\n try {\n unsubscribe();\n } catch {\n /* best-effort */\n }\n }\n unsubscribeHandles.clear();\n bridgeWindowActions.clear();\n },\n };\n }\n\n const actionRegistry = new Map<string, ActionEntry>(); // actionId → {chord, chordString, windowId}\n const windowActions = new Map<string, Set<string>>(); // windowId → Set<actionId>\n // Per-window `send` callback captured at registerAction time. Used to push\n // keys.action envelopes back to the owning napplet on chord match — this is\n // the canonical @napplet/nap/keys surface the SDK's `keys.onAction(...)`\n // helper consumes.\n const sendHandles = new Map<string, (msg: NappletMessage) => void>(); // windowId → send\n\n // ─── Listener target (SSR / test-safe fallback, mirrors keys-forwarder.ts) ─\n const target: EventTarget =\n options.listenerTarget ??\n (typeof document !== 'undefined' ? document : new EventTarget());\n\n function chordMatches(spec: ChordSpec, ev: KeyboardEvent): boolean {\n if (spec.ctrl !== ev.ctrlKey) return false;\n if (spec.alt !== ev.altKey) return false;\n if (spec.shift !== ev.shiftKey) return false;\n if (spec.meta !== ev.metaKey) return false;\n const evKey = ev.key.length === 1 ? ev.key.toUpperCase() : ev.key;\n return spec.key === evKey;\n }\n\n const listener = (rawEv: Event): void => {\n const ev = rawEv as KeyboardEvent;\n if (ev.repeat) return; // ignore OS autorepeat — matches \"I pressed it once\" intent (CONTEXT Area 1)\n\n // Phase 33 / KEYS-04-05: reserved chords fire onForward but suppress\n // keys.action fan-out to napplets. Check ONCE up front.\n const isReserved = reservedChordKeys.has(eventKey(ev));\n\n // Determine if any registered action would match — needed to decide\n // whether to fire onForward on a non-reserved keydown (legacy parity).\n // A reserved chord fires onForward regardless of napplet registration\n // (WM-launcher case: shell declares chord, no napplet registers it).\n let anyMatch = false;\n for (const entry of actionRegistry.values()) {\n if (chordMatches(entry.chord, ev)) {\n anyMatch = true;\n break;\n }\n }\n\n if (isReserved || anyMatch) {\n options.onForward?.({\n key: ev.key,\n code: ev.code,\n ctrlKey: ev.ctrlKey,\n altKey: ev.altKey,\n shiftKey: ev.shiftKey,\n metaKey: ev.metaKey,\n });\n }\n\n if (isReserved) return; // reserved → no napplet fan-out\n\n // (2) Canonical shell→napplet push: emit keys.action to the owning\n // napplet via its captured send callback. The SDK's keys.onAction\n // helper subscribes to this envelope. We attach a `chord` extension\n // field so the demo napplet can display the fired chord without\n // reconstructing it from the original registration.\n for (const [actionId, entry] of actionRegistry.entries()) {\n if (chordMatches(entry.chord, ev)) {\n const send = sendHandles.get(entry.windowId);\n if (send) {\n const payload: KeysActionMessage & { chord: ChordSpec } = {\n type: 'keys.action',\n actionId,\n chord: entry.chord,\n };\n send(payload as NappletMessage);\n }\n // Intentionally no `break` — two actions subscribing to the same chord\n // both receive the event. Conflict resolution is an explicit v1.5+\n // concern per CONTEXT.md Deferred Ideas.\n }\n }\n };\n\n target.addEventListener('keydown', listener);\n\n return {\n descriptor,\n\n handleMessage(\n windowId: string,\n message: NappletMessage,\n send: (msg: NappletMessage) => void,\n ): void {\n switch (message.type) {\n case 'keys.forward': {\n // Legacy passthrough: napplet-forwarded keydown translation.\n // Preserved bit-for-bit from the stub — existing tests + the\n // keys-forwarder.ts -> service.handleMessage path depend on this shape.\n //\n // Phase 33 / KEYS-04-05: Branch B's keys.forward handler does NOT\n // emit keys.action (fan-out happens in the document keydown listener),\n // so reservation is observationally identical to the base case.\n // The explicit guard below pins the contract for future edits.\n const m = message as KeysForwardMessage;\n const reserved = reservedChordKeys.has(forwardKey(m));\n options.onForward?.(forwardPayload(m));\n if (reserved) return;\n return;\n }\n\n case 'keys.registerAction': {\n const m = message as KeysRegisterActionMessage;\n // Capture (or refresh) the per-window send callback. The runtime's\n // service-handler contract guarantees `send` remains valid for this\n // windowId until onWindowDestroyed(windowId) fires — we cache the\n // most recent invocation so the keydown listener can push\n // keys.action envelopes back to the owning napplet.\n sendHandles.set(windowId, send);\n\n if (m.action.defaultKey) {\n try {\n const chord = parseChord(m.action.defaultKey);\n actionRegistry.set(m.action.id, {\n chord,\n chordString: m.action.defaultKey,\n windowId,\n });\n if (!windowActions.has(windowId)) windowActions.set(windowId, new Set());\n windowActions.get(windowId)!.add(m.action.id);\n } catch (err) {\n // Parse failure: respond with .error envelope (unknown-method pattern).\n const id = m.id ?? '';\n send({\n type: 'keys.registerAction.error',\n id,\n error: `invalid chord: ${(err as Error).message}`,\n } as NappletMessage);\n return;\n }\n }\n const result: KeysRegisterActionResultMessage = {\n type: 'keys.registerAction.result',\n id: m.id,\n actionId: m.action.id,\n ...(m.action.defaultKey ? { binding: m.action.defaultKey } : {}),\n };\n send(result as NappletMessage);\n return;\n }\n\n case 'keys.unregisterAction': {\n // Fire-and-forget per @napplet/nap/keys spec. Remove subscription if present.\n const m = message as NappletMessage & { actionId?: string };\n if (m.actionId && actionRegistry.has(m.actionId)) {\n const entry = actionRegistry.get(m.actionId)!;\n actionRegistry.delete(m.actionId);\n const set = windowActions.get(entry.windowId);\n if (set) {\n set.delete(m.actionId);\n // If the window has no remaining actions, drop its cached send\n // that no longer subscribes to anything.\n if (set.size === 0) {\n windowActions.delete(entry.windowId);\n sendHandles.delete(entry.windowId);\n }\n }\n }\n return;\n }\n\n default: {\n const id = (message as NappletMessage & { id?: string }).id ?? '';\n send({\n type: `${message.type}.error`,\n id,\n error: `Unknown keys method: ${message.type}`,\n } as NappletMessage);\n return;\n }\n }\n },\n\n onWindowDestroyed(windowId: string): void {\n const actions = windowActions.get(windowId);\n if (actions) {\n for (const actionId of actions) actionRegistry.delete(actionId);\n windowActions.delete(windowId);\n }\n sendHandles.delete(windowId);\n },\n\n destroy(): void {\n target.removeEventListener('keydown', listener);\n actionRegistry.clear();\n windowActions.clear();\n sendHandles.clear();\n },\n };\n}\n","import type { MediaAction, MediaMetadata } from '@napplet/nap/media/types';\nimport type { HostMediaBridge } from './media-service.js';\n\n/** Silent-audio prime data URL (4 kHz silent WAV, 44 bytes, zero network dependency).\n * Browsers refuse to render OS media controls without a playing audio element —\n * this silent loop primes the MediaSession API. Per CONTEXT.md Area 1. */\nconst SILENT_AUDIO_DATA_URL =\n 'data:audio/wav;base64,UklGRiQAAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQAAAAA=';\n\n/** Default action set — all 5 nap-media transport actions. */\nexport const DEFAULT_MEDIA_ACTIONS: readonly MediaAction[] = ['play', 'pause', 'next', 'prev', 'seek'];\n\n/** Minimal subset of navigator.mediaSession the browser bridge depends on. Makes the bridge\n * Node/test-safe: unit tests pass a MockMediaSession via mediaSessionTarget.\n * The handler parameter uses `details?` (optional) so both the real DOM impl\n * (which always passes an object) and test mocks that omit details both satisfy\n * this type structurally. */\nexport type MediaSessionTarget = {\n metadata: MediaMetadataLike | null;\n playbackState: 'none' | 'playing' | 'paused';\n setActionHandler(action: string, handler: ((details?: { action?: string; seekTime?: number }) => void) | null): void;\n};\n\n/** Structural subset of the DOM MediaMetadata class — assignable from a plain object\n * with title/artist/album/artwork fields. The browser impl uses `new MediaMetadata({...})`;\n * tests can pass a plain object. */\nexport type MediaMetadataLike = { title?: string; artist?: string; album?: string; artwork?: unknown };\n\n/** Mapping from DOM MediaSession action names to nap-media MediaAction literals. */\nconst ACTION_MATRIX: ReadonlyArray<[string, MediaAction]> = [\n ['play', 'play'],\n ['pause', 'pause'],\n ['nexttrack', 'next'],\n ['previoustrack', 'prev'],\n ['seekto', 'seek'],\n];\n\n/**\n * Reference browser implementation of {@link HostMediaBridge}.\n *\n * Mirrors metadata to `navigator.mediaSession.metadata` (via the DOM\n * `MediaMetadata` constructor when available; plain-object fallback in test\n * envs). Mirrors playback state to `navigator.mediaSession.playbackState`\n * with the canonical mapping: 'playing' maps to 'playing', 'paused' maps to\n * 'paused', 'buffering' maps to 'paused', 'stopped' maps to 'none'. Installs\n * `setActionHandler` callbacks for play/pause/nexttrack/previoustrack/seekto\n * that fan into the onAction subscriber with the mapped `MediaAction` literal\n * (and `value` from `details.seekTime` for seekto). When `setActiveSession` is\n * called with a non-null `actions` parameter, only the declared actions get\n * active handlers — the remaining are cleared (matching the capabilities\n * narrowing behavior of Plan 27-01's inline implementation).\n *\n * Installs a silent-audio prime (4 kHz silent WAV data URL) when the first\n * session becomes active (setActiveSession called with a non-null sessionId) —\n * browsers refuse to render OS media controls without a playing audio element.\n * Removes the element when destroySession brings the active session count to\n * zero.\n *\n * @param opts.mediaSessionTarget - Override navigator.mediaSession (tests).\n * @param opts.documentTarget - Override document (tests; pass null to disable silent-audio prime).\n */\nexport function createBrowserMediaBridge(opts: {\n mediaSessionTarget?: MediaSessionTarget;\n documentTarget?: Document | null;\n} = {}): HostMediaBridge {\n const ms: MediaSessionTarget | null =\n opts.mediaSessionTarget\n ?? (typeof navigator !== 'undefined' && 'mediaSession' in navigator\n ? (navigator.mediaSession as MediaSessionTarget)\n : null);\n const doc: Document | null =\n opts.documentTarget !== undefined\n ? opts.documentTarget\n : (typeof document !== 'undefined' ? document : null);\n\n let silentAudioEl: HTMLAudioElement | null = null;\n let activeSessionId: string | null = null;\n let sessionsActive = 0;\n const actionCallbacks = new Set<(sessionId: string, action: MediaAction, value?: number) => void>();\n\n function primeSilentAudio(): void {\n if (silentAudioEl || !doc) return;\n const el = doc.createElement('audio') as HTMLAudioElement;\n el.src = SILENT_AUDIO_DATA_URL;\n el.loop = true;\n el.style.display = 'none';\n (el as HTMLAudioElement).setAttribute('data-kehto-silent-audio-prime', 'true');\n doc.body.appendChild(el);\n void el.play().catch(() => { /* autoplay refused — metadata mirror still works */ });\n silentAudioEl = el;\n }\n\n function teardownSilentAudio(): void {\n if (!silentAudioEl) return;\n try { silentAudioEl.pause(); } catch { /* best-effort */ }\n try { silentAudioEl.remove(); } catch { /* best-effort */ }\n silentAudioEl = null;\n }\n\n /**\n * Install action handlers for the given set of nap-media actions. Actions not\n * in the set get their handler cleared. Matches Plan 27-01's installActionHandlersFor\n * behavior exactly so capabilities-narrowing tests continue to pass.\n */\n function applyActionHandlers(actions: readonly MediaAction[] = DEFAULT_MEDIA_ACTIONS): void {\n if (!ms) return;\n for (const [domAction, napAction] of ACTION_MATRIX) {\n if (!actions.includes(napAction)) {\n try { ms.setActionHandler(domAction, null); } catch { /* best-effort */ }\n continue;\n }\n ms.setActionHandler(domAction, (details) => {\n if (!activeSessionId) return;\n const value = napAction === 'seek' && typeof details?.seekTime === 'number' ? details.seekTime : undefined;\n for (const cb of actionCallbacks) {\n cb(activeSessionId, napAction, value);\n }\n });\n }\n }\n\n function writeMetadata(metadata: MediaMetadata | undefined): void {\n if (!ms) return;\n if (!metadata) { ms.metadata = null; return; }\n const artwork = metadata.artwork?.url ? [{ src: metadata.artwork.url }] : undefined;\n const init: MediaMetadataLike & { artwork?: unknown } = {\n title: metadata.title ?? '',\n artist: metadata.artist ?? '',\n album: metadata.album ?? '',\n ...(artwork ? { artwork } : {}),\n };\n try {\n const ctor = (globalThis as typeof globalThis & { MediaMetadata?: new (init: MediaMetadataLike) => MediaMetadataLike }).MediaMetadata;\n ms.metadata = ctor ? new ctor(init) : (init as MediaMetadataLike);\n } catch {\n ms.metadata = init as MediaMetadataLike;\n }\n }\n\n return {\n setMetadata(sessionId, metadata) {\n if (sessionId === activeSessionId) writeMetadata(metadata);\n },\n setPlaybackState(sessionId, state) {\n if (!ms || sessionId !== activeSessionId) return;\n ms.playbackState =\n state === 'playing' ? 'playing'\n : state === 'paused' || state === 'buffering' ? 'paused'\n : 'none';\n },\n onAction(callback) {\n actionCallbacks.add(callback);\n return () => { actionCallbacks.delete(callback); };\n },\n setActiveSession(sessionId, actions) {\n activeSessionId = sessionId;\n if (!sessionId) {\n if (ms) {\n ms.metadata = null;\n ms.playbackState = 'none';\n for (const [domAction] of ACTION_MATRIX) {\n try { ms.setActionHandler(domAction, null); } catch { /* best-effort */ }\n }\n }\n return;\n }\n // Prime silent-audio on first session becoming active.\n if (sessionsActive === 0) { primeSilentAudio(); sessionsActive = 1; }\n applyActionHandlers(actions ?? DEFAULT_MEDIA_ACTIONS);\n },\n destroySession(_sessionId) {\n sessionsActive = Math.max(0, sessionsActive - 1);\n if (sessionsActive === 0) teardownSilentAudio();\n },\n };\n}\n","/**\n * media-service.ts — NIP-5D media NAP reference service (navigator.mediaSession\n * reference implementation).\n *\n * Handles the napplet-owned subset of @napplet/nap/media:\n * media.session.create (result), media.session.update, media.session.destroy,\n * media.state, media.capabilities.\n *\n * HostMediaBridge contract: {@link HostMediaBridge} defines the pluggable backend\n * contract for metadata/state mirroring + action routing. The browser reference\n * implementation is {@link createBrowserMediaBridge} (mirrors to navigator.mediaSession\n * with setActionHandler matrix). When no hostBridge option is passed, createMediaService\n * internally uses createBrowserMediaBridge as the default — behavior is identical\n * to the Plan 27-01 single-path implementation.\n *\n * navigator.mediaSession mirroring: on session.create the browser bridge mirrors the\n * napplet-supplied metadata to navigator.mediaSession.metadata via new MediaMetadata()\n * and installs setActionHandler callbacks for the 5 OS transport actions\n * (play / pause / nexttrack / previoustrack / seekto). Each callback emits a canonical\n * media.command envelope to the owning napplet — that is the @napplet/nap/media\n * MediaCommandMessage shape consumed by the SDK's mediaOnCommand() helper.\n *\n * NAP-MEDIA now distinguishes napplet-owned playback from shell-owned\n * playback. This reference backend supports `owner: \"napplet\"` because it\n * mirrors a napplet's own media element to navigator.mediaSession. It rejects\n * `owner: \"shell\"` until a host bridge provides policy-checked source fetching\n * and playback.\n *\n * media.command push: when the OS user clicks a media control (hardware key, lock-screen\n * transport, OS media overlay), the bridge's onAction callback fires. The service looks\n * up the active session's owning napplet, invokes the per-window send callback captured\n * at session.create time, and delivers:\n * { type: 'media.command', sessionId, action, value? }\n * where action is the nap-media MediaAction literal (play|pause|next|prev|seek) and\n * value is the seekTime (seconds) for seek.\n *\n * Silent-audio prime: the browser bridge creates a hidden <audio> element with a\n * 4 kHz silent WAV data URL and plays it when the first session becomes active\n * (setActiveSession with a non-null sessionId). Without a playing audio element in\n * the host page, most browsers refuse to render OS media controls. The element is\n * cleaned up when the last session is destroyed (via bridge.destroySession).\n *\n * Multi-session registry with last-active-wins semantics: every session.create is\n * tracked in a Map<sessionId, SessionEntry>; any media.state report promotes that\n * session to active. The active session's metadata and playback state are reflected\n * via bridge.setMetadata / bridge.setPlaybackState. When the active session is\n * destroyed, the next-most-recently-touched session is promoted automatically.\n *\n * Note: this is SEPARATE from packages/services/src/audio-service.ts, which\n * is the legacy inc-topic-based audio source registry (audio:* topic events\n * over inc.emit). media-service is the canonical @napplet/nap/media NIP-5D\n * path and they coexist — audio-service continues to track audio sources for\n * shell UI, while media-service handles the NAP protocol envelope surface.\n *\n * Shell -> Napplet push types (media.command) are emitted here when\n * bridge.onAction callbacks fire — this is the canonical Phase 27 real backend.\n */\n\nimport type { NappletMessage } from '@napplet/core';\n// DRIFT-CORE-06 — Phase 11-deviation: ServiceDescriptor dropped from @napplet/core\n// v0.2.0+ (napplet phase-81). Re-exported from @kehto/runtime (canonical home after Phase 24 DRIFT-01).\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\nimport type {\n MediaAction,\n MediaCapabilitiesMessage,\n MediaCommandMessage,\n MediaMetadata,\n MediaSessionDestroyMessage,\n MediaSessionUpdateMessage,\n MediaStateMessage,\n} from '@napplet/nap/media/types';\nimport {\n createBrowserMediaBridge,\n DEFAULT_MEDIA_ACTIONS,\n type MediaSessionTarget,\n} from './browser-media-bridge.js';\n\nexport { createBrowserMediaBridge } from './browser-media-bridge.js';\nexport type {\n MediaMetadataLike,\n MediaSessionTarget,\n} from './browser-media-bridge.js';\n\n/** Registry entry — maps a sessionId back to its owning window, metadata snapshot,\n * state snapshot, and declared capabilities. Internal; never on the wire. */\ninterface SessionEntry {\n sessionId: string;\n windowId: string;\n owner: MediaPlaybackOwner;\n source: MediaSourceRef | undefined;\n metadata: MediaMetadata | undefined;\n state: { status: 'playing' | 'paused' | 'stopped' | 'buffering'; position?: number; duration?: number; volume?: number } | undefined;\n actions: readonly MediaAction[]; // from media.capabilities; defaults to ['play','pause','next','prev','seek']\n /** Monotonic tick updated on every session.create / session.update / state — used for last-active-wins resolution when the current active session is destroyed. */\n lastTouched: number;\n}\n\n/** Media service version — follows semver. */\nconst MEDIA_SERVICE_VERSION = '1.1.0';\n\nexport type MediaPlaybackOwner = 'shell' | 'napplet';\n\nexport interface MediaSourceRef {\n url?: string;\n blossomHash?: string;\n nostr?: {\n eventId?: string;\n address?: string;\n relays?: string[];\n };\n mimeType?: string;\n}\n\nexport interface MediaSessionCreateOptions {\n owner: MediaPlaybackOwner;\n sessionId?: string;\n source?: MediaSourceRef;\n metadata?: MediaMetadata;\n capabilities?: MediaAction[];\n autoplay?: boolean;\n live?: boolean;\n}\n\ntype MediaSessionCreateEnvelope = NappletMessage & Partial<MediaSessionCreateOptions> & {\n type: 'media.session.create';\n id?: string;\n};\n\ntype MediaSessionCreateResultEnvelope = NappletMessage & {\n type: 'media.session.create.result';\n id: string;\n sessionId?: string;\n owner?: MediaPlaybackOwner;\n error?: string;\n};\n\n/**\n * Host-bridge contract for pluggable media backends.\n *\n * The browser reference implementation ({@link createBrowserMediaBridge}) mirrors\n * napplet-reported metadata/state to `navigator.mediaSession` and installs\n * `setActionHandler` callbacks that fan into the bridge's onAction subscribers.\n * It satisfies this interface with all 5 fields implemented (setActiveSession\n * switches the active session and optionally re-applies action-handler narrowing;\n * destroySession tears down the silent-audio prime on last-session teardown).\n *\n * Host apps (Electron, Tauri) implement this interface in their own code and\n * pass it via `createMediaService({ hostBridge: myBridge })` — the service\n * then delegates metadata/state mirroring + action routing to the bridge and\n * remains browser-free. Session-ownership bookkeeping (which windowId owns\n * which sessionId, which send callback routes media.command back to which\n * napplet) stays in the service layer — that's wire-protocol concern, not a\n * bridge concern.\n *\n * Reference implementations for Electron / Tauri are explicitly out of v1.4\n * scope and live in host-app examples / follow-up milestones (see\n * REQUIREMENTS.md \"Future Requirements\").\n *\n * @example\n * ```ts\n * // Host-app pseudocode (Electron main-process relay):\n * const electronBridge: HostMediaBridge = {\n * setMetadata(sessionId, md) { mediaBridge.sendMetadata({ sessionId, md }); },\n * setPlaybackState(sessionId, state) { mediaBridge.sendPlaybackState({ sessionId, state }); },\n * onAction(cb) {\n * const handler = (_: unknown, msg: { sessionId: string; action: MediaAction; value?: number }) =>\n * cb(msg.sessionId, msg.action, msg.value);\n * mediaBridge.onAction(handler);\n * return () => mediaBridge.offAction(handler);\n * },\n * };\n * const media = createMediaService({ hostBridge: electronBridge });\n * runtime.registerService('media', media);\n * ```\n */\nexport interface HostMediaBridge {\n /**\n * Set the metadata displayed on the OS transport surface for a session.\n * Called on session.create (with initial metadata) and on session.update\n * (with merged metadata) whenever the session is the active session.\n * Implementations MUST be idempotent.\n */\n setMetadata(sessionId: string, metadata: MediaMetadata): void;\n\n /**\n * Set the playback state for a session. Called on media.state reports\n * whenever the session is the active session. State strings match\n * nap-media MediaState.status exactly. Implementations MUST be idempotent.\n */\n setPlaybackState(sessionId: string, state: 'playing' | 'paused' | 'stopped' | 'buffering'): void;\n\n /**\n * Subscribe to OS-level action events (user clicks play/pause/seek/next/prev\n * on the transport surface). Returns an unsubscribe handle.\n *\n * The callback receives `(sessionId, action, value?)`. `sessionId` is the\n * bridge's currently-active session (the browser impl tracks this internally\n * via setActionHandler-at-fire-time; native impls track via setActiveSession).\n * `value` is populated for `action === 'seek'` (seek target in seconds) and\n * for `action === 'volume'` (0.0-1.0). The service dispatches the resulting\n * `media.command` envelope to the owning napplet of that session.\n */\n onAction(callback: (sessionId: string, action: MediaAction, value?: number) => void): () => void;\n\n /**\n * Optional: notify the bridge that the active session has changed. The\n * browser reference impl uses this to switch which session's metadata/state\n * is mirrored to the singleton navigator.mediaSession and to install (or\n * clear) action handlers for the session's declared capabilities.\n *\n * The optional `actions` parameter carries the session's declared capability\n * set so the bridge can narrow which OS transport buttons are active. When\n * omitted, the bridge applies its default set. Native OS bridges that track\n * active-session state internally may omit this field entirely.\n */\n setActiveSession?(sessionId: string | null, actions?: readonly MediaAction[]): void;\n\n /**\n * Optional: tear down per-session resources. The browser reference impl\n * uses this to remove the silent-audio prime element when the last session\n * is destroyed. Bridges that need no per-session teardown may omit this field.\n */\n destroySession?(sessionId: string): void;\n}\n\n/**\n * Optional host callbacks for the media service.\n *\n * Host shells can hook session creation and state updates to drive\n * their own UI (e.g., now-playing banner) without replacing the\n * ServiceHandler wholesale.\n *\n * @example\n * ```ts\n * const media = createMediaService({\n * onSessionCreate: (windowId, sessionId, metadata) => {\n * console.log(`[${windowId}] created session ${sessionId}`, metadata);\n * },\n * onState: (windowId, sessionId, state) => {\n * nowPlaying.update(windowId, state);\n * },\n * });\n *\n * runtime.registerService('media', media);\n * ```\n */\nexport interface MediaServiceOptions {\n /** Called when a napplet creates a session. May inspect/record the session. */\n onSessionCreate?: (windowId: string, sessionId: string, metadata?: unknown) => void;\n /** Called on media.state updates — high-frequency; keep handler work minimal. */\n onState?: (windowId: string, sessionId: string, state: unknown) => void;\n /** Called when a napplet destroys a session. */\n onSessionDestroy?: (windowId: string, sessionId: string) => void;\n /** Called when a napplet updates session metadata. */\n onSessionUpdate?: (windowId: string, sessionId: string, metadata: unknown) => void;\n /** Called when a napplet declares capabilities for a session. */\n onCapabilities?: (windowId: string, sessionId: string, actions: unknown) => void;\n\n /**\n * MediaSession target override (used by default browser bridge only).\n * Defaults to `navigator.mediaSession` when running in a browser. Pass a\n * MockMediaSession in unit tests. Ignored when `hostBridge` is provided.\n */\n mediaSessionTarget?: MediaSessionTarget;\n\n /**\n * DOM document override (used by default browser bridge only).\n * Defaults to `document` when available. Set to null to disable the silent-audio\n * prime entirely — useful in unit tests. Ignored when `hostBridge` is provided.\n */\n documentTarget?: Document | null;\n\n /**\n * Optional pluggable backend for metadata/state mirroring + OS action handling.\n * When provided, the service delegates setMetadata / setPlaybackState / onAction\n * to the bridge and skips navigator.mediaSession entirely. When omitted, the\n * service internally uses {@link createBrowserMediaBridge} as the default.\n * See {@link HostMediaBridge}.\n */\n hostBridge?: HostMediaBridge;\n}\n\ninterface MediaServiceState {\n bridge: HostMediaBridge;\n options: MediaServiceOptions;\n sessionRegistry: Map<string, SessionEntry>;\n windowSessions: Map<string, Set<string>>;\n sendHandles: Map<string, (msg: NappletMessage) => void>;\n activeSessionId: string | null;\n touchCounter: number;\n sessionCounter: number;\n}\n\nfunction createMediaServiceState(options: MediaServiceOptions, bridge: HostMediaBridge): MediaServiceState {\n return {\n bridge,\n options,\n sessionRegistry: new Map<string, SessionEntry>(),\n windowSessions: new Map<string, Set<string>>(),\n sendHandles: new Map<string, (msg: NappletMessage) => void>(),\n activeSessionId: null,\n touchCounter: 0,\n sessionCounter: 0,\n };\n}\n\nfunction setActive(state: MediaServiceState, sessionId: string | null, actions?: readonly MediaAction[]): void {\n state.activeSessionId = sessionId;\n state.bridge.setActiveSession?.(sessionId, actions);\n if (!sessionId) return;\n const entry = state.sessionRegistry.get(sessionId);\n if (!entry) return;\n if (entry.metadata) state.bridge.setMetadata(sessionId, entry.metadata);\n if (entry.state) state.bridge.setPlaybackState(sessionId, entry.state.status);\n}\n\nfunction promoteNextActiveOrClear(state: MediaServiceState): void {\n if (state.sessionRegistry.size === 0) {\n setActive(state, null);\n return;\n }\n let latest: SessionEntry | null = null;\n for (const entry of state.sessionRegistry.values()) {\n if (!latest || entry.lastTouched > latest.lastTouched) latest = entry;\n }\n setActive(state, latest ? latest.sessionId : null, latest?.actions);\n}\n\nfunction sendMediaCommand(state: MediaServiceState, sessionId: string, action: MediaAction, value?: number): void {\n const entry = state.sessionRegistry.get(sessionId);\n if (!entry) return;\n const send = state.sendHandles.get(entry.windowId);\n if (!send) return;\n const payload: MediaCommandMessage = {\n type: 'media.command',\n sessionId,\n action,\n ...(typeof value === 'number' ? { value } : {}),\n };\n send(payload as NappletMessage);\n}\n\nfunction registerWindowSession(state: MediaServiceState, windowId: string, sessionId: string): void {\n if (!state.windowSessions.has(windowId)) state.windowSessions.set(windowId, new Set());\n state.windowSessions.get(windowId)!.add(sessionId);\n}\n\nfunction sendSessionCreateResult(\n send: (msg: NappletMessage) => void,\n id: string | undefined,\n fields: Omit<MediaSessionCreateResultEnvelope, 'type' | 'id'>,\n): void {\n send({\n type: 'media.session.create.result',\n id: id ?? '',\n ...fields,\n } as NappletMessage);\n}\n\nfunction isMediaPlaybackOwner(value: unknown): value is MediaPlaybackOwner {\n return value === 'shell' || value === 'napplet';\n}\n\nfunction hasSourceRef(source: MediaSourceRef | undefined): boolean {\n if (!source) return false;\n if (typeof source.url === 'string' && source.url.length > 0) return true;\n if (typeof source.blossomHash === 'string' && source.blossomHash.length > 0) return true;\n if (source.nostr) {\n return Boolean(source.nostr.eventId || source.nostr.address);\n }\n return false;\n}\n\nfunction canonicalizeSessionId(\n state: MediaServiceState,\n windowId: string,\n preferredSessionId: string | undefined,\n): string {\n const trimmed = typeof preferredSessionId === 'string' ? preferredSessionId.trim() : '';\n const hint = trimmed || `session-${++state.sessionCounter}`;\n if (!state.sessionRegistry.has(hint)) return hint;\n\n let next: string;\n do {\n next = `${windowId}:${hint}:${++state.sessionCounter}`;\n } while (state.sessionRegistry.has(next));\n return next;\n}\n\nfunction handleSessionCreate(\n state: MediaServiceState,\n windowId: string,\n message: MediaSessionCreateEnvelope,\n send: (msg: NappletMessage) => void,\n): void {\n if (!isMediaPlaybackOwner(message.owner)) {\n sendSessionCreateResult(send, message.id, { error: 'missing owner' });\n return;\n }\n\n if (message.owner === 'shell') {\n if (!hasSourceRef(message.source)) {\n sendSessionCreateResult(send, message.id, { owner: 'shell', error: 'missing source' });\n return;\n }\n sendSessionCreateResult(send, message.id, { owner: 'shell', error: 'unsupported owner mode' });\n return;\n }\n\n state.sendHandles.set(windowId, send);\n const sessionId = canonicalizeSessionId(state, windowId, message.sessionId);\n const entry: SessionEntry = {\n sessionId,\n windowId,\n owner: message.owner,\n source: message.source,\n metadata: message.metadata,\n state: undefined,\n actions: message.capabilities ?? DEFAULT_MEDIA_ACTIONS,\n lastTouched: ++state.touchCounter,\n };\n state.sessionRegistry.set(sessionId, entry);\n registerWindowSession(state, windowId, sessionId);\n setActive(state, sessionId, entry.actions);\n state.options.onSessionCreate?.(windowId, sessionId, message.metadata);\n sendSessionCreateResult(send, message.id, { sessionId, owner: message.owner });\n}\n\nfunction handleSessionUpdate(state: MediaServiceState, windowId: string, message: MediaSessionUpdateMessage): void {\n const entry = state.sessionRegistry.get(message.sessionId);\n if (entry) {\n entry.metadata = { ...entry.metadata, ...message.metadata };\n entry.lastTouched = ++state.touchCounter;\n if (entry.owner === 'napplet' && message.sessionId === state.activeSessionId && entry.metadata) {\n state.bridge.setMetadata(message.sessionId, entry.metadata);\n }\n }\n state.options.onSessionUpdate?.(windowId, message.sessionId, message.metadata);\n}\n\nfunction handleSessionDestroy(state: MediaServiceState, windowId: string, message: MediaSessionDestroyMessage): void {\n const entry = state.sessionRegistry.get(message.sessionId);\n if (entry) {\n state.sessionRegistry.delete(message.sessionId);\n const set = state.windowSessions.get(entry.windowId);\n if (set) {\n set.delete(message.sessionId);\n if (set.size === 0) state.windowSessions.delete(entry.windowId);\n }\n state.bridge.destroySession?.(message.sessionId);\n if (message.sessionId === state.activeSessionId) promoteNextActiveOrClear(state);\n }\n state.options.onSessionDestroy?.(windowId, message.sessionId);\n}\n\nfunction handleMediaState(state: MediaServiceState, windowId: string, message: MediaStateMessage): void {\n const entry = state.sessionRegistry.get(message.sessionId);\n if (entry?.owner === 'napplet') {\n entry.state = {\n status: message.status,\n position: message.position,\n duration: message.duration,\n volume: message.volume,\n };\n entry.lastTouched = ++state.touchCounter;\n if (state.activeSessionId !== message.sessionId) setActive(state, message.sessionId, entry.actions);\n else state.bridge.setPlaybackState(message.sessionId, message.status);\n }\n state.options.onState?.(windowId, message.sessionId, message);\n}\n\nfunction handleMediaCapabilities(\n state: MediaServiceState,\n windowId: string,\n message: MediaCapabilitiesMessage,\n): void {\n const entry = state.sessionRegistry.get(message.sessionId);\n if (entry?.owner === 'napplet') {\n entry.actions = message.actions;\n entry.lastTouched = ++state.touchCounter;\n if (message.sessionId === state.activeSessionId) {\n state.bridge.setActiveSession?.(message.sessionId, entry.actions);\n }\n }\n state.options.onCapabilities?.(windowId, message.sessionId, message.actions);\n}\n\nfunction handleMediaMessage(\n state: MediaServiceState,\n windowId: string,\n message: NappletMessage,\n send: (msg: NappletMessage) => void,\n): void {\n switch (message.type) {\n case 'media.session.create':\n handleSessionCreate(state, windowId, message as MediaSessionCreateEnvelope, send);\n return;\n case 'media.session.update':\n handleSessionUpdate(state, windowId, message as MediaSessionUpdateMessage);\n return;\n case 'media.session.destroy':\n handleSessionDestroy(state, windowId, message as MediaSessionDestroyMessage);\n return;\n case 'media.state':\n handleMediaState(state, windowId, message as MediaStateMessage);\n return;\n case 'media.capabilities':\n handleMediaCapabilities(state, windowId, message as MediaCapabilitiesMessage);\n return;\n default: {\n const id = (message as NappletMessage & { id?: string }).id ?? '';\n send({\n type: `${message.type}.error`,\n id,\n error: `Unknown media method: ${message.type}`,\n } as NappletMessage);\n }\n }\n}\n\nfunction destroyWindowSessions(state: MediaServiceState, windowId: string): void {\n const sessions = state.windowSessions.get(windowId);\n if (sessions) {\n const ownedActive = state.activeSessionId !== null && sessions.has(state.activeSessionId);\n for (const sessionId of sessions) {\n state.sessionRegistry.delete(sessionId);\n state.bridge.destroySession?.(sessionId);\n }\n state.windowSessions.delete(windowId);\n if (ownedActive) promoteNextActiveOrClear(state);\n }\n state.sendHandles.delete(windowId);\n}\n\nfunction destroyMediaState(state: MediaServiceState, unsubscribeAction: () => void): void {\n unsubscribeAction();\n for (const sessionId of state.sessionRegistry.keys()) state.bridge.destroySession?.(sessionId);\n state.bridge.setActiveSession?.(null);\n state.sessionRegistry.clear();\n state.windowSessions.clear();\n state.sendHandles.clear();\n state.activeSessionId = null;\n state.touchCounter = 0;\n state.sessionCounter = 0;\n}\n\n/**\n * Create a media NAP service handler with navigator.mediaSession integration.\n *\n * Implements the 5 napplet->shell media.* request types defined in\n * `@napplet/nap/media`. Only `media.session.create` produces a reply\n * envelope (`media.session.create.result`) — the remaining four\n * (`session.update`, `session.destroy`, `state`, `capabilities`) are\n * fire-and-forget per the NAP spec.\n *\n * Unknown `media.*` actions produce a `<type>.error` envelope so\n * napplets are never left hanging on a malformed request.\n *\n * @param options - Optional host callbacks for session lifecycle + state, plus\n * mediaSessionTarget and documentTarget for test injection, and an optional\n * hostBridge for native-OS media backend delegation.\n * @returns A ServiceHandler (with `destroy()`) to register with the runtime\n *\n * @example\n * ```ts\n * import { createMediaService } from '@kehto/services';\n *\n * const media = createMediaService();\n * runtime.registerService('media', media);\n * // Later, on shell teardown:\n * media.destroy();\n * ```\n */\nexport function createMediaService(options: MediaServiceOptions = {}): ServiceHandler & { destroy(): void } {\n const descriptor: ServiceDescriptor = {\n name: 'media',\n version: MEDIA_SERVICE_VERSION,\n description: options.hostBridge\n ? 'NIP-5D media NAP reference handler (host-bridge delegated)'\n : 'NIP-5D media NAP reference handler (navigator.mediaSession mirror)',\n };\n\n const bridge: HostMediaBridge = options.hostBridge\n ?? createBrowserMediaBridge({\n mediaSessionTarget: options.mediaSessionTarget,\n documentTarget: options.documentTarget,\n });\n const state = createMediaServiceState(options, bridge);\n\n const unsubscribeAction = bridge.onAction((sessionId, action, value) => {\n sendMediaCommand(state, sessionId, action, value);\n });\n\n return {\n descriptor,\n\n handleMessage(windowId: string, message: NappletMessage, send: (msg: NappletMessage) => void): void {\n handleMediaMessage(state, windowId, message, send);\n },\n\n onWindowDestroyed(windowId: string): void {\n destroyWindowSessions(state, windowId);\n },\n\n destroy(): void {\n destroyMediaState(state, unsubscribeAction);\n },\n };\n}\n","/**\n * notify-service.ts — NIP-5D notify NAP reference service (stub-level).\n *\n * Handles the 5 napplet -> shell request types from `@napplet/nap/notify`:\n * - `notify.send` -> `notify.send.result` (shell-assigned id)\n * - `notify.dismiss` -> fire-and-forget\n * - `notify.badge` -> fire-and-forget\n * - `notify.channel.register` -> fire-and-forget\n * - `notify.permission.request` -> `notify.permission.result { granted }`\n *\n * Stub-level: no real Notification API calls, no real channel registry.\n * Host apps wire a real backend via\n * `runtime.registerService('notify', realHandler)`.\n *\n * Coexistence note: this is SEPARATE from\n * `packages/services/src/notification-service.ts`, which is the legacy\n * inc-topic-based notification registry (operates on `inc.emit` topics\n * under `notifications:*`). `notify-service.ts` is the canonical\n * `@napplet/nap/notify` NIP-5D path and lives alongside the legacy module.\n * If the host app registers this service via\n * `runtime.registerService('notify', ...)`, @napplet/nap/notify messages\n * land here; the legacy inc-emit topic path remains untouched.\n *\n * Shell -> napplet push messages (`notify.action`, `notify.clicked`,\n * `notify.dismissed`, `notify.controls`) are not emitted by this stub —\n * they are the host app's responsibility and are deferred to a future plan.\n */\n\nimport type { NappletMessage } from '@napplet/core';\n// DRIFT-CORE-06 — Phase 11-deviation: ServiceDescriptor dropped from @napplet/core\n// v0.2.0+ (napplet phase-81). Re-exported from @kehto/runtime (canonical home after Phase 24 DRIFT-01).\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\nimport type {\n NotifySendMessage,\n NotifySendResultMessage,\n NotifyPermissionRequestMessage,\n NotifyPermissionResultMessage,\n} from '@napplet/nap/notify/types';\n\n/** Notify service version — follows semver. */\nconst NOTIFY_SERVICE_VERSION = '1.0.0';\n\n/**\n * Optional configuration for `createNotifyService`.\n *\n * @example\n * ```ts\n * const notify = createNotifyService({\n * generateId: () => crypto.randomUUID(),\n * defaultGrant: false,\n * onSend: (windowId, msg) => console.log(`napplet ${windowId} sent ${msg.title}`),\n * });\n * runtime.registerService('notify', notify);\n * ```\n */\nexport interface NotifyServiceOptions {\n /**\n * Generate a shell-assigned notification ID for `notify.send.result`.\n * Default: a monotonically increasing `shell-<n>` counter.\n */\n generateId?: () => string;\n /**\n * Default permission grant for `notify.permission.request`.\n * Host apps that want real permission prompts should replace this\n * service with a real backend via `runtime.registerService`.\n * Default: `true`.\n */\n defaultGrant?: boolean;\n /**\n * Called synchronously when a napplet dispatches `notify.send`.\n * Intended for host-app plumbing (UI toast, logging, etc.) without\n * requiring a full backend replacement.\n */\n onSend?: (windowId: string, payload: NotifySendMessage) => void;\n}\n\n/**\n * Create a stub-level notify service handler.\n *\n * Answers the 5 napplet->shell request types from `@napplet/nap/notify`.\n * Does NOT implement a real backend (no DOM Notification API, no channel\n * registry, no permission prompt). Host apps replace this via\n * `runtime.registerService('notify', realHandler)` when a real backend is\n * needed.\n *\n * @param options - Optional service configuration (see NotifyServiceOptions)\n * @returns A ServiceHandler to register with the runtime under domain `notify`\n *\n * @example\n * ```ts\n * import { createNotifyService } from '@kehto/services';\n *\n * const notify = createNotifyService();\n * runtime.registerService('notify', notify);\n * ```\n */\nexport function createNotifyService(options: NotifyServiceOptions = {}): ServiceHandler {\n let counter = 0;\n const gen = options.generateId ?? ((): string => {\n counter += 1;\n return `shell-${counter}`;\n });\n const defaultGrant = options.defaultGrant ?? true;\n\n const descriptor: ServiceDescriptor = {\n name: 'notify',\n version: NOTIFY_SERVICE_VERSION,\n description: 'NIP-5D notify NAP reference handler (stub)',\n };\n\n return {\n descriptor,\n\n handleMessage(windowId: string, message: NappletMessage, send: (msg: NappletMessage) => void): void {\n switch (message.type) {\n case 'notify.send': {\n const m = message as NotifySendMessage;\n options.onSend?.(windowId, m);\n const result: NotifySendResultMessage = {\n type: 'notify.send.result',\n id: m.id,\n notificationId: gen(),\n };\n send(result as NappletMessage);\n return;\n }\n\n case 'notify.dismiss':\n case 'notify.badge':\n case 'notify.channel.register':\n // state to mutate; host apps with real backends override.\n return;\n\n case 'notify.permission.request': {\n const m = message as NotifyPermissionRequestMessage;\n const result: NotifyPermissionResultMessage = {\n type: 'notify.permission.result',\n id: m.id,\n granted: defaultGrant,\n };\n send(result as NappletMessage);\n return;\n }\n\n default: {\n const id = (message as NappletMessage & { id?: string }).id ?? '';\n send({\n type: `${message.type}.error`,\n id,\n error: `Unknown notify method: ${message.type}`,\n } as NappletMessage);\n }\n }\n },\n\n onWindowDestroyed(_windowId: string): void {\n // No per-window state in the stub. Host apps with real backends\n // override to clean up pending notifications / channel registrations.\n },\n };\n}\n","/**\n * theme-service.ts — NIP-5D theme NAP reference service.\n *\n * Handles the single napplet->shell request type from @napplet/nap/theme:\n * - `theme.get` -> `theme.get.result { theme }` (current theme)\n *\n * Exposes a host-facing `publishTheme(theme)` handle that:\n * 1. Replaces the service's internal current theme.\n * 2. Invokes `options.onBroadcast(envelope)` synchronously with a\n * `theme.changed` envelope so the shell adapter (Plan 13-02) can\n * fan-out the push to every registered napplet.\n * 3. Returns the envelope so callers can use it directly if they prefer.\n *\n * The default theme values are centralized here and mirrored in the runtime's\n * fallback path (`packages/runtime/src/runtime.ts`) — runtime does NOT import\n * from @kehto/services because services depends on runtime (one-way only).\n *\n * Host apps replace this via `runtime.registerService('theme', realHandler)`\n * when real CSS injection / storage / dark-mode logic is needed.\n *\n * @example\n * ```ts\n * import { createThemeService } from '@kehto/services';\n *\n * const theme = createThemeService({\n * onBroadcast: (envelope) => broadcastToAllNapplets(envelope),\n * });\n * runtime.registerService('theme', theme.handler);\n *\n * // Later, when the user flips dark/light mode:\n * theme.publishTheme(newTheme);\n * ```\n */\n\nimport type { NappletMessage } from '@napplet/core';\n// DRIFT-CORE-06 — Phase 11-deviation: ServiceDescriptor dropped from\n// @napplet/core v0.2.0+ (napplet phase-81). Re-exported from\n// @kehto/runtime (canonical home after Phase 24 DRIFT-01).\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\nimport type {\n Theme,\n ThemeChangedMessage,\n ThemeGetResultMessage,\n} from '@napplet/nap/theme/types';\n\n/** Theme service version — follows semver. */\nconst THEME_SERVICE_VERSION = '1.0.0';\n\n/**\n * Canonical default theme values.\n *\n * Must stay synchronized with the runtime fallback constant at\n * `packages/runtime/src/runtime.ts` (THEME_FALLBACK_DEFAULT). The runtime\n * keeps a local copy instead of importing from @kehto/services to avoid a\n * runtime->services dependency (services depends on runtime; one-way).\n */\nconst DEFAULT_THEME: Theme = {\n colors: {\n background: '#0a0a0a',\n text: '#e0e0e0',\n primary: '#7aa2f7',\n },\n // fonts, background, title intentionally undefined — all optional per\n // @napplet/nap/theme Theme interface.\n};\n\n/**\n * Configuration for `createThemeService`.\n *\n * @example\n * ```ts\n * const theme = createThemeService({\n * initialTheme: { colors: { background: '#fff', text: '#000', primary: '#00f' } },\n * onBroadcast: (envelope) => shellBridge.broadcastToAll(envelope),\n * });\n * ```\n */\nexport interface ThemeServiceOptions {\n /**\n * Override the default theme payload. If omitted, the service starts with\n * the canonical defaults (`#0a0a0a / #e0e0e0 / #7aa2f7`).\n */\n initialTheme?: Theme;\n\n /**\n * Called synchronously from `publishTheme(theme)` with a `theme.changed`\n * envelope. Intended for the shell adapter (Plan 13-02) to fan-out the\n * push to every registered napplet via the runtime's sendToNapplet\n * primitive.\n *\n * Keep this callback shape framework-agnostic — the service does NOT\n * import any shell / browser APIs.\n */\n onBroadcast?: (envelope: ThemeChangedMessage) => void;\n}\n\n/**\n * A theme service bundle — the ServiceHandler that handles `theme.*`\n * envelopes, the host-facing `publishTheme(theme)` handle for theme-change\n * broadcasts, and a `getCurrentTheme()` accessor for host-side reads.\n */\nexport interface ThemeService {\n /** Register this with the runtime via `runtime.registerService('theme', handler)`. */\n handler: ServiceHandler;\n\n /**\n * Publish a theme-change to the shell adapter. Updates the service's\n * internal current theme, invokes `options.onBroadcast` with a\n * `theme.changed` envelope, and returns the envelope.\n *\n * @param theme - The new theme payload\n * @returns A `theme.changed` envelope (same one passed to onBroadcast)\n */\n publishTheme: (theme: Theme) => ThemeChangedMessage;\n\n /** Return the current theme. Equivalent to the payload a napplet's `theme.get` would receive. */\n getCurrentTheme: () => Theme;\n}\n\n/**\n * Create a theme service that handles the NIP-5D `theme.*` NAP.\n *\n * Answers `theme.get` with the current theme (default or\n * `options.initialTheme`). Exposes `publishTheme(theme)` for the host app to\n * broadcast theme changes to every registered napplet — the shell adapter\n * (Plan 13-02) wires `onBroadcast` to `runtime.sendToNapplet` fan-out.\n *\n * @param options - Optional service configuration (see ThemeServiceOptions)\n * @returns A ThemeService bundle ready for `runtime.registerService('theme', service.handler)`\n *\n * @example\n * ```ts\n * import { createThemeService } from '@kehto/services';\n *\n * const theme = createThemeService();\n * runtime.registerService('theme', theme.handler);\n * theme.publishTheme({ colors: { background: '#fff', text: '#000', primary: '#00f' } });\n * ```\n */\nexport function createThemeService(options: ThemeServiceOptions = {}): ThemeService {\n let currentTheme: Theme = options.initialTheme ?? DEFAULT_THEME;\n\n const descriptor: ServiceDescriptor = {\n name: 'theme',\n version: THEME_SERVICE_VERSION,\n description: 'NIP-5D theme NAP reference handler',\n };\n\n const handler: ServiceHandler = {\n descriptor,\n\n handleMessage(\n _windowId: string,\n message: NappletMessage,\n send: (msg: NappletMessage) => void,\n ): void {\n const id = (message as NappletMessage & { id?: string }).id ?? '';\n\n if (message.type === 'theme.get') {\n const result: ThemeGetResultMessage = {\n type: 'theme.get.result',\n id,\n theme: currentTheme,\n };\n send(result as NappletMessage);\n return;\n }\n\n send({\n type: `${message.type}.error`,\n id,\n error: `Unknown theme method: ${message.type}`,\n } as NappletMessage);\n },\n\n // Theme service has no per-window state to clean up.\n onWindowDestroyed(_windowId: string): void {\n /* no-op */\n },\n };\n\n function publishTheme(theme: Theme): ThemeChangedMessage {\n currentTheme = theme;\n const envelope: ThemeChangedMessage = { type: 'theme.changed', theme };\n options.onBroadcast?.(envelope);\n return envelope;\n }\n\n function getCurrentTheme(): Theme {\n return currentTheme;\n }\n\n return { handler, publishTheme, getCurrentTheme };\n}\n","/**\n * config-service.ts — NAP-CONFIG reference service (9th NAP domain, v1.7 Phase 39).\n *\n * Shell-side reference implementation for the canonical NAP-CONFIG wire\n * protocol (`@napplet/nap/config`, published at `^0.3.0`). Handles the full\n * 8-message discriminated union: 5 napplet→shell request types + 3\n * shell→napplet result/push types.\n *\n * ──────────────────────────── SCOPE BOUNDARY (CONFIG-04) ─────────────────────────\n * NAP-CONFIG is **shell-managed per-napplet configuration**. Napplets observe\n * values via `config.get` (one-shot) or `config.subscribe` (snapshot + live\n * push). The shell is the **sole writer** — there is intentionally **NO**\n * `config.set` wire message. Napplets cannot mutate configuration values;\n * the shell owns persistence and the update flow.\n *\n * Do NOT use this service as a general key-value store. NAP-STORAGE\n * (`state:read` / `state:write`) remains the general KV surface. Using\n * NAP-CONFIG to store e.g. `{ lastScrollPosition: 420 }` is an anti-pattern\n * (H-07 in PITFALLS.md) — such state belongs in NAP-STORAGE.\n * ──────────────────────────────────────────────────────────────────────────────────\n *\n * Host integration: provide `getValues()` returning the current\n * `ConfigValues` snapshot. Call the returned `publishValues(newValues)`\n * whenever the configuration changes — the service fans the new snapshot\n * out to every napplet that has an active `config.subscribe`.\n *\n * Optional: provide `registerSchema` to accept napplet-declared schemas at\n * runtime (the ref impl does a minimal shape check using the Core Subset\n * validator; use `ajv` in host impls that need strict draft-07 conformance).\n * Provide `openSettings` to open a shell-side UI for the napplet (no\n * response envelope — fire-and-forget UI hook).\n *\n * @example\n * ```ts\n * import { createConfigService } from '@kehto/services';\n *\n * const configFixtures = { theme: 'dark', density: 'compact', recentSearches: [] };\n * const config = createConfigService({\n * getValues: () => ({ ...configFixtures }),\n * });\n * runtime.registerService('config', config.handler);\n *\n * // Later, when shell-side values change:\n * configFixtures.theme = 'light';\n * config.publishValues({ ...configFixtures });\n * ```\n */\n\nimport type { NappletMessage } from '@napplet/core';\n// DRIFT-CORE-06 — Phase 11-deviation: ServiceDescriptor dropped from @napplet/core\n// v0.2.0+ (napplet phase-81). Re-exported from @kehto/runtime (canonical home after Phase 24 DRIFT-01).\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\nimport type {\n ConfigGetMessage,\n ConfigRegisterSchemaMessage,\n ConfigOpenSettingsMessage,\n ConfigValues,\n ConfigValuesMessage,\n ConfigRegisterSchemaResultMessage,\n ConfigSchemaErrorCode,\n NappletConfigSchema,\n} from '@napplet/nap/config/types';\n\n/** Config service version — follows semver. */\nconst CONFIG_SERVICE_VERSION = '1.0.0';\n\n/**\n * Shape returned by a successful `registerSchema` result (ok=true) or a\n * rejection (ok=false + code + error). Mirrors the wire envelope fields.\n */\nexport type ConfigSchemaValidation =\n | { ok: true }\n | { ok: false; code: ConfigSchemaErrorCode; error: string };\n\n/**\n * Configuration options for `createConfigService` (options-as-bridge\n * per v1.6 Decision 18).\n *\n * @example\n * ```ts\n * const config = createConfigService({\n * getValues: () => ({ theme: 'dark', density: 'compact' }),\n * openSettings: (windowId, section) => showSettingsPanel(windowId, section),\n * });\n * ```\n */\nexport interface ConfigServiceOptions {\n /**\n * Returns the current configuration values snapshot.\n * Called on every `config.get` and at every `config.subscribe` initial push.\n * Implementations should return a fresh object (not a mutable reference).\n */\n getValues(): ConfigValues;\n\n /**\n * Optional: receive notification when a napplet subscribes to config updates.\n * Fire-and-forget — the service tracks the subscription internally regardless.\n */\n onSubscribe?: (windowId: string) => void;\n\n /**\n * Optional: receive notification when a napplet unsubscribes.\n */\n onUnsubscribe?: (windowId: string) => void;\n\n /**\n * Optional: validate and store a napplet-provided schema.\n *\n * If omitted, the ref impl runs its own Core Subset check (hand-coded\n * validator; 30-50 lines) and returns ok/reject. Hosts that need strict\n * draft-07 conformance should provide an ajv-backed implementation.\n *\n * Return shape mirrors `config.registerSchema.result` wire envelope\n * (minus the `id` — the dispatch layer correlates).\n */\n registerSchema?: (\n windowId: string,\n schema: NappletConfigSchema,\n version: number | undefined,\n ) => ConfigSchemaValidation;\n\n /**\n * Optional: open the shell-side settings UI for this napplet.\n * Fire-and-forget — no response envelope per the wire spec.\n * If omitted, `config.openSettings` is silently dropped (D10 allows\n * the config-demo napplet to function without a settings UI).\n */\n openSettings?: (windowId: string, section: string | undefined) => void;\n}\n\n/**\n * NAP-CONFIG reference service bundle — `handler` to register with the\n * runtime, `publishValues` for the host app to push updates live to all\n * subscribed napplets.\n */\nexport interface ConfigService {\n /** Register this with the runtime via `runtime.registerService('config', handler)`. */\n handler: ServiceHandler;\n\n /**\n * Broadcast a new values snapshot to every napplet with an active\n * `config.subscribe`. Each subscriber receives a `config.values` envelope\n * with no `id` (push form per wire spec — absence of `id` distinguishes\n * push from correlated `config.get` response).\n *\n * @param values - The new configuration snapshot (full object, not a diff)\n */\n publishValues(values: ConfigValues): void;\n}\n\n/**\n * Minimal JSON Schema validator covering the NAP-CONFIG Core Subset:\n * type: object / string / number / boolean / array, required[], default, properties.\n *\n * Explicitly rejects: $ref, pattern, oneOf/anyOf/allOf/not, if/then/else.\n * Returns { ok: true } on shape sanity, otherwise an error code per the\n * canonical ConfigSchemaErrorCode union.\n *\n * Host apps that need strict draft-07 conformance should supply a custom\n * `registerSchema` callback backed by ajv@8.\n */\nfunction validateCoreSubset(schema: unknown): ConfigSchemaValidation {\n if (typeof schema !== 'object' || schema === null || Array.isArray(schema)) {\n return { ok: false, code: 'invalid-schema', error: 'schema root must be an object' };\n }\n const s = schema as Record<string, unknown>;\n\n // Reject forbidden keywords (NAP-CONFIG Core Subset limits per spec).\n if ('$ref' in s) {\n return { ok: false, code: 'ref-not-allowed', error: '$ref is not permitted in the Core Subset' };\n }\n if ('pattern' in s) {\n return {\n ok: false,\n code: 'pattern-not-allowed',\n error: 'pattern is not permitted in the Core Subset',\n };\n }\n if ('oneOf' in s || 'anyOf' in s || 'allOf' in s || 'not' in s) {\n return {\n ok: false,\n code: 'invalid-schema',\n error: 'oneOf/anyOf/allOf/not are not permitted in the Core Subset',\n };\n }\n if ('if' in s || 'then' in s || 'else' in s) {\n return {\n ok: false,\n code: 'invalid-schema',\n error: 'if/then/else are not permitted in the Core Subset',\n };\n }\n if (s.type !== 'object') {\n return { ok: false, code: 'invalid-schema', error: 'schema root must have type: \"object\"' };\n }\n\n // Shallow properties check: each declared property must use a supported type.\n const props = s.properties;\n if (props !== undefined && (typeof props !== 'object' || props === null)) {\n return { ok: false, code: 'invalid-schema', error: 'properties must be an object' };\n }\n if (props) {\n for (const [key, val] of Object.entries(props as Record<string, unknown>)) {\n if (typeof val !== 'object' || val === null) {\n return {\n ok: false,\n code: 'invalid-schema',\n error: `property \"${key}\" must be an object schema`,\n };\n }\n const pv = val as Record<string, unknown>;\n const ALLOWED_TYPES = new Set(['string', 'number', 'boolean', 'array', 'object']);\n if (pv.type !== undefined && !ALLOWED_TYPES.has(pv.type as string)) {\n return {\n ok: false,\n code: 'invalid-schema',\n error: `property \"${key}\" must have type: string|number|boolean|array|object`,\n };\n }\n }\n }\n\n return { ok: true };\n}\n\n/**\n * Create a NAP-CONFIG reference service.\n *\n * Shell-writes, napplet-reads. Handles the full `@napplet/nap/config` wire\n * protocol: `config.get` (correlated snapshot), `config.subscribe` /\n * `config.unsubscribe` (live push stream), `config.registerSchema` (optional\n * schema registration + Core Subset validation), `config.openSettings`\n * (optional UI deep-link, fire-and-forget).\n *\n * Returns a `ConfigService` bundle: `{ handler, publishValues }`.\n * Register `handler` with the runtime; call `publishValues(newValues)` from\n * the shell whenever config state changes.\n *\n * @param options - Host-supplied implementation hooks (options-as-bridge,\n * v1.6 Decision 18). `getValues` is required; all other fields are optional.\n * @returns A ConfigService bundle.\n *\n * @see ConfigServiceOptions for the options shape.\n * @see packages/services/src/theme-service.ts for the sibling pattern.\n * @see SCOPE BOUNDARY comment at the top of this file re: NAP-STORAGE separation.\n *\n * @example\n * ```ts\n * import { createConfigService } from '@kehto/services';\n *\n * const config = createConfigService({\n * getValues: () => ({ theme: 'dark', density: 'compact' }),\n * openSettings: (windowId, section) => openSettingsUI(section),\n * });\n * runtime.registerService('config', config.handler);\n *\n * // Push a live update to all subscribers:\n * config.publishValues({ theme: 'light', density: 'compact' });\n * ```\n */\nexport function createConfigService(options: ConfigServiceOptions): ConfigService {\n /**\n * Per-window subscriber set. Maps windowId → the send callback captured at\n * `config.subscribe` time. `publishValues` fans out to every entry.\n */\n const subscribers = new Map<string, (msg: NappletMessage) => void>();\n\n const descriptor: ServiceDescriptor = {\n name: 'config',\n version: CONFIG_SERVICE_VERSION,\n description: 'NAP-CONFIG reference service — shell-writes, napplet-reads configuration',\n };\n\n const handler: ServiceHandler = {\n descriptor,\n\n handleMessage(\n windowId: string,\n message: NappletMessage,\n send: (msg: NappletMessage) => void,\n ): void {\n switch (message.type) {\n case 'config.get': {\n const m = message as ConfigGetMessage;\n const reply: ConfigValuesMessage = {\n type: 'config.values',\n id: m.id,\n values: options.getValues(),\n };\n send(reply as NappletMessage);\n return;\n }\n\n case 'config.subscribe': {\n // Capture the send callback so publishValues can fan pushes out.\n subscribers.set(windowId, send);\n // Immediate initial snapshot push — no `id` (push form per wire spec).\n const push: ConfigValuesMessage = {\n type: 'config.values',\n values: options.getValues(),\n };\n send(push as NappletMessage);\n options.onSubscribe?.(windowId);\n return;\n }\n\n case 'config.unsubscribe': {\n subscribers.delete(windowId);\n options.onUnsubscribe?.(windowId);\n return;\n }\n\n case 'config.registerSchema': {\n const m = message as ConfigRegisterSchemaMessage;\n // Delegate to host-supplied validator if present; otherwise use\n // the built-in Core Subset hand-coded validator (D12).\n const validation: ConfigSchemaValidation = options.registerSchema\n ? options.registerSchema(windowId, m.schema, m.version)\n : validateCoreSubset(m.schema);\n\n const result: ConfigRegisterSchemaResultMessage = validation.ok\n ? { type: 'config.registerSchema.result', id: m.id, ok: true }\n : {\n type: 'config.registerSchema.result',\n id: m.id,\n ok: false,\n code: validation.code,\n error: validation.error,\n };\n send(result as NappletMessage);\n return;\n }\n\n case 'config.openSettings': {\n const m = message as ConfigOpenSettingsMessage;\n // Silently dropped if openSettings hook not provided (D10).\n options.openSettings?.(windowId, m.section);\n return;\n }\n\n default:\n // Unknown config.* message — silently ignored per NIP-5D.\n return;\n }\n },\n\n onWindowDestroyed(windowId: string): void {\n // A napplet iframe was destroyed — drop any active subscription so we\n // don't retain the stale send callback in the subscribers map.\n subscribers.delete(windowId);\n },\n };\n\n /**\n * Broadcast a new config values snapshot to every subscribed napplet.\n * Each subscriber receives a `config.values` push envelope (no `id` —\n * absence of `id` distinguishes a push from a correlated `config.get`\n * response per the NAP-CONFIG wire spec).\n */\n function publishValues(values: ConfigValues): void {\n const envelope: ConfigValuesMessage = {\n type: 'config.values',\n values,\n };\n for (const send of subscribers.values()) {\n try {\n send(envelope as NappletMessage);\n } catch {\n // Subscriber's send callback threw (e.g., iframe gone without\n // onWindowDestroyed firing yet). Best-effort — drop silently.\n }\n }\n }\n\n return { handler, publishValues };\n}\n","/**\n * resource-service.ts — NAP-RESOURCE reference service (10th NAP domain, v1.7 Phase 40).\n *\n * Shell-side reference implementation for the canonical NAP-RESOURCE wire\n * protocol (`internal-resource.ts` in @kehto/shell/src/types; kehto-internal\n * model per PROJECT.md Decision #31 — diverges from upstream `@napplet/nap/\n * resource` in field names + error vocabulary). Handles the canonical\n * 4-message protocol:\n * Inbound: resource.bytes, resource.cancel\n * Outbound: resource.bytes.result, resource.bytes.error\n *\n * ──────────────────────── SCOPE BOUNDARY (RESOURCE-01) ────────────────────────\n * NAP-RESOURCE is an **authenticated fetch proxy** — read-only, atomic.\n *\n * This service is NOT responsible for:\n * - Streaming / chunked responses (host-app concern)\n * - Response caching / conditional requests (host-app concern)\n * - Upload / POST body construction (NAP-RESOURCE v1.7 is read-only)\n * - Redirect limits, MIME sniffing, SVG rasterization (host-fetch concern)\n * - Private-IP blocking, SSRF mitigation (host-provided-fetch responsibility)\n *\n * These belong to the host-app's `fetch` implementation per D7 and\n * SHELL-RESOURCE-POLICY.md (Phase 40 Plan 40-03). Kehto ships a reference\n * service; production hardening is the host app's concern.\n * ──────────────────────────────────────────────────────────────────────────────\n *\n * Host integration: provide `fetch`, `isOriginGranted`, `getConnectGrants`,\n * and `resolveIdentity`. ALL FOUR are required from day one (H-03 prevention).\n *\n * @example\n * ```ts\n * import { createResourceService } from '@kehto/services';\n *\n * const resourceSvc = createResourceService({\n * fetch: (url, init) => globalThis.fetch(url, init),\n * isOriginGranted: (origin, grants) => grants.includes(origin),\n * getConnectGrants: (dTag, hash) => myOriginGrantStore.getOrigins(dTag, hash),\n * resolveIdentity: (windowId) => sessionRegistry.getEntryByWindowId(windowId) ?? null,\n * });\n * runtime.registerService('resource', resourceSvc);\n * ```\n */\n\nimport type { NappletMessage } from '@napplet/core';\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\n\n/** Resource service version — follows semver. */\nconst RESOURCE_SERVICE_VERSION = '1.0.0';\n\n/**\n * Options for `createResourceService` (options-as-bridge per v1.6 Decision 18).\n *\n * ALL FOUR fields are required. The factory throws at construction if any is\n * missing — H-03 prevention: the grants source (`getConnectGrants`) MUST be\n * wired from day one so there is no window where resource requests bypass the\n * grant check.\n *\n * @see PITFALLS.md:228 (H-03) — grants-source coupling must be present at construction\n */\nexport interface ResourceServiceOptions {\n /**\n * Host-supplied fetch implementation. Receives the URL, a partial init\n * (method, headers, signal), and must return a `Response`-compatible promise.\n *\n * The host's `fetch` is the ONLY place to implement redirect limits, MIME\n * sniffing, SVG rasterization, private-IP / SSRF blocking, etc.\n * This service does NOT filter: it proxies transparently.\n *\n * @param url - The URL from the resource.bytes request\n * @param init - Method, headers (from napplet), and an AbortSignal\n */\n fetch(\n url: string,\n init: { method?: string; headers?: Record<string, string>; signal: AbortSignal }\n ): Promise<Response>;\n\n /**\n * Returns true if `origin` is present in `grants` (the list returned by\n * `getConnectGrants` for the napplet's dTag + aggregateHash).\n *\n * The reference implementation is simply `grants.includes(origin)`. Host apps\n * may provide normalized-origin comparison if needed.\n *\n * @param origin - Parsed origin of the requested URL (scheme + host + port)\n * @param grants - Readonly list from getConnectGrants for this napplet identity\n */\n isOriginGranted(origin: string, grants: readonly string[]): boolean;\n\n /**\n * Returns the list of allowed fetch origins for the given napplet identity.\n * Called on every `resource.bytes` request — must be synchronous and fast.\n *\n * Host-supplied grant source (e.g. a static per-dTag allowlist map, or any\n * other host-controlled policy). Returns an empty array to deny all origins.\n *\n * H-03 prevention: REQUIRED from day one — factory throws on construction\n * if omitted.\n *\n * @param dTag - The napplet's d-tag (from session registry)\n * @param aggregateHash - The napplet's aggregate hash (from session registry)\n */\n getConnectGrants(dTag: string, aggregateHash: string): readonly string[];\n\n /**\n * Resolve a windowId to the napplet's identity (dTag + aggregateHash).\n * Returns null if the window is not in the session registry.\n *\n * Typically wraps `sessionRegistry.getEntryByWindowId(windowId)`.\n *\n * @param windowId - The iframe window identifier\n */\n resolveIdentity(windowId: string): { dTag: string; aggregateHash: string } | null;\n}\n\n/**\n * Type alias for the ServiceHandler returned by `createResourceService`.\n * Exported for host apps that need to type-annotate the handler reference.\n */\nexport type ResourceService = ServiceHandler;\n\n/**\n * Convert an ArrayBuffer to base64 string, safe for both browser and Node.\n * Chunked in 0x8000-byte slices to avoid `String.fromCharCode(...largeArray)`\n * stack overflow on large responses.\n */\nfunction arrayBufferToBase64(buf: ArrayBuffer): string {\n const bytes = new Uint8Array(buf);\n const CHUNK = 0x8000; // 32 KB\n let binary = '';\n for (let i = 0; i < bytes.length; i += CHUNK) {\n binary += String.fromCharCode(...bytes.subarray(i, i + CHUNK));\n }\n return btoa(binary);\n}\n\ninterface ResourceRequestState {\n inFlight: Map<string, { controller: AbortController; windowId: string }>;\n perWindow: Map<string, Set<string>>;\n}\n\nfunction assertResourceOptions(options: ResourceServiceOptions): void {\n if (\n typeof options?.fetch !== 'function' ||\n typeof options?.isOriginGranted !== 'function' ||\n typeof options?.getConnectGrants !== 'function' ||\n typeof options?.resolveIdentity !== 'function'\n ) {\n throw new Error(\n '[RESOURCE-01 / H-03] createResourceService requires {fetch, isOriginGranted, getConnectGrants, resolveIdentity} ' +\n '— all four options are required from day one. ' +\n 'The grants source (getConnectGrants) MUST be wired at construction time to prevent unguarded fetch proxying.',\n );\n }\n}\n\nfunction trackRequest(\n state: ResourceRequestState,\n requestId: string,\n windowId: string,\n controller: AbortController,\n): void {\n state.inFlight.set(requestId, { controller, windowId });\n if (!state.perWindow.has(windowId)) {\n state.perWindow.set(windowId, new Set());\n }\n state.perWindow.get(windowId)!.add(requestId);\n}\n\nfunction untrackRequest(state: ResourceRequestState, requestId: string): void {\n const entry = state.inFlight.get(requestId);\n if (entry) {\n state.inFlight.delete(requestId);\n state.perWindow.get(entry.windowId)?.delete(requestId);\n }\n}\n\nfunction sendResourceError(\n send: (m: NappletMessage) => void,\n requestId: string,\n code: 'denied' | 'invalid-url' | 'canceled' | 'network-error',\n message: string,\n): void {\n send({\n type: 'resource.bytes.error',\n requestId,\n code,\n message,\n } as NappletMessage);\n}\n\nfunction parseResourceUrl(send: (m: NappletMessage) => void, requestId: string, url: string): URL | null {\n try {\n return new URL(url);\n } catch {\n sendResourceError(send, requestId, 'invalid-url', `invalid URL: ${url}`);\n return null;\n }\n}\n\nfunction collectResponseHeaders(response: Response): Record<string, string> {\n const headers: Record<string, string> = {};\n response.headers.forEach((value, key) => {\n headers[key] = value;\n });\n return headers;\n}\n\nasync function handleBytes(\n options: ResourceServiceOptions,\n state: ResourceRequestState,\n windowId: string,\n msg: { requestId: string; url: string; init?: { method?: string; headers?: Readonly<Record<string, string>> } },\n send: (m: NappletMessage) => void,\n): Promise<void> {\n const { requestId, url, init } = msg;\n const identity = options.resolveIdentity(windowId);\n if (!identity) {\n sendResourceError(send, requestId, 'denied', 'napplet identity not resolvable');\n return;\n }\n\n const parsedUrl = parseResourceUrl(send, requestId, url);\n if (!parsedUrl) return;\n\n const origin = parsedUrl.origin;\n const grants = options.getConnectGrants(identity.dTag, identity.aggregateHash);\n if (!options.isOriginGranted(origin, grants)) {\n sendResourceError(send, requestId, 'denied', `origin ${origin} not granted`);\n return;\n }\n\n const controller = new AbortController();\n trackRequest(state, requestId, windowId, controller);\n\n try {\n const response = await options.fetch(url, {\n method: init?.method,\n headers: init?.headers ? { ...init.headers } : undefined,\n signal: controller.signal,\n });\n const buffer = await response.arrayBuffer();\n send({\n type: 'resource.bytes.result',\n requestId,\n status: response.status,\n headers: collectResponseHeaders(response),\n bodyBase64: arrayBufferToBase64(buffer),\n } as NappletMessage);\n } catch (err: unknown) {\n const isAbort =\n controller.signal.aborted ||\n (err instanceof Error && (err.name === 'AbortError' || err.name === 'DOMException'));\n sendResourceError(\n send,\n requestId,\n isAbort ? 'canceled' : 'network-error',\n err instanceof Error ? err.message : String(err),\n );\n } finally {\n untrackRequest(state, requestId);\n }\n}\n\nfunction handleCancel(state: ResourceRequestState, requestId: string): void {\n const entry = state.inFlight.get(requestId);\n if (entry) {\n entry.controller.abort();\n }\n}\n\nfunction destroyWindowRequests(state: ResourceRequestState, windowId: string): void {\n const requestIds = state.perWindow.get(windowId);\n if (!requestIds) return;\n for (const requestId of requestIds) {\n const entry = state.inFlight.get(requestId);\n if (entry) {\n entry.controller.abort();\n state.inFlight.delete(requestId);\n }\n }\n state.perWindow.delete(windowId);\n}\n\n/**\n * Create a NAP-RESOURCE reference service.\n *\n * Implements canonical 4-message protocol: `resource.bytes` (napplet → shell\n * fetch request), `resource.cancel` (napplet → shell in-flight cancellation),\n * `resource.bytes.result` (shell → napplet success), `resource.bytes.error`\n * (shell → napplet failure/denial/cancel).\n *\n * On-construction guard (H-03 prevention): all four options are validated at\n * factory call time. If any is missing, the factory throws immediately with a\n * message containing `[RESOURCE-01 / H-03]` so misconfigured shell apps fail\n * loudly at startup rather than silently at first dispatch.\n *\n * Returns a `ServiceHandler` (no `publishValues`-style surface — resource has\n * no shell-initiated push beyond the response/error path).\n *\n * @param options - REQUIRED: fetch, isOriginGranted, getConnectGrants, resolveIdentity\n * @returns ServiceHandler to register via `runtime.registerService('resource', handler)`\n *\n * @example\n * ```ts\n * import { createResourceService } from '@kehto/services';\n *\n * const svc = createResourceService({\n * fetch: (url, init) => globalThis.fetch(url, init),\n * isOriginGranted: (origin, grants) => grants.includes(origin),\n * getConnectGrants: (dTag, hash) => myOriginGrantStore.getOrigins(dTag, hash),\n * resolveIdentity: (windowId) => sessionRegistry.getEntryByWindowId(windowId) ?? null,\n * });\n * runtime.registerService('resource', svc);\n * ```\n */\nexport function createResourceService(options: ResourceServiceOptions): ResourceService {\n assertResourceOptions(options);\n const state: ResourceRequestState = {\n inFlight: new Map<string, { controller: AbortController; windowId: string }>(),\n perWindow: new Map<string, Set<string>>(),\n };\n\n const descriptor: ServiceDescriptor = {\n name: 'resource',\n version: RESOURCE_SERVICE_VERSION,\n description:\n 'NAP-RESOURCE reference service — shell-proxied authenticated fetch (RESOURCE-01..06)',\n };\n\n const handler: ServiceHandler = {\n descriptor,\n\n handleMessage(\n windowId: string,\n message: NappletMessage,\n send: (msg: NappletMessage) => void,\n ): void {\n switch (message.type) {\n case 'resource.bytes': {\n const m = message as NappletMessage & {\n requestId: string;\n url: string;\n init?: { method?: string; headers?: Readonly<Record<string, string>> };\n };\n handleBytes(options, state, windowId, m, send).catch(() => { /* errors surface via send() */ });\n return;\n }\n\n case 'resource.cancel': {\n const m = message as NappletMessage & { requestId: string };\n handleCancel(state, m.requestId);\n return;\n }\n\n default:\n // Unknown resource.* message — silently ignored per NIP-5D.\n return;\n }\n },\n\n onWindowDestroyed(windowId: string): void {\n destroyWindowRequests(state, windowId);\n },\n };\n\n return handler;\n}\n","/**\n * outbox-service.ts — NAP-OUTBOX (outbox-aware relay routing) reference service.\n *\n * Shell-side handler for the NAP-OUTBOX wire protocol. It is a pure envelope\n * router: it validates `outbox.*` envelopes, delegates the actual relay\n * discovery / routing / dedup / publish-fanout work to an injected\n * {@link OutboxRouter}, and posts the correlated result / lifecycle messages\n * (echoing the request `id` or `subId`) back to the napplet.\n *\n * The router is injected (options-as-bridge) so this service has no Nostr\n * dependency and is fully unit-testable. A concrete relay-pool-backed router\n * ships alongside as {@link createRelayPoolOutboxRouter}.\n *\n * ──────────────────────────── Responsibilities ────────────────────────────\n * Inbound: outbox.query, outbox.subscribe, outbox.close, outbox.publish,\n * outbox.resolveRelays\n * Outbound: outbox.query.result, outbox.event, outbox.eose, outbox.closed,\n * outbox.publish.result, outbox.resolveRelays.result\n *\n * The shell owns relay discovery, routing, fallback, deduplication, signature\n * validation, signing, and publish fanout policy — all of which live behind\n * the {@link OutboxRouter}. This service only marshals the wire protocol.\n *\n * @example\n * ```ts\n * import { createOutboxService, createRelayPoolOutboxRouter } from '@kehto/services';\n *\n * const router = createRelayPoolOutboxRouter({ relayPool, loadRelayLists, fallbackRelays });\n * runtime.registerService('outbox', createOutboxService({ router }));\n * ```\n *\n * @packageDocumentation\n */\n\nimport type { EventTemplate, NappletMessage, NostrEvent, NostrFilter } from '@napplet/core';\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\n\n/** Outbox service version — follows semver. */\nconst OUTBOX_SERVICE_VERSION = '1.0.0';\n\n/**\n * Relay-selection strategy:\n * - `outbox` — query/publish via author write relays (the outbox model)\n * - `inbox` — query/publish via recipient read relays (the inbox model)\n * - `auto` — let the shell choose per its policy and relay intelligence\n */\nexport type OutboxStrategy = 'outbox' | 'inbox' | 'auto';\n\n/** Options for a one-shot outbox query. */\nexport interface OutboxQueryOptions {\n /** Explicit author hints (augment/override authors derived from filters). */\n authors?: string[];\n /** Relay hints; treated as a hint subject to shell validation, not a bypass. */\n relays?: string[];\n /** Relay-selection strategy. */\n strategy?: OutboxStrategy;\n /** Maximum events to collect. */\n limit?: number;\n /** Wall-clock budget for the query, in milliseconds. */\n timeoutMs?: number;\n}\n\n/** Options for a live outbox subscription. */\nexport interface OutboxSubscribeOptions extends OutboxQueryOptions {\n /** Keep the subscription open for real-time events after EOSE. */\n live?: boolean;\n}\n\n/** Options for an outbox publish. */\nexport interface OutboxPublishOptions {\n /** Relay hints; treated as a hint subject to shell validation. */\n relays?: string[];\n /** Recipient authors whose inbox relays should be included for directed events. */\n targetAuthors?: string[];\n /** Relay-selection strategy. */\n strategy?: OutboxStrategy;\n}\n\n/** A read/write target for relay-plan resolution. */\nexport interface OutboxTarget {\n /** Authors to resolve relays for. */\n authors?: string[];\n /** Single pubkey to resolve relays for. */\n pubkey?: string;\n /** Whether the plan is for reading (their write relays) or writing (their read relays). */\n direction?: 'read' | 'write';\n /** Relay-selection strategy. */\n strategy?: OutboxStrategy;\n}\n\n/** The relay plan the shell would use for a target. */\nexport interface OutboxRelayPlan {\n /** Resolved relay URLs. */\n relays: string[];\n /** Where the plan came from. */\n source: 'nip65' | 'cache' | 'policy' | 'fallback';\n /** Authors for which no relay list could be resolved. */\n missingAuthors?: string[];\n}\n\n/** Outcome of an outbox query, as returned by the {@link OutboxRouter}. */\nexport interface OutboxResult {\n /** Deduplicated, signature-validated events. */\n events: NostrEvent[];\n /** Map of event id -> relay URLs where the shell observed the event. */\n relays: Record<string, string[]>;\n /** True when some relay lists or connections failed and results are partial. */\n incomplete?: boolean;\n /** Error reason when the query could not complete. */\n error?: string;\n}\n\n/** Outcome of an outbox publish, as returned by the {@link OutboxRouter}. */\nexport interface OutboxPublishResult {\n /** Whether the publish succeeded on at least the required relays. */\n ok: boolean;\n /** The signed event returned by the shell. */\n event?: NostrEvent;\n /** The published event id. */\n eventId?: string;\n /** Map of relay URL -> per-relay publish success. */\n relays?: Record<string, boolean>;\n /** Error reason when the publish failed. */\n error?: string;\n}\n\n/** Sink an {@link OutboxRouter} streams subscription lifecycle through. */\nexport interface OutboxSubscriptionSink {\n /** Deliver a matching event; `relay` is the relay it was observed on, when known. */\n event(event: NostrEvent, relay?: string): void;\n /** Signal end-of-stored-events. */\n eose(): void;\n /** Signal that the subscription was closed upstream; `reason` is optional. */\n closed(reason?: string): void;\n}\n\n/** Handle to a router-owned subscription. */\nexport interface OutboxRouterSubscription {\n /** Stop the subscription and release its relay connections. */\n close(): void;\n}\n\n/**\n * Abstract outbox router. Implementors own relay discovery (NIP-65 / NIP-66),\n * routing, fallback, deduplication, signature validation, signing, and publish\n * fanout. The service translates wire envelopes into these calls and back.\n */\nexport interface OutboxRouter {\n /** Resolve relays, query them, dedup by id, validate signatures, collect events. */\n query(filters: NostrFilter[], options?: OutboxQueryOptions): Promise<OutboxResult>;\n /** Open a live outbox-aware subscription, streaming through `sink`. */\n subscribe(\n filters: NostrFilter[],\n options: OutboxSubscribeOptions | undefined,\n sink: OutboxSubscriptionSink,\n ): OutboxRouterSubscription;\n /** Sign `template` and fan it out to the relevant write/inbox relays. */\n publish(template: EventTemplate, options?: OutboxPublishOptions): Promise<OutboxPublishResult>;\n /** Return the relay plan the shell would use for a read/write target. */\n resolveRelays(target: OutboxTarget): Promise<OutboxRelayPlan>;\n}\n\n/** Options for {@link createOutboxService}. */\nexport interface OutboxServiceOptions {\n /** The outbox router the shell uses to reach relays. Required. */\n router: OutboxRouter;\n}\n\ntype Send = (msg: NappletMessage) => void;\n\nconst OUTBOX_DESCRIPTOR: ServiceDescriptor = {\n name: 'outbox',\n version: OUTBOX_SERVICE_VERSION,\n description: 'NAP-OUTBOX outbox-aware relay routing — query/subscribe/publish/resolveRelays',\n};\n\n/**\n * Normalize a wire `filters` field (a single NIP-01 filter or an array) into a\n * filter array. Returns `null` when the input is missing or has no usable\n * filter objects.\n */\nfunction normalizeFilters(raw: unknown): NostrFilter[] | null {\n if (Array.isArray(raw)) {\n const filters = raw.filter((f): f is NostrFilter => typeof f === 'object' && f !== null);\n return filters.length > 0 ? filters : null;\n }\n if (typeof raw === 'object' && raw !== null) return [raw as NostrFilter];\n return null;\n}\n\n/**\n * Create the NAP-OUTBOX service handler.\n *\n * @param options - Must provide an {@link OutboxRouter}.\n * @returns A `ServiceHandler` ready for `runtime.registerService('outbox', handler)`.\n * @throws If `options.router` is missing.\n */\nexport function createOutboxService(options: OutboxServiceOptions): ServiceHandler {\n if (!options || typeof options.router !== 'object' || options.router === null) {\n throw new Error('createOutboxService: options.router is required');\n }\n const { router } = options;\n\n // Active subscriptions keyed by `windowId:subId` for lifecycle management.\n const subscriptions = new Map<string, OutboxRouterSubscription>();\n\n function handleQuery(msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { id?: string; filters?: unknown; options?: OutboxQueryOptions };\n const id = m.id ?? '';\n const filters = normalizeFilters(m.filters);\n if (!filters) {\n send({ type: 'outbox.query.result', id, events: [], relays: {}, error: 'invalid filter' } as NappletMessage);\n return;\n }\n void router\n .query(filters, m.options)\n .then((result) =>\n send({\n type: 'outbox.query.result',\n id,\n events: result.events,\n relays: result.relays,\n ...(result.incomplete === undefined ? {} : { incomplete: result.incomplete }),\n ...(result.error === undefined ? {} : { error: result.error }),\n } as NappletMessage),\n )\n .catch((err) =>\n send({ type: 'outbox.query.result', id, events: [], relays: {}, error: toErrorMessage(err) } as NappletMessage),\n );\n }\n\n function handleSubscribe(windowId: string, msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { subId?: string; filters?: unknown; options?: OutboxSubscribeOptions };\n const subId = m.subId;\n if (typeof subId !== 'string' || subId.length === 0) return;\n const subKey = `${windowId}:${subId}`;\n\n // Replace any existing subscription for this key.\n subscriptions.get(subKey)?.close();\n subscriptions.delete(subKey);\n\n const filters = normalizeFilters(m.filters);\n if (!filters) {\n send({ type: 'outbox.closed', subId, reason: 'invalid filter' } as NappletMessage);\n return;\n }\n\n const sink: OutboxSubscriptionSink = {\n event: (event, relay) =>\n send({ type: 'outbox.event', subId, event, ...(relay === undefined ? {} : { relay }) } as NappletMessage),\n eose: () => send({ type: 'outbox.eose', subId } as NappletMessage),\n closed: (reason) => {\n subscriptions.delete(subKey);\n send({ type: 'outbox.closed', subId, ...(reason === undefined ? {} : { reason }) } as NappletMessage);\n },\n };\n\n subscriptions.set(subKey, router.subscribe(filters, m.options, sink));\n }\n\n function handleClose(windowId: string, msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { subId?: string };\n const subId = m.subId;\n if (typeof subId !== 'string') return;\n const subKey = `${windowId}:${subId}`;\n subscriptions.get(subKey)?.close();\n subscriptions.delete(subKey);\n send({ type: 'outbox.closed', subId } as NappletMessage);\n }\n\n function handlePublish(msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { id?: string; event?: EventTemplate; options?: OutboxPublishOptions };\n const id = m.id ?? '';\n if (!m.event || typeof m.event !== 'object') {\n send({ type: 'outbox.publish.result', id, ok: false, error: 'invalid filter' } as NappletMessage);\n return;\n }\n void router\n .publish(m.event, m.options)\n .then((result) =>\n send({\n type: 'outbox.publish.result',\n id,\n ok: result.ok,\n ...(result.event === undefined ? {} : { event: result.event }),\n ...(result.eventId === undefined ? {} : { eventId: result.eventId }),\n ...(result.relays === undefined ? {} : { relays: result.relays }),\n ...(result.error === undefined ? {} : { error: result.error }),\n } as NappletMessage),\n )\n .catch((err) =>\n send({ type: 'outbox.publish.result', id, ok: false, error: toErrorMessage(err) } as NappletMessage),\n );\n }\n\n function handleResolveRelays(msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { id?: string; target?: OutboxTarget };\n const id = m.id ?? '';\n if (!m.target || typeof m.target !== 'object') {\n send({ type: 'outbox.resolveRelays.result', id, error: 'invalid filter' } as NappletMessage);\n return;\n }\n void router\n .resolveRelays(m.target)\n .then((plan) => send({ type: 'outbox.resolveRelays.result', id, plan } as NappletMessage))\n .catch((err) =>\n send({ type: 'outbox.resolveRelays.result', id, error: toErrorMessage(err) } as NappletMessage),\n );\n }\n\n return {\n descriptor: OUTBOX_DESCRIPTOR,\n handleMessage(windowId: string, message: NappletMessage, send: Send): void {\n switch (message.type) {\n case 'outbox.query':\n handleQuery(message, send);\n return;\n case 'outbox.subscribe':\n handleSubscribe(windowId, message, send);\n return;\n case 'outbox.close':\n handleClose(windowId, message, send);\n return;\n case 'outbox.publish':\n handlePublish(message, send);\n return;\n case 'outbox.resolveRelays':\n handleResolveRelays(message, send);\n return;\n default:\n // Unknown outbox.* action — silently ignored (forward-compatible).\n return;\n }\n },\n onWindowDestroyed(windowId: string): void {\n const prefix = `${windowId}:`;\n for (const [key, sub] of subscriptions) {\n if (key.startsWith(prefix)) {\n sub.close();\n subscriptions.delete(key);\n }\n }\n },\n };\n}\n\nfunction toErrorMessage(err: unknown): string {\n if (err instanceof Error) return err.message;\n if (typeof err === 'string') return err;\n return 'outbox request failed';\n}\n","/**\n * relay-pool-outbox-router.ts — concrete {@link OutboxRouter} backed by a relay pool.\n *\n * Implements the outbox-model routing that NAP-OUTBOX centralizes so napplets\n * don't each reinvent it: derive authors, resolve their NIP-65 relays, fan a\n * per-relay subscription out across the plan, deduplicate by event id (while\n * recording every relay an event was observed on), validate signatures, and —\n * for publish — sign the template and fan it out to the relevant write/inbox\n * relays.\n *\n * NIP-65 relay-list *fetching* is the host's concern (it may come from a\n * kind-10002 cache, a NIP-66 indexer via `@kehto/nip/66`, or a live query), so\n * it is injected via {@link RelayPoolOutboxRouterOptions.loadRelayLists}. The\n * relay pool, signer, and signature verifier are injected too — keeping this\n * router browser-agnostic and unit-testable with mocks.\n *\n * Relay-selection model (per the outbox model):\n * - reading an author's events → their **write** relays (where they publish)\n * - writing to reach an author → their **read** relays (their inbox)\n *\n * `strategy` overrides the direction default: `outbox` forces write relays,\n * `inbox` forces read relays, `auto` (default) follows the read/write direction.\n *\n * @example\n * ```ts\n * import { createOutboxService, createRelayPoolOutboxRouter } from '@kehto/services';\n *\n * const router = createRelayPoolOutboxRouter({\n * relayPool: myOutboxPool,\n * loadRelayLists: (pubkeys) => relayListCache.getMany(pubkeys),\n * fallbackRelays: ['wss://relay.damus.io', 'wss://nos.lol'],\n * signEvent: (tmpl) => signer.signEvent(tmpl),\n * verifyEvent: (ev) => verifyEvent(ev),\n * });\n * runtime.registerService('outbox', createOutboxService({ router }));\n * ```\n *\n * @packageDocumentation\n */\n\nimport type { EventTemplate, NostrEvent, NostrFilter } from '@napplet/core';\nimport type {\n OutboxRouter,\n OutboxResult,\n OutboxPublishResult,\n OutboxRelayPlan,\n OutboxQueryOptions,\n OutboxSubscribeOptions,\n OutboxPublishOptions,\n OutboxStrategy,\n OutboxTarget,\n OutboxSubscriptionSink,\n OutboxRouterSubscription,\n} from './outbox-service.js';\n\n// Timer globals available in all JS runtimes.\ndeclare function setTimeout(callback: () => void, ms: number): unknown;\ndeclare function clearTimeout(id: unknown): void;\n\n/** Default per-query wall-clock budget when `options.timeoutMs` is unset. */\nconst DEFAULT_QUERY_TIMEOUT_MS = 4000;\n\n/** A NIP-65 relay list for a single pubkey. */\nexport interface RelayListEntry {\n /** Relays the author reads from (their inbox). */\n read: string[];\n /** Relays the author writes to (where their events land). */\n write: string[];\n}\n\n/**\n * Relay pool contract the router drives. Implementors adapt their pool library\n * (nostr-tools SimplePool, applesauce-relay, etc.). Unlike the lower-level\n * relay NAP pool, both methods take an explicit relay-URL set so the router\n * controls outbox routing and can attribute events to the relay they arrived on.\n */\nexport interface OutboxRelayPool {\n /**\n * Subscribe to `filters` on exactly `relayUrls`. The callback receives each\n * matching event or the literal `'EOSE'` once stored events are exhausted.\n * Returns a handle to cancel the subscription.\n */\n subscribe(\n filters: NostrFilter[],\n relayUrls: string[],\n callback: (item: NostrEvent | 'EOSE') => void,\n ): { unsubscribe(): void };\n /**\n * Publish `event` to `relayUrls`. May return a per-relay success map; a\n * `void`/missing return is treated as optimistic success on every target.\n */\n publish(\n event: NostrEvent,\n relayUrls: string[],\n ): Promise<Record<string, boolean>> | Record<string, boolean> | void;\n /** Whether the relay pool is connected and able to handle requests. */\n isAvailable(): boolean;\n}\n\n/** Options for {@link createRelayPoolOutboxRouter}. */\nexport interface RelayPoolOutboxRouterOptions {\n /** Relay pool the router subscribes/publishes through. Required. */\n relayPool: OutboxRelayPool;\n /**\n * Resolve NIP-65 relay lists for a set of pubkeys. Pubkeys with no known\n * list are simply omitted from the returned map (they become `missingAuthors`).\n */\n loadRelayLists(pubkeys: string[]): Promise<Map<string, RelayListEntry>> | Map<string, RelayListEntry>;\n /** Relays to fall back to when NIP-65 data is absent, stale, or empty. Required. */\n fallbackRelays: string[];\n /**\n * Sign a template before publish (shell-mediated; napplets never sign). When\n * omitted, `publish` resolves with `{ ok: false, error: 'publish denied' }`.\n */\n signEvent?(template: EventTemplate): Promise<NostrEvent>;\n /**\n * Validate an event signature before delivering it to a napplet. May be sync\n * or async. Defaults to accepting every event (host pools often pre-verify).\n */\n verifyEvent?(event: NostrEvent): Promise<boolean> | boolean;\n /**\n * Gate relay URLs (e.g. block private-network hosts). Defaults to allowing\n * only `ws://` / `wss://` URLs — `options.relays` hints pass through this too.\n */\n isRelayAllowed?(url: string): boolean;\n /** Default query timeout when `options.timeoutMs` is unset. Default 4000ms. */\n defaultTimeoutMs?: number;\n}\n\n/** Resolved router dependencies threaded into the module-level helpers. */\ninterface RouterCtx {\n relayPool: OutboxRelayPool;\n loadRelayLists: RelayPoolOutboxRouterOptions['loadRelayLists'];\n fallbackRelays: string[];\n signEvent?: RelayPoolOutboxRouterOptions['signEvent'];\n isRelayAllowed: (url: string) => boolean;\n defaultTimeoutMs: number;\n verify(event: NostrEvent): Promise<boolean>;\n}\n\n/** Default relay gate: only ws(s):// URLs are permitted. */\nfunction defaultRelayAllowed(url: string): boolean {\n return typeof url === 'string' && (url.startsWith('wss://') || url.startsWith('ws://'));\n}\n\n/** Collect a deduplicated author set from filters + explicit option hints. */\nfunction deriveAuthors(filters: NostrFilter[], optionAuthors?: string[]): string[] {\n const authors = new Set<string>();\n for (const filter of filters) {\n for (const author of filter.authors ?? []) authors.add(author);\n }\n for (const author of optionAuthors ?? []) authors.add(author);\n return [...authors];\n}\n\n/**\n * Whether a resolved plan should use authors' write relays (true) or read\n * relays (false), given the read/write direction and an explicit strategy.\n */\nfunction wantsWriteRelays(direction: 'read' | 'write', strategy: OutboxStrategy): boolean {\n if (strategy === 'outbox') return true;\n if (strategy === 'inbox') return false;\n return direction === 'read'; // auto: reading → author write relays\n}\n\n/** Apply the relay gate to a candidate set, deduplicating. */\nfunction allowed(ctx: RouterCtx, urls: Iterable<string>): string[] {\n const out = new Set<string>();\n for (const url of urls) {\n if (ctx.isRelayAllowed(url)) out.add(url);\n }\n return [...out];\n}\n\n/**\n * Resolve the relay plan for a set of pubkeys. Returns the allowed relay set,\n * its provenance, and any pubkeys whose relay list was unavailable.\n */\nasync function resolvePlan(\n ctx: RouterCtx,\n pubkeys: string[],\n direction: 'read' | 'write',\n strategy: OutboxStrategy,\n relayHints?: string[],\n): Promise<OutboxRelayPlan> {\n const useWrite = wantsWriteRelays(direction, strategy);\n const collected = new Set<string>();\n const missingAuthors: string[] = [];\n let sawNip65 = false;\n\n if (pubkeys.length > 0) {\n const lists = await ctx.loadRelayLists(pubkeys);\n for (const pubkey of pubkeys) {\n const entry = lists.get(pubkey);\n const relays = entry ? (useWrite ? entry.write : entry.read) : undefined;\n if (relays && relays.length > 0) {\n sawNip65 = true;\n for (const url of relays) collected.add(url);\n } else {\n missingAuthors.push(pubkey);\n }\n }\n }\n\n // Relay hints from the napplet augment the plan, subject to the relay gate.\n for (const url of relayHints ?? []) collected.add(url);\n\n let relays = allowed(ctx, collected);\n let source: OutboxRelayPlan['source'];\n if (relays.length === 0) {\n relays = allowed(ctx, ctx.fallbackRelays);\n source = 'fallback';\n } else {\n // nip65 if any author list contributed; otherwise only hints did (policy).\n source = sawNip65 ? 'nip65' : 'policy';\n }\n\n const plan: OutboxRelayPlan = { relays, source };\n if (missingAuthors.length > 0) plan.missingAuthors = missingAuthors;\n return plan;\n}\n\n/** Mutable accumulator for a one-shot fan-out collection. */\ninterface Collector {\n seen: Map<string, NostrEvent>;\n relayMap: Map<string, Set<string>>;\n verifications: Promise<void>[];\n}\n\n/** Record that `id` was observed on `relayUrl`. */\nfunction recordRelay(collector: Collector, id: string, relayUrl: string): void {\n let set = collector.relayMap.get(id);\n if (!set) { set = new Set<string>(); collector.relayMap.set(id, set); }\n set.add(relayUrl);\n}\n\n/** Verify a freshly-seen event and admit it (or drop its sightings) once settled. */\nfunction admitEvent(ctx: RouterCtx, collector: Collector, event: NostrEvent): void {\n if (collector.seen.has(event.id)) return;\n collector.verifications.push(\n ctx.verify(event).then((ok) => {\n if (ok && !collector.seen.has(event.id)) collector.seen.set(event.id, event);\n else if (!ok) collector.relayMap.delete(event.id);\n }),\n );\n}\n\n/** Build the final query outcome from a settled collector. */\nfunction buildCollectResult(\n collector: Collector,\n timedOut: boolean,\n): { events: NostrEvent[]; relayMap: Record<string, string[]>; incomplete: boolean } {\n const events = [...collector.seen.values()];\n const relayObj: Record<string, string[]> = {};\n for (const event of events) relayObj[event.id] = [...(collector.relayMap.get(event.id) ?? [])];\n return { events, relayMap: relayObj, incomplete: timedOut };\n}\n\n/**\n * Fan a one-shot query out across `relayUrls` (one subscription per relay so\n * events can be attributed to their source relay), dedup by id, validate\n * signatures, and finalize on all-EOSE or timeout.\n */\nfunction collectFromRelays(\n ctx: RouterCtx,\n filters: NostrFilter[],\n relayUrls: string[],\n timeoutMs: number,\n): Promise<{ events: NostrEvent[]; relayMap: Record<string, string[]>; incomplete: boolean }> {\n return new Promise((resolve) => {\n const collector: Collector = { seen: new Map(), relayMap: new Map(), verifications: [] };\n const handles: { unsubscribe(): void }[] = [];\n let eoseCount = 0;\n let finished = false;\n let timedOut = false;\n\n function finalize(): void {\n if (finished) return;\n finished = true;\n clearTimeout(timer);\n for (const handle of handles) {\n try { handle.unsubscribe(); } catch { /* best-effort */ }\n }\n void Promise.all(collector.verifications).then(() => resolve(buildCollectResult(collector, timedOut)));\n }\n\n const timer = setTimeout(() => { timedOut = true; finalize(); }, timeoutMs);\n\n for (const relayUrl of relayUrls) {\n handles.push(relayPoolSubscribe(ctx, filters, relayUrl, (item) => {\n if (finished) return;\n if (item === 'EOSE') {\n eoseCount += 1;\n if (eoseCount >= relayUrls.length) finalize();\n return;\n }\n recordRelay(collector, item.id, relayUrl);\n admitEvent(ctx, collector, item);\n }));\n }\n\n if (relayUrls.length === 0) finalize();\n });\n}\n\n/** Thin wrapper so callers read as a single-relay subscribe. */\nfunction relayPoolSubscribe(\n ctx: RouterCtx,\n filters: NostrFilter[],\n relayUrl: string,\n cb: (item: NostrEvent | 'EOSE') => void,\n): { unsubscribe(): void } {\n return ctx.relayPool.subscribe(filters, [relayUrl], cb);\n}\n\nasync function queryImpl(ctx: RouterCtx, filters: NostrFilter[], options?: OutboxQueryOptions): Promise<OutboxResult> {\n if (!ctx.relayPool.isAvailable()) {\n return { events: [], relays: {}, incomplete: true, error: 'relay list unavailable' };\n }\n const strategy = options?.strategy ?? 'auto';\n const authors = deriveAuthors(filters, options?.authors);\n const plan = await resolvePlan(ctx, authors, 'read', strategy, options?.relays);\n if (plan.relays.length === 0) {\n return { events: [], relays: {}, incomplete: true, error: 'relay list unavailable' };\n }\n\n const timeoutMs = options?.timeoutMs ?? ctx.defaultTimeoutMs;\n const collected = await collectFromRelays(ctx, filters, plan.relays, timeoutMs);\n\n const incomplete = collected.incomplete || (plan.missingAuthors?.length ?? 0) > 0;\n let events = collected.events;\n if (options?.limit !== undefined && events.length > options.limit) {\n events = [...events].sort((a, b) => b.created_at - a.created_at).slice(0, options.limit);\n }\n const result: OutboxResult = { events, relays: collected.relayMap };\n if (incomplete) result.incomplete = true;\n return result;\n}\n\n/** Tracks a live/one-shot subscription across its per-relay fan-out. */\ninterface LiveSub {\n handles: { unsubscribe(): void }[];\n seen: Set<string>;\n closed: boolean;\n eoseCount: number;\n relayCount: number;\n eoseSent: boolean;\n}\n\nfunction closeLiveSub(sub: LiveSub): void {\n for (const handle of sub.handles) {\n try { handle.unsubscribe(); } catch { /* best-effort */ }\n }\n sub.handles.length = 0;\n}\n\n/** Wire one relay's subscription into a live subscription's event/eose flow. */\nfunction attachLiveRelay(\n ctx: RouterCtx,\n sub: LiveSub,\n filters: NostrFilter[],\n relayUrl: string,\n live: boolean,\n sink: OutboxSubscriptionSink,\n): void {\n sub.handles.push(relayPoolSubscribe(ctx, filters, relayUrl, (item) => {\n if (sub.closed) return;\n if (item === 'EOSE') {\n sub.eoseCount += 1;\n if (!sub.eoseSent && sub.eoseCount >= sub.relayCount) {\n sub.eoseSent = true;\n sink.eose();\n if (!live) { sub.closed = true; closeLiveSub(sub); sink.closed(); }\n }\n return;\n }\n if (sub.seen.has(item.id)) return;\n void ctx.verify(item).then((ok) => {\n if (!ok || sub.closed || sub.seen.has(item.id)) return;\n sub.seen.add(item.id);\n sink.event(item, relayUrl);\n });\n }));\n}\n\nfunction startSubscription(\n ctx: RouterCtx,\n filters: NostrFilter[],\n options: OutboxSubscribeOptions | undefined,\n sink: OutboxSubscriptionSink,\n): OutboxRouterSubscription {\n const live = options?.live ?? true;\n const strategy = options?.strategy ?? 'auto';\n const authors = deriveAuthors(filters, options?.authors);\n const sub: LiveSub = { handles: [], seen: new Set(), closed: false, eoseCount: 0, relayCount: 0, eoseSent: false };\n\n void resolvePlan(ctx, authors, 'read', strategy, options?.relays)\n .then((plan) => {\n if (sub.closed) return;\n if (plan.relays.length === 0) { sink.closed('relay list unavailable'); return; }\n sub.relayCount = plan.relays.length;\n for (const relayUrl of plan.relays) attachLiveRelay(ctx, sub, filters, relayUrl, live, sink);\n })\n .catch((err) => {\n if (!sub.closed) sink.closed(err instanceof Error ? err.message : 'subscribe failed');\n });\n\n return {\n close(): void {\n if (sub.closed) return;\n sub.closed = true;\n closeLiveSub(sub);\n },\n };\n}\n\n/** Resolve the full write/inbox/hint relay set a publish should fan out to. */\nasync function resolvePublishTargets(\n ctx: RouterCtx,\n signed: NostrEvent,\n options?: OutboxPublishOptions,\n): Promise<string[]> {\n const strategy = options?.strategy ?? 'auto';\n const targets = new Set<string>();\n\n // The author's own write relays (outbox model for the user's own event).\n const authorPlan = await resolvePlan(ctx, [signed.pubkey], 'read', strategy === 'inbox' ? 'auto' : 'outbox');\n for (const url of authorPlan.relays) targets.add(url);\n\n // Directed events: include recipients' read relays (their inbox).\n if (options?.targetAuthors && options.targetAuthors.length > 0) {\n const inboxPlan = await resolvePlan(ctx, options.targetAuthors, 'write', 'inbox');\n for (const url of inboxPlan.relays) targets.add(url);\n }\n\n for (const url of options?.relays ?? []) targets.add(url);\n return allowed(ctx, targets);\n}\n\nasync function publishImpl(ctx: RouterCtx, template: EventTemplate, options?: OutboxPublishOptions): Promise<OutboxPublishResult> {\n if (!ctx.signEvent) return { ok: false, error: 'publish denied' };\n if (!ctx.relayPool.isAvailable()) return { ok: false, error: 'relay list unavailable' };\n\n let signed: NostrEvent;\n try {\n signed = await ctx.signEvent(template);\n } catch (err) {\n return { ok: false, error: err instanceof Error ? err.message : 'sign failed' };\n }\n\n const relayUrls = await resolvePublishTargets(ctx, signed, options);\n if (relayUrls.length === 0) return { ok: false, event: signed, eventId: signed.id, error: 'relay list unavailable' };\n\n let relays: Record<string, boolean>;\n try {\n relays = normalizePublishResult(await ctx.relayPool.publish(signed, relayUrls), relayUrls);\n } catch (err) {\n return { ok: false, event: signed, eventId: signed.id, error: err instanceof Error ? err.message : 'publish failed' };\n }\n\n const ok = Object.values(relays).some(Boolean);\n const result: OutboxPublishResult = { ok, event: signed, eventId: signed.id, relays };\n if (!ok) result.error = 'publish denied';\n return result;\n}\n\n/**\n * Create a relay-pool-backed {@link OutboxRouter}.\n *\n * @param options - Relay pool, NIP-65 loader, fallback relays, and optional\n * signer / verifier / relay gate / timeout.\n * @returns An {@link OutboxRouter} for {@link createOutboxService}.\n * @throws If `relayPool`, `loadRelayLists`, or `fallbackRelays` are missing.\n */\nexport function createRelayPoolOutboxRouter(options: RelayPoolOutboxRouterOptions): OutboxRouter {\n if (!options || typeof options.relayPool !== 'object' || options.relayPool === null) {\n throw new Error('createRelayPoolOutboxRouter: options.relayPool is required');\n }\n if (typeof options.loadRelayLists !== 'function') {\n throw new Error('createRelayPoolOutboxRouter: options.loadRelayLists is required');\n }\n if (!Array.isArray(options.fallbackRelays)) {\n throw new Error('createRelayPoolOutboxRouter: options.fallbackRelays is required');\n }\n\n const verifyEvent = options.verifyEvent;\n const ctx: RouterCtx = {\n relayPool: options.relayPool,\n loadRelayLists: options.loadRelayLists,\n fallbackRelays: options.fallbackRelays,\n signEvent: options.signEvent,\n isRelayAllowed: options.isRelayAllowed ?? defaultRelayAllowed,\n defaultTimeoutMs: options.defaultTimeoutMs ?? DEFAULT_QUERY_TIMEOUT_MS,\n async verify(event: NostrEvent): Promise<boolean> {\n if (!verifyEvent) return true;\n try {\n return await verifyEvent(event);\n } catch {\n return false;\n }\n },\n };\n\n return {\n query: (filters, queryOptions) => queryImpl(ctx, filters, queryOptions),\n subscribe: (filters, subscribeOptions, sink) => startSubscription(ctx, filters, subscribeOptions, sink),\n publish: (template, publishOptions) => publishImpl(ctx, template, publishOptions),\n resolveRelays: (target: OutboxTarget) => {\n const pubkeys = target.authors ?? (target.pubkey ? [target.pubkey] : []);\n return resolvePlan(ctx, pubkeys, target.direction ?? 'read', target.strategy ?? 'auto');\n },\n };\n}\n\n/** Normalize a pool publish return into a per-relay success map. */\nfunction normalizePublishResult(\n res: Record<string, boolean> | void,\n relayUrls: string[],\n): Record<string, boolean> {\n const out: Record<string, boolean> = {};\n if (res && typeof res === 'object') {\n for (const url of relayUrls) out[url] = res[url] ?? false;\n } else {\n // void return → optimistic success on every targeted relay.\n for (const url of relayUrls) out[url] = true;\n }\n return out;\n}\n","/**\n * upload-service.ts — NAP-UPLOAD (shell-mediated file/blob upload) reference service.\n *\n * Shell-side handler for the NAP-UPLOAD wire protocol. It is a pure envelope\n * router: it validates `upload.*` envelopes, delegates the actual byte transfer\n * (server selection, rail authorization signing, the HTTP upload) to an injected\n * {@link Uploader}, and posts the correlated result / status messages back to the\n * napplet.\n *\n * The uploader is injected (options-as-bridge) so this service has no transport\n * or Nostr dependency and is fully unit-testable. NAP-UPLOAD is deliberately\n * abstract over the backend — the runtime decides *how* it uploads (NIP-96,\n * Blossom, …). A concrete HTTP-backed uploader ships alongside as\n * {@link createHttpUploader}.\n *\n * ──────────────────────────── Responsibilities ────────────────────────────\n * Inbound: upload.upload, upload.status\n * Outbound: upload.upload.result, upload.status.result, upload.status.changed\n *\n * The service owns the `uploadId` (generated per request, scoped to the\n * requesting napplet), tracks the latest {@link UploadStatus} per upload for\n * `upload.status` queries, and cleans up on window teardown. The shell owns\n * consent, policy, server selection, signing, and the HTTP upload — all behind\n * the {@link Uploader}.\n *\n * @example\n * ```ts\n * import { createUploadService, createHttpUploader } from '@kehto/services';\n *\n * const uploader = createHttpUploader({ rails: { nip96: { servers } }, signEvent });\n * runtime.registerService('upload', createUploadService({ uploader }));\n * ```\n *\n * @packageDocumentation\n */\n\nimport type { NappletMessage } from '@napplet/core';\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\n\n/** Upload service version — follows semver. */\nconst UPLOAD_SERVICE_VERSION = '1.0.0';\n\n/**\n * Storage rail. `nip96` (NIP-96 HTTP file storage) and `blossom` (Blossom blob\n * storage) are the first concrete backends; the open string keeps the API\n * stable as shells add rails (torrents, usenet, …).\n */\nexport type UploadRail = 'nip96' | 'blossom' | (string & {});\n\n/** Lifecycle state of an upload. */\nexport type UploadState = 'pending' | 'uploading' | 'complete' | 'failed' | 'cancelled';\n\n/** Pixel dimensions of an uploaded image/video. */\nexport interface UploadDimensions {\n width: number;\n height: number;\n}\n\n/**\n * A napplet's upload request. `data` crosses the postMessage boundary by\n * structured clone — shells never require base64 encoding.\n */\nexport interface UploadRequest {\n /** Storage rail; omit to let the shell pick a configured default. */\n rail?: UploadRail;\n /** The bytes to upload. */\n data: ArrayBuffer | Blob;\n /** MIME type; inferred from `data` when omitted. */\n mimeType?: string;\n /** Suggested filename. */\n filename?: string;\n /** Alt text / description for the file event. */\n caption?: string;\n /** Request the server not re-encode the file (NIP-96 `no_transform`). */\n noTransform?: boolean;\n /** Rail-specific or shell-specific extra metadata. */\n metadata?: Record<string, unknown>;\n}\n\n/** A single Nostr tag (NIP-94 / imeta entries are arrays of strings). */\nexport type NostrTag = string[];\n\n/** The result of an upload. */\nexport interface UploadResult {\n /** Whether the upload succeeded (or is progressing) vs failed/cancelled. */\n ok: boolean;\n /** Shell-generated id, scoped to the requesting napplet. */\n uploadId: string;\n /** Current lifecycle state. */\n status: UploadState;\n /** The rail the shell used. */\n rail: UploadRail;\n /** Primary download URL. */\n url?: string;\n /** Mirrors / alternative server URLs. */\n fallbackUrls?: string[];\n /** Hash of the stored blob (NIP-94 `x`). */\n sha256?: string;\n /** Hash before server transforms (NIP-94 `ox`). */\n originalSha256?: string;\n /** Size in bytes. */\n size?: number;\n /** Stored MIME type. */\n mimeType?: string;\n /** Image/video dimensions when known. */\n dimensions?: UploadDimensions;\n /** Blurhash placeholder when known. */\n blurhash?: string;\n /** Ready-to-attach NIP-94 / imeta tags. */\n nip94?: NostrTag[];\n /** Error reason when the upload failed or was cancelled. */\n error?: string;\n}\n\n/** A status snapshot for an upload, including progress counters. */\nexport interface UploadStatus extends UploadResult {\n /** Bytes sent so far (while uploading). */\n bytesSent?: number;\n /** Total bytes to send. */\n bytesTotal?: number;\n /** Unix ms timestamp of this status. */\n updatedAt: number;\n}\n\n/**\n * Context handed to an {@link Uploader} for a single upload. Carries the\n * service-owned `uploadId` and a sink for streaming progress / state changes.\n */\nexport interface UploaderContext {\n /** The service-generated upload id (authoritative; scoped to the napplet). */\n uploadId: string;\n /** The napplet window that requested the upload. */\n windowId: string;\n /**\n * Push a status update (progress, or a transition to complete/failed). The\n * service stamps `uploadId` and `updatedAt` before forwarding to the napplet\n * as `upload.status.changed`, and records it as the latest tracked status.\n */\n onStatus(status: UploadStatus): void;\n}\n\n/**\n * Abstract upload backend. Implementors own server selection, rail\n * authorization signing (NIP-98 for NIP-96, kind 24242 for Blossom), the HTTP\n * upload, and integrity-hash reporting. The service translates wire envelopes\n * into these calls and back. A concrete reference implementation ships as\n * {@link createHttpUploader}.\n */\nexport interface Uploader {\n /** Upload `request.data`, streaming progress through `ctx.onStatus`. */\n upload(request: UploadRequest, ctx: UploaderContext): Promise<UploadResult>;\n /** Optional: resolve the latest status for an upload the service is not tracking. */\n status?(uploadId: string): Promise<UploadStatus | undefined>;\n /** Optional: abort an in-flight upload (called on window teardown). */\n cancel?(uploadId: string): void;\n}\n\n/** Options for {@link createUploadService}. */\nexport interface UploadServiceOptions {\n /** The upload backend the shell uses. Required. */\n uploader: Uploader;\n /** Generate an upload id; defaults to `crypto.randomUUID()`. */\n generateId?: () => string;\n /** Current time in unix ms; defaults to `Date.now()`. */\n now?: () => number;\n}\n\ntype Send = (msg: NappletMessage) => void;\n\nconst UPLOAD_DESCRIPTOR: ServiceDescriptor = {\n name: 'upload',\n version: UPLOAD_SERVICE_VERSION,\n description: 'NAP-UPLOAD shell-mediated file/blob upload — upload/status with progress pushes',\n};\n\n/** Per-upload tracking entry, keyed by `windowId:uploadId`. */\ninterface UploadEntry {\n uploadId: string;\n status?: UploadStatus;\n}\n\ndeclare const crypto: { randomUUID(): string };\n\n/**\n * Create the NAP-UPLOAD service handler.\n *\n * @param options - Must provide an {@link Uploader}.\n * @returns A `ServiceHandler` ready for `runtime.registerService('upload', handler)`.\n * @throws If `options.uploader` is missing.\n */\nexport function createUploadService(options: UploadServiceOptions): ServiceHandler {\n if (!options || typeof options.uploader !== 'object' || options.uploader === null) {\n throw new Error('createUploadService: options.uploader is required');\n }\n const { uploader } = options;\n const generateId = options.generateId ?? (() => crypto.randomUUID());\n const now = options.now ?? (() => Date.now());\n\n // Tracked uploads keyed by `windowId:uploadId` for status lookup + cleanup.\n const entries = new Map<string, UploadEntry>();\n\n function handleUpload(windowId: string, msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { id?: string; request?: UploadRequest };\n const id = m.id ?? '';\n const request = m.request;\n if (!request || typeof request !== 'object' || request.data == null) {\n send({ type: 'upload.upload.result', id, error: 'invalid request' } as NappletMessage);\n return;\n }\n\n const uploadId = generateId();\n const key = `${windowId}:${uploadId}`;\n entries.set(key, { uploadId });\n\n const ctx: UploaderContext = {\n uploadId,\n windowId,\n onStatus: (status) => {\n const stamped: UploadStatus = { ...status, uploadId, updatedAt: status.updatedAt || now() };\n const entry = entries.get(key);\n if (entry) entry.status = stamped;\n send({ type: 'upload.status.changed', status: stamped } as NappletMessage);\n },\n };\n\n void uploader\n .upload(request, ctx)\n .then((result) => {\n const stamped: UploadResult = { ...result, uploadId };\n const entry = entries.get(key);\n if (entry) entry.status = { ...stamped, updatedAt: now() };\n send({ type: 'upload.upload.result', id, result: stamped } as NappletMessage);\n })\n .catch((err) => {\n entries.delete(key);\n send({ type: 'upload.upload.result', id, error: toErrorMessage(err) } as NappletMessage);\n });\n }\n\n function handleStatus(windowId: string, msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { id?: string; uploadId?: string };\n const id = m.id ?? '';\n const uploadId = m.uploadId;\n if (typeof uploadId !== 'string' || uploadId.length === 0) {\n send({ type: 'upload.status.result', id, error: 'invalid uploadId' } as NappletMessage);\n return;\n }\n\n const tracked = entries.get(`${windowId}:${uploadId}`)?.status;\n if (tracked) {\n send({ type: 'upload.status.result', id, status: tracked } as NappletMessage);\n return;\n }\n\n if (uploader.status) {\n void uploader\n .status(uploadId)\n .then((status) =>\n send(\n status\n ? ({ type: 'upload.status.result', id, status } as NappletMessage)\n : ({ type: 'upload.status.result', id, error: 'unknown upload' } as NappletMessage),\n ),\n )\n .catch((err) =>\n send({ type: 'upload.status.result', id, error: toErrorMessage(err) } as NappletMessage),\n );\n return;\n }\n\n send({ type: 'upload.status.result', id, error: 'unknown upload' } as NappletMessage);\n }\n\n return {\n descriptor: UPLOAD_DESCRIPTOR,\n handleMessage(windowId: string, message: NappletMessage, send: Send): void {\n switch (message.type) {\n case 'upload.upload':\n handleUpload(windowId, message, send);\n return;\n case 'upload.status':\n handleStatus(windowId, message, send);\n return;\n default:\n // Unknown upload.* action — silently ignored (forward-compatible).\n return;\n }\n },\n onWindowDestroyed(windowId: string): void {\n const prefix = `${windowId}:`;\n for (const [key, entry] of entries) {\n if (key.startsWith(prefix)) {\n uploader.cancel?.(entry.uploadId);\n entries.delete(key);\n }\n }\n },\n };\n}\n\nfunction toErrorMessage(err: unknown): string {\n if (err instanceof Error) return err.message;\n if (typeof err === 'string') return err;\n return 'upload request failed';\n}\n","/**\n * http-uploader.ts — NAP-UPLOAD concrete HTTP-backed {@link Uploader}.\n *\n * The reference upload backend for {@link createUploadService}. Implements two\n * storage rails over HTTP:\n *\n * - **NIP-96** — signs a NIP-98 (kind 27235) HTTP-auth event, POSTs the file as\n * `multipart/form-data`, and maps the returned NIP-94 event tags into an\n * {@link UploadResult}.\n * - **Blossom** — signs a kind 24242 authorization event, PUTs the raw bytes to\n * `<server>/upload`, and maps the returned blob descriptor.\n *\n * Signing (`signEvent`) and transport (`fetch`) are injected so the uploader\n * carries no Nostr or network dependency and is fully unit-testable. The shell\n * holds the signing key and never exposes it to napplets — the uploader only\n * receives a signing callback. Server URLs are shell configuration, not napplet\n * input: a napplet may *hint* a rail, but never a server.\n *\n * The configured server URL is used directly as the upload endpoint (the\n * NIP-96 `api_url` / Blossom base). Hosts that need `.well-known` discovery can\n * resolve it before constructing the uploader.\n *\n * @example\n * ```ts\n * const uploader = createHttpUploader({\n * rails: { nip96: { servers: ['https://nostr.build/api/v2/nip96/upload'] } },\n * signEvent: (tmpl) => signer.signEvent(tmpl),\n * });\n * runtime.registerService('upload', createUploadService({ uploader }));\n * ```\n *\n * @packageDocumentation\n */\n\nimport type { EventTemplate, NostrEvent } from '@napplet/core';\nimport type {\n NostrTag,\n UploadDimensions,\n UploadRail,\n UploadRequest,\n UploadResult,\n Uploader,\n UploaderContext,\n} from './upload-service.js';\n\n/** NIP-98 HTTP-auth event kind. */\nconst KIND_NIP98 = 27235;\n/** Blossom authorization event kind. */\nconst KIND_BLOSSOM_AUTH = 24242;\n/** Blossom auth-event lifetime, in seconds. */\nconst BLOSSOM_AUTH_TTL_S = 3600;\n\n/** Per-rail server configuration. The first server is the primary endpoint. */\nexport interface RailServerConfig {\n /** Ordered server endpoint URLs; index 0 is primary. */\n servers: string[];\n}\n\n/** Storage rails this uploader can serve. */\nexport interface HttpUploaderRails {\n /** NIP-96 HTTP file storage. */\n nip96?: RailServerConfig;\n /** Blossom blob storage. */\n blossom?: RailServerConfig;\n}\n\n/** Signs an event template on the user's behalf (shell holds the key). */\nexport type SignEvent = (template: EventTemplate) => Promise<NostrEvent>;\n\n/** Options for {@link createHttpUploader}. */\nexport interface HttpUploaderOptions {\n /** Configured rails + their servers. */\n rails: HttpUploaderRails;\n /** Rail to use when a request omits one; defaults to the first configured rail. */\n defaultRail?: UploadRail;\n /** Signs NIP-98 / Blossom auth events. Required. */\n signEvent: SignEvent;\n /** Fetch implementation; defaults to the global `fetch`. */\n fetch?: typeof fetch;\n /** Hex SHA-256 of the payload bytes; defaults to Web Crypto. */\n digestSha256?: (bytes: Uint8Array) => Promise<string>;\n /** Unix *seconds* clock for event timestamps; defaults to `Date.now()/1000`. */\n now?: () => number;\n}\n\ndeclare const btoa: (data: string) => string;\ndeclare const crypto: { subtle: { digest(alg: string, data: ArrayBuffer): Promise<ArrayBuffer> } };\n\n/**\n * Create the reference HTTP {@link Uploader} (NIP-96 + Blossom rails).\n *\n * @param options - Rails, server config, and the injected `signEvent`.\n * @returns An {@link Uploader} for `createUploadService({ uploader })`.\n * @throws If `options.signEvent` is missing.\n */\nexport function createHttpUploader(options: HttpUploaderOptions): Uploader {\n if (!options || typeof options.signEvent !== 'function') {\n throw new Error('createHttpUploader: options.signEvent is required');\n }\n const rails = options.rails ?? {};\n const signEvent = options.signEvent;\n const fetchFn = options.fetch ?? fetch;\n const digest = options.digestSha256 ?? defaultDigestSha256;\n const nowS = options.now ?? (() => Math.floor(Date.now() / 1000));\n const defaultRail = options.defaultRail ?? firstConfiguredRail(rails);\n\n async function upload(request: UploadRequest, ctx: UploaderContext): Promise<UploadResult> {\n const rail = request.rail ?? defaultRail;\n const config = rail === 'nip96' ? rails.nip96 : rail === 'blossom' ? rails.blossom : undefined;\n if (rail !== 'nip96' && rail !== 'blossom') {\n return failed(ctx.uploadId, rail ?? 'unknown', 'unsupported rail');\n }\n\n const server = config?.servers?.[0];\n if (!server) {\n return failed(ctx.uploadId, rail, 'no server configured');\n }\n\n const bytes = await toBytes(request.data);\n const sha256 = await digest(bytes);\n\n try {\n return rail === 'nip96'\n ? await uploadNip96({ request, ctx, server, bytes, sha256, signEvent, fetchFn, nowS })\n : await uploadBlossom({ request, ctx, server, bytes, sha256, signEvent, fetchFn, nowS });\n } catch (err) {\n return failed(ctx.uploadId, rail, toErrorMessage(err), sha256);\n }\n }\n\n return { upload };\n}\n\ninterface RailUploadArgs {\n request: UploadRequest;\n ctx: UploaderContext;\n server: string;\n bytes: Uint8Array;\n sha256: string;\n signEvent: SignEvent;\n fetchFn: typeof fetch;\n nowS: () => number;\n}\n\nasync function uploadNip96(args: RailUploadArgs): Promise<UploadResult> {\n const { request, ctx, server, bytes, sha256, signEvent, fetchFn, nowS } = args;\n\n const auth = await signEvent({\n kind: KIND_NIP98,\n created_at: nowS(),\n content: '',\n tags: [\n ['u', server],\n ['method', 'POST'],\n ['payload', sha256],\n ],\n });\n\n const form = new FormData();\n form.append('file', new Blob([bytesToArrayBuffer(bytes)], { type: request.mimeType }), request.filename ?? 'file');\n if (request.caption !== undefined) form.append('caption', request.caption);\n if (request.mimeType !== undefined) form.append('content_type', request.mimeType);\n if (request.noTransform) form.append('no_transform', 'true');\n\n const res = await fetchFn(server, {\n method: 'POST',\n headers: { Authorization: nostrAuthHeader(auth) },\n body: form,\n });\n\n if (!res.ok) {\n return failed(ctx.uploadId, 'nip96', `server rejected (HTTP ${res.status})`, sha256);\n }\n\n const body = (await res.json()) as Nip96Response;\n if (body.status === 'error') {\n return failed(ctx.uploadId, 'nip96', body.message ?? 'upload failed', sha256);\n }\n\n const tags = body.nip94_event?.tags ?? [];\n return fromNip94Tags(ctx.uploadId, 'nip96', tags, bytes.byteLength, sha256);\n}\n\ninterface Nip96Response {\n status?: 'success' | 'error' | string;\n message?: string;\n nip94_event?: { tags?: string[][] };\n}\n\n/** Map NIP-94 / imeta tags into an {@link UploadResult}. */\nfunction fromNip94Tags(\n uploadId: string,\n rail: UploadRail,\n tags: string[][],\n fallbackSize: number,\n fallbackSha: string,\n): UploadResult {\n const get = (name: string): string | undefined => tags.find((t) => t[0] === name)?.[1];\n const url = get('url');\n const result: UploadResult = {\n ok: Boolean(url),\n uploadId,\n status: url ? 'complete' : 'failed',\n rail,\n sha256: get('x') ?? fallbackSha,\n nip94: tags as NostrTag[],\n };\n if (url) result.url = url;\n const ox = get('ox');\n if (ox) result.originalSha256 = ox;\n const size = get('size');\n result.size = size ? Number(size) : fallbackSize;\n const m = get('m');\n if (m) result.mimeType = m;\n const dim = parseDimensions(get('dim'));\n if (dim) result.dimensions = dim;\n const blurhash = get('blurhash');\n if (blurhash) result.blurhash = blurhash;\n if (!url) result.error = 'server returned no url';\n return result;\n}\n\nasync function uploadBlossom(args: RailUploadArgs): Promise<UploadResult> {\n const { request, ctx, server, bytes, sha256, signEvent, fetchFn, nowS } = args;\n\n const auth = await signEvent({\n kind: KIND_BLOSSOM_AUTH,\n created_at: nowS(),\n content: `Upload ${request.filename ?? 'file'}`,\n tags: [\n ['t', 'upload'],\n ['x', sha256],\n ['expiration', String(nowS() + BLOSSOM_AUTH_TTL_S)],\n ],\n });\n\n const endpoint = `${trimTrailingSlash(server)}/upload`;\n const headers: Record<string, string> = { Authorization: nostrAuthHeader(auth) };\n if (request.mimeType) headers['Content-Type'] = request.mimeType;\n\n const res = await fetchFn(endpoint, {\n method: 'PUT',\n headers,\n body: bytesToArrayBuffer(bytes),\n });\n\n if (!res.ok) {\n return failed(ctx.uploadId, 'blossom', `server rejected (HTTP ${res.status})`, sha256);\n }\n\n const blob = (await res.json()) as BlossomDescriptor;\n if (!blob.url) {\n return failed(ctx.uploadId, 'blossom', 'server returned no url', sha256);\n }\n\n const result: UploadResult = {\n ok: true,\n uploadId: ctx.uploadId,\n status: 'complete',\n rail: 'blossom',\n url: blob.url,\n sha256: blob.sha256 ?? sha256,\n size: blob.size ?? bytes.byteLength,\n };\n if (blob.type) result.mimeType = blob.type;\n return result;\n}\n\ninterface BlossomDescriptor {\n url?: string;\n sha256?: string;\n size?: number;\n type?: string;\n}\n\nfunction failed(uploadId: string, rail: UploadRail, error: string, sha256?: string): UploadResult {\n return { ok: false, uploadId, status: 'failed', rail, error, ...(sha256 ? { sha256 } : {}) };\n}\n\nfunction firstConfiguredRail(rails: HttpUploaderRails): UploadRail | undefined {\n if (rails.nip96?.servers?.length) return 'nip96';\n if (rails.blossom?.servers?.length) return 'blossom';\n return undefined;\n}\n\nfunction nostrAuthHeader(event: NostrEvent): string {\n return `Nostr ${base64Utf8(JSON.stringify(event))}`;\n}\n\nfunction base64Utf8(s: string): string {\n // UTF-8 safe base64 (event content/tags may contain non-ASCII).\n return btoa(String.fromCharCode(...new TextEncoder().encode(s)));\n}\n\nfunction parseDimensions(dim: string | undefined): UploadDimensions | undefined {\n if (!dim) return undefined;\n const m = /^(\\d+)x(\\d+)$/.exec(dim);\n if (!m) return undefined;\n return { width: Number(m[1]), height: Number(m[2]) };\n}\n\nasync function toBytes(data: ArrayBuffer | Blob): Promise<Uint8Array> {\n if (data instanceof ArrayBuffer) return new Uint8Array(data);\n return new Uint8Array(await data.arrayBuffer());\n}\n\nfunction bytesToArrayBuffer(bytes: Uint8Array): ArrayBuffer {\n return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer;\n}\n\nfunction trimTrailingSlash(url: string): string {\n return url.endsWith('/') ? url.slice(0, -1) : url;\n}\n\nasync function defaultDigestSha256(bytes: Uint8Array): Promise<string> {\n const buf = await crypto.subtle.digest('SHA-256', bytesToArrayBuffer(bytes));\n return Array.from(new Uint8Array(buf))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n}\n\nfunction toErrorMessage(err: unknown): string {\n if (err instanceof Error) return err.message;\n if (typeof err === 'string') return err;\n return 'upload failed';\n}\n","/**\n * intent-service.ts — NAP-INTENT (archetype intent dispatch) reference service.\n *\n * Shell-side handler for the NAP-INTENT wire protocol. It is a pure envelope\n * router: it validates `intent.*` envelopes, delegates archetype resolution,\n * default handling, window lifecycle, and payload delivery to an injected\n * {@link IntentResolver}, and posts the correlated result / push messages\n * (echoing the request `id`) back to the napplet.\n *\n * The resolver is injected (options-as-bridge) so this service has no shell or\n * window-manager dependency and is fully unit-testable. A concrete\n * catalog-backed resolver ships alongside as {@link createCatalogIntentResolver}.\n *\n * ──────────────────────────── Responsibilities ────────────────────────────\n * Inbound: intent.invoke, intent.available, intent.handlers\n * Outbound: intent.invoke.result, intent.available.result,\n * intent.handlers.result, intent.changed\n *\n * The shell owns archetype→handler resolution, the user's default-handler\n * preference, the \"open with…\" chooser, and window creation/focus — all behind\n * the {@link IntentResolver}. This service only marshals the wire protocol and\n * fans `intent.changed` pushes out to the napplets it has served.\n *\n * @example\n * ```ts\n * import { createIntentService, createCatalogIntentResolver } from '@kehto/services';\n *\n * const resolver = createCatalogIntentResolver({ loadCatalog, windows });\n * runtime.registerService('intent', createIntentService({ resolver }));\n * ```\n *\n * @packageDocumentation\n */\n\nimport type { NappletMessage } from '@napplet/core';\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\nimport type { IntentAvailability, IntentRequest, IntentResult } from './intent-types.js';\n\n/** Intent service version — follows semver. */\nconst INTENT_SERVICE_VERSION = '1.0.0';\n\n/** Context passed to {@link IntentResolver.invoke} for trust/attribution. */\nexport interface IntentResolverContext {\n /** Window id of the napplet that issued the request. */\n windowId: string;\n}\n\n/**\n * Abstract intent resolver. Implementors own the installed-napplet catalog,\n * archetype→handler resolution, the user's default-handler preference, the\n * \"open with…\" chooser, window creation/focus, and payload delivery. The\n * service translates wire envelopes into these calls and back.\n */\nexport interface IntentResolver {\n /**\n * Resolve `request.archetype` to a handler, create or focus its window, and\n * deliver `payload`. Returns once the handler is resolved and the window\n * created; delivery MAY complete asynchronously.\n */\n invoke(request: IntentRequest, context: IntentResolverContext): IntentResult | Promise<IntentResult>;\n /** Report whether the runtime can currently satisfy `archetype`, and how. */\n available(archetype: string): IntentAvailability | Promise<IntentAvailability>;\n /** Report availability for every archetype the runtime can currently satisfy. */\n handlers(): IntentAvailability[] | Promise<IntentAvailability[]>;\n /**\n * Register for availability changes (a napplet installed/removed, or a default\n * handler changed). The service forwards each change to served napplets as an\n * `intent.changed` push. Returns an unsubscribe handle. Resolvers whose\n * catalog never changes at runtime MAY omit this.\n */\n onChanged?(listener: (availability: IntentAvailability) => void): () => void;\n}\n\n/** Options for {@link createIntentService}. */\nexport interface IntentServiceOptions {\n /** The intent resolver the shell uses to route archetypes. Required. */\n resolver: IntentResolver;\n}\n\ntype Send = (msg: NappletMessage) => void;\n\nconst INTENT_DESCRIPTOR: ServiceDescriptor = {\n name: 'intent',\n version: INTENT_SERVICE_VERSION,\n description: 'NAP-INTENT archetype intent dispatch — invoke/available/handlers',\n};\n\nfunction toErrorMessage(err: unknown): string {\n if (err instanceof Error) return err.message;\n if (typeof err === 'string') return err;\n return 'intent request failed';\n}\n\n/**\n * Create the NAP-INTENT service handler.\n *\n * @param options - Must provide an {@link IntentResolver}.\n * @returns A `ServiceHandler` ready for `runtime.registerService('intent', handler)`.\n * @throws If `options.resolver` is missing.\n */\nexport function createIntentService(options: IntentServiceOptions): ServiceHandler {\n if (!options || typeof options.resolver !== 'object' || options.resolver === null) {\n throw new Error('createIntentService: options.resolver is required');\n }\n const { resolver } = options;\n\n // Latest send callback per served window, used to fan `intent.changed`\n // pushes out to every napplet that has interacted with the intent domain.\n const windows = new Map<string, Send>();\n\n // Subscribe once for availability changes and broadcast them as pushes.\n resolver.onChanged?.((availability) => {\n for (const send of windows.values()) {\n send({ type: 'intent.changed', availability } as NappletMessage);\n }\n });\n\n /**\n * Run a resolver call and marshal its outcome onto `resultType`. The resolver\n * method may be sync or async — `settle` normalizes it to a promise and\n * catches a synchronous throw (e.g. a sync resolver that rejects an input)\n * the same way it catches an async rejection.\n */\n function settle<T>(\n call: () => T | Promise<T>,\n send: Send,\n resultType: string,\n id: string,\n onValue: (value: T) => NappletMessage,\n ): void {\n let pending: Promise<T>;\n try {\n pending = Promise.resolve(call());\n } catch (err) {\n send({ type: resultType, id, error: toErrorMessage(err) } as NappletMessage);\n return;\n }\n pending\n .then((value) => send(onValue(value)))\n .catch((err) => send({ type: resultType, id, error: toErrorMessage(err) } as NappletMessage));\n }\n\n function handleInvoke(windowId: string, msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { id?: string; request?: IntentRequest };\n const id = m.id ?? '';\n const request = m.request;\n if (!request || typeof request !== 'object' || typeof request.archetype !== 'string' || request.archetype.length === 0) {\n send({ type: 'intent.invoke.result', id, error: 'invalid request' } as NappletMessage);\n return;\n }\n settle(\n () => resolver.invoke(request, { windowId }),\n send, 'intent.invoke.result', id,\n (result) => ({ type: 'intent.invoke.result', id, result } as NappletMessage),\n );\n }\n\n function handleAvailable(msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { id?: string; archetype?: unknown };\n const id = m.id ?? '';\n if (typeof m.archetype !== 'string' || m.archetype.length === 0) {\n send({ type: 'intent.available.result', id, error: 'invalid archetype' } as NappletMessage);\n return;\n }\n const archetype = m.archetype;\n settle(\n () => resolver.available(archetype),\n send, 'intent.available.result', id,\n (availability) => ({ type: 'intent.available.result', id, availability } as NappletMessage),\n );\n }\n\n function handleHandlers(msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { id?: string };\n const id = m.id ?? '';\n settle(\n () => resolver.handlers(),\n send, 'intent.handlers.result', id,\n (handlers) => ({ type: 'intent.handlers.result', id, handlers } as NappletMessage),\n );\n }\n\n return {\n descriptor: INTENT_DESCRIPTOR,\n handleMessage(windowId: string, message: NappletMessage, send: Send): void {\n windows.set(windowId, send);\n switch (message.type) {\n case 'intent.invoke':\n handleInvoke(windowId, message, send);\n return;\n case 'intent.available':\n handleAvailable(message, send);\n return;\n case 'intent.handlers':\n handleHandlers(message, send);\n return;\n default:\n // Unknown intent.* action — silently ignored (forward-compatible).\n return;\n }\n },\n onWindowDestroyed(windowId: string): void {\n windows.delete(windowId);\n },\n };\n}\n","/**\n * catalog-intent-resolver.ts — NAP-INTENT concrete {@link IntentResolver}.\n *\n * A reference resolver that satisfies {@link IntentResolver} from an\n * installed-napplet catalog plus host-supplied policy: the user's\n * default-handler preference, an optional \"open with…\" chooser, the archetype's\n * recommended default protocol, and a window controller that creates or focuses\n * the handler window and delivers the payload.\n *\n * Resolution policy (mirrors an OS implicit-intent + default-app model):\n * 1. Gather candidates for the archetype from the catalog.\n * 2. Pick the handler — an explicit `handler` dTag, a user choice\n * (`handler: \"choose\"`), the user's default, the sole candidate, or a\n * deterministic first-candidate fallback.\n * 3. Validate the resolved handler supports the requested `action` and\n * `protocol` (or fall back to the archetype's recommended default protocol,\n * then the candidate's first accepted protocol).\n * 4. Open or focus the handler window and return its id.\n *\n * The catalog, defaults, chooser, and window controller are all injected, so\n * this resolver carries no shell, manifest, or DOM dependency and is fully\n * unit-testable.\n *\n * @packageDocumentation\n */\n\nimport type {\n IntentAvailability,\n IntentBehavior,\n IntentCandidate,\n IntentRequest,\n IntentResult,\n} from './intent-types.js';\nimport type { IntentResolver, IntentResolverContext } from './intent-service.js';\n\n/** The actions/protocols a napplet fulfills for a single archetype. */\nexport interface IntentArchetypeSupport {\n /** Verbs this napplet supports for the archetype (e.g. `[\"open\", \"edit\"]`). */\n actions: string[];\n /** NAP-N protocol ids this napplet accepts for the archetype. */\n protocols: string[];\n}\n\n/**\n * One installed napplet's intent surface, derived from its signed NIP-5A\n * manifest. Keyed by archetype slug so a single napplet can fulfill several\n * roles.\n */\nexport interface IntentCatalogEntry {\n /** The napplet's dTag. */\n dTag: string;\n /** Human-readable title from the manifest. */\n title?: string;\n /** Archetype slug → the actions/protocols this napplet fulfills for it. */\n archetypes: Record<string, IntentArchetypeSupport>;\n}\n\n/** Parameters handed to {@link IntentWindowController.open}. */\nexport interface IntentOpenParams {\n /** dTag of the resolved handler napplet. */\n dTag: string;\n /** The archetype being dispatched. */\n archetype: string;\n /** The resolved action (e.g. `\"open\"`). */\n action: string;\n /** The wire format the payload is delivered with, when one was resolved. */\n protocol?: string;\n /** The opaque payload to deliver. */\n payload?: unknown;\n /** Window-behavior hints from the request. */\n behavior?: IntentBehavior;\n /** Window id of the napplet that issued the intent. */\n callerWindowId: string;\n}\n\n/** Creates or focuses the handler window and delivers the intent payload. */\nexport interface IntentWindowController {\n /**\n * Create or focus the handler window and deliver `payload` via `protocol`.\n * Returns the window id the handler was created or focused in.\n */\n open(params: IntentOpenParams): { windowId: string } | Promise<{ windowId: string }>;\n}\n\n/** Options for {@link createCatalogIntentResolver}. */\nexport interface CatalogIntentResolverOptions {\n /** Return the installed-napplet catalog (signed NIP-5A manifests). Required. */\n loadCatalog(): IntentCatalogEntry[] | Promise<IntentCatalogEntry[]>;\n /** Window controller used to create/focus the resolved handler's window. Required. */\n windows: IntentWindowController;\n /**\n * The user's default handler dTag for an archetype, or `undefined` when none\n * is set. Default-handler settings are user state — never set by napplets.\n */\n getDefaultHandler?(archetype: string): string | undefined;\n /**\n * Resolve a `handler: \"choose\"` prompt (or a no-default ambiguity) to a dTag.\n * Returning `undefined` means the user cancelled. When omitted, ambiguous\n * resolution falls back to the first candidate.\n */\n chooseHandler?(\n archetype: string,\n candidates: IntentCandidate[],\n callerWindowId: string,\n ): string | undefined | Promise<string | undefined>;\n /** The archetype's recommended default protocol when the caller omits one. */\n defaultProtocol?(archetype: string): string | undefined;\n}\n\n/**\n * A {@link IntentResolver} backed by a catalog, with a host hook to announce\n * catalog/default changes.\n */\nexport interface CatalogIntentResolver extends IntentResolver {\n /**\n * Announce that the catalog or default handler for `archetype` changed. The\n * resolver recomputes availability and notifies `onChanged` listeners, which\n * the intent service forwards to napplets as `intent.changed` pushes.\n */\n notifyChanged(archetype: string): void;\n}\n\n/** Build the candidate list for an archetype, marking the user's default. */\nfunction candidatesFor(\n catalog: IntentCatalogEntry[],\n archetype: string,\n defaultHandler: string | undefined,\n): IntentCandidate[] {\n const candidates: IntentCandidate[] = [];\n for (const entry of catalog) {\n const support = entry.archetypes[archetype];\n if (!support) continue;\n candidates.push({\n dTag: entry.dTag,\n ...(entry.title === undefined ? {} : { title: entry.title }),\n actions: support.actions,\n protocols: support.protocols,\n ...(entry.dTag === defaultHandler ? { isDefault: true } : {}),\n });\n }\n return candidates;\n}\n\nfunction fail(archetype: string, action: string, error: string): IntentResult {\n return { ok: false, archetype, action, handled: false, error };\n}\n\n/**\n * Create a catalog-backed NAP-INTENT resolver.\n *\n * @param options - Catalog loader and window controller (required) plus\n * optional default-handler, chooser, and default-protocol policy hooks.\n * @returns A {@link CatalogIntentResolver} for `createIntentService({ resolver })`.\n * @throws If `options.loadCatalog` or `options.windows` is missing.\n *\n * @example\n * ```ts\n * const resolver = createCatalogIntentResolver({\n * loadCatalog: () => installedNapplets,\n * windows: { open: ({ dTag }) => ({ windowId: openWindow(dTag) }) },\n * getDefaultHandler: (a) => userDefaults[a],\n * });\n * ```\n */\nexport function createCatalogIntentResolver(options: CatalogIntentResolverOptions): CatalogIntentResolver {\n if (!options || typeof options.loadCatalog !== 'function') {\n throw new Error('createCatalogIntentResolver: options.loadCatalog is required');\n }\n if (!options.windows || typeof options.windows.open !== 'function') {\n throw new Error('createCatalogIntentResolver: options.windows is required');\n }\n const { loadCatalog, windows, getDefaultHandler, chooseHandler, defaultProtocol } = options;\n const listeners = new Set<(availability: IntentAvailability) => void>();\n\n async function availabilityFor(archetype: string): Promise<IntentAvailability> {\n const catalog = await loadCatalog();\n const def = getDefaultHandler?.(archetype);\n const candidates = candidatesFor(catalog, archetype, def);\n return {\n archetype,\n available: candidates.length > 0,\n candidates,\n hasDefault: def !== undefined && candidates.some((c) => c.dTag === def),\n };\n }\n\n /** Decide which candidate dTag should handle the request, or null to cancel. */\n async function pickHandler(\n archetype: string,\n candidates: IntentCandidate[],\n preference: IntentRequest['handler'],\n callerWindowId: string,\n ): Promise<string | null> {\n // Explicit dTag target (anything that isn't the two reserved keywords).\n if (typeof preference === 'string' && preference !== 'default' && preference !== 'choose') {\n return preference;\n }\n if (preference === 'choose') {\n const picked = await chooseHandler?.(archetype, candidates, callerWindowId);\n return picked ?? null;\n }\n // Default path: user's default, then the sole candidate, then chooser, then\n // a deterministic first-candidate fallback.\n const def = getDefaultHandler?.(archetype);\n if (def !== undefined && candidates.some((c) => c.dTag === def)) return def;\n if (candidates.length === 1) return candidates[0].dTag;\n if (chooseHandler) {\n const picked = await chooseHandler(archetype, candidates, callerWindowId);\n return picked ?? null;\n }\n return candidates[0].dTag;\n }\n\n async function invoke(request: IntentRequest, context: IntentResolverContext): Promise<IntentResult> {\n const archetype = request.archetype;\n const action = request.action ?? 'open';\n const catalog = await loadCatalog();\n const candidates = candidatesFor(catalog, archetype, getDefaultHandler?.(archetype));\n if (candidates.length === 0) return fail(archetype, action, 'no handler');\n\n const pickedDTag = await pickHandler(archetype, candidates, request.handler, context.windowId);\n if (pickedDTag === null) return fail(archetype, action, 'user cancelled');\n\n const handler = candidates.find((c) => c.dTag === pickedDTag);\n if (!handler) return fail(archetype, action, 'no handler');\n\n if (!handler.actions.includes(action)) return fail(archetype, action, 'unsupported action');\n\n const protocol = request.protocol ?? defaultProtocol?.(archetype) ?? handler.protocols[0];\n if (protocol !== undefined && handler.protocols.length > 0 && !handler.protocols.includes(protocol)) {\n return fail(archetype, action, 'unsupported protocol');\n }\n\n let windowId: string;\n try {\n const opened = await windows.open({\n dTag: handler.dTag,\n archetype,\n action,\n ...(protocol === undefined ? {} : { protocol }),\n ...(request.payload === undefined ? {} : { payload: request.payload }),\n ...(request.behavior === undefined ? {} : { behavior: request.behavior }),\n callerWindowId: context.windowId,\n });\n windowId = opened.windowId;\n } catch {\n return fail(archetype, action, 'invoke failed');\n }\n\n return {\n ok: true,\n archetype,\n action,\n handled: true,\n handler: handler.dTag,\n windowId,\n ...(protocol === undefined ? {} : { protocol }),\n };\n }\n\n async function handlers(): Promise<IntentAvailability[]> {\n const catalog = await loadCatalog();\n const archetypes = new Set<string>();\n for (const entry of catalog) {\n for (const slug of Object.keys(entry.archetypes)) archetypes.add(slug);\n }\n return Promise.all([...archetypes].map((a) => availabilityFor(a)));\n }\n\n return {\n invoke,\n available: availabilityFor,\n handlers,\n onChanged(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n notifyChanged(archetype) {\n if (listeners.size === 0) return;\n void availabilityFor(archetype).then((availability) => {\n for (const listener of listeners) listener(availability);\n });\n },\n };\n}\n","/**\n * manifest-intent-catalog.ts — signed-manifest → NAP-INTENT catalog adapter.\n *\n * Adapts a resolved NIP-5A/5D napplet manifest's archetype tags into an\n * {@link IntentCatalogEntry} — the shape `createCatalogIntentResolver.loadCatalog`\n * consumes. This lets NAP-INTENT availability and handler candidacy flow from\n * verified manifest tags rather than host-injected catalog data.\n *\n * To avoid a `@kehto/services → @kehto/nip` dependency cycle (services must stay\n * dependency-light and `@kehto/nip` is a lower-level NIP utility), the adapter\n * takes a minimal STRUCTURAL input {@link ManifestArchetypeInput} that the\n * `@kehto/nip/5d` `NappletManifest` satisfies by duck typing — callers pass\n * `resolved.manifest` directly without any package coupling.\n *\n * @packageDocumentation\n */\n\nimport type { IntentArchetypeSupport, IntentCatalogEntry } from './catalog-intent-resolver.js';\n\n/** NAP-INTENT default action when a manifest does not enumerate actions. */\nconst DEFAULT_ACTIONS: readonly string[] = ['open'];\n\n/**\n * The structural subset of `@kehto/nip/5d` `NappletManifest` the adapter needs.\n * Intentionally a duck-typed shape so the playground (or any caller) can pass a\n * resolved manifest without importing `@kehto/nip`.\n */\nexport interface ManifestArchetypeInput {\n /** The napplet's `d` identifier. */\n dTag: string;\n /** Optional human-readable title from the manifest. */\n title?: string;\n /**\n * Archetype slugs this napplet fulfills, from the manifest's `archetype` tags;\n * the optional `nap` is the recommended default wire protocol.\n */\n archetypes: Array<{ slug: string; nap?: string }>;\n}\n\n/**\n * Map a resolved napplet manifest's archetype data into an\n * {@link IntentCatalogEntry}.\n *\n * Each archetype `{ slug, nap }` becomes a keyed support record where `actions`\n * defaults to `['open']` (the NAP-INTENT default action — manifests do not\n * enumerate actions in this phase) and `protocols` is `[nap]` when a NAP-N is\n * present, else `[]`. Duplicate slugs keep the last occurrence.\n *\n * @param manifest - A resolved manifest's structural archetype data.\n * @returns The `IntentCatalogEntry` for `createCatalogIntentResolver`.\n *\n * @example\n * ```ts\n * manifestToIntentCatalogEntry({\n * dTag: 'profile-viewer',\n * title: 'Profile',\n * archetypes: [{ slug: 'profile', nap: 'NAP-1' }],\n * });\n * // → { dTag: 'profile-viewer', title: 'Profile',\n * // archetypes: { profile: { actions: ['open'], protocols: ['NAP-1'] } } }\n * ```\n */\nexport function manifestToIntentCatalogEntry(manifest: ManifestArchetypeInput): IntentCatalogEntry {\n const archetypes: Record<string, IntentArchetypeSupport> = {};\n for (const { slug, nap } of manifest.archetypes) {\n archetypes[slug] = {\n actions: [...DEFAULT_ACTIONS],\n protocols: nap ? [nap] : [],\n };\n }\n return {\n dTag: manifest.dTag,\n ...(manifest.title === undefined ? {} : { title: manifest.title }),\n archetypes,\n };\n}\n","/**\n * cvm-service.ts — NAP-CVM (ContextVM bridge) reference service.\n *\n * Shell-side handler for the NAP-CVM wire protocol. It is a pure envelope\n * router: it validates `cvm.*` envelopes, delegates the actual ContextVM /\n * MCP-over-Nostr work to an injected {@link CvmTransport}, and posts the\n * correlated `*.result` (echoing the request `id`) back to the napplet.\n *\n * The transport is injected (options-as-bridge) so this service has no Nostr\n * dependency and is fully unit-testable. A concrete ContextVM transport ships\n * separately at `@kehto/services/cvm-nostr-transport`.\n *\n * ──────────────────────────── Responsibilities ────────────────────────────\n * Inbound: cvm.discover, cvm.request, cvm.close\n * Outbound: cvm.discover.result, cvm.request.result, cvm.close.result,\n * cvm.event (server-pushed MCP notifications)\n *\n * MCP-level errors are returned inside `request.result.message.error`;\n * transport/shell-policy failures are returned in the envelope `error` field.\n *\n * `cvm.event` is fanned out to every window that holds an active session with\n * the originating server (a window opens a session by issuing a `cvm.request`\n * and closes it via `cvm.close` or window teardown).\n *\n * @example\n * ```ts\n * import { createCvmService } from '@kehto/services';\n * import { createNostrCvmTransport } from '@kehto/services/cvm-nostr-transport';\n *\n * const transport = createNostrCvmTransport({ defaultRelays: ['wss://relay.contextvm.org'] });\n * runtime.registerService('cvm', createCvmService({ transport }));\n * ```\n */\n\nimport type { NappletMessage } from '@napplet/core';\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\nimport type {\n CvmDiscoverQuery,\n CvmRequestOptions,\n CvmServer,\n CvmServerRef,\n McpMessage,\n} from './cvm-types.js';\n\n/** CVM service version — follows semver. */\nconst CVM_SERVICE_VERSION = '1.0.0';\n\n/**\n * Abstract ContextVM transport. Implementors own Nostr relay access, signing,\n * encryption (CEP-4 gift wrap), JSON-RPC correlation, and MCP initialization.\n */\nexport interface CvmTransport {\n /** Resolve public ContextVM server announcements matching the query. */\n discover(query?: CvmDiscoverQuery): Promise<CvmServer[]>;\n /** Send a raw MCP message to a server and resolve with the MCP response. */\n request(server: CvmServerRef, message: McpMessage, options?: CvmRequestOptions): Promise<McpMessage>;\n /** Release any session state held for a server (subscriptions, init cache). */\n close(server: CvmServerRef): Promise<void>;\n /**\n * Subscribe to server-pushed MCP messages not correlated to a single\n * request (e.g. notifications). Returns a handle whose `close()` detaches.\n */\n onEvent(handler: (server: CvmServerRef, message: McpMessage) => void): { close(): void };\n}\n\n/** Options for {@link createCvmService}. */\nexport interface CvmServiceOptions {\n /** The ContextVM transport the shell uses to reach servers. Required. */\n transport: CvmTransport;\n}\n\n/** The created CVM service, exposing the handler plus a disposal hook. */\nexport interface CvmService extends ServiceHandler {\n /** Detach the transport event subscription. Idempotent. */\n dispose(): void;\n}\n\ntype Send = (msg: NappletMessage) => void;\n\nconst CVM_DESCRIPTOR: ServiceDescriptor = {\n name: 'cvm',\n version: CVM_SERVICE_VERSION,\n description: 'NAP-CVM ContextVM bridge — MCP over Nostr',\n};\n\n/**\n * Create the NAP-CVM service handler.\n *\n * @param options - Must provide a {@link CvmTransport}.\n * @returns A {@link CvmService} (a `ServiceHandler` with a `dispose()` hook).\n * @throws If `options.transport` is missing.\n */\nexport function createCvmService(options: CvmServiceOptions): CvmService {\n if (!options || typeof options.transport !== 'object' || options.transport === null) {\n throw new Error('createCvmService: options.transport is required');\n }\n const { transport } = options;\n\n // Per-window send callbacks, captured at request time for cvm.event fan-out.\n const sendByWindow = new Map<string, Send>();\n // serverPubkey -> set of windowIds with an active session.\n const windowsByServer = new Map<string, Set<string>>();\n\n function openSession(windowId: string, server: CvmServerRef, send: Send): void {\n sendByWindow.set(windowId, send);\n let windows = windowsByServer.get(server.pubkey);\n if (!windows) {\n windows = new Set<string>();\n windowsByServer.set(server.pubkey, windows);\n }\n windows.add(windowId);\n }\n\n function closeSession(windowId: string, serverPubkey: string): void {\n const windows = windowsByServer.get(serverPubkey);\n if (windows) {\n windows.delete(windowId);\n if (windows.size === 0) windowsByServer.delete(serverPubkey);\n }\n }\n\n const eventSub = transport.onEvent((server, message) => {\n const windows = windowsByServer.get(server.pubkey);\n if (!windows) return;\n for (const windowId of windows) {\n const send = sendByWindow.get(windowId);\n send?.({ type: 'cvm.event', server, message } as NappletMessage);\n }\n });\n\n function handleDiscover(msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { id?: string; query?: CvmDiscoverQuery };\n const id = m.id ?? '';\n void transport\n .discover(m.query)\n .then((servers) => send({ type: 'cvm.discover.result', id, servers } as NappletMessage))\n .catch((err) =>\n send({ type: 'cvm.discover.result', id, servers: [], error: toErrorMessage(err) } as NappletMessage),\n );\n }\n\n function handleRequest(windowId: string, msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & {\n id?: string;\n server?: CvmServerRef;\n message?: McpMessage;\n options?: CvmRequestOptions;\n };\n const id = m.id ?? '';\n if (!m.server || typeof m.server.pubkey !== 'string' || m.server.pubkey.length === 0) {\n send({ type: 'cvm.request.result', id, error: 'server not found' } as NappletMessage);\n return;\n }\n if (!m.message || typeof m.message !== 'object') {\n send({ type: 'cvm.request.result', id, error: 'unsupported method' } as NappletMessage);\n return;\n }\n openSession(windowId, m.server, send);\n void transport\n .request(m.server, m.message, m.options)\n .then((message) => send({ type: 'cvm.request.result', id, message } as NappletMessage))\n .catch((err) =>\n send({ type: 'cvm.request.result', id, error: toErrorMessage(err) } as NappletMessage),\n );\n }\n\n function handleClose(windowId: string, msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { id?: string; server?: CvmServerRef };\n const id = m.id ?? '';\n if (!m.server || typeof m.server.pubkey !== 'string') {\n send({ type: 'cvm.close.result', id, error: 'server not found' } as NappletMessage);\n return;\n }\n closeSession(windowId, m.server.pubkey);\n void transport\n .close(m.server)\n .then(() => send({ type: 'cvm.close.result', id } as NappletMessage))\n .catch((err) => send({ type: 'cvm.close.result', id, error: toErrorMessage(err) } as NappletMessage));\n }\n\n return {\n descriptor: CVM_DESCRIPTOR,\n handleMessage(windowId: string, message: NappletMessage, send: Send): void {\n switch (message.type) {\n case 'cvm.discover':\n handleDiscover(message, send);\n return;\n case 'cvm.request':\n handleRequest(windowId, message, send);\n return;\n case 'cvm.close':\n handleClose(windowId, message, send);\n return;\n default:\n // Unknown cvm.* action — silently ignored (forward-compatible).\n return;\n }\n },\n onWindowDestroyed(windowId: string): void {\n sendByWindow.delete(windowId);\n for (const [pubkey, windows] of windowsByServer) {\n windows.delete(windowId);\n if (windows.size === 0) windowsByServer.delete(pubkey);\n }\n },\n dispose(): void {\n eventSub.close();\n },\n };\n}\n\nfunction toErrorMessage(err: unknown): string {\n if (err instanceof Error) return err.message;\n if (typeof err === 'string') return err;\n return 'cvm request failed';\n}\n","export type JsonPrimitive = string | number | boolean | null;\nexport type JsonValue = JsonPrimitive | JsonValue[] | { readonly [key: string]: JsonValue };\nexport type JsonRecord = Record<string, JsonValue>;\n\nexport type PajaCapabilityDomain =\n | 'relay'\n | 'outbox'\n | 'storage'\n | 'identity'\n | 'keys'\n | 'config'\n | 'resource'\n | 'theme'\n | 'notify'\n | 'media'\n | 'upload'\n | 'intent'\n | 'cvm'\n | 'inc';\n\nexport interface PajaSimulationRawOptions {\n readonly capabilities?: {\n readonly domains?: Partial<Record<PajaCapabilityDomain, boolean>>;\n };\n readonly acl?: {\n readonly mode?: 'allow' | 'deny';\n };\n readonly firewall?: {\n readonly mode?: 'allow' | 'deny' | 'observe';\n };\n readonly identity?: {\n readonly mode?: 'anonymous' | 'fixed';\n readonly pubkey?: string;\n };\n readonly relay?: {\n readonly mode?: 'memory' | 'disabled';\n readonly urls?: readonly string[];\n readonly fixtures?: readonly JsonRecord[];\n };\n readonly storage?: {\n readonly mode?: 'local' | 'memory' | 'disabled';\n };\n readonly cache?: {\n readonly mode?: 'memory' | 'disabled';\n };\n readonly upload?: {\n readonly mode?: 'memory' | 'disabled';\n readonly rail?: string;\n };\n readonly media?: {\n readonly enabled?: boolean;\n };\n readonly notifications?: {\n readonly enabled?: boolean;\n readonly grant?: boolean;\n };\n readonly config?: {\n readonly values?: JsonRecord;\n };\n readonly theme?: {\n readonly mode?: 'dark' | 'light';\n readonly values?: JsonRecord;\n };\n readonly intent?: {\n readonly enabled?: boolean;\n };\n readonly cvm?: {\n readonly enabled?: boolean;\n };\n}\n\nexport interface PajaSimulation {\n readonly capabilities: {\n readonly domains: Record<PajaCapabilityDomain, boolean>;\n readonly disabledDomains: readonly PajaCapabilityDomain[];\n };\n readonly acl: {\n readonly mode: 'allow' | 'deny';\n };\n readonly firewall: {\n readonly mode: 'allow' | 'deny' | 'observe';\n };\n readonly identity: {\n readonly mode: 'anonymous' | 'fixed';\n readonly pubkey: string;\n };\n readonly relay: {\n readonly mode: 'memory' | 'disabled';\n readonly urls: readonly string[];\n readonly fixtures: readonly JsonRecord[];\n };\n readonly storage: {\n readonly mode: 'local' | 'memory' | 'disabled';\n };\n readonly cache: {\n readonly mode: 'memory' | 'disabled';\n };\n readonly upload: {\n readonly mode: 'memory' | 'disabled';\n readonly rail: string;\n };\n readonly media: {\n readonly enabled: boolean;\n };\n readonly notifications: {\n readonly enabled: boolean;\n readonly grant: boolean;\n };\n readonly config: {\n readonly values: JsonRecord;\n };\n readonly theme: {\n readonly mode: 'dark' | 'light';\n readonly values: JsonRecord;\n };\n readonly intent: {\n readonly enabled: boolean;\n };\n readonly cvm: {\n readonly enabled: boolean;\n };\n}\n\nexport class PajaSimulationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'PajaSimulationError';\n }\n}\n\nexport const PAJA_SIMULATION_DOMAINS: readonly PajaCapabilityDomain[] = [\n 'relay',\n 'outbox',\n 'storage',\n 'identity',\n 'keys',\n 'config',\n 'resource',\n 'theme',\n 'notify',\n 'media',\n 'upload',\n 'intent',\n 'cvm',\n 'inc',\n];\n\nconst DEFAULT_RELAY_URLS = ['wss://relay.kehto.dev'] as const;\nconst DEFAULT_CONFIG_VALUES: JsonRecord = {\n runtime: 'kehto paja',\n mode: 'development',\n target: 'single-window',\n};\n\nconst DEFAULT_THEME_VALUES: JsonRecord = {\n title: 'Kehto Paja',\n};\n\nexport function normalizePajaSimulation(\n raw: PajaSimulationRawOptions | undefined,\n): PajaSimulation {\n const domainOverrides = raw?.capabilities?.domains ?? {};\n const domains = Object.fromEntries(\n PAJA_SIMULATION_DOMAINS.map((domain) => [domain, domainOverrides[domain] ?? true]),\n ) as Record<PajaCapabilityDomain, boolean>;\n\n const relayMode = raw?.relay?.mode ?? (domains.relay ? 'memory' : 'disabled');\n if (!domains.relay && relayMode !== 'disabled') {\n throw new PajaSimulationError('Invalid simulation: relay.mode must be \"disabled\" when capabilities.domains.relay is false.');\n }\n if (!domains.outbox && relayMode !== 'disabled') {\n throw new PajaSimulationError('Invalid simulation: relay.mode must be \"disabled\" when capabilities.domains.outbox is false.');\n }\n if (relayMode === 'disabled') {\n domains.relay = false;\n domains.outbox = false;\n }\n\n const uploadMode = raw?.upload?.mode ?? (domains.upload ? 'memory' : 'disabled');\n if (!domains.upload && uploadMode !== 'disabled') {\n throw new PajaSimulationError('Invalid simulation: upload.mode must be \"disabled\" when capabilities.domains.upload is false.');\n }\n if (uploadMode === 'disabled') domains.upload = false;\n\n const intentEnabled = raw?.intent?.enabled ?? domains.intent;\n if (!domains.intent && intentEnabled) {\n throw new PajaSimulationError('Invalid simulation: intent.enabled must be false when capabilities.domains.intent is false.');\n }\n if (!intentEnabled) domains.intent = false;\n\n const mediaEnabled = raw?.media?.enabled ?? domains.media;\n if (!domains.media && mediaEnabled) {\n throw new PajaSimulationError('Invalid simulation: media.enabled must be false when capabilities.domains.media is false.');\n }\n if (!mediaEnabled) domains.media = false;\n\n const cvmEnabled = raw?.cvm?.enabled ?? domains.cvm;\n if (!domains.cvm && cvmEnabled) {\n throw new PajaSimulationError('Invalid simulation: cvm.enabled must be false when capabilities.domains.cvm is false.');\n }\n if (!cvmEnabled) domains.cvm = false;\n\n const notificationsEnabled = raw?.notifications?.enabled ?? domains.notify;\n if (!domains.notify && notificationsEnabled) {\n throw new PajaSimulationError('Invalid simulation: notifications.enabled must be false when capabilities.domains.notify is false.');\n }\n if (!notificationsEnabled) domains.notify = false;\n\n const storageMode = raw?.storage?.mode ?? (domains.storage ? 'local' : 'disabled');\n if (!domains.storage && storageMode !== 'disabled') {\n throw new PajaSimulationError('Invalid simulation: storage.mode must be \"disabled\" when capabilities.domains.storage is false.');\n }\n if (storageMode === 'disabled') domains.storage = false;\n\n const identityMode = raw?.identity?.mode ?? 'anonymous';\n const pubkey = raw?.identity?.pubkey?.trim() ?? '';\n if (identityMode === 'fixed' && !isHexPubkey(pubkey)) {\n throw new PajaSimulationError('Invalid simulation: identity.pubkey must be a 64-character hex string when identity.mode is \"fixed\".');\n }\n\n const relayUrls = raw?.relay?.urls ?? DEFAULT_RELAY_URLS;\n if (relayMode === 'memory' && relayUrls.length === 0) {\n throw new PajaSimulationError('Invalid simulation: relay.urls must contain at least one URL when relay.mode is \"memory\".');\n }\n for (const url of relayUrls) {\n if (typeof url !== 'string' || url.trim().length === 0) {\n throw new PajaSimulationError('Invalid simulation: relay.urls entries must be non-empty strings.');\n }\n }\n\n const uploadRail = raw?.upload?.rail?.trim() || 'dev-memory';\n if (uploadMode === 'memory' && uploadRail.length === 0) {\n throw new PajaSimulationError('Invalid simulation: upload.rail must be non-empty when upload.mode is \"memory\".');\n }\n\n return {\n capabilities: {\n domains,\n disabledDomains: PAJA_SIMULATION_DOMAINS.filter((domain) => !domains[domain]),\n },\n acl: {\n mode: raw?.acl?.mode ?? 'allow',\n },\n firewall: {\n mode: raw?.firewall?.mode ?? 'observe',\n },\n identity: {\n mode: identityMode,\n pubkey: identityMode === 'fixed' ? pubkey : '',\n },\n relay: {\n mode: relayMode,\n urls: relayUrls.map((url) => url.trim()),\n fixtures: raw?.relay?.fixtures ?? [],\n },\n storage: {\n mode: storageMode,\n },\n cache: {\n mode: raw?.cache?.mode ?? 'memory',\n },\n upload: {\n mode: uploadMode,\n rail: uploadRail,\n },\n media: {\n enabled: mediaEnabled,\n },\n notifications: {\n enabled: notificationsEnabled,\n grant: raw?.notifications?.grant ?? true,\n },\n config: {\n values: { ...DEFAULT_CONFIG_VALUES, ...raw?.config?.values },\n },\n theme: {\n mode: raw?.theme?.mode ?? 'dark',\n values: { ...DEFAULT_THEME_VALUES, ...raw?.theme?.values },\n },\n intent: {\n enabled: intentEnabled,\n },\n cvm: {\n enabled: cvmEnabled,\n },\n };\n}\n\nexport function summarizePajaSimulation(simulation: PajaSimulation): string {\n const relay = simulation.relay.mode === 'memory' ? `relay:${simulation.relay.urls.length}` : 'relay:off';\n const identity = simulation.identity.mode === 'fixed' ? 'identity:fixed' : 'identity:anon';\n const storage = `storage:${simulation.storage.mode}`;\n const theme = `theme:${simulation.theme.mode}`;\n const disabled = simulation.capabilities.disabledDomains.length > 0\n ? `off:${simulation.capabilities.disabledDomains.join(',')}`\n : 'off:none';\n return `${identity} ${relay} ${storage} ${theme} ${disabled}`;\n}\n\nfunction isHexPubkey(value: string): boolean {\n return /^[0-9a-fA-F]{64}$/.test(value);\n}\n","import type { NostrEvent, NostrFilter } from '@napplet/core';\nimport {\n buildShellCapabilities,\n createShellBridge,\n originRegistry,\n type RelayPoolHooks,\n type RelayPoolLike,\n type ServiceHandler,\n type ShellAdapter,\n type ShellBridge,\n type ShellCapabilities,\n} from '@kehto/shell';\nimport {\n createConfigService,\n createCvmService,\n createIdentityService,\n createIntentService,\n createKeysService,\n createMediaService,\n createNotificationService,\n createNotifyService,\n createOutboxService,\n createRelayPoolService,\n createResourceService,\n createThemeService,\n createUploadService,\n type CvmServer,\n type CvmTransport,\n type IntentAvailability,\n type IntentRequest,\n type IntentResult,\n type McpMessage,\n type Uploader,\n type UploadRequest,\n type UploadResult,\n type UploadStatus,\n} from '@kehto/services';\nimport type { Theme } from '@napplet/nap/theme/types';\n\nimport type { PajaHostConfig } from './options.js';\nimport {\n summarizePajaSimulation,\n type PajaSimulation,\n} from './simulation.js';\n\ninterface PajaBrowserState {\n readonly config: PajaHostConfig;\n readonly capabilities: ShellCapabilities;\n readonly services: string[];\n simulation: PajaSimulation;\n generation: number;\n status: 'booting' | 'ready' | 'reloading' | 'error';\n reload(): void;\n setThemeMode(mode: PajaSimulation['theme']['mode']): void;\n getState(): {\n generation: number;\n status: PajaBrowserState['status'];\n iframeCount: number;\n initSent: boolean;\n services: string[];\n simulation: PajaSimulation;\n };\n}\n\ndeclare global {\n interface Window {\n __KEHTO_PAJA__?: PajaBrowserState;\n }\n}\n\nconst DEV_INTENT_ARCHETYPE = 'paja-target';\nconst DEV_CVM_SERVER: CvmServer = {\n pubkey: '0'.repeat(64),\n name: 'Kehto Paja ContextVM',\n description: 'Deterministic development ContextVM adapter',\n relays: ['wss://relay.kehto.dev'],\n capabilities: ['echo'],\n};\n\nfunction readConfig(): PajaHostConfig {\n const script = document.getElementById('kehto-paja-config');\n if (!script?.textContent) {\n throw new Error('Missing Kehto Paja config.');\n }\n return JSON.parse(script.textContent) as PajaHostConfig;\n}\n\nfunction setStatus(state: PajaBrowserState, status: PajaBrowserState['status']): void {\n state.status = status;\n const statusEl = document.getElementById('lifecycle-status');\n if (statusEl) statusEl.textContent = status;\n}\n\nfunction setSimulationStatus(state: PajaBrowserState): void {\n const statusEl = document.getElementById('simulation-status');\n if (statusEl) statusEl.textContent = summarizePajaSimulation(state.simulation);\n const themeSelect = document.getElementById('simulation-theme');\n if (themeSelect instanceof HTMLSelectElement) themeSelect.value = state.simulation.theme.mode;\n}\n\nfunction getFrame(): HTMLIFrameElement {\n const frame = document.getElementById('napplet-frame');\n if (!(frame instanceof HTMLIFrameElement)) {\n throw new Error('Missing Kehto Paja iframe.');\n }\n frame.sandbox.add('allow-scripts');\n frame.sandbox.remove('allow-same-origin');\n return frame;\n}\n\nfunction matchesFilter(event: NostrEvent, filter: NostrFilter): boolean {\n const ids = filter.ids;\n if (ids && !ids.includes(event.id)) return false;\n const authors = filter.authors;\n if (authors && !authors.includes(event.pubkey)) return false;\n const kinds = filter.kinds;\n if (kinds && !kinds.includes(event.kind)) return false;\n return true;\n}\n\nfunction matchesAnyFilter(event: NostrEvent, filters: NostrFilter[]): boolean {\n return filters.length === 0 || filters.some((filter) => matchesFilter(event, filter));\n}\n\nfunction createMemoryRelayPool(getSimulation: () => PajaSimulation): RelayPoolLike {\n const events: NostrEvent[] = getSimulation().relay.fixtures.flatMap(toNostrEvent);\n const subscribers = new Set<{\n filters: NostrFilter[];\n next(item: NostrEvent | 'EOSE'): void;\n }>();\n\n return {\n subscription(_relayUrls: string[], filters: NostrFilter[]) {\n return {\n subscribe(next: (item: unknown) => void) {\n const subscriber = {\n filters,\n next: (item: NostrEvent | 'EOSE') => next(item),\n };\n subscribers.add(subscriber);\n for (const event of events) {\n if (matchesAnyFilter(event, filters)) next(event);\n }\n queueMicrotask(() => {\n if (subscribers.has(subscriber)) next('EOSE');\n });\n return {\n unsubscribe() {\n subscribers.delete(subscriber);\n },\n };\n },\n };\n },\n publish(_relayUrls: string[], event: NostrEvent): void {\n if (getSimulation().relay.mode === 'disabled') return;\n events.push(event);\n for (const subscriber of subscribers) {\n if (matchesAnyFilter(event, subscriber.filters)) subscriber.next(event);\n }\n },\n request(_relayUrls: string[], filters: NostrFilter[]) {\n return {\n subscribe(observer: { next: (event: unknown) => void; complete: () => void; error: () => void }) {\n for (const event of events) {\n if (matchesAnyFilter(event, filters)) observer.next(event);\n }\n queueMicrotask(() => observer.complete());\n return { unsubscribe() { /* no-op */ } };\n },\n };\n },\n };\n}\n\nfunction toNostrEvent(value: unknown): NostrEvent[] {\n if (\n typeof value === 'object'\n && value !== null\n && typeof (value as { id?: unknown }).id === 'string'\n && typeof (value as { pubkey?: unknown }).pubkey === 'string'\n && typeof (value as { kind?: unknown }).kind === 'number'\n && Array.isArray((value as { tags?: unknown }).tags)\n && typeof (value as { content?: unknown }).content === 'string'\n && typeof (value as { sig?: unknown }).sig === 'string'\n ) {\n return [value as NostrEvent];\n }\n return [];\n}\n\nfunction getRelayUrls(simulation: PajaSimulation): string[] {\n return simulation.relay.mode === 'memory' ? [...simulation.relay.urls] : [];\n}\n\nfunction createRelayHooks(pool: RelayPoolLike, getSimulation: () => PajaSimulation): RelayPoolHooks {\n const cleanups = new Map<string, () => void>();\n return {\n getRelayPool: () => pool,\n trackSubscription(subKey, cleanup) {\n cleanups.set(subKey, cleanup);\n },\n untrackSubscription(subKey) {\n cleanups.get(subKey)?.();\n cleanups.delete(subKey);\n },\n openScopedRelay: () => {},\n closeScopedRelay: () => {},\n publishToScopedRelay: (_windowId, event) => {\n if (getSimulation().relay.mode === 'disabled') return false;\n pool.publish(getRelayUrls(getSimulation()), event);\n return true;\n },\n selectRelayTier: () => getRelayUrls(getSimulation()),\n };\n}\n\nfunction createWorkerRelay(events: NostrEvent[]) {\n return {\n event(event: NostrEvent) {\n events.push(event);\n return Promise.resolve({ ok: true });\n },\n query(req: unknown): Promise<NostrEvent[]> {\n const filters = Array.isArray(req) ? req.slice(2).filter((item): item is NostrFilter => typeof item === 'object' && item !== null) : [];\n return Promise.resolve(events.filter((event) => matchesAnyFilter(event, filters)));\n },\n count(req: unknown): Promise<number> {\n return this.query(req).then((matched) => matched.length);\n },\n };\n}\n\nfunction createDevUploader(getSimulation: () => PajaSimulation): Uploader {\n return {\n async upload(request: UploadRequest, ctx): Promise<UploadResult> {\n const simulation = getSimulation();\n if (simulation.upload.mode === 'disabled') {\n throw new Error('upload simulation is disabled');\n }\n const size = request.data instanceof Blob ? request.data.size : request.data.byteLength;\n const result: UploadResult = {\n ok: true,\n uploadId: ctx.uploadId,\n status: 'complete',\n rail: request.rail ?? simulation.upload.rail,\n url: `kehto-dev://${simulation.upload.rail}/${ctx.uploadId}`,\n size,\n mimeType: request.mimeType ?? (request.data instanceof Blob ? request.data.type : undefined),\n };\n ctx.onStatus({ ...result, updatedAt: Date.now() });\n return result;\n },\n async status(uploadId: string): Promise<UploadStatus> {\n const simulation = getSimulation();\n return {\n ok: simulation.upload.mode !== 'disabled',\n uploadId,\n status: simulation.upload.mode === 'disabled' ? 'failed' : 'complete',\n rail: simulation.upload.rail,\n url: `kehto-dev://${simulation.upload.rail}/${uploadId}`,\n updatedAt: Date.now(),\n };\n },\n };\n}\n\nfunction createDevIntentAvailability(): IntentAvailability {\n return {\n archetype: DEV_INTENT_ARCHETYPE,\n available: true,\n hasDefault: true,\n candidates: [{\n dTag: 'dev-target',\n title: 'Dev runtime target',\n actions: ['open'],\n protocols: ['NAP-01'],\n isDefault: true,\n }],\n };\n}\n\nfunction createDevCvmTransport(getSimulation: () => PajaSimulation): CvmTransport {\n return {\n async discover() {\n if (!getSimulation().cvm.enabled) return [];\n return [{ ...DEV_CVM_SERVER, relays: getRelayUrls(getSimulation()) }];\n },\n async request(_server, message): Promise<McpMessage> {\n const id = typeof message.id === 'string' || typeof message.id === 'number' ? message.id : 'paja';\n return {\n jsonrpc: '2.0',\n id,\n result: {\n echoed: true,\n method: typeof message.method === 'string' ? message.method : null,\n },\n };\n },\n async close() {},\n onEvent() {\n return {\n close() {},\n };\n },\n };\n}\n\nfunction createOutboxRouter(pool: RelayPoolLike, getSimulation: () => PajaSimulation) {\n return {\n async query(filters: NostrFilter[]) {\n const relayUrls = getRelayUrls(getSimulation());\n const events = await new Promise<NostrEvent[]>((resolve) => {\n const out: NostrEvent[] = [];\n pool.request(relayUrls, filters).subscribe({\n next: (event) => out.push(event as NostrEvent),\n complete: () => resolve(out),\n error: () => resolve(out),\n });\n });\n return { events, relays: Object.fromEntries(events.map((event) => [event.id, relayUrls])) };\n },\n subscribe(filters: NostrFilter[], _options: unknown, sink: { event(event: NostrEvent, relay?: string): void; eose(): void; closed(reason?: string): void }) {\n const relayUrls = getRelayUrls(getSimulation());\n const sub = pool.subscription(relayUrls, filters).subscribe((item) => {\n if (item === 'EOSE') sink.eose();\n else sink.event(item as NostrEvent, relayUrls[0]);\n });\n return { close: () => sub.unsubscribe() };\n },\n async publish(template: Partial<NostrEvent>) {\n const event = {\n id: crypto.randomUUID().replace(/-/g, '').padEnd(64, '0').slice(0, 64),\n pubkey: '0'.repeat(64),\n created_at: Math.floor(Date.now() / 1000),\n kind: 1,\n tags: [],\n content: '',\n sig: '0'.repeat(128),\n ...template,\n } as NostrEvent;\n const relayUrls = getRelayUrls(getSimulation());\n pool.publish(relayUrls, event);\n return { ok: true, event, eventId: event.id, relays: { [relayUrls[0] ?? 'dev']: true } };\n },\n async resolveRelays() {\n return { relays: getRelayUrls(getSimulation()), source: 'policy' as const };\n },\n };\n}\n\nfunction createDevTheme(mode: PajaSimulation['theme']['mode'], values: PajaSimulation['theme']['values']): Theme {\n const defaultColors = mode === 'light'\n ? { background: '#f7f5ed', text: '#1d211d', primary: '#6a5a12' }\n : { background: '#101211', text: '#f4f0df', primary: '#d8c36a' };\n return {\n ...values,\n colors: {\n ...defaultColors,\n ...((typeof values.colors === 'object' && values.colors !== null && !Array.isArray(values.colors)) ? values.colors : {}),\n },\n } as Theme;\n}\n\nfunction createDevSigner(getSimulation: () => PajaSimulation) {\n return {\n getPublicKey: () => getSimulation().identity.pubkey,\n getRelays: () => Object.fromEntries(getRelayUrls(getSimulation()).map((relay) => [relay, { read: true, write: true }])),\n };\n}\n\nfunction createDevServices(\n pool: RelayPoolLike,\n getSimulation: () => PajaSimulation,\n onThemeService: (theme: ReturnType<typeof createThemeService>) => void,\n): Record<string, ServiceHandler> {\n const notification = createNotificationService({ maxPerWindow: 50 });\n const theme = createThemeService({\n initialTheme: createDevTheme(getSimulation().theme.mode, getSimulation().theme.values),\n onBroadcast: () => {},\n });\n onThemeService(theme);\n const config = createConfigService({\n getValues: () => ({\n ...getSimulation().config.values,\n simulation: {\n identity: getSimulation().identity.mode,\n relay: getSimulation().relay.mode,\n storage: getSimulation().storage.mode,\n cache: getSimulation().cache.mode,\n upload: getSimulation().upload.mode,\n theme: getSimulation().theme.mode,\n },\n }),\n });\n const services: Record<string, ServiceHandler> = {\n keys: createKeysService(),\n resource: createResourceService({\n fetch: (url, init) => fetch(url, init),\n isOriginGranted: () => true,\n getConnectGrants: () => ['*'],\n resolveIdentity: () => ({ dTag: 'dev-target', aggregateHash: 'paja' }),\n }),\n };\n\n if (getSimulation().relay.mode === 'memory') {\n services.relay = createRelayPoolService({\n subscribe: (filters, callback, relayUrls) => pool.subscription(relayUrls ?? getRelayUrls(getSimulation()), filters).subscribe((item) => {\n if (item === 'EOSE' || (typeof item === 'object' && item !== null)) {\n callback(item as NostrEvent | 'EOSE');\n }\n }),\n publish: (event) => pool.publish(getRelayUrls(getSimulation()), event),\n selectRelayTier: () => getRelayUrls(getSimulation()),\n isAvailable: () => getSimulation().relay.mode === 'memory',\n });\n services.outbox = createOutboxService({ router: createOutboxRouter(pool, getSimulation) });\n }\n\n if (getSimulation().identity.mode === 'fixed') {\n services.identity = createIdentityService({ getSigner: () => createDevSigner(getSimulation) });\n } else if (getSimulation().capabilities.domains.identity) {\n services.identity = createIdentityService({ getSigner: () => null });\n }\n if (getSimulation().notifications.enabled) {\n services.notifications = notification;\n services.notify = createNotifyService({ defaultGrant: getSimulation().notifications.grant });\n }\n if (getSimulation().media.enabled) services.media = createMediaService();\n if (getSimulation().capabilities.domains.theme) services.theme = theme.handler;\n if (getSimulation().capabilities.domains.config) services.config = config.handler;\n if (getSimulation().cvm.enabled) services.cvm = createCvmService({ transport: createDevCvmTransport(getSimulation) });\n if (getSimulation().upload.mode === 'memory') services.upload = createUploadService({ uploader: createDevUploader(getSimulation) });\n if (getSimulation().intent.enabled) {\n services.intent = createIntentService({\n resolver: {\n invoke(request: IntentRequest): IntentResult {\n return {\n ok: true,\n archetype: request.archetype,\n action: request.action ?? 'open',\n handled: request.archetype === DEV_INTENT_ARCHETYPE,\n handler: 'dev-target',\n windowId: 'kehto-paja-window',\n protocol: request.protocol ?? 'NAP-01',\n };\n },\n available: (archetype) => ({\n ...createDevIntentAvailability(),\n archetype,\n available: archetype === DEV_INTENT_ARCHETYPE,\n }),\n handlers: () => [createDevIntentAvailability()],\n },\n });\n }\n\n return services;\n}\n\nfunction createPajaAdapter(\n config: PajaHostConfig,\n getSimulation: () => PajaSimulation,\n onThemeService: (theme: ReturnType<typeof createThemeService>) => void,\n): ShellAdapter {\n const pool = createMemoryRelayPool(getSimulation);\n const workerRelayEvents: NostrEvent[] = [];\n return {\n relayPool: createRelayHooks(pool, getSimulation),\n relayConfig: {\n addRelay: () => {},\n removeRelay: () => {},\n getRelayConfig: () => {\n const relays = getRelayUrls(getSimulation());\n return { discovery: relays, super: relays, outbox: relays };\n },\n getNip66Suggestions: () => getRelayUrls(getSimulation()),\n },\n windowManager: { createWindow: () => null },\n auth: {\n getUserPubkey: () => getSimulation().identity.pubkey,\n getSigner: () => (getSimulation().identity.mode === 'fixed' ? createDevSigner(getSimulation) : null),\n },\n services: createDevServices(pool, getSimulation, onThemeService),\n capabilities: { disabledDomains: getSimulation().capabilities.disabledDomains },\n config: { getNappUpdateBehavior: () => 'auto-grant' },\n hotkeys: { executeHotkeyFromForward: () => {} },\n workerRelay: { getWorkerRelay: () => createWorkerRelay(workerRelayEvents) },\n upload: getSimulation().upload.mode === 'memory' ? { getUploader: () => ({ rails: [getSimulation().upload.rail] }) } : undefined,\n intent: { isAvailable: () => getSimulation().intent.enabled },\n crypto: {\n verifyEvent: async () => true,\n },\n onNip5dIframeCreate: () => ({\n dTag: config.window.dTag,\n aggregateHash: config.window.aggregateHash,\n }),\n };\n}\n\nfunction registerFrameForGeneration(bridge: ShellBridge, frame: HTMLIFrameElement, config: PajaHostConfig, generation: number): string | null {\n const win = frame.contentWindow;\n if (!win) return null;\n const windowId = `${config.window.id}:${generation}`;\n originRegistry.register(win, windowId, {\n dTag: config.window.dTag,\n aggregateHash: config.window.aggregateHash,\n });\n return windowId;\n}\n\nfunction navigateFrame(bridge: ShellBridge, frame: HTMLIFrameElement, config: PajaHostConfig, generation: number): string | null {\n const windowId = registerFrameForGeneration(bridge, frame, config, generation);\n frame.src = 'about:blank';\n window.setTimeout(() => {\n registerFrameForGeneration(bridge, frame, config, generation);\n frame.src = config.target.url;\n }, 0);\n return windowId;\n}\n\nfunction installPajaHost(): void {\n const config = readConfig();\n const frame = getFrame();\n let currentSimulation = config.simulation;\n const getSimulation = () => currentSimulation;\n let themeService: ReturnType<typeof createThemeService> | null = null;\n const adapter = createPajaAdapter(config, getSimulation, (theme) => {\n themeService = theme;\n });\n const bridge = createShellBridge(adapter);\n const capabilities = buildShellCapabilities(adapter);\n const services = Object.keys(adapter.services ?? {}).sort();\n let currentWindowId: string | null = null;\n let initReceivedGeneration = -1;\n\n const state: PajaBrowserState = {\n config,\n capabilities,\n services,\n simulation: currentSimulation,\n generation: 0,\n status: 'booting',\n reload() {\n if (currentWindowId) {\n bridge.runtime.destroyWindow(currentWindowId);\n bridge.runtime.sessionRegistry.unregister(currentWindowId);\n originRegistry.unregister(currentWindowId);\n }\n this.generation += 1;\n initReceivedGeneration = -1;\n setStatus(this, 'reloading');\n currentWindowId = navigateFrame(bridge, frame, config, this.generation);\n },\n setThemeMode(mode) {\n currentSimulation = {\n ...currentSimulation,\n theme: {\n ...currentSimulation.theme,\n mode,\n },\n };\n this.simulation = currentSimulation;\n themeService?.publishTheme(createDevTheme(currentSimulation.theme.mode, currentSimulation.theme.values));\n setSimulationStatus(this);\n },\n getState() {\n return {\n generation: this.generation,\n status: this.status,\n iframeCount: document.querySelectorAll('iframe').length,\n initSent: initReceivedGeneration === this.generation,\n services,\n simulation: currentSimulation,\n };\n },\n };\n\n window.__KEHTO_PAJA__ = state;\n\n window.addEventListener('message', (event) => {\n if (event.source !== frame.contentWindow) return;\n bridge.handleMessage(event);\n const data = event.data as { type?: unknown } | null;\n if (data && typeof data === 'object' && data.type === 'shell.ready') {\n initReceivedGeneration = state.generation;\n setStatus(state, 'ready');\n }\n });\n\n frame.addEventListener('load', () => {\n if (state.status === 'booting' || state.status === 'reloading') {\n registerFrameForGeneration(bridge, frame, config, state.generation);\n }\n });\n\n frame.addEventListener('error', () => {\n setStatus(state, 'error');\n });\n\n document.getElementById('reload-target')?.addEventListener('click', () => {\n state.reload();\n });\n\n document.getElementById('simulation-theme')?.addEventListener('change', (event) => {\n const target = event.target;\n if (!(target instanceof HTMLSelectElement)) return;\n if (target.value === 'dark' || target.value === 'light') {\n state.setThemeMode(target.value);\n }\n });\n\n setStatus(state, 'booting');\n setSimulationStatus(state);\n currentWindowId = navigateFrame(bridge, frame, config, state.generation);\n}\n\ntry {\n installPajaHost();\n} catch (error) {\n const statusEl = document.getElementById('lifecycle-status');\n if (statusEl) statusEl.textContent = 'error';\n console.error(error);\n}\n"],"mappings":"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","names":["toKey","createState","serialize","deserialize","registerNap","dispatch","getRegisteredDomains","byWindowId","byPubkey","pendingUpdates","CAP_IDENTITY_READ","CAP_KEYS_BIND","CAP_KEYS_FORWARD","CAP_MEDIA_CONTROL","CAP_NOTIFY_SEND","CAP_NOTIFY_CHANNEL","CAP_THEME_READ","CAP_CONFIG_READ","CAP_RESOURCE_FETCH","CAP_CVM_CALL","CAP_OUTBOX_READ","CAP_OUTBOX_WRITE","CAP_UPLOAD_WRITE","CAP_INTENT_READ","CAP_INTENT_WRITE","createState","serialize","deserialize","cache","cacheKey","sessionRegistry","cache","sessionRegistry","byPubkey","sessionRegistry","manifestCache","originRegistry","scopedKey","STORAGE_KEY","store","handleMediaMessage","handleSubscribe","generateId","toErrorMessage","toErrorMessage","toErrorMessage","matchesFilter","matchesAnyFilter"]}
|
|
1
|
+
{"version":3,"sources":["../../acl/src/capabilities.ts","../../acl/src/types.ts","../../acl/src/check.ts","../../acl/src/mutations.ts","../../acl/src/migrate.ts","../../acl/src/resolve.ts","../../firewall/src/evaluate.ts","../../firewall/src/defaults.ts","../../firewall/src/config.ts","../../../node_modules/.pnpm/@napplet+core@0.21.0/node_modules/@napplet/core/src/envelope.ts","../../../node_modules/.pnpm/@napplet+core@0.21.0/node_modules/@napplet/core/src/dispatch.ts","../../../node_modules/.pnpm/@napplet+core@0.21.0/node_modules/@napplet/core/src/topics.ts","../../../node_modules/.pnpm/@napplet+core@0.21.0/node_modules/@napplet/core/src/boundary.ts","../../runtime/src/enforce.ts","../../runtime/src/session-registry.ts","../../runtime/src/acl-state.ts","../../runtime/src/firewall-state.ts","../../runtime/src/manifest-cache.ts","../../runtime/src/replay.ts","../../runtime/src/event-buffer.ts","../../runtime/src/runtime.ts","../../runtime/src/service-dispatch.ts","../../runtime/src/relay-handler.ts","../../runtime/src/identity-handler.ts","../../runtime/src/inc-handler.ts","../../runtime/src/state-handler.ts","../../runtime/src/domain-handlers.ts","../../runtime/src/index.ts","../../shell/src/shell-bridge.ts","../../shell/src/hooks-adapter.ts","../../shell/src/origin-registry.ts","../../shell/src/session-registry.ts","../../shell/src/acl-store.ts","../../shell/src/manifest-cache.ts","../../shell/src/audio-manager.ts","../../shell/src/keys-forwarder.ts","../../shell/src/shell-init.ts","../../shell/src/shell-ready.ts","../../shell/src/index.ts","../../shell/src/topics.ts","../../shell/src/identity-proxy.ts","../../shell/src/theme-proxy.ts","../../shell/src/keys-proxy.ts","../../shell/src/media-proxy.ts","../../shell/src/notify-proxy.ts","../../../node_modules/.pnpm/@noble+hashes@2.0.1/node_modules/@noble/hashes/src/utils.ts","../../../node_modules/.pnpm/@scure+base@2.0.0/node_modules/@scure/base/index.ts","../../../node_modules/.pnpm/nostr-tools@2.23.3_typescript@5.9.3/node_modules/nostr-tools/lib/esm/nip19.js","../../services/src/audio-service.ts","../../services/src/notification-service.ts","../../services/src/identity-service.ts","../../services/src/relay-pool-service.ts","../../services/src/cache-service.ts","../../services/src/coordinated-relay.ts","../../services/src/keys-service.ts","../../services/src/browser-media-bridge.ts","../../services/src/media-service.ts","../../services/src/notify-service.ts","../../services/src/theme-service.ts","../../services/src/config-service.ts","../../services/src/resource-service.ts","../../services/src/outbox-service.ts","../../services/src/relay-pool-outbox-router.ts","../../services/src/upload-service.ts","../../services/src/http-uploader.ts","../../services/src/intent-service.ts","../../services/src/catalog-intent-resolver.ts","../../services/src/manifest-intent-catalog.ts","../../services/src/cvm-service.ts","../../services/src/link-service.ts","../../services/src/lists-service.ts","../../services/src/serial-service.ts","../../services/src/ble-service.ts","../../services/src/webrtc-service.ts","../../services/src/common-service.ts","../src/simulation.ts","../src/development-services.ts","../src/browser-host.ts"],"sourcesContent":["\n/**\n * All capability strings recognized by @kehto/acl.\n *\n * Ordering: v1.1 surface first (relay/cache/hotkey/state), then the\n * v1.2 additions for the seven naps + theme. The v1.1 `sign:event`,\n * `sign:nip04`, `sign:nip44` strings were intentionally removed — no\n * napplet-visible signing exists in canonical NIP-5D; signing flows\n * through shell-internal `relay.publishEncrypted` instead.\n */\nexport const ALL_CAPABILITIES = [\n // v1.1 kept:\n 'relay:read', 'relay:write',\n 'cache:read', 'cache:write',\n 'hotkey:forward',\n 'state:read', 'state:write',\n // v1.2 additions (seven naps + theme):\n 'identity:read',\n 'keys:bind', 'keys:forward',\n 'media:control',\n 'notify:send', 'notify:channel',\n 'theme:read',\n // v1.7 Phase 39 — NAP-CONFIG reference service (9th domain):\n 'config:read',\n // v1.7 Phase 40 — NAP-RESOURCE reference service (10th domain):\n 'resource:fetch',\n // NAP-CVM — ContextVM bridge (11th domain): call MCP-over-Nostr servers.\n 'cvm:call',\n // NAP-OUTBOX — outbox-aware relay routing (12th domain): read = query/\n // subscribe/resolveRelays/close; write = publish (shell-signed fanout).\n 'outbox:read', 'outbox:write',\n // NAP-UPLOAD — shell-mediated file/blob upload (13th domain): a single write\n // cap gates the network-egress + identity-linking upload op; status queries\n // ride the same grant (a napplet only inspects its own uploads).\n 'upload:write',\n // NAP-INTENT — archetype intent dispatch (14th domain): read = available/\n // handlers introspection (and receipt of shell pushes); write = invoke (the\n // focus-stealing cross-napplet dispatch op).\n 'intent:read', 'intent:write',\n] as const;\n\n/** Union of every capability string in ALL_CAPABILITIES. */\nexport type Capability = typeof ALL_CAPABILITIES[number];\n\n/** identity.getProfile/getFollows/getList/getZaps/getMutes/getBlocked/getBadges */\nexport const CAP_IDENTITY_READ = 'identity:read' as const;\n/** keys.registerAction / keys.unregisterAction / keys.bindings */\nexport const CAP_KEYS_BIND = 'keys:bind' as const;\n/** keys.forward / keys.action */\nexport const CAP_KEYS_FORWARD = 'keys:forward' as const;\n/** media.* (all actions) */\nexport const CAP_MEDIA_CONTROL = 'media:control' as const;\n/** notify.send / notify.dismiss / notify.badge / notify.action / notify.clicked / notify.dismissed / notify.controls / notify.send.result */\nexport const CAP_NOTIFY_SEND = 'notify:send' as const;\n/** notify.channel.register / notify.permission.request / notify.permission.result */\nexport const CAP_NOTIFY_CHANNEL = 'notify:channel' as const;\n/** theme.get / theme.changed */\nexport const CAP_THEME_READ = 'theme:read' as const;\n/** config.get / config.subscribe / config.unsubscribe / config.registerSchema / config.openSettings */\nexport const CAP_CONFIG_READ = 'config:read' as const;\n/** resource.bytes / resource.bytesMany / resource.cancel (inbound) + resource result/error pushes */\nexport const CAP_RESOURCE_FETCH = 'resource:fetch' as const;\n/** cvm.discover / cvm.request / cvm.close (inbound) + cvm.*.result / cvm.event (outbound) */\nexport const CAP_CVM_CALL = 'cvm:call' as const;\n/** outbox.query / outbox.subscribe / outbox.close / outbox.resolveRelays (read-side outbox access) */\nexport const CAP_OUTBOX_READ = 'outbox:read' as const;\n/** outbox.publish (shell-signed, outbox-aware publish fanout) */\nexport const CAP_OUTBOX_WRITE = 'outbox:write' as const;\n/** upload.upload / upload.status (shell-mediated file/blob upload + status query) */\nexport const CAP_UPLOAD_WRITE = 'upload:write' as const;\n/** intent.available / intent.handlers (read-side archetype introspection + push receipt) */\nexport const CAP_INTENT_READ = 'intent:read' as const;\n/** intent.invoke (focus-stealing cross-napplet archetype dispatch) */\nexport const CAP_INTENT_WRITE = 'intent:write' as const;\n","/**\n * @kehto/acl — Type definitions and capability bit constants.\n *\n * All types use Readonly<> to enforce immutability at the type level.\n * Capability constants are bitfield values for fast check/grant/revoke.\n */\n\n/** relay:read — subscribe to relay events */\nexport const CAP_RELAY_READ = 1 << 0; // 1\n/** relay:write — publish events to relays */\nexport const CAP_RELAY_WRITE = 1 << 1; // 2\n/** cache:read — read from local cache */\nexport const CAP_CACHE_READ = 1 << 2; // 4\n/** cache:write — write to local cache */\nexport const CAP_CACHE_WRITE = 1 << 3; // 8\n/** hotkey:forward — forward keyboard shortcuts to shell */\nexport const CAP_HOTKEY_FORWARD = 1 << 4; // 16\n/** sign:event — request event signing */\nexport const CAP_SIGN_EVENT = 1 << 5; // 32\n/** sign:nip04 — request NIP-04 encrypt/decrypt */\nexport const CAP_SIGN_NIP04 = 1 << 6; // 64\n/** sign:nip44 — request NIP-44 encrypt/decrypt */\nexport const CAP_SIGN_NIP44 = 1 << 7; // 128\n/** state:read — read napplet-scoped state */\nexport const CAP_STATE_READ = 1 << 8; // 256\n/** state:write — write napplet-scoped state */\nexport const CAP_STATE_WRITE = 1 << 9; // 512\n\n/** All capabilities granted (bits 0-9 set) */\nexport const CAP_ALL = (1 << 10) - 1; // 1023\n\n/** No capabilities granted */\nexport const CAP_NONE = 0;\n\n/**\n * Napplet identity — composite key for ACL lookups.\n *\n * Under NIP-5D v0.1.0, identity is assigned from the NIP-5A manifest\n * at iframe creation time. The pubkey field is no longer used.\n *\n * @param pubkey - (deprecated) Ephemeral AUTH keypair pubkey. Ignored by toKey().\n * @param dTag - Derived tag (deterministic from napp type)\n * @param hash - Aggregate hash of napplet build artifacts\n */\nexport interface Identity {\n /** @deprecated NIP-5D: AUTH keypair no longer exists. Pass '' or omit entirely.\n * Kept as optional for backward compatibility during data migration. */\n readonly pubkey?: string;\n readonly dTag: string;\n readonly hash: string;\n}\n\n/**\n * A single ACL entry for one napplet identity.\n *\n * @param caps - Bitfield of granted capabilities (use CAP_* constants)\n * @param blocked - Orthogonal block flag; when true, all checks fail regardless of caps\n * @param quota - State storage quota in bytes\n */\nexport interface AclEntry {\n readonly caps: number;\n readonly blocked: boolean;\n readonly quota: number;\n}\n\n/**\n * Complete ACL state — immutable data structure.\n *\n * All mutations return a new AclState; the original is never modified.\n *\n * @param defaultPolicy - 'permissive' grants all caps to unknown identities;\n * 'restrictive' denies all caps to unknown identities\n * @param entries - Map from composite key ('dTag:hash') to AclEntry\n */\nexport interface AclState {\n readonly defaultPolicy: 'permissive' | 'restrictive';\n readonly entries: Readonly<Record<string, AclEntry>>;\n}\n\n/** Default state storage quota in bytes (512 KB) */\nexport const DEFAULT_QUOTA = 512 * 1024;\n","/**\n * @kehto/acl — Pure check function.\n *\n * Determines whether an identity has a specific capability.\n * No side effects, no I/O, no mutations.\n */\n\nimport type { AclState, Identity } from './types.js';\n\n/**\n * Compute composite key from identity fields.\n *\n * Under NIP-5D v0.1.0, the key is 'dTag:hash' (pubkey is ignored).\n *\n * @param identity - Napplet identity\n * @returns Composite key string 'dTag:hash'\n *\n * @example\n * ```ts\n * toKey({ dTag: 'chat', hash: 'ff00' })\n * // => 'chat:ff00'\n * ```\n */\nexport function toKey(identity: Identity): string {\n return `${identity.dTag}:${identity.hash}`;\n}\n\n/**\n * Check whether an identity has a specific capability.\n *\n * Decision logic:\n * 1. If identity has no entry: return based on defaultPolicy\n * - 'permissive' → true (all caps granted to unknown identities)\n * - 'restrictive' → false (all caps denied to unknown identities)\n * 2. If identity is blocked: return false (blocked overrides all caps)\n * 3. Otherwise: return (entry.caps & cap) !== 0\n *\n * @param state - Current ACL state (immutable)\n * @param identity - Napplet identity to check\n * @param cap - Capability bit constant (e.g., CAP_RELAY_READ)\n * @returns true if the identity has the capability, false otherwise\n *\n * @example\n * ```ts\n * import { check, createState, grant } from '@kehto/acl';\n * import { CAP_RELAY_READ } from '@kehto/acl';\n *\n * const state = createState('restrictive');\n * const id = { dTag: 'chat', hash: 'ff00' };\n *\n * check(state, id, CAP_RELAY_READ); // false (restrictive, no entry)\n *\n * const state2 = grant(state, id, CAP_RELAY_READ);\n * check(state2, id, CAP_RELAY_READ); // true\n * ```\n */\nexport function check(state: AclState, identity: Identity, cap: number): boolean {\n const key = toKey(identity);\n const entry = state.entries[key];\n if (!entry) {\n return state.defaultPolicy === 'permissive';\n }\n if (entry.blocked) {\n return false;\n }\n return (entry.caps & cap) !== 0;\n}\n","/**\n * @kehto/acl — Pure state mutation functions.\n *\n * Every function takes an AclState and returns a NEW AclState.\n * The original state is never modified. No side effects, no I/O.\n */\n\nimport { CAP_ALL, DEFAULT_QUOTA, type AclEntry, type AclState, type Identity } from './types.js';\nimport { toKey } from './check.js';\n\n/**\n * Create a new ACL state with the given default policy.\n *\n * @param policy - 'permissive' grants all caps to unknown identities;\n * 'restrictive' denies all caps to unknown identities.\n * Defaults to 'permissive'.\n * @returns A new empty AclState\n *\n * @example\n * ```ts\n * const state = createState('restrictive');\n * // { defaultPolicy: 'restrictive', entries: {} }\n * ```\n */\nexport function createState(policy: 'permissive' | 'restrictive' = 'permissive'): AclState {\n return { defaultPolicy: policy, entries: {} };\n}\n\n/**\n * Get the entry for an identity, or a default entry based on policy.\n * Internal helper — not exported.\n */\nfunction getEntry(state: AclState, key: string): AclEntry {\n const existing = state.entries[key];\n if (existing) return existing;\n // Default entry: all caps if permissive, no caps if restrictive\n return {\n caps: state.defaultPolicy === 'permissive' ? CAP_ALL : 0,\n blocked: false,\n quota: DEFAULT_QUOTA,\n };\n}\n\n/**\n * Grant a capability to an identity.\n *\n * If the identity has no entry, one is created with default caps plus the granted cap.\n * Returns a new AclState — the original is not modified.\n *\n * @param state - Current ACL state\n * @param identity - Napplet identity\n * @param cap - Capability bit constant to grant (e.g., CAP_RELAY_READ)\n * @returns New AclState with the capability granted\n *\n * @example\n * ```ts\n * const state2 = grant(state, id, CAP_RELAY_READ);\n * check(state2, id, CAP_RELAY_READ); // true\n * ```\n */\nexport function grant(state: AclState, identity: Identity, cap: number): AclState {\n const key = toKey(identity);\n const entry = getEntry(state, key);\n return {\n ...state,\n entries: {\n ...state.entries,\n [key]: { ...entry, caps: entry.caps | cap },\n },\n };\n}\n\n/**\n * Revoke a capability from an identity.\n *\n * If the identity has no entry, one is created with default caps minus the revoked cap.\n * Returns a new AclState — the original is not modified.\n *\n * @param state - Current ACL state\n * @param identity - Napplet identity\n * @param cap - Capability bit constant to revoke (e.g., CAP_RELAY_WRITE)\n * @returns New AclState with the capability revoked\n *\n * @example\n * ```ts\n * const state2 = revoke(state, id, CAP_RELAY_WRITE);\n * check(state2, id, CAP_RELAY_WRITE); // false\n * ```\n */\nexport function revoke(state: AclState, identity: Identity, cap: number): AclState {\n const key = toKey(identity);\n const entry = getEntry(state, key);\n return {\n ...state,\n entries: {\n ...state.entries,\n [key]: { ...entry, caps: entry.caps & ~cap },\n },\n };\n}\n\n/**\n * Block an identity.\n *\n * A blocked identity fails all capability checks regardless of granted caps.\n * The caps bitfield is preserved — unblocking restores previous capabilities.\n * Returns a new AclState — the original is not modified.\n *\n * @param state - Current ACL state\n * @param identity - Napplet identity to block\n * @returns New AclState with the identity blocked\n *\n * @example\n * ```ts\n * const state2 = block(state, id);\n * check(state2, id, CAP_RELAY_READ); // false (blocked)\n * ```\n */\nexport function block(state: AclState, identity: Identity): AclState {\n const key = toKey(identity);\n const entry = getEntry(state, key);\n return {\n ...state,\n entries: {\n ...state.entries,\n [key]: { ...entry, blocked: true },\n },\n };\n}\n\n/**\n * Unblock an identity.\n *\n * Restores capability checks to use the caps bitfield.\n * Returns a new AclState — the original is not modified.\n *\n * @param state - Current ACL state\n * @param identity - Napplet identity to unblock\n * @returns New AclState with the identity unblocked\n *\n * @example\n * ```ts\n * const state2 = unblock(state, id);\n * check(state2, id, CAP_RELAY_READ); // true (if cap was granted)\n * ```\n */\nexport function unblock(state: AclState, identity: Identity): AclState {\n const key = toKey(identity);\n const entry = getEntry(state, key);\n return {\n ...state,\n entries: {\n ...state.entries,\n [key]: { ...entry, blocked: false },\n },\n };\n}\n\n/**\n * Set the state storage quota for an identity.\n *\n * @param state - Current ACL state\n * @param identity - Napplet identity\n * @param bytes - Quota in bytes\n * @returns New AclState with the quota set\n *\n * @example\n * ```ts\n * const state2 = setQuota(state, id, 1024 * 1024); // 1 MB\n * getQuota(state2, id); // 1048576\n * ```\n */\nexport function setQuota(state: AclState, identity: Identity, bytes: number): AclState {\n const key = toKey(identity);\n const entry = getEntry(state, key);\n return {\n ...state,\n entries: {\n ...state.entries,\n [key]: { ...entry, quota: bytes },\n },\n };\n}\n\n/**\n * Get the state storage quota for an identity.\n *\n * Returns DEFAULT_QUOTA (512 KB) if no entry exists.\n *\n * @param state - Current ACL state\n * @param identity - Napplet identity\n * @returns Quota in bytes\n *\n * @example\n * ```ts\n * getQuota(state, id); // 524288 (default 512 KB)\n * ```\n */\nexport function getQuota(state: AclState, identity: Identity): number {\n const key = toKey(identity);\n const entry = state.entries[key];\n return entry?.quota ?? DEFAULT_QUOTA;\n}\n\n/**\n * Serialize ACL state to a JSON string.\n *\n * Pure function — no I/O. The persistence adapter in @kehto/shell\n * uses this to write state to localStorage or other backends.\n *\n * @param state - ACL state to serialize\n * @returns JSON string representation\n *\n * @example\n * ```ts\n * const json = serialize(state);\n * localStorage.setItem('napplet:acl', json);\n * ```\n */\nexport function serialize(state: AclState): string {\n return JSON.stringify(state);\n}\n\n/**\n * Deserialize ACL state from a JSON string.\n *\n * Pure function — no I/O. Returns a valid AclState or a fresh\n * permissive state if the input is invalid.\n *\n * @param json - JSON string to parse\n * @returns Parsed AclState, or fresh permissive state on parse failure\n *\n * @example\n * ```ts\n * const json = localStorage.getItem('napplet:acl') ?? '';\n * const state = deserialize(json);\n * ```\n */\nexport function deserialize(json: string): AclState {\n try {\n const parsed = JSON.parse(json);\n if (\n typeof parsed === 'object' &&\n parsed !== null &&\n (parsed.defaultPolicy === 'permissive' || parsed.defaultPolicy === 'restrictive') &&\n typeof parsed.entries === 'object' &&\n parsed.entries !== null\n ) {\n const entries: Record<string, AclEntry> = {};\n for (const [key, value] of Object.entries(parsed.entries)) {\n const entry = value as Record<string, unknown>;\n if (\n typeof entry.caps === 'number' &&\n typeof entry.blocked === 'boolean' &&\n typeof entry.quota === 'number'\n ) {\n entries[key] = {\n caps: entry.caps,\n blocked: entry.blocked,\n quota: entry.quota,\n };\n }\n }\n return { defaultPolicy: parsed.defaultPolicy, entries };\n }\n } catch {\n // Invalid JSON — fall through to default\n }\n return createState('permissive');\n}\n","/**\n * @kehto/acl — ACL state migration utility.\n *\n * Provides a pure function to migrate persisted ACL state from the old\n * 3-segment composite key format (pubkey:dTag:hash) to the new 2-segment\n * format (dTag:hash) introduced in NIP-5D v0.1.0.\n *\n * No I/O, no side effects. Pure function: takes AclState, returns AclState.\n */\n\nimport type { AclState, AclEntry } from './types.js';\n\n/**\n * Migrate ACL state from old 3-segment key format to new 2-segment key format.\n *\n * Converts entries stored under 'pubkey:dTag:hash' keys to 'dTag:hash' keys.\n * If two old entries map to the same dTag:hash, merges them conservatively:\n * - caps: OR of both bitfields (never removes a granted capability)\n * - blocked: OR of both flags (blocks if either source was blocked)\n * - quota: MAX of both values (keeps the higher allocation)\n *\n * Idempotent: if no 3-segment keys are found, returns the original state\n * unchanged (same object reference).\n *\n * @param state - Current ACL state (may contain old-format entries)\n * @returns Migrated AclState with only 2-segment keys, or the original\n * state unchanged if no migration was needed\n *\n * @example\n * ```ts\n * const oldState = deserialize(localStorage.getItem('napplet:acl') ?? '');\n * const newState = migrateAclState(oldState);\n * if (newState !== oldState) {\n * // Migration occurred — persist the new format\n * localStorage.setItem('napplet:acl', serialize(newState));\n * }\n * ```\n */\nexport function migrateAclState(state: AclState): AclState {\n const newEntries: Record<string, AclEntry> = {};\n let migrated = false;\n\n for (const [key, entry] of Object.entries(state.entries)) {\n const parts = key.split(':');\n if (parts.length === 3) {\n // Old format: pubkey:dTag:hash -> dTag:hash\n const newKey = `${parts[1]}:${parts[2]}`;\n const existing = newEntries[newKey];\n if (existing) {\n // Merge: union caps, preserve block, max quota\n newEntries[newKey] = {\n caps: existing.caps | entry.caps,\n blocked: existing.blocked || entry.blocked,\n quota: Math.max(existing.quota, entry.quota),\n };\n } else {\n newEntries[newKey] = entry;\n }\n migrated = true;\n } else {\n // Already new format or other key — merge if collision with a previously migrated entry\n const existing = newEntries[key];\n if (existing) {\n // Collision: old-format entry was processed first under the same key — merge\n newEntries[key] = {\n caps: existing.caps | entry.caps,\n blocked: existing.blocked || entry.blocked,\n quota: Math.max(existing.quota, entry.quota),\n };\n } else {\n newEntries[key] = entry;\n }\n }\n }\n\n if (!migrated) return state; // No old entries found — return original unchanged\n\n return { defaultPolicy: state.defaultPolicy, entries: newEntries };\n}\n","/**\n * @kehto/acl — NAP/NAP domain capability resolution (8 canonical + config,\n * resource, cvm, outbox, upload, intent).\n *\n * Maps NAP message types (e.g., 'relay.subscribe', 'identity.getProfile') to\n * the capability strings required by sender and recipient. This is the\n * canonical source for \"which capability does this NAP operation require?\"\n * in the @kehto/acl package.\n *\n * Canonical NIP-5D 8 domains: identity, keys, media, notify, relay,\n * storage, inc, theme. Extended in v1.7 with: config (Phase 39, 9th domain),\n * resource (Phase 40, 10th domain). The v1.1 `signer` domain is REMOVED —\n * getPublicKey/getRelays migrated to `identity`; signEvent/nip04/nip44 have\n * no napplet-visible surface (shell handles encryption inside\n * `relay.publishEncrypted`).\n *\n * Zero dependencies. No imports from @napplet/core or any external package.\n *\n * @see packages/acl/src/capabilities.ts for cap string constants + ALL_CAPABILITIES.\n * @see docs/ACL-MIGRATION.md section 2 — Capability Constant to NAP Domain Mapping.\n */\n\n/**\n * Minimal message shape used for capability resolution.\n *\n * Compatible with NappletMessage from @napplet/core, but defined here\n * independently to maintain @kehto/acl's zero-dependency constraint.\n *\n * @param type - NAP message type, e.g. 'relay.subscribe', 'identity.getProfile'\n */\nexport interface NapMessage {\n readonly type: string;\n}\n\n/**\n * Result of resolving what capabilities a NAP message requires.\n *\n * | Field | Description |\n * |----------------|----------------------------------------------------------------|\n * | `senderCap` | Capability the sender must have, or null if no check needed |\n * | `recipientCap` | Capability the recipient must have, or null if no check needed |\n *\n * @param senderCap - Capability the sender must have, or null if no ACL gate required\n * @param recipientCap - Capability the recipient must have, or null if no recipient check\n */\nexport interface CapabilityResolution {\n readonly senderCap: string | null;\n readonly recipientCap: string | null;\n}\n\n/**\n * `relay.*` — split publish / publishEncrypted / read actions.\n *\n * - `publish` → sender `relay:write`, recipient `relay:read`.\n * - `publishEncrypted` → sender `relay:write`, recipient `null` (the shell\n * handles encryption internally; no napplet-visible recipient ACL check).\n * - `subscribe` / `query` / `close` (and `.result` / `event` / `eose` /\n * `closed` / `publish.result` / `publishEncrypted.result`) → sender\n * `relay:read`, recipient `null`.\n */\nfunction relayMap(action: string): CapabilityResolution {\n if (action === 'publish') return { senderCap: 'relay:write', recipientCap: 'relay:read' };\n if (action === 'publishEncrypted') return { senderCap: 'relay:write', recipientCap: null };\n return { senderCap: 'relay:read', recipientCap: null };\n}\n\n/**\n * `identity.*` — split shell-public reads from gated profile reads.\n *\n * Identity is strictly read-only per NAP-IDENTITY: napplets cannot sign,\n * encrypt, or decrypt (encryption is delegated via `relay.publishEncrypted`).\n *\n * - `getPublicKey` / `getRelays` → `null`/`null` (shell-public info).\n * - `getProfile` / `getFollows` / `getList` / `getZaps` / `getMutes` /\n * `getBlocked` / `getBadges` (and any other identity read) → sender\n * `identity:read`, recipient `null`.\n */\nfunction identityMap(action: string): CapabilityResolution {\n if (action === 'getPublicKey' || action === 'getRelays') {\n return { senderCap: null, recipientCap: null };\n }\n return { senderCap: 'identity:read', recipientCap: null };\n}\n\n/**\n * `keys.*` — split forwarding from binding lifecycle.\n *\n * - `forward` / `action` → `keys:forward`.\n * - `registerAction` / `unregisterAction` / `bindings` → `keys:bind`.\n */\nfunction keysMap(action: string): CapabilityResolution {\n if (action === 'forward' || action === 'action') {\n return { senderCap: 'keys:forward', recipientCap: null };\n }\n return { senderCap: 'keys:bind', recipientCap: null };\n}\n\n/**\n * `notify.*` — split channel/permission registration from send/interaction.\n *\n * - `channel.register` / `permission.request` / `permission.result` → `notify:channel`.\n * - `send` / `dismiss` / `badge` / `send.result` / `action` / `clicked` /\n * `dismissed` / `controls` (and any other notify action) → `notify:send`.\n */\nfunction notifyMap(action: string): CapabilityResolution {\n if (\n action === 'channel.register' ||\n action === 'permission.request' ||\n action === 'permission.result'\n ) {\n return { senderCap: 'notify:channel', recipientCap: null };\n }\n return { senderCap: 'notify:send', recipientCap: null };\n}\n\n/**\n * `storage.*` — narrowed to the canonical 4 actions (get/keys/set/remove).\n *\n * - `get` / `keys` → `state:read`.\n * - `set` / `remove` → `state:write`.\n * - anything else (incl. the removed `clear`) → `null`/`null`. The runtime\n * storage handler rejects non-canonical actions before ACL resolution so\n * napplets see the explicit rejection rather than a misleading cap denial.\n */\nfunction storageMap(action: string): CapabilityResolution {\n if (action === 'get' || action === 'keys') return { senderCap: 'state:read', recipientCap: null };\n if (action === 'set' || action === 'remove') return { senderCap: 'state:write', recipientCap: null };\n return { senderCap: null, recipientCap: null };\n}\n\n/**\n * `inc.*` — topic + channel sub-protocol.\n *\n * - Write actions (`emit`, `channel.emit`, `channel.broadcast`) → sender\n * `relay:write`, recipient `relay:read`. Semantically equivalent to relay\n * publish: point-to-point or fan-out writes gate on relay-write at wire\n * level even though channel membership ACL is enforced at `channel.open`.\n * - Read / control actions (`subscribe`, `unsubscribe`, `channel.open`,\n * `channel.list`, `channel.close`) → sender\n * `relay:read`, recipient `null`. Channel open-time ACL semantics: the\n * caller must already hold `relay:read`, and channel membership is\n * recorded by the inc handler.\n */\nfunction incMap(action: string): CapabilityResolution {\n if (action === 'emit' || action === 'channel.emit' || action === 'channel.broadcast') {\n return { senderCap: 'relay:write', recipientCap: 'relay:read' };\n }\n return { senderCap: 'relay:read', recipientCap: null };\n}\n\n/**\n * `config.*` — NAP-CONFIG reference service (v1.7 Phase 39 / 9th NAP domain).\n *\n * Asymmetric protocol: napplet reads, shell writes. ALL napplet-originated\n * config messages require `config:read`. Shell→napplet pushes\n * (`config.values`, `config.registerSchema.result`, `config.schemaError`)\n * are gated by the recipient's `config:read` cap.\n *\n * Anti-overlap: NAP-STORAGE remains the general key-value surface\n * (`state:read`/`state:write`). NAP-CONFIG is shell-managed per-napplet\n * configuration only — see CONFIG-04 scope boundary docs.\n */\nfunction configMap(action: string): CapabilityResolution {\n // Shell-originated pushes: recipient gate (napplet must hold config:read to see them).\n if (action === 'values' || action === 'registerSchema.result' || action === 'schemaError') {\n return { senderCap: null, recipientCap: 'config:read' };\n }\n // Napplet-originated requests: sender gate.\n return { senderCap: 'config:read', recipientCap: null };\n}\n\n/**\n * `resource.*` — NAP-RESOURCE authenticated fetch proxy (v1.7 Phase 40 / 10th NAP domain).\n *\n * Asymmetric protocol: napplet initiates fetch requests, shell proxies and responds.\n *\n * - `bytes` / `bytesMany` / `cancel` (napplet → shell requests) →\n * sender `resource:fetch`, recipient `null`. The napplet must hold\n * `resource:fetch` to issue resource requests or cancel one.\n * - `bytes.result` / `bytes.error` / `bytesMany.result` /\n * `bytesMany.error` (shell → napplet pushes) →\n * sender `null`, recipient `resource:fetch`. The napplet must hold\n * `resource:fetch` to receive the result/error push.\n * - Unknown resource.* actions → sender `resource:fetch`, recipient `null`\n * (default sender gate: napplet must hold resource:fetch to send anything\n * in the resource domain).\n */\nfunction resourceMap(action: string): CapabilityResolution {\n // Shell-originated pushes: recipient gate (napplet must hold resource:fetch to see them).\n if (action === 'bytes.result' || action === 'bytes.error' || action === 'bytesMany.result' || action === 'bytesMany.error') {\n return { senderCap: null, recipientCap: 'resource:fetch' };\n }\n // Napplet-originated requests: sender gate (bytes, cancel, and any unknown).\n return { senderCap: 'resource:fetch', recipientCap: null };\n}\n\n/**\n * `cvm.*` — NAP-CVM ContextVM bridge. Single `cvm:call` cap gates the domain.\n *\n * - `discover` / `request` / `close` (napplet → shell requests) →\n * sender `cvm:call`, recipient `null`. The napplet must hold `cvm:call`\n * to query servers or send MCP messages.\n * - `discover.result` / `request.result` / `close.result` / `event`\n * (shell → napplet pushes) → sender `null`, recipient `cvm:call`. The push\n * is gated against the receiving napplet's cap so a napplet without\n * `cvm:call` never sees CVM results or server-pushed events.\n * - Unknown `cvm.*` actions → sender `cvm:call` (default sender gate).\n */\nfunction cvmMap(action: string): CapabilityResolution {\n // Shell-originated pushes: recipient gate.\n if (action === 'event' || action.endsWith('.result') || action.endsWith('.error')) {\n return { senderCap: null, recipientCap: 'cvm:call' };\n }\n // Napplet-originated requests: sender gate (discover, request, close, unknown).\n return { senderCap: 'cvm:call', recipientCap: null };\n}\n\n/**\n * `outbox.*` — NAP-OUTBOX outbox-aware relay routing (12th NAP domain).\n *\n * Split read/write like the `relay` domain, but with dedicated caps so a shell\n * can grant outbox routing independently of raw relay access:\n *\n * - `publish` (napplet → shell) → sender `outbox:write`,\n * recipient `null`. The shell signs and fans the event out to the relevant\n * write relays; there is no napplet-visible recipient ACL check.\n * - `query` / `subscribe` / `close` / `resolveRelays` (and any other\n * napplet-originated request) → sender `outbox:read`,\n * recipient `null`.\n * - `event` / `eose` / `closed` / `*.result` / `*.error` (shell → napplet\n * pushes) → sender `null`,\n * recipient `outbox:read`. The push is gated against the receiving napplet's\n * cap so a napplet without `outbox:read` never sees results or streamed\n * events.\n */\nfunction outboxMap(action: string): CapabilityResolution {\n // Shell-originated pushes: recipient gate (napplet must hold outbox:read to see them).\n if (\n action === 'event' ||\n action === 'eose' ||\n action === 'closed' ||\n action.endsWith('.result') ||\n action.endsWith('.error')\n ) {\n return { senderCap: null, recipientCap: 'outbox:read' };\n }\n // Publish is the write op.\n if (action === 'publish') return { senderCap: 'outbox:write', recipientCap: null };\n // Napplet-originated reads: query, subscribe, close, resolveRelays, unknown.\n return { senderCap: 'outbox:read', recipientCap: null };\n}\n\n/**\n * `upload.*` — NAP-UPLOAD shell-mediated file/blob upload (13th NAP domain).\n *\n * A single `upload:write` cap gates the domain — uploading is the sensitive op\n * (network egress + identity-linking), and `status` only inspects the requesting\n * napplet's own uploads, so it rides the same grant rather than a separate read\n * cap.\n *\n * - `upload` / `status` (and any other napplet-originated request) → sender\n * `upload:write`, recipient `null`.\n * - `upload.result` / `status.result` / `status.changed` / `*.error` (shell →\n * napplet pushes) → sender `null`, recipient `upload:write`. The push is gated\n * against the receiving napplet's cap so a napplet without `upload:write`\n * never sees results or progress updates.\n */\nfunction uploadMap(action: string): CapabilityResolution {\n // Shell-originated pushes: recipient gate.\n if (\n action === 'status.changed' ||\n action.endsWith('.result') ||\n action.endsWith('.error')\n ) {\n return { senderCap: null, recipientCap: 'upload:write' };\n }\n // Napplet-originated requests: sender gate (upload, status, unknown).\n return { senderCap: 'upload:write', recipientCap: null };\n}\n\n/**\n * `intent.*` — NAP-INTENT archetype intent dispatch (14th NAP domain).\n *\n * Split read/write so a read-only class can introspect available handlers but\n * cannot dispatch (mirrors `outbox` / `relay`, where the write op is the\n * sensitive one — here `invoke` is a focus-stealing cross-napplet navigation):\n *\n * - `invoke` (napplet → shell) → sender `intent:write`,\n * recipient `null`. The shell resolves the archetype to a handler, creates or\n * focuses its window, and delivers the payload.\n * - `available` / `handlers` (and any other napplet-originated request) →\n * sender `intent:read`, recipient `null`. Read-side catalog introspection.\n * - `changed` / `*.result` / `*.error` (shell → napplet pushes) →\n * sender `null`, recipient `intent:read`. The push is gated against the\n * receiving napplet's cap so a napplet without `intent:read` never sees\n * availability updates or invoke results.\n */\nfunction intentMap(action: string): CapabilityResolution {\n // Shell-originated pushes: recipient gate (napplet must hold intent:read to see them).\n if (action === 'changed' || action.endsWith('.result') || action.endsWith('.error')) {\n return { senderCap: null, recipientCap: 'intent:read' };\n }\n // Invoke is the write op (cross-napplet dispatch / focus-steal).\n if (action === 'invoke') return { senderCap: 'intent:write', recipientCap: null };\n // Napplet-originated reads: available, handlers, unknown.\n return { senderCap: 'intent:read', recipientCap: null };\n}\n\n/**\n * `theme.*` — napplet read gate vs shell-initiated push.\n *\n * - `get` / `get.result` (and any other napplet-originated query) →\n * sender `theme:read`, recipient `null`.\n * - `changed` (shell → napplet push) →\n * sender `null`, recipient `theme:read`. The push is gated against the\n * receiving napplet's cap so a napplet without `theme:read` never sees\n * the update.\n *\n * Note: theme's runtime/service wiring lands in Phase 13. The ACL gate is\n * defined here in Phase 12 so the cap surface is canonical ahead of the\n * runtime work.\n */\nfunction themeMap(action: string): CapabilityResolution {\n if (action === 'changed') return { senderCap: null, recipientCap: 'theme:read' };\n return { senderCap: 'theme:read', recipientCap: null };\n}\n\n/**\n * Resolve the capabilities required by a NAP message.\n *\n * Splits `msg.type` on '.' to obtain `[domain, action]`, then dispatches to\n * a per-domain mapper. Unknown domains return `null/null` (silently ignored).\n *\n * **NAP domain mapping table (8 canonical domains):**\n *\n * | Domain | Action(s) | senderCap | recipientCap |\n * |------------|--------------------------------------------------------------|-----------------|---------------|\n * | `relay` | `subscribe`, `query`, `close`, results/pushes | `relay:read` | `null` |\n * | `relay` | `publish` | `relay:write` | `relay:read` |\n * | `relay` | `publishEncrypted` | `relay:write` | `null` |\n * | `identity` | `getPublicKey`, `getRelays` | `null` | `null` |\n * | `identity` | `getProfile/getFollows/getList/getZaps/getMutes/...` | `identity:read` | `null` |\n * | `keys` | `forward`, `action` | `keys:forward` | `null` |\n * | `keys` | `registerAction`, `unregisterAction`, `bindings` | `keys:bind` | `null` |\n * | `media` | any | `media:control` | `null` |\n * | `notify` | `channel.register`, `permission.request`, `permission.result` | `notify:channel`| `null` |\n * | `notify` | `send`, `dismiss`, `badge`, `clicked`, `action`, ... | `notify:send` | `null` |\n * | `storage` | `get`, `keys` | `state:read` | `null` |\n * | `storage` | `set`, `remove` | `state:write` | `null` |\n * | `storage` | any other (incl. removed `clear`) | `null` | `null` |\n * | `inc` | `emit`, `channel.emit`, `channel.broadcast` | `relay:write` | `relay:read` |\n * | `inc` | `subscribe`, `unsubscribe`, `channel.open/list/close` | `relay:read` | `null` |\n * | `theme` | `get`, `get.result` | `theme:read` | `null` |\n * | `theme` | `changed` (shell → napplet push) | `null` | `theme:read` |\n * | `config` | `get`, `subscribe`, `unsubscribe`, `registerSchema`, `openSettings` | `config:read` | `null` |\n * | `config` | `values`, `registerSchema.result`, `schemaError` (shell → napplet pushes) | `null` | `config:read` |\n * | `resource` | `bytes`, `bytesMany`, `cancel` (napplet → shell requests) | `resource:fetch`| `null` |\n * | `resource` | `bytes*.result`, `bytes*.error` (shell → napplet pushes) | `null` | `resource:fetch` |\n * | `intent` | `invoke` (napplet → shell) | `intent:write` | `null` |\n * | `intent` | `available`, `handlers` (napplet → shell) | `intent:read` | `null` |\n * | `intent` | `changed`, `*.result`, `*.error` (shell → napplet pushes) | `null` | `intent:read` |\n * | unknown | any | `null` | `null` |\n *\n * The `signer` domain is REMOVED — signer messages fall through to the\n * default null/null branch. `getPublicKey`/`getRelays` migrated to\n * `identity`; napplet-visible signing does not exist in NIP-5D (shell\n * signs internally for `relay.publishEncrypted`).\n *\n * @param msg - Message with a `type` field in NAP format (e.g., 'relay.subscribe')\n * @returns CapabilityResolution with senderCap and recipientCap (each may be null)\n *\n * @example\n * ```ts\n * resolveCapabilitiesNap({ type: 'relay.subscribe' })\n * // => { senderCap: 'relay:read', recipientCap: null }\n *\n * resolveCapabilitiesNap({ type: 'relay.publishEncrypted' })\n * // => { senderCap: 'relay:write', recipientCap: null }\n *\n * resolveCapabilitiesNap({ type: 'identity.getProfile' })\n * // => { senderCap: 'identity:read', recipientCap: null }\n *\n * resolveCapabilitiesNap({ type: 'keys.forward' })\n * // => { senderCap: 'keys:forward', recipientCap: null }\n *\n * resolveCapabilitiesNap({ type: 'inc.channel.broadcast' })\n * // => { senderCap: 'relay:write', recipientCap: 'relay:read' }\n *\n * resolveCapabilitiesNap({ type: 'theme.changed' })\n * // => { senderCap: null, recipientCap: 'theme:read' }\n *\n * resolveCapabilitiesNap({ type: 'signer.signEvent' })\n * // => { senderCap: null, recipientCap: null } // domain removed\n * ```\n */\nexport function resolveCapabilitiesNap(msg: NapMessage): CapabilityResolution {\n const dotIdx = msg.type.indexOf('.');\n if (dotIdx === -1) return { senderCap: null, recipientCap: null };\n const domain = msg.type.slice(0, dotIdx);\n const action = msg.type.slice(dotIdx + 1);\n\n switch (domain) {\n case 'relay': return relayMap(action);\n case 'identity': return identityMap(action);\n case 'keys': return keysMap(action);\n case 'media': return { senderCap: 'media:control', recipientCap: null };\n case 'notify': return notifyMap(action);\n case 'storage': return storageMap(action);\n case 'inc': return incMap(action);\n case 'theme': return themeMap(action);\n case 'config': return configMap(action);\n case 'resource': return resourceMap(action); // Phase 40 (RESOURCE-02)\n case 'cvm': return cvmMap(action); // NAP-CVM ContextVM bridge\n case 'outbox': return outboxMap(action); // NAP-OUTBOX outbox-aware relay routing\n case 'upload': return uploadMap(action); // NAP-UPLOAD shell-mediated file/blob upload\n case 'intent': return intentMap(action); // NAP-INTENT archetype intent dispatch\n default: return { senderCap: null, recipientCap: null };\n }\n}\n","/**\n * @kehto/firewall — Pure evaluate function.\n *\n * This module is PURE, SIDE-EFFECT-FREE, and NEVER reads a wall clock.\n * `observation.now` is the only time source — no wall-clock reads, no I/O, no mutations.\n *\n * Designed for deterministic access control decisions that could be compiled to\n * WASM without modification (the WASM-ready boundary).\n */\n\nimport type {\n Observation,\n FirewallConfig,\n FirewallState,\n Bucket,\n BurstCounter,\n EvaluateResult,\n Action,\n Decision,\n} from './types.js';\n\n/**\n * Compute the token-bucket key from napplet dTag and operation class.\n *\n * Key shape: `${napplet}:${opClass}` — deliberately dTag-only (version-agnostic).\n *\n * DIVERGENCE FROM @kehto/acl: acl uses `dTag:hash` to distinguish napplet\n * versions. The firewall intentionally omits the hash so rate budgets are shared\n * across ALL versions of the same napplet dTag. This is the correct behavior for\n * a behavioral abuse control: we want to track the napplet identity over time,\n * not its specific loaded version.\n *\n * @param napplet - Napplet dTag (version-agnostic identity key)\n * @param opClass - Operation class string (e.g. 'relay:write', 'outbox:publish')\n * @returns Composite key string `napplet:opClass`\n *\n * @example\n * ```ts\n * toKey('chat', 'relay:write')\n * // => 'chat:relay:write'\n * ```\n */\nexport function toKey(napplet: string, opClass: string): string {\n return `${napplet}:${opClass}`;\n}\n\n/**\n * Map a rule Action to the caller-facing Decision.\n *\n * - `'flag'` → `'pass'` (caller dispatches + emits audit event)\n * - `'block'` → `'reject'` (caller drops the operation)\n * - `'ignore'` → `'pass'` (caller dispatches silently)\n */\nfunction actionToDecision(action: Action): Decision {\n if (action === 'block') return 'reject';\n return 'pass'; // 'flag' and 'ignore' both pass\n}\n\n/**\n * Evaluate a single firewall observation and return the access decision.\n *\n * PURE: no wall-clock reads (no system time APIs), no I/O, no mutation.\n * All time comes from `observation.now`. The original `config` and `state`\n * are NEVER modified — every `newState` is returned via immutable spread.\n *\n * ## Precedence order (A1 — POLICY-03, first-match-wins, most→least specific)\n *\n * 1. **Per-napplet policy** (`allow` / `deny` / `ask`) — hard override for the dTag.\n * `allow` → pass (bypass everything); `deny` → reject (block); `ask` → prompt.\n * Policy returns do NOT advance any counters; newState = input state.\n *\n * 2. **Init-burst guard** — if `observation.initElapsedMs` is defined and less than\n * `config.burstGuard.windowMs`, the burst counter for this napplet is advanced.\n * If the count exceeds `config.burstGuard.maxOps`, the burst action fires\n * (default `block`). The advanced burst counter is returned in newState.\n *\n * 3. **Content matchers** — `config.matchers` are evaluated in order; the FIRST\n * matcher whose declared conditions (opClass, kinds, size, focus, msSinceFocusGain)\n * ALL hold fires its action. Matchers do NOT advance the token bucket.\n *\n * 4. **Per-napplet × op-class rate limit** (`config.napplets[napplet].rateLimits[opClass]`)\n * with ruleId `'rate:opclass'`.\n *\n * 5. **Per-napplet global rate fallback** (`config.napplets[napplet].globalRate`)\n * for op-classes with no specific rateLimits entry. ruleId `'rate:global'`.\n *\n * 6. **Global default rate** (`config.defaultRate`) — applied when no napplet-specific\n * rule exists. ruleId `'rate:default'`.\n *\n * ## Unfocused multiplier (A2 — FOCUS-02)\n *\n * When `observation.focused === false`, the effective bucket capacity is tightened:\n * `effectiveCapacity = limit.capacity * config.unfocusedMultiplier`.\n * Refill rate is derived as `effectiveCapacity / windowMs` so the drip also tightens\n * proportionally. The bucket KEY stays stable (`napplet:opClass`, no focus suffix).\n * Because the multiplier is always `> 0`, an unfocused napplet's budget is reduced\n * but never zero — **focus alone NEVER hard-blocks**.\n *\n * @param config - Immutable firewall configuration\n * @param state - Current ephemeral counter state (never mutated)\n * @param observation - Normalized observation (the sole input surface — CORE-02)\n * @returns Decision result with updated counter state\n *\n * @example\n * ```ts\n * import { evaluate, defaultConfig, createState } from '@kehto/firewall';\n *\n * const config = defaultConfig();\n * const state = createState();\n * const obs = {\n * napplet: 'chat',\n * opClass: 'relay:write',\n * focused: true,\n * now: injectedTimestamp, // caller supplies time; evaluate() never reads a clock\n * };\n *\n * const result = evaluate(config, state, obs);\n * // result.decision === 'pass'\n * // result.newState has an updated token bucket for 'chat:relay:write'\n * ```\n */\nexport function evaluate(\n config: FirewallConfig,\n state: FirewallState,\n observation: Observation,\n): EvaluateResult {\n const { napplet, opClass, now } = observation;\n\n const nappletRules = config.napplets[napplet];\n const policy = nappletRules?.policy;\n\n if (policy === 'allow') {\n return {\n decision: 'pass',\n action: 'ignore',\n ruleId: 'policy:allow',\n reason: `napplet ${napplet} has allow policy — bypassing all checks`,\n newState: state,\n };\n }\n\n if (policy === 'deny') {\n return {\n decision: 'reject',\n action: 'block',\n ruleId: 'policy:deny',\n reason: `napplet ${napplet} has deny policy — always rejected`,\n newState: state,\n };\n }\n\n if (policy === 'ask') {\n return {\n decision: 'prompt',\n action: 'block',\n ruleId: 'policy:ask',\n reason: `napplet ${napplet} has ask policy — prompting for consent`,\n newState: state,\n };\n }\n\n const { initElapsedMs } = observation;\n\n if (initElapsedMs !== undefined && initElapsedMs < config.burstGuard.windowMs) {\n // Advance the burst counter for this napplet\n const existingBurst: BurstCounter | undefined = state.bursts[napplet];\n const newBurst: BurstCounter = {\n count: (existingBurst?.count ?? 0) + 1,\n windowStart: existingBurst?.windowStart ?? now,\n };\n\n const newBursts = { ...state.bursts, [napplet]: newBurst };\n\n if (newBurst.count > config.burstGuard.maxOps) {\n const burstAction = config.burstGuard.action;\n return {\n decision: actionToDecision(burstAction),\n action: burstAction,\n ruleId: 'burst',\n reason: `napplet ${napplet} exceeded init-burst limit (${newBurst.count} > ${config.burstGuard.maxOps} ops within ${config.burstGuard.windowMs}ms)`,\n newState: { ...state, bursts: newBursts },\n };\n }\n\n // Burst count advanced but not exceeded — continue to next tier with updated bursts\n // We update state to carry the burst counter forward\n state = { ...state, bursts: newBursts };\n }\n\n for (const matcher of config.matchers) {\n if (matcher.opClass !== undefined && matcher.opClass !== opClass) continue;\n\n // Check kinds condition (observation.kind must be in the set)\n if (matcher.kinds !== undefined) {\n if (observation.kind === undefined) continue;\n if (!matcher.kinds.includes(observation.kind)) continue;\n }\n\n if (matcher.minSize !== undefined) {\n if (observation.size === undefined) continue;\n if (observation.size < matcher.minSize) continue;\n }\n\n if (matcher.focused !== undefined && matcher.focused !== observation.focused) continue;\n\n if (matcher.maxMsSinceFocusGain !== undefined) {\n if (observation.msSinceFocusGain === undefined) continue;\n if (observation.msSinceFocusGain > matcher.maxMsSinceFocusGain) continue;\n }\n\n // All conditions satisfied — this matcher fires\n const matcherAction = matcher.action;\n return {\n decision: actionToDecision(matcherAction),\n action: matcherAction,\n ruleId: `matcher:${matcher.id}`,\n reason: `content matcher '${matcher.id}' fired`,\n newState: state,\n };\n }\n\n // Resolve the applicable RateLimit (precedence: op-class > global > default)\n let rateLimit = config.defaultRate;\n let rateLimitRuleId = 'rate:default';\n\n if (nappletRules) {\n const opClassLimit = nappletRules.rateLimits[opClass];\n if (opClassLimit) {\n rateLimit = opClassLimit;\n rateLimitRuleId = 'rate:opclass';\n } else if (nappletRules.globalRate) {\n rateLimit = nappletRules.globalRate;\n rateLimitRuleId = 'rate:global';\n }\n }\n\n // Apply focus multiplier to capacity (A2 — never zeroes the budget)\n const effectiveCapacity = observation.focused\n ? rateLimit.capacity\n : rateLimit.capacity * config.unfocusedMultiplier;\n\n // Refill rate derived from effective capacity (drip tightens proportionally with focus)\n const refillRatePerMs = effectiveCapacity / rateLimit.windowMs;\n\n // Lazy-init bucket: absent key means fresh napplet starting full at `now`\n const bucketKey = toKey(napplet, opClass);\n const existingBucket: Bucket | undefined = state.buckets[bucketKey];\n\n // RESEARCH Pattern 2 token-bucket math:\n // lazy `lastRefill || now` init — a fresh key starts FULL at effectiveCapacity at now\n const lastRefill = existingBucket?.lastRefill ?? now;\n const initialTokens = existingBucket?.tokens ?? effectiveCapacity;\n\n // Clamp negative clock skew to 0 (T-80-03 mitigation)\n const elapsed = Math.max(0, now - lastRefill);\n\n // Refill tokens up to effectiveCapacity, keep fractional tokens\n const tokens = Math.min(effectiveCapacity, initialTokens + elapsed * refillRatePerMs);\n\n if (tokens >= 1) {\n // Within budget — spend one token and pass\n // ruleId encodes the resolution path so callers and tests can observe which tier resolved\n const nextBucket: Bucket = { tokens: tokens - 1, lastRefill: now };\n return {\n decision: 'pass',\n action: 'ignore',\n ruleId: rateLimitRuleId,\n reason: `within budget (${rateLimitRuleId})`,\n newState: {\n ...state,\n buckets: { ...state.buckets, [bucketKey]: nextBucket },\n },\n };\n } else {\n // Exceeded budget — apply the rule's exceed-action; still update bucket (no token spent)\n const nextBucket: Bucket = { tokens, lastRefill: now };\n const exceedAction = rateLimit.action;\n return {\n decision: actionToDecision(exceedAction),\n action: exceedAction,\n ruleId: rateLimitRuleId,\n reason: `rate limit exceeded (${rateLimitRuleId}): ${tokens.toFixed(4)} tokens available, need 1`,\n newState: {\n ...state,\n buckets: { ...state.buckets, [bucketKey]: nextBucket },\n },\n };\n }\n}\n","\nimport type { Action, FirewallConfig, FirewallState } from './types.js';\n\n/**\n * Default exceed-action for rate limits: `flag` (pass + audit).\n *\n * Conservative, allow-and-audit default — operations are never silently blocked\n * on first deployment. The shell hears about violations via audit events without\n * disrupting the napplet experience (CORE-04).\n *\n * @example\n * ```ts\n * const limit: RateLimit = { capacity: 60, windowMs: 60_000, action: DEFAULT_EXCEED_ACTION };\n * ```\n */\nexport const DEFAULT_EXCEED_ACTION: Action = 'flag';\n\n/**\n * Default action for the init-burst guard: `block`.\n *\n * The burst guard is the one documented exception to the conservative `flag`\n * default. A napplet that fires more than `DEFAULT_BURST_MAX_OPS` operations\n * within its initialization window is almost certainly misbehaving and should\n * be stopped immediately (BURST-02).\n *\n * @example\n * ```ts\n * const guard: BurstGuard = { windowMs: DEFAULT_BURST_WINDOW_MS, maxOps: DEFAULT_BURST_MAX_OPS, action: DEFAULT_BURST_ACTION };\n * ```\n */\nexport const DEFAULT_BURST_ACTION: Action = 'block';\n\n/**\n * Fractional capacity multiplier applied to unfocused napplets: `0.25`.\n *\n * Scales the effective token-bucket capacity for napplets that are not the\n * currently focused window. Chosen at 1/4 so background napplets can still\n * make legitimate low-rate requests while a sustained high-rate attack from a\n * background napplet is throttled quickly.\n *\n * MUST remain strictly greater than 0 — focus alone must NEVER hard-block a\n * napplet (FOCUS-02 invariant). A value of 0 would be equivalent to an\n * unconditional deny for all unfocused napplets.\n *\n * @example\n * ```ts\n * const effectiveCapacity = limit.capacity * (obs.focused ? 1 : DEFAULT_UNFOCUSED_MULTIPLIER);\n * ```\n */\nexport const DEFAULT_UNFOCUSED_MULTIPLIER = 0.25;\n\n/**\n * Default token-bucket capacity: 60 operations per window.\n *\n * 60 ops/minute is generous for typical napplet relay interactions (reading\n * profiles, publishing notes) while providing a clear ceiling against rapid\n * automated relay flooding. Conservative without being restrictive for\n * well-behaved napplets (CORE-04).\n *\n * @example\n * ```ts\n * const limit: RateLimit = { capacity: DEFAULT_RATE_CAPACITY, windowMs: DEFAULT_RATE_WINDOW_MS, action: DEFAULT_EXCEED_ACTION };\n * ```\n */\nexport const DEFAULT_RATE_CAPACITY = 60;\n\n/**\n * Default token-bucket window: 60 000 ms (1 minute).\n *\n * A 1-minute rolling window pairs with `DEFAULT_RATE_CAPACITY` (60 ops) to\n * give each napplet 1 op/second sustained throughput — sufficient for relay\n * reads, publish flows, and intent invocations under normal usage (CORE-04).\n *\n * @example\n * ```ts\n * const refillRatePerMs = DEFAULT_RATE_CAPACITY / DEFAULT_RATE_WINDOW_MS; // 0.001 ops/ms\n * ```\n */\nexport const DEFAULT_RATE_WINDOW_MS = 60_000;\n\n/**\n * Default init-burst guard window: 3 000 ms (3 seconds).\n *\n * The initialization window covers the first few seconds after a napplet loads.\n * Legitimate napplets bootstrap with a small number of setup requests; a napplet\n * that fires many ops in the first 3 seconds is exhibiting burst-attack behavior\n * (BURST-01, BURST-02).\n *\n * @example\n * ```ts\n * const guard: BurstGuard = { windowMs: DEFAULT_BURST_WINDOW_MS, maxOps: DEFAULT_BURST_MAX_OPS, action: DEFAULT_BURST_ACTION };\n * ```\n */\nexport const DEFAULT_BURST_WINDOW_MS = 3_000;\n\n/**\n * Default init-burst guard operation cap: 20 operations.\n *\n * 20 ops within the first 3 seconds is more than enough for any legitimate\n * initialization sequence (subscribe, fetch profile, query relays). Exceeding\n * this indicates automated request flooding and triggers the `block` action\n * (BURST-02). Value chosen to tolerate brief SDK setup bursts while stopping\n * malicious behavior.\n *\n * @example\n * ```ts\n * const guard: BurstGuard = { windowMs: DEFAULT_BURST_WINDOW_MS, maxOps: DEFAULT_BURST_MAX_OPS, action: DEFAULT_BURST_ACTION };\n * ```\n */\nexport const DEFAULT_BURST_MAX_OPS = 20;\n\n/**\n * Assemble the built-in default FirewallConfig.\n *\n * Returns a fresh config applying conservative rate/burst limits to every\n * napplet out of the box. No per-napplet rules or content matchers are\n * pre-configured — those are added via the mutation functions in config.ts.\n *\n * The exceed-action default is `flag` (CORE-04 — allow + audit). The\n * init-burst guard default is `block` (BURST-02 — the documented exception).\n *\n * @returns A new FirewallConfig with conservative built-in limits\n *\n * @example\n * ```ts\n * const config = defaultConfig();\n * // config.defaultRate.action === 'flag'\n * // config.burstGuard.action === 'block'\n * // config.unfocusedMultiplier === 0.25\n * ```\n */\nexport function defaultConfig(): FirewallConfig {\n return {\n napplets: {},\n matchers: [],\n burstGuard: {\n windowMs: DEFAULT_BURST_WINDOW_MS,\n maxOps: DEFAULT_BURST_MAX_OPS,\n action: DEFAULT_BURST_ACTION,\n },\n defaultRate: {\n capacity: DEFAULT_RATE_CAPACITY,\n windowMs: DEFAULT_RATE_WINDOW_MS,\n action: DEFAULT_EXCEED_ACTION,\n },\n unfocusedMultiplier: DEFAULT_UNFOCUSED_MULTIPLIER,\n };\n}\n\n/**\n * Create an empty FirewallState with no token-bucket or burst counters.\n *\n * Returns a fresh ephemeral counter state. Counter state is never persisted\n * (Phase 81 concern) — it is reset on reload and rebuilt as observations arrive.\n *\n * Mirrors the `createState()` factory pattern from @kehto/acl/mutations.ts.\n *\n * @returns A new FirewallState with empty buckets and bursts maps\n *\n * @example\n * ```ts\n * const state = createState();\n * // { buckets: {}, bursts: {} }\n * ```\n */\nexport function createState(): FirewallState {\n return { buckets: {}, bursts: {} };\n}\n","/**\n * @kehto/firewall — Pure config mutation functions and serialization.\n *\n * Every mutation function takes a FirewallConfig and returns a NEW FirewallConfig.\n * The original config is never modified. No side effects, no I/O.\n *\n * Mirrors the role of @kehto/acl's mutations.ts: immutable spread-return mutations\n * (grant/revoke pattern), plain JSON.stringify serialize, and defensive deserialize\n * with shape validation and defaultConfig() fallback (V5 input validation — T-80-01).\n */\n\nimport type {\n FirewallConfig,\n NappletRules,\n RateLimit,\n ContentMatcher,\n NappletPolicy,\n Action,\n} from './types.js';\nimport { defaultConfig } from './defaults.js';\n\n/**\n * Return the existing NappletRules for `napplet`, or a fresh empty entry.\n * Internal helper — not exported. Mirrors acl's `getEntry` (mutations.ts:33-42).\n */\nfunction getNapplet(config: FirewallConfig, napplet: string): NappletRules {\n const existing = config.napplets[napplet];\n if (existing) return existing;\n return { rateLimits: {} };\n}\n\n/**\n * Set the hard policy posture for a specific napplet (dTag).\n *\n * Returns a new FirewallConfig with `napplets[napplet].policy` set to `policy`.\n * If the napplet has no existing entry, a fresh entry is created.\n * The original config is never modified.\n *\n * @param config - Current firewall config\n * @param napplet - Napplet dTag to configure\n * @param policy - Policy posture ('allow' | 'deny' | 'ask')\n * @returns New FirewallConfig with the policy set\n *\n * @example\n * ```ts\n * const cfg2 = setPolicy(cfg, 'chat', 'deny');\n * // cfg2.napplets['chat'].policy === 'deny'\n * // cfg is unchanged\n * ```\n */\nexport function setPolicy(\n config: FirewallConfig,\n napplet: string,\n policy: NappletPolicy,\n): FirewallConfig {\n const entry = getNapplet(config, napplet);\n return {\n ...config,\n napplets: {\n ...config.napplets,\n [napplet]: { ...entry, policy },\n },\n };\n}\n\n/**\n * Set a per-napplet, per-opClass token-bucket rate limit.\n *\n * Returns a new FirewallConfig with `napplets[napplet].rateLimits[opClass]`\n * set to `limit`. If the napplet has no existing entry, a fresh entry is created.\n * The original config is never modified.\n *\n * @param config - Current firewall config\n * @param napplet - Napplet dTag to configure\n * @param opClass - Operation class string (e.g. 'relay:write', 'outbox:write')\n * @param limit - Token-bucket rate limit to apply\n * @returns New FirewallConfig with the rate limit set\n *\n * @example\n * ```ts\n * const cfg2 = setRateLimit(cfg, 'chat', 'relay:write', { capacity: 10, windowMs: 5000, action: 'block' });\n * // cfg2.napplets['chat'].rateLimits['relay:write'].capacity === 10\n * ```\n */\nexport function setRateLimit(\n config: FirewallConfig,\n napplet: string,\n opClass: string,\n limit: RateLimit,\n): FirewallConfig {\n const entry = getNapplet(config, napplet);\n return {\n ...config,\n napplets: {\n ...config.napplets,\n [napplet]: {\n ...entry,\n rateLimits: { ...entry.rateLimits, [opClass]: limit },\n },\n },\n };\n}\n\n/**\n * Set a per-napplet global fallback rate limit (RATE-03).\n *\n * The global rate is applied to all op-classes for this napplet that lack a\n * specific `rateLimits` entry. This provides a single budget covering all\n * unlisted operations rather than relying solely on the config-wide `defaultRate`.\n *\n * Returns a new FirewallConfig with `napplets[napplet].globalRate` set.\n * The original config is never modified.\n *\n * @param config - Current firewall config\n * @param napplet - Napplet dTag to configure\n * @param limit - Global fallback rate limit for this napplet\n * @returns New FirewallConfig with the global rate set\n *\n * @example\n * ```ts\n * const cfg2 = setGlobalRate(cfg, 'chat', { capacity: 30, windowMs: 30000, action: 'flag' });\n * // cfg2.napplets['chat'].globalRate is set; other napplets unaffected\n * ```\n */\nexport function setGlobalRate(\n config: FirewallConfig,\n napplet: string,\n limit: RateLimit,\n): FirewallConfig {\n const entry = getNapplet(config, napplet);\n return {\n ...config,\n napplets: {\n ...config.napplets,\n [napplet]: { ...entry, globalRate: limit },\n },\n };\n}\n\n/**\n * Append a content matcher to the firewall config.\n *\n * Matchers are evaluated in order; the first match wins (POLICY-03). Returns a\n * new FirewallConfig with `matcher` appended to the end of `config.matchers`.\n * The original config is never modified.\n *\n * @param config - Current firewall config\n * @param matcher - Content matcher to append\n * @returns New FirewallConfig with the matcher appended\n *\n * @example\n * ```ts\n * const cfg2 = addMatcher(cfg, { id: 'delete-spam', opClass: 'relay:write', kinds: [5], action: 'block' });\n * // cfg2.matchers.length === cfg.matchers.length + 1\n * ```\n */\nexport function addMatcher(config: FirewallConfig, matcher: ContentMatcher): FirewallConfig {\n return { ...config, matchers: [...config.matchers, matcher] };\n}\n\n/**\n * Serialize a FirewallConfig to a JSON string.\n *\n * Pure function — no I/O. The persistence adapter in @kehto/shell (Phase 81)\n * uses this to write config to localStorage or other backends.\n *\n * @param config - Firewall config to serialize\n * @returns JSON string representation\n *\n * @example\n * ```ts\n * const json = serialize(config);\n * localStorage.setItem('kehto:firewall', json);\n * ```\n */\nexport function serialize(config: FirewallConfig): string {\n return JSON.stringify(config);\n}\n\nconst VALID_ACTIONS: Action[] = ['flag', 'block', 'ignore'];\n\nfunction isValidAction(v: unknown): v is Action {\n return VALID_ACTIONS.includes(v as Action);\n}\n\nfunction isValidRateLimit(v: unknown): v is RateLimit {\n if (typeof v !== 'object' || v === null) return false;\n const r = v as Record<string, unknown>;\n return (\n typeof r['capacity'] === 'number' &&\n isFinite(r['capacity'] as number) &&\n typeof r['windowMs'] === 'number' &&\n isFinite(r['windowMs'] as number) &&\n isValidAction(r['action'])\n );\n}\n\nfunction isValidBurstGuard(v: unknown): boolean {\n if (typeof v !== 'object' || v === null) return false;\n const b = v as Record<string, unknown>;\n return (\n typeof b['windowMs'] === 'number' &&\n isFinite(b['windowMs'] as number) &&\n typeof b['maxOps'] === 'number' &&\n isFinite(b['maxOps'] as number) &&\n isValidAction(b['action'])\n );\n}\n\nfunction isValidContentMatcher(v: unknown): v is ContentMatcher {\n if (typeof v !== 'object' || v === null) return false;\n const m = v as Record<string, unknown>;\n if (typeof m['id'] !== 'string') return false;\n if (!isValidAction(m['action'])) return false;\n // Optional fields — if present, validate types\n if ('opClass' in m && typeof m['opClass'] !== 'string') return false;\n if ('minSize' in m && typeof m['minSize'] !== 'number') return false;\n if ('focused' in m && typeof m['focused'] !== 'boolean') return false;\n if ('maxMsSinceFocusGain' in m && typeof m['maxMsSinceFocusGain'] !== 'number') return false;\n if ('kinds' in m) {\n if (!Array.isArray(m['kinds'])) return false;\n if (!(m['kinds'] as unknown[]).every((k) => typeof k === 'number')) return false;\n }\n return true;\n}\n\nfunction isValidNappletRules(v: unknown): v is NappletRules {\n if (typeof v !== 'object' || v === null) return false;\n const n = v as Record<string, unknown>;\n // rateLimits must be an object\n if (typeof n['rateLimits'] !== 'object' || n['rateLimits'] === null) return false;\n for (const limit of Object.values(n['rateLimits'] as Record<string, unknown>)) {\n if (!isValidRateLimit(limit)) return false;\n }\n // Optional policy\n if ('policy' in n) {\n const p = n['policy'];\n if (p !== 'allow' && p !== 'deny' && p !== 'ask') return false;\n }\n // Optional globalRate\n if ('globalRate' in n && !isValidRateLimit(n['globalRate'])) return false;\n return true;\n}\n\n/**\n * Deserialize a FirewallConfig from a JSON string.\n *\n * Defensive parse: tries JSON.parse, then shape-validates every top-level and\n * nested field (napplets map, matchers array, burstGuard, defaultRate, and\n * unfocusedMultiplier). Rebuilds a validated config from the parsed data.\n *\n * On ANY failure (invalid JSON, missing fields, wrong types, invalid action\n * values, malformed nested structures) falls through to `defaultConfig()`.\n * This function NEVER throws.\n *\n * Security control for T-80-01 (Tampering — persisted config string):\n * the config string is the only untrusted input to @kehto/firewall. Poisoned\n * or malformed strings always produce a safe, valid default config.\n *\n * @param json - JSON string to parse (may be untrusted)\n * @returns Parsed and validated FirewallConfig, or defaultConfig() on any failure\n *\n * @example\n * ```ts\n * const json = localStorage.getItem('kehto:firewall') ?? '';\n * const config = deserialize(json);\n * // config is always a valid FirewallConfig — never throws\n * ```\n */\nexport function deserialize(json: string): FirewallConfig {\n try {\n const parsed = JSON.parse(json);\n\n // Top-level shape check\n if (\n typeof parsed !== 'object' ||\n parsed === null ||\n typeof parsed.napplets !== 'object' ||\n parsed.napplets === null ||\n !Array.isArray(parsed.matchers) ||\n !isValidBurstGuard(parsed.burstGuard) ||\n !isValidRateLimit(parsed.defaultRate) ||\n typeof parsed.unfocusedMultiplier !== 'number' ||\n !isFinite(parsed.unfocusedMultiplier)\n ) {\n return defaultConfig();\n }\n\n const napplets: Record<string, NappletRules> = {};\n for (const [key, value] of Object.entries(parsed.napplets as Record<string, unknown>)) {\n if (!isValidNappletRules(value)) return defaultConfig();\n // The type guard above narrows `value` to NappletRules.\n const raw = value;\n const rateLimits: Record<string, RateLimit> = {};\n for (const [opClass, limit] of Object.entries(raw.rateLimits)) {\n rateLimits[opClass] = limit;\n }\n const entry: NappletRules = { rateLimits };\n const withPolicy: NappletRules = raw.policy !== undefined\n ? { ...entry, policy: raw.policy }\n : entry;\n const withGlobalRate: NappletRules = raw.globalRate !== undefined\n ? { ...withPolicy, globalRate: raw.globalRate }\n : withPolicy;\n napplets[key] = withGlobalRate;\n }\n\n const matchers: ContentMatcher[] = [];\n for (const item of parsed.matchers as unknown[]) {\n if (!isValidContentMatcher(item)) return defaultConfig();\n matchers.push(item as ContentMatcher);\n }\n\n // Rebuild burstGuard and defaultRate from validated parsed data\n const bg = parsed.burstGuard as Record<string, unknown>;\n const dr = parsed.defaultRate as Record<string, unknown>;\n\n return {\n napplets,\n matchers,\n burstGuard: {\n windowMs: bg['windowMs'] as number,\n maxOps: bg['maxOps'] as number,\n action: bg['action'] as Action,\n },\n defaultRate: {\n capacity: dr['capacity'] as number,\n windowMs: dr['windowMs'] as number,\n action: dr['action'] as Action,\n },\n unfocusedMultiplier: parsed.unfocusedMultiplier as number,\n };\n } catch {\n // Invalid JSON — fall through to default\n }\n return defaultConfig();\n}\n","/**\n * @napplet/core -- JSON envelope types for the napplet-shell wire protocol.\n *\n * Defines the base types for the JSON envelope wire format introduced\n * in NIP-5D v4. All messages between napplet and shell use a `type`\n * field as a discriminant in `domain.action` format.\n *\n * @example\n * ```ts\n * import type { NappletMessage, NapDomain, NapProtocolId, ShellSupports } from '@napplet/core';\n * import { NAP_DOMAINS } from '@napplet/core';\n * ```\n *\n * @packageDocumentation\n */\n\n/**\n * Base interface for all JSON envelope messages exchanged between\n * napplet and shell. The `type` field is a string discriminant\n * in `domain.action` format (e.g., `\"relay.subscribe\"`, `\"storage.get\"`).\n *\n * Concrete message types extend this interface with domain-specific payload fields.\n *\n * @example\n * ```ts\n * const msg: NappletMessage = { type: 'relay.subscribe' };\n *\n * // Concrete message with payload:\n * interface RelaySubscribe extends NappletMessage {\n * type: 'relay.subscribe';\n * filters: NostrFilter[];\n * }\n * ```\n */\nexport interface NappletMessage {\n /** Message type discriminant in \"domain.action\" format (e.g., \"relay.subscribe\", \"storage.get\") */\n type: string;\n}\n\n/**\n * String literal union of the active NAP (Nostr Applet Protocol) domains.\n * Each domain corresponds to a capability namespace that a shell may support.\n *\n * | Domain | Scope |\n * |------------|----------------------------------------------------|\n * | `relay` | NIP-01 relay proxy (subscribe, publish) |\n * | `identity` | Read-only user identity queries |\n * | `storage` | Scoped key-value storage proxy |\n * | `inc` | Inter-napplet communication (INC peer bus) |\n * | `theme` | Theme tokens and appearance settings |\n * | `keys` | Keyboard forwarding and action keybindings |\n * | `media` | Media session control and playback |\n * | `notify` | Shell-rendered notifications |\n * | `config` | Per-napplet declarative configuration |\n * | `resource` | Byte-fetching primitive (URL → Blob) |\n * | `ble` | Runtime-mediated Bluetooth LE/GATT sessions |\n * | `webrtc` | Runtime-mediated WebRTC signaling and data sessions |\n * | `link` | Shell-mediated user-visible link opening |\n * | `lists` | Runtime-mediated NIP-51 list mutations |\n * | `serial` | Runtime-mediated serial device access |\n * | `common` | Common social actions |\n *\n * @example\n * ```ts\n * const domain: NapDomain = 'relay';\n * const isValid = NAP_DOMAINS.includes(domain); // true\n * ```\n */\nexport type NapDomain = 'relay' | 'identity' | 'storage' | 'inc' | 'theme' | 'keys' | 'media' | 'notify' | 'config' | 'resource' | 'cvm' | 'outbox' | 'upload' | 'intent' | 'ble' | 'webrtc' | 'link' | 'lists' | 'serial' | 'common';\n\n/**\n * Runtime-accessible constant array of all NAP domain names.\n * Useful for iteration, validation, and capability enumeration.\n *\n * @example\n * ```ts\n * for (const domain of NAP_DOMAINS) {\n * console.log(`Checking support for: ${domain}`);\n * }\n * ```\n */\nexport const NAP_DOMAINS: readonly NapDomain[] = ['relay', 'identity', 'storage', 'inc', 'theme', 'keys', 'media', 'notify', 'config', 'resource', 'cvm', 'outbox', 'upload', 'intent', 'ble', 'webrtc', 'link', 'lists', 'serial', 'common'] as const;\n\n/**\n * Namespaced capability string for {@link ShellSupports.supports}.\n *\n * Accepts NAP capability prefixes plus bare domain shorthand:\n *\n * | Prefix | Example | Meaning |\n * |---------|---------------------|--------------------------------|\n * | `nap:` | `'nap:relay'` | Shell implements the relay NAP |\n * | `perm:` | `'perm:popups'` | Shell grants popup permission |\n * | *(bare)*| `'relay'` | Shorthand for `'nap:relay'` |\n *\n * Bare strings are valid only for NAP domains. Permissions MUST use the\n * `perm:` prefix.\n *\n * @example\n * ```ts\n * const cap: NamespacedCapability = 'nap:relay';\n * const bare: NamespacedCapability = 'relay'; // shorthand OK\n * const perm: NamespacedCapability = 'perm:popups';\n * ```\n */\nexport type NamespacedCapability =\n | NapDomain\n | `nap:${NapDomain}`\n | `perm:${string}`;\n\n/**\n * Numbered NAP protocol identifier for napplet-to-napplet message semantics.\n *\n * NAP-WORD interfaces are discovered with the first `supports()` argument.\n * NAP-NN message protocols are negotiated with the optional second argument.\n *\n * @example\n * ```ts\n * const protocol: NapProtocolId = 'NAP-01';\n * window.napplet.shell.supports('inc', protocol);\n * ```\n */\nexport type NapProtocolId = `NAP-${number}`;\n\n/** Numbered protocol identifier accepted by shell.supports(). */\nexport type ProtocolId = NapProtocolId;\n\n/**\n * Interface for the shell capability query API.\n * Allows napplets to check whether the shell supports a NAP domain,\n * a permission, or a numbered NAP protocol at runtime.\n *\n * @example\n * ```ts\n * // NAP domain queries (bare shorthand or prefixed):\n * shell.supports('relay'); // shorthand for 'nap:relay'\n * shell.supports('nap:storage'); // explicit NAP prefix\n *\n * // Permission queries:\n * shell.supports('perm:popups'); // popup permission\n *\n * // Numbered protocol queries over an interface:\n * shell.supports('inc', 'NAP-01');\n * ```\n */\nexport interface ShellSupports {\n /** Check whether the shell supports a NAP capability, permission, or numbered protocol. */\n supports(capability: NamespacedCapability, protocol?: ProtocolId): boolean;\n}\n","/**\n * @napplet/core -- NAP registration and message dispatch infrastructure.\n *\n * Provides a NAP-agnostic mechanism for NAP modules (relay, identity, storage, inc)\n * to register their domain string and a message handler function. Inbound messages\n * are dispatched to the correct NAP handler based on the domain prefix extracted\n * from `message.type` (the part before the first `.`).\n *\n * Use the {@link createDispatch} factory for isolated registries (testing,\n * multi-instance), or the module-level singleton exports ({@link registerNap},\n * {@link dispatch}, {@link getRegisteredDomains}) for the common single-registry case.\n *\n * @example\n * ```ts\n * import { registerNap, dispatch } from '@napplet/core';\n *\n * // NAP module registers its domain:\n * registerNap('relay', (msg) => {\n * console.log('relay handler received:', msg.type);\n * });\n *\n * // Dispatch routes by domain prefix:\n * dispatch({ type: 'relay.subscribe' }); // => true, calls relay handler\n * dispatch({ type: 'identity.getPublicKey' }); // => false, no identity handler\n * ```\n *\n * @packageDocumentation\n */\n\nimport type { NappletMessage } from './envelope.js';\n\n/**\n * Callback that a NAP module provides to handle messages in its domain.\n *\n * @param message - The envelope message whose `type` matched this handler's domain.\n *\n * @example\n * ```ts\n * const handler: NapHandler = (msg) => {\n * console.log('Received:', msg.type);\n * };\n * ```\n */\nexport type NapHandler = (message: NappletMessage) => void;\n\n/**\n * Shape returned by {@link createDispatch}. Contains the three dispatch\n * operations backed by a shared, isolated handler registry.\n */\nexport interface NapDispatch {\n /** Register a NAP domain handler. Throws if the domain is already registered. */\n registerNap: (domain: string, handler: NapHandler) => void;\n /** Dispatch a message to the handler matching its domain prefix. Returns `true` if handled. */\n dispatch: (message: NappletMessage) => boolean;\n /** Return all currently registered domain strings. */\n getRegisteredDomains: () => string[];\n}\n\n/**\n * Create an isolated NAP dispatch registry.\n *\n * Each call returns a fresh `{ registerNap, dispatch, getRegisteredDomains }`\n * backed by its own `Map<string, NapHandler>`. Use this factory for\n * testability or when multiple independent dispatch registries are needed.\n *\n * @returns A fresh dispatch instance with its own handler map.\n *\n * @example\n * ```ts\n * import { createDispatch } from '@napplet/core';\n *\n * const { registerNap, dispatch } = createDispatch();\n * registerNap('relay', handleRelayMessage);\n * dispatch({ type: 'relay.subscribe' }); // true\n * ```\n */\nexport function createDispatch(): NapDispatch {\n const handlers = new Map<string, NapHandler>();\n\n /**\n * Register a handler for the given NAP domain.\n *\n * @param domain - The domain string (e.g., `'relay'`, `'identity'`).\n * @param handler - Callback invoked for messages in this domain.\n * @throws {Error} If the domain is already registered.\n *\n * @example\n * ```ts\n * registerNap('identity', (msg) => { /* handle identity.* messages *\\/ });\n * ```\n */\n function registerNap(domain: string, handler: NapHandler): void {\n if (handlers.has(domain)) {\n throw new Error(`NAP domain \"${domain}\" is already registered`);\n }\n handlers.set(domain, handler);\n }\n\n /**\n * Dispatch a message to the handler matching its domain prefix.\n *\n * The domain is extracted from `message.type` by splitting on the first `.`.\n * If the domain portion is empty, has no `.`, or no handler is registered,\n * the function returns `false` without throwing.\n *\n * @param message - The envelope message to dispatch.\n * @returns `true` if a handler was found and called, `false` otherwise.\n *\n * @example\n * ```ts\n * dispatch({ type: 'relay.subscribe' }); // true (if relay handler exists)\n * dispatch({ type: 'unknown.action' }); // false\n * dispatch({ type: 'malformed' }); // false (no dot)\n * ```\n */\n function dispatch(message: NappletMessage): boolean {\n const dotIndex = message.type.indexOf('.');\n if (dotIndex <= 0) return false;\n\n const domain = message.type.slice(0, dotIndex);\n const handler = handlers.get(domain);\n if (!handler) return false;\n\n handler(message);\n return true;\n }\n\n /**\n * Return all currently registered domain strings.\n *\n * @returns Array of domain strings in registration order.\n *\n * @example\n * ```ts\n * getRegisteredDomains(); // ['relay', 'identity']\n * ```\n */\n function getRegisteredDomains(): string[] {\n return Array.from(handlers.keys());\n }\n\n return { registerNap, dispatch, getRegisteredDomains };\n}\n\nconst _default = createDispatch();\n\n/**\n * Register a handler for the given NAP domain on the default registry.\n *\n * @param domain - The domain string (e.g., `'relay'`, `'identity'`).\n * @param handler - Callback invoked for messages in this domain.\n * @throws {Error} If the domain is already registered.\n *\n * @example\n * ```ts\n * import { registerNap } from '@napplet/core';\n * registerNap('relay', (msg) => console.log(msg));\n * ```\n */\nexport const registerNap: NapDispatch['registerNap'] = _default.registerNap;\n\n/**\n * Dispatch a message on the default registry.\n *\n * @param message - The envelope message to dispatch.\n * @returns `true` if a handler was found and called, `false` otherwise.\n *\n * @example\n * ```ts\n * import { dispatch } from '@napplet/core';\n * dispatch({ type: 'relay.subscribe' }); // true if relay handler registered\n * ```\n */\nexport const dispatch: NapDispatch['dispatch'] = _default.dispatch;\n\n/**\n * Return all registered domain strings from the default registry.\n *\n * @returns Array of domain strings.\n *\n * @example\n * ```ts\n * import { getRegisteredDomains } from '@napplet/core';\n * getRegisteredDomains(); // ['relay', 'identity']\n * ```\n */\nexport const getRegisteredDomains: NapDispatch['getRegisteredDomains'] = _default.getRegisteredDomains;\n","/**\n * @napplet/core — Topic constants for the napplet INC-PEER event bus.\n *\n * These constants define the topic strings used in INC-PEER\n * events for shell commands, state operations, audio,\n * and UI coordination.\n */\n\n/**\n * Built-in topic constants for the napplet shell INC-PEER protocol.\n *\n * @example\n * ```ts\n * import { TOPICS } from '@napplet/core';\n *\n * // Open a profile view via INC-PEER\n * shim.publish({ kind: 29003, tags: [['t', TOPICS.PROFILE_OPEN]], content: '{}' });\n * ```\n *\n * ## Topic Prefix Conventions\n *\n * Topic strings follow a prefix convention that signals message direction:\n *\n * | Prefix | Direction | Meaning |\n * |--------|-----------|---------|\n * | `shell:*` | napplet → shell | Commands sent by a napplet to the shell |\n * | `napplet:*` | shell → napplet | Responses/notifications sent by shell to napplet |\n * | `{service}:*` | bidirectional | Service-scoped messages; direction is per-topic |\n *\n * Examples of `{service}:*` prefixes: `auth:*`, `stream:*`, `profile:*`,\n * `wm:*`, `keybinds:*`, `chat:*`, `audio:*`.\n */\nexport const TOPICS = {\n STREAM_CHANNEL_SWITCH: 'stream:channel-switch',\n STREAM_CURRENT_CONTEXT_GET: 'stream:current-context-get',\n STREAM_CURRENT_CONTEXT: 'stream:current-context',\n\n PROFILE_OPEN: 'profile:open',\n\n KEYBINDS_GET: 'keybinds:get-all',\n KEYBINDS_ALL: 'keybinds:all',\n KEYBINDS_UPDATE: 'keybinds:update',\n KEYBINDS_RESET: 'keybinds:reset',\n KEYBINDS_CAPTURE_START: 'keybinds:capture-start',\n KEYBINDS_CAPTURE_END: 'keybinds:capture-end',\n\n WM_FOCUSED_WINDOW_CHANGED: 'wm:focused-window-changed',\n\n CHAT_OPEN_DM: 'chat:open-dm',\n\n} as const;\n\n/** Key type for the TOPICS constant object. */\nexport type TopicKey = keyof typeof TOPICS;\n\n/** Value type for the TOPICS constant object. */\nexport type TopicValue = (typeof TOPICS)[TopicKey];\n","/**\n * @napplet/core -- iframe boundary helpers for clone-safe `postMessage`.\n *\n * Every NAP shim crosses the napplet ⇄ shell boundary by posting a JSON\n * envelope through `window.parent.postMessage(msg, '*')`, which **structured-\n * clones** its argument. Framework reactive values -- Svelte 5 `$state`, Vue\n * `reactive`, Solid stores -- are `Proxy` objects that are NOT structured-\n * cloneable, so `postMessage` throws a `DataCloneError`. In an async/Promise\n * path that throw becomes an unhandled rejection that gets silently swallowed,\n * and the envelope simply never crosses the boundary (napplet/web#67).\n *\n * These helpers make that failure mode disappear or surface loudly, depending\n * on the configured {@link CloneMode}:\n *\n * - **`'auto'` (default)** -- post the envelope as-is; only if the structured\n * clone fails, take a proxy-stripping {@link toCloneableSnapshot} and retry,\n * warning once per message type. Zero overhead on the happy path; reactive\n * napplets \"just work\"; a genuinely non-cloneable value (a function, etc.)\n * still throws a loud, actionable, synchronous error.\n * - **`'strict'`** -- never auto-recover; on `DataCloneError` throw the loud\n * actionable error immediately. Use this to catch accidental proxy leaks.\n * - **`'snapshot'`** -- always snapshot before posting (eager normalization).\n *\n * NOTE: this module references no browser globals. The post target is passed\n * in by the caller (NAP shims pass `window.parent`), so `@napplet/core` stays\n * DOM-free. These helpers are pure SDK plumbing: the bytes placed on the wire\n * are identical plain envelopes either way, so nothing here is protocol surface.\n *\n * @packageDocumentation\n */\n\n/**\n * How {@link sendEnvelope} treats arguments that are not structured-cloneable.\n *\n * - `'auto'` -- post as-is, snapshot-and-retry on `DataCloneError` (default).\n * - `'strict'` -- throw a loud actionable error on `DataCloneError`.\n * - `'snapshot'` -- always snapshot before posting.\n */\nexport type CloneMode = 'auto' | 'strict' | 'snapshot';\n\n/** Minimal post target -- `window.parent` satisfies this without DOM types. */\nexport interface PostMessageTarget {\n postMessage(message: unknown, targetOrigin: string): void;\n}\n\nlet cloneMode: CloneMode = 'auto';\n\n/** Message types we have already warned about (auto-recovery), to warn once each. */\nconst warnedTypes = new Set<string>();\n\n/**\n * Set the global clone mode for {@link sendEnvelope}.\n *\n * @param mode - One of `'auto'` (default), `'strict'`, or `'snapshot'`.\n *\n * @example\n * ```ts\n * import { setCloneMode } from '@napplet/core';\n * // Eagerly snapshot every outbound argument (framework-heavy napplets):\n * setCloneMode('snapshot');\n * ```\n */\nexport function setCloneMode(mode: CloneMode): void {\n cloneMode = mode;\n}\n\n/**\n * Return the current clone mode.\n *\n * @returns The active {@link CloneMode}.\n */\nexport function getCloneMode(): CloneMode {\n return cloneMode;\n}\n\n/**\n * Reset the once-per-type auto-recovery warning state.\n *\n * Mostly useful in tests and when a shell re-initializes the shim runtime.\n */\nexport function clearCloneWarnings(): void {\n warnedTypes.clear();\n}\n\n/** Tags of values that are structured-cloneable leaves -- copied by reference. */\nfunction isCloneableLeaf(value: object): boolean {\n if (\n value instanceof Date ||\n value instanceof RegExp ||\n value instanceof ArrayBuffer ||\n ArrayBuffer.isView(value)\n ) {\n return true;\n }\n // Tag check keeps Blob/File detection DOM-lib-free.\n const tag = Object.prototype.toString.call(value);\n return tag === '[object Blob]' || tag === '[object File]';\n}\n\n/**\n * Produce a structured-cloneable deep snapshot of `value`, stripping framework\n * reactive proxies (Svelte 5 `$state`, Vue `reactive`, Solid stores) into plain\n * objects and arrays.\n *\n * Unlike a `JSON` round-trip, this **preserves binary** (`Uint8Array`,\n * `ArrayBuffer`, typed arrays), `Date`, `RegExp`, `Map`, and `Set`, and handles\n * cyclic references. Functions and symbols are not representable and throw a\n * `TypeError` -- matching `structuredClone`, so genuinely non-cloneable input is\n * never silently masked.\n *\n * @typeParam T - The value type (preserved in the return type).\n * @param value - The value to snapshot.\n * @returns A plain, structured-cloneable copy of `value`.\n * @throws {TypeError} If `value` contains a function or symbol.\n *\n * @example\n * ```ts\n * import { toCloneableSnapshot } from '@napplet/core';\n * // In a Svelte 5 napplet, filters/relays are $state proxies:\n * napplet.outbox.subscribe(\n * toCloneableSnapshot(filters),\n * { relays: toCloneableSnapshot(relays), live: true },\n * );\n * ```\n */\nexport function toCloneableSnapshot<T>(value: T): T {\n return snapshot(value, new WeakMap()) as T;\n}\n\nfunction snapshot(value: unknown, seen: WeakMap<object, unknown>): unknown {\n if (value === null) return null;\n\n const type = typeof value;\n if (type === 'function' || type === 'symbol') {\n throw new TypeError(\n `toCloneableSnapshot: a ${type} is not structured-cloneable and cannot be sent across the napplet boundary`,\n );\n }\n if (type !== 'object') return value; // string, number, boolean, bigint, undefined\n\n const obj = value as object;\n const existing = seen.get(obj);\n if (existing !== undefined) return existing; // cyclic / shared reference\n\n if (isCloneableLeaf(obj)) return obj;\n\n if (Array.isArray(obj)) {\n const out: unknown[] = [];\n seen.set(obj, out);\n for (const item of obj) out.push(snapshot(item, seen));\n return out;\n }\n\n if (obj instanceof Map) {\n const out = new Map<unknown, unknown>();\n seen.set(obj, out);\n for (const [k, v] of obj) out.set(snapshot(k, seen), snapshot(v, seen));\n return out;\n }\n\n if (obj instanceof Set) {\n const out = new Set<unknown>();\n seen.set(obj, out);\n for (const v of obj) out.add(snapshot(v, seen));\n return out;\n }\n\n // Plain object, class instance, or reactive proxy: rebuild from own\n // enumerable keys (reads pass transparently through framework proxies).\n const out: Record<string, unknown> = {};\n seen.set(obj, out);\n for (const key of Object.keys(obj)) {\n out[key] = snapshot((obj as Record<string, unknown>)[key], seen);\n }\n return out;\n}\n\nfunction isDataCloneError(err: unknown): boolean {\n return err instanceof Error && err.name === 'DataCloneError';\n}\n\nfunction cloneError(type: string, cause: unknown): Error {\n const err = new Error(\n `napplet boundary: message \"${type}\" could not be sent -- an argument is not ` +\n `structured-cloneable (a reactive Proxy?). Svelte 5 $state / Vue reactive / ` +\n `Solid store values can't be postMessage'd. Pass a plain snapshot ` +\n `(e.g. $state.snapshot(x), toCloneableSnapshot(x) from @napplet/core), or ` +\n `call setCloneMode('snapshot') to normalize automatically.`,\n );\n err.name = 'NappletDataCloneError';\n (err as { cause?: unknown }).cause = cause;\n return err;\n}\n\n/**\n * Post a JSON envelope to the shell across the iframe boundary, handling\n * non-structured-cloneable arguments per the active {@link CloneMode}.\n *\n * This is the single boundary chokepoint every NAP shim uses instead of calling\n * `target.postMessage(msg, '*')` directly, so that a `DataCloneError` is either\n * transparently recovered (`'auto'`/`'snapshot'`) or raised as a loud,\n * actionable, synchronous error (`'strict'`, or any mode when the value is\n * genuinely non-cloneable) rather than swallowed in an async path.\n *\n * @param target - The post target (NAP shims pass `window.parent`).\n * @param message - The envelope; must carry a `type` discriminator.\n * @param targetOrigin - The `postMessage` target origin (defaults to `'*'`).\n * @throws {Error} `NappletDataCloneError` if the message cannot be made cloneable.\n *\n * @example\n * ```ts\n * import { sendEnvelope } from '@napplet/core';\n * sendEnvelope(window.parent, { type: 'outbox.subscribe', id, subId, filters });\n * ```\n */\nexport function sendEnvelope<T extends { type: string }>(\n target: PostMessageTarget,\n message: T,\n targetOrigin = '*',\n): void {\n if (cloneMode === 'snapshot') {\n let snap: unknown;\n try {\n snap = toCloneableSnapshot(message);\n } catch (cause) {\n throw cloneError(message.type, cause);\n }\n try {\n target.postMessage(snap, targetOrigin);\n } catch (cause) {\n if (isDataCloneError(cause)) throw cloneError(message.type, cause);\n throw cause;\n }\n return;\n }\n\n try {\n target.postMessage(message, targetOrigin);\n return;\n } catch (cause) {\n if (!isDataCloneError(cause)) throw cause;\n if (cloneMode === 'strict') throw cloneError(message.type, cause);\n\n // 'auto': snapshot-and-retry, then warn once per message type.\n try {\n target.postMessage(toCloneableSnapshot(message), targetOrigin);\n } catch {\n throw cloneError(message.type, cause);\n }\n if (!warnedTypes.has(message.type)) {\n warnedTypes.add(message.type);\n console.warn(\n `napplet boundary: \"${message.type}\" received a non-cloneable argument ` +\n `(a reactive Proxy?) and was auto-snapshotted before sending. Pass a ` +\n `plain snapshot (e.g. $state.snapshot(x), toCloneableSnapshot(x)) to ` +\n `silence this, or call setCloneMode('strict') to make it throw.`,\n );\n }\n }\n}\n","\nimport type { Capability } from '@kehto/acl/capabilities';\nimport type { NapMessage } from '@kehto/acl';\nimport type { AclCheckEvent } from './types.js';\n\n// Re-export NAP capability resolution for consumers who import through enforce.ts\nexport { resolveCapabilitiesNap } from '@kehto/acl';\nexport type { NapMessage } from '@kehto/acl';\n\n/**\n * Result of an enforcement check.\n *\n * @param allowed - Whether the capability check passed\n * @param capability - The capability that was checked (human-readable string)\n * @param reason - Why the decision was reached (v1.7 CLASS-03 / D7). Always set on return.\n */\nexport interface EnforceResult {\n allowed: boolean;\n capability: Capability;\n /**\n * Why the decision was reached. Always set on the return path.\n * Distinct from AclCheckEvent.reason (which is optional for backwards compat).\n */\n reason: 'allowed' | 'capability-missing';\n}\n\n/**\n * Identity lookup function type — resolves a pubkey to its full identity.\n * Provided by sessionRegistry at runtime.\n */\nexport type IdentityResolver = (pubkey: string) => { dTag: string; aggregateHash: string } | undefined;\n\n/**\n * ACL check function type — performs the actual capability check.\n * Provided by @kehto/acl's check() at runtime, or by the legacy aclStore.check().\n */\nexport type AclChecker = (pubkey: string, dTag: string, aggregateHash: string, capability: Capability) => boolean;\n\n/**\n * Enforcement gate configuration.\n *\n * @param checkAcl - The ACL check function (wraps @kehto/acl or legacy aclStore)\n * @param resolveIdentity - Maps pubkey to full identity (dTag, aggregateHash)\n * @param onAclCheck - Optional audit callback. Called on every enforce() check\n * with the identity, capability, and decision.\n */\nexport interface EnforceConfig {\n checkAcl: AclChecker;\n resolveIdentity: IdentityResolver;\n onAclCheck?: (event: AclCheckEvent) => void;\n}\n\n/**\n * Create an enforcement gate with the given configuration.\n *\n * Returns a function that checks a single capability for a given pubkey.\n * Every call is logged to the audit callback.\n *\n * @param config - Enforcement configuration with ACL checker, identity resolver, and audit hooks\n * @returns An enforce function that checks capabilities and logs decisions\n *\n * @example\n * ```ts\n * const gate = createEnforceGate({\n * checkAcl: aclStore.check,\n * resolveIdentity: (pk) => nappKeyRegistry.getEntry(pk),\n * onAclCheck: hooks.onAclCheck,\n * });\n * const result = gate('abc123...', 'relay:write');\n * // result.allowed === true | false\n * ```\n */\nexport function createEnforceGate(config: EnforceConfig): (pubkey: string, capability: Capability, message?: unknown[]) => EnforceResult {\n const { checkAcl, resolveIdentity, onAclCheck } = config;\n\n return function enforce(pubkey: string, capability: Capability, message?: unknown[]): EnforceResult {\n const entry = resolveIdentity(pubkey);\n const dTag = entry?.dTag ?? '';\n const aggregateHash = entry?.aggregateHash ?? '';\n\n const allowed = checkAcl(pubkey, dTag, aggregateHash, capability);\n\n // Audit logging — every check, both allows and denials\n const identity = { pubkey, dTag, hash: aggregateHash };\n const decision = allowed ? 'allow' as const : 'deny' as const;\n const reason = allowed ? 'allowed' as const : 'capability-missing' as const;\n\n if (onAclCheck) {\n onAclCheck({ identity, capability, decision, message, reason });\n }\n\n return { allowed, capability, reason };\n };\n}\n\n/**\n * Enforcement gate configuration for NIP-5D NAP handlers.\n * Uses windowId for identity resolution instead of pubkey (which is '' in NIP-5D sessions).\n *\n * @param checkAcl - The ACL check function\n * @param resolveIdentityByWindowId - Maps windowId to identity (dTag, aggregateHash).\n * @param onAclCheck - Optional audit callback, called on every enforceNap() check\n */\nexport interface NapEnforceConfig {\n checkAcl: AclChecker;\n resolveIdentityByWindowId: (windowId: string) => { dTag: string; aggregateHash: string } | undefined;\n onAclCheck?: (event: AclCheckEvent) => void;\n}\n\n/**\n * Create an enforcement gate for NIP-5D NAP message handlers.\n *\n * Unlike createEnforceGate (which resolves identity by pubkey), this factory\n * resolves identity by windowId — necessary for NIP-5D sessions where pubkey is ''.\n *\n * @param config - NAP enforcement configuration\n * @returns An enforceNap function that resolves identity by windowId and\n * delegates to the ACL check.\n *\n * @example\n * ```ts\n * const gate = createNapEnforceGate({\n * checkAcl: aclStore.check,\n * resolveIdentityByWindowId: (wid) => sessionRegistry.getEntryByWindowId(wid),\n * onAclCheck: hooks.onAclCheck,\n * });\n * const result = gate('win-1', 'relay:write', { type: 'relay.publish' });\n * // result.allowed === true | false\n * ```\n */\nexport function createNapEnforceGate(config: NapEnforceConfig): (windowId: string, capability: Capability, message?: NapMessage) => EnforceResult {\n const { checkAcl, resolveIdentityByWindowId, onAclCheck } = config;\n\n return function enforceNap(windowId: string, capability: Capability, message?: NapMessage): EnforceResult {\n const entry = resolveIdentityByWindowId(windowId);\n const dTag = entry?.dTag ?? '';\n const aggregateHash = entry?.aggregateHash ?? '';\n\n // NIP-5D: pass empty string for pubkey - toKey() ignores it (uses dTag:hash).\n const allowed = checkAcl('', dTag, aggregateHash, capability);\n\n const identity = { pubkey: '', dTag, hash: aggregateHash };\n const decision = allowed ? 'allow' as const : 'deny' as const;\n const reason = allowed ? 'allowed' as const : 'capability-missing' as const;\n\n if (onAclCheck) {\n onAclCheck({ identity, capability, decision, message, reason });\n }\n\n return { allowed, capability, reason };\n };\n}\n\n/**\n * Format a denial reason string with the standard 'denied:' prefix.\n *\n * @param capability - The denied capability name\n * @returns Formatted denial string, e.g., 'denied: relay:write'\n *\n * @example\n * ```ts\n * formatDenialReason('relay:write')\n * // => 'denied: relay:write'\n * ```\n */\nexport function formatDenialReason(capability: Capability): string {\n return `denied: ${capability}`;\n}\n","\nimport type { SessionEntry, PendingUpdate, PendingUpdateNotifier } from './types.js';\n\n/**\n * Bidirectional registry mapping windowIds to verified napplet pubkeys.\n * Maintained by the runtime after successful AUTH handshakes.\n *\n * @example\n * ```ts\n * const registry = createSessionRegistry();\n * registry.register('win-1', entry);\n * const pubkey = registry.getPubkey('win-1');\n * ```\n */\nexport interface SessionRegistry {\n /** Register a napplet entry, mapping windowId to pubkey and vice versa. */\n register(windowId: string, entry: SessionEntry): void;\n /** Unregister a napplet by windowId, removing both mappings. */\n unregister(windowId: string): void;\n /** Get the pubkey associated with a windowId. */\n getPubkey(windowId: string): string | undefined;\n /** Get the full entry for a napplet pubkey. */\n getEntry(pubkey: string): SessionEntry | undefined;\n /** Get the windowId for a napplet pubkey. */\n getWindowId(pubkey: string): string | undefined;\n /** Check if a windowId has a registered napplet. */\n isRegistered(windowId: string): boolean;\n /** Get all registered napplet entries. */\n getAllEntries(): SessionEntry[];\n /** Get the instance GUID for a window. */\n getInstanceId(windowId: string): string | undefined;\n /** Set a pending update for a window (napplet reconnected with different hash). */\n setPendingUpdate(windowId: string, update: PendingUpdate): void;\n /** Get a pending update for a window. */\n getPendingUpdate(windowId: string): PendingUpdate | undefined;\n /** Clear a pending update for a window. */\n clearPendingUpdate(windowId: string): void;\n /**\n * Get the full entry for a napplet by windowId directly.\n * NIP-5D: Required for sessions where pubkey is '' (identity established via originRegistry).\n * Unlike getEntry(pubkey), this works when pubkey is empty.\n */\n getEntryByWindowId(windowId: string): SessionEntry | undefined;\n /** Clear all registrations and pending updates. */\n clear(): void;\n}\n\n/** @deprecated Use SessionRegistry. Will be removed in v0.9.0. */\nexport type NappKeyRegistry = SessionRegistry;\n\n/**\n * Create a new SessionRegistry instance.\n *\n * @param notifier - Optional callback invoked when pending updates change\n * @returns A SessionRegistry instance\n *\n * @example\n * ```ts\n * const registry = createSessionRegistry((windowId) => {\n * console.log('Pending update changed for', windowId);\n * });\n * ```\n */\nexport function createSessionRegistry(notifier?: PendingUpdateNotifier): SessionRegistry {\n const byWindowId = new Map<string, string>();\n const byPubkey = new Map<string, SessionEntry>();\n const byWindowIdEntry = new Map<string, SessionEntry>();\n const pendingUpdates = new Map<string, PendingUpdate>();\n\n return {\n register(windowId: string, entry: SessionEntry): void {\n byWindowId.set(windowId, entry.pubkey);\n byPubkey.set(entry.pubkey, entry);\n byWindowIdEntry.set(windowId, entry);\n },\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 byWindowIdEntry.delete(windowId);\n pendingUpdates.delete(windowId);\n },\n\n getPubkey(windowId: string): string | undefined {\n return byWindowId.get(windowId);\n },\n\n getEntry(pubkey: string): SessionEntry | undefined {\n return byPubkey.get(pubkey);\n },\n\n getWindowId(pubkey: string): string | undefined {\n return byPubkey.get(pubkey)?.windowId;\n },\n\n isRegistered(windowId: string): boolean {\n return byWindowId.has(windowId);\n },\n\n getAllEntries(): SessionEntry[] {\n return Array.from(byPubkey.values());\n },\n\n getInstanceId(windowId: string): string | undefined {\n const pubkey = byWindowId.get(windowId);\n if (!pubkey) return undefined;\n return byPubkey.get(pubkey)?.instanceId;\n },\n\n getEntryByWindowId(windowId: string): SessionEntry | undefined {\n return byWindowIdEntry.get(windowId);\n },\n\n setPendingUpdate(windowId: string, update: PendingUpdate): void {\n pendingUpdates.set(windowId, update);\n notifier?.(windowId);\n },\n\n getPendingUpdate(windowId: string): PendingUpdate | undefined {\n return pendingUpdates.get(windowId);\n },\n\n clearPendingUpdate(windowId: string): void {\n pendingUpdates.delete(windowId);\n notifier?.(windowId);\n },\n\n clear(): void {\n byWindowId.clear();\n byPubkey.clear();\n byWindowIdEntry.clear();\n pendingUpdates.clear();\n },\n };\n}\n\n/** @deprecated Use createSessionRegistry. Will be removed in v0.9.0. */\nexport const createNappKeyRegistry = createSessionRegistry;\n","/**\n * acl-state.ts — ACL state container with persistence hooks.\n *\n * Wraps @kehto/acl's pure functions with persistence via\n * AclPersistence. No localStorage or DOM references.\n */\n\nimport type { Capability } from '@kehto/acl/capabilities';\nimport type { AclState, Identity } from '@kehto/acl';\nimport {\n createState, check, grant, revoke, block, unblock,\n serialize, deserialize, getQuota,\n CAP_RELAY_READ, CAP_RELAY_WRITE, CAP_CACHE_READ, CAP_CACHE_WRITE,\n CAP_HOTKEY_FORWARD,\n CAP_STATE_READ, CAP_STATE_WRITE,\n toKey,\n} from '@kehto/acl';\nimport type { AclPersistence, AclEntryExternal } from './types.js';\n\nconst CAP_IDENTITY_READ = 1 << 5; // 32 (reclaimed from CAP_SIGN_EVENT)\nconst CAP_KEYS_BIND = 1 << 6; // 64 (reclaimed from CAP_SIGN_NIP04)\nconst CAP_KEYS_FORWARD = 1 << 7; // 128 (reclaimed from CAP_SIGN_NIP44)\nconst CAP_MEDIA_CONTROL = 1 << 10; // 1024\nconst CAP_NOTIFY_SEND = 1 << 11; // 2048\nconst CAP_NOTIFY_CHANNEL = 1 << 12; // 4096\nconst CAP_THEME_READ = 1 << 13; // 8192\nconst CAP_CONFIG_READ = 1 << 14; // 16384 (v1.7 Phase 39 NAP-CONFIG)\nconst CAP_RESOURCE_FETCH = 1 << 15; // 32768 (v1.7 Phase 40 NAP-RESOURCE)\n// 1 << 16 (65536) RETIRED — was identity:decrypt (v1.8); removed as a spec\n// violation. Left as a permanent gap; do NOT reuse this bit (persisted ACL\n// grants are bitfields — reassigning it would silently re-grant old state).\nconst CAP_CVM_CALL = 1 << 17; // 131072 (NAP-CVM ContextVM bridge)\nconst CAP_OUTBOX_READ = 1 << 18; // 262144 (NAP-OUTBOX read-side routing)\nconst CAP_OUTBOX_WRITE = 1 << 19; // 524288 (NAP-OUTBOX shell-signed publish)\nconst CAP_UPLOAD_WRITE = 1 << 20; // 1048576 (NAP-UPLOAD shell-mediated upload)\nconst CAP_INTENT_READ = 1 << 21; // 2097152 (NAP-INTENT archetype introspection)\nconst CAP_INTENT_WRITE = 1 << 22; // 4194304 (NAP-INTENT cross-napplet dispatch)\n\nconst CAP_MAP: Record<Capability, number> = {\n 'relay:read': CAP_RELAY_READ,\n 'relay:write': CAP_RELAY_WRITE,\n 'cache:read': CAP_CACHE_READ,\n 'cache:write': CAP_CACHE_WRITE,\n 'hotkey:forward': CAP_HOTKEY_FORWARD,\n 'state:read': CAP_STATE_READ,\n 'state:write': CAP_STATE_WRITE,\n 'identity:read': CAP_IDENTITY_READ,\n 'keys:bind': CAP_KEYS_BIND,\n 'keys:forward': CAP_KEYS_FORWARD,\n 'media:control': CAP_MEDIA_CONTROL,\n 'notify:send': CAP_NOTIFY_SEND,\n 'notify:channel': CAP_NOTIFY_CHANNEL,\n 'theme:read': CAP_THEME_READ,\n 'config:read': CAP_CONFIG_READ,\n 'resource:fetch': CAP_RESOURCE_FETCH,\n 'cvm:call': CAP_CVM_CALL,\n 'outbox:read': CAP_OUTBOX_READ,\n 'outbox:write': CAP_OUTBOX_WRITE,\n 'upload:write': CAP_UPLOAD_WRITE,\n 'intent:read': CAP_INTENT_READ,\n 'intent:write': CAP_INTENT_WRITE,\n};\n\nconst RUNTIME_CAP_ALL = Object.values(CAP_MAP).reduce((bits, bit) => bits | bit, 0);\n\nfunction capToBit(cap: Capability): number {\n return CAP_MAP[cap] ?? 0;\n}\n\n/** Convert a bitfield to an array of capability strings. */\nfunction bitsToCapabilities(bits: number): Capability[] {\n const result: Capability[] = [];\n for (const [cap, bit] of Object.entries(CAP_MAP)) {\n if (bits & bit) result.push(cap as Capability);\n }\n return result;\n}\n\nfunction toIdentity(pubkey: string, dTag: string, hash: string): Identity {\n return { pubkey, dTag, hash };\n}\n\n/**\n * ACL state container — wraps @kehto/acl's pure functions with\n * persistence and a convenient imperative API.\n *\n * @example\n * ```ts\n * const aclState = createAclState(persistence);\n * aclState.load();\n * const allowed = aclState.check(pubkey, dTag, hash, 'relay:read');\n * ```\n */\nexport interface AclStateContainer {\n check(pubkey: string, dTag: string, aggregateHash: string, capability: Capability): boolean;\n grant(pubkey: string, dTag: string, aggregateHash: string, capability: Capability): void;\n revoke(pubkey: string, dTag: string, aggregateHash: string, capability: Capability): void;\n block(pubkey: string, dTag: string, aggregateHash: string): void;\n unblock(pubkey: string, dTag: string, aggregateHash: string): void;\n isBlocked(pubkey: string, dTag: string, aggregateHash: string): boolean;\n getEntry(pubkey: string, dTag: string, aggregateHash: string): AclEntryExternal | undefined;\n getAllEntries(): AclEntryExternal[];\n getStateQuota(pubkey: string, dTag: string, aggregateHash: string): number;\n persist(): void;\n load(): void;\n clear(): void;\n}\n\n/**\n * Create an ACL state container backed by @kehto/acl and persisted\n * via the given persistence hooks.\n *\n * @param persistence - Storage backend for ACL state\n * @param defaultPolicy - Default ACL policy for unknown identities\n * @returns An AclStateContainer instance\n *\n * @example\n * ```ts\n * const aclState = createAclState(persistence, 'permissive');\n * aclState.load();\n * ```\n */\nexport function createAclState(\n persistence: AclPersistence,\n defaultPolicy: 'permissive' | 'restrictive' = 'permissive',\n): AclStateContainer {\n let state: AclState = createState(defaultPolicy);\n\n function ensureRuntimeDefaultEntry(id: Identity): void {\n if (state.defaultPolicy !== 'permissive') return;\n if (state.entries[toKey(id)]) return;\n state = grant(state, id, RUNTIME_CAP_ALL);\n }\n\n return {\n check(pubkey: string, dTag: string, aggregateHash: string, capability: Capability): boolean {\n const id = toIdentity(pubkey, dTag, aggregateHash);\n return check(state, id, capToBit(capability));\n },\n\n grant(pubkey: string, dTag: string, aggregateHash: string, capability: Capability): void {\n const id = toIdentity(pubkey, dTag, aggregateHash);\n ensureRuntimeDefaultEntry(id);\n state = grant(state, id, capToBit(capability));\n },\n\n revoke(pubkey: string, dTag: string, aggregateHash: string, capability: Capability): void {\n const id = toIdentity(pubkey, dTag, aggregateHash);\n ensureRuntimeDefaultEntry(id);\n state = revoke(state, id, capToBit(capability));\n },\n\n block(pubkey: string, dTag: string, aggregateHash: string): void {\n const id = toIdentity(pubkey, dTag, aggregateHash);\n ensureRuntimeDefaultEntry(id);\n state = block(state, id);\n },\n\n unblock(pubkey: string, dTag: string, aggregateHash: string): void {\n const id = toIdentity(pubkey, dTag, aggregateHash);\n ensureRuntimeDefaultEntry(id);\n state = unblock(state, id);\n },\n\n isBlocked(pubkey: string, dTag: string, aggregateHash: string): boolean {\n const id = toIdentity(pubkey, dTag, aggregateHash);\n // A blocked identity fails all checks — check with all runtime caps.\n // If blocked, check returns false even for all caps\n return !check(state, id, RUNTIME_CAP_ALL) && this.getEntry(pubkey, dTag, aggregateHash)?.blocked === true;\n },\n\n getEntry(pubkey: string, dTag: string, aggregateHash: string): AclEntryExternal | undefined {\n const id = toIdentity(pubkey, dTag, aggregateHash);\n const key = `${id.dTag}:${id.hash}`;\n const entry = state.entries[key];\n if (!entry) return undefined;\n return {\n pubkey: pubkey || '',\n capabilities: bitsToCapabilities(entry.caps),\n blocked: entry.blocked,\n stateQuota: entry.quota,\n };\n },\n\n getAllEntries(): AclEntryExternal[] {\n return Object.entries(state.entries).map(([, entry]) => {\n return {\n pubkey: '',\n capabilities: bitsToCapabilities(entry.caps),\n blocked: entry.blocked,\n stateQuota: entry.quota,\n };\n });\n },\n\n getStateQuota(pubkey: string, dTag: string, aggregateHash: string): number {\n const id = toIdentity(pubkey, dTag, aggregateHash);\n return getQuota(state, id);\n },\n\n persist(): void {\n try {\n persistence.persist(serialize(state));\n } catch { /* persistence is best-effort */ }\n },\n\n load(): void {\n try {\n const raw = persistence.load();\n if (!raw) return;\n state = deserialize(raw);\n } catch {\n state = createState(defaultPolicy);\n }\n },\n\n clear(): void {\n state = createState(defaultPolicy);\n try { persistence.persist(''); } catch { /* best-effort */ }\n },\n };\n}\n","/**\n * firewall-state.ts — Firewall state container with persistence hooks.\n *\n * Wraps @kehto/firewall's pure functions with persistence via\n * FirewallPersistence. No localStorage or DOM references.\n *\n * Two independent `let`-bound cells:\n * - `config` — immutable FirewallConfig; persisted on each mutation.\n * - `counters` — ephemeral FirewallState (token-buckets + burst counters);\n * in-memory only, reset on reload (RUNTIME-03).\n *\n * CRITICAL: `evaluate` reassigns `counters = result.newState` on every call.\n * Without this, token buckets never advance and flood events never escalate\n * from 'flag' to 'block'.\n */\n\nimport type {\n Observation,\n FirewallConfig,\n FirewallState,\n NappletPolicy,\n RateLimit,\n ContentMatcher,\n EvaluateResult,\n} from '@kehto/firewall';\nimport {\n evaluate,\n defaultConfig,\n createState,\n serialize,\n deserialize,\n setPolicy,\n setRateLimit,\n setGlobalRate,\n addMatcher,\n} from '@kehto/firewall';\nimport type { FirewallPersistence } from './types.js';\n\n/**\n * Stateful firewall container — wraps @kehto/firewall's pure functions\n * with persistence and a convenient imperative API.\n *\n * Mirrors AclStateContainer from acl-state.ts in structure and naming.\n *\n * @example\n * ```ts\n * const firewall = createFirewallState(persistence);\n * firewall.load();\n * const result = firewall.evaluate({ napplet: 'chat', opClass: 'relay:write', focused: true, now: Date.now() });\n * ```\n */\nexport interface FirewallStateContainer {\n /**\n * Evaluate an observation against the current firewall config and counters.\n * CRITICAL: advances the in-memory counter state on each call.\n *\n * @param observation - Normalized observation extracted from the napplet message envelope.\n * @returns The full EvaluateResult (decision, action, ruleId, reason, newState).\n */\n evaluate(observation: Observation): EvaluateResult;\n /**\n * Set a per-napplet policy override (allow / deny / ask).\n *\n * @param napplet - The napplet dTag (version-agnostic identity key).\n * @param policy - Hard policy override for this napplet.\n */\n setPolicy(napplet: string, policy: NappletPolicy): void;\n /**\n * Set a per-(napplet, opClass) token-bucket rate limit.\n *\n * @param napplet - The napplet dTag.\n * @param opClass - The operation class string.\n * @param limit - The rate limit to apply.\n */\n setRateLimit(napplet: string, opClass: string, limit: RateLimit): void;\n /**\n * Set a global rate limit applied to all op-classes that have no specific entry.\n *\n * @param napplet - The napplet dTag.\n * @param limit - The global fallback rate limit.\n */\n setGlobalRate(napplet: string, limit: RateLimit): void;\n /**\n * Add a content matcher to the firewall config.\n *\n * @param matcher - The content matcher to append.\n */\n addMatcher(matcher: ContentMatcher): void;\n /** Return the current firewall config. */\n getConfig(): FirewallConfig;\n /** Persist the current firewall config via the persistence hook. Best-effort. */\n persist(): void;\n /** Load previously persisted firewall config. Counters are NOT restored. */\n load(): void;\n /** Reset config to defaultConfig() and counters to createState(), then persist empty. */\n clear(): void;\n}\n\n/**\n * Create a firewall state container backed by @kehto/firewall and optionally\n * persisted via the given persistence hooks.\n *\n * When `persistence` is absent (or undefined), firewall config is in-memory\n * only and resets on container recreation. This is safe for hosts that do not\n * need durable per-napplet policies.\n *\n * @param persistence - Optional storage backend for firewall config.\n * @returns A FirewallStateContainer instance.\n *\n * @example\n * ```ts\n * const firewall = createFirewallState(persistence);\n * firewall.load();\n * firewall.setPolicy('chat', 'allow');\n * firewall.persist();\n * ```\n */\nexport function createFirewallState(\n persistence?: FirewallPersistence,\n): FirewallStateContainer {\n let config: FirewallConfig = defaultConfig();\n let counters: FirewallState = createState();\n\n return {\n evaluate(observation: Observation): EvaluateResult {\n const result = evaluate(config, counters, observation);\n counters = result.newState; // CRITICAL: advance ephemeral counter state\n return result;\n },\n\n setPolicy(napplet: string, policy: NappletPolicy): void {\n config = setPolicy(config, napplet, policy);\n },\n\n setRateLimit(napplet: string, opClass: string, limit: RateLimit): void {\n config = setRateLimit(config, napplet, opClass, limit);\n },\n\n setGlobalRate(napplet: string, limit: RateLimit): void {\n config = setGlobalRate(config, napplet, limit);\n },\n\n addMatcher(matcher: ContentMatcher): void {\n config = addMatcher(config, matcher);\n },\n\n getConfig(): FirewallConfig {\n return config;\n },\n\n persist(): void {\n try {\n persistence?.persist(serialize(config));\n } catch { /* persistence is best-effort */ }\n },\n\n load(): void {\n try {\n const raw = persistence?.load() ?? null;\n if (!raw) return;\n config = deserialize(raw);\n // counters are deliberately NOT restored (RUNTIME-03: ephemeral)\n } catch {\n config = defaultConfig();\n }\n },\n\n clear(): void {\n config = defaultConfig();\n counters = createState();\n try { persistence?.persist(''); } catch { /* best-effort */ }\n },\n };\n}\n","/**\n * manifest-cache.ts — Manifest cache with persistence hooks.\n *\n * Caches NIP-5A manifest data (aggregate hashes) per napplet identity.\n * Delegates storage to ManifestPersistence — no localStorage.\n */\n\nimport type { ManifestPersistence, ManifestCacheEntry, VerificationCacheEntry } from './types.js';\n\n/**\n * Cache for verified napplet manifest entries.\n * Used to detect napplet updates (aggregateHash changes) across sessions.\n *\n * @example\n * ```ts\n * const cache = createManifestCache(persistence);\n * cache.load();\n * cache.set({ pubkey: 'abc...', dTag: 'chat', aggregateHash: 'dead', verifiedAt: Date.now() });\n * ```\n */\nexport interface ManifestCache {\n /** Get a cached manifest entry by pubkey and dTag. */\n get(pubkey: string, dTag: string): ManifestCacheEntry | undefined;\n /** Set (upsert) a manifest cache entry and persist. */\n set(entry: ManifestCacheEntry): void;\n /** Check if a specific hash is cached for a pubkey/dTag combination. */\n has(pubkey: string, dTag: string, hash: string): boolean;\n /** Get the requires list for a cached manifest, or empty array if not found. */\n getRequires(pubkey: string, dTag: string): string[];\n /** Remove a cached entry for a pubkey/dTag and persist. */\n remove(pubkey: string, dTag: string): void;\n /** Load the cache from persistence. */\n load(): void;\n /** Persist the cache to storage. */\n persist(): void;\n /** Clear all cached entries. */\n clear(): void;\n\n /** Get a cached verification result by manifest event ID. */\n getVerification(eventId: string): VerificationCacheEntry | undefined;\n\n /** Cache a verification result keyed by manifest event ID. */\n setVerification(eventId: string, result: VerificationCacheEntry): void;\n\n /** Check if a manifest event ID has been verified. */\n hasVerification(eventId: string): boolean;\n\n /** Clear all verification cache entries. */\n clearVerifications(): void;\n}\n\n/**\n * Create a manifest cache backed by the given persistence hooks.\n *\n * @param persistence - Storage backend for manifest data\n * @returns A ManifestCache instance\n *\n * @example\n * ```ts\n * import { createManifestCache } from '@kehto/runtime';\n *\n * const cache = createManifestCache(manifestPersistence);\n * cache.load();\n * cache.set({ pubkey: 'abc...', dTag: 'chat', aggregateHash: 'dead', verifiedAt: Date.now() });\n * cache.has('abc...', 'chat', 'dead'); // true\n * ```\n */\nexport function createManifestCache(persistence: ManifestPersistence): ManifestCache {\n const cache = new Map<string, ManifestCacheEntry>();\n const verificationCache = new Map<string, VerificationCacheEntry>();\n\n function cacheKey(pubkey: string, dTag: string): string {\n return `${pubkey}:${dTag}`;\n }\n\n const self: ManifestCache = {\n get(pubkey: string, dTag: string): ManifestCacheEntry | undefined {\n return cache.get(cacheKey(pubkey, dTag));\n },\n\n set(entry: ManifestCacheEntry): void {\n cache.set(cacheKey(entry.pubkey, entry.dTag), entry);\n self.persist();\n },\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 getRequires(pubkey: string, dTag: string): string[] {\n const entry = cache.get(cacheKey(pubkey, dTag));\n return entry?.requires ?? [];\n },\n\n remove(pubkey: string, dTag: string): void {\n cache.delete(cacheKey(pubkey, dTag));\n self.persist();\n },\n\n load(): void {\n try {\n const raw = persistence.load();\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 { cache.clear(); }\n },\n\n persist(): void {\n try {\n persistence.persist(JSON.stringify(Array.from(cache.entries())));\n } catch { /* persistence is best-effort */ }\n },\n\n clear(): void {\n cache.clear();\n verificationCache.clear();\n try { persistence.persist(''); } catch { /* best-effort */ }\n },\n\n getVerification(eventId: string): VerificationCacheEntry | undefined {\n return verificationCache.get(eventId);\n },\n\n setVerification(eventId: string, result: VerificationCacheEntry): void {\n verificationCache.set(eventId, result);\n // Verification cache is in-memory only — no persistence needed\n // since manifest event IDs are immutable and re-fetching is cheap\n },\n\n hasVerification(eventId: string): boolean {\n return verificationCache.has(eventId);\n },\n\n clearVerifications(): void {\n verificationCache.clear();\n },\n };\n\n return self;\n}\n","/**\n * replay.ts — Replay detection module.\n *\n * Tracks seen event IDs and validates timestamps to prevent\n * duplicate event processing and replay attacks.\n */\n\nimport type { NostrEvent } from '@napplet/core';\n\n/**\n * Replay detection window in seconds — events older than this are rejected.\n * Relocated inline from the former @napplet/core compatibility shim\n * (DRIFT-CORE-06, Phase 24). Numeric value preserved unchanged from the shim\n * to hold behavioral parity with the Phase 23 test baseline.\n */\nconst REPLAY_WINDOW_SECONDS = 30;\n\n/**\n * Replay detection engine. Tracks seen event IDs and validates timestamps.\n *\n * @example\n * ```ts\n * const detector = createReplayDetector();\n * const reason = detector.check(event);\n * if (reason !== null) { // reject event }\n * ```\n */\nexport interface ReplayDetector {\n /**\n * Check if an event should be rejected as a replay.\n * Returns null if event is valid, or a string reason if it should be rejected.\n */\n check(event: NostrEvent): string | null;\n\n /** Clear all tracked event IDs. */\n clear(): void;\n}\n\n/**\n * Create a replay detector that rejects duplicate events and events\n * with timestamps outside the replay window.\n *\n * @param getReplayWindow - Optional getter for a dynamic replay window override.\n * When provided, its return value is used instead of the module-level constant.\n * Called on every check, so changes take effect immediately.\n * @returns A ReplayDetector instance\n *\n * @example\n * ```ts\n * import { createReplayDetector } from '@kehto/runtime';\n *\n * const detector = createReplayDetector();\n * const reason = detector.check(event);\n * if (reason !== null) {\n * // Reject — duplicate, stale, or future-dated\n * }\n * ```\n */\nexport function createReplayDetector(getReplayWindow?: () => number | undefined): ReplayDetector {\n const seenEventIds = new Map<string, number>();\n\n return {\n check(event: NostrEvent): string | null {\n const replayWindow = getReplayWindow?.() ?? REPLAY_WINDOW_SECONDS;\n const now = Math.floor(Date.now() / 1000);\n if (now - event.created_at > replayWindow) return 'invalid: event created_at too old';\n if (event.created_at - now > 10) return 'invalid: event created_at in the future';\n if (seenEventIds.has(event.id)) return 'duplicate: already processed';\n seenEventIds.set(event.id, now);\n for (const [id, timestamp] of seenEventIds) {\n if (now - timestamp > replayWindow) seenEventIds.delete(id);\n }\n return null;\n },\n\n clear(): void {\n seenEventIds.clear();\n },\n };\n}\n","/**\n * event-buffer.ts — Ring buffer and subscription delivery engine.\n *\n * Buffers events in a fixed-size ring buffer and delivers matching\n * events to subscribed napplets via the abstract sendToNapplet transport.\n */\n\nimport type { NostrEvent, NostrFilter } from '@napplet/core';\nimport type { Capability } from '@kehto/acl/capabilities';\nimport type { SendToNapplet } from './types.js';\nimport type { SessionRegistry } from './session-registry.js';\nimport type { EnforceResult } from './enforce.js';\n\n/** Default ring buffer size. */\nexport const RING_BUFFER_SIZE = 100;\n\n/** Subscription entry — tracks a subscription for a specific napplet window. */\nexport interface SubscriptionEntry {\n windowId: string;\n filters: NostrFilter[];\n}\n\n/**\n * Check if an event matches a single NIP-01 filter.\n * Pure function — no side effects.\n *\n * @param event - The event to check\n * @param filter - The filter to match against\n * @returns True if the event matches the filter\n *\n * @example\n * ```ts\n * import { matchesFilter } from '@kehto/runtime';\n *\n * matchesFilter(event, { kinds: [1], authors: ['abc'] });\n * // true if event.kind === 1 and event.pubkey starts with 'abc'\n * ```\n */\nexport function matchesFilter(event: NostrEvent, filter: NostrFilter): boolean {\n if (filter.ids !== undefined && !filter.ids.some((id) => event.id.startsWith(id))) return false;\n if (filter.authors !== undefined && !filter.authors.some((a) => event.pubkey.startsWith(a))) return false;\n if (filter.kinds !== undefined && !filter.kinds.includes(event.kind)) return false;\n if (filter.since !== undefined && event.created_at < filter.since) return false;\n if (filter.until !== undefined && event.created_at > filter.until) return false;\n for (const [key, values] of Object.entries(filter)) {\n if (!key.startsWith('#') || values === undefined) continue;\n const tagName = key.slice(1);\n const tagValues = values as string[];\n const eventTagValues = event.tags.filter((t) => t[0] === tagName).map((t) => t[1]);\n if (!tagValues.some((v) => eventTagValues.includes(v))) return false;\n }\n return true;\n}\n\n/**\n * Check if an event matches any filter in a list.\n * Returns true for empty filter lists.\n *\n * @param event - The event to check\n * @param filters - The filters to match against\n * @returns True if the event matches any filter (or if filters is empty)\n *\n * @example\n * ```ts\n * import { matchesAnyFilter } from '@kehto/runtime';\n *\n * matchesAnyFilter(event, [{ kinds: [1] }, { kinds: [7] }]);\n * // true if event is kind 1 or kind 7\n * ```\n */\nexport function matchesAnyFilter(event: NostrEvent, filters: NostrFilter[]): boolean {\n if (filters.length === 0) return true;\n return filters.some((filter) => matchesFilter(event, filter));\n}\n\n/** Event buffer and subscription delivery engine. */\nexport interface EventBuffer {\n /** Add an event to the ring buffer and deliver to matching subscriptions. */\n bufferAndDeliver(event: NostrEvent, senderId: string | null): void;\n\n /** Deliver an event to matching subscriptions without buffering. */\n deliverToSubscriptions(event: NostrEvent, senderId: string | null): void;\n\n /** Get the current subscription map (for REQ handler to register subs). */\n getSubscriptions(): Map<string, SubscriptionEntry>;\n\n /** Get buffered events (for REQ replay). */\n getBufferedEvents(): readonly NostrEvent[];\n\n /** Clear the buffer. */\n clear(): void;\n}\n\n/**\n * Create an event buffer with subscription delivery.\n *\n * @param sendToNapplet - Transport function to send messages to napplets\n * @param sessionRegistry - Identity registry for looking up napplet pubkeys\n * @param enforce - Enforcement function for checking relay:read on recipients\n * @param subscriptions - Shared subscription map (owned by the runtime)\n * @param getBufferSize - Optional getter for a dynamic buffer size override.\n * When provided, its return value is used instead of RING_BUFFER_SIZE.\n * Called on every bufferAndDeliver, so changes take effect immediately.\n * @returns An EventBuffer instance\n *\n * @example\n * ```ts\n * import { createEventBuffer } from '@kehto/runtime';\n *\n * const buffer = createEventBuffer(sendToNapplet, sessionRegistry, enforce, subscriptions);\n * buffer.bufferAndDeliver(event, senderWindowId);\n * ```\n */\nexport function createEventBuffer(\n sendToNapplet: SendToNapplet,\n sessionRegistry: SessionRegistry,\n enforce: (pubkey: string, capability: Capability, message?: unknown[]) => EnforceResult,\n subscriptions: Map<string, SubscriptionEntry>,\n getBufferSize?: () => number,\n): EventBuffer {\n const buffer: NostrEvent[] = [];\n\n function deliverToSubscriptions(event: NostrEvent, senderId: string | null): void {\n const pTag = event.tags?.find((t) => t[0] === 'p');\n const targetPubkey = pTag?.[1];\n\n for (const [subKey, sub] of subscriptions) {\n if (senderId !== null && sub.windowId === senderId) continue;\n\n const recipientPubkey = sessionRegistry.getPubkey(sub.windowId);\n if (recipientPubkey) {\n const recipientResult = enforce(recipientPubkey, 'relay:read', ['EVENT', event]);\n if (!recipientResult.allowed) continue;\n }\n\n if (targetPubkey) {\n const subPubkey = recipientPubkey;\n if (subPubkey !== targetPubkey) continue;\n }\n\n if (!matchesAnyFilter(event, sub.filters)) continue;\n\n const prefix = `${sub.windowId}:`;\n if (!subKey.startsWith(prefix)) continue;\n const subId = subKey.slice(prefix.length);\n\n sendToNapplet(sub.windowId, ['EVENT', subId, event]);\n }\n }\n\n return {\n bufferAndDeliver(event: NostrEvent, senderId: string | null): void {\n const maxSize = getBufferSize?.() ?? RING_BUFFER_SIZE;\n if (buffer.length >= maxSize) buffer.shift();\n buffer.push(event);\n deliverToSubscriptions(event, senderId);\n },\n\n deliverToSubscriptions,\n\n getSubscriptions(): Map<string, SubscriptionEntry> { return subscriptions; },\n\n getBufferedEvents(): readonly NostrEvent[] { return buffer; },\n\n clear(): void {\n buffer.length = 0;\n },\n };\n}\n","\nimport { createDispatch, type NappletMessage, type NostrEvent, type NapHandler } from '@napplet/core';\nimport type { Capability } from '@kehto/acl/capabilities';\nimport type { Observation } from '@kehto/firewall';\n\nimport type {\n RuntimeAdapter, ConsentHandler, FirewallEvent,\n ServiceHandler, ServiceRegistry, ServiceInfo,\n} from './types.js';\nimport { notifyServiceWindowDestroyed } from './service-dispatch.js';\nimport { createSessionRegistry, type SessionRegistry } from './session-registry.js';\nimport { createAclState, type AclStateContainer } from './acl-state.js';\nimport { createFirewallState, type FirewallStateContainer } from './firewall-state.js';\nimport { createManifestCache, type ManifestCache } from './manifest-cache.js';\nimport { createReplayDetector, type ReplayDetector } from './replay.js';\nimport { createEventBuffer, RING_BUFFER_SIZE, type EventBuffer, type SubscriptionEntry } from './event-buffer.js';\nimport { createEnforceGate, createNapEnforceGate, resolveCapabilitiesNap, formatDenialReason } from './enforce.js';\nimport { createRelayHandler } from './relay-handler.js';\nimport { createIdentityHandler } from './identity-handler.js';\nimport { createIncRuntime, type IncRuntime } from './inc-handler.js';\nimport { createRuntimeDomainHandlers, type RuntimeDomainHandlers } from './domain-handlers.js';\n\n/**\n * The napplet protocol engine — handles NIP-5D NAP domain dispatch,\n * ACL enforcement, subscription lifecycle.\n *\n * @example\n * ```ts\n * import { createRuntime } from '@kehto/runtime';\n *\n * const runtime = createRuntime(hooks);\n * runtime.handleMessage('window-1', { type: 'relay.req', id: 'sub1', filters: [{ kinds: [1] }] });\n * ```\n */\nexport interface Runtime {\n /**\n * Handle an incoming NIP-5D NappletMessage envelope from a napplet.\n * The caller is responsible for identifying the source (windowId).\n * Legacy NIP-01 arrays are silently dropped (clean break — no dual-mode).\n *\n * @param windowId - The identifier of the napplet that sent the message\n * @param msg - The raw message (NappletMessage envelopes are processed; other types dropped)\n */\n handleMessage(windowId: string, msg: unknown): void;\n\n /**\n * Inject a shell-originated event into subscription delivery.\n *\n * @param topic - The event topic tag value\n * @param payload - The event content\n */\n injectEvent(topic: string, payload: unknown): void;\n\n /** Destroy the runtime, persisting state and clearing all internal state. */\n destroy(): void;\n\n /** Register a handler for consent requests on destructive signing kinds. */\n registerConsentHandler(handler: ConsentHandler): void;\n\n /**\n * Register a service handler dynamically after runtime creation.\n * If a handler is already registered for this name, it is replaced.\n *\n * @param name - Service name (e.g., 'audio', 'notifications')\n * @param handler - The service handler implementation\n */\n registerService(name: string, handler: ServiceHandler): void;\n\n /**\n * Unregister a service handler by name. No-op if the name is not registered.\n *\n * @param name - Service name to remove\n */\n unregisterService(name: string): void;\n\n /**\n * Clean up all state associated with a napplet window.\n * Removes subscriptions, pending state, and notifies service handlers.\n *\n * @param windowId - The window to clean up\n */\n destroyWindow(windowId: string): void;\n\n /** Access the identity registry (for shell adapter to read napplet session state). */\n readonly sessionRegistry: SessionRegistry;\n\n /** Access the ACL state container. */\n readonly aclState: AclStateContainer;\n\n /** Access the firewall state container (for tests to pre-set policy/rules). */\n readonly firewallState: FirewallStateContainer;\n\n /** Access the manifest cache. */\n readonly manifestCache: ManifestCache;\n}\n\ntype RuntimeNapHandlers = RuntimeDomainHandlers & {\n relay: (windowId: string, msg: NappletMessage) => void;\n identity: (windowId: string, msg: NappletMessage) => void;\n inc: (windowId: string, msg: NappletMessage) => void;\n};\n\ntype RuntimeInstanceContext = {\n hooks: RuntimeAdapter;\n serviceRegistry: ServiceRegistry;\n registeredServices: Map<string, ServiceInfo>;\n replayDetector: ReplayDetector;\n subscriptions: Map<string, SubscriptionEntry>;\n eventBuffer: EventBuffer;\n incRuntime: IncRuntime;\n sessionRegistry: SessionRegistry;\n aclState: AclStateContainer;\n firewallState: FirewallStateContainer;\n manifestCache: ManifestCache;\n consentHandlerRef: { current: ConsentHandler | null };\n handleMessage: Runtime['handleMessage'];\n};\n\nfunction createRegisteredServices(serviceRegistry: ServiceRegistry): Map<string, ServiceInfo> {\n const registeredServices = new Map<string, ServiceInfo>();\n for (const [name, handler] of Object.entries(serviceRegistry)) {\n registeredServices.set(name, {\n name: handler.descriptor.name,\n version: handler.descriptor.version,\n description: handler.descriptor.description,\n });\n }\n return registeredServices;\n}\n\nfunction createNapEnvelopeDispatcher(handlers: RuntimeNapHandlers): (windowId: string, envelope: NappletMessage) => void {\n let currentWindowId: string | null = null;\n const napDispatch = createDispatch();\n const adapt = (handler: (windowId: string, msg: NappletMessage) => void): NapHandler => (msg) => {\n if (currentWindowId !== null) handler(currentWindowId, msg);\n };\n\n napDispatch.registerNap('relay', adapt(handlers.relay));\n napDispatch.registerNap('identity', adapt(handlers.identity));\n napDispatch.registerNap('keys', adapt(handlers.keys));\n napDispatch.registerNap('media', adapt(handlers.media));\n napDispatch.registerNap('notify', adapt(handlers.notify));\n napDispatch.registerNap('storage', adapt(handlers.storage));\n napDispatch.registerNap('inc', adapt(handlers.inc));\n napDispatch.registerNap('theme', adapt(handlers.theme));\n napDispatch.registerNap('config', adapt(handlers.config));\n napDispatch.registerNap('resource', adapt(handlers.resource));\n napDispatch.registerNap('cvm', adapt(handlers.cvm));\n napDispatch.registerNap('outbox', adapt(handlers.outbox));\n napDispatch.registerNap('upload', adapt(handlers.upload));\n napDispatch.registerNap('intent', adapt(handlers.intent));\n napDispatch.registerNap('link', adapt(handlers.link));\n napDispatch.registerNap('common', adapt(handlers.common));\n napDispatch.registerNap('lists', adapt(handlers.lists));\n napDispatch.registerNap('serial', adapt(handlers.serial));\n napDispatch.registerNap('ble', adapt(handlers.ble));\n napDispatch.registerNap('webrtc', adapt(handlers.webrtc));\n\n return (windowId, envelope) => {\n currentWindowId = windowId;\n try {\n napDispatch.dispatch(envelope);\n } finally {\n currentWindowId = null;\n }\n };\n}\n\n/**\n * Compute the UTF-8 byte length of a string without TextEncoder (ES2022-safe).\n * Mirrors the same helper in state-handler.ts to avoid cross-file import.\n */\nfunction utf8ByteLength(str: string): number {\n let bytes = 0;\n for (let i = 0; i < str.length; i++) {\n const c = str.charCodeAt(i);\n if (c < 0x80) bytes += 1;\n else if (c < 0x800) bytes += 2;\n else if (c < 0xd800 || c >= 0xe000) bytes += 3;\n else { i++; bytes += 4; } // surrogate pair\n }\n return bytes;\n}\n\n/**\n * Extract kind and byte-size from a message envelope (publish-style ops only).\n * Guards against malformed envelopes — returns undefined fields on any failure.\n *\n * @param envelope - Incoming NappletMessage envelope.\n * @returns `{ kind?, size? }` — both optional; undefined when not a publish-style op.\n */\nfunction extractKindSize(envelope: NappletMessage): { kind?: number; size?: number } {\n const ev = (envelope as NappletMessage & { event?: unknown }).event;\n if (typeof ev !== 'object' || ev === null) return {};\n const kind = typeof (ev as { kind?: unknown }).kind === 'number'\n ? (ev as { kind: number }).kind\n : undefined;\n let size: number | undefined;\n try {\n size = utf8ByteLength(JSON.stringify(ev));\n } catch { /* malformed event — size stays undefined */ }\n return { kind, size };\n}\n\n/**\n * Build a normalized Observation from a message envelope and runtime context.\n * The runtime owns the clock (Date.now()); the pure engine never reads it.\n * Focus is sourced exclusively via getFocusContext (FOCUS-01) — never napplet-self-reported.\n * When getFocusContext is absent, defaults to `{ focused: true }` (safe relax-only default).\n *\n * @param envelope - Incoming NappletMessage envelope.\n * @param windowId - Source napplet window identifier.\n * @param senderCap - Resolved sender capability string (or null).\n * @param sessionRegistry - Used to resolve dTag and registeredAt for the window.\n * @param getFocusContext - Optional shell-supplied focus query hook.\n * @returns A complete Observation ready for evaluate().\n */\nfunction buildObservation(\n envelope: NappletMessage,\n windowId: string,\n senderCap: string | null,\n sessionRegistry: SessionRegistry,\n getFocusContext?: RuntimeAdapter['getFocusContext'],\n): Observation {\n const now = Date.now();\n const entry = sessionRegistry.getEntryByWindowId(windowId);\n const napplet = entry?.dTag ?? '';\n const initElapsedMs = now - (entry?.registeredAt ?? now);\n const focus = getFocusContext?.(windowId) ?? { focused: true };\n const opClass = senderCap ?? envelope.type;\n const { kind, size } = extractKindSize(envelope);\n return { napplet, opClass, kind, size, initElapsedMs, focused: focus.focused, msSinceFocusGain: focus.msSinceFocusGain, now };\n}\n\n/** Configuration for createFirewallGate. */\ninterface FirewallGateConfig {\n firewallState: FirewallStateContainer;\n sessionRegistry: SessionRegistry;\n hooks: RuntimeAdapter;\n fireConsent: (windowId: string, napplet: string) => void;\n}\n\n/**\n * Create the firewall gate closure to inject into createMessageHandler.\n * Returns 'dispatch' to allow the message through or 'drop' to reject it.\n *\n * Decision→action mapping (RUNTIME-01, RUNTIME-04, POLICY-02):\n * - reject → error envelope + drop (no dispatch)\n * - prompt → error envelope + fireConsent + drop (ask path)\n * - pass + flag → onFirewallEvent audit + dispatch\n * - pass (ignore/allow) → dispatch (no audit)\n *\n * @param config - Gate configuration (firewallState, sessionRegistry, hooks, fireConsent).\n * @returns A gate function `(windowId, envelope, senderCap) => 'dispatch' | 'drop'`.\n */\nfunction createFirewallGate(config: FirewallGateConfig): (windowId: string, envelope: NappletMessage, senderCap: string | null) => 'dispatch' | 'drop' {\n const { firewallState, sessionRegistry, hooks, fireConsent } = config;\n\n return function firewallGate(windowId: string, envelope: NappletMessage, senderCap: string | null): 'dispatch' | 'drop' {\n const obs = buildObservation(envelope, windowId, senderCap, sessionRegistry, hooks.getFocusContext);\n const result = firewallState.evaluate(obs);\n const { decision, action, ruleId, reason } = result;\n const napplet = obs.napplet;\n const opClass = obs.opClass;\n\n if (decision === 'reject' || decision === 'prompt') {\n // Mirror the ACL denial envelope shaping (runtime.ts ACL path):\n // storage envelopes → `.result`; all others → `.error` (T-81-03: no internals leaked)\n const id = (envelope as NappletMessage & { id?: string }).id ?? '';\n const isStorageEnvelope = envelope.type.startsWith('storage.');\n const type = isStorageEnvelope ? `${envelope.type}.result` : `${envelope.type}.error`;\n hooks.sendToNapplet(windowId, { type, id, error: `firewall: ${reason}` } as NappletMessage);\n\n hooks.onFirewallEvent?.({ windowId, napplet, opClass, decision, action, ruleId, reason, message: envelope } as FirewallEvent);\n\n if (decision === 'prompt') {\n // POLICY-02: reject current message AND fire async consent prompt\n fireConsent(windowId, napplet);\n }\n return 'drop';\n }\n\n // decision === 'pass'\n if (action === 'flag') {\n // RUNTIME-04: flag → audit + dispatch (message is NOT dropped)\n hooks.onFirewallEvent?.({ windowId, napplet, opClass, decision, action, ruleId, reason, message: envelope } as FirewallEvent);\n }\n return 'dispatch';\n };\n}\n\nfunction createMessageHandler(\n hooks: RuntimeAdapter,\n enforceNap: ReturnType<typeof createNapEnforceGate>,\n dispatchNapEnvelope: (windowId: string, envelope: NappletMessage) => void,\n firewallGate: (windowId: string, envelope: NappletMessage, senderCap: string | null) => 'dispatch' | 'drop',\n): Runtime['handleMessage'] {\n return (windowId: string, msg: unknown): void => {\n if (typeof msg !== 'object' || msg === null || !('type' in msg)) return;\n const envelope = msg as NappletMessage;\n const dotIdx = envelope.type.indexOf('.');\n if (dotIdx === -1) return;\n\n const caps = resolveCapabilitiesNap(envelope);\n if (caps.senderCap) {\n const result = enforceNap(windowId, caps.senderCap as Capability, envelope);\n if (!result.allowed) {\n const id = (envelope as NappletMessage & { id?: string }).id ?? '';\n const isStorageEnvelope = envelope.type.startsWith('storage.');\n const error = formatDenialReason(result.capability);\n const type = isStorageEnvelope ? `${envelope.type}.result` : `${envelope.type}.error`;\n hooks.sendToNapplet(windowId, { type, id, error } as NappletMessage);\n return;\n }\n }\n\n const verdict = firewallGate(windowId, envelope, caps.senderCap);\n if (verdict === 'drop') return;\n\n dispatchNapEnvelope(windowId, envelope);\n };\n}\n\nfunction createInjectedEvent(hooks: RuntimeAdapter, topic: string, payload: unknown): NostrEvent {\n const uuid = hooks.crypto.randomUUID().replace(/-/g, '').slice(0, 64).padEnd(64, '0');\n return {\n id: uuid,\n pubkey: '0'.repeat(64),\n created_at: Math.floor(Date.now() / 1000),\n kind: 29000,\n tags: [['t', topic]],\n content: JSON.stringify(payload),\n sig: '0'.repeat(128),\n };\n}\n\nfunction createRuntimeInstance(context: RuntimeInstanceContext): Runtime {\n const {\n aclState, firewallState, eventBuffer, hooks, incRuntime, manifestCache, registeredServices,\n replayDetector, serviceRegistry, sessionRegistry, subscriptions, consentHandlerRef,\n } = context;\n const undeclaredServiceConsents = new Set<string>();\n\n return {\n handleMessage: context.handleMessage,\n injectEvent(topic: string, payload: unknown): void {\n eventBuffer.bufferAndDeliver(createInjectedEvent(hooks, topic, payload), null);\n },\n destroy(): void {\n manifestCache.persist();\n aclState.persist();\n firewallState.persist();\n replayDetector.clear();\n subscriptions.clear();\n incRuntime.clear();\n eventBuffer.clear();\n registeredServices.clear();\n undeclaredServiceConsents.clear();\n },\n registerConsentHandler(handler: ConsentHandler): void {\n consentHandlerRef.current = handler;\n },\n registerService(name: string, handler: ServiceHandler): void {\n serviceRegistry[name] = handler;\n registeredServices.set(name, {\n name: handler.descriptor.name,\n version: handler.descriptor.version,\n description: handler.descriptor.description,\n });\n },\n unregisterService(name: string): void {\n delete serviceRegistry[name];\n registeredServices.delete(name);\n },\n destroyWindow(windowId: string): void {\n for (const [key] of subscriptions) {\n if (key.startsWith(`${windowId}:`)) {\n subscriptions.delete(key);\n hooks.relayPool?.untrackSubscription(key);\n }\n }\n incRuntime.destroyWindow(windowId);\n notifyServiceWindowDestroyed(windowId, serviceRegistry);\n },\n get sessionRegistry() { return sessionRegistry; },\n get aclState() { return aclState; },\n get firewallState() { return firewallState; },\n get manifestCache() { return manifestCache; },\n };\n}\n\n/**\n * Create a runtime instance with dependency injection via hooks.\n *\n * @param hooks - Host application provides relay pool, auth, config, etc.\n * @returns A Runtime instance ready to handle napplet messages\n *\n * @example\n * ```ts\n * const runtime = createRuntime(hooks);\n * // On incoming message from napplet:\n * runtime.handleMessage(windowId, { type: 'relay.req', id: 'sub1', filters: [] });\n * ```\n */\nexport function createRuntime(hooks: RuntimeAdapter): Runtime {\n const subscriptions = new Map<string, SubscriptionEntry>();\n const serviceRegistry: ServiceRegistry = { ...hooks.services };\n const registeredServices = createRegisteredServices(serviceRegistry);\n const sessionRegistry = createSessionRegistry(hooks.onPendingUpdate);\n const aclState = createAclState(hooks.aclPersistence);\n const firewallState = createFirewallState(hooks.firewallPersistence);\n const manifestCache = createManifestCache(hooks.manifestPersistence);\n const replayDetector = createReplayDetector(\n hooks.getConfigOverrides\n ? () => hooks.getConfigOverrides!().replayWindowSeconds\n : undefined,\n );\n\n // Shared consent handler ref — lifted to createRuntime scope so the firewall gate\n // can fire it. The gate is built here (outer scope) but registerConsentHandler is\n // called on the runtime instance (inner scope); the ref bridges both closures.\n const consentHandlerRef: { current: ConsentHandler | null } = { current: null };\n\n // fireConsent: invoked by the gate on 'prompt' decisions (POLICY-02 ask path).\n // On resolution the user's choice is persisted as a per-napplet policy so subsequent\n // messages are NOT re-prompted (T-81-04: bounded consent spam prevention).\n const fireConsent = (windowId: string, napplet: string): void => {\n const handler = consentHandlerRef.current;\n if (!handler) return;\n handler({\n type: 'firewall-policy',\n windowId,\n napplet,\n pubkey: '',\n resolve: (allowed: boolean): void => {\n firewallState.setPolicy(napplet, allowed ? 'allow' : 'deny');\n firewallState.persist();\n },\n });\n };\n\n const enforce = createEnforceGate({\n checkAcl: (pubkey, dTag, aggregateHash, capability) =>\n aclState.check(pubkey, dTag, aggregateHash, capability),\n resolveIdentity: (pubkey) => {\n const entry = sessionRegistry.getEntry(pubkey);\n return entry ? { dTag: entry.dTag, aggregateHash: entry.aggregateHash } : undefined;\n },\n onAclCheck: hooks.onAclCheck,\n });\n\n const enforceNap = createNapEnforceGate({\n checkAcl: (pubkey, dTag, aggregateHash, capability) =>\n aclState.check(pubkey, dTag, aggregateHash, capability),\n resolveIdentityByWindowId: (windowId) => {\n const entry = sessionRegistry.getEntryByWindowId(windowId);\n return entry\n ? { dTag: entry.dTag, aggregateHash: entry.aggregateHash }\n : undefined;\n },\n onAclCheck: hooks.onAclCheck,\n });\n\n const firewallGate = createFirewallGate({ firewallState, sessionRegistry, hooks, fireConsent });\n\n const eventBuffer = createEventBuffer(\n hooks.sendToNapplet,\n sessionRegistry,\n enforce,\n subscriptions,\n hooks.getConfigOverrides\n ? () => hooks.getConfigOverrides!().ringBufferSize ?? RING_BUFFER_SIZE\n : undefined,\n );\n\n aclState.load();\n firewallState.load();\n manifestCache.load();\n\n const incRuntime = createIncRuntime(hooks, sessionRegistry);\n const domainHandlers = createRuntimeDomainHandlers({ hooks, serviceRegistry, sessionRegistry, aclState });\n const dispatchNapEnvelope = createNapEnvelopeDispatcher({\n relay: createRelayHandler({ hooks, serviceRegistry, subscriptions, eventBuffer, replayDetector }),\n identity: createIdentityHandler({ hooks, serviceRegistry }),\n inc: incRuntime.handleMessage,\n ...domainHandlers,\n });\n const handleMessage = createMessageHandler(hooks, enforceNap, dispatchNapEnvelope, firewallGate);\n\n return createRuntimeInstance({\n hooks,\n serviceRegistry,\n registeredServices,\n replayDetector,\n subscriptions,\n eventBuffer,\n incRuntime,\n sessionRegistry,\n aclState,\n firewallState,\n manifestCache,\n consentHandlerRef,\n handleMessage,\n });\n}\n","\nimport type { NappletMessage } from '@napplet/core';\nimport type { ServiceRegistry, SendToNapplet } from './types.js';\n\n/**\n * Route a NappletMessage envelope to the matching service handler.\n *\n * NAP-domain services are routed by the domain prefix of message.type\n * (e.g., 'signer.signEvent' -> 'signer' service).\n *\n * INC-routed services receive inc.emit messages and are routed by the\n * topic prefix before ':' (e.g., topic 'audio:play' -> 'audio' service).\n *\n * Returns true if a service handled the message, false otherwise.\n *\n * @param windowId - The napplet window that sent the message\n * @param message - The NappletMessage envelope to route\n * @param services - The service registry to look up handlers\n * @param sendToNapplet - Callback to send messages back to the napplet\n * @returns true if a service handled the message, false if no matching service\n *\n * @example\n * ```ts\n * const handled = routeServiceMessage(windowId, msg, services, sendToNapplet);\n * if (!handled) { // fallback handling }\n * ```\n */\nexport function routeServiceMessage(\n windowId: string,\n message: NappletMessage,\n services: ServiceRegistry,\n sendToNapplet: SendToNapplet,\n): boolean {\n // NAP-domain services: signer.*, relay.*, storage.* route by type prefix\n const domain = message.type.split('.')[0];\n const handler = services[domain];\n if (handler) {\n handler.handleMessage(windowId, message, (msg) => sendToNapplet(windowId, msg));\n return true;\n }\n\n // INC-routed services: audio and notifications receive inc.emit with topic prefix\n const incMessage = message as NappletMessage & { topic?: unknown };\n if (message.type === 'inc.emit' && typeof incMessage.topic === 'string') {\n const prefix = incMessage.topic.split(':')[0];\n const incHandler = services[prefix];\n if (incHandler) {\n incHandler.handleMessage(windowId, message, (msg) => sendToNapplet(windowId, msg));\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Notify all registered service handlers that a napplet window was destroyed.\n * Calls onWindowDestroyed() on every handler that implements it.\n * Errors in individual handlers are caught and silently ignored to prevent\n * one service's cleanup failure from blocking others.\n *\n * @param windowId - The destroyed napplet's window identifier\n * @param services - The service registry containing all handlers\n *\n * @example\n * ```ts\n * notifyServiceWindowDestroyed('window-1', services);\n * ```\n */\nexport function notifyServiceWindowDestroyed(\n windowId: string,\n services: ServiceRegistry,\n): void {\n for (const handler of Object.values(services)) {\n try {\n handler.onWindowDestroyed?.(windowId);\n } catch {\n /* Service cleanup is best-effort — don't let one service block others */\n }\n }\n}\n","import type { NappletMessage, NostrEvent, NostrFilter } from '@napplet/core';\nimport type { RelayMessage } from '@napplet/nap/relay/types';\n\nimport { matchesAnyFilter, type EventBuffer, type SubscriptionEntry } from './event-buffer.js';\nimport type { ReplayDetector } from './replay.js';\nimport type { RuntimeAdapter, ServiceHandler, ServiceRegistry } from './types.js';\n\ndeclare function setTimeout(callback: () => void, ms: number): unknown;\ndeclare function clearTimeout(id: unknown): void;\n\ntype RuntimeRelayMessage = RelayMessage & {\n subId?: string;\n filters?: NostrFilter[];\n event?: NostrEvent;\n id?: string;\n relay?: string;\n};\n\ntype RelayHandlerContext = {\n hooks: RuntimeAdapter;\n serviceRegistry: ServiceRegistry;\n subscriptions: Map<string, SubscriptionEntry>;\n eventBuffer: EventBuffer;\n replayDetector: ReplayDetector;\n};\n\nexport type RelayHandler = (windowId: string, msg: NappletMessage) => void;\n\nexport function createRelayHandler(context: RelayHandlerContext): RelayHandler {\n return function handleRelayMessage(windowId: string, msg: NappletMessage): void {\n const m = msg as RuntimeRelayMessage;\n const dotIdx = msg.type.indexOf('.');\n const action = msg.type.slice(dotIdx + 1);\n\n switch (action) {\n case 'subscribe':\n handleRelaySubscribe(context, windowId, msg, m);\n return;\n case 'close':\n handleRelayClose(context, windowId, msg, m);\n return;\n case 'publish':\n handleRelayPublish(context, windowId, msg, m);\n return;\n case 'publishEncrypted':\n handleRelayPublishEncrypted(context, windowId, msg);\n return;\n case 'query':\n handleRelayQuery(context, windowId, m);\n return;\n default:\n return;\n }\n };\n}\n\nfunction relayServiceFrom(context: RelayHandlerContext): ServiceHandler | undefined {\n return context.serviceRegistry['relay'] ?? context.serviceRegistry['relay-pool'];\n}\n\nfunction isShellKindQuery(filters: NostrFilter[]): boolean {\n return filters.length > 0 && filters.every((filter) => filter.kinds?.every((kind) => kind >= 29000 && kind < 30000));\n}\n\nfunction handleRelaySubscribe(\n context: RelayHandlerContext,\n windowId: string,\n msg: NappletMessage,\n m: RuntimeRelayMessage,\n): void {\n const { eventBuffer, hooks, serviceRegistry, subscriptions } = context;\n const subId = m.subId ?? '';\n const filters = m.filters ?? [];\n if (!subId) return;\n\n const subKey = `${windowId}:${subId}`;\n subscriptions.set(subKey, { windowId, filters });\n\n const seenIds = new Set<string>();\n function deliver(event: NostrEvent): void {\n if (seenIds.has(event.id)) return;\n seenIds.add(event.id);\n if (subscriptions.has(subKey)) {\n hooks.sendToNapplet(windowId, { type: 'relay.event', subId, event } as NappletMessage);\n }\n }\n\n for (const bufferedEvent of eventBuffer.getBufferedEvents()) {\n if (matchesAnyFilter(bufferedEvent, filters)) deliver(bufferedEvent);\n }\n\n const isShellKind = isShellKindQuery(filters);\n const relayService = relayServiceFrom(context);\n const cacheService = !serviceRegistry['relay'] ? serviceRegistry['cache'] : undefined;\n\n if (!isShellKind && relayService) {\n relayService.handleMessage(windowId, msg, (resp: NappletMessage) => {\n if (!subscriptions.has(subKey)) return;\n hooks.sendToNapplet(windowId, resp);\n });\n if (cacheService) {\n cacheService.handleMessage(windowId, msg, (resp: NappletMessage) => {\n if (!subscriptions.has(subKey)) return;\n hooks.sendToNapplet(windowId, resp);\n });\n }\n return;\n }\n\n const relayHint = typeof m.relay === 'string' && m.relay.length > 0 ? m.relay : undefined;\n deliverFromRuntimeBackends(context, windowId, subId, subKey, filters, isShellKind, deliver, relayHint);\n}\n\nfunction deliverFromRuntimeBackends(\n context: RelayHandlerContext,\n windowId: string,\n subId: string,\n subKey: string,\n filters: NostrFilter[],\n isShellKind: boolean,\n deliver: (event: NostrEvent) => void,\n relayHint?: string,\n): void {\n const { hooks } = context;\n const cache = hooks.cache;\n\n if (cache?.isAvailable() && !isShellKind) {\n cache.query(filters)\n .then((cachedEvents) => {\n for (const event of cachedEvents) deliver(event);\n })\n .catch(() => {});\n }\n\n const pool = hooks.relayPool;\n if (!pool?.isAvailable() && !isShellKind) {\n hooks.sendToNapplet(windowId, { type: 'relay.eose', subId } as NappletMessage);\n return;\n }\n if (!pool?.isAvailable() || isShellKind) return;\n\n const relayUrls = relayHint ? [relayHint] : pool.selectRelayTier(filters);\n let eoseSent = false;\n const eoseFallbackTimer = setTimeout(() => {\n if (!eoseSent) {\n eoseSent = true;\n hooks.sendToNapplet(windowId, { type: 'relay.eose', subId } as NappletMessage);\n }\n }, 15_000);\n\n const subscription = pool.subscribe(filters, (item) => {\n if (item === 'EOSE') {\n clearTimeout(eoseFallbackTimer);\n if (!eoseSent) {\n eoseSent = true;\n hooks.sendToNapplet(windowId, { type: 'relay.eose', subId } as NappletMessage);\n }\n return;\n }\n deliver(item as NostrEvent);\n if (cache?.isAvailable() && !isShellKind) {\n try { cache.store(item as NostrEvent); } catch { return; }\n }\n }, relayUrls);\n\n pool.trackSubscription(subKey, () => {\n clearTimeout(eoseFallbackTimer);\n subscription.unsubscribe();\n });\n}\n\nfunction handleRelayClose(\n context: RelayHandlerContext,\n windowId: string,\n msg: NappletMessage,\n m: RuntimeRelayMessage,\n): void {\n const { hooks, subscriptions } = context;\n const subId = m.subId ?? '';\n if (!subId) return;\n const subKey = `${windowId}:${subId}`;\n subscriptions.delete(subKey);\n\n const relayService = relayServiceFrom(context);\n if (relayService) relayService.handleMessage(windowId, msg, () => {});\n hooks.relayPool?.untrackSubscription(subKey);\n hooks.sendToNapplet(windowId, { type: 'relay.closed', subId, message: '' } as NappletMessage);\n}\n\nfunction handleRelayPublish(\n context: RelayHandlerContext,\n windowId: string,\n msg: NappletMessage,\n m: RuntimeRelayMessage,\n): void {\n const { eventBuffer, hooks, replayDetector } = context;\n const event = m.event;\n const id = m.id ?? '';\n if (!event || typeof event !== 'object') {\n hooks.sendToNapplet(windowId, { type: 'relay.publish.error', id, error: 'invalid event' } as NappletMessage);\n return;\n }\n\n const replayResult = replayDetector.check(event);\n if (replayResult !== null) {\n hooks.sendToNapplet(windowId, { type: 'relay.publish.result', id, accepted: false, message: replayResult } as NappletMessage);\n return;\n }\n\n const relayService = relayServiceFrom(context);\n if (relayService) {\n relayService.handleMessage(windowId, msg, (resp: NappletMessage) => hooks.sendToNapplet(windowId, resp));\n } else if (hooks.relayPool?.isAvailable()) {\n hooks.relayPool.publish(event);\n hooks.sendToNapplet(windowId, { type: 'relay.publish.result', id, accepted: true } as NappletMessage);\n } else {\n hooks.sendToNapplet(windowId, { type: 'relay.publish.result', id, accepted: false, message: 'no relay pool available' } as NappletMessage);\n }\n\n eventBuffer.bufferAndDeliver(event, windowId);\n}\n\nfunction handleRelayPublishEncrypted(\n context: RelayHandlerContext,\n windowId: string,\n msg: NappletMessage,\n): void {\n const { hooks } = context;\n const id = (msg as RuntimeRelayMessage).id ?? '';\n const eventTemplate = (msg as RuntimeRelayMessage).event;\n const peMsg = msg as NappletMessage & { recipient?: string; encryption?: string };\n const recipient = peMsg.recipient ?? '';\n const encryption = (peMsg.encryption ?? 'nip44') as 'nip04' | 'nip44' | string;\n\n const replyPe = (ok: boolean, extra: Record<string, unknown> = {}) => {\n hooks.sendToNapplet(windowId, { type: 'relay.publishEncrypted.result', id, ok, ...extra } as NappletMessage);\n };\n\n if (!recipient) { replyPe(false, { error: 'missing recipient' }); return; }\n if (encryption !== 'nip44' && encryption !== 'nip04') {\n replyPe(false, { error: `unsupported encryption scheme: ${encryption}` });\n return;\n }\n const peSigner = hooks.auth.getSigner();\n if (!peSigner) { replyPe(false, { error: 'no signer configured' }); return; }\n if (!eventTemplate || typeof eventTemplate !== 'object') {\n replyPe(false, { error: 'invalid event template' });\n return;\n }\n\n publishEncrypted(context, windowId, id, recipient, encryption, eventTemplate, replyPe);\n}\n\nfunction publishEncrypted(\n context: RelayHandlerContext,\n windowId: string,\n id: string,\n recipient: string,\n encryption: 'nip04' | 'nip44' | string,\n eventTemplate: NostrEvent,\n replyPe: (ok: boolean, extra?: Record<string, unknown>) => void,\n): void {\n const { eventBuffer, hooks } = context;\n const peSigner = hooks.auth.getSigner();\n if (!peSigner) return;\n\n (async (): Promise<void> => {\n try {\n const plaintext = String((eventTemplate as { content?: unknown }).content ?? '');\n const ciphertext: string = encryption === 'nip44'\n ? (await peSigner.nip44?.encrypt(recipient, plaintext)) ?? ''\n : (await peSigner.nip04?.encrypt(recipient, plaintext)) ?? '';\n const eventWithCiphertext = { ...(eventTemplate as object), content: ciphertext } as NostrEvent;\n const signed = await peSigner.signEvent?.(eventWithCiphertext);\n if (!signed) { replyPe(false, { error: 'signEvent returned null' }); return; }\n\n publishSignedEncrypted(context, windowId, id, signed, replyPe);\n try { eventBuffer.bufferAndDeliver(signed, windowId); } catch { return; }\n } catch (err) {\n replyPe(false, { error: (err as Error)?.message ?? 'encryption failed' });\n }\n })();\n}\n\nfunction publishSignedEncrypted(\n context: RelayHandlerContext,\n windowId: string,\n id: string,\n signed: NostrEvent,\n replyPe: (ok: boolean, extra?: Record<string, unknown>) => void,\n): void {\n const { hooks } = context;\n const relayService = relayServiceFrom(context);\n if (!relayService) {\n if (hooks.relayPool?.isAvailable()) {\n hooks.relayPool.publish(signed);\n replyPe(true, { event: signed, eventId: signed.id });\n } else {\n replyPe(false, { error: 'no relay pool available' });\n }\n return;\n }\n\n const publishMsg = { type: 'relay.publish', id, event: signed } as NappletMessage;\n let replied = false;\n relayService.handleMessage(windowId, publishMsg, (resp: NappletMessage) => {\n if (replied) return;\n const r = resp as NappletMessage & {\n ok?: boolean; accepted?: boolean; eventId?: string;\n message?: string; error?: string;\n };\n if (typeof r.type !== 'string' || !r.type.startsWith('relay.publish')) return;\n const okVal = r.ok ?? r.accepted ?? false;\n replied = true;\n const publishResult = { event: signed, eventId: signed.id } as {\n event: NostrEvent;\n eventId: string;\n error?: string;\n };\n if (!okVal) publishResult.error = r.error ?? r.message ?? 'publish failed';\n replyPe(okVal, publishResult);\n });\n\n if (!replied) {\n replied = true;\n replyPe(true, { event: signed, eventId: signed.id });\n }\n}\n\nfunction handleRelayQuery(\n context: RelayHandlerContext,\n windowId: string,\n m: RuntimeRelayMessage,\n): void {\n const id = m.id ?? '';\n const filters = m.filters ?? [];\n let count = 0;\n for (const event of context.eventBuffer.getBufferedEvents()) {\n if (matchesAnyFilter(event, filters)) count++;\n }\n context.hooks.sendToNapplet(windowId, { type: 'relay.query.result', id, count } as NappletMessage);\n}\n","import type { NappletMessage } from '@napplet/core';\n\nimport type { RuntimeAdapter, ServiceRegistry } from './types.js';\n\ntype IdentityHandlerContext = {\n hooks: RuntimeAdapter;\n serviceRegistry: ServiceRegistry;\n};\n\nexport type IdentityHandler = (windowId: string, msg: NappletMessage) => void;\n\nexport function createIdentityHandler(context: IdentityHandlerContext): IdentityHandler {\n return function handleIdentityMessage(windowId: string, msg: NappletMessage): void {\n const { hooks, serviceRegistry } = context;\n const identityService = serviceRegistry['identity'];\n if (identityService) {\n identityService.handleMessage(windowId, msg, (resp: NappletMessage) => hooks.sendToNapplet(windowId, resp));\n return;\n }\n\n const id = (msg as NappletMessage & { id?: string }).id ?? '';\n const action = msg.type.slice('identity.'.length);\n const signer = hooks.auth.getSigner();\n const sendError = (error: string) => {\n hooks.sendToNapplet(windowId, { type: `${msg.type}.error`, id, error } as NappletMessage);\n };\n const sendResult = (payload: Record<string, unknown>) => {\n hooks.sendToNapplet(windowId, { type: `${msg.type}.result`, id, ...payload } as NappletMessage);\n };\n\n switch (action) {\n case 'getPublicKey':\n if (!signer) { sendResult({ pubkey: '' }); return; }\n Promise.resolve(signer.getPublicKey?.())\n .then((pubkey) => sendResult({ pubkey: pubkey ?? '' }))\n .catch((err: unknown) => sendError((err as Error)?.message ?? 'getPublicKey failed'));\n return;\n case 'getRelays':\n if (!signer) { sendError('no signer configured'); return; }\n Promise.resolve(signer.getRelays?.() ?? {})\n .then((relays) => sendResult({ relays }))\n .catch((err: unknown) => sendError((err as Error)?.message ?? 'getRelays failed'));\n return;\n case 'getProfile': sendResult({ profile: null }); return;\n case 'getFollows': sendResult({ pubkeys: [] }); return;\n case 'getList': sendResult({ entries: [] }); return;\n case 'getZaps': sendResult({ zaps: [] }); return;\n case 'getMutes': sendResult({ pubkeys: [] }); return;\n case 'getBlocked': sendResult({ pubkeys: [] }); return;\n case 'getBadges': sendResult({ badges: [] }); return;\n default:\n sendError(`Unknown identity action: ${action}`);\n }\n };\n}\n","import type { NappletMessage } from '@napplet/core';\nimport type { IncMessage } from '@napplet/nap/inc/types';\n\nimport type { SessionRegistry } from './session-registry.js';\nimport type { RuntimeAdapter } from './types.js';\n\ninterface IncChannel {\n channelId: string;\n peerA: string;\n peerB: string;\n}\n\ntype RuntimeIncMessage = IncMessage & {\n id?: string;\n topic?: string;\n payload?: unknown;\n target?: string;\n channelId?: string;\n};\n\ntype IncState = {\n subscriptions: Map<string, Set<string>>;\n channels: Map<string, IncChannel>;\n channelsByWindow: Map<string, Set<string>>;\n};\n\nexport interface IncRuntime {\n handleMessage(windowId: string, msg: NappletMessage): void;\n destroyWindow(windowId: string): void;\n clear(): void;\n}\n\nexport function createIncRuntime(hooks: RuntimeAdapter, sessionRegistry: SessionRegistry): IncRuntime {\n const state: IncState = {\n subscriptions: new Map(),\n channels: new Map(),\n channelsByWindow: new Map(),\n };\n\n return {\n handleMessage(windowId: string, msg: NappletMessage): void {\n handleIncMessage(state, hooks, sessionRegistry, windowId, msg);\n },\n destroyWindow(windowId: string): void {\n removeWindowChannels(state, hooks, windowId);\n removeWindowSubscriptions(state, windowId);\n },\n clear(): void {\n state.subscriptions.clear();\n state.channels.clear();\n state.channelsByWindow.clear();\n },\n };\n}\n\nfunction addChannel(state: IncState, channelId: string, peerA: string, peerB: string): void {\n state.channels.set(channelId, { channelId, peerA, peerB });\n for (const windowId of [peerA, peerB]) {\n let set = state.channelsByWindow.get(windowId);\n if (!set) {\n set = new Set();\n state.channelsByWindow.set(windowId, set);\n }\n set.add(channelId);\n }\n}\n\nfunction removeChannel(state: IncState, channelId: string): void {\n const channel = state.channels.get(channelId);\n if (!channel) return;\n state.channels.delete(channelId);\n for (const windowId of [channel.peerA, channel.peerB]) {\n const set = state.channelsByWindow.get(windowId);\n if (!set) continue;\n set.delete(channelId);\n if (set.size === 0) state.channelsByWindow.delete(windowId);\n }\n}\n\nfunction peerOf(state: IncState, channelId: string, self: string): string | null {\n const channel = state.channels.get(channelId);\n if (!channel) return null;\n if (channel.peerA === self) return channel.peerB;\n if (channel.peerB === self) return channel.peerA;\n return null;\n}\n\nfunction resolveTarget(sessionRegistry: SessionRegistry, target: string): string | null {\n if (sessionRegistry.getEntryByWindowId(target)) return target;\n const entries = sessionRegistry.getAllEntries();\n const byPubkey = entries.find((entry) => entry.pubkey === target);\n return byPubkey?.windowId ?? null;\n}\n\nfunction handleIncMessage(\n state: IncState,\n hooks: RuntimeAdapter,\n sessionRegistry: SessionRegistry,\n windowId: string,\n msg: NappletMessage,\n): void {\n const m = msg as RuntimeIncMessage;\n const dotIdx = msg.type.indexOf('.');\n const action = msg.type.slice(dotIdx + 1);\n\n switch (action) {\n case 'emit': handleEmit(state, hooks, windowId, m); return;\n case 'subscribe': handleSubscribe(state, hooks, windowId, m); return;\n case 'unsubscribe': handleUnsubscribe(state, windowId, m); return;\n case 'channel.open': handleChannelOpen(state, hooks, sessionRegistry, windowId, m); return;\n case 'channel.emit': handleChannelEmit(state, hooks, windowId, m); return;\n case 'channel.broadcast': handleChannelBroadcast(state, hooks, windowId, m); return;\n case 'channel.list': handleChannelList(state, hooks, windowId, m); return;\n case 'channel.close': handleChannelClose(state, hooks, windowId, m); return;\n default: return;\n }\n}\n\nfunction handleEmit(\n state: IncState,\n hooks: RuntimeAdapter,\n windowId: string,\n m: RuntimeIncMessage,\n): void {\n const topic = m.topic ?? '';\n if (!topic) return;\n const subscribers = state.subscriptions.get(topic);\n if (!subscribers) return;\n for (const subscriberWindowId of subscribers) {\n if (subscriberWindowId !== windowId) {\n hooks.sendToNapplet(subscriberWindowId, { type: 'inc.event', topic, payload: m.payload, sender: windowId } as NappletMessage);\n }\n }\n}\n\nfunction handleSubscribe(\n state: IncState,\n hooks: RuntimeAdapter,\n windowId: string,\n m: RuntimeIncMessage,\n): void {\n const id = m.id ?? '';\n const topic = m.topic ?? '';\n if (!topic) {\n hooks.sendToNapplet(windowId, { type: 'inc.subscribe.result', id, error: 'missing topic' } as NappletMessage);\n return;\n }\n let subscriptions = state.subscriptions.get(topic);\n if (!subscriptions) {\n subscriptions = new Set();\n state.subscriptions.set(topic, subscriptions);\n }\n subscriptions.add(windowId);\n hooks.sendToNapplet(windowId, { type: 'inc.subscribe.result', id } as NappletMessage);\n}\n\nfunction handleUnsubscribe(state: IncState, windowId: string, m: RuntimeIncMessage): void {\n const topic = m.topic ?? '';\n if (!topic) return;\n const subscriptions = state.subscriptions.get(topic);\n if (!subscriptions) return;\n subscriptions.delete(windowId);\n if (subscriptions.size === 0) state.subscriptions.delete(topic);\n}\n\nfunction handleChannelOpen(\n state: IncState,\n hooks: RuntimeAdapter,\n sessionRegistry: SessionRegistry,\n windowId: string,\n m: RuntimeIncMessage,\n): void {\n const id = m.id ?? '';\n const peerWindow = resolveTarget(sessionRegistry, m.target ?? '');\n if (!peerWindow) {\n hooks.sendToNapplet(windowId, { type: 'inc.channel.open.result', id, error: 'target not found' } as NappletMessage);\n return;\n }\n const channelId = hooks.crypto.randomUUID().replace(/-/g, '').slice(0, 32);\n addChannel(state, channelId, windowId, peerWindow);\n hooks.sendToNapplet(windowId, { type: 'inc.channel.open.result', id, channelId, peer: peerWindow } as NappletMessage);\n}\n\nfunction handleChannelEmit(\n state: IncState,\n hooks: RuntimeAdapter,\n windowId: string,\n m: RuntimeIncMessage,\n): void {\n const peer = peerOf(state, m.channelId ?? '', windowId);\n if (peer) {\n hooks.sendToNapplet(peer, { type: 'inc.channel.event', channelId: m.channelId ?? '', sender: windowId, payload: m.payload } as NappletMessage);\n }\n}\n\nfunction handleChannelBroadcast(\n state: IncState,\n hooks: RuntimeAdapter,\n windowId: string,\n m: RuntimeIncMessage,\n): void {\n const channels = state.channelsByWindow.get(windowId);\n if (!channels) return;\n for (const channelId of channels) {\n const peer = peerOf(state, channelId, windowId);\n if (peer) {\n hooks.sendToNapplet(peer, { type: 'inc.channel.event', channelId, sender: windowId, payload: m.payload } as NappletMessage);\n }\n }\n}\n\nfunction handleChannelList(\n state: IncState,\n hooks: RuntimeAdapter,\n windowId: string,\n m: RuntimeIncMessage,\n): void {\n const channels = [];\n const set = state.channelsByWindow.get(windowId);\n if (set) {\n for (const channelId of set) {\n const peer = peerOf(state, channelId, windowId);\n if (peer) channels.push({ id: channelId, peer });\n }\n }\n hooks.sendToNapplet(windowId, { type: 'inc.channel.list.result', id: m.id ?? '', channels } as NappletMessage);\n}\n\nfunction handleChannelClose(\n state: IncState,\n hooks: RuntimeAdapter,\n windowId: string,\n m: RuntimeIncMessage,\n): void {\n const channelId = m.channelId ?? '';\n const peer = peerOf(state, channelId, windowId);\n if (!peer) return;\n hooks.sendToNapplet(windowId, { type: 'inc.channel.closed', channelId } as NappletMessage);\n hooks.sendToNapplet(peer, { type: 'inc.channel.closed', channelId } as NappletMessage);\n removeChannel(state, channelId);\n}\n\nfunction removeWindowSubscriptions(state: IncState, windowId: string): void {\n for (const [topic, subscriptions] of state.subscriptions) {\n subscriptions.delete(windowId);\n if (subscriptions.size === 0) state.subscriptions.delete(topic);\n }\n}\n\nfunction removeWindowChannels(state: IncState, hooks: RuntimeAdapter, windowId: string): void {\n const channelIds = state.channelsByWindow.get(windowId);\n if (!channelIds) return;\n for (const channelId of Array.from(channelIds)) {\n const peer = peerOf(state, channelId, windowId);\n if (peer) {\n hooks.sendToNapplet(peer, { type: 'inc.channel.closed', channelId } as NappletMessage);\n }\n removeChannel(state, channelId);\n }\n}\n","/**\n * state-handler.ts — Storage NAP request handler using persistence hooks.\n *\n * Handles napplet storage operations (get, set, remove, keys) via the\n * canonical `@napplet/nap/storage` NIP-5D envelope surface. Delegates\n * storage to StatePersistence. No localStorage, no legacy NIP-01 dispatch.\n *\n * Per-instance scope (NAP-STORAGE, napplet/naps#3): every request carries an\n * optional `scope: \"shared\" | \"instance\"` (default `\"shared\"`). `\"instance\"`\n * folds a stable per-window discriminator (`@i/<windowId>:`) into the key so\n * that multiple open windows of the *same* napplet keep isolated, independently\n * persisted state. Instancing is a property of the data (per call), not the\n * napplet — there is no napplet-wide mode. The napplet never sees or names an\n * instance identifier; it sets `scope` and trusts the shell's Unique + Stable\n * guarantees. `\"shared\"` (or absent) addresses the napplet-wide namespace,\n * byte-identical to the historical behavior.\n */\n\nimport type { NappletMessage } from '@napplet/core';\nimport type { StorageMessage } from '@napplet/nap/storage/types';\nimport type { SendToNapplet, StatePersistence } from './types.js';\nimport type { SessionRegistry } from './session-registry.js';\nimport type { AclStateContainer } from './acl-state.js';\n\n/** Canonical NAP-STORAGE scope values. `scope` absent ⇔ `\"shared\"`. */\ntype StorageScope = 'shared' | 'instance';\n\n/**\n * Reserved key segment that marks the per-instance sub-namespace inside a\n * napplet's `(dTag, aggregateHash)` bucket. Server-side only — napplets never\n * see it. Used for `scope: \"instance\"` requests (NAP-STORAGE per-instance scope).\n */\nconst INSTANCE_MARKER = '@i/';\n\n/**\n * Build the per-window instance sub-namespace segment: `@i/<windowId>:`.\n *\n * The discriminator is derived from the runtime's `windowId`, which the shell\n * keeps stable across reload / workspace restore, so per-instance storage\n * persists across sessions (the spec's \"Stable\" guarantee). Two distinct\n * windows of the same napplet get distinct segments (the \"Unique\" guarantee).\n * This format is internal and never exposed to the napplet.\n */\nfunction instanceSegment(windowId: string): string {\n return `${INSTANCE_MARKER}${windowId}:`;\n}\n\n/**\n * Build the storage key for a napplet user key.\n *\n * For `scope: \"shared\"` (default) this is byte-identical to the historical key.\n * For `scope: \"instance\"` the runtime folds in the opaque per-window segment —\n * transparently, with no napplet involvement (the napplet only sets the wire\n * field; it never sees the resulting key shape).\n */\nfunction scopedKey(\n dTag: string,\n aggregateHash: string,\n userKey: string,\n instance = false,\n windowId = '',\n): string {\n const segment = instance ? instanceSegment(windowId) : '';\n return `napplet-state:${dTag}:${aggregateHash}:${segment}${userKey}`;\n}\n\n/** Build a legacy scoped key that includes pubkey (for migration reads). */\nfunction legacyScopedKey(pubkey: string, dTag: string, aggregateHash: string, userKey: string): string {\n return `napplet-state:${pubkey}:${dTag}:${aggregateHash}:${userKey}`;\n}\n\n/** Compute byte length of a UTF-8 string without TextEncoder (ES2022-safe). */\nfunction byteLength(str: string): number {\n let bytes = 0;\n for (let i = 0; i < str.length; i++) {\n const c = str.charCodeAt(i);\n if (c < 0x80) bytes += 1;\n else if (c < 0x800) bytes += 2;\n else if (c < 0xd800 || c >= 0xe000) bytes += 3;\n else { i++; bytes += 4; } // surrogate pair\n }\n return bytes;\n}\n\n/**\n * Handle a NIP-5D NAP storage message from a napplet.\n * Routes to the canonical four `@napplet/nap/storage` actions:\n * - `storage.get` → `storage.get.result` `{ value: string | null }`\n * - `storage.set` → `storage.set.result` `{ ok: boolean }` (canonical only checks `error`)\n * - `storage.remove` → `storage.remove.result` `{ ok: boolean }`\n * - `storage.keys` → `storage.keys.result` `{ keys: string[] }`\n *\n * `storage.clear` is NOT in the canonical `@napplet/nap/storage` union (it was a\n * kehto unilateral extension); attempts produce a `storage.clear.result` envelope\n * with an `error` field set. Internal lifecycle cleanup still uses the\n * `cleanupNappState()` helper below — it is not napplet-reachable.\n *\n * **Per-instance scope (NAP-STORAGE / napplet/naps#3):** each request MAY carry\n * `scope: \"shared\" | \"instance\"` (default `\"shared\"`). `\"instance\"` addresses a\n * per-window sub-namespace keyed by `windowId`, so multiple windows of the same\n * napplet keep isolated state; `\"shared\"` (or absent) addresses the napplet-wide\n * namespace, byte-identical to historical behavior. An unrecognized `scope` value\n * is an invalid request and produces a `.result` envelope with `error` set.\n *\n * **Deviation note (Phase 15 to decide):** Set/remove results emit both `ok`\n * (legacy compat) and an `error` field on failure. Canonical `@napplet/nap/storage`\n * only specifies the optional `error`; napplets check `!result.error` for success.\n * Emitting `ok` preserves backward compatibility with existing in-tree callers.\n * Phase 15 (v1.2 release prep) decides whether to drop `ok` pre-release.\n *\n * @param windowId - The window identifier of the requesting napplet\n * @param msg - The NappletMessage containing the storage request\n * @param sendToNapplet - Transport function to send responses\n * @param sessionRegistry - Identity registry for looking up napplet session identity\n * @param aclState - ACL state for quota checks\n * @param statePersistence - State storage backend\n *\n * @example\n * ```ts\n * import { handleStorageNap } from '@kehto/runtime';\n *\n * handleStorageNap(windowId, { type: 'storage.get', id: 'q1', key: 'draft' },\n * sendToNapplet, sessionRegistry, aclState, statePersistence);\n * ```\n */\nexport function handleStorageNap(\n windowId: string,\n msg: NappletMessage,\n sendToNapplet: SendToNapplet,\n sessionRegistry: SessionRegistry,\n aclState: AclStateContainer,\n statePersistence: StatePersistence,\n): void {\n const m = msg as StorageMessage & {\n id?: string;\n key?: string;\n value?: string;\n scope?: string;\n };\n const id = m.id ?? '';\n const action = msg.type.split('.')[1];\n\n function sendResult(payload: Record<string, unknown>): void {\n sendToNapplet(windowId, { type: `${msg.type}.result`, id, ...payload } as NappletMessage);\n }\n function sendErrorNap(error: string): void {\n sendToNapplet(windowId, { type: `${msg.type}.result`, id, error } as NappletMessage);\n }\n\n // Identity lookup via windowId (NIP-5D path)\n const entry = sessionRegistry.getEntryByWindowId(windowId);\n if (!entry) { sendErrorNap('not registered'); return; }\n\n const { dTag, aggregateHash, pubkey } = entry;\n const prefix = `napplet-state:${dTag}:${aggregateHash}:`;\n const legacyPrefix = pubkey ? `napplet-state:${pubkey}:${dTag}:${aggregateHash}:` : '';\n\n // NAP-STORAGE per-call scope. Absent ⇔ \"shared\" (byte-identical to history).\n // An unrecognized value is an invalid request — the shell MUST return an error.\n if (m.scope !== undefined && m.scope !== 'shared' && m.scope !== 'instance') {\n sendErrorNap(`invalid scope: ${String(m.scope)} (expected \"shared\" or \"instance\")`);\n return;\n }\n const scope: StorageScope = m.scope === 'instance' ? 'instance' : 'shared';\n const isInstance = scope === 'instance';\n // The per-window sub-namespace for this request, when instance-scoped.\n const instancePrefix = `${prefix}${instanceSegment(windowId)}`;\n const keyFor = (userKey: string): string =>\n scopedKey(dTag, aggregateHash, userKey, isInstance, windowId);\n\n switch (action) {\n case 'get': {\n const key = m.key as string;\n if (!key) { sendErrorNap('missing key'); return; }\n // Instance scope addresses a fresh per-window namespace with no legacy data —\n // read only the instance key. Shared scope keeps the triple-read migration.\n if (isInstance) {\n sendResult({ value: statePersistence.get(keyFor(key)) });\n break;\n }\n const newKey = scopedKey(dTag, aggregateHash, key);\n // Triple-read for migration: new format, legacy-with-pubkey, old prefix\n let result = statePersistence.get(newKey);\n if (result === null && pubkey) {\n result = statePersistence.get(legacyScopedKey(pubkey, dTag, aggregateHash, key));\n }\n if (result === null && pubkey) {\n result = statePersistence.get(`napp-state:${pubkey}:${dTag}:${aggregateHash}:${key}`);\n }\n // Canonical @napplet/nap/storage: `value: string | null` — null ⇔ missing.\n sendResult({ value: result });\n break;\n }\n case 'set': {\n const key = m.key as string;\n const value = (m.value as string) ?? '';\n if (!key) { sendErrorNap('missing key'); return; }\n const quota = aclState.getStateQuota(pubkey ?? '', dTag, aggregateHash);\n const sk = keyFor(key);\n const newWriteBytes = byteLength(sk + value);\n // Quota is per napplet identity: `prefix` spans both the shared namespace and\n // every per-instance sub-key, so instance writes draw from the same budget.\n const existingBytes = statePersistence.calculateBytes(prefix, key);\n if (existingBytes + newWriteBytes > quota) {\n sendErrorNap(`quota exceeded: napplet state limit is ${quota} bytes`);\n return;\n }\n const success = statePersistence.set(sk, value);\n sendResult({ ok: success });\n break;\n }\n case 'remove': {\n const key = m.key as string;\n if (!key) { sendErrorNap('missing key'); return; }\n statePersistence.remove(keyFor(key));\n // legacyPrefix exists only while `pubkey` is non-empty (legacy AUTH sessions).\n // Suppress \"unused binding\" warnings: we intentionally retain the computation\n // so future migration lands at call sites, not here.\n void legacyPrefix;\n sendResult({ ok: true });\n break;\n }\n case 'clear': {\n // storage.clear was a kehto unilateral extension; it is NOT in the canonical\n // @napplet/nap/storage union. Napplets hitting this branch receive a\n // storage.clear.result envelope with the error field set. Internal lifecycle\n // cleanup uses cleanupNappState() below (not napplet-reachable).\n sendErrorNap('storage.clear is not in @napplet/nap/storage; action not supported');\n break;\n }\n case 'keys': {\n // Instance scope sees only this window's sub-namespace.\n if (isInstance) {\n const instKeys = statePersistence.keys(instancePrefix);\n const instanceKeySet = new Set<string>();\n for (const k of instKeys) {\n instanceKeySet.add(k.startsWith(instancePrefix) ? k.slice(instancePrefix.length) : k);\n }\n sendResult({ keys: Array.from(instanceKeySet) });\n break;\n }\n const newKeys = statePersistence.keys(prefix);\n const legacyKeys = legacyPrefix ? statePersistence.keys(legacyPrefix) : [];\n const userKeySet = new Set<string>();\n // Shared scope excludes per-instance sub-keys (reserved `@i/` marker) so an\n // instance-scoped napplet's per-window data never leaks into a shared listing.\n for (const k of newKeys) {\n if (!k.startsWith(prefix)) { userKeySet.add(k); continue; }\n const userKey = k.slice(prefix.length);\n if (userKey.startsWith(INSTANCE_MARKER)) continue;\n userKeySet.add(userKey);\n }\n for (const k of legacyKeys) userKeySet.add(k.startsWith(legacyPrefix) ? k.slice(legacyPrefix.length) : k);\n sendResult({ keys: Array.from(userKeySet) });\n break;\n }\n default:\n sendErrorNap(`unknown storage action: ${action}`);\n break;\n }\n}\n\n/**\n * Remove all state entries for a napplet identity.\n * Clears both new-format and legacy-format keys for completeness.\n * Used during napplet cleanup when a window is closed.\n *\n * @param pubkey - The napplet's pubkey (needed for legacy key cleanup)\n * @param dTag - The napplet's dTag\n * @param aggregateHash - The napplet's build hash\n * @param statePersistence - State storage backend\n *\n * @example\n * ```ts\n * import { cleanupNappState } from '@kehto/runtime';\n *\n * cleanupNappState(pubkey, dTag, aggregateHash, statePersistence);\n * ```\n */\nexport function cleanupNappState(\n pubkey: string,\n dTag: string,\n aggregateHash: string,\n statePersistence: StatePersistence,\n): void {\n // Clear new format\n const prefix = `napplet-state:${dTag}:${aggregateHash}:`;\n statePersistence.clear(prefix);\n // Clear legacy format (includes pubkey)\n const legacyPrefix = `napplet-state:${pubkey}:${dTag}:${aggregateHash}:`;\n statePersistence.clear(legacyPrefix);\n}\n","import type { NappletMessage } from '@napplet/core';\n\nimport type { AclStateContainer } from './acl-state.js';\nimport type { SessionRegistry } from './session-registry.js';\nimport { handleStorageNap } from './state-handler.js';\nimport type { RuntimeAdapter, ServiceRegistry } from './types.js';\n\ntype DomainHandler = (windowId: string, msg: NappletMessage) => void;\n\ntype RuntimeDomainContext = {\n hooks: RuntimeAdapter;\n serviceRegistry: ServiceRegistry;\n sessionRegistry: SessionRegistry;\n aclState: AclStateContainer;\n};\n\nexport type RuntimeDomainHandlers = {\n storage: DomainHandler;\n media: DomainHandler;\n keys: DomainHandler;\n notify: DomainHandler;\n theme: DomainHandler;\n config: DomainHandler;\n resource: DomainHandler;\n cvm: DomainHandler;\n outbox: DomainHandler;\n upload: DomainHandler;\n intent: DomainHandler;\n link: DomainHandler;\n common: DomainHandler;\n lists: DomainHandler;\n serial: DomainHandler;\n ble: DomainHandler;\n webrtc: DomainHandler;\n};\n\nconst THEME_FALLBACK_DEFAULT = {\n colors: { background: '#0a0a0a', text: '#e0e0e0', primary: '#7aa2f7' },\n} as const;\n\nexport function createRuntimeDomainHandlers(context: RuntimeDomainContext): RuntimeDomainHandlers {\n return {\n storage: (windowId, msg) => handleStorageMessage(context, windowId, msg),\n media: (windowId, msg) => handleMediaMessage(context, windowId, msg),\n keys: (windowId, msg) => handleKeysMessage(context, windowId, msg),\n notify: (windowId, msg) => handleNotifyMessage(context, windowId, msg),\n theme: (windowId, msg) => handleThemeMessage(context, windowId, msg),\n config: (windowId, msg) => handleServiceOnlyMessage(context, 'config', windowId, msg),\n resource: (windowId, msg) => handleServiceOnlyMessage(context, 'resource', windowId, msg),\n cvm: (windowId, msg) => handleServiceOnlyMessage(context, 'cvm', windowId, msg),\n outbox: (windowId, msg) => handleServiceOnlyMessage(context, 'outbox', windowId, msg),\n upload: (windowId, msg) => handleServiceOnlyMessage(context, 'upload', windowId, msg),\n intent: (windowId, msg) => handleServiceOnlyMessage(context, 'intent', windowId, msg),\n link: (windowId, msg) => handleServiceOnlyMessage(context, 'link', windowId, msg),\n common: (windowId, msg) => handleServiceOnlyMessage(context, 'common', windowId, msg),\n lists: (windowId, msg) => handleServiceOnlyMessage(context, 'lists', windowId, msg),\n serial: (windowId, msg) => handleServiceOnlyMessage(context, 'serial', windowId, msg),\n ble: (windowId, msg) => handleServiceOnlyMessage(context, 'ble', windowId, msg),\n webrtc: (windowId, msg) => handleServiceOnlyMessage(context, 'webrtc', windowId, msg),\n };\n}\n\nfunction handleStorageMessage(context: RuntimeDomainContext, windowId: string, msg: NappletMessage): void {\n const { aclState, hooks, sessionRegistry } = context;\n handleStorageNap(windowId, msg, hooks.sendToNapplet, sessionRegistry, aclState, hooks.statePersistence);\n}\n\nfunction handleMediaMessage(context: RuntimeDomainContext, windowId: string, msg: NappletMessage): void {\n const { hooks, serviceRegistry } = context;\n const mediaService = serviceRegistry['media'];\n if (mediaService) {\n mediaService.handleMessage(windowId, msg, (resp: NappletMessage) => hooks.sendToNapplet(windowId, resp));\n return;\n }\n if (msg.type === 'media.session.create') {\n const m = msg as NappletMessage & { id?: string; owner?: string; sessionId?: string };\n if (m.owner !== 'napplet' && m.owner !== 'shell') {\n hooks.sendToNapplet(windowId, {\n type: 'media.session.create.result',\n id: m.id ?? '',\n error: 'missing owner',\n } as NappletMessage);\n return;\n }\n if (m.owner === 'shell') {\n hooks.sendToNapplet(windowId, {\n type: 'media.session.create.result',\n id: m.id ?? '',\n owner: 'shell',\n error: 'unsupported owner mode',\n } as NappletMessage);\n return;\n }\n hooks.sendToNapplet(windowId, {\n type: 'media.session.create.result',\n id: m.id ?? '',\n sessionId: m.sessionId ?? '',\n owner: m.owner,\n } as NappletMessage);\n }\n}\n\nfunction handleKeysMessage(context: RuntimeDomainContext, windowId: string, msg: NappletMessage): void {\n const { hooks, serviceRegistry } = context;\n const keysService = serviceRegistry['keys'];\n if (keysService) {\n keysService.handleMessage(windowId, msg, (resp: NappletMessage) => hooks.sendToNapplet(windowId, resp));\n return;\n }\n if (msg.type === 'keys.forward') {\n forwardHotkey(hooks, msg);\n return;\n }\n if (msg.type === 'keys.registerAction') sendRegisterActionResult(hooks, windowId, msg);\n}\n\nfunction forwardHotkey(hooks: RuntimeAdapter, msg: NappletMessage): void {\n const m = msg as NappletMessage & {\n key?: string; code?: string;\n ctrl?: boolean; alt?: boolean; shift?: boolean; meta?: boolean;\n };\n hooks.hotkeys.executeHotkeyFromForward({\n key: m.key ?? '',\n code: m.code ?? '',\n ctrlKey: !!m.ctrl,\n altKey: !!m.alt,\n shiftKey: !!m.shift,\n metaKey: !!m.meta,\n });\n}\n\nfunction sendRegisterActionResult(hooks: RuntimeAdapter, windowId: string, msg: NappletMessage): void {\n const m = msg as NappletMessage & {\n id?: string; action?: { id: string; defaultKey?: string };\n };\n hooks.sendToNapplet(windowId, {\n type: 'keys.registerAction.result',\n id: m.id ?? '',\n actionId: m.action?.id ?? '',\n ...(m.action?.defaultKey ? { binding: m.action.defaultKey } : {}),\n } as NappletMessage);\n}\n\nfunction handleNotifyMessage(context: RuntimeDomainContext, windowId: string, msg: NappletMessage): void {\n const { hooks, serviceRegistry } = context;\n const notifyService = serviceRegistry['notify'];\n if (notifyService) {\n notifyService.handleMessage(windowId, msg, (resp: NappletMessage) => hooks.sendToNapplet(windowId, resp));\n return;\n }\n if (msg.type === 'notify.send') {\n const m = msg as NappletMessage & { id?: string };\n hooks.sendToNapplet(windowId, { type: 'notify.send.result', id: m.id ?? '', notificationId: `shell-${Date.now()}` } as NappletMessage);\n } else if (msg.type === 'notify.permission.request') {\n const m = msg as NappletMessage & { id?: string };\n hooks.sendToNapplet(windowId, { type: 'notify.permission.result', id: m.id ?? '', granted: true } as NappletMessage);\n }\n}\n\nfunction handleThemeMessage(context: RuntimeDomainContext, windowId: string, msg: NappletMessage): void {\n const { hooks, serviceRegistry } = context;\n const themeService = serviceRegistry['theme'];\n if (themeService) {\n themeService.handleMessage(windowId, msg, (resp: NappletMessage) => hooks.sendToNapplet(windowId, resp));\n return;\n }\n if (msg.type === 'theme.get') {\n const m = msg as NappletMessage & { id?: string };\n hooks.sendToNapplet(windowId, {\n type: 'theme.get.result',\n id: m.id ?? '',\n theme: THEME_FALLBACK_DEFAULT,\n } as NappletMessage);\n }\n}\n\nfunction handleServiceOnlyMessage(\n context: RuntimeDomainContext,\n name: 'config' | 'resource' | 'cvm' | 'outbox' | 'upload' | 'intent' | 'link' | 'common' | 'lists' | 'serial' | 'ble' | 'webrtc',\n windowId: string,\n msg: NappletMessage,\n): void {\n const service = context.serviceRegistry[name];\n if (!service) return;\n service.handleMessage(windowId, msg, (resp: NappletMessage) => context.hooks.sendToNapplet(windowId, resp));\n}\n","// @kehto/runtime — Browser-agnostic protocol engine for the napplet protocol.\n\nexport type {\n RuntimeAdapter,\n SendToNapplet,\n RelayPoolAdapter,\n RelaySubscriptionHandle,\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 ConsentRequest,\n ConsentHandler,\n SessionEntry,\n NappKeyEntry, // @deprecated — use SessionEntry\n PendingUpdate,\n PendingUpdateNotifier,\n ManifestCacheEntry,\n AclEntryExternal,\n AclCheckEvent,\n FirewallPersistence,\n FirewallEvent,\n ServiceHandler,\n ServiceRegistry,\n CompatibilityReport,\n ServiceInfo,\n ShellSecretPersistence,\n GuidPersistence,\n HashVerifierAdapter,\n VerificationCacheEntry,\n RuntimeConfigOverrides,\n NappletMessage,\n} from './types.js';\n\nexport { createEnforceGate, createNapEnforceGate, resolveCapabilitiesNap, formatDenialReason } from './enforce.js';\nexport type { EnforceResult, EnforceConfig, NapEnforceConfig, IdentityResolver, AclChecker, NapMessage } from './enforce.js';\n\nexport { createSessionRegistry, createNappKeyRegistry } from './session-registry.js';\nexport type { SessionRegistry, NappKeyRegistry } from './session-registry.js';\n\nexport { createAclState } from './acl-state.js';\nexport type { AclStateContainer } from './acl-state.js';\n\nexport { createFirewallState } from './firewall-state.js';\nexport type { FirewallStateContainer } from './firewall-state.js';\n\nexport { createManifestCache } from './manifest-cache.js';\nexport type { ManifestCache } from './manifest-cache.js';\n\nexport { createReplayDetector } from './replay.js';\nexport type { ReplayDetector } from './replay.js';\n\nexport { createEventBuffer, matchesFilter, matchesAnyFilter, RING_BUFFER_SIZE } from './event-buffer.js';\nexport type { EventBuffer, SubscriptionEntry } from './event-buffer.js';\n\nexport { createRuntime } from './runtime.js';\nexport type { Runtime } from './runtime.js';\n\nexport { handleStorageNap, cleanupNappState } from './state-handler.js';\n\nexport { routeServiceMessage, notifyServiceWindowDestroyed } from './service-dispatch.js';\n\nexport type { Capability } from '@kehto/acl/capabilities';\nexport { ALL_CAPABILITIES } from '@kehto/acl/capabilities';\nexport type { ServiceDescriptor } from './types.js';\n","\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","/**\n * Utilities for hex, bytes, CSPRNG.\n * @module\n */\n/*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */\n/** Checks if something is Uint8Array. Be careful: nodejs Buffer will return true. */\nexport function isBytes(a: unknown): a is Uint8Array {\n return a instanceof Uint8Array || (ArrayBuffer.isView(a) && a.constructor.name === 'Uint8Array');\n}\n\n/** Asserts something is positive integer. */\nexport function anumber(n: number, title: string = ''): void {\n if (!Number.isSafeInteger(n) || n < 0) {\n const prefix = title && `\"${title}\" `;\n throw new Error(`${prefix}expected integer >= 0, got ${n}`);\n }\n}\n\n/** Asserts something is Uint8Array. */\nexport function abytes(value: Uint8Array, length?: number, title: string = ''): Uint8Array {\n const bytes = isBytes(value);\n const len = value?.length;\n const needsLen = length !== undefined;\n if (!bytes || (needsLen && len !== length)) {\n const prefix = title && `\"${title}\" `;\n const ofLen = needsLen ? ` of length ${length}` : '';\n const got = bytes ? `length=${len}` : `type=${typeof value}`;\n throw new Error(prefix + 'expected Uint8Array' + ofLen + ', got ' + got);\n }\n return value;\n}\n\n/** Asserts something is hash */\nexport function ahash(h: CHash): void {\n if (typeof h !== 'function' || typeof h.create !== 'function')\n throw new Error('Hash must wrapped by utils.createHasher');\n anumber(h.outputLen);\n anumber(h.blockLen);\n}\n\n/** Asserts a hash instance has not been destroyed / finished */\nexport function aexists(instance: any, checkFinished = true): void {\n if (instance.destroyed) throw new Error('Hash instance has been destroyed');\n if (checkFinished && instance.finished) throw new Error('Hash#digest() has already been called');\n}\n\n/** Asserts output is properly-sized byte array */\nexport function aoutput(out: any, instance: any): void {\n abytes(out, undefined, 'digestInto() output');\n const min = instance.outputLen;\n if (out.length < min) {\n throw new Error('\"digestInto() output\" expected to be of length >=' + min);\n }\n}\n\n/** Generic type encompassing 8/16/32-byte arrays - but not 64-byte. */\n// prettier-ignore\nexport type TypedArray = Int8Array | Uint8ClampedArray | Uint8Array |\n Uint16Array | Int16Array | Uint32Array | Int32Array;\n\n/** Cast u8 / u16 / u32 to u8. */\nexport function u8(arr: TypedArray): Uint8Array {\n return new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength);\n}\n\n/** Cast u8 / u16 / u32 to u32. */\nexport function u32(arr: TypedArray): Uint32Array {\n return new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));\n}\n\n/** Zeroize a byte array. Warning: JS provides no guarantees. */\nexport function clean(...arrays: TypedArray[]): void {\n for (let i = 0; i < arrays.length; i++) {\n arrays[i].fill(0);\n }\n}\n\n/** Create DataView of an array for easy byte-level manipulation. */\nexport function createView(arr: TypedArray): DataView {\n return new DataView(arr.buffer, arr.byteOffset, arr.byteLength);\n}\n\n/** The rotate right (circular right shift) operation for uint32 */\nexport function rotr(word: number, shift: number): number {\n return (word << (32 - shift)) | (word >>> shift);\n}\n\n/** The rotate left (circular left shift) operation for uint32 */\nexport function rotl(word: number, shift: number): number {\n return (word << shift) | ((word >>> (32 - shift)) >>> 0);\n}\n\n/** Is current platform little-endian? Most are. Big-Endian platform: IBM */\nexport const isLE: boolean = /* @__PURE__ */ (() =>\n new Uint8Array(new Uint32Array([0x11223344]).buffer)[0] === 0x44)();\n\n/** The byte swap operation for uint32 */\nexport function byteSwap(word: number): number {\n return (\n ((word << 24) & 0xff000000) |\n ((word << 8) & 0xff0000) |\n ((word >>> 8) & 0xff00) |\n ((word >>> 24) & 0xff)\n );\n}\n/** Conditionally byte swap if on a big-endian platform */\nexport const swap8IfBE: (n: number) => number = isLE\n ? (n: number) => n\n : (n: number) => byteSwap(n);\n\n/** In place byte swap for Uint32Array */\nexport function byteSwap32(arr: Uint32Array): Uint32Array {\n for (let i = 0; i < arr.length; i++) {\n arr[i] = byteSwap(arr[i]);\n }\n return arr;\n}\n\nexport const swap32IfBE: (u: Uint32Array) => Uint32Array = isLE\n ? (u: Uint32Array) => u\n : byteSwap32;\n\n// Built-in hex conversion https://caniuse.com/mdn-javascript_builtins_uint8array_fromhex\nconst hasHexBuiltin: boolean = /* @__PURE__ */ (() =>\n // @ts-ignore\n typeof Uint8Array.from([]).toHex === 'function' && typeof Uint8Array.fromHex === 'function')();\n\n// Array where index 0xf0 (240) is mapped to string 'f0'\nconst hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) =>\n i.toString(16).padStart(2, '0')\n);\n\n/**\n * Convert byte array to hex string. Uses built-in function, when available.\n * @example bytesToHex(Uint8Array.from([0xca, 0xfe, 0x01, 0x23])) // 'cafe0123'\n */\nexport function bytesToHex(bytes: Uint8Array): string {\n abytes(bytes);\n // @ts-ignore\n if (hasHexBuiltin) return bytes.toHex();\n // pre-caching improves the speed 6x\n let hex = '';\n for (let i = 0; i < bytes.length; i++) {\n hex += hexes[bytes[i]];\n }\n return hex;\n}\n\n// We use optimized technique to convert hex string to byte array\nconst asciis = { _0: 48, _9: 57, A: 65, F: 70, a: 97, f: 102 } as const;\nfunction asciiToBase16(ch: number): number | undefined {\n if (ch >= asciis._0 && ch <= asciis._9) return ch - asciis._0; // '2' => 50-48\n if (ch >= asciis.A && ch <= asciis.F) return ch - (asciis.A - 10); // 'B' => 66-(65-10)\n if (ch >= asciis.a && ch <= asciis.f) return ch - (asciis.a - 10); // 'b' => 98-(97-10)\n return;\n}\n\n/**\n * Convert hex string to byte array. Uses built-in function, when available.\n * @example hexToBytes('cafe0123') // Uint8Array.from([0xca, 0xfe, 0x01, 0x23])\n */\nexport function hexToBytes(hex: string): Uint8Array {\n if (typeof hex !== 'string') throw new Error('hex string expected, got ' + typeof hex);\n // @ts-ignore\n if (hasHexBuiltin) return Uint8Array.fromHex(hex);\n const hl = hex.length;\n const al = hl / 2;\n if (hl % 2) throw new Error('hex string expected, got unpadded hex of length ' + hl);\n const array = new Uint8Array(al);\n for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {\n const n1 = asciiToBase16(hex.charCodeAt(hi));\n const n2 = asciiToBase16(hex.charCodeAt(hi + 1));\n if (n1 === undefined || n2 === undefined) {\n const char = hex[hi] + hex[hi + 1];\n throw new Error('hex string expected, got non-hex character \"' + char + '\" at index ' + hi);\n }\n array[ai] = n1 * 16 + n2; // multiply first octet, e.g. 'a3' => 10*16+3 => 160 + 3 => 163\n }\n return array;\n}\n\n/**\n * There is no setImmediate in browser and setTimeout is slow.\n * Call of async fn will return Promise, which will be fullfiled only on\n * next scheduler queue processing step and this is exactly what we need.\n */\nexport const nextTick = async (): Promise<void> => {};\n\n/** Returns control to thread each 'tick' ms to avoid blocking. */\nexport async function asyncLoop(\n iters: number,\n tick: number,\n cb: (i: number) => void\n): Promise<void> {\n let ts = Date.now();\n for (let i = 0; i < iters; i++) {\n cb(i);\n // Date.now() is not monotonic, so in case if clock goes backwards we return return control too\n const diff = Date.now() - ts;\n if (diff >= 0 && diff < tick) continue;\n await nextTick();\n ts += diff;\n }\n}\n\n// Global symbols, but ts doesn't see them: https://github.com/microsoft/TypeScript/issues/31535\ndeclare const TextEncoder: any;\n\n/**\n * Converts string to bytes using UTF8 encoding.\n * Built-in doesn't validate input to be string: we do the check.\n * @example utf8ToBytes('abc') // Uint8Array.from([97, 98, 99])\n */\nexport function utf8ToBytes(str: string): Uint8Array {\n if (typeof str !== 'string') throw new Error('string expected');\n return new Uint8Array(new TextEncoder().encode(str)); // https://bugzil.la/1681809\n}\n\n/** KDFs can accept string or Uint8Array for user convenience. */\nexport type KDFInput = string | Uint8Array;\n\n/**\n * Helper for KDFs: consumes uint8array or string.\n * When string is passed, does utf8 decoding, using TextDecoder.\n */\nexport function kdfInputToBytes(data: KDFInput, errorTitle = ''): Uint8Array {\n if (typeof data === 'string') return utf8ToBytes(data);\n return abytes(data, undefined, errorTitle);\n}\n\n/** Copies several Uint8Arrays into one. */\nexport function concatBytes(...arrays: Uint8Array[]): Uint8Array {\n let sum = 0;\n for (let i = 0; i < arrays.length; i++) {\n const a = arrays[i];\n abytes(a);\n sum += a.length;\n }\n const res = new Uint8Array(sum);\n for (let i = 0, pad = 0; i < arrays.length; i++) {\n const a = arrays[i];\n res.set(a, pad);\n pad += a.length;\n }\n return res;\n}\n\ntype EmptyObj = {};\n/** Merges default options and passed options. */\nexport function checkOpts<T1 extends EmptyObj, T2 extends EmptyObj>(\n defaults: T1,\n opts?: T2\n): T1 & T2 {\n if (opts !== undefined && {}.toString.call(opts) !== '[object Object]')\n throw new Error('options must be object or undefined');\n const merged = Object.assign(defaults, opts);\n return merged as T1 & T2;\n}\n\n/** Common interface for all hashes. */\nexport interface Hash<T> {\n blockLen: number; // Bytes per block\n outputLen: number; // Bytes in output\n update(buf: Uint8Array): this;\n digestInto(buf: Uint8Array): void;\n digest(): Uint8Array;\n destroy(): void;\n _cloneInto(to?: T): T;\n clone(): T;\n}\n\n/** PseudoRandom (number) Generator */\nexport interface PRG {\n addEntropy(seed: Uint8Array): void;\n randomBytes(length: number): Uint8Array;\n clean(): void;\n}\n\n/**\n * XOF: streaming API to read digest in chunks.\n * Same as 'squeeze' in keccak/k12 and 'seek' in blake3, but more generic name.\n * When hash used in XOF mode it is up to user to call '.destroy' afterwards, since we cannot\n * destroy state, next call can require more bytes.\n */\nexport type HashXOF<T extends Hash<T>> = Hash<T> & {\n xof(bytes: number): Uint8Array; // Read 'bytes' bytes from digest stream\n xofInto(buf: Uint8Array): Uint8Array; // read buf.length bytes from digest stream into buf\n};\n\n/** Hash constructor */\nexport type HasherCons<T, Opts = undefined> = Opts extends undefined ? () => T : (opts?: Opts) => T;\n/** Optional hash params. */\nexport type HashInfo = {\n oid?: Uint8Array; // DER encoded OID in bytes\n};\n/** Hash function */\nexport type CHash<T extends Hash<T> = Hash<any>, Opts = undefined> = {\n outputLen: number;\n blockLen: number;\n} & HashInfo &\n (Opts extends undefined\n ? {\n (msg: Uint8Array): Uint8Array;\n create(): T;\n }\n : {\n (msg: Uint8Array, opts?: Opts): Uint8Array;\n create(opts?: Opts): T;\n });\n/** XOF with output */\nexport type CHashXOF<T extends HashXOF<T> = HashXOF<any>, Opts = undefined> = CHash<T, Opts>;\n\n/** Creates function with outputLen, blockLen, create properties from a class constructor. */\nexport function createHasher<T extends Hash<T>, Opts = undefined>(\n hashCons: HasherCons<T, Opts>,\n info: HashInfo = {}\n): CHash<T, Opts> {\n const hashC: any = (msg: Uint8Array, opts?: Opts) => hashCons(opts).update(msg).digest();\n const tmp = hashCons(undefined);\n hashC.outputLen = tmp.outputLen;\n hashC.blockLen = tmp.blockLen;\n hashC.create = (opts?: Opts) => hashCons(opts);\n Object.assign(hashC, info);\n return Object.freeze(hashC);\n}\n\n/** Cryptographically secure PRNG. Uses internal OS-level `crypto.getRandomValues`. */\nexport function randomBytes(bytesLength = 32): Uint8Array {\n const cr = typeof globalThis === 'object' ? (globalThis as any).crypto : null;\n if (typeof cr?.getRandomValues !== 'function')\n throw new Error('crypto.getRandomValues must be defined');\n return cr.getRandomValues(new Uint8Array(bytesLength));\n}\n\n/** Creates OID opts for NIST hashes, with prefix 06 09 60 86 48 01 65 03 04 02. */\nexport const oidNist = (suffix: number): Required<HashInfo> => ({\n oid: Uint8Array.from([0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, suffix]),\n});\n","/*! scure-base - MIT License (c) 2022 Paul Miller (paulmillr.com) */\n\nexport interface Coder<F, T> {\n encode(from: F): T;\n decode(to: T): F;\n}\n\nexport interface BytesCoder extends Coder<Uint8Array, string> {\n encode: (data: Uint8Array) => string;\n decode: (str: string) => Uint8Array;\n}\n\nfunction isBytes(a: unknown): a is Uint8Array {\n return a instanceof Uint8Array || (ArrayBuffer.isView(a) && a.constructor.name === 'Uint8Array');\n}\n/** Asserts something is Uint8Array. */\nfunction abytes(b: Uint8Array | undefined): void {\n if (!isBytes(b)) throw new Error('Uint8Array expected');\n}\n\nfunction isArrayOf(isString: boolean, arr: any[]) {\n if (!Array.isArray(arr)) return false;\n if (arr.length === 0) return true;\n if (isString) {\n return arr.every((item) => typeof item === 'string');\n } else {\n return arr.every((item) => Number.isSafeInteger(item));\n }\n}\n\nfunction afn(input: Function): input is Function {\n if (typeof input !== 'function') throw new Error('function expected');\n return true;\n}\n\nfunction astr(label: string, input: unknown): input is string {\n if (typeof input !== 'string') throw new Error(`${label}: string expected`);\n return true;\n}\n\nfunction anumber(n: number): void {\n if (!Number.isSafeInteger(n)) throw new Error(`invalid integer: ${n}`);\n}\n\nfunction aArr(input: any[]) {\n if (!Array.isArray(input)) throw new Error('array expected');\n}\nfunction astrArr(label: string, input: string[]) {\n if (!isArrayOf(true, input)) throw new Error(`${label}: array of strings expected`);\n}\nfunction anumArr(label: string, input: number[]) {\n if (!isArrayOf(false, input)) throw new Error(`${label}: array of numbers expected`);\n}\n\n// TODO: some recusive type inference so it would check correct order of input/output inside rest?\n// like <string, number>, <number, bytes>, <bytes, float>\ntype Chain = [Coder<any, any>, ...Coder<any, any>[]];\n// Extract info from Coder type\ntype Input<F> = F extends Coder<infer T, any> ? T : never;\ntype Output<F> = F extends Coder<any, infer T> ? T : never;\n// Generic function for arrays\ntype First<T> = T extends [infer U, ...any[]] ? U : never;\ntype Last<T> = T extends [...any[], infer U] ? U : never;\ntype Tail<T> = T extends [any, ...infer U] ? U : never;\n\ntype AsChain<C extends Chain, Rest = Tail<C>> = {\n // C[K] = Coder<Input<C[K]>, Input<Rest[k]>>\n [K in keyof C]: Coder<Input<C[K]>, Input<K extends keyof Rest ? Rest[K] : any>>;\n};\n\n/**\n * @__NO_SIDE_EFFECTS__\n */\nfunction chain<T extends Chain & AsChain<T>>(...args: T): Coder<Input<First<T>>, Output<Last<T>>> {\n const id = (a: any) => a;\n // Wrap call in closure so JIT can inline calls\n const wrap = (a: any, b: any) => (c: any) => a(b(c));\n // Construct chain of args[-1].encode(args[-2].encode([...]))\n const encode = args.map((x) => x.encode).reduceRight(wrap, id);\n // Construct chain of args[0].decode(args[1].decode(...))\n const decode = args.map((x) => x.decode).reduce(wrap, id);\n return { encode, decode };\n}\n\n/**\n * Encodes integer radix representation to array of strings using alphabet and back.\n * Could also be array of strings.\n * @__NO_SIDE_EFFECTS__\n */\nfunction alphabet(letters: string | string[]): Coder<number[], string[]> {\n // mapping 1 to \"b\"\n const lettersA = typeof letters === 'string' ? letters.split('') : letters;\n const len = lettersA.length;\n astrArr('alphabet', lettersA);\n\n // mapping \"b\" to 1\n const indexes = new Map(lettersA.map((l, i) => [l, i]));\n return {\n encode: (digits: number[]) => {\n aArr(digits);\n return digits.map((i) => {\n if (!Number.isSafeInteger(i) || i < 0 || i >= len)\n throw new Error(\n `alphabet.encode: digit index outside alphabet \"${i}\". Allowed: ${letters}`\n );\n return lettersA[i]!;\n });\n },\n decode: (input: string[]): number[] => {\n aArr(input);\n return input.map((letter) => {\n astr('alphabet.decode', letter);\n const i = indexes.get(letter);\n if (i === undefined) throw new Error(`Unknown letter: \"${letter}\". Allowed: ${letters}`);\n return i;\n });\n },\n };\n}\n\n/**\n * @__NO_SIDE_EFFECTS__\n */\nfunction join(separator = ''): Coder<string[], string> {\n astr('join', separator);\n return {\n encode: (from) => {\n astrArr('join.decode', from);\n return from.join(separator);\n },\n decode: (to) => {\n astr('join.decode', to);\n return to.split(separator);\n },\n };\n}\n\n/**\n * Pad strings array so it has integer number of bits\n * @__NO_SIDE_EFFECTS__\n */\nfunction padding(bits: number, chr = '='): Coder<string[], string[]> {\n anumber(bits);\n astr('padding', chr);\n return {\n encode(data: string[]): string[] {\n astrArr('padding.encode', data);\n while ((data.length * bits) % 8) data.push(chr);\n return data;\n },\n decode(input: string[]): string[] {\n astrArr('padding.decode', input);\n let end = input.length;\n if ((end * bits) % 8)\n throw new Error('padding: invalid, string should have whole number of bytes');\n for (; end > 0 && input[end - 1] === chr; end--) {\n const last = end - 1;\n const byte = last * bits;\n if (byte % 8 === 0) throw new Error('padding: invalid, string has too much padding');\n }\n return input.slice(0, end);\n },\n };\n}\n\n/**\n * @__NO_SIDE_EFFECTS__\n */\nfunction normalize<T>(fn: (val: T) => T): Coder<T, T> {\n afn(fn);\n return { encode: (from: T) => from, decode: (to: T) => fn(to) };\n}\n\n/**\n * Slow: O(n^2) time complexity\n */\nfunction convertRadix(data: number[], from: number, to: number): number[] {\n // base 1 is impossible\n if (from < 2) throw new Error(`convertRadix: invalid from=${from}, base cannot be less than 2`);\n if (to < 2) throw new Error(`convertRadix: invalid to=${to}, base cannot be less than 2`);\n aArr(data);\n if (!data.length) return [];\n let pos = 0;\n const res = [];\n const digits = Array.from(data, (d) => {\n anumber(d);\n if (d < 0 || d >= from) throw new Error(`invalid integer: ${d}`);\n return d;\n });\n const dlen = digits.length;\n while (true) {\n let carry = 0;\n let done = true;\n for (let i = pos; i < dlen; i++) {\n const digit = digits[i]!;\n const fromCarry = from * carry;\n const digitBase = fromCarry + digit;\n if (\n !Number.isSafeInteger(digitBase) ||\n fromCarry / from !== carry ||\n digitBase - digit !== fromCarry\n ) {\n throw new Error('convertRadix: carry overflow');\n }\n const div = digitBase / to;\n carry = digitBase % to;\n const rounded = Math.floor(div);\n digits[i] = rounded;\n if (!Number.isSafeInteger(rounded) || rounded * to + carry !== digitBase)\n throw new Error('convertRadix: carry overflow');\n if (!done) continue;\n else if (!rounded) pos = i;\n else done = false;\n }\n res.push(carry);\n if (done) break;\n }\n for (let i = 0; i < data.length - 1 && data[i] === 0; i++) res.push(0);\n return res.reverse();\n}\n\nconst gcd = (a: number, b: number): number => (b === 0 ? a : gcd(b, a % b));\nconst radix2carry = /* @__NO_SIDE_EFFECTS__ */ (from: number, to: number) =>\n from + (to - gcd(from, to));\nconst powers: number[] = /* @__PURE__ */ (() => {\n let res = [];\n for (let i = 0; i < 40; i++) res.push(2 ** i);\n return res;\n})();\n/**\n * Implemented with numbers, because BigInt is 5x slower\n */\nfunction convertRadix2(data: number[], from: number, to: number, padding: boolean): number[] {\n aArr(data);\n if (from <= 0 || from > 32) throw new Error(`convertRadix2: wrong from=${from}`);\n if (to <= 0 || to > 32) throw new Error(`convertRadix2: wrong to=${to}`);\n if (radix2carry(from, to) > 32) {\n throw new Error(\n `convertRadix2: carry overflow from=${from} to=${to} carryBits=${radix2carry(from, to)}`\n );\n }\n let carry = 0;\n let pos = 0; // bitwise position in current element\n const max = powers[from]!;\n const mask = powers[to]! - 1;\n const res: number[] = [];\n for (const n of data) {\n anumber(n);\n if (n >= max) throw new Error(`convertRadix2: invalid data word=${n} from=${from}`);\n carry = (carry << from) | n;\n if (pos + from > 32) throw new Error(`convertRadix2: carry overflow pos=${pos} from=${from}`);\n pos += from;\n for (; pos >= to; pos -= to) res.push(((carry >> (pos - to)) & mask) >>> 0);\n const pow = powers[pos];\n if (pow === undefined) throw new Error('invalid carry');\n carry &= pow - 1; // clean carry, otherwise it will cause overflow\n }\n carry = (carry << (to - pos)) & mask;\n if (!padding && pos >= from) throw new Error('Excess padding');\n if (!padding && carry > 0) throw new Error(`Non-zero padding: ${carry}`);\n if (padding && pos > 0) res.push(carry >>> 0);\n return res;\n}\n\n/**\n * @__NO_SIDE_EFFECTS__\n */\nfunction radix(num: number): Coder<Uint8Array, number[]> {\n anumber(num);\n const _256 = 2 ** 8;\n return {\n encode: (bytes: Uint8Array) => {\n if (!isBytes(bytes)) throw new Error('radix.encode input should be Uint8Array');\n return convertRadix(Array.from(bytes), _256, num);\n },\n decode: (digits: number[]) => {\n anumArr('radix.decode', digits);\n return Uint8Array.from(convertRadix(digits, num, _256));\n },\n };\n}\n\n/**\n * If both bases are power of same number (like `2**8 <-> 2**64`),\n * there is a linear algorithm. For now we have implementation for power-of-two bases only.\n * @__NO_SIDE_EFFECTS__\n */\nfunction radix2(bits: number, revPadding = false): Coder<Uint8Array, number[]> {\n anumber(bits);\n if (bits <= 0 || bits > 32) throw new Error('radix2: bits should be in (0..32]');\n if (radix2carry(8, bits) > 32 || radix2carry(bits, 8) > 32)\n throw new Error('radix2: carry overflow');\n return {\n encode: (bytes: Uint8Array) => {\n if (!isBytes(bytes)) throw new Error('radix2.encode input should be Uint8Array');\n return convertRadix2(Array.from(bytes), 8, bits, !revPadding);\n },\n decode: (digits: number[]) => {\n anumArr('radix2.decode', digits);\n return Uint8Array.from(convertRadix2(digits, bits, 8, revPadding));\n },\n };\n}\n\ntype ArgumentTypes<F extends Function> = F extends (...args: infer A) => any ? A : never;\nfunction unsafeWrapper<T extends (...args: any) => any>(fn: T) {\n afn(fn);\n return function (...args: ArgumentTypes<T>): ReturnType<T> | void {\n try {\n return fn.apply(null, args);\n } catch (e) {}\n };\n}\n\nfunction checksum(\n len: number,\n fn: (data: Uint8Array) => Uint8Array\n): Coder<Uint8Array, Uint8Array> {\n anumber(len);\n afn(fn);\n return {\n encode(data: Uint8Array) {\n if (!isBytes(data)) throw new Error('checksum.encode: input should be Uint8Array');\n const sum = fn(data).slice(0, len);\n const res = new Uint8Array(data.length + len);\n res.set(data);\n res.set(sum, data.length);\n return res;\n },\n decode(data: Uint8Array) {\n if (!isBytes(data)) throw new Error('checksum.decode: input should be Uint8Array');\n const payload = data.slice(0, -len);\n const oldChecksum = data.slice(-len);\n const newChecksum = fn(payload).slice(0, len);\n for (let i = 0; i < len; i++)\n if (newChecksum[i] !== oldChecksum[i]) throw new Error('Invalid checksum');\n return payload;\n },\n };\n}\n\n// prettier-ignore\nexport const utils: { alphabet: typeof alphabet; chain: typeof chain; checksum: typeof checksum; convertRadix: typeof convertRadix; convertRadix2: typeof convertRadix2; radix: typeof radix; radix2: typeof radix2; join: typeof join; padding: typeof padding; } = {\n alphabet, chain, checksum, convertRadix, convertRadix2, radix, radix2, join, padding,\n};\n\n// RFC 4648 aka RFC 3548\n// ---------------------\n\n/**\n * base16 encoding from RFC 4648.\n * @example\n * ```js\n * base16.encode(Uint8Array.from([0x12, 0xab]));\n * // => '12AB'\n * ```\n */\nexport const base16: BytesCoder = chain(radix2(4), alphabet('0123456789ABCDEF'), join(''));\n\n/**\n * base32 encoding from RFC 4648. Has padding.\n * Use `base32nopad` for unpadded version.\n * Also check out `base32hex`, `base32hexnopad`, `base32crockford`.\n * @example\n * ```js\n * base32.encode(Uint8Array.from([0x12, 0xab]));\n * // => 'CKVQ===='\n * base32.decode('CKVQ====');\n * // => Uint8Array.from([0x12, 0xab])\n * ```\n */\nexport const base32: BytesCoder = chain(\n radix2(5),\n alphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'),\n padding(5),\n join('')\n);\n\n/**\n * base32 encoding from RFC 4648. No padding.\n * Use `base32` for padded version.\n * Also check out `base32hex`, `base32hexnopad`, `base32crockford`.\n * @example\n * ```js\n * base32nopad.encode(Uint8Array.from([0x12, 0xab]));\n * // => 'CKVQ'\n * base32nopad.decode('CKVQ');\n * // => Uint8Array.from([0x12, 0xab])\n * ```\n */\nexport const base32nopad: BytesCoder = chain(\n radix2(5),\n alphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'),\n join('')\n);\n/**\n * base32 encoding from RFC 4648. Padded. Compared to ordinary `base32`, slightly different alphabet.\n * Use `base32hexnopad` for unpadded version.\n * @example\n * ```js\n * base32hex.encode(Uint8Array.from([0x12, 0xab]));\n * // => '2ALG===='\n * base32hex.decode('2ALG====');\n * // => Uint8Array.from([0x12, 0xab])\n * ```\n */\nexport const base32hex: BytesCoder = chain(\n radix2(5),\n alphabet('0123456789ABCDEFGHIJKLMNOPQRSTUV'),\n padding(5),\n join('')\n);\n\n/**\n * base32 encoding from RFC 4648. No padding. Compared to ordinary `base32`, slightly different alphabet.\n * Use `base32hex` for padded version.\n * @example\n * ```js\n * base32hexnopad.encode(Uint8Array.from([0x12, 0xab]));\n * // => '2ALG'\n * base32hexnopad.decode('2ALG');\n * // => Uint8Array.from([0x12, 0xab])\n * ```\n */\nexport const base32hexnopad: BytesCoder = chain(\n radix2(5),\n alphabet('0123456789ABCDEFGHIJKLMNOPQRSTUV'),\n join('')\n);\n/**\n * base32 encoding from RFC 4648. Doug Crockford's version.\n * https://www.crockford.com/base32.html\n * @example\n * ```js\n * base32crockford.encode(Uint8Array.from([0x12, 0xab]));\n * // => '2ANG'\n * base32crockford.decode('2ANG');\n * // => Uint8Array.from([0x12, 0xab])\n * ```\n */\nexport const base32crockford: BytesCoder = chain(\n radix2(5),\n alphabet('0123456789ABCDEFGHJKMNPQRSTVWXYZ'),\n join(''),\n normalize((s: string) => s.toUpperCase().replace(/O/g, '0').replace(/[IL]/g, '1'))\n);\n\n// Built-in base64 conversion https://caniuse.com/mdn-javascript_builtins_uint8array_frombase64\n// prettier-ignore\nconst hasBase64Builtin: boolean = /* @__PURE__ */ (() =>\n typeof (Uint8Array as any).from([]).toBase64 === 'function' &&\n typeof (Uint8Array as any).fromBase64 === 'function')();\n\nconst decodeBase64Builtin = (s: string, isUrl: boolean) => {\n astr('base64', s);\n const re = isUrl ? /^[A-Za-z0-9=_-]+$/ : /^[A-Za-z0-9=+/]+$/;\n const alphabet = isUrl ? 'base64url' : 'base64';\n if (s.length > 0 && !re.test(s)) throw new Error('invalid base64');\n return (Uint8Array as any).fromBase64(s, { alphabet, lastChunkHandling: 'strict' });\n};\n\n/**\n * base64 from RFC 4648. Padded.\n * Use `base64nopad` for unpadded version.\n * Also check out `base64url`, `base64urlnopad`.\n * Falls back to built-in function, when available.\n * @example\n * ```js\n * base64.encode(Uint8Array.from([0x12, 0xab]));\n * // => 'Eqs='\n * base64.decode('Eqs=');\n * // => Uint8Array.from([0x12, 0xab])\n * ```\n */\n// prettier-ignore\nexport const base64: BytesCoder = hasBase64Builtin ? {\n encode(b) { abytes(b); return (b as any).toBase64(); },\n decode(s) { return decodeBase64Builtin(s, false); },\n} : chain(\n radix2(6),\n alphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'),\n padding(6),\n join('')\n);\n/**\n * base64 from RFC 4648. No padding.\n * Use `base64` for padded version.\n * @example\n * ```js\n * base64nopad.encode(Uint8Array.from([0x12, 0xab]));\n * // => 'Eqs'\n * base64nopad.decode('Eqs');\n * // => Uint8Array.from([0x12, 0xab])\n * ```\n */\nexport const base64nopad: BytesCoder = chain(\n radix2(6),\n alphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'),\n join('')\n);\n\n/**\n * base64 from RFC 4648, using URL-safe alphabet. Padded.\n * Use `base64urlnopad` for unpadded version.\n * Falls back to built-in function, when available.\n * @example\n * ```js\n * base64url.encode(Uint8Array.from([0x12, 0xab]));\n * // => 'Eqs='\n * base64url.decode('Eqs=');\n * // => Uint8Array.from([0x12, 0xab])\n * ```\n */\n// prettier-ignore\nexport const base64url: BytesCoder = hasBase64Builtin ? {\n encode(b) { abytes(b); return (b as any).toBase64({ alphabet: 'base64url' }); },\n decode(s) { return decodeBase64Builtin(s, true); },\n} : chain(\n radix2(6),\n alphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'),\n padding(6),\n join('')\n);\n\n/**\n * base64 from RFC 4648, using URL-safe alphabet. No padding.\n * Use `base64url` for padded version.\n * @example\n * ```js\n * base64urlnopad.encode(Uint8Array.from([0x12, 0xab]));\n * // => 'Eqs'\n * base64urlnopad.decode('Eqs');\n * // => Uint8Array.from([0x12, 0xab])\n * ```\n */\nexport const base64urlnopad: BytesCoder = chain(\n radix2(6),\n alphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'),\n join('')\n);\n\n// base58 code\n// -----------\nconst genBase58 = /* @__NO_SIDE_EFFECTS__ */ (abc: string) =>\n chain(radix(58), alphabet(abc), join(''));\n\n/**\n * base58: base64 without ambigous characters +, /, 0, O, I, l.\n * Quadratic (O(n^2)) - so, can't be used on large inputs.\n * @example\n * ```js\n * base58.decode('01abcdef');\n * // => '3UhJW'\n * ```\n */\nexport const base58: BytesCoder = genBase58(\n '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'\n);\n/**\n * base58: flickr version. Check out `base58`.\n */\nexport const base58flickr: BytesCoder = genBase58(\n '123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ'\n);\n/**\n * base58: XRP version. Check out `base58`.\n */\nexport const base58xrp: BytesCoder = genBase58(\n 'rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz'\n);\n\n// Data len (index) -> encoded block len\nconst XMR_BLOCK_LEN = [0, 2, 3, 5, 6, 7, 9, 10, 11];\n\n/**\n * base58: XMR version. Check out `base58`.\n * Done in 8-byte blocks (which equals 11 chars in decoding). Last (non-full) block padded with '1' to size in XMR_BLOCK_LEN.\n * Block encoding significantly reduces quadratic complexity of base58.\n */\nexport const base58xmr: BytesCoder = {\n encode(data: Uint8Array) {\n let res = '';\n for (let i = 0; i < data.length; i += 8) {\n const block = data.subarray(i, i + 8);\n res += base58.encode(block).padStart(XMR_BLOCK_LEN[block.length]!, '1');\n }\n return res;\n },\n decode(str: string) {\n let res: number[] = [];\n for (let i = 0; i < str.length; i += 11) {\n const slice = str.slice(i, i + 11);\n const blockLen = XMR_BLOCK_LEN.indexOf(slice.length);\n const block = base58.decode(slice);\n for (let j = 0; j < block.length - blockLen; j++) {\n if (block[j] !== 0) throw new Error('base58xmr: wrong padding');\n }\n res = res.concat(Array.from(block.slice(block.length - blockLen)));\n }\n return Uint8Array.from(res);\n },\n};\n\n/**\n * Method, which creates base58check encoder.\n * Requires function, calculating sha256.\n */\nexport const createBase58check = (sha256: (data: Uint8Array) => Uint8Array): BytesCoder =>\n chain(\n checksum(4, (data) => sha256(sha256(data))),\n base58\n );\n\n/**\n * Use `createBase58check` instead.\n * @deprecated\n */\nexport const base58check: (sha256: (data: Uint8Array) => Uint8Array) => BytesCoder =\n createBase58check;\n\n// Bech32 code\n// -----------\nexport interface Bech32Decoded<Prefix extends string = string> {\n prefix: Prefix;\n words: number[];\n}\nexport interface Bech32DecodedWithArray<Prefix extends string = string> {\n prefix: Prefix;\n words: number[];\n bytes: Uint8Array;\n}\n\nconst BECH_ALPHABET: Coder<number[], string> = chain(\n alphabet('qpzry9x8gf2tvdw0s3jn54khce6mua7l'),\n join('')\n);\n\nconst POLYMOD_GENERATORS = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3];\nfunction bech32Polymod(pre: number): number {\n const b = pre >> 25;\n let chk = (pre & 0x1ffffff) << 5;\n for (let i = 0; i < POLYMOD_GENERATORS.length; i++) {\n if (((b >> i) & 1) === 1) chk ^= POLYMOD_GENERATORS[i]!;\n }\n return chk;\n}\n\nfunction bechChecksum(prefix: string, words: number[], encodingConst = 1): string {\n const len = prefix.length;\n let chk = 1;\n for (let i = 0; i < len; i++) {\n const c = prefix.charCodeAt(i);\n if (c < 33 || c > 126) throw new Error(`Invalid prefix (${prefix})`);\n chk = bech32Polymod(chk) ^ (c >> 5);\n }\n chk = bech32Polymod(chk);\n for (let i = 0; i < len; i++) chk = bech32Polymod(chk) ^ (prefix.charCodeAt(i) & 0x1f);\n for (let v of words) chk = bech32Polymod(chk) ^ v;\n for (let i = 0; i < 6; i++) chk = bech32Polymod(chk);\n chk ^= encodingConst;\n return BECH_ALPHABET.encode(convertRadix2([chk % powers[30]!], 30, 5, false));\n}\n\nexport interface Bech32 {\n encode<Prefix extends string>(\n prefix: Prefix,\n words: number[] | Uint8Array,\n limit?: number | false\n ): `${Lowercase<Prefix>}1${string}`;\n decode<Prefix extends string>(\n str: `${Prefix}1${string}`,\n limit?: number | false\n ): Bech32Decoded<Prefix>;\n encodeFromBytes(prefix: string, bytes: Uint8Array): string;\n decodeToBytes(str: string): Bech32DecodedWithArray;\n decodeUnsafe(str: string, limit?: number | false): void | Bech32Decoded<string>;\n fromWords(to: number[]): Uint8Array;\n fromWordsUnsafe(to: number[]): void | Uint8Array;\n toWords(from: Uint8Array): number[];\n}\n/**\n * @__NO_SIDE_EFFECTS__\n */\nfunction genBech32(encoding: 'bech32' | 'bech32m'): Bech32 {\n const ENCODING_CONST = encoding === 'bech32' ? 1 : 0x2bc830a3;\n const _words = radix2(5);\n const fromWords = _words.decode;\n const toWords = _words.encode;\n const fromWordsUnsafe = unsafeWrapper(fromWords);\n\n function encode<Prefix extends string>(\n prefix: Prefix,\n words: number[] | Uint8Array,\n limit: number | false = 90\n ): `${Lowercase<Prefix>}1${string}` {\n astr('bech32.encode prefix', prefix);\n if (isBytes(words)) words = Array.from(words);\n anumArr('bech32.encode', words);\n const plen = prefix.length;\n if (plen === 0) throw new TypeError(`Invalid prefix length ${plen}`);\n const actualLength = plen + 7 + words.length;\n if (limit !== false && actualLength > limit)\n throw new TypeError(`Length ${actualLength} exceeds limit ${limit}`);\n const lowered = prefix.toLowerCase();\n const sum = bechChecksum(lowered, words, ENCODING_CONST);\n return `${lowered}1${BECH_ALPHABET.encode(words)}${sum}` as `${Lowercase<Prefix>}1${string}`;\n }\n\n function decode<Prefix extends string>(\n str: `${Prefix}1${string}`,\n limit?: number | false\n ): Bech32Decoded<Prefix>;\n function decode(str: string, limit?: number | false): Bech32Decoded;\n function decode(str: string, limit: number | false = 90): Bech32Decoded {\n astr('bech32.decode input', str);\n const slen = str.length;\n if (slen < 8 || (limit !== false && slen > limit))\n throw new TypeError(`invalid string length: ${slen} (${str}). Expected (8..${limit})`);\n // don't allow mixed case\n const lowered = str.toLowerCase();\n if (str !== lowered && str !== str.toUpperCase())\n throw new Error(`String must be lowercase or uppercase`);\n const sepIndex = lowered.lastIndexOf('1');\n if (sepIndex === 0 || sepIndex === -1)\n throw new Error(`Letter \"1\" must be present between prefix and data only`);\n const prefix = lowered.slice(0, sepIndex);\n const data = lowered.slice(sepIndex + 1);\n if (data.length < 6) throw new Error('Data must be at least 6 characters long');\n const words = BECH_ALPHABET.decode(data).slice(0, -6);\n const sum = bechChecksum(prefix, words, ENCODING_CONST);\n if (!data.endsWith(sum)) throw new Error(`Invalid checksum in ${str}: expected \"${sum}\"`);\n return { prefix, words };\n }\n\n const decodeUnsafe = unsafeWrapper(decode);\n\n function decodeToBytes(str: string): Bech32DecodedWithArray {\n const { prefix, words } = decode(str, false);\n return { prefix, words, bytes: fromWords(words) };\n }\n\n function encodeFromBytes(prefix: string, bytes: Uint8Array) {\n return encode(prefix, toWords(bytes));\n }\n\n return {\n encode,\n decode,\n encodeFromBytes,\n decodeToBytes,\n decodeUnsafe,\n fromWords,\n fromWordsUnsafe,\n toWords,\n };\n}\n\n/**\n * bech32 from BIP 173. Operates on words.\n * For high-level, check out scure-btc-signer:\n * https://github.com/paulmillr/scure-btc-signer.\n */\nexport const bech32: Bech32 = genBech32('bech32');\n\n/**\n * bech32m from BIP 350. Operates on words.\n * It was to mitigate `bech32` weaknesses.\n * For high-level, check out scure-btc-signer:\n * https://github.com/paulmillr/scure-btc-signer.\n */\nexport const bech32m: Bech32 = genBech32('bech32m');\n\ndeclare const TextEncoder: any;\ndeclare const TextDecoder: any;\n\n/**\n * UTF-8-to-byte decoder. Uses built-in TextDecoder / TextEncoder.\n * @example\n * ```js\n * const b = utf8.decode(\"hey\"); // => new Uint8Array([ 104, 101, 121 ])\n * const str = utf8.encode(b); // \"hey\"\n * ```\n */\nexport const utf8: BytesCoder = {\n encode: (data) => new TextDecoder().decode(data),\n decode: (str) => new TextEncoder().encode(str),\n};\n\n// Built-in hex conversion https://caniuse.com/mdn-javascript_builtins_uint8array_fromhex\n// prettier-ignore\nconst hasHexBuiltin: boolean = /* @__PURE__ */ (() =>\n typeof (Uint8Array as any).from([]).toHex === 'function' &&\n typeof (Uint8Array as any).fromHex === 'function')();\n// prettier-ignore\nconst hexBuiltin: BytesCoder = {\n encode(data) { abytes(data); return (data as any).toHex(); },\n decode(s) { astr('hex', s); return (Uint8Array as any).fromHex(s); },\n};\n/**\n * hex string decoder. Uses built-in function, when available.\n * @example\n * ```js\n * const b = hex.decode(\"0102ff\"); // => new Uint8Array([ 1, 2, 255 ])\n * const str = hex.encode(b); // \"0102ff\"\n * ```\n */\nexport const hex: BytesCoder = hasHexBuiltin\n ? hexBuiltin\n : chain(\n radix2(4),\n alphabet('0123456789abcdef'),\n join(''),\n normalize((s: string) => {\n if (typeof s !== 'string' || s.length % 2 !== 0)\n throw new TypeError(\n `hex.decode: expected string, got ${typeof s} with length ${s.length}`\n );\n return s.toLowerCase();\n })\n );\n\nexport type SomeCoders = {\n utf8: BytesCoder;\n hex: BytesCoder;\n base16: BytesCoder;\n base32: BytesCoder;\n base64: BytesCoder;\n base64url: BytesCoder;\n base58: BytesCoder;\n base58xmr: BytesCoder;\n};\n// prettier-ignore\nconst CODERS: SomeCoders = {\n utf8, hex, base16, base32, base64, base64url, base58, base58xmr\n};\ntype CoderType = keyof SomeCoders;\nconst coderTypeError =\n 'Invalid encoding type. Available types: utf8, hex, base16, base32, base64, base64url, base58, base58xmr';\n\n/** @deprecated */\nexport const bytesToString = (type: CoderType, bytes: Uint8Array): string => {\n if (typeof type !== 'string' || !CODERS.hasOwnProperty(type)) throw new TypeError(coderTypeError);\n if (!isBytes(bytes)) throw new TypeError('bytesToString() expects Uint8Array');\n return CODERS[type].encode(bytes);\n};\n\n/** @deprecated */\nexport const str: (type: CoderType, bytes: Uint8Array) => string = bytesToString; // as in python, but for bytes only\n\n/** @deprecated */\nexport const stringToBytes = (type: CoderType, str: string): Uint8Array => {\n if (!CODERS.hasOwnProperty(type)) throw new TypeError(coderTypeError);\n if (typeof str !== 'string') throw new TypeError('stringToBytes() expects string');\n return CODERS[type].decode(str);\n};\n/** @deprecated */\nexport const bytes: (type: CoderType, str: string) => Uint8Array = stringToBytes;\n","// nip19.ts\nimport { bytesToHex as bytesToHex2, concatBytes, hexToBytes as hexToBytes2 } from \"@noble/hashes/utils.js\";\nimport { bech32 } from \"@scure/base\";\n\n// utils.ts\nimport { bytesToHex, hexToBytes } from \"@noble/hashes/utils.js\";\nvar utf8Decoder = new TextDecoder(\"utf-8\");\nvar utf8Encoder = new TextEncoder();\n\n// nip19.ts\nvar NostrTypeGuard = {\n isNProfile: (value) => /^nprofile1[a-z\\d]+$/.test(value || \"\"),\n isNEvent: (value) => /^nevent1[a-z\\d]+$/.test(value || \"\"),\n isNAddr: (value) => /^naddr1[a-z\\d]+$/.test(value || \"\"),\n isNSec: (value) => /^nsec1[a-z\\d]{58}$/.test(value || \"\"),\n isNPub: (value) => /^npub1[a-z\\d]{58}$/.test(value || \"\"),\n isNote: (value) => /^note1[a-z\\d]+$/.test(value || \"\"),\n isNcryptsec: (value) => /^ncryptsec1[a-z\\d]+$/.test(value || \"\")\n};\nvar Bech32MaxSize = 5e3;\nvar BECH32_REGEX = /[\\x21-\\x7E]{1,83}1[023456789acdefghjklmnpqrstuvwxyz]{6,}/;\nfunction integerToUint8Array(number) {\n const uint8Array = new Uint8Array(4);\n uint8Array[0] = number >> 24 & 255;\n uint8Array[1] = number >> 16 & 255;\n uint8Array[2] = number >> 8 & 255;\n uint8Array[3] = number & 255;\n return uint8Array;\n}\nfunction decodeNostrURI(nip19code) {\n try {\n if (nip19code.startsWith(\"nostr:\"))\n nip19code = nip19code.substring(6);\n return decode(nip19code);\n } catch (_err) {\n return { type: \"invalid\", data: null };\n }\n}\nfunction decode(code) {\n let { prefix, words } = bech32.decode(code, Bech32MaxSize);\n let data = new Uint8Array(bech32.fromWords(words));\n switch (prefix) {\n case \"nprofile\": {\n let tlv = parseTLV(data);\n if (!tlv[0]?.[0])\n throw new Error(\"missing TLV 0 for nprofile\");\n if (tlv[0][0].length !== 32)\n throw new Error(\"TLV 0 should be 32 bytes\");\n return {\n type: \"nprofile\",\n data: {\n pubkey: bytesToHex2(tlv[0][0]),\n relays: tlv[1] ? tlv[1].map((d) => utf8Decoder.decode(d)) : []\n }\n };\n }\n case \"nevent\": {\n let tlv = parseTLV(data);\n if (!tlv[0]?.[0])\n throw new Error(\"missing TLV 0 for nevent\");\n if (tlv[0][0].length !== 32)\n throw new Error(\"TLV 0 should be 32 bytes\");\n if (tlv[2] && tlv[2][0].length !== 32)\n throw new Error(\"TLV 2 should be 32 bytes\");\n if (tlv[3] && tlv[3][0].length !== 4)\n throw new Error(\"TLV 3 should be 4 bytes\");\n return {\n type: \"nevent\",\n data: {\n id: bytesToHex2(tlv[0][0]),\n relays: tlv[1] ? tlv[1].map((d) => utf8Decoder.decode(d)) : [],\n author: tlv[2]?.[0] ? bytesToHex2(tlv[2][0]) : void 0,\n kind: tlv[3]?.[0] ? parseInt(bytesToHex2(tlv[3][0]), 16) : void 0\n }\n };\n }\n case \"naddr\": {\n let tlv = parseTLV(data);\n if (!tlv[0]?.[0])\n throw new Error(\"missing TLV 0 for naddr\");\n if (!tlv[2]?.[0])\n throw new Error(\"missing TLV 2 for naddr\");\n if (tlv[2][0].length !== 32)\n throw new Error(\"TLV 2 should be 32 bytes\");\n if (!tlv[3]?.[0])\n throw new Error(\"missing TLV 3 for naddr\");\n if (tlv[3][0].length !== 4)\n throw new Error(\"TLV 3 should be 4 bytes\");\n return {\n type: \"naddr\",\n data: {\n identifier: utf8Decoder.decode(tlv[0][0]),\n pubkey: bytesToHex2(tlv[2][0]),\n kind: parseInt(bytesToHex2(tlv[3][0]), 16),\n relays: tlv[1] ? tlv[1].map((d) => utf8Decoder.decode(d)) : []\n }\n };\n }\n case \"nsec\":\n return { type: prefix, data };\n case \"npub\":\n case \"note\":\n return { type: prefix, data: bytesToHex2(data) };\n default:\n throw new Error(`unknown prefix ${prefix}`);\n }\n}\nfunction parseTLV(data) {\n let result = {};\n let rest = data;\n while (rest.length > 0) {\n let t = rest[0];\n let l = rest[1];\n let v = rest.slice(2, 2 + l);\n rest = rest.slice(2 + l);\n if (v.length < l)\n throw new Error(`not enough data to read on TLV ${t}`);\n result[t] = result[t] || [];\n result[t].push(v);\n }\n return result;\n}\nfunction nsecEncode(key) {\n return encodeBytes(\"nsec\", key);\n}\nfunction npubEncode(hex) {\n return encodeBytes(\"npub\", hexToBytes2(hex));\n}\nfunction noteEncode(hex) {\n return encodeBytes(\"note\", hexToBytes2(hex));\n}\nfunction encodeBech32(prefix, data) {\n let words = bech32.toWords(data);\n return bech32.encode(prefix, words, Bech32MaxSize);\n}\nfunction encodeBytes(prefix, bytes) {\n return encodeBech32(prefix, bytes);\n}\nfunction nprofileEncode(profile) {\n let data = encodeTLV({\n 0: [hexToBytes2(profile.pubkey)],\n 1: (profile.relays || []).map((url) => utf8Encoder.encode(url))\n });\n return encodeBech32(\"nprofile\", data);\n}\nfunction neventEncode(event) {\n let kindArray;\n if (event.kind !== void 0) {\n kindArray = integerToUint8Array(event.kind);\n }\n let data = encodeTLV({\n 0: [hexToBytes2(event.id)],\n 1: (event.relays || []).map((url) => utf8Encoder.encode(url)),\n 2: event.author ? [hexToBytes2(event.author)] : [],\n 3: kindArray ? [new Uint8Array(kindArray)] : []\n });\n return encodeBech32(\"nevent\", data);\n}\nfunction naddrEncode(addr) {\n let kind = new ArrayBuffer(4);\n new DataView(kind).setUint32(0, addr.kind, false);\n let data = encodeTLV({\n 0: [utf8Encoder.encode(addr.identifier)],\n 1: (addr.relays || []).map((url) => utf8Encoder.encode(url)),\n 2: [hexToBytes2(addr.pubkey)],\n 3: [new Uint8Array(kind)]\n });\n return encodeBech32(\"naddr\", data);\n}\nfunction encodeTLV(tlv) {\n let entries = [];\n Object.entries(tlv).reverse().forEach(([t, vs]) => {\n vs.forEach((v) => {\n let entry = new Uint8Array(v.length + 2);\n entry.set([parseInt(t)], 0);\n entry.set([v.length], 1);\n entry.set(v, 2);\n entries.push(entry);\n });\n });\n return concatBytes(...entries);\n}\nexport {\n BECH32_REGEX,\n Bech32MaxSize,\n NostrTypeGuard,\n decode,\n decodeNostrURI,\n encodeBytes,\n naddrEncode,\n neventEncode,\n noteEncode,\n nprofileEncode,\n npubEncode,\n nsecEncode\n};\n","/**\n * audio-service.ts — Audio source registry as a ServiceHandler.\n *\n * Tracks which napplet windows are producing audio. Shell hosts wire this\n * into the runtime via registerService('audio', createAudioService(opts)).\n * Browser-agnostic — no DOM, no window, no postMessage.\n */\n\nimport type { NappletMessage } from '@napplet/core';\n// DRIFT-CORE-06 — Phase 11-deviation: ServiceDescriptor dropped from @napplet/core\n// v0.2.0+ (napplet phase-81). Re-exported from @kehto/runtime (canonical home after Phase 24 DRIFT-01).\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\nimport type { AudioSource, AudioServiceOptions } from './types.js';\n\n/** Audio service version — follows semver. */\nconst AUDIO_SERVICE_VERSION = '1.0.0';\n\n/**\n * Create an audio service handler.\n *\n * The audio service is a state registry that tracks active audio sources\n * per napplet window. Napplets announce audio state via `audio:*` topic\n * events; the service tracks sources and can relay mute commands back.\n *\n * @param options - Optional configuration (onChange callback for UI updates)\n * @returns A ServiceHandler to register with the runtime\n *\n * @example\n * ```ts\n * import { createAudioService } from '@kehto/services';\n *\n * const audio = createAudioService({\n * onChange: (sources) => {\n * // Update UI with current audio sources\n * for (const [windowId, source] of sources) {\n * console.log(`${source.title} (${source.muted ? 'muted' : 'playing'})`);\n * }\n * },\n * });\n *\n * runtime.registerService('audio', audio);\n * ```\n */\nexport function createAudioService(options?: AudioServiceOptions): ServiceHandler {\n const sources = new Map<string, AudioSource>();\n const onChange = options?.onChange;\n\n function notify(): void {\n onChange?.(new Map(sources));\n }\n\n const descriptor: ServiceDescriptor = {\n name: 'audio',\n version: AUDIO_SERVICE_VERSION,\n description: 'Audio source registry — tracks active audio sources per napplet window',\n };\n\n return {\n descriptor,\n\n handleMessage(windowId: string, message: NappletMessage, send: (msg: NappletMessage) => void): void {\n if (message.type !== 'inc.emit') return;\n const incMessage = message as NappletMessage & { topic?: unknown; payload?: unknown };\n const topic = typeof incMessage.topic === 'string' ? incMessage.topic : undefined;\n if (!topic?.startsWith('audio:')) return;\n\n const action = topic.slice(6); // 'audio:'.length === 6\n const payload = incMessage.payload && typeof incMessage.payload === 'object'\n ? incMessage.payload as Record<string, unknown>\n : {};\n\n switch (action) {\n case 'register': {\n const nappletClass = typeof payload.nappletClass === 'string' ? payload.nappletClass : '';\n const title = typeof payload.title === 'string' ? payload.title : '';\n sources.set(windowId, { windowId, nappletClass, title, muted: false });\n notify();\n break;\n }\n\n case 'unregister': {\n if (sources.delete(windowId)) {\n notify();\n }\n break;\n }\n\n case 'state-changed': {\n const source = sources.get(windowId);\n if (!source) return;\n if (typeof payload.title === 'string') {\n source.title = payload.title;\n }\n notify();\n break;\n }\n\n case 'mute': {\n const targetWindowId = typeof payload.windowId === 'string'\n ? payload.windowId\n : windowId;\n const muted = payload.muted === true;\n\n const source = sources.get(targetWindowId);\n if (source) {\n source.muted = muted;\n notify();\n }\n\n send({ type: 'inc.event', topic: 'napplet:audio-muted', payload: { muted } } as NappletMessage);\n break;\n }\n\n default:\n // Unknown audio action — ignore\n break;\n }\n },\n\n onWindowDestroyed(windowId: string): void {\n if (sources.delete(windowId)) {\n notify();\n }\n },\n };\n}\n","\nimport type { NappletMessage } from '@napplet/core';\n// DRIFT-CORE-06 — Phase 11-deviation: ServiceDescriptor dropped from @napplet/core\n// v0.2.0+ (napplet phase-81). Re-exported from @kehto/runtime (canonical home after Phase 24 DRIFT-01).\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\nimport type { Notification, NotificationServiceOptions } from './types.js';\n\n/** Notification service version — follows semver. */\nconst NOTIFICATION_SERVICE_VERSION = '1.0.0';\n\n/** Default maximum notifications per window. */\nconst DEFAULT_MAX_PER_WINDOW = 100;\n\n/** Counter for generating unique notification IDs. */\nlet idCounter = 0;\n\n/**\n * Generate a unique notification ID.\n */\nfunction generateId(): string {\n idCounter++;\n return `notif-${Date.now()}-${idCounter}`;\n}\n\ninterface NotificationStore {\n notifications: Map<string, Notification[]>;\n onChange?: (notifications: Notification[]) => void;\n maxPerWindow: number;\n}\n\nfunction getAllNotifications(store: NotificationStore): Notification[] {\n const all: Notification[] = [];\n for (const windowNotifs of store.notifications.values()) {\n all.push(...windowNotifs);\n }\n return all;\n}\n\nfunction notify(store: NotificationStore): void {\n store.onChange?.(getAllNotifications(store));\n}\n\nfunction getWindowNotifications(store: NotificationStore, windowId: string): Notification[] {\n let list = store.notifications.get(windowId);\n if (!list) {\n list = [];\n store.notifications.set(windowId, list);\n }\n return list;\n}\n\nfunction enforceLimit(store: NotificationStore, list: Notification[]): void {\n while (list.length > store.maxPerWindow) {\n list.shift();\n }\n}\n\nfunction findById(store: NotificationStore, id: string): [string, Notification, number] | undefined {\n for (const [windowId, list] of store.notifications) {\n const index = list.findIndex((n) => n.id === id);\n if (index !== -1) {\n return [windowId, list[index], index];\n }\n }\n return undefined;\n}\n\nfunction createNotification(\n store: NotificationStore,\n windowId: string,\n title: string,\n body: string,\n): Notification {\n const notification: Notification = {\n id: generateId(),\n windowId,\n title,\n body,\n read: false,\n createdAt: Math.floor(Date.now() / 1000),\n };\n const list = getWindowNotifications(store, windowId);\n list.push(notification);\n enforceLimit(store, list);\n notify(store);\n return notification;\n}\n\nfunction dismissNotification(store: NotificationStore, id: string): void {\n const found = findById(store, id);\n if (!found) return;\n const [foundWindowId, , index] = found;\n const list = store.notifications.get(foundWindowId);\n if (!list) return;\n list.splice(index, 1);\n if (list.length === 0) store.notifications.delete(foundWindowId);\n notify(store);\n}\n\nfunction markNotificationRead(store: NotificationStore, id: string): void {\n const found = findById(store, id);\n if (!found) return;\n const [, notification] = found;\n if (!notification.read) {\n notification.read = true;\n notify(store);\n }\n}\n\nfunction handleNotifyEnvelope(\n store: NotificationStore,\n windowId: string,\n action: string,\n msg: NappletMessage & Record<string, unknown>,\n send: (msg: NappletMessage) => void,\n): void {\n switch (action) {\n case 'create': {\n const title = typeof msg.title === 'string' ? msg.title : '';\n const body = typeof msg.body === 'string' ? msg.body : '';\n const notification = createNotification(store, windowId, title, body);\n send({ type: 'notify.created', id: notification.id } as NappletMessage);\n break;\n }\n\n case 'dismiss': {\n const notifId = typeof msg.notificationId === 'string' ? msg.notificationId : '';\n if (notifId) dismissNotification(store, notifId);\n break;\n }\n\n case 'read': {\n const notifId = typeof msg.notificationId === 'string' ? msg.notificationId : '';\n if (notifId) markNotificationRead(store, notifId);\n break;\n }\n\n case 'list': {\n const windowNotifs = store.notifications.get(windowId) ?? [];\n send({ type: 'notify.listed', notifications: windowNotifs } as NappletMessage);\n break;\n }\n\n default:\n break;\n }\n}\n\nfunction handleIncNotification(\n store: NotificationStore,\n windowId: string,\n action: string,\n payload: Record<string, unknown>,\n send: (msg: NappletMessage) => void,\n): void {\n switch (action) {\n case 'create': {\n const title = typeof payload.title === 'string' ? payload.title : '';\n const body = typeof payload.body === 'string' ? payload.body : '';\n const notification = createNotification(store, windowId, title, body);\n send({ type: 'inc.event', topic: 'notifications:created', payload: { id: notification.id } } as NappletMessage);\n break;\n }\n\n case 'dismiss': {\n const id = typeof payload.id === 'string' ? payload.id : '';\n if (id) dismissNotification(store, id);\n break;\n }\n\n case 'read': {\n const id = typeof payload.id === 'string' ? payload.id : '';\n if (id) markNotificationRead(store, id);\n break;\n }\n\n case 'list': {\n const windowNotifs = store.notifications.get(windowId) ?? [];\n send({ type: 'inc.event', topic: 'notifications:listed', payload: { notifications: windowNotifs } } as NappletMessage);\n break;\n }\n\n default:\n break;\n }\n}\n\n/**\n * Create a notification service handler.\n *\n * The notification service is a state registry that tracks notifications\n * per napplet window. Napplets create and manage notifications via\n * `notifications:*` topic events; the shell host controls presentation\n * via the onChange callback.\n *\n * @param options - Optional configuration (onChange callback, maxPerWindow limit)\n * @returns A ServiceHandler to register with the runtime\n *\n * @example\n * ```ts\n * import { createNotificationService } from '@kehto/services';\n *\n * const notifications = createNotificationService({\n * onChange: (list) => {\n * const unread = list.filter(n => !n.read);\n * updateBadge(unread.length);\n * },\n * maxPerWindow: 50,\n * });\n *\n * runtime.registerService('notifications', notifications);\n * ```\n */\nexport function createNotificationService(options?: NotificationServiceOptions): ServiceHandler {\n const store: NotificationStore = {\n notifications: new Map<string, Notification[]>(),\n onChange: options?.onChange,\n maxPerWindow: options?.maxPerWindow ?? DEFAULT_MAX_PER_WINDOW,\n };\n\n const descriptor: ServiceDescriptor = {\n name: 'notifications',\n version: NOTIFICATION_SERVICE_VERSION,\n description: 'Notification state registry — tracks notifications per napplet window',\n };\n\n return {\n descriptor,\n\n handleMessage(windowId: string, message: NappletMessage, send: (msg: NappletMessage) => void): void {\n const msg = message as NappletMessage & Record<string, unknown>;\n\n if (message.type.startsWith('notify.')) {\n handleNotifyEnvelope(store, windowId, message.type.slice(7), msg, send);\n return;\n }\n\n if (message.type !== 'inc.emit') return;\n const topic = msg.topic as string | undefined;\n if (!topic?.startsWith('notifications:')) return;\n\n const payload = ((msg.payload ?? {}) as Record<string, unknown>);\n handleIncNotification(store, windowId, topic.slice(14), payload, send);\n },\n\n onWindowDestroyed(windowId: string): void {\n if (store.notifications.delete(windowId)) {\n notify(store);\n }\n },\n };\n}\n","/**\n * identity-service.ts — NIP-5D identity nap reference service.\n *\n * MIGRATION from signer-service (v1.1 -> v1.2):\n * - signer.getPublicKey -> identity.getPublicKey (same shell state)\n * - signer.getRelays -> identity.getRelays (same shell state)\n * - signer.signEvent -> DELETED (no napplet-visible path; shell signs\n * internally inside relay.publish)\n * - signer.nip04.encrypt/decrypt -> DELETED\n * - signer.nip44.encrypt/decrypt -> DELETED (shell encrypts internally\n * inside relay.publishEncrypted)\n *\n * Identity is strictly read-only per NAP-IDENTITY: napplets learn *about* the\n * user but cannot act *as* the user — no signing, encryption, or decryption.\n * (An `identity.decrypt` capability was briefly added in v1.8 and removed as a\n * spec violation; decryption belongs to the runtime, inline over the wire.)\n *\n * Handles 9 identity.* request types from @napplet/nap/identity. getPublicKey\n * and getRelays return real values sourced from hooks.auth.getSigner(); the\n * remaining read-only queries can be backed by optional host providers. When a\n * provider is absent, the service keeps returning spec-shaped empty results.\n */\n\nimport type { NappletMessage } from '@napplet/core';\nimport type { ServiceHandler, Signer } from '@kehto/runtime';\nimport type {\n IdentityGetBadgesMessage,\n IdentityGetBlockedMessage,\n IdentityGetFollowsMessage,\n IdentityGetListMessage,\n IdentityGetMutesMessage,\n IdentityGetProfileMessage,\n IdentityGetPublicKeyResultMessage,\n IdentityGetRelaysResultMessage,\n IdentityGetProfileResultMessage,\n IdentityGetFollowsResultMessage,\n IdentityGetListResultMessage,\n IdentityGetZapsResultMessage,\n IdentityGetZapsMessage,\n IdentityGetMutesResultMessage,\n IdentityGetBlockedResultMessage,\n IdentityGetBadgesResultMessage,\n RelayPermission,\n} from '@napplet/nap/identity/types';\n\n/** Identity service version — follows semver. */\nconst IDENTITY_SERVICE_VERSION = '1.0.0';\n\n/** A value that may be returned synchronously or through a Promise. */\nexport type MaybePromise<T> = T | Promise<T>;\n\ntype IdentityProviderResult = NappletMessage & { error?: string };\ntype SendIdentityMessage = (msg: NappletMessage) => void;\n\nfunction sendProviderError<T extends IdentityProviderResult>(\n send: SendIdentityMessage,\n result: T,\n fallback: string,\n err: unknown,\n): void {\n send({\n ...result,\n error: (err as Error)?.message ?? fallback,\n });\n}\n\nasync function getCurrentPubkey(options: IdentityServiceOptions): Promise<string> {\n const currentSigner = options.getSigner();\n if (!currentSigner?.getPublicKey) return '';\n try {\n return (await currentSigner.getPublicKey()) ?? '';\n } catch {\n return '';\n }\n}\n\nfunction sendOptionalProviderResult<T extends IdentityProviderResult>(\n options: IdentityServiceOptions,\n send: SendIdentityMessage,\n fallbackResult: T,\n errorFallback: string,\n buildResult?: (pubkey: string) => MaybePromise<T>,\n): void {\n if (!buildResult) {\n send(fallbackResult);\n return;\n }\n\n Promise.resolve(getCurrentPubkey(options))\n .then((pubkey) => buildResult(pubkey))\n .then((result) => send(result))\n .catch((err: unknown) => sendProviderError(send, fallbackResult, errorFallback, err));\n}\n\n/**\n * Options for creating the identity service.\n *\n * @example\n * ```ts\n * const identityService = createIdentityService({\n * getSigner: () => window.nostr ?? null,\n * });\n * runtime.registerService('identity', identityService);\n * ```\n */\nexport interface IdentityServiceOptions {\n /**\n * Return the NIP-07-compatible signer (or null) used to resolve\n * identity.getPublicKey / identity.getRelays. Called on every request —\n * availability can change dynamically.\n */\n getSigner: () => Signer | null;\n\n /**\n * Optional host-backed profile lookup for the current user.\n *\n * Kehto does not query relays itself; hosts that already maintain kind-0\n * metadata can provide it here without replacing the whole identity service.\n *\n * @param pubkey - Current signer pubkey, or \"\" when no signer is connected.\n * @param message - Original identity.getProfile request envelope.\n * @returns Profile metadata, or null when unavailable.\n */\n getProfile?: (\n pubkey: string,\n message: IdentityGetProfileMessage,\n ) => MaybePromise<IdentityGetProfileResultMessage['profile']>;\n\n /**\n * Optional host-backed follow-list lookup for the current user.\n *\n * @param pubkey - Current signer pubkey, or \"\" when no signer is connected.\n * @param message - Original identity.getFollows request envelope.\n * @returns Hex-encoded followed pubkeys.\n */\n getFollows?: (\n pubkey: string,\n message: IdentityGetFollowsMessage,\n ) => MaybePromise<IdentityGetFollowsResultMessage['pubkeys']>;\n\n /**\n * Optional host-backed categorized-list lookup for the current user.\n *\n * @param listType - Requested list category from the wire envelope.\n * @param pubkey - Current signer pubkey, or \"\" when no signer is connected.\n * @param message - Original identity.getList request envelope.\n * @returns List entries.\n */\n getList?: (\n listType: string,\n pubkey: string,\n message: IdentityGetListMessage,\n ) => MaybePromise<IdentityGetListResultMessage['entries']>;\n\n /**\n * Optional host-backed zap receipt lookup for the current user.\n *\n * @param pubkey - Current signer pubkey, or \"\" when no signer is connected.\n * @param message - Original identity.getZaps request envelope.\n * @returns Zap receipts.\n */\n getZaps?: (\n pubkey: string,\n message: IdentityGetZapsMessage,\n ) => MaybePromise<IdentityGetZapsResultMessage['zaps']>;\n\n /**\n * Optional host-backed mute-list lookup for the current user.\n *\n * @param pubkey - Current signer pubkey, or \"\" when no signer is connected.\n * @param message - Original identity.getMutes request envelope.\n * @returns Hex-encoded muted pubkeys.\n */\n getMutes?: (\n pubkey: string,\n message: IdentityGetMutesMessage,\n ) => MaybePromise<IdentityGetMutesResultMessage['pubkeys']>;\n\n /**\n * Optional host-backed block-list lookup for the current user.\n *\n * @param pubkey - Current signer pubkey, or \"\" when no signer is connected.\n * @param message - Original identity.getBlocked request envelope.\n * @returns Hex-encoded blocked pubkeys.\n */\n getBlocked?: (\n pubkey: string,\n message: IdentityGetBlockedMessage,\n ) => MaybePromise<IdentityGetBlockedResultMessage['pubkeys']>;\n\n /**\n * Optional host-backed badge lookup for the current user.\n *\n * @param pubkey - Current signer pubkey, or \"\" when no signer is connected.\n * @param message - Original identity.getBadges request envelope.\n * @returns Badges awarded to the user.\n */\n getBadges?: (\n pubkey: string,\n message: IdentityGetBadgesMessage,\n ) => MaybePromise<IdentityGetBadgesResultMessage['badges']>;\n}\n\nfunction sendIdentityError(\n send: SendIdentityMessage,\n id: string,\n typeBase: string,\n error: string,\n): void {\n send({ type: `${typeBase}.error`, id, error } as NappletMessage);\n}\n\nfunction sendSignerError(\n send: SendIdentityMessage,\n id: string,\n typeBase: string,\n fallback: string,\n err: unknown,\n): void {\n sendIdentityError(send, id, typeBase, (err as Error)?.message ?? fallback);\n}\n\nfunction handleGetPublicKey(options: IdentityServiceOptions, id: string, send: SendIdentityMessage): void {\n const signer = options.getSigner();\n if (!signer) {\n const result: IdentityGetPublicKeyResultMessage = {\n type: 'identity.getPublicKey.result',\n id,\n pubkey: '',\n };\n send(result);\n return;\n }\n\n Promise.resolve(signer.getPublicKey?.())\n .then((pubkey) => {\n const result: IdentityGetPublicKeyResultMessage = {\n type: 'identity.getPublicKey.result',\n id,\n pubkey: (pubkey as string) ?? '',\n };\n send(result);\n })\n .catch((err: unknown) => sendSignerError(send, id, 'identity.getPublicKey', 'getPublicKey failed', err));\n}\n\nfunction handleGetRelays(options: IdentityServiceOptions, id: string, send: SendIdentityMessage): void {\n const signer = options.getSigner();\n if (!signer) {\n sendIdentityError(send, id, 'identity.getRelays', 'no signer configured');\n return;\n }\n\n Promise.resolve(signer.getRelays?.() ?? {})\n .then((relays) => {\n const result: IdentityGetRelaysResultMessage = {\n type: 'identity.getRelays.result',\n id,\n relays: relays as Record<string, RelayPermission>,\n };\n send(result);\n })\n .catch((err: unknown) => sendSignerError(send, id, 'identity.getRelays', 'getRelays failed', err));\n}\n\nfunction handleReadProvider(\n options: IdentityServiceOptions,\n id: string,\n message: NappletMessage,\n send: SendIdentityMessage,\n): boolean {\n switch (message.type) {\n case 'identity.getProfile':\n sendOptionalProviderResult(\n options,\n send,\n { type: 'identity.getProfile.result', id, profile: null },\n 'getProfile failed',\n options.getProfile\n ? async (pubkey): Promise<IdentityGetProfileResultMessage> => ({\n type: 'identity.getProfile.result',\n id,\n profile: await options.getProfile!(pubkey, message as IdentityGetProfileMessage),\n })\n : undefined,\n );\n return true;\n\n case 'identity.getFollows':\n sendOptionalProviderResult(\n options,\n send,\n { type: 'identity.getFollows.result', id, pubkeys: [] },\n 'getFollows failed',\n options.getFollows\n ? async (pubkey): Promise<IdentityGetFollowsResultMessage> => ({\n type: 'identity.getFollows.result',\n id,\n pubkeys: await options.getFollows!(pubkey, message as IdentityGetFollowsMessage),\n })\n : undefined,\n );\n return true;\n\n case 'identity.getList':\n handleGetList(options, id, message as IdentityGetListMessage, send);\n return true;\n\n case 'identity.getZaps':\n sendOptionalProviderResult(\n options,\n send,\n { type: 'identity.getZaps.result', id, zaps: [] },\n 'getZaps failed',\n options.getZaps\n ? async (pubkey): Promise<IdentityGetZapsResultMessage> => ({\n type: 'identity.getZaps.result',\n id,\n zaps: await options.getZaps!(pubkey, message as IdentityGetZapsMessage),\n })\n : undefined,\n );\n return true;\n\n case 'identity.getMutes':\n sendOptionalProviderResult(\n options,\n send,\n { type: 'identity.getMutes.result', id, pubkeys: [] },\n 'getMutes failed',\n options.getMutes\n ? async (pubkey): Promise<IdentityGetMutesResultMessage> => ({\n type: 'identity.getMutes.result',\n id,\n pubkeys: await options.getMutes!(pubkey, message as IdentityGetMutesMessage),\n })\n : undefined,\n );\n return true;\n\n case 'identity.getBlocked':\n sendOptionalProviderResult(\n options,\n send,\n { type: 'identity.getBlocked.result', id, pubkeys: [] },\n 'getBlocked failed',\n options.getBlocked\n ? async (pubkey): Promise<IdentityGetBlockedResultMessage> => ({\n type: 'identity.getBlocked.result',\n id,\n pubkeys: await options.getBlocked!(pubkey, message as IdentityGetBlockedMessage),\n })\n : undefined,\n );\n return true;\n\n case 'identity.getBadges':\n sendOptionalProviderResult(\n options,\n send,\n { type: 'identity.getBadges.result', id, badges: [] },\n 'getBadges failed',\n options.getBadges\n ? async (pubkey): Promise<IdentityGetBadgesResultMessage> => ({\n type: 'identity.getBadges.result',\n id,\n badges: await options.getBadges!(pubkey, message as IdentityGetBadgesMessage),\n })\n : undefined,\n );\n return true;\n\n default:\n return false;\n }\n}\n\nfunction handleGetList(\n options: IdentityServiceOptions,\n id: string,\n message: IdentityGetListMessage,\n send: SendIdentityMessage,\n): void {\n sendOptionalProviderResult(\n options,\n send,\n { type: 'identity.getList.result', id, entries: [] },\n 'getList failed',\n options.getList\n ? async (pubkey): Promise<IdentityGetListResultMessage> => ({\n type: 'identity.getList.result',\n id,\n entries: await options.getList!(message.listType, pubkey, message),\n })\n : undefined,\n );\n}\n\nfunction handleIdentityServiceMessage(\n options: IdentityServiceOptions,\n message: NappletMessage,\n send: SendIdentityMessage,\n): void {\n const id = (message as NappletMessage & { id?: string }).id ?? '';\n\n switch (message.type) {\n case 'identity.getPublicKey':\n // Per NIP-5D, no signer resolves to the empty-pubkey sentinel, not an error.\n handleGetPublicKey(options, id, send);\n return;\n\n case 'identity.getRelays':\n handleGetRelays(options, id, send);\n return;\n\n default:\n if (!handleReadProvider(options, id, message, send)) {\n sendIdentityError(send, id, message.type, `Unknown identity method: ${message.type}`);\n }\n }\n}\n\n/**\n * Create an identity service that handles NIP-5D identity.* envelope messages.\n *\n * Supports the 9 read-only identity.* request types from @napplet/nap/identity.\n * The two nostr-info queries (getPublicKey, getRelays) resolve through the\n * caller-supplied signer; the remaining read-only queries resolve through\n * optional host providers or return default/empty payloads with spec-correct\n * envelope shapes so napplets always receive a result envelope.\n *\n * @param options - Identity service configuration (getSigner)\n * @returns A ServiceHandler ready for runtime.registerService('identity', handler)\n *\n * @example\n * ```ts\n * import { createIdentityService } from '@kehto/services';\n *\n * const identity = createIdentityService({\n * getSigner: () => mySignerAdapter,\n * });\n * runtime.registerService('identity', identity);\n * ```\n */\nexport function createIdentityService(options: IdentityServiceOptions): ServiceHandler {\n return {\n descriptor: {\n name: 'identity',\n version: IDENTITY_SERVICE_VERSION,\n description: 'NIP-5D identity NAP reference handler (9 read-only identity queries)',\n },\n\n handleMessage(\n _windowId: string,\n message: NappletMessage,\n send: (msg: NappletMessage) => void,\n ): void {\n handleIdentityServiceMessage(options, message, send);\n },\n\n // Identity service has no per-window state to clean up.\n onWindowDestroyed(_windowId: string): void {\n /* no-op */\n },\n };\n}\n","/**\n * relay-pool-service.ts — Relay pool as a ServiceHandler.\n *\n * Wraps an existing relay pool implementation (subscribe, publish,\n * selectRelayTier, isAvailable) as a ServiceHandler that receives\n * relay NAP envelope messages and manages subscription lifecycle.\n *\n * Handles: relay.subscribe, relay.close, relay.publish, relay.publishEncrypted.\n *\n * Note on `relay.publishEncrypted`: the canonical napplet→shell path routes\n * through @kehto/runtime handleRelayMessage, which performs shell-internal\n * NIP-44/NIP-04 encryption via the signer, then synthesizes a relay.publish\n * envelope handed to this service. The publishEncrypted branch here is a\n * fallback for alternate wirings; by the time the service sees the\n * envelope, content MUST already be ciphertext (the service never encrypts\n * or decrypts).\n */\n\nimport type { NostrEvent, NostrFilter, NappletMessage } from '@napplet/core';\nimport type { ServiceHandler } from '@kehto/runtime';\n\n// Timer globals available in all JS runtimes\ndeclare function setTimeout(callback: () => void, ms: number): unknown;\ndeclare function clearTimeout(id: unknown): void;\n\n/** EOSE fallback timeout in milliseconds. */\nconst EOSE_FALLBACK_MS = 15_000;\n\n/**\n * Options for creating a relay pool service.\n *\n * @example\n * ```ts\n * const relayPoolService = createRelayPoolService({\n * subscribe: (filters, cb, urls) => myPool.subscribe(filters, cb, urls),\n * publish: (event) => myPool.publish(event),\n * selectRelayTier: (filters) => myPool.selectRelays(filters),\n * isAvailable: () => myPool.connected,\n * });\n * ```\n */\nexport interface RelayPoolServiceOptions {\n /**\n * Subscribe to events matching filters. Returns handle with unsubscribe().\n *\n * @param filters - NIP-01 filter objects\n * @param callback - Receives matching events or 'EOSE'\n * @param relayUrls - Optional relay URL hints\n * @returns Handle to cancel the subscription\n */\n subscribe(\n filters: NostrFilter[],\n callback: (item: NostrEvent | 'EOSE') => void,\n relayUrls?: string[],\n ): { unsubscribe(): void };\n\n /**\n * Publish an event to relays.\n *\n * @param event - The event to publish\n */\n publish(event: NostrEvent): void;\n\n /**\n * Select relay URLs appropriate for the given filters.\n *\n * @param filters - NIP-01 filter objects\n * @returns Array of relay URLs\n */\n selectRelayTier(filters: NostrFilter[]): string[];\n\n /**\n * Whether the relay pool is available and connected.\n *\n * @returns true if the relay pool can handle requests\n */\n isAvailable(): boolean;\n}\n\n/** Internal subscription tracking entry. */\ninterface TrackedSubscription {\n handle: { unsubscribe(): void };\n eoseTimer: unknown;\n}\n\ntype RelayServiceMessage = NappletMessage & {\n subId?: unknown;\n filters?: unknown;\n event?: unknown;\n relay?: unknown;\n};\n\n/**\n * Create a relay pool service that wraps an existing relay pool\n * implementation as a ServiceHandler.\n *\n * Handles relay.subscribe, relay.close, and relay.publish envelopes.\n * Tracks subscriptions per windowId:subId for lifecycle management.\n * Sets a 15-second EOSE fallback timer on each subscription.\n *\n * @param options - Relay pool implementation to wrap\n * @returns A ServiceHandler ready for runtime.registerService('relay', handler)\n *\n * @example\n * ```ts\n * import { createRelayPoolService } from '@kehto/services';\n *\n * const pool = createRelayPoolService({\n * subscribe: (f, cb, urls) => applesauce.subscribe(f, cb, urls),\n * publish: (e) => applesauce.publish(e),\n * selectRelayTier: (f) => applesauce.getRelays(f),\n * isAvailable: () => applesauce.connected,\n * });\n * runtime.registerService('relay', pool);\n * ```\n */\nexport function createRelayPoolService(options: RelayPoolServiceOptions): ServiceHandler {\n const tracked = new Map<string, TrackedSubscription>();\n\n return {\n descriptor: {\n name: 'relay-pool',\n version: '1.0.0',\n description: 'Relay pool subscription and publishing',\n },\n\n handleMessage(windowId: string, message: NappletMessage, send: (msg: NappletMessage) => void): void {\n const relayMessage = message as RelayServiceMessage;\n if (message.type === 'relay.subscribe') {\n const subId = relayMessage.subId;\n if (typeof subId !== 'string') return;\n const filters = Array.isArray(relayMessage.filters)\n ? relayMessage.filters as NostrFilter[]\n : [];\n const subKey = `${windowId}:${subId}`;\n\n // Cancel existing subscription for this key if any\n const existing = tracked.get(subKey);\n if (existing) {\n existing.handle.unsubscribe();\n clearTimeout(existing.eoseTimer);\n tracked.delete(subKey);\n }\n\n if (!options.isAvailable()) {\n send({ type: 'relay.eose', subId } as NappletMessage);\n return;\n }\n\n const relayHint = typeof relayMessage.relay === 'string' && relayMessage.relay.length > 0\n ? relayMessage.relay\n : undefined;\n const relayUrls = relayHint ? [relayHint] : options.selectRelayTier(filters);\n let eoseSent = false;\n\n const eoseTimer = setTimeout(() => {\n if (!eoseSent) {\n eoseSent = true;\n send({ type: 'relay.eose', subId } as NappletMessage);\n }\n }, EOSE_FALLBACK_MS);\n\n const handle = options.subscribe(filters, (item) => {\n if (item === 'EOSE') {\n clearTimeout(eoseTimer);\n if (!eoseSent) {\n eoseSent = true;\n send({ type: 'relay.eose', subId } as NappletMessage);\n }\n return;\n }\n send({ type: 'relay.event', subId, event: item } as NappletMessage);\n }, relayUrls);\n\n tracked.set(subKey, { handle, eoseTimer });\n return;\n }\n\n if (message.type === 'relay.close') {\n const subId = relayMessage.subId;\n if (typeof subId !== 'string') return;\n const subKey = `${windowId}:${subId}`;\n const entry = tracked.get(subKey);\n if (entry) {\n entry.handle.unsubscribe();\n clearTimeout(entry.eoseTimer);\n tracked.delete(subKey);\n }\n return;\n }\n\n if (message.type === 'relay.publish') {\n const event = relayMessage.event as NostrEvent | undefined;\n if (event && typeof event === 'object' && options.isAvailable()) {\n options.publish(event);\n }\n return;\n }\n\n if (message.type === 'relay.publishEncrypted') {\n const event = relayMessage.event as NostrEvent | undefined;\n if (event && typeof event === 'object' && options.isAvailable()) {\n options.publish(event);\n }\n return;\n }\n },\n\n onWindowDestroyed(windowId: string): void {\n const prefix = `${windowId}:`;\n for (const [key, entry] of tracked) {\n if (key.startsWith(prefix)) {\n entry.handle.unsubscribe();\n clearTimeout(entry.eoseTimer);\n tracked.delete(key);\n }\n }\n },\n };\n}\n","\nimport type { NostrEvent, NostrFilter, NappletMessage } from '@napplet/core';\nimport type { ServiceHandler } from '@kehto/runtime';\n\ntype RelayServiceMessage = NappletMessage & {\n subId?: unknown;\n filters?: unknown;\n event?: unknown;\n};\n\n/**\n * Options for creating a cache service.\n *\n * @example\n * ```ts\n * const cacheService = createCacheService({\n * query: (filters) => myIndexedDB.query(filters),\n * store: (event) => myIndexedDB.store(event),\n * isAvailable: () => true,\n * });\n * ```\n */\nexport interface CacheServiceOptions {\n /**\n * Query cached events matching the given filters.\n *\n * @param filters - NIP-01 filter objects\n * @returns Promise resolving to matching cached events\n */\n query(filters: NostrFilter[]): Promise<NostrEvent[]>;\n\n /**\n * Store an event in cache. Best-effort, may silently fail.\n *\n * @param event - The event to store\n */\n store(event: NostrEvent): void;\n\n /**\n * Whether the cache is available.\n *\n * @returns true if the cache can handle requests\n */\n isAvailable(): boolean;\n}\n\n/**\n * @kehto/services cross-package naming-parity alias for {@link CacheServiceOptions}.\n *\n * `HostCacheBridge` matches the v1.4 `HostKeysBridge` / `HostMediaBridge`\n * convention — it is a pure type alias for `CacheServiceOptions`, NOT a new\n * type. Existing consumers of `CacheServiceOptions` continue to work\n * unchanged; new consumers may prefer `HostCacheBridge` for consistency\n * with the other Host*Bridge names in `@kehto/services`.\n *\n * Anti-feature note (PITFALLS.md M-02): `CacheServiceOptions` MUST remain\n * the primary export. This alias is additive; do not rename or delete\n * `CacheServiceOptions` when other Host*Bridge names eventually\n * stabilize.\n *\n * @example\n * ```ts\n * import type { HostCacheBridge } from '@kehto/services';\n * const cache: HostCacheBridge = {\n * query: (filters) => myIndexedDB.query(filters),\n * store: (event) => myIndexedDB.store(event),\n * isAvailable: () => true,\n * };\n * ```\n */\nexport type HostCacheBridge = CacheServiceOptions;\n\n/**\n * Create a cache service that wraps an existing cache implementation\n * as a ServiceHandler.\n *\n * Cache relay.subscribe subscriptions are one-shot — they query, deliver\n * results, send EOSE, and are done. No long-lived subscription tracking needed.\n * Cache query failures are best-effort: EOSE is sent even on failure.\n *\n * @param options - Cache implementation to wrap\n * @returns A ServiceHandler ready for runtime.registerService('cache', handler)\n *\n * @example\n * ```ts\n * import { createCacheService } from '@kehto/services';\n *\n * const cache = createCacheService({\n * query: (f) => workerRelay.query(f),\n * store: (e) => workerRelay.store(e),\n * isAvailable: () => workerRelay.ready,\n * });\n * runtime.registerService('cache', cache);\n * ```\n */\nexport function createCacheService(options: CacheServiceOptions): ServiceHandler {\n return {\n descriptor: {\n name: 'cache',\n version: '1.0.0',\n description: 'Local event cache (IndexedDB, worker relay, etc.)',\n },\n\n handleMessage(_windowId: string, message: NappletMessage, send: (msg: NappletMessage) => void): void {\n const relayMessage = message as RelayServiceMessage;\n if (message.type === 'relay.subscribe') {\n const subId = relayMessage.subId;\n if (typeof subId !== 'string') return;\n const filters = Array.isArray(relayMessage.filters)\n ? relayMessage.filters as NostrFilter[]\n : [];\n\n if (!options.isAvailable()) {\n send({ type: 'relay.eose', subId } as NappletMessage);\n return;\n }\n\n options\n .query(filters)\n .then((events) => {\n for (const event of events) {\n send({ type: 'relay.event', subId, event } as NappletMessage);\n }\n send({ type: 'relay.eose', subId } as NappletMessage);\n })\n .catch(() => {\n // Cache query is best-effort — send EOSE even on failure\n send({ type: 'relay.eose', subId } as NappletMessage);\n });\n return;\n }\n\n if (message.type === 'relay.publish') {\n const event = relayMessage.event as NostrEvent | undefined;\n if (event && typeof event === 'object' && options.isAvailable()) {\n try {\n options.store(event);\n } catch {\n /* Cache write is best-effort */\n }\n }\n return;\n }\n },\n\n // Cache has no per-window state to clean up\n onWindowDestroyed(_windowId: string): void {\n /* no-op */\n },\n };\n}\n","\nimport type { NostrEvent, NostrFilter, NappletMessage } from '@napplet/core';\nimport type { ServiceHandler } from '@kehto/runtime';\nimport type { RelayPoolServiceOptions } from './relay-pool-service.js';\nimport type { CacheServiceOptions } from './cache-service.js';\n\n// Timer globals available in all JS runtimes\ndeclare function setTimeout(callback: () => void, ms: number): unknown;\ndeclare function clearTimeout(id: unknown): void;\n\n/** Default EOSE fallback timeout. */\nconst DEFAULT_EOSE_TIMEOUT_MS = 15_000;\n\n/**\n * Options for creating a coordinated relay service.\n *\n * @example\n * ```ts\n * const relay = createCoordinatedRelay({\n * relayPool: {\n * subscribe: (f, cb, urls) => pool.subscribe(f, cb, urls),\n * publish: (e) => pool.publish(e),\n * selectRelayTier: (f) => pool.selectRelays(f),\n * isAvailable: () => pool.connected,\n * },\n * cache: {\n * query: (f) => db.query(f),\n * store: (e) => db.store(e),\n * isAvailable: () => db.ready,\n * },\n * });\n * runtime.registerService('relay', relay);\n * ```\n */\nexport interface CoordinatedRelayOptions {\n /**\n * Relay pool implementation.\n * Uses the same interface as RelayPoolServiceOptions.\n */\n relayPool: RelayPoolServiceOptions;\n\n /**\n * Local cache implementation.\n * Uses the same interface as CacheServiceOptions.\n */\n cache: CacheServiceOptions;\n\n /**\n * EOSE fallback timeout in milliseconds.\n * Sent if relay pool doesn't respond within this time.\n * Default: 15000 (15 seconds).\n */\n eoseTimeoutMs?: number;\n}\n\n/** Internal state for a tracked subscription. */\ninterface TrackedSub {\n seenIds: Set<string>;\n cacheEose: boolean;\n relayEose: boolean;\n eoseSent: boolean;\n eoseTimer: unknown;\n relayHandle: { unsubscribe(): void } | null;\n}\n\ntype RelayServiceMessage = NappletMessage & {\n subId?: unknown;\n filters?: unknown;\n event?: unknown;\n relay?: unknown;\n};\n\n/**\n * Create a coordinated relay service that combines relay pool and cache\n * into a single ServiceHandler with dedup and unified EOSE.\n *\n * On relay.subscribe: queries cache first, then subscribes to relay pool.\n * Events are deduplicated by ID. EOSE is sent after both sources complete.\n * On relay.publish: publishes to relay pool and stores in cache.\n * On relay.close: cancels relay pool subscription.\n *\n * @param options - Relay pool and cache implementations to coordinate\n * @returns A ServiceHandler ready for runtime.registerService('relay', handler)\n *\n * @example\n * ```ts\n * import { createCoordinatedRelay } from '@kehto/services';\n *\n * const relay = createCoordinatedRelay({ relayPool: myPool, cache: myCache });\n * runtime.registerService('relay', relay);\n * ```\n */\nexport function createCoordinatedRelay(options: CoordinatedRelayOptions): ServiceHandler {\n const timeoutMs = options.eoseTimeoutMs ?? DEFAULT_EOSE_TIMEOUT_MS;\n const subs = new Map<string, TrackedSub>();\n\n function maybeSendEose(subKey: string, subId: string, send: (msg: NappletMessage) => void): void {\n const sub = subs.get(subKey);\n if (!sub || sub.eoseSent) return;\n if (sub.cacheEose && sub.relayEose) {\n sub.eoseSent = true;\n clearTimeout(sub.eoseTimer);\n send({ type: 'relay.eose', subId } as NappletMessage);\n }\n }\n\n return {\n descriptor: {\n name: 'relay',\n version: '1.0.0',\n description: 'Coordinated relay pool + cache with dedup and unified EOSE',\n },\n\n handleMessage(windowId: string, message: NappletMessage, send: (msg: NappletMessage) => void): void {\n const relayMessage = message as RelayServiceMessage;\n if (message.type === 'relay.subscribe') {\n const subId = relayMessage.subId;\n if (typeof subId !== 'string') return;\n const filters = Array.isArray(relayMessage.filters)\n ? relayMessage.filters as NostrFilter[]\n : [];\n const subKey = `${windowId}:${subId}`;\n\n // Cancel existing subscription for this key\n const existing = subs.get(subKey);\n if (existing) {\n existing.relayHandle?.unsubscribe();\n clearTimeout(existing.eoseTimer);\n subs.delete(subKey);\n }\n\n const cacheAvailable = options.cache.isAvailable();\n const relayAvailable = options.relayPool.isAvailable();\n\n // Neither source available — send EOSE immediately\n if (!cacheAvailable && !relayAvailable) {\n send({ type: 'relay.eose', subId } as NappletMessage);\n return;\n }\n\n const tracked: TrackedSub = {\n seenIds: new Set(),\n cacheEose: !cacheAvailable, // mark done if cache not available\n relayEose: !relayAvailable, // mark done if relay not available\n eoseSent: false,\n eoseTimer: null as unknown,\n relayHandle: null,\n };\n subs.set(subKey, tracked);\n\n function deliver(event: NostrEvent): void {\n if (tracked.seenIds.has(event.id)) return;\n tracked.seenIds.add(event.id);\n if (subs.has(subKey)) send({ type: 'relay.event', subId, event } as NappletMessage);\n }\n\n // Query cache (async)\n if (cacheAvailable) {\n options.cache\n .query(filters)\n .then((events) => {\n for (const event of events) deliver(event);\n tracked.cacheEose = true;\n maybeSendEose(subKey, subId, send);\n })\n .catch(() => {\n // Cache query is best-effort\n tracked.cacheEose = true;\n maybeSendEose(subKey, subId, send);\n });\n }\n\n // Subscribe to relay pool\n if (relayAvailable) {\n tracked.eoseTimer = setTimeout(() => {\n if (!tracked.eoseSent) {\n tracked.relayEose = true;\n maybeSendEose(subKey, subId, send);\n }\n }, timeoutMs);\n\n const relayHint = typeof relayMessage.relay === 'string' && relayMessage.relay.length > 0\n ? relayMessage.relay\n : undefined;\n const relayUrls = relayHint ? [relayHint] : options.relayPool.selectRelayTier(filters);\n tracked.relayHandle = options.relayPool.subscribe(filters, (item) => {\n if (item === 'EOSE') {\n clearTimeout(tracked.eoseTimer);\n tracked.relayEose = true;\n maybeSendEose(subKey, subId, send);\n return;\n }\n deliver(item);\n // Store relay events in cache\n if (cacheAvailable) {\n try { options.cache.store(item); } catch { /* best-effort */ }\n }\n }, relayUrls);\n }\n return;\n }\n\n if (message.type === 'relay.close') {\n const subId = relayMessage.subId;\n if (typeof subId !== 'string') return;\n const subKey = `${windowId}:${subId}`;\n const entry = subs.get(subKey);\n if (entry) {\n entry.relayHandle?.unsubscribe();\n clearTimeout(entry.eoseTimer);\n subs.delete(subKey);\n }\n return;\n }\n\n if (message.type === 'relay.publish') {\n const event = relayMessage.event as NostrEvent | undefined;\n if (!event || typeof event !== 'object') return;\n // Publish to relay pool\n if (options.relayPool.isAvailable()) {\n options.relayPool.publish(event);\n }\n if (options.cache.isAvailable()) {\n try { options.cache.store(event); } catch { /* best-effort */ }\n }\n return;\n }\n },\n\n onWindowDestroyed(windowId: string): void {\n const prefix = `${windowId}:`;\n for (const [key, entry] of subs) {\n if (key.startsWith(prefix)) {\n entry.relayHandle?.unsubscribe();\n clearTimeout(entry.eoseTimer);\n subs.delete(key);\n }\n }\n },\n };\n}\n","/**\n * keys-service.ts — NIP-5D keys NAP reference document-level listener implementation.\n *\n * Handles the 3 napplet -> shell request types from @napplet/nap/keys:\n * - keys.forward -> invokes options.onForward (hotkey passthrough, fire-and-forget)\n * - keys.registerAction -> parses action.defaultKey into a chord spec, stores the\n * subscription in an in-memory registry keyed by actionId,\n * tracks windowId ownership so onWindowDestroyed can auto-\n * unsubscribe, and echoes { actionId, binding } as .result\n * - keys.unregisterAction -> removes the subscription; fire-and-forget (no envelope)\n *\n * Real listener: on service construction the handler attaches a single\n * `keydown` listener to `options.listenerTarget` (default: `document`). Each\n * keydown is matched against the chord-subscription registry; matches invoke\n * `options.onForward` with the DOM-shape payload AND push a canonical\n * `keys.action` envelope back to the owning napplet via the per-window `send`\n * callback captured at `keys.registerAction` time. Subscriptions persist\n * across messages; `onWindowDestroyed(windowId)` drops all subscriptions owned\n * by the destroyed window as well as its cached `send` handle.\n *\n * On each document keydown matching a registered action, the service\n * additionally emits a `keys.action` envelope to the action's owning napplet\n * via the per-window `send` callback — this is the canonical @napplet/nap/keys\n * surface the SDK's `keys.onAction(...)` helper consumes. The shape is a\n * superset of `KeysActionMessage`: `{ type, actionId, chord }` where `chord`\n * is the parsed `{ ctrl, alt, shift, meta, key }` struct (extension field;\n * base shape unchanged, downstream SDKs that only read `{ type, actionId }`\n * ignore `chord` silently).\n *\n * Field-name translation: @napplet/nap/keys uses the compact\n * { ctrl, alt, shift, meta } form on the wire; the shell's HotkeyHooks\n * (packages/shell/src/types.ts) expects the DOM-compatible\n * { ctrlKey, altKey, shiftKey, metaKey } form. This service performs the\n * translation so callers of `onForward` see the DOM shape.\n *\n * Shell -> napplet push envelopes `keys.bindings` remain the shell-side keys\n * forwarder's responsibility (DRIFT-SHELL-06, tracked under Plan 12-11 /\n * future phase); `keys.action` is emitted here per Plan 26-01.\n */\n\nimport type { NappletMessage } from '@napplet/core';\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\nimport type {\n KeysForwardMessage,\n KeysRegisterActionMessage,\n KeysRegisterActionResultMessage,\n KeysActionMessage,\n} from '@napplet/nap/keys/types';\n\n/**\n * Minimal structural subset of the DOM `KeyboardEvent` exposed to\n * `HostKeysBridge` subscribe callbacks. DOM `KeyboardEvent` satisfies this\n * structurally with no adapter needed. OS-bridge impls (Electron, Tauri —\n * out of v1.4 scope) synthesize this from native key events.\n */\nexport interface HostKeyEvent {\n key: string;\n code: string;\n ctrlKey: boolean;\n altKey: boolean;\n shiftKey: boolean;\n metaKey: boolean;\n /** True for OS autorepeat; the service filters these by default. */\n repeat?: boolean;\n}\n\n/**\n * Host-bridge contract for pluggable keyboard backends.\n *\n * The browser reference implementation (the default {@link createKeysService}\n * behaviour when `hostBridge` is omitted) registers a `document`-level keydown\n * listener and satisfies this interface structurally — it exposes\n * `subscribe(chord, callback) => unsubscribe` semantics but omits the two\n * OS-level optional fields (browsers cannot register global hotkeys without\n * privileged APIs).\n *\n * Host apps (Electron, Tauri) implement this interface in their own code and\n * pass it via `createKeysService({ hostBridge: myBridge })` — the service\n * then delegates subscription lifecycle to the bridge and remains browser-free.\n *\n * Reference implementations for Electron / Tauri are explicitly out of v1.4\n * scope and live in host-app examples / follow-up milestones (see\n * REQUIREMENTS.md \"Future Requirements\").\n *\n * @example\n * ```ts\n * // Host-app pseudocode (Electron main-process relay):\n * const electronBridge: HostKeysBridge = {\n * subscribe(chord, cb) {\n * const handle = globalShortcut.register(chord, () => cb({ key: '', code: '', ctrlKey: false, altKey: false, shiftKey: false, metaKey: false }));\n * return () => globalShortcut.unregister(chord);\n * },\n * registerGlobalHotkey: (chord) => globalShortcut.register(chord, () => {}),\n * onGlobalHotkey: (cb) => globalHotkeyBridge.on('global-hotkey', (_, chord) => cb(chord)),\n * };\n *\n * const keys = createKeysService({ hostBridge: electronBridge });\n * runtime.registerService('keys', keys);\n * ```\n */\nexport interface HostKeysBridge {\n /**\n * Subscribe a callback to a chord. Returns an unsubscribe handle.\n *\n * Implementations MUST:\n * - invoke `callback` exactly once per matching chord event (implementations\n * are responsible for any OS-autorepeat filtering)\n * - invoke `callback` synchronously during the event delivery\n * - accept the string chord format documented by @napplet/nap/keys\n * (e.g. `'Ctrl+Shift+K'`, `'Cmd+P'`)\n */\n subscribe(chord: string, callback: (event: KeyboardEvent | HostKeyEvent) => void): () => void;\n\n /**\n * Optional: register an OS-level global hotkey (works even when the host\n * window is not focused). Returns true on success, false if the chord\n * cannot be registered (e.g. already claimed by another app).\n *\n * Omitted by the browser reference implementation — browsers cannot\n * register OS-level global hotkeys without privileged APIs. Electron\n * (`globalShortcut`) and Tauri (`GlobalShortcut`) provide this.\n */\n registerGlobalHotkey?(chord: string): boolean;\n\n /**\n * Optional: subscribe to OS-level global hotkey events (regardless of\n * focus). Returns an unsubscribe handle.\n *\n * Omitted by the browser reference implementation. See\n * {@link HostKeysBridge.registerGlobalHotkey}.\n */\n onGlobalHotkey?(callback: (chord: string) => void): () => void;\n}\n\n/** Keys service version — follows semver. */\nconst KEYS_SERVICE_VERSION = '1.2.0';\n\n/**\n * Parsed chord struct — internal, never on the wire. The napplet-facing API\n * (and the `action.defaultKey` field) is a string like `\"Ctrl+Shift+K\"`;\n * `parseChord` lowers those strings into this struct for efficient matching\n * against `KeyboardEvent` modifier flags.\n */\ninterface ChordSpec {\n ctrl: boolean;\n alt: boolean;\n shift: boolean;\n meta: boolean;\n /** Normalized uppercase single character or DOM key name (e.g. 'K', 'Enter'). */\n key: string;\n}\n\n/** Registry entry — maps a registered actionId back to its owning window + chord. */\ninterface ActionEntry {\n chord: ChordSpec;\n /** Original chord string, preserved for the .result `binding` field. */\n chordString: string;\n windowId: string;\n}\n\n/**\n * Options for creating a keys service via createKeysService().\n *\n * @example\n * ```ts\n * const keys = createKeysService({\n * onForward: (event) => {\n * // event has DOM-compatible field names: ctrlKey, altKey, etc.\n * hotkeyDispatcher.dispatch(event);\n * },\n * });\n * ```\n */\nexport interface KeysServiceOptions {\n /**\n * Called on `keys.forward` (napplet-forwarded chord) AND on document keydown\n * matching a registered action. Receives the DOM-style field names\n * (ctrlKey/altKey/shiftKey/metaKey) to match the shell's HotkeyHooks\n * contract. The service translates from the wire shape\n * ({ ctrl, alt, shift, meta }) before invoking this callback.\n */\n onForward?: (event: {\n key: string;\n code: string;\n ctrlKey: boolean;\n altKey: boolean;\n shiftKey: boolean;\n metaKey: boolean;\n }) => void;\n /**\n * EventTarget to attach the default keydown listener to. Defaults to the\n * global `document` when running in a DOM environment, else an isolated\n * `new EventTarget()` (SSR / Node-test safe). Passing a fresh\n * `new EventTarget()` is useful for unit tests. Mirrors the pattern used\n * by `@kehto/shell`'s `createKeysForwarder`.\n *\n * Ignored when `hostBridge` is provided — the bridge owns subscription\n * lifecycle and no document listener is attached.\n */\n listenerTarget?: EventTarget;\n /**\n * Optional pluggable backend for chord subscription. When provided, the\n * service delegates `keys.registerAction` → `bridge.subscribe(chord, cb)`\n * and stores the returned unsubscribe handle keyed on `actionId`. The\n * default document-listener path is NOT attached when `hostBridge` is\n * provided — the bridge is authoritative. See {@link HostKeysBridge}.\n */\n hostBridge?: HostKeysBridge;\n /**\n * Optional set of shell-reserved chords. Strings in the `@napplet/nap/keys`\n * wire format (e.g. `'Ctrl+Shift+K'`, `'Cmd+P'`). When a napplet sends\n * `keys.forward` with a chord matching this set — or when a document\n * keydown matches a reserved chord — the service invokes `onForward`\n * (or the `hostBridge`-registered handler) but suppresses the\n * `keys.action` push to any napplet that registered the same chord via\n * `keys.registerAction`. Precedence: reserved > registered. The shell\n * WANTS the forward — that is why it reserved the chord.\n *\n * Normalized once at service construction via the same chord parser used\n * for `action.defaultKey` — `'Ctrl+K'` / `'Control+k'` / `'ctrl+K'` all\n * match. Static; no runtime mutation. For dynamic reservation see the\n * deferred `HostKeysBridge.reserveAbsolute(chords)` extension.\n *\n * @example\n * ```ts\n * const keys = createKeysService({\n * reservedChords: ['Ctrl+Alt+T', 'Super+Space'],\n * onForward: (event) => wm.dispatch(event),\n * });\n * ```\n */\n reservedChords?: ReadonlyArray<string>;\n}\n\n/** Modifier-token aliases accepted by `parseChord` (case-insensitive). */\nconst MODIFIER_ALIASES: Record<string, keyof Pick<ChordSpec, 'ctrl' | 'alt' | 'shift' | 'meta'>> = {\n ctrl: 'ctrl',\n control: 'ctrl',\n alt: 'alt',\n option: 'alt',\n shift: 'shift',\n meta: 'meta',\n cmd: 'meta',\n command: 'meta',\n win: 'meta',\n super: 'meta',\n};\n\n/**\n * Parse a chord string into a `ChordSpec`. Modifier tokens are case-insensitive\n * and recognize common aliases (Cmd/Command/Win/Super → meta, Control → ctrl,\n * Option → alt). Single-character keys are normalized to uppercase so chord\n * matching is case-insensitive; multi-character DOM key names (`Enter`,\n * `ArrowUp`, `F4`) preserve their original casing.\n *\n * Examples:\n * parseChord('Ctrl+Shift+K') → { ctrl: true, alt: false, shift: true, meta: false, key: 'K' }\n * parseChord('ctrl+s') → { ctrl: true, alt: false, shift: false, meta: false, key: 'S' }\n * parseChord('Cmd+P') → { ctrl: false, alt: false, shift: false, meta: true, key: 'P' }\n * parseChord('K') → { ctrl: false, alt: false, shift: false, meta: false, key: 'K' }\n * parseChord('Ctrl++') → { ctrl: true, alt: false, shift: false, meta: false, key: '+' }\n *\n * @throws Error('empty chord') when the input is the empty string.\n * @throws Error(`unknown modifier: ${tok}`) when a non-final token isn't a recognized modifier.\n * @throws Error(`empty key in chord: ${chord}`) when the final token is empty/whitespace.\n */\nfunction parseChord(chord: string): ChordSpec {\n if (chord.length === 0) throw new Error('empty chord');\n const parts = chord.split('+');\n const out: ChordSpec = { ctrl: false, alt: false, shift: false, meta: false, key: '' };\n // The final token is always the key — even if it is literally '+' (chord like 'Ctrl++').\n // All preceding tokens must be modifiers.\n for (let i = 0; i < parts.length - 1; i++) {\n const tok = parts[i].trim().toLowerCase();\n if (tok.length === 0) continue; // tolerate stray whitespace\n const slot = MODIFIER_ALIASES[tok];\n if (!slot) throw new Error(`unknown modifier: ${parts[i]}`);\n out[slot] = true;\n }\n const keyTok = parts[parts.length - 1].trim();\n if (keyTok.length === 0) throw new Error(`empty key in chord: ${chord}`);\n // Single characters normalize to uppercase for case-insensitive comparison;\n // multi-character DOM key names (Enter, ArrowUp, F4) preserve their original casing.\n out.key = keyTok.length === 1 ? keyTok.toUpperCase() : keyTok;\n return out;\n}\n\n/**\n * Create a keys service handler.\n *\n * Attaches a single `keydown` listener to `options.listenerTarget`\n * (default `document`). Matching chord subscriptions invoke `options.onForward`\n * with a DOM-shape payload AND push a `keys.action` envelope back to the\n * owning napplet via the per-window `send` callback captured at\n * `keys.registerAction` time. Returns a `ServiceHandler` augmented with a\n * `destroy()` method that detaches the listener and clears all registries.\n *\n * @param options - Optional configuration (onForward callback, listenerTarget)\n * @returns A ServiceHandler (with `destroy()`) to register with the runtime\n *\n * @example\n * ```ts\n * import { createKeysService } from '@kehto/services';\n *\n * const keys = createKeysService({\n * onForward: (event) => shellHotkeyDispatcher.execute(event),\n * });\n *\n * runtime.registerService('keys', keys);\n * // Later, on shell teardown:\n * keys.destroy();\n * ```\n */\nexport function createKeysService(\n options: KeysServiceOptions = {},\n): ServiceHandler & { destroy(): void } {\n const descriptor: ServiceDescriptor = {\n name: 'keys',\n version: KEYS_SERVICE_VERSION,\n description: options.hostBridge\n ? 'NIP-5D keys NAP reference handler (host-bridge delegated)'\n : 'NIP-5D keys NAP reference handler (document-level chord listener)',\n };\n\n function chordSpecKey(spec: {\n ctrl: boolean;\n alt: boolean;\n shift: boolean;\n meta: boolean;\n key: string;\n }): string {\n return `${spec.ctrl}|${spec.alt}|${spec.shift}|${spec.meta}|${spec.key}`;\n }\n const reservedChordKeys: Set<string> = new Set();\n if (options.reservedChords) {\n for (const chordStr of options.reservedChords) {\n // parseChord throws on malformed input — let it bubble up at construction\n // so misconfigured shells fail loudly at boot, not silently at runtime.\n reservedChordKeys.add(chordSpecKey(parseChord(chordStr)));\n }\n }\n // Canonicalize a wire-shape keys.forward payload into the same key.\n function forwardKey(m: {\n key: string;\n ctrl: boolean;\n alt: boolean;\n shift: boolean;\n meta: boolean;\n }): string {\n const k = m.key.length === 1 ? m.key.toUpperCase() : m.key;\n return `${m.ctrl}|${m.alt}|${m.shift}|${m.meta}|${k}`;\n }\n function forwardPayload(m: KeysForwardMessage): Omit<HostKeyEvent, 'repeat'> {\n return {\n key: m.key,\n code: m.code,\n ctrlKey: m.ctrl,\n altKey: m.alt,\n shiftKey: m.shift,\n metaKey: m.meta,\n };\n }\n // Canonicalize a DOM KeyboardEvent into the same key (for Branch B keydown listener).\n function eventKey(ev: KeyboardEvent): string {\n const k = ev.key.length === 1 ? ev.key.toUpperCase() : ev.key;\n return `${ev.ctrlKey}|${ev.altKey}|${ev.shiftKey}|${ev.metaKey}|${k}`;\n }\n\n if (options.hostBridge) {\n const bridge = options.hostBridge;\n // windowId → Set<actionId> — parallels Branch B for scoped cleanup.\n const bridgeWindowActions = new Map<string, Set<string>>();\n // actionId → unsubscribe handle returned from bridge.subscribe.\n const unsubscribeHandles = new Map<string, () => void>();\n\n return {\n descriptor,\n\n handleMessage(\n windowId: string,\n message: NappletMessage,\n send: (msg: NappletMessage) => void,\n ): void {\n switch (message.type) {\n case 'keys.forward': {\n // Legacy napplet-forwarded path still works identically — preserves wire contract.\n // Phase 33 / KEYS-04-05: reserved chords take precedence. The Branch-A\n // handler never dispatches keys.action to napplets on forward (bridge\n // owns chord → napplet routing via its own subscribe callback), so\n // reservation here is observationally identical to the base case —\n // but we compute and check the reservation explicitly to document\n // the contract for future edits and keep both branches uniform.\n const m = message as KeysForwardMessage;\n const reserved = reservedChordKeys.has(forwardKey(m));\n options.onForward?.(forwardPayload(m));\n if (reserved) {\n return;\n }\n return;\n }\n\n case 'keys.registerAction': {\n const m = message as KeysRegisterActionMessage;\n if (m.action.defaultKey) {\n try {\n const unsubscribe = bridge.subscribe(m.action.defaultKey, (ev) => {\n // Normalize either KeyboardEvent or HostKeyEvent to the DOM-shape onForward payload.\n const e = ev as KeyboardEvent | HostKeyEvent;\n // Bridges may not filter autorepeat — we do, matching Branch B semantics.\n if ('repeat' in e && e.repeat) return;\n options.onForward?.({\n key: e.key,\n code: e.code,\n ctrlKey: e.ctrlKey,\n altKey: e.altKey,\n shiftKey: e.shiftKey,\n metaKey: e.metaKey,\n });\n // Canonical shell→napplet push: emit keys.action to the owning\n // napplet. Shape matches Branch B (superset of KeysActionMessage);\n // the `chord` extension is omitted here because bridges deliver\n // pre-parsed chord events without the internal ChordSpec struct.\n const payload: KeysActionMessage = {\n type: 'keys.action',\n actionId: m.action.id,\n };\n send(payload as NappletMessage);\n });\n unsubscribeHandles.set(m.action.id, unsubscribe);\n if (!bridgeWindowActions.has(windowId)) bridgeWindowActions.set(windowId, new Set());\n bridgeWindowActions.get(windowId)!.add(m.action.id);\n } catch (err) {\n const id = m.id ?? '';\n send({\n type: 'keys.registerAction.error',\n id,\n error: `bridge subscribe failed: ${(err as Error).message}`,\n } as NappletMessage);\n return;\n }\n }\n const result: KeysRegisterActionResultMessage = {\n type: 'keys.registerAction.result',\n id: m.id,\n actionId: m.action.id,\n ...(m.action.defaultKey ? { binding: m.action.defaultKey } : {}),\n };\n send(result as NappletMessage);\n return;\n }\n\n case 'keys.unregisterAction': {\n const m = message as NappletMessage & { actionId?: string };\n if (m.actionId) {\n const unsubscribe = unsubscribeHandles.get(m.actionId);\n if (unsubscribe) {\n try {\n unsubscribe();\n } catch {\n /* best-effort */\n }\n unsubscribeHandles.delete(m.actionId);\n // Prune the bridgeWindowActions entry that owns this actionId.\n for (const [wid, set] of bridgeWindowActions.entries()) {\n if (set.delete(m.actionId) && set.size === 0) bridgeWindowActions.delete(wid);\n }\n }\n }\n return;\n }\n\n default: {\n const id = (message as NappletMessage & { id?: string }).id ?? '';\n send({\n type: `${message.type}.error`,\n id,\n error: `Unknown keys method: ${message.type}`,\n } as NappletMessage);\n return;\n }\n }\n },\n\n onWindowDestroyed(windowId: string): void {\n const actions = bridgeWindowActions.get(windowId);\n if (!actions) return;\n for (const actionId of actions) {\n const unsubscribe = unsubscribeHandles.get(actionId);\n if (unsubscribe) {\n try {\n unsubscribe();\n } catch {\n /* best-effort */\n }\n }\n unsubscribeHandles.delete(actionId);\n }\n bridgeWindowActions.delete(windowId);\n },\n\n destroy(): void {\n for (const unsubscribe of unsubscribeHandles.values()) {\n try {\n unsubscribe();\n } catch {\n /* best-effort */\n }\n }\n unsubscribeHandles.clear();\n bridgeWindowActions.clear();\n },\n };\n }\n\n const actionRegistry = new Map<string, ActionEntry>(); // actionId → {chord, chordString, windowId}\n const windowActions = new Map<string, Set<string>>(); // windowId → Set<actionId>\n // Per-window `send` callback captured at registerAction time. Used to push\n // keys.action envelopes back to the owning napplet on chord match — this is\n // the canonical @napplet/nap/keys surface the SDK's `keys.onAction(...)`\n // helper consumes.\n const sendHandles = new Map<string, (msg: NappletMessage) => void>(); // windowId → send\n\n // ─── Listener target (SSR / test-safe fallback, mirrors keys-forwarder.ts) ─\n const target: EventTarget =\n options.listenerTarget ??\n (typeof document !== 'undefined' ? document : new EventTarget());\n\n function chordMatches(spec: ChordSpec, ev: KeyboardEvent): boolean {\n if (spec.ctrl !== ev.ctrlKey) return false;\n if (spec.alt !== ev.altKey) return false;\n if (spec.shift !== ev.shiftKey) return false;\n if (spec.meta !== ev.metaKey) return false;\n const evKey = ev.key.length === 1 ? ev.key.toUpperCase() : ev.key;\n return spec.key === evKey;\n }\n\n const listener = (rawEv: Event): void => {\n const ev = rawEv as KeyboardEvent;\n if (ev.repeat) return; // ignore OS autorepeat — matches \"I pressed it once\" intent (CONTEXT Area 1)\n\n // Phase 33 / KEYS-04-05: reserved chords fire onForward but suppress\n // keys.action fan-out to napplets. Check ONCE up front.\n const isReserved = reservedChordKeys.has(eventKey(ev));\n\n // Determine if any registered action would match — needed to decide\n // whether to fire onForward on a non-reserved keydown (legacy parity).\n // A reserved chord fires onForward regardless of napplet registration\n // (WM-launcher case: shell declares chord, no napplet registers it).\n let anyMatch = false;\n for (const entry of actionRegistry.values()) {\n if (chordMatches(entry.chord, ev)) {\n anyMatch = true;\n break;\n }\n }\n\n if (isReserved || anyMatch) {\n options.onForward?.({\n key: ev.key,\n code: ev.code,\n ctrlKey: ev.ctrlKey,\n altKey: ev.altKey,\n shiftKey: ev.shiftKey,\n metaKey: ev.metaKey,\n });\n }\n\n if (isReserved) return; // reserved → no napplet fan-out\n\n // (2) Canonical shell→napplet push: emit keys.action to the owning\n // napplet via its captured send callback. The SDK's keys.onAction\n // helper subscribes to this envelope. We attach a `chord` extension\n // field so the demo napplet can display the fired chord without\n // reconstructing it from the original registration.\n for (const [actionId, entry] of actionRegistry.entries()) {\n if (chordMatches(entry.chord, ev)) {\n const send = sendHandles.get(entry.windowId);\n if (send) {\n const payload: KeysActionMessage & { chord: ChordSpec } = {\n type: 'keys.action',\n actionId,\n chord: entry.chord,\n };\n send(payload as NappletMessage);\n }\n // Intentionally no `break` — two actions subscribing to the same chord\n // both receive the event. Conflict resolution is an explicit v1.5+\n // concern per CONTEXT.md Deferred Ideas.\n }\n }\n };\n\n target.addEventListener('keydown', listener);\n\n return {\n descriptor,\n\n handleMessage(\n windowId: string,\n message: NappletMessage,\n send: (msg: NappletMessage) => void,\n ): void {\n switch (message.type) {\n case 'keys.forward': {\n // Legacy passthrough: napplet-forwarded keydown translation.\n // Preserved bit-for-bit from the stub — existing tests + the\n // keys-forwarder.ts -> service.handleMessage path depend on this shape.\n //\n // Phase 33 / KEYS-04-05: Branch B's keys.forward handler does NOT\n // emit keys.action (fan-out happens in the document keydown listener),\n // so reservation is observationally identical to the base case.\n // The explicit guard below pins the contract for future edits.\n const m = message as KeysForwardMessage;\n const reserved = reservedChordKeys.has(forwardKey(m));\n options.onForward?.(forwardPayload(m));\n if (reserved) return;\n return;\n }\n\n case 'keys.registerAction': {\n const m = message as KeysRegisterActionMessage;\n // Capture (or refresh) the per-window send callback. The runtime's\n // service-handler contract guarantees `send` remains valid for this\n // windowId until onWindowDestroyed(windowId) fires — we cache the\n // most recent invocation so the keydown listener can push\n // keys.action envelopes back to the owning napplet.\n sendHandles.set(windowId, send);\n\n if (m.action.defaultKey) {\n try {\n const chord = parseChord(m.action.defaultKey);\n actionRegistry.set(m.action.id, {\n chord,\n chordString: m.action.defaultKey,\n windowId,\n });\n if (!windowActions.has(windowId)) windowActions.set(windowId, new Set());\n windowActions.get(windowId)!.add(m.action.id);\n } catch (err) {\n // Parse failure: respond with .error envelope (unknown-method pattern).\n const id = m.id ?? '';\n send({\n type: 'keys.registerAction.error',\n id,\n error: `invalid chord: ${(err as Error).message}`,\n } as NappletMessage);\n return;\n }\n }\n const result: KeysRegisterActionResultMessage = {\n type: 'keys.registerAction.result',\n id: m.id,\n actionId: m.action.id,\n ...(m.action.defaultKey ? { binding: m.action.defaultKey } : {}),\n };\n send(result as NappletMessage);\n return;\n }\n\n case 'keys.unregisterAction': {\n // Fire-and-forget per @napplet/nap/keys spec. Remove subscription if present.\n const m = message as NappletMessage & { actionId?: string };\n if (m.actionId && actionRegistry.has(m.actionId)) {\n const entry = actionRegistry.get(m.actionId)!;\n actionRegistry.delete(m.actionId);\n const set = windowActions.get(entry.windowId);\n if (set) {\n set.delete(m.actionId);\n // If the window has no remaining actions, drop its cached send\n // that no longer subscribes to anything.\n if (set.size === 0) {\n windowActions.delete(entry.windowId);\n sendHandles.delete(entry.windowId);\n }\n }\n }\n return;\n }\n\n default: {\n const id = (message as NappletMessage & { id?: string }).id ?? '';\n send({\n type: `${message.type}.error`,\n id,\n error: `Unknown keys method: ${message.type}`,\n } as NappletMessage);\n return;\n }\n }\n },\n\n onWindowDestroyed(windowId: string): void {\n const actions = windowActions.get(windowId);\n if (actions) {\n for (const actionId of actions) actionRegistry.delete(actionId);\n windowActions.delete(windowId);\n }\n sendHandles.delete(windowId);\n },\n\n destroy(): void {\n target.removeEventListener('keydown', listener);\n actionRegistry.clear();\n windowActions.clear();\n sendHandles.clear();\n },\n };\n}\n","import type { MediaAction, MediaMetadata } from '@napplet/nap/media/types';\nimport type { HostMediaBridge } from './media-service.js';\n\n/** Silent-audio prime data URL (4 kHz silent WAV, 44 bytes, zero network dependency).\n * Browsers refuse to render OS media controls without a playing audio element —\n * this silent loop primes the MediaSession API. Per CONTEXT.md Area 1. */\nconst SILENT_AUDIO_DATA_URL =\n 'data:audio/wav;base64,UklGRiQAAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQAAAAA=';\n\n/** Default action set — all 5 nap-media transport actions. */\nexport const DEFAULT_MEDIA_ACTIONS: readonly MediaAction[] = ['play', 'pause', 'next', 'prev', 'seek'];\n\n/** Minimal subset of navigator.mediaSession the browser bridge depends on. Makes the bridge\n * Node/test-safe: unit tests pass a MockMediaSession via mediaSessionTarget.\n * The handler parameter uses `details?` (optional) so both the real DOM impl\n * (which always passes an object) and test mocks that omit details both satisfy\n * this type structurally. */\nexport type MediaSessionTarget = {\n metadata: MediaMetadataLike | null;\n playbackState: 'none' | 'playing' | 'paused';\n setActionHandler(action: string, handler: ((details?: { action?: string; seekTime?: number }) => void) | null): void;\n};\n\n/** Structural subset of the DOM MediaMetadata class — assignable from a plain object\n * with title/artist/album/artwork fields. The browser impl uses `new MediaMetadata({...})`;\n * tests can pass a plain object. */\nexport type MediaMetadataLike = { title?: string; artist?: string; album?: string; artwork?: unknown };\n\n/** Mapping from DOM MediaSession action names to nap-media MediaAction literals. */\nconst ACTION_MATRIX: ReadonlyArray<[string, MediaAction]> = [\n ['play', 'play'],\n ['pause', 'pause'],\n ['nexttrack', 'next'],\n ['previoustrack', 'prev'],\n ['seekto', 'seek'],\n];\n\n/**\n * Reference browser implementation of {@link HostMediaBridge}.\n *\n * Mirrors metadata to `navigator.mediaSession.metadata` (via the DOM\n * `MediaMetadata` constructor when available; plain-object fallback in test\n * envs). Mirrors playback state to `navigator.mediaSession.playbackState`\n * with the canonical mapping: 'playing' maps to 'playing', 'paused' maps to\n * 'paused', 'buffering' maps to 'paused', 'stopped' maps to 'none'. Installs\n * `setActionHandler` callbacks for play/pause/nexttrack/previoustrack/seekto\n * that fan into the onAction subscriber with the mapped `MediaAction` literal\n * (and `value` from `details.seekTime` for seekto). When `setActiveSession` is\n * called with a non-null `actions` parameter, only the declared actions get\n * active handlers — the remaining are cleared (matching the capabilities\n * narrowing behavior of Plan 27-01's inline implementation).\n *\n * Installs a silent-audio prime (4 kHz silent WAV data URL) when the first\n * session becomes active (setActiveSession called with a non-null sessionId) —\n * browsers refuse to render OS media controls without a playing audio element.\n * Removes the element when destroySession brings the active session count to\n * zero.\n *\n * @param opts.mediaSessionTarget - Override navigator.mediaSession (tests).\n * @param opts.documentTarget - Override document (tests; pass null to disable silent-audio prime).\n */\nexport function createBrowserMediaBridge(opts: {\n mediaSessionTarget?: MediaSessionTarget;\n documentTarget?: Document | null;\n} = {}): HostMediaBridge {\n const ms: MediaSessionTarget | null =\n opts.mediaSessionTarget\n ?? (typeof navigator !== 'undefined' && 'mediaSession' in navigator\n ? (navigator.mediaSession as MediaSessionTarget)\n : null);\n const doc: Document | null =\n opts.documentTarget !== undefined\n ? opts.documentTarget\n : (typeof document !== 'undefined' ? document : null);\n\n let silentAudioEl: HTMLAudioElement | null = null;\n let activeSessionId: string | null = null;\n let sessionsActive = 0;\n const actionCallbacks = new Set<(sessionId: string, action: MediaAction, value?: number) => void>();\n\n function primeSilentAudio(): void {\n if (silentAudioEl || !doc) return;\n const el = doc.createElement('audio') as HTMLAudioElement;\n el.src = SILENT_AUDIO_DATA_URL;\n el.loop = true;\n el.style.display = 'none';\n (el as HTMLAudioElement).setAttribute('data-kehto-silent-audio-prime', 'true');\n doc.body.appendChild(el);\n void el.play().catch(() => { /* autoplay refused — metadata mirror still works */ });\n silentAudioEl = el;\n }\n\n function teardownSilentAudio(): void {\n if (!silentAudioEl) return;\n try { silentAudioEl.pause(); } catch { /* best-effort */ }\n try { silentAudioEl.remove(); } catch { /* best-effort */ }\n silentAudioEl = null;\n }\n\n /**\n * Install action handlers for the given set of nap-media actions. Actions not\n * in the set get their handler cleared. Matches Plan 27-01's installActionHandlersFor\n * behavior exactly so capabilities-narrowing tests continue to pass.\n */\n function applyActionHandlers(actions: readonly MediaAction[] = DEFAULT_MEDIA_ACTIONS): void {\n if (!ms) return;\n for (const [domAction, napAction] of ACTION_MATRIX) {\n if (!actions.includes(napAction)) {\n try { ms.setActionHandler(domAction, null); } catch { /* best-effort */ }\n continue;\n }\n ms.setActionHandler(domAction, (details) => {\n if (!activeSessionId) return;\n const value = napAction === 'seek' && typeof details?.seekTime === 'number' ? details.seekTime : undefined;\n for (const cb of actionCallbacks) {\n cb(activeSessionId, napAction, value);\n }\n });\n }\n }\n\n function writeMetadata(metadata: MediaMetadata | undefined): void {\n if (!ms) return;\n if (!metadata) { ms.metadata = null; return; }\n const artwork = metadata.artwork?.url ? [{ src: metadata.artwork.url }] : undefined;\n const init: MediaMetadataLike & { artwork?: unknown } = {\n title: metadata.title ?? '',\n artist: metadata.artist ?? '',\n album: metadata.album ?? '',\n ...(artwork ? { artwork } : {}),\n };\n try {\n const ctor = (globalThis as typeof globalThis & { MediaMetadata?: new (init: MediaMetadataLike) => MediaMetadataLike }).MediaMetadata;\n ms.metadata = ctor ? new ctor(init) : (init as MediaMetadataLike);\n } catch {\n ms.metadata = init as MediaMetadataLike;\n }\n }\n\n return {\n setMetadata(sessionId, metadata) {\n if (sessionId === activeSessionId) writeMetadata(metadata);\n },\n setPlaybackState(sessionId, state) {\n if (!ms || sessionId !== activeSessionId) return;\n ms.playbackState =\n state === 'playing' ? 'playing'\n : state === 'paused' || state === 'buffering' ? 'paused'\n : 'none';\n },\n onAction(callback) {\n actionCallbacks.add(callback);\n return () => { actionCallbacks.delete(callback); };\n },\n setActiveSession(sessionId, actions) {\n activeSessionId = sessionId;\n if (!sessionId) {\n if (ms) {\n ms.metadata = null;\n ms.playbackState = 'none';\n for (const [domAction] of ACTION_MATRIX) {\n try { ms.setActionHandler(domAction, null); } catch { /* best-effort */ }\n }\n }\n return;\n }\n // Prime silent-audio on first session becoming active.\n if (sessionsActive === 0) { primeSilentAudio(); sessionsActive = 1; }\n applyActionHandlers(actions ?? DEFAULT_MEDIA_ACTIONS);\n },\n destroySession(_sessionId) {\n sessionsActive = Math.max(0, sessionsActive - 1);\n if (sessionsActive === 0) teardownSilentAudio();\n },\n };\n}\n","/**\n * media-service.ts — NIP-5D media NAP reference service (navigator.mediaSession\n * reference implementation).\n *\n * Handles the napplet-owned subset of @napplet/nap/media:\n * media.session.create (result), media.session.update, media.session.destroy,\n * media.state, media.capabilities.\n *\n * HostMediaBridge contract: {@link HostMediaBridge} defines the pluggable backend\n * contract for metadata/state mirroring + action routing. The browser reference\n * implementation is {@link createBrowserMediaBridge} (mirrors to navigator.mediaSession\n * with setActionHandler matrix). When no hostBridge option is passed, createMediaService\n * internally uses createBrowserMediaBridge as the default — behavior is identical\n * to the Plan 27-01 single-path implementation.\n *\n * navigator.mediaSession mirroring: on session.create the browser bridge mirrors the\n * napplet-supplied metadata to navigator.mediaSession.metadata via new MediaMetadata()\n * and installs setActionHandler callbacks for the 5 OS transport actions\n * (play / pause / nexttrack / previoustrack / seekto). Each callback emits a canonical\n * media.command envelope to the owning napplet — that is the @napplet/nap/media\n * MediaCommandMessage shape consumed by the SDK's mediaOnCommand() helper.\n *\n * NAP-MEDIA now distinguishes napplet-owned playback from shell-owned\n * playback. This reference backend supports `owner: \"napplet\"` because it\n * mirrors a napplet's own media element to navigator.mediaSession. It rejects\n * `owner: \"shell\"` until a host bridge provides policy-checked source fetching\n * and playback.\n *\n * media.command push: when the OS user clicks a media control (hardware key, lock-screen\n * transport, OS media overlay), the bridge's onAction callback fires. The service looks\n * up the active session's owning napplet, invokes the per-window send callback captured\n * at session.create time, and delivers:\n * { type: 'media.command', sessionId, action, value? }\n * where action is the nap-media MediaAction literal (play|pause|next|prev|seek) and\n * value is the seekTime (seconds) for seek.\n *\n * Silent-audio prime: the browser bridge creates a hidden <audio> element with a\n * 4 kHz silent WAV data URL and plays it when the first session becomes active\n * (setActiveSession with a non-null sessionId). Without a playing audio element in\n * the host page, most browsers refuse to render OS media controls. The element is\n * cleaned up when the last session is destroyed (via bridge.destroySession).\n *\n * Multi-session registry with last-active-wins semantics: every session.create is\n * tracked in a Map<sessionId, SessionEntry>; any media.state report promotes that\n * session to active. The active session's metadata and playback state are reflected\n * via bridge.setMetadata / bridge.setPlaybackState. When the active session is\n * destroyed, the next-most-recently-touched session is promoted automatically.\n *\n * Note: this is SEPARATE from packages/services/src/audio-service.ts, which\n * is the legacy inc-topic-based audio source registry (audio:* topic events\n * over inc.emit). media-service is the canonical @napplet/nap/media NIP-5D\n * path and they coexist — audio-service continues to track audio sources for\n * shell UI, while media-service handles the NAP protocol envelope surface.\n *\n * Shell -> Napplet push types (media.command) are emitted here when\n * bridge.onAction callbacks fire — this is the canonical Phase 27 real backend.\n */\n\nimport type { NappletMessage } from '@napplet/core';\n// DRIFT-CORE-06 — Phase 11-deviation: ServiceDescriptor dropped from @napplet/core\n// v0.2.0+ (napplet phase-81). Re-exported from @kehto/runtime (canonical home after Phase 24 DRIFT-01).\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\nimport type {\n MediaAction,\n MediaCapabilitiesMessage,\n MediaCommandMessage,\n MediaMetadata,\n MediaSessionDestroyMessage,\n MediaSessionUpdateMessage,\n MediaStateMessage,\n} from '@napplet/nap/media/types';\nimport {\n createBrowserMediaBridge,\n DEFAULT_MEDIA_ACTIONS,\n type MediaSessionTarget,\n} from './browser-media-bridge.js';\n\nexport { createBrowserMediaBridge } from './browser-media-bridge.js';\nexport type {\n MediaMetadataLike,\n MediaSessionTarget,\n} from './browser-media-bridge.js';\n\n/** Registry entry — maps a sessionId back to its owning window, metadata snapshot,\n * state snapshot, and declared capabilities. Internal; never on the wire. */\ninterface SessionEntry {\n sessionId: string;\n windowId: string;\n owner: MediaPlaybackOwner;\n source: MediaSourceRef | undefined;\n metadata: MediaMetadata | undefined;\n state: { status: 'playing' | 'paused' | 'stopped' | 'buffering'; position?: number; duration?: number; volume?: number } | undefined;\n actions: readonly MediaAction[]; // from media.capabilities; defaults to ['play','pause','next','prev','seek']\n /** Monotonic tick updated on every session.create / session.update / state — used for last-active-wins resolution when the current active session is destroyed. */\n lastTouched: number;\n}\n\n/** Media service version — follows semver. */\nconst MEDIA_SERVICE_VERSION = '1.1.0';\n\nexport type MediaPlaybackOwner = 'shell' | 'napplet';\n\nexport interface MediaSourceRef {\n url?: string;\n blossomHash?: string;\n nostr?: {\n eventId?: string;\n address?: string;\n relays?: string[];\n };\n mimeType?: string;\n}\n\nexport interface MediaSessionCreateOptions {\n owner: MediaPlaybackOwner;\n sessionId?: string;\n source?: MediaSourceRef;\n metadata?: MediaMetadata;\n capabilities?: MediaAction[];\n autoplay?: boolean;\n live?: boolean;\n}\n\ntype MediaSessionCreateEnvelope = NappletMessage & Partial<MediaSessionCreateOptions> & {\n type: 'media.session.create';\n id?: string;\n};\n\ntype MediaSessionCreateResultEnvelope = NappletMessage & {\n type: 'media.session.create.result';\n id: string;\n sessionId?: string;\n owner?: MediaPlaybackOwner;\n error?: string;\n};\n\n/**\n * Host-bridge contract for pluggable media backends.\n *\n * The browser reference implementation ({@link createBrowserMediaBridge}) mirrors\n * napplet-reported metadata/state to `navigator.mediaSession` and installs\n * `setActionHandler` callbacks that fan into the bridge's onAction subscribers.\n * It satisfies this interface with all 5 fields implemented (setActiveSession\n * switches the active session and optionally re-applies action-handler narrowing;\n * destroySession tears down the silent-audio prime on last-session teardown).\n *\n * Host apps (Electron, Tauri) implement this interface in their own code and\n * pass it via `createMediaService({ hostBridge: myBridge })` — the service\n * then delegates metadata/state mirroring + action routing to the bridge and\n * remains browser-free. Session-ownership bookkeeping (which windowId owns\n * which sessionId, which send callback routes media.command back to which\n * napplet) stays in the service layer — that's wire-protocol concern, not a\n * bridge concern.\n *\n * Reference implementations for Electron / Tauri are explicitly out of v1.4\n * scope and live in host-app examples / follow-up milestones (see\n * REQUIREMENTS.md \"Future Requirements\").\n *\n * @example\n * ```ts\n * // Host-app pseudocode (Electron main-process relay):\n * const electronBridge: HostMediaBridge = {\n * setMetadata(sessionId, md) { mediaBridge.sendMetadata({ sessionId, md }); },\n * setPlaybackState(sessionId, state) { mediaBridge.sendPlaybackState({ sessionId, state }); },\n * onAction(cb) {\n * const handler = (_: unknown, msg: { sessionId: string; action: MediaAction; value?: number }) =>\n * cb(msg.sessionId, msg.action, msg.value);\n * mediaBridge.onAction(handler);\n * return () => mediaBridge.offAction(handler);\n * },\n * };\n * const media = createMediaService({ hostBridge: electronBridge });\n * runtime.registerService('media', media);\n * ```\n */\nexport interface HostMediaBridge {\n /**\n * Set the metadata displayed on the OS transport surface for a session.\n * Called on session.create (with initial metadata) and on session.update\n * (with merged metadata) whenever the session is the active session.\n * Implementations MUST be idempotent.\n */\n setMetadata(sessionId: string, metadata: MediaMetadata): void;\n\n /**\n * Set the playback state for a session. Called on media.state reports\n * whenever the session is the active session. State strings match\n * nap-media MediaState.status exactly. Implementations MUST be idempotent.\n */\n setPlaybackState(sessionId: string, state: 'playing' | 'paused' | 'stopped' | 'buffering'): void;\n\n /**\n * Subscribe to OS-level action events (user clicks play/pause/seek/next/prev\n * on the transport surface). Returns an unsubscribe handle.\n *\n * The callback receives `(sessionId, action, value?)`. `sessionId` is the\n * bridge's currently-active session (the browser impl tracks this internally\n * via setActionHandler-at-fire-time; native impls track via setActiveSession).\n * `value` is populated for `action === 'seek'` (seek target in seconds) and\n * for `action === 'volume'` (0.0-1.0). The service dispatches the resulting\n * `media.command` envelope to the owning napplet of that session.\n */\n onAction(callback: (sessionId: string, action: MediaAction, value?: number) => void): () => void;\n\n /**\n * Optional: notify the bridge that the active session has changed. The\n * browser reference impl uses this to switch which session's metadata/state\n * is mirrored to the singleton navigator.mediaSession and to install (or\n * clear) action handlers for the session's declared capabilities.\n *\n * The optional `actions` parameter carries the session's declared capability\n * set so the bridge can narrow which OS transport buttons are active. When\n * omitted, the bridge applies its default set. Native OS bridges that track\n * active-session state internally may omit this field entirely.\n */\n setActiveSession?(sessionId: string | null, actions?: readonly MediaAction[]): void;\n\n /**\n * Optional: tear down per-session resources. The browser reference impl\n * uses this to remove the silent-audio prime element when the last session\n * is destroyed. Bridges that need no per-session teardown may omit this field.\n */\n destroySession?(sessionId: string): void;\n}\n\n/**\n * Optional host callbacks for the media service.\n *\n * Host shells can hook session creation and state updates to drive\n * their own UI (e.g., now-playing banner) without replacing the\n * ServiceHandler wholesale.\n *\n * @example\n * ```ts\n * const media = createMediaService({\n * onSessionCreate: (windowId, sessionId, metadata) => {\n * console.log(`[${windowId}] created session ${sessionId}`, metadata);\n * },\n * onState: (windowId, sessionId, state) => {\n * nowPlaying.update(windowId, state);\n * },\n * });\n *\n * runtime.registerService('media', media);\n * ```\n */\nexport interface MediaServiceOptions {\n /** Called when a napplet creates a session. May inspect/record the session. */\n onSessionCreate?: (windowId: string, sessionId: string, metadata?: unknown) => void;\n /** Called on media.state updates — high-frequency; keep handler work minimal. */\n onState?: (windowId: string, sessionId: string, state: unknown) => void;\n /** Called when a napplet destroys a session. */\n onSessionDestroy?: (windowId: string, sessionId: string) => void;\n /** Called when a napplet updates session metadata. */\n onSessionUpdate?: (windowId: string, sessionId: string, metadata: unknown) => void;\n /** Called when a napplet declares capabilities for a session. */\n onCapabilities?: (windowId: string, sessionId: string, actions: unknown) => void;\n\n /**\n * MediaSession target override (used by default browser bridge only).\n * Defaults to `navigator.mediaSession` when running in a browser. Pass a\n * MockMediaSession in unit tests. Ignored when `hostBridge` is provided.\n */\n mediaSessionTarget?: MediaSessionTarget;\n\n /**\n * DOM document override (used by default browser bridge only).\n * Defaults to `document` when available. Set to null to disable the silent-audio\n * prime entirely — useful in unit tests. Ignored when `hostBridge` is provided.\n */\n documentTarget?: Document | null;\n\n /**\n * Optional pluggable backend for metadata/state mirroring + OS action handling.\n * When provided, the service delegates setMetadata / setPlaybackState / onAction\n * to the bridge and skips navigator.mediaSession entirely. When omitted, the\n * service internally uses {@link createBrowserMediaBridge} as the default.\n * See {@link HostMediaBridge}.\n */\n hostBridge?: HostMediaBridge;\n}\n\ninterface MediaServiceState {\n bridge: HostMediaBridge;\n options: MediaServiceOptions;\n sessionRegistry: Map<string, SessionEntry>;\n windowSessions: Map<string, Set<string>>;\n sendHandles: Map<string, (msg: NappletMessage) => void>;\n activeSessionId: string | null;\n touchCounter: number;\n sessionCounter: number;\n}\n\nfunction createMediaServiceState(options: MediaServiceOptions, bridge: HostMediaBridge): MediaServiceState {\n return {\n bridge,\n options,\n sessionRegistry: new Map<string, SessionEntry>(),\n windowSessions: new Map<string, Set<string>>(),\n sendHandles: new Map<string, (msg: NappletMessage) => void>(),\n activeSessionId: null,\n touchCounter: 0,\n sessionCounter: 0,\n };\n}\n\nfunction setActive(state: MediaServiceState, sessionId: string | null, actions?: readonly MediaAction[]): void {\n state.activeSessionId = sessionId;\n state.bridge.setActiveSession?.(sessionId, actions);\n if (!sessionId) return;\n const entry = state.sessionRegistry.get(sessionId);\n if (!entry) return;\n if (entry.metadata) state.bridge.setMetadata(sessionId, entry.metadata);\n if (entry.state) state.bridge.setPlaybackState(sessionId, entry.state.status);\n}\n\nfunction promoteNextActiveOrClear(state: MediaServiceState): void {\n if (state.sessionRegistry.size === 0) {\n setActive(state, null);\n return;\n }\n let latest: SessionEntry | null = null;\n for (const entry of state.sessionRegistry.values()) {\n if (!latest || entry.lastTouched > latest.lastTouched) latest = entry;\n }\n setActive(state, latest ? latest.sessionId : null, latest?.actions);\n}\n\nfunction sendMediaCommand(state: MediaServiceState, sessionId: string, action: MediaAction, value?: number): void {\n const entry = state.sessionRegistry.get(sessionId);\n if (!entry) return;\n const send = state.sendHandles.get(entry.windowId);\n if (!send) return;\n const payload: MediaCommandMessage = {\n type: 'media.command',\n sessionId,\n action,\n ...(typeof value === 'number' ? { value } : {}),\n };\n send(payload as NappletMessage);\n}\n\nfunction registerWindowSession(state: MediaServiceState, windowId: string, sessionId: string): void {\n if (!state.windowSessions.has(windowId)) state.windowSessions.set(windowId, new Set());\n state.windowSessions.get(windowId)!.add(sessionId);\n}\n\nfunction sendSessionCreateResult(\n send: (msg: NappletMessage) => void,\n id: string | undefined,\n fields: Omit<MediaSessionCreateResultEnvelope, 'type' | 'id'>,\n): void {\n send({\n type: 'media.session.create.result',\n id: id ?? '',\n ...fields,\n } as NappletMessage);\n}\n\nfunction isMediaPlaybackOwner(value: unknown): value is MediaPlaybackOwner {\n return value === 'shell' || value === 'napplet';\n}\n\nfunction hasSourceRef(source: MediaSourceRef | undefined): boolean {\n if (!source) return false;\n if (typeof source.url === 'string' && source.url.length > 0) return true;\n if (typeof source.blossomHash === 'string' && source.blossomHash.length > 0) return true;\n if (source.nostr) {\n return Boolean(source.nostr.eventId || source.nostr.address);\n }\n return false;\n}\n\nfunction canonicalizeSessionId(\n state: MediaServiceState,\n windowId: string,\n preferredSessionId: string | undefined,\n): string {\n const trimmed = typeof preferredSessionId === 'string' ? preferredSessionId.trim() : '';\n const hint = trimmed || `session-${++state.sessionCounter}`;\n if (!state.sessionRegistry.has(hint)) return hint;\n\n let next: string;\n do {\n next = `${windowId}:${hint}:${++state.sessionCounter}`;\n } while (state.sessionRegistry.has(next));\n return next;\n}\n\nfunction handleSessionCreate(\n state: MediaServiceState,\n windowId: string,\n message: MediaSessionCreateEnvelope,\n send: (msg: NappletMessage) => void,\n): void {\n if (!isMediaPlaybackOwner(message.owner)) {\n sendSessionCreateResult(send, message.id, { error: 'missing owner' });\n return;\n }\n\n if (message.owner === 'shell') {\n if (!hasSourceRef(message.source)) {\n sendSessionCreateResult(send, message.id, { owner: 'shell', error: 'missing source' });\n return;\n }\n sendSessionCreateResult(send, message.id, { owner: 'shell', error: 'unsupported owner mode' });\n return;\n }\n\n state.sendHandles.set(windowId, send);\n const sessionId = canonicalizeSessionId(state, windowId, message.sessionId);\n const entry: SessionEntry = {\n sessionId,\n windowId,\n owner: message.owner,\n source: message.source,\n metadata: message.metadata,\n state: undefined,\n actions: message.capabilities ?? DEFAULT_MEDIA_ACTIONS,\n lastTouched: ++state.touchCounter,\n };\n state.sessionRegistry.set(sessionId, entry);\n registerWindowSession(state, windowId, sessionId);\n setActive(state, sessionId, entry.actions);\n state.options.onSessionCreate?.(windowId, sessionId, message.metadata);\n sendSessionCreateResult(send, message.id, { sessionId, owner: message.owner });\n}\n\nfunction handleSessionUpdate(state: MediaServiceState, windowId: string, message: MediaSessionUpdateMessage): void {\n const entry = state.sessionRegistry.get(message.sessionId);\n if (entry) {\n entry.metadata = { ...entry.metadata, ...message.metadata };\n entry.lastTouched = ++state.touchCounter;\n if (entry.owner === 'napplet' && message.sessionId === state.activeSessionId && entry.metadata) {\n state.bridge.setMetadata(message.sessionId, entry.metadata);\n }\n }\n state.options.onSessionUpdate?.(windowId, message.sessionId, message.metadata);\n}\n\nfunction handleSessionDestroy(state: MediaServiceState, windowId: string, message: MediaSessionDestroyMessage): void {\n const entry = state.sessionRegistry.get(message.sessionId);\n if (entry) {\n state.sessionRegistry.delete(message.sessionId);\n const set = state.windowSessions.get(entry.windowId);\n if (set) {\n set.delete(message.sessionId);\n if (set.size === 0) state.windowSessions.delete(entry.windowId);\n }\n state.bridge.destroySession?.(message.sessionId);\n if (message.sessionId === state.activeSessionId) promoteNextActiveOrClear(state);\n }\n state.options.onSessionDestroy?.(windowId, message.sessionId);\n}\n\nfunction handleMediaState(state: MediaServiceState, windowId: string, message: MediaStateMessage): void {\n const entry = state.sessionRegistry.get(message.sessionId);\n if (entry?.owner === 'napplet') {\n entry.state = {\n status: message.status,\n position: message.position,\n duration: message.duration,\n volume: message.volume,\n };\n entry.lastTouched = ++state.touchCounter;\n if (state.activeSessionId !== message.sessionId) setActive(state, message.sessionId, entry.actions);\n else state.bridge.setPlaybackState(message.sessionId, message.status);\n }\n state.options.onState?.(windowId, message.sessionId, message);\n}\n\nfunction handleMediaCapabilities(\n state: MediaServiceState,\n windowId: string,\n message: MediaCapabilitiesMessage,\n): void {\n const entry = state.sessionRegistry.get(message.sessionId);\n if (entry?.owner === 'napplet') {\n entry.actions = message.actions;\n entry.lastTouched = ++state.touchCounter;\n if (message.sessionId === state.activeSessionId) {\n state.bridge.setActiveSession?.(message.sessionId, entry.actions);\n }\n }\n state.options.onCapabilities?.(windowId, message.sessionId, message.actions);\n}\n\nfunction handleMediaMessage(\n state: MediaServiceState,\n windowId: string,\n message: NappletMessage,\n send: (msg: NappletMessage) => void,\n): void {\n switch (message.type) {\n case 'media.session.create':\n handleSessionCreate(state, windowId, message as MediaSessionCreateEnvelope, send);\n return;\n case 'media.session.update':\n handleSessionUpdate(state, windowId, message as MediaSessionUpdateMessage);\n return;\n case 'media.session.destroy':\n handleSessionDestroy(state, windowId, message as MediaSessionDestroyMessage);\n return;\n case 'media.state':\n handleMediaState(state, windowId, message as MediaStateMessage);\n return;\n case 'media.capabilities':\n handleMediaCapabilities(state, windowId, message as MediaCapabilitiesMessage);\n return;\n default: {\n const id = (message as NappletMessage & { id?: string }).id ?? '';\n send({\n type: `${message.type}.error`,\n id,\n error: `Unknown media method: ${message.type}`,\n } as NappletMessage);\n }\n }\n}\n\nfunction destroyWindowSessions(state: MediaServiceState, windowId: string): void {\n const sessions = state.windowSessions.get(windowId);\n if (sessions) {\n const ownedActive = state.activeSessionId !== null && sessions.has(state.activeSessionId);\n for (const sessionId of sessions) {\n state.sessionRegistry.delete(sessionId);\n state.bridge.destroySession?.(sessionId);\n }\n state.windowSessions.delete(windowId);\n if (ownedActive) promoteNextActiveOrClear(state);\n }\n state.sendHandles.delete(windowId);\n}\n\nfunction destroyMediaState(state: MediaServiceState, unsubscribeAction: () => void): void {\n unsubscribeAction();\n for (const sessionId of state.sessionRegistry.keys()) state.bridge.destroySession?.(sessionId);\n state.bridge.setActiveSession?.(null);\n state.sessionRegistry.clear();\n state.windowSessions.clear();\n state.sendHandles.clear();\n state.activeSessionId = null;\n state.touchCounter = 0;\n state.sessionCounter = 0;\n}\n\n/**\n * Create a media NAP service handler with navigator.mediaSession integration.\n *\n * Implements the 5 napplet->shell media.* request types defined in\n * `@napplet/nap/media`. Only `media.session.create` produces a reply\n * envelope (`media.session.create.result`) — the remaining four\n * (`session.update`, `session.destroy`, `state`, `capabilities`) are\n * fire-and-forget per the NAP spec.\n *\n * Unknown `media.*` actions produce a `<type>.error` envelope so\n * napplets are never left hanging on a malformed request.\n *\n * @param options - Optional host callbacks for session lifecycle + state, plus\n * mediaSessionTarget and documentTarget for test injection, and an optional\n * hostBridge for native-OS media backend delegation.\n * @returns A ServiceHandler (with `destroy()`) to register with the runtime\n *\n * @example\n * ```ts\n * import { createMediaService } from '@kehto/services';\n *\n * const media = createMediaService();\n * runtime.registerService('media', media);\n * // Later, on shell teardown:\n * media.destroy();\n * ```\n */\nexport function createMediaService(options: MediaServiceOptions = {}): ServiceHandler & { destroy(): void } {\n const descriptor: ServiceDescriptor = {\n name: 'media',\n version: MEDIA_SERVICE_VERSION,\n description: options.hostBridge\n ? 'NIP-5D media NAP reference handler (host-bridge delegated)'\n : 'NIP-5D media NAP reference handler (navigator.mediaSession mirror)',\n };\n\n const bridge: HostMediaBridge = options.hostBridge\n ?? createBrowserMediaBridge({\n mediaSessionTarget: options.mediaSessionTarget,\n documentTarget: options.documentTarget,\n });\n const state = createMediaServiceState(options, bridge);\n\n const unsubscribeAction = bridge.onAction((sessionId, action, value) => {\n sendMediaCommand(state, sessionId, action, value);\n });\n\n return {\n descriptor,\n\n handleMessage(windowId: string, message: NappletMessage, send: (msg: NappletMessage) => void): void {\n handleMediaMessage(state, windowId, message, send);\n },\n\n onWindowDestroyed(windowId: string): void {\n destroyWindowSessions(state, windowId);\n },\n\n destroy(): void {\n destroyMediaState(state, unsubscribeAction);\n },\n };\n}\n","/**\n * notify-service.ts — NIP-5D notify NAP reference service (stub-level).\n *\n * Handles the 5 napplet -> shell request types from `@napplet/nap/notify`:\n * - `notify.send` -> `notify.send.result` (shell-assigned id)\n * - `notify.dismiss` -> fire-and-forget\n * - `notify.badge` -> fire-and-forget\n * - `notify.channel.register` -> fire-and-forget\n * - `notify.permission.request` -> `notify.permission.result { granted }`\n *\n * Stub-level: no real Notification API calls, no real channel registry.\n * Host apps wire a real backend via\n * `runtime.registerService('notify', realHandler)`.\n *\n * Coexistence note: this is SEPARATE from\n * `packages/services/src/notification-service.ts`, which is the legacy\n * inc-topic-based notification registry (operates on `inc.emit` topics\n * under `notifications:*`). `notify-service.ts` is the canonical\n * `@napplet/nap/notify` NIP-5D path and lives alongside the legacy module.\n * If the host app registers this service via\n * `runtime.registerService('notify', ...)`, @napplet/nap/notify messages\n * land here; the legacy inc-emit topic path remains untouched.\n *\n * Shell -> napplet push messages (`notify.action`, `notify.clicked`,\n * `notify.dismissed`, `notify.controls`) are not emitted by this stub —\n * they are the host app's responsibility and are deferred to a future plan.\n */\n\nimport type { NappletMessage } from '@napplet/core';\n// DRIFT-CORE-06 — Phase 11-deviation: ServiceDescriptor dropped from @napplet/core\n// v0.2.0+ (napplet phase-81). Re-exported from @kehto/runtime (canonical home after Phase 24 DRIFT-01).\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\nimport type {\n NotifySendMessage,\n NotifySendResultMessage,\n NotifyPermissionRequestMessage,\n NotifyPermissionResultMessage,\n} from '@napplet/nap/notify/types';\n\n/** Notify service version — follows semver. */\nconst NOTIFY_SERVICE_VERSION = '1.0.0';\n\n/**\n * Optional configuration for `createNotifyService`.\n *\n * @example\n * ```ts\n * const notify = createNotifyService({\n * generateId: () => crypto.randomUUID(),\n * defaultGrant: false,\n * onSend: (windowId, msg) => console.log(`napplet ${windowId} sent ${msg.title}`),\n * });\n * runtime.registerService('notify', notify);\n * ```\n */\nexport interface NotifyServiceOptions {\n /**\n * Generate a shell-assigned notification ID for `notify.send.result`.\n * Default: a monotonically increasing `shell-<n>` counter.\n */\n generateId?: () => string;\n /**\n * Default permission grant for `notify.permission.request`.\n * Host apps that want real permission prompts should replace this\n * service with a real backend via `runtime.registerService`.\n * Default: `true`.\n */\n defaultGrant?: boolean;\n /**\n * Called synchronously when a napplet dispatches `notify.send`.\n * Intended for host-app plumbing (UI toast, logging, etc.) without\n * requiring a full backend replacement.\n */\n onSend?: (windowId: string, payload: NotifySendMessage) => void;\n}\n\n/**\n * Create a stub-level notify service handler.\n *\n * Answers the 5 napplet->shell request types from `@napplet/nap/notify`.\n * Does NOT implement a real backend (no DOM Notification API, no channel\n * registry, no permission prompt). Host apps replace this via\n * `runtime.registerService('notify', realHandler)` when a real backend is\n * needed.\n *\n * @param options - Optional service configuration (see NotifyServiceOptions)\n * @returns A ServiceHandler to register with the runtime under domain `notify`\n *\n * @example\n * ```ts\n * import { createNotifyService } from '@kehto/services';\n *\n * const notify = createNotifyService();\n * runtime.registerService('notify', notify);\n * ```\n */\nexport function createNotifyService(options: NotifyServiceOptions = {}): ServiceHandler {\n let counter = 0;\n const gen = options.generateId ?? ((): string => {\n counter += 1;\n return `shell-${counter}`;\n });\n const defaultGrant = options.defaultGrant ?? true;\n\n const descriptor: ServiceDescriptor = {\n name: 'notify',\n version: NOTIFY_SERVICE_VERSION,\n description: 'NIP-5D notify NAP reference handler (stub)',\n };\n\n return {\n descriptor,\n\n handleMessage(windowId: string, message: NappletMessage, send: (msg: NappletMessage) => void): void {\n switch (message.type) {\n case 'notify.send': {\n const m = message as NotifySendMessage;\n options.onSend?.(windowId, m);\n const result: NotifySendResultMessage = {\n type: 'notify.send.result',\n id: m.id,\n notificationId: gen(),\n };\n send(result as NappletMessage);\n return;\n }\n\n case 'notify.dismiss':\n case 'notify.badge':\n case 'notify.channel.register':\n // state to mutate; host apps with real backends override.\n return;\n\n case 'notify.permission.request': {\n const m = message as NotifyPermissionRequestMessage;\n const result: NotifyPermissionResultMessage = {\n type: 'notify.permission.result',\n id: m.id,\n granted: defaultGrant,\n };\n send(result as NappletMessage);\n return;\n }\n\n default: {\n const id = (message as NappletMessage & { id?: string }).id ?? '';\n send({\n type: `${message.type}.error`,\n id,\n error: `Unknown notify method: ${message.type}`,\n } as NappletMessage);\n }\n }\n },\n\n onWindowDestroyed(_windowId: string): void {\n // No per-window state in the stub. Host apps with real backends\n // override to clean up pending notifications / channel registrations.\n },\n };\n}\n","/**\n * theme-service.ts — NIP-5D theme NAP reference service.\n *\n * Handles the single napplet->shell request type from @napplet/nap/theme:\n * - `theme.get` -> `theme.get.result { theme }` (current theme)\n *\n * Exposes a host-facing `publishTheme(theme)` handle that:\n * 1. Replaces the service's internal current theme.\n * 2. Invokes `options.onBroadcast(envelope)` synchronously with a\n * `theme.changed` envelope so the shell adapter (Plan 13-02) can\n * fan-out the push to every registered napplet.\n * 3. Returns the envelope so callers can use it directly if they prefer.\n *\n * The default theme values are centralized here and mirrored in the runtime's\n * fallback path (`packages/runtime/src/runtime.ts`) — runtime does NOT import\n * from @kehto/services because services depends on runtime (one-way only).\n *\n * Host apps replace this via `runtime.registerService('theme', realHandler)`\n * when real CSS injection / storage / dark-mode logic is needed.\n *\n * @example\n * ```ts\n * import { createThemeService } from '@kehto/services';\n *\n * const theme = createThemeService({\n * onBroadcast: (envelope) => broadcastToAllNapplets(envelope),\n * });\n * runtime.registerService('theme', theme.handler);\n *\n * // Later, when the user flips dark/light mode:\n * theme.publishTheme(newTheme);\n * ```\n */\n\nimport type { NappletMessage } from '@napplet/core';\n// DRIFT-CORE-06 — Phase 11-deviation: ServiceDescriptor dropped from\n// @napplet/core v0.2.0+ (napplet phase-81). Re-exported from\n// @kehto/runtime (canonical home after Phase 24 DRIFT-01).\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\nimport type {\n Theme,\n ThemeChangedMessage,\n ThemeGetResultMessage,\n} from '@napplet/nap/theme/types';\n\n/** Theme service version — follows semver. */\nconst THEME_SERVICE_VERSION = '1.0.0';\n\n/**\n * Canonical default theme values.\n *\n * Must stay synchronized with the runtime fallback constant at\n * `packages/runtime/src/runtime.ts` (THEME_FALLBACK_DEFAULT). The runtime\n * keeps a local copy instead of importing from @kehto/services to avoid a\n * runtime->services dependency (services depends on runtime; one-way).\n */\nconst DEFAULT_THEME: Theme = {\n colors: {\n background: '#0a0a0a',\n text: '#e0e0e0',\n primary: '#7aa2f7',\n },\n // fonts, background, title intentionally undefined — all optional per\n // @napplet/nap/theme Theme interface.\n};\n\n/**\n * Configuration for `createThemeService`.\n *\n * @example\n * ```ts\n * const theme = createThemeService({\n * initialTheme: { colors: { background: '#fff', text: '#000', primary: '#00f' } },\n * onBroadcast: (envelope) => shellBridge.broadcastToAll(envelope),\n * });\n * ```\n */\nexport interface ThemeServiceOptions {\n /**\n * Override the default theme payload. If omitted, the service starts with\n * the canonical defaults (`#0a0a0a / #e0e0e0 / #7aa2f7`).\n */\n initialTheme?: Theme;\n\n /**\n * Called synchronously from `publishTheme(theme)` with a `theme.changed`\n * envelope. Intended for the shell adapter (Plan 13-02) to fan-out the\n * push to every registered napplet via the runtime's sendToNapplet\n * primitive.\n *\n * Keep this callback shape framework-agnostic — the service does NOT\n * import any shell / browser APIs.\n */\n onBroadcast?: (envelope: ThemeChangedMessage) => void;\n}\n\n/**\n * A theme service bundle — the ServiceHandler that handles `theme.*`\n * envelopes, the host-facing `publishTheme(theme)` handle for theme-change\n * broadcasts, and a `getCurrentTheme()` accessor for host-side reads.\n */\nexport interface ThemeService {\n /** Register this with the runtime via `runtime.registerService('theme', handler)`. */\n handler: ServiceHandler;\n\n /**\n * Publish a theme-change to the shell adapter. Updates the service's\n * internal current theme, invokes `options.onBroadcast` with a\n * `theme.changed` envelope, and returns the envelope.\n *\n * @param theme - The new theme payload\n * @returns A `theme.changed` envelope (same one passed to onBroadcast)\n */\n publishTheme: (theme: Theme) => ThemeChangedMessage;\n\n /** Return the current theme. Equivalent to the payload a napplet's `theme.get` would receive. */\n getCurrentTheme: () => Theme;\n}\n\n/**\n * Create a theme service that handles the NIP-5D `theme.*` NAP.\n *\n * Answers `theme.get` with the current theme (default or\n * `options.initialTheme`). Exposes `publishTheme(theme)` for the host app to\n * broadcast theme changes to every registered napplet — the shell adapter\n * (Plan 13-02) wires `onBroadcast` to `runtime.sendToNapplet` fan-out.\n *\n * @param options - Optional service configuration (see ThemeServiceOptions)\n * @returns A ThemeService bundle ready for `runtime.registerService('theme', service.handler)`\n *\n * @example\n * ```ts\n * import { createThemeService } from '@kehto/services';\n *\n * const theme = createThemeService();\n * runtime.registerService('theme', theme.handler);\n * theme.publishTheme({ colors: { background: '#fff', text: '#000', primary: '#00f' } });\n * ```\n */\nexport function createThemeService(options: ThemeServiceOptions = {}): ThemeService {\n let currentTheme: Theme = options.initialTheme ?? DEFAULT_THEME;\n\n const descriptor: ServiceDescriptor = {\n name: 'theme',\n version: THEME_SERVICE_VERSION,\n description: 'NIP-5D theme NAP reference handler',\n };\n\n const handler: ServiceHandler = {\n descriptor,\n\n handleMessage(\n _windowId: string,\n message: NappletMessage,\n send: (msg: NappletMessage) => void,\n ): void {\n const id = (message as NappletMessage & { id?: string }).id ?? '';\n\n if (message.type === 'theme.get') {\n const result: ThemeGetResultMessage = {\n type: 'theme.get.result',\n id,\n theme: currentTheme,\n };\n send(result as NappletMessage);\n return;\n }\n\n send({\n type: `${message.type}.error`,\n id,\n error: `Unknown theme method: ${message.type}`,\n } as NappletMessage);\n },\n\n // Theme service has no per-window state to clean up.\n onWindowDestroyed(_windowId: string): void {\n /* no-op */\n },\n };\n\n function publishTheme(theme: Theme): ThemeChangedMessage {\n currentTheme = theme;\n const envelope: ThemeChangedMessage = { type: 'theme.changed', theme };\n options.onBroadcast?.(envelope);\n return envelope;\n }\n\n function getCurrentTheme(): Theme {\n return currentTheme;\n }\n\n return { handler, publishTheme, getCurrentTheme };\n}\n","/**\n * config-service.ts — NAP-CONFIG reference service (9th NAP domain, v1.7 Phase 39).\n *\n * Shell-side reference implementation for the canonical NAP-CONFIG wire\n * protocol (`@napplet/nap/config`, published at `^0.3.0`). Handles the full\n * 8-message discriminated union: 5 napplet→shell request types + 3\n * shell→napplet result/push types.\n *\n * ──────────────────────────── SCOPE BOUNDARY (CONFIG-04) ─────────────────────────\n * NAP-CONFIG is **shell-managed per-napplet configuration**. Napplets observe\n * values via `config.get` (one-shot) or `config.subscribe` (snapshot + live\n * push). The shell is the **sole writer** — there is intentionally **NO**\n * `config.set` wire message. Napplets cannot mutate configuration values;\n * the shell owns persistence and the update flow.\n *\n * Do NOT use this service as a general key-value store. NAP-STORAGE\n * (`state:read` / `state:write`) remains the general KV surface. Using\n * NAP-CONFIG to store e.g. `{ lastScrollPosition: 420 }` is an anti-pattern\n * (H-07 in PITFALLS.md) — such state belongs in NAP-STORAGE.\n * ──────────────────────────────────────────────────────────────────────────────────\n *\n * Host integration: provide `getValues()` returning the current\n * `ConfigValues` snapshot. Call the returned `publishValues(newValues)`\n * whenever the configuration changes — the service fans the new snapshot\n * out to every napplet that has an active `config.subscribe`.\n *\n * Optional: provide `registerSchema` to accept napplet-declared schemas at\n * runtime (the ref impl does a minimal shape check using the Core Subset\n * validator; use `ajv` in host impls that need strict draft-07 conformance).\n * Provide `openSettings` to open a shell-side UI for the napplet (no\n * response envelope — fire-and-forget UI hook).\n *\n * @example\n * ```ts\n * import { createConfigService } from '@kehto/services';\n *\n * const configFixtures = { theme: 'dark', density: 'compact', recentSearches: [] };\n * const config = createConfigService({\n * getValues: () => ({ ...configFixtures }),\n * });\n * runtime.registerService('config', config.handler);\n *\n * // Later, when shell-side values change:\n * configFixtures.theme = 'light';\n * config.publishValues({ ...configFixtures });\n * ```\n */\n\nimport type { NappletMessage } from '@napplet/core';\n// DRIFT-CORE-06 — Phase 11-deviation: ServiceDescriptor dropped from @napplet/core\n// v0.2.0+ (napplet phase-81). Re-exported from @kehto/runtime (canonical home after Phase 24 DRIFT-01).\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\nimport type {\n ConfigGetMessage,\n ConfigRegisterSchemaMessage,\n ConfigOpenSettingsMessage,\n ConfigValues,\n ConfigValuesMessage,\n ConfigRegisterSchemaResultMessage,\n ConfigSchemaErrorCode,\n NappletConfigSchema,\n} from '@napplet/nap/config/types';\n\n/** Config service version — follows semver. */\nconst CONFIG_SERVICE_VERSION = '1.0.0';\n\n/**\n * Shape returned by a successful `registerSchema` result (ok=true) or a\n * rejection (ok=false + code + error). Mirrors the wire envelope fields.\n */\nexport type ConfigSchemaValidation =\n | { ok: true }\n | { ok: false; code: ConfigSchemaErrorCode; error: string };\n\n/**\n * Configuration options for `createConfigService` (options-as-bridge\n * per v1.6 Decision 18).\n *\n * @example\n * ```ts\n * const config = createConfigService({\n * getValues: () => ({ theme: 'dark', density: 'compact' }),\n * openSettings: (windowId, section) => showSettingsPanel(windowId, section),\n * });\n * ```\n */\nexport interface ConfigServiceOptions {\n /**\n * Returns the current configuration values snapshot.\n * Called on every `config.get` and at every `config.subscribe` initial push.\n * Implementations should return a fresh object (not a mutable reference).\n */\n getValues(): ConfigValues;\n\n /**\n * Optional: receive notification when a napplet subscribes to config updates.\n * Fire-and-forget — the service tracks the subscription internally regardless.\n */\n onSubscribe?: (windowId: string) => void;\n\n /**\n * Optional: receive notification when a napplet unsubscribes.\n */\n onUnsubscribe?: (windowId: string) => void;\n\n /**\n * Optional: validate and store a napplet-provided schema.\n *\n * If omitted, the ref impl runs its own Core Subset check (hand-coded\n * validator; 30-50 lines) and returns ok/reject. Hosts that need strict\n * draft-07 conformance should provide an ajv-backed implementation.\n *\n * Return shape mirrors `config.registerSchema.result` wire envelope\n * (minus the `id` — the dispatch layer correlates).\n */\n registerSchema?: (\n windowId: string,\n schema: NappletConfigSchema,\n version: number | undefined,\n ) => ConfigSchemaValidation;\n\n /**\n * Optional: open the shell-side settings UI for this napplet.\n * Fire-and-forget — no response envelope per the wire spec.\n * If omitted, `config.openSettings` is silently dropped (D10 allows\n * the config-demo napplet to function without a settings UI).\n */\n openSettings?: (windowId: string, section: string | undefined) => void;\n}\n\n/**\n * NAP-CONFIG reference service bundle — `handler` to register with the\n * runtime, `publishValues` for the host app to push updates live to all\n * subscribed napplets.\n */\nexport interface ConfigService {\n /** Register this with the runtime via `runtime.registerService('config', handler)`. */\n handler: ServiceHandler;\n\n /**\n * Broadcast a new values snapshot to every napplet with an active\n * `config.subscribe`. Each subscriber receives a `config.values` envelope\n * with no `id` (push form per wire spec — absence of `id` distinguishes\n * push from correlated `config.get` response).\n *\n * @param values - The new configuration snapshot (full object, not a diff)\n */\n publishValues(values: ConfigValues): void;\n}\n\n/**\n * Minimal JSON Schema validator covering the NAP-CONFIG Core Subset:\n * type: object / string / number / boolean / array, required[], default, properties.\n *\n * Explicitly rejects: $ref, pattern, oneOf/anyOf/allOf/not, if/then/else.\n * Returns { ok: true } on shape sanity, otherwise an error code per the\n * canonical ConfigSchemaErrorCode union.\n *\n * Host apps that need strict draft-07 conformance should supply a custom\n * `registerSchema` callback backed by ajv@8.\n */\nfunction validateCoreSubset(schema: unknown): ConfigSchemaValidation {\n if (typeof schema !== 'object' || schema === null || Array.isArray(schema)) {\n return { ok: false, code: 'invalid-schema', error: 'schema root must be an object' };\n }\n const s = schema as Record<string, unknown>;\n\n // Reject forbidden keywords (NAP-CONFIG Core Subset limits per spec).\n if ('$ref' in s) {\n return { ok: false, code: 'ref-not-allowed', error: '$ref is not permitted in the Core Subset' };\n }\n if ('pattern' in s) {\n return {\n ok: false,\n code: 'pattern-not-allowed',\n error: 'pattern is not permitted in the Core Subset',\n };\n }\n if ('oneOf' in s || 'anyOf' in s || 'allOf' in s || 'not' in s) {\n return {\n ok: false,\n code: 'invalid-schema',\n error: 'oneOf/anyOf/allOf/not are not permitted in the Core Subset',\n };\n }\n if ('if' in s || 'then' in s || 'else' in s) {\n return {\n ok: false,\n code: 'invalid-schema',\n error: 'if/then/else are not permitted in the Core Subset',\n };\n }\n if (s.type !== 'object') {\n return { ok: false, code: 'invalid-schema', error: 'schema root must have type: \"object\"' };\n }\n\n // Shallow properties check: each declared property must use a supported type.\n const props = s.properties;\n if (props !== undefined && (typeof props !== 'object' || props === null)) {\n return { ok: false, code: 'invalid-schema', error: 'properties must be an object' };\n }\n if (props) {\n for (const [key, val] of Object.entries(props as Record<string, unknown>)) {\n if (typeof val !== 'object' || val === null) {\n return {\n ok: false,\n code: 'invalid-schema',\n error: `property \"${key}\" must be an object schema`,\n };\n }\n const pv = val as Record<string, unknown>;\n const ALLOWED_TYPES = new Set(['string', 'number', 'boolean', 'array', 'object']);\n if (pv.type !== undefined && !ALLOWED_TYPES.has(pv.type as string)) {\n return {\n ok: false,\n code: 'invalid-schema',\n error: `property \"${key}\" must have type: string|number|boolean|array|object`,\n };\n }\n }\n }\n\n return { ok: true };\n}\n\n/**\n * Create a NAP-CONFIG reference service.\n *\n * Shell-writes, napplet-reads. Handles the full `@napplet/nap/config` wire\n * protocol: `config.get` (correlated snapshot), `config.subscribe` /\n * `config.unsubscribe` (live push stream), `config.registerSchema` (optional\n * schema registration + Core Subset validation), `config.openSettings`\n * (optional UI deep-link, fire-and-forget).\n *\n * Returns a `ConfigService` bundle: `{ handler, publishValues }`.\n * Register `handler` with the runtime; call `publishValues(newValues)` from\n * the shell whenever config state changes.\n *\n * @param options - Host-supplied implementation hooks (options-as-bridge,\n * v1.6 Decision 18). `getValues` is required; all other fields are optional.\n * @returns A ConfigService bundle.\n *\n * @see ConfigServiceOptions for the options shape.\n * @see packages/services/src/theme-service.ts for the sibling pattern.\n * @see SCOPE BOUNDARY comment at the top of this file re: NAP-STORAGE separation.\n *\n * @example\n * ```ts\n * import { createConfigService } from '@kehto/services';\n *\n * const config = createConfigService({\n * getValues: () => ({ theme: 'dark', density: 'compact' }),\n * openSettings: (windowId, section) => openSettingsUI(section),\n * });\n * runtime.registerService('config', config.handler);\n *\n * // Push a live update to all subscribers:\n * config.publishValues({ theme: 'light', density: 'compact' });\n * ```\n */\nexport function createConfigService(options: ConfigServiceOptions): ConfigService {\n /**\n * Per-window subscriber set. Maps windowId → the send callback captured at\n * `config.subscribe` time. `publishValues` fans out to every entry.\n */\n const subscribers = new Map<string, (msg: NappletMessage) => void>();\n\n const descriptor: ServiceDescriptor = {\n name: 'config',\n version: CONFIG_SERVICE_VERSION,\n description: 'NAP-CONFIG reference service — shell-writes, napplet-reads configuration',\n };\n\n const handler: ServiceHandler = {\n descriptor,\n\n handleMessage(\n windowId: string,\n message: NappletMessage,\n send: (msg: NappletMessage) => void,\n ): void {\n switch (message.type) {\n case 'config.get': {\n const m = message as ConfigGetMessage;\n const reply: ConfigValuesMessage = {\n type: 'config.values',\n id: m.id,\n values: options.getValues(),\n };\n send(reply as NappletMessage);\n return;\n }\n\n case 'config.subscribe': {\n // Capture the send callback so publishValues can fan pushes out.\n subscribers.set(windowId, send);\n // Immediate initial snapshot push — no `id` (push form per wire spec).\n const push: ConfigValuesMessage = {\n type: 'config.values',\n values: options.getValues(),\n };\n send(push as NappletMessage);\n options.onSubscribe?.(windowId);\n return;\n }\n\n case 'config.unsubscribe': {\n subscribers.delete(windowId);\n options.onUnsubscribe?.(windowId);\n return;\n }\n\n case 'config.registerSchema': {\n const m = message as ConfigRegisterSchemaMessage;\n // Delegate to host-supplied validator if present; otherwise use\n // the built-in Core Subset hand-coded validator (D12).\n const validation: ConfigSchemaValidation = options.registerSchema\n ? options.registerSchema(windowId, m.schema, m.version)\n : validateCoreSubset(m.schema);\n\n const result: ConfigRegisterSchemaResultMessage = validation.ok\n ? { type: 'config.registerSchema.result', id: m.id, ok: true }\n : {\n type: 'config.registerSchema.result',\n id: m.id,\n ok: false,\n code: validation.code,\n error: validation.error,\n };\n send(result as NappletMessage);\n return;\n }\n\n case 'config.openSettings': {\n const m = message as ConfigOpenSettingsMessage;\n // Silently dropped if openSettings hook not provided (D10).\n options.openSettings?.(windowId, m.section);\n return;\n }\n\n default:\n // Unknown config.* message — silently ignored per NIP-5D.\n return;\n }\n },\n\n onWindowDestroyed(windowId: string): void {\n // A napplet iframe was destroyed — drop any active subscription so we\n // don't retain the stale send callback in the subscribers map.\n subscribers.delete(windowId);\n },\n };\n\n /**\n * Broadcast a new config values snapshot to every subscribed napplet.\n * Each subscriber receives a `config.values` push envelope (no `id` —\n * absence of `id` distinguishes a push from a correlated `config.get`\n * response per the NAP-CONFIG wire spec).\n */\n function publishValues(values: ConfigValues): void {\n const envelope: ConfigValuesMessage = {\n type: 'config.values',\n values,\n };\n for (const send of subscribers.values()) {\n try {\n send(envelope as NappletMessage);\n } catch {\n // Subscriber's send callback threw (e.g., iframe gone without\n // onWindowDestroyed firing yet). Best-effort — drop silently.\n }\n }\n }\n\n return { handler, publishValues };\n}\n","/**\n * resource-service.ts — NAP-RESOURCE reference service (10th NAP domain, v1.7 Phase 40).\n *\n * Shell-side reference implementation for the canonical NAP-RESOURCE wire\n * protocol (`internal-resource.ts` in @kehto/shell/src/types; kehto-internal\n * model per PROJECT.md Decision #31. Kehto keeps legacy single-fetch fields\n * for existing callers and also emits the current NAP-RESOURCE fields.\n * Handles:\n * Inbound: resource.bytes, resource.bytesMany, resource.cancel\n * Outbound: resource.bytes.result, resource.bytes.error,\n * resource.bytesMany.result, resource.bytesMany.error\n *\n * ──────────────────────── SCOPE BOUNDARY (RESOURCE-01) ────────────────────────\n * NAP-RESOURCE is an **authenticated fetch proxy** — read-only, atomic.\n *\n * This service is NOT responsible for:\n * - Streaming / chunked responses (host-app concern)\n * - Response caching / conditional requests (host-app concern)\n * - Upload / POST body construction (NAP-RESOURCE v1.7 is read-only)\n * - Redirect limits, MIME sniffing, SVG rasterization (host-fetch concern)\n * - Private-IP blocking, SSRF mitigation (host-provided-fetch responsibility)\n *\n * These belong to the host-app's `fetch` implementation per D7 and\n * SHELL-RESOURCE-POLICY.md (Phase 40 Plan 40-03). Kehto ships a reference\n * service; production hardening is the host app's concern.\n * ──────────────────────────────────────────────────────────────────────────────\n *\n * Host integration: provide `fetch`, `isOriginGranted`, `getConnectGrants`,\n * and `resolveIdentity`. ALL FOUR are required from day one (H-03 prevention).\n *\n * @example\n * ```ts\n * import { createResourceService } from '@kehto/services';\n *\n * const resourceSvc = createResourceService({\n * fetch: (url, init) => globalThis.fetch(url, init),\n * isOriginGranted: (origin, grants) => grants.includes(origin),\n * getConnectGrants: (dTag, hash) => myOriginGrantStore.getOrigins(dTag, hash),\n * resolveIdentity: (windowId) => sessionRegistry.getEntryByWindowId(windowId) ?? null,\n * });\n * runtime.registerService('resource', resourceSvc);\n * ```\n */\n\nimport type { NappletMessage } from '@napplet/core';\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\n\n/** Resource service version — follows semver. */\nconst RESOURCE_SERVICE_VERSION = '1.0.0';\n\n/**\n * Options for `createResourceService` (options-as-bridge per v1.6 Decision 18).\n *\n * ALL FOUR fields are required. The factory throws at construction if any is\n * missing — H-03 prevention: the grants source (`getConnectGrants`) MUST be\n * wired from day one so there is no window where resource requests bypass the\n * grant check.\n *\n * @see PITFALLS.md:228 (H-03) — grants-source coupling must be present at construction\n */\nexport interface ResourceServiceOptions {\n /**\n * Host-supplied fetch implementation. Receives the URL, a partial init\n * (method, headers, signal), and must return a `Response`-compatible promise.\n *\n * The host's `fetch` is the ONLY place to implement redirect limits, MIME\n * sniffing, SVG rasterization, private-IP / SSRF blocking, etc.\n * This service does NOT filter: it proxies transparently.\n *\n * @param url - The URL from the resource.bytes request\n * @param init - Method, headers (from napplet), and an AbortSignal\n */\n fetch(\n url: string,\n init: { method?: string; headers?: Record<string, string>; signal: AbortSignal }\n ): Promise<Response>;\n\n /**\n * Returns true if `origin` is present in `grants` (the list returned by\n * `getConnectGrants` for the napplet's dTag + aggregateHash).\n *\n * The reference implementation is simply `grants.includes(origin)`. Host apps\n * may provide normalized-origin comparison if needed.\n *\n * @param origin - Parsed origin of the requested URL (scheme + host + port)\n * @param grants - Readonly list from getConnectGrants for this napplet identity\n */\n isOriginGranted(origin: string, grants: readonly string[]): boolean;\n\n /**\n * Returns the list of allowed fetch origins for the given napplet identity.\n * Called on every `resource.bytes` request — must be synchronous and fast.\n *\n * Host-supplied grant source (e.g. a static per-dTag allowlist map, or any\n * other host-controlled policy). Returns an empty array to deny all origins.\n *\n * H-03 prevention: REQUIRED from day one — factory throws on construction\n * if omitted.\n *\n * @param dTag - The napplet's d-tag (from session registry)\n * @param aggregateHash - The napplet's aggregate hash (from session registry)\n */\n getConnectGrants(dTag: string, aggregateHash: string): readonly string[];\n\n /**\n * Resolve a windowId to the napplet's identity (dTag + aggregateHash).\n * Returns null if the window is not in the session registry.\n *\n * Typically wraps `sessionRegistry.getEntryByWindowId(windowId)`.\n *\n * @param windowId - The iframe window identifier\n */\n resolveIdentity(windowId: string): { dTag: string; aggregateHash: string } | null;\n}\n\n/**\n * Type alias for the ServiceHandler returned by `createResourceService`.\n * Exported for host apps that need to type-annotate the handler reference.\n */\nexport type ResourceService = ServiceHandler;\n\n/**\n * Convert an ArrayBuffer to base64 string, safe for both browser and Node.\n * Chunked in 0x8000-byte slices to avoid `String.fromCharCode(...largeArray)`\n * stack overflow on large responses.\n */\nfunction arrayBufferToBase64(buf: ArrayBuffer): string {\n const bytes = new Uint8Array(buf);\n const CHUNK = 0x8000; // 32 KB\n let binary = '';\n for (let i = 0; i < bytes.length; i += CHUNK) {\n binary += String.fromCharCode(...bytes.subarray(i, i + CHUNK));\n }\n return btoa(binary);\n}\n\ninterface ResourceRequestState {\n inFlight: Map<string, { controller: AbortController; windowId: string }>;\n perWindow: Map<string, Set<string>>;\n}\n\ntype LegacyResourceErrorCode = 'denied' | 'invalid-url' | 'canceled' | 'network-error';\n\ntype ResourceErrorCode =\n | 'invalid-request'\n | 'not-found'\n | 'blocked-by-policy'\n | 'timeout'\n | 'too-large'\n | 'unsupported-scheme'\n | 'decode-failed'\n | 'network-error'\n | 'quota-exceeded';\n\ntype ResourceFetchSuccess = {\n ok: true;\n url: string;\n blob: Blob;\n mime: string;\n status: number;\n headers: Record<string, string>;\n bodyBase64: string;\n};\n\ntype ResourceFetchFailure = {\n ok: false;\n url: string;\n error: ResourceErrorCode;\n code: LegacyResourceErrorCode;\n message: string;\n};\n\ntype ResourceFetchItem = ResourceFetchSuccess | ResourceFetchFailure;\n\nfunction assertResourceOptions(options: ResourceServiceOptions): void {\n if (\n typeof options?.fetch !== 'function' ||\n typeof options?.isOriginGranted !== 'function' ||\n typeof options?.getConnectGrants !== 'function' ||\n typeof options?.resolveIdentity !== 'function'\n ) {\n throw new Error(\n '[RESOURCE-01 / H-03] createResourceService requires {fetch, isOriginGranted, getConnectGrants, resolveIdentity} ' +\n '— all four options are required from day one. ' +\n 'The grants source (getConnectGrants) MUST be wired at construction time to prevent unguarded fetch proxying.',\n );\n }\n}\n\nfunction trackRequest(\n state: ResourceRequestState,\n requestId: string,\n windowId: string,\n controller: AbortController,\n): void {\n state.inFlight.set(requestId, { controller, windowId });\n if (!state.perWindow.has(windowId)) {\n state.perWindow.set(windowId, new Set());\n }\n state.perWindow.get(windowId)!.add(requestId);\n}\n\nfunction untrackRequest(state: ResourceRequestState, requestId: string): void {\n const entry = state.inFlight.get(requestId);\n if (entry) {\n state.inFlight.delete(requestId);\n state.perWindow.get(entry.windowId)?.delete(requestId);\n }\n}\n\nfunction sendResourceError(\n send: (m: NappletMessage) => void,\n requestId: string,\n code: LegacyResourceErrorCode,\n message: string,\n error: ResourceErrorCode = toResourceError(code),\n type: 'resource.bytes.error' | 'resource.bytesMany.error' = 'resource.bytes.error',\n): void {\n send({\n type,\n id: requestId,\n requestId,\n error,\n code,\n message,\n } as NappletMessage);\n}\n\nfunction sendBytesManyError(\n send: (m: NappletMessage) => void,\n requestId: string,\n code: LegacyResourceErrorCode,\n message: string,\n error: ResourceErrorCode,\n): void {\n sendResourceError(send, requestId, code, message, error, 'resource.bytesMany.error');\n}\n\nfunction toResourceError(code: LegacyResourceErrorCode): ResourceErrorCode {\n switch (code) {\n case 'denied':\n return 'blocked-by-policy';\n case 'invalid-url':\n return 'invalid-request';\n case 'canceled':\n return 'timeout';\n case 'network-error':\n return 'network-error';\n }\n}\n\nfunction parseResourceUrl(url: string): URL | null {\n try {\n return new URL(url);\n } catch {\n return null;\n }\n}\n\nfunction collectResponseHeaders(response: Response): Record<string, string> {\n const headers: Record<string, string> = {};\n response.headers.forEach((value, key) => {\n headers[key] = value;\n });\n return headers;\n}\n\nfunction responseMime(response: Response): string {\n return response.headers.get('content-type') || 'application/octet-stream';\n}\n\nfunction responseBlob(buffer: ArrayBuffer, mime: string): Blob {\n return new Blob([buffer], { type: mime });\n}\n\nfunction requestIdFromMessage(message: NappletMessage & { id?: unknown; requestId?: unknown }): string | null {\n if (typeof message.id === 'string' && message.id.length > 0) return message.id;\n if (typeof message.requestId === 'string' && message.requestId.length > 0) return message.requestId;\n return null;\n}\n\nfunction resourceInvalidRequest(url: string, message: string): ResourceFetchFailure {\n return {\n ok: false,\n url,\n error: 'invalid-request',\n code: 'invalid-url',\n message,\n };\n}\n\nasync function fetchResourceItem(\n options: ResourceServiceOptions,\n identity: { dTag: string; aggregateHash: string },\n url: string,\n init: { method?: string; headers?: Readonly<Record<string, string>> } | undefined,\n signal: AbortSignal,\n): Promise<ResourceFetchItem> {\n const parsedUrl = parseResourceUrl(url);\n if (!parsedUrl) return resourceInvalidRequest(url, `invalid URL: ${url}`);\n const origin = parsedUrl.origin;\n const grants = options.getConnectGrants(identity.dTag, identity.aggregateHash);\n if (!options.isOriginGranted(origin, grants)) {\n return {\n ok: false,\n url,\n error: 'blocked-by-policy',\n code: 'denied',\n message: `origin ${origin} not granted`,\n };\n }\n\n try {\n const response = await options.fetch(url, {\n method: init?.method,\n headers: init?.headers ? { ...init.headers } : undefined,\n signal,\n });\n const buffer = await response.arrayBuffer();\n const headers = collectResponseHeaders(response);\n const mime = responseMime(response);\n return {\n ok: true,\n url,\n blob: responseBlob(buffer, mime),\n mime,\n status: response.status,\n headers,\n bodyBase64: arrayBufferToBase64(buffer),\n };\n } catch (err: unknown) {\n const isAbort =\n signal.aborted ||\n (err instanceof Error && (err.name === 'AbortError' || err.name === 'DOMException'));\n return {\n ok: false,\n url,\n error: isAbort ? 'timeout' : 'network-error',\n code: isAbort ? 'canceled' : 'network-error',\n message: err instanceof Error ? err.message : String(err),\n };\n }\n}\n\nasync function handleBytes(\n options: ResourceServiceOptions,\n state: ResourceRequestState,\n windowId: string,\n msg: { requestId: string; url: string; init?: { method?: string; headers?: Readonly<Record<string, string>> } },\n send: (m: NappletMessage) => void,\n): Promise<void> {\n const { requestId, url, init } = msg;\n const identity = options.resolveIdentity(windowId);\n if (!identity) {\n sendResourceError(send, requestId, 'denied', 'napplet identity not resolvable', 'blocked-by-policy');\n return;\n }\n\n const controller = new AbortController();\n trackRequest(state, requestId, windowId, controller);\n\n try {\n const item = await fetchResourceItem(options, identity, url, init, controller.signal);\n if (!item.ok) {\n sendResourceError(send, requestId, item.code, item.message, item.error);\n return;\n }\n send({\n type: 'resource.bytes.result',\n id: requestId,\n requestId,\n blob: item.blob,\n mime: item.mime,\n status: item.status,\n headers: item.headers,\n bodyBase64: item.bodyBase64,\n } as NappletMessage);\n } finally {\n untrackRequest(state, requestId);\n }\n}\n\nasync function handleBytesMany(\n options: ResourceServiceOptions,\n state: ResourceRequestState,\n windowId: string,\n msg: { requestId: string; urls: readonly string[]; init?: { method?: string; headers?: Readonly<Record<string, string>> } },\n send: (m: NappletMessage) => void,\n): Promise<void> {\n const { requestId, urls, init } = msg;\n if (!Array.isArray(urls) || urls.length === 0 || urls.some((url) => typeof url !== 'string')) {\n sendBytesManyError(send, requestId, 'invalid-url', 'resource.bytesMany requires a non-empty urls array', 'invalid-request');\n return;\n }\n\n const identity = options.resolveIdentity(windowId);\n if (!identity) {\n sendBytesManyError(send, requestId, 'denied', 'napplet identity not resolvable', 'blocked-by-policy');\n return;\n }\n\n const controller = new AbortController();\n trackRequest(state, requestId, windowId, controller);\n try {\n const items: Array<Record<string, unknown>> = [];\n for (const url of urls) {\n const item = await fetchResourceItem(options, identity, url, init, controller.signal);\n if (item.ok) {\n items.push({\n url: item.url,\n ok: true,\n blob: item.blob,\n mime: item.mime,\n });\n } else {\n items.push({\n url: item.url,\n ok: false,\n error: item.error,\n code: item.code,\n message: item.message,\n });\n }\n }\n send({\n type: 'resource.bytesMany.result',\n id: requestId,\n requestId,\n items,\n } as NappletMessage);\n } finally {\n untrackRequest(state, requestId);\n }\n}\n\nfunction handleCancel(state: ResourceRequestState, requestId: string): void {\n const entry = state.inFlight.get(requestId);\n if (entry) {\n entry.controller.abort();\n }\n}\n\nfunction destroyWindowRequests(state: ResourceRequestState, windowId: string): void {\n const requestIds = state.perWindow.get(windowId);\n if (!requestIds) return;\n for (const requestId of requestIds) {\n const entry = state.inFlight.get(requestId);\n if (entry) {\n entry.controller.abort();\n state.inFlight.delete(requestId);\n }\n }\n state.perWindow.delete(windowId);\n}\n\n/**\n * Create a NAP-RESOURCE reference service.\n *\n * Implements the NAP-RESOURCE request/response protocol: `resource.bytes`,\n * `resource.bytesMany`, `resource.cancel`, and their result/error envelopes.\n *\n * On-construction guard (H-03 prevention): all four options are validated at\n * factory call time. If any is missing, the factory throws immediately with a\n * message containing `[RESOURCE-01 / H-03]` so misconfigured shell apps fail\n * loudly at startup rather than silently at first dispatch.\n *\n * Returns a `ServiceHandler` (no `publishValues`-style surface — resource has\n * no shell-initiated push beyond the response/error path).\n *\n * @param options - REQUIRED: fetch, isOriginGranted, getConnectGrants, resolveIdentity\n * @returns ServiceHandler to register via `runtime.registerService('resource', handler)`\n *\n * @example\n * ```ts\n * import { createResourceService } from '@kehto/services';\n *\n * const svc = createResourceService({\n * fetch: (url, init) => globalThis.fetch(url, init),\n * isOriginGranted: (origin, grants) => grants.includes(origin),\n * getConnectGrants: (dTag, hash) => myOriginGrantStore.getOrigins(dTag, hash),\n * resolveIdentity: (windowId) => sessionRegistry.getEntryByWindowId(windowId) ?? null,\n * });\n * runtime.registerService('resource', svc);\n * ```\n */\nexport function createResourceService(options: ResourceServiceOptions): ResourceService {\n assertResourceOptions(options);\n const state: ResourceRequestState = {\n inFlight: new Map<string, { controller: AbortController; windowId: string }>(),\n perWindow: new Map<string, Set<string>>(),\n };\n\n const descriptor: ServiceDescriptor = {\n name: 'resource',\n version: RESOURCE_SERVICE_VERSION,\n description:\n 'NAP-RESOURCE reference service — shell-proxied authenticated fetch (RESOURCE-01..06)',\n };\n\n const handler: ServiceHandler = {\n descriptor,\n\n handleMessage(\n windowId: string,\n message: NappletMessage,\n send: (msg: NappletMessage) => void,\n ): void {\n switch (message.type) {\n case 'resource.bytes': {\n const m = message as NappletMessage & {\n id?: string;\n requestId?: string;\n url: string;\n init?: { method?: string; headers?: Readonly<Record<string, string>> };\n };\n const requestId = requestIdFromMessage(m);\n if (!requestId || typeof m.url !== 'string') return;\n handleBytes(options, state, windowId, { requestId, url: m.url, init: m.init }, send).catch(() => { /* errors surface via send() */ });\n return;\n }\n\n case 'resource.bytesMany': {\n const m = message as NappletMessage & {\n id?: string;\n requestId?: string;\n urls?: readonly string[];\n init?: { method?: string; headers?: Readonly<Record<string, string>> };\n };\n const requestId = requestIdFromMessage(m);\n if (!requestId) return;\n handleBytesMany(options, state, windowId, { requestId, urls: m.urls ?? [], init: m.init }, send).catch(() => { /* errors surface via send() */ });\n return;\n }\n\n case 'resource.cancel': {\n const m = message as NappletMessage & { id?: string; requestId?: string };\n const requestId = requestIdFromMessage(m);\n if (requestId) handleCancel(state, requestId);\n return;\n }\n\n default:\n // Unknown resource.* message — silently ignored per NIP-5D.\n return;\n }\n },\n\n onWindowDestroyed(windowId: string): void {\n destroyWindowRequests(state, windowId);\n },\n };\n\n return handler;\n}\n","/**\n * outbox-service.ts — NAP-OUTBOX (outbox-aware relay routing) reference service.\n *\n * Shell-side handler for the NAP-OUTBOX wire protocol. It is a pure envelope\n * router: it validates `outbox.*` envelopes, delegates the actual relay\n * discovery / routing / dedup / publish-fanout work to an injected\n * {@link OutboxRouter}, and posts the correlated result / lifecycle messages\n * (echoing the request `id` or `subId`) back to the napplet.\n *\n * The router is injected (options-as-bridge) so this service has no Nostr\n * dependency and is fully unit-testable. A concrete relay-pool-backed router\n * ships alongside as {@link createRelayPoolOutboxRouter}.\n *\n * ──────────────────────────── Responsibilities ────────────────────────────\n * Inbound: outbox.query, outbox.subscribe, outbox.close, outbox.publish,\n * outbox.resolveRelays\n * Outbound: outbox.query.result, outbox.event, outbox.eose, outbox.closed,\n * outbox.publish.result, outbox.resolveRelays.result\n *\n * The shell owns relay discovery, routing, fallback, deduplication, signature\n * validation, signing, and publish fanout policy — all of which live behind\n * the {@link OutboxRouter}. This service only marshals the wire protocol.\n *\n * @example\n * ```ts\n * import { createOutboxService, createRelayPoolOutboxRouter } from '@kehto/services';\n *\n * const router = createRelayPoolOutboxRouter({ relayPool, loadRelayLists, fallbackRelays });\n * runtime.registerService('outbox', createOutboxService({ router }));\n * ```\n *\n * @packageDocumentation\n */\n\nimport type { EventTemplate, NappletMessage, NostrEvent, NostrFilter } from '@napplet/core';\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\n\n/** Outbox service version — follows semver. */\nconst OUTBOX_SERVICE_VERSION = '1.0.0';\n\n/**\n * Relay-selection strategy:\n * - `outbox` — query/publish via author write relays (the outbox model)\n * - `inbox` — query/publish via recipient read relays (the inbox model)\n * - `auto` — let the shell choose per its policy and relay intelligence\n */\nexport type OutboxStrategy = 'outbox' | 'inbox' | 'auto';\n\n/** Options for a one-shot outbox query. */\nexport interface OutboxQueryOptions {\n /** Explicit author hints (augment/override authors derived from filters). */\n authors?: string[];\n /** Relay hints; treated as a hint subject to shell validation, not a bypass. */\n relays?: string[];\n /** Relay-selection strategy. */\n strategy?: OutboxStrategy;\n /** Maximum events to collect. */\n limit?: number;\n /** Wall-clock budget for the query, in milliseconds. */\n timeoutMs?: number;\n}\n\n/** Options for a live outbox subscription. */\nexport interface OutboxSubscribeOptions extends OutboxQueryOptions {\n /** Keep the subscription open for real-time events after EOSE. */\n live?: boolean;\n}\n\n/** Options for an outbox publish. */\nexport interface OutboxPublishOptions {\n /** Relay hints; treated as a hint subject to shell validation. */\n relays?: string[];\n /** Recipient authors whose inbox relays should be included for directed events. */\n targetAuthors?: string[];\n /** Relay-selection strategy. */\n strategy?: OutboxStrategy;\n}\n\n/** A read/write target for relay-plan resolution. */\nexport interface OutboxTarget {\n /** Authors to resolve relays for. */\n authors?: string[];\n /** Single pubkey to resolve relays for. */\n pubkey?: string;\n /** Whether the plan is for reading (their write relays) or writing (their read relays). */\n direction?: 'read' | 'write';\n /** Relay-selection strategy. */\n strategy?: OutboxStrategy;\n}\n\n/** The relay plan the shell would use for a target. */\nexport interface OutboxRelayPlan {\n /** Resolved relay URLs. */\n relays: string[];\n /** Where the plan came from. */\n source: 'nip65' | 'cache' | 'policy' | 'fallback';\n /** Authors for which no relay list could be resolved. */\n missingAuthors?: string[];\n}\n\n/** Outcome of an outbox query, as returned by the {@link OutboxRouter}. */\nexport interface OutboxResult {\n /** Deduplicated, signature-validated events. */\n events: NostrEvent[];\n /** Map of event id -> relay URLs where the shell observed the event. */\n relays: Record<string, string[]>;\n /** True when some relay lists or connections failed and results are partial. */\n incomplete?: boolean;\n /** Error reason when the query could not complete. */\n error?: string;\n}\n\n/** Outcome of an outbox publish, as returned by the {@link OutboxRouter}. */\nexport interface OutboxPublishResult {\n /** Whether the publish succeeded on at least the required relays. */\n ok: boolean;\n /** The signed event returned by the shell. */\n event?: NostrEvent;\n /** The published event id. */\n eventId?: string;\n /** Map of relay URL -> per-relay publish success. */\n relays?: Record<string, boolean>;\n /** Error reason when the publish failed. */\n error?: string;\n}\n\n/** Sink an {@link OutboxRouter} streams subscription lifecycle through. */\nexport interface OutboxSubscriptionSink {\n /** Deliver a matching event; `relay` is the relay it was observed on, when known. */\n event(event: NostrEvent, relay?: string): void;\n /** Signal end-of-stored-events. */\n eose(): void;\n /** Signal that the subscription was closed upstream; `reason` is optional. */\n closed(reason?: string): void;\n}\n\n/** Handle to a router-owned subscription. */\nexport interface OutboxRouterSubscription {\n /** Stop the subscription and release its relay connections. */\n close(): void;\n}\n\n/**\n * Abstract outbox router. Implementors own relay discovery (NIP-65 / NIP-66),\n * routing, fallback, deduplication, signature validation, signing, and publish\n * fanout. The service translates wire envelopes into these calls and back.\n */\nexport interface OutboxRouter {\n /** Resolve relays, query them, dedup by id, validate signatures, collect events. */\n query(filters: NostrFilter[], options?: OutboxQueryOptions): Promise<OutboxResult>;\n /** Open a live outbox-aware subscription, streaming through `sink`. */\n subscribe(\n filters: NostrFilter[],\n options: OutboxSubscribeOptions | undefined,\n sink: OutboxSubscriptionSink,\n ): OutboxRouterSubscription;\n /** Sign `template` and fan it out to the relevant write/inbox relays. */\n publish(template: EventTemplate, options?: OutboxPublishOptions): Promise<OutboxPublishResult>;\n /** Return the relay plan the shell would use for a read/write target. */\n resolveRelays(target: OutboxTarget): Promise<OutboxRelayPlan>;\n}\n\n/** Options for {@link createOutboxService}. */\nexport interface OutboxServiceOptions {\n /** The outbox router the shell uses to reach relays. Required. */\n router: OutboxRouter;\n}\n\ntype Send = (msg: NappletMessage) => void;\n\nconst OUTBOX_DESCRIPTOR: ServiceDescriptor = {\n name: 'outbox',\n version: OUTBOX_SERVICE_VERSION,\n description: 'NAP-OUTBOX outbox-aware relay routing — query/subscribe/publish/resolveRelays',\n};\n\n/**\n * Normalize a wire `filters` field (a single NIP-01 filter or an array) into a\n * filter array. Returns `null` when the input is missing or has no usable\n * filter objects.\n */\nfunction normalizeFilters(raw: unknown): NostrFilter[] | null {\n if (Array.isArray(raw)) {\n const filters = raw.filter((f): f is NostrFilter => typeof f === 'object' && f !== null);\n return filters.length > 0 ? filters : null;\n }\n if (typeof raw === 'object' && raw !== null) return [raw as NostrFilter];\n return null;\n}\n\n/**\n * Create the NAP-OUTBOX service handler.\n *\n * @param options - Must provide an {@link OutboxRouter}.\n * @returns A `ServiceHandler` ready for `runtime.registerService('outbox', handler)`.\n * @throws If `options.router` is missing.\n */\nexport function createOutboxService(options: OutboxServiceOptions): ServiceHandler {\n if (!options || typeof options.router !== 'object' || options.router === null) {\n throw new Error('createOutboxService: options.router is required');\n }\n const { router } = options;\n\n // Active subscriptions keyed by `windowId:subId` for lifecycle management.\n const subscriptions = new Map<string, OutboxRouterSubscription>();\n\n function handleQuery(msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { id?: string; filters?: unknown; options?: OutboxQueryOptions };\n const id = m.id ?? '';\n const filters = normalizeFilters(m.filters);\n if (!filters) {\n send({ type: 'outbox.query.result', id, events: [], relays: {}, error: 'invalid filter' } as NappletMessage);\n return;\n }\n void router\n .query(filters, m.options)\n .then((result) =>\n send({\n type: 'outbox.query.result',\n id,\n events: result.events,\n relays: result.relays,\n ...(result.incomplete === undefined ? {} : { incomplete: result.incomplete }),\n ...(result.error === undefined ? {} : { error: result.error }),\n } as NappletMessage),\n )\n .catch((err) =>\n send({ type: 'outbox.query.result', id, events: [], relays: {}, error: toErrorMessage(err) } as NappletMessage),\n );\n }\n\n function handleSubscribe(windowId: string, msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { subId?: string; filters?: unknown; options?: OutboxSubscribeOptions };\n const subId = m.subId;\n if (typeof subId !== 'string' || subId.length === 0) return;\n const subKey = `${windowId}:${subId}`;\n\n // Replace any existing subscription for this key.\n subscriptions.get(subKey)?.close();\n subscriptions.delete(subKey);\n\n const filters = normalizeFilters(m.filters);\n if (!filters) {\n send({ type: 'outbox.closed', subId, reason: 'invalid filter' } as NappletMessage);\n return;\n }\n\n const sink: OutboxSubscriptionSink = {\n event: (event, relay) =>\n send({ type: 'outbox.event', subId, event, ...(relay === undefined ? {} : { relay }) } as NappletMessage),\n eose: () => send({ type: 'outbox.eose', subId } as NappletMessage),\n closed: (reason) => {\n subscriptions.delete(subKey);\n send({ type: 'outbox.closed', subId, ...(reason === undefined ? {} : { reason }) } as NappletMessage);\n },\n };\n\n subscriptions.set(subKey, router.subscribe(filters, m.options, sink));\n }\n\n function handleClose(windowId: string, msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { subId?: string };\n const subId = m.subId;\n if (typeof subId !== 'string') return;\n const subKey = `${windowId}:${subId}`;\n subscriptions.get(subKey)?.close();\n subscriptions.delete(subKey);\n send({ type: 'outbox.closed', subId } as NappletMessage);\n }\n\n function handlePublish(msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { id?: string; event?: EventTemplate; options?: OutboxPublishOptions };\n const id = m.id ?? '';\n if (!m.event || typeof m.event !== 'object') {\n send({ type: 'outbox.publish.result', id, ok: false, error: 'invalid filter' } as NappletMessage);\n return;\n }\n void router\n .publish(m.event, m.options)\n .then((result) =>\n send({\n type: 'outbox.publish.result',\n id,\n ok: result.ok,\n ...(result.event === undefined ? {} : { event: result.event }),\n ...(result.eventId === undefined ? {} : { eventId: result.eventId }),\n ...(result.relays === undefined ? {} : { relays: result.relays }),\n ...(result.error === undefined ? {} : { error: result.error }),\n } as NappletMessage),\n )\n .catch((err) =>\n send({ type: 'outbox.publish.result', id, ok: false, error: toErrorMessage(err) } as NappletMessage),\n );\n }\n\n function handleResolveRelays(msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { id?: string; target?: OutboxTarget };\n const id = m.id ?? '';\n if (!m.target || typeof m.target !== 'object') {\n send({ type: 'outbox.resolveRelays.result', id, error: 'invalid filter' } as NappletMessage);\n return;\n }\n void router\n .resolveRelays(m.target)\n .then((plan) => send({ type: 'outbox.resolveRelays.result', id, plan } as NappletMessage))\n .catch((err) =>\n send({ type: 'outbox.resolveRelays.result', id, error: toErrorMessage(err) } as NappletMessage),\n );\n }\n\n return {\n descriptor: OUTBOX_DESCRIPTOR,\n handleMessage(windowId: string, message: NappletMessage, send: Send): void {\n switch (message.type) {\n case 'outbox.query':\n handleQuery(message, send);\n return;\n case 'outbox.subscribe':\n handleSubscribe(windowId, message, send);\n return;\n case 'outbox.close':\n handleClose(windowId, message, send);\n return;\n case 'outbox.publish':\n handlePublish(message, send);\n return;\n case 'outbox.resolveRelays':\n handleResolveRelays(message, send);\n return;\n default:\n // Unknown outbox.* action — silently ignored (forward-compatible).\n return;\n }\n },\n onWindowDestroyed(windowId: string): void {\n const prefix = `${windowId}:`;\n for (const [key, sub] of subscriptions) {\n if (key.startsWith(prefix)) {\n sub.close();\n subscriptions.delete(key);\n }\n }\n },\n };\n}\n\nfunction toErrorMessage(err: unknown): string {\n if (err instanceof Error) return err.message;\n if (typeof err === 'string') return err;\n return 'outbox request failed';\n}\n","/**\n * relay-pool-outbox-router.ts — concrete {@link OutboxRouter} backed by a relay pool.\n *\n * Implements the outbox-model routing that NAP-OUTBOX centralizes so napplets\n * don't each reinvent it: derive authors, resolve their NIP-65 relays, fan a\n * per-relay subscription out across the plan, deduplicate by event id (while\n * recording every relay an event was observed on), validate signatures, and —\n * for publish — sign the template and fan it out to the relevant write/inbox\n * relays.\n *\n * NIP-65 relay-list *fetching* is the host's concern (it may come from a\n * kind-10002 cache, a NIP-66 indexer via `@kehto/nip/66`, or a live query), so\n * it is injected via {@link RelayPoolOutboxRouterOptions.loadRelayLists}. The\n * relay pool, signer, and signature verifier are injected too — keeping this\n * router browser-agnostic and unit-testable with mocks.\n *\n * Relay-selection model (per the outbox model):\n * - reading an author's events → their **write** relays (where they publish)\n * - writing to reach an author → their **read** relays (their inbox)\n *\n * `strategy` overrides the direction default: `outbox` forces write relays,\n * `inbox` forces read relays, `auto` (default) follows the read/write direction.\n *\n * @example\n * ```ts\n * import { createOutboxService, createRelayPoolOutboxRouter } from '@kehto/services';\n *\n * const router = createRelayPoolOutboxRouter({\n * relayPool: myOutboxPool,\n * loadRelayLists: (pubkeys) => relayListCache.getMany(pubkeys),\n * fallbackRelays: ['wss://relay.damus.io', 'wss://nos.lol'],\n * signEvent: (tmpl) => signer.signEvent(tmpl),\n * verifyEvent: (ev) => verifyEvent(ev),\n * });\n * runtime.registerService('outbox', createOutboxService({ router }));\n * ```\n *\n * @packageDocumentation\n */\n\nimport type { EventTemplate, NostrEvent, NostrFilter } from '@napplet/core';\nimport type {\n OutboxRouter,\n OutboxResult,\n OutboxPublishResult,\n OutboxRelayPlan,\n OutboxQueryOptions,\n OutboxSubscribeOptions,\n OutboxPublishOptions,\n OutboxStrategy,\n OutboxTarget,\n OutboxSubscriptionSink,\n OutboxRouterSubscription,\n} from './outbox-service.js';\n\n// Timer globals available in all JS runtimes.\ndeclare function setTimeout(callback: () => void, ms: number): unknown;\ndeclare function clearTimeout(id: unknown): void;\n\n/** Default per-query wall-clock budget when `options.timeoutMs` is unset. */\nconst DEFAULT_QUERY_TIMEOUT_MS = 4000;\n\n/** A NIP-65 relay list for a single pubkey. */\nexport interface RelayListEntry {\n /** Relays the author reads from (their inbox). */\n read: string[];\n /** Relays the author writes to (where their events land). */\n write: string[];\n}\n\n/**\n * Relay pool contract the router drives. Implementors adapt their pool library\n * (nostr-tools SimplePool, applesauce-relay, etc.). Unlike the lower-level\n * relay NAP pool, both methods take an explicit relay-URL set so the router\n * controls outbox routing and can attribute events to the relay they arrived on.\n */\nexport interface OutboxRelayPool {\n /**\n * Subscribe to `filters` on exactly `relayUrls`. The callback receives each\n * matching event or the literal `'EOSE'` once stored events are exhausted.\n * Returns a handle to cancel the subscription.\n */\n subscribe(\n filters: NostrFilter[],\n relayUrls: string[],\n callback: (item: NostrEvent | 'EOSE') => void,\n ): { unsubscribe(): void };\n /**\n * Publish `event` to `relayUrls`. May return a per-relay success map; a\n * `void`/missing return is treated as optimistic success on every target.\n */\n publish(\n event: NostrEvent,\n relayUrls: string[],\n ): Promise<Record<string, boolean>> | Record<string, boolean> | void;\n /** Whether the relay pool is connected and able to handle requests. */\n isAvailable(): boolean;\n}\n\n/** Options for {@link createRelayPoolOutboxRouter}. */\nexport interface RelayPoolOutboxRouterOptions {\n /** Relay pool the router subscribes/publishes through. Required. */\n relayPool: OutboxRelayPool;\n /**\n * Resolve NIP-65 relay lists for a set of pubkeys. Pubkeys with no known\n * list are simply omitted from the returned map (they become `missingAuthors`).\n */\n loadRelayLists(pubkeys: string[]): Promise<Map<string, RelayListEntry>> | Map<string, RelayListEntry>;\n /** Relays to fall back to when NIP-65 data is absent, stale, or empty. Required. */\n fallbackRelays: string[];\n /**\n * Sign a template before publish (shell-mediated; napplets never sign). When\n * omitted, `publish` resolves with `{ ok: false, error: 'publish denied' }`.\n */\n signEvent?(template: EventTemplate): Promise<NostrEvent>;\n /**\n * Validate an event signature before delivering it to a napplet. May be sync\n * or async. Defaults to accepting every event (host pools often pre-verify).\n */\n verifyEvent?(event: NostrEvent): Promise<boolean> | boolean;\n /**\n * Gate relay URLs (e.g. block private-network hosts). Defaults to allowing\n * only `ws://` / `wss://` URLs — `options.relays` hints pass through this too.\n */\n isRelayAllowed?(url: string): boolean;\n /** Default query timeout when `options.timeoutMs` is unset. Default 4000ms. */\n defaultTimeoutMs?: number;\n}\n\n/** Resolved router dependencies threaded into the module-level helpers. */\ninterface RouterCtx {\n relayPool: OutboxRelayPool;\n loadRelayLists: RelayPoolOutboxRouterOptions['loadRelayLists'];\n fallbackRelays: string[];\n signEvent?: RelayPoolOutboxRouterOptions['signEvent'];\n isRelayAllowed: (url: string) => boolean;\n defaultTimeoutMs: number;\n verify(event: NostrEvent): Promise<boolean>;\n}\n\n/** Default relay gate: only ws(s):// URLs are permitted. */\nfunction defaultRelayAllowed(url: string): boolean {\n return typeof url === 'string' && (url.startsWith('wss://') || url.startsWith('ws://'));\n}\n\n/** Collect a deduplicated author set from filters + explicit option hints. */\nfunction deriveAuthors(filters: NostrFilter[], optionAuthors?: string[]): string[] {\n const authors = new Set<string>();\n for (const filter of filters) {\n for (const author of filter.authors ?? []) authors.add(author);\n }\n for (const author of optionAuthors ?? []) authors.add(author);\n return [...authors];\n}\n\n/**\n * Whether a resolved plan should use authors' write relays (true) or read\n * relays (false), given the read/write direction and an explicit strategy.\n */\nfunction wantsWriteRelays(direction: 'read' | 'write', strategy: OutboxStrategy): boolean {\n if (strategy === 'outbox') return true;\n if (strategy === 'inbox') return false;\n return direction === 'read'; // auto: reading → author write relays\n}\n\n/** Apply the relay gate to a candidate set, deduplicating. */\nfunction allowed(ctx: RouterCtx, urls: Iterable<string>): string[] {\n const out = new Set<string>();\n for (const url of urls) {\n if (ctx.isRelayAllowed(url)) out.add(url);\n }\n return [...out];\n}\n\n/**\n * Resolve the relay plan for a set of pubkeys. Returns the allowed relay set,\n * its provenance, and any pubkeys whose relay list was unavailable.\n */\nasync function resolvePlan(\n ctx: RouterCtx,\n pubkeys: string[],\n direction: 'read' | 'write',\n strategy: OutboxStrategy,\n relayHints?: string[],\n): Promise<OutboxRelayPlan> {\n const useWrite = wantsWriteRelays(direction, strategy);\n const collected = new Set<string>();\n const missingAuthors: string[] = [];\n let sawNip65 = false;\n\n if (pubkeys.length > 0) {\n const lists = await ctx.loadRelayLists(pubkeys);\n for (const pubkey of pubkeys) {\n const entry = lists.get(pubkey);\n const relays = entry ? (useWrite ? entry.write : entry.read) : undefined;\n if (relays && relays.length > 0) {\n sawNip65 = true;\n for (const url of relays) collected.add(url);\n } else {\n missingAuthors.push(pubkey);\n }\n }\n }\n\n // Relay hints from the napplet augment the plan, subject to the relay gate.\n for (const url of relayHints ?? []) collected.add(url);\n\n let relays = allowed(ctx, collected);\n let source: OutboxRelayPlan['source'];\n if (relays.length === 0) {\n relays = allowed(ctx, ctx.fallbackRelays);\n source = 'fallback';\n } else {\n // nip65 if any author list contributed; otherwise only hints did (policy).\n source = sawNip65 ? 'nip65' : 'policy';\n }\n\n const plan: OutboxRelayPlan = { relays, source };\n if (missingAuthors.length > 0) plan.missingAuthors = missingAuthors;\n return plan;\n}\n\n/** Mutable accumulator for a one-shot fan-out collection. */\ninterface Collector {\n seen: Map<string, NostrEvent>;\n relayMap: Map<string, Set<string>>;\n verifications: Promise<void>[];\n}\n\n/** Record that `id` was observed on `relayUrl`. */\nfunction recordRelay(collector: Collector, id: string, relayUrl: string): void {\n let set = collector.relayMap.get(id);\n if (!set) { set = new Set<string>(); collector.relayMap.set(id, set); }\n set.add(relayUrl);\n}\n\n/** Verify a freshly-seen event and admit it (or drop its sightings) once settled. */\nfunction admitEvent(ctx: RouterCtx, collector: Collector, event: NostrEvent): void {\n if (collector.seen.has(event.id)) return;\n collector.verifications.push(\n ctx.verify(event).then((ok) => {\n if (ok && !collector.seen.has(event.id)) collector.seen.set(event.id, event);\n else if (!ok) collector.relayMap.delete(event.id);\n }),\n );\n}\n\n/** Build the final query outcome from a settled collector. */\nfunction buildCollectResult(\n collector: Collector,\n timedOut: boolean,\n): { events: NostrEvent[]; relayMap: Record<string, string[]>; incomplete: boolean } {\n const events = [...collector.seen.values()];\n const relayObj: Record<string, string[]> = {};\n for (const event of events) relayObj[event.id] = [...(collector.relayMap.get(event.id) ?? [])];\n return { events, relayMap: relayObj, incomplete: timedOut };\n}\n\n/**\n * Fan a one-shot query out across `relayUrls` (one subscription per relay so\n * events can be attributed to their source relay), dedup by id, validate\n * signatures, and finalize on all-EOSE or timeout.\n */\nfunction collectFromRelays(\n ctx: RouterCtx,\n filters: NostrFilter[],\n relayUrls: string[],\n timeoutMs: number,\n): Promise<{ events: NostrEvent[]; relayMap: Record<string, string[]>; incomplete: boolean }> {\n return new Promise((resolve) => {\n const collector: Collector = { seen: new Map(), relayMap: new Map(), verifications: [] };\n const handles: { unsubscribe(): void }[] = [];\n let eoseCount = 0;\n let finished = false;\n let timedOut = false;\n\n function finalize(): void {\n if (finished) return;\n finished = true;\n clearTimeout(timer);\n for (const handle of handles) {\n try { handle.unsubscribe(); } catch { /* best-effort */ }\n }\n void Promise.all(collector.verifications).then(() => resolve(buildCollectResult(collector, timedOut)));\n }\n\n const timer = setTimeout(() => { timedOut = true; finalize(); }, timeoutMs);\n\n for (const relayUrl of relayUrls) {\n handles.push(relayPoolSubscribe(ctx, filters, relayUrl, (item) => {\n if (finished) return;\n if (item === 'EOSE') {\n eoseCount += 1;\n if (eoseCount >= relayUrls.length) finalize();\n return;\n }\n recordRelay(collector, item.id, relayUrl);\n admitEvent(ctx, collector, item);\n }));\n }\n\n if (relayUrls.length === 0) finalize();\n });\n}\n\n/** Thin wrapper so callers read as a single-relay subscribe. */\nfunction relayPoolSubscribe(\n ctx: RouterCtx,\n filters: NostrFilter[],\n relayUrl: string,\n cb: (item: NostrEvent | 'EOSE') => void,\n): { unsubscribe(): void } {\n return ctx.relayPool.subscribe(filters, [relayUrl], cb);\n}\n\nasync function queryImpl(ctx: RouterCtx, filters: NostrFilter[], options?: OutboxQueryOptions): Promise<OutboxResult> {\n if (!ctx.relayPool.isAvailable()) {\n return { events: [], relays: {}, incomplete: true, error: 'relay list unavailable' };\n }\n const strategy = options?.strategy ?? 'auto';\n const authors = deriveAuthors(filters, options?.authors);\n const plan = await resolvePlan(ctx, authors, 'read', strategy, options?.relays);\n if (plan.relays.length === 0) {\n return { events: [], relays: {}, incomplete: true, error: 'relay list unavailable' };\n }\n\n const timeoutMs = options?.timeoutMs ?? ctx.defaultTimeoutMs;\n const collected = await collectFromRelays(ctx, filters, plan.relays, timeoutMs);\n\n const incomplete = collected.incomplete || (plan.missingAuthors?.length ?? 0) > 0;\n let events = collected.events;\n if (options?.limit !== undefined && events.length > options.limit) {\n events = [...events].sort((a, b) => b.created_at - a.created_at).slice(0, options.limit);\n }\n const result: OutboxResult = { events, relays: collected.relayMap };\n if (incomplete) result.incomplete = true;\n return result;\n}\n\n/** Tracks a live/one-shot subscription across its per-relay fan-out. */\ninterface LiveSub {\n handles: { unsubscribe(): void }[];\n seen: Set<string>;\n closed: boolean;\n eoseCount: number;\n relayCount: number;\n eoseSent: boolean;\n}\n\nfunction closeLiveSub(sub: LiveSub): void {\n for (const handle of sub.handles) {\n try { handle.unsubscribe(); } catch { /* best-effort */ }\n }\n sub.handles.length = 0;\n}\n\n/** Wire one relay's subscription into a live subscription's event/eose flow. */\nfunction attachLiveRelay(\n ctx: RouterCtx,\n sub: LiveSub,\n filters: NostrFilter[],\n relayUrl: string,\n live: boolean,\n sink: OutboxSubscriptionSink,\n): void {\n sub.handles.push(relayPoolSubscribe(ctx, filters, relayUrl, (item) => {\n if (sub.closed) return;\n if (item === 'EOSE') {\n sub.eoseCount += 1;\n if (!sub.eoseSent && sub.eoseCount >= sub.relayCount) {\n sub.eoseSent = true;\n sink.eose();\n if (!live) { sub.closed = true; closeLiveSub(sub); sink.closed(); }\n }\n return;\n }\n if (sub.seen.has(item.id)) return;\n void ctx.verify(item).then((ok) => {\n if (!ok || sub.closed || sub.seen.has(item.id)) return;\n sub.seen.add(item.id);\n sink.event(item, relayUrl);\n });\n }));\n}\n\nfunction startSubscription(\n ctx: RouterCtx,\n filters: NostrFilter[],\n options: OutboxSubscribeOptions | undefined,\n sink: OutboxSubscriptionSink,\n): OutboxRouterSubscription {\n const live = options?.live ?? true;\n const strategy = options?.strategy ?? 'auto';\n const authors = deriveAuthors(filters, options?.authors);\n const sub: LiveSub = { handles: [], seen: new Set(), closed: false, eoseCount: 0, relayCount: 0, eoseSent: false };\n\n void resolvePlan(ctx, authors, 'read', strategy, options?.relays)\n .then((plan) => {\n if (sub.closed) return;\n if (plan.relays.length === 0) { sink.closed('relay list unavailable'); return; }\n sub.relayCount = plan.relays.length;\n for (const relayUrl of plan.relays) attachLiveRelay(ctx, sub, filters, relayUrl, live, sink);\n })\n .catch((err) => {\n if (!sub.closed) sink.closed(err instanceof Error ? err.message : 'subscribe failed');\n });\n\n return {\n close(): void {\n if (sub.closed) return;\n sub.closed = true;\n closeLiveSub(sub);\n },\n };\n}\n\n/** Resolve the full write/inbox/hint relay set a publish should fan out to. */\nasync function resolvePublishTargets(\n ctx: RouterCtx,\n signed: NostrEvent,\n options?: OutboxPublishOptions,\n): Promise<string[]> {\n const strategy = options?.strategy ?? 'auto';\n const targets = new Set<string>();\n\n // The author's own write relays (outbox model for the user's own event).\n const authorPlan = await resolvePlan(ctx, [signed.pubkey], 'read', strategy === 'inbox' ? 'auto' : 'outbox');\n for (const url of authorPlan.relays) targets.add(url);\n\n // Directed events: include recipients' read relays (their inbox).\n if (options?.targetAuthors && options.targetAuthors.length > 0) {\n const inboxPlan = await resolvePlan(ctx, options.targetAuthors, 'write', 'inbox');\n for (const url of inboxPlan.relays) targets.add(url);\n }\n\n for (const url of options?.relays ?? []) targets.add(url);\n return allowed(ctx, targets);\n}\n\nasync function publishImpl(ctx: RouterCtx, template: EventTemplate, options?: OutboxPublishOptions): Promise<OutboxPublishResult> {\n if (!ctx.signEvent) return { ok: false, error: 'publish denied' };\n if (!ctx.relayPool.isAvailable()) return { ok: false, error: 'relay list unavailable' };\n\n let signed: NostrEvent;\n try {\n signed = await ctx.signEvent(template);\n } catch (err) {\n return { ok: false, error: err instanceof Error ? err.message : 'sign failed' };\n }\n\n const relayUrls = await resolvePublishTargets(ctx, signed, options);\n if (relayUrls.length === 0) return { ok: false, event: signed, eventId: signed.id, error: 'relay list unavailable' };\n\n let relays: Record<string, boolean>;\n try {\n relays = normalizePublishResult(await ctx.relayPool.publish(signed, relayUrls), relayUrls);\n } catch (err) {\n return { ok: false, event: signed, eventId: signed.id, error: err instanceof Error ? err.message : 'publish failed' };\n }\n\n const ok = Object.values(relays).some(Boolean);\n const result: OutboxPublishResult = { ok, event: signed, eventId: signed.id, relays };\n if (!ok) result.error = 'publish denied';\n return result;\n}\n\n/**\n * Create a relay-pool-backed {@link OutboxRouter}.\n *\n * @param options - Relay pool, NIP-65 loader, fallback relays, and optional\n * signer / verifier / relay gate / timeout.\n * @returns An {@link OutboxRouter} for {@link createOutboxService}.\n * @throws If `relayPool`, `loadRelayLists`, or `fallbackRelays` are missing.\n */\nexport function createRelayPoolOutboxRouter(options: RelayPoolOutboxRouterOptions): OutboxRouter {\n if (!options || typeof options.relayPool !== 'object' || options.relayPool === null) {\n throw new Error('createRelayPoolOutboxRouter: options.relayPool is required');\n }\n if (typeof options.loadRelayLists !== 'function') {\n throw new Error('createRelayPoolOutboxRouter: options.loadRelayLists is required');\n }\n if (!Array.isArray(options.fallbackRelays)) {\n throw new Error('createRelayPoolOutboxRouter: options.fallbackRelays is required');\n }\n\n const verifyEvent = options.verifyEvent;\n const ctx: RouterCtx = {\n relayPool: options.relayPool,\n loadRelayLists: options.loadRelayLists,\n fallbackRelays: options.fallbackRelays,\n signEvent: options.signEvent,\n isRelayAllowed: options.isRelayAllowed ?? defaultRelayAllowed,\n defaultTimeoutMs: options.defaultTimeoutMs ?? DEFAULT_QUERY_TIMEOUT_MS,\n async verify(event: NostrEvent): Promise<boolean> {\n if (!verifyEvent) return true;\n try {\n return await verifyEvent(event);\n } catch {\n return false;\n }\n },\n };\n\n return {\n query: (filters, queryOptions) => queryImpl(ctx, filters, queryOptions),\n subscribe: (filters, subscribeOptions, sink) => startSubscription(ctx, filters, subscribeOptions, sink),\n publish: (template, publishOptions) => publishImpl(ctx, template, publishOptions),\n resolveRelays: (target: OutboxTarget) => {\n const pubkeys = target.authors ?? (target.pubkey ? [target.pubkey] : []);\n return resolvePlan(ctx, pubkeys, target.direction ?? 'read', target.strategy ?? 'auto');\n },\n };\n}\n\n/** Normalize a pool publish return into a per-relay success map. */\nfunction normalizePublishResult(\n res: Record<string, boolean> | void,\n relayUrls: string[],\n): Record<string, boolean> {\n const out: Record<string, boolean> = {};\n if (res && typeof res === 'object') {\n for (const url of relayUrls) out[url] = res[url] ?? false;\n } else {\n // void return → optimistic success on every targeted relay.\n for (const url of relayUrls) out[url] = true;\n }\n return out;\n}\n","/**\n * upload-service.ts — NAP-UPLOAD (shell-mediated file/blob upload) reference service.\n *\n * Shell-side handler for the NAP-UPLOAD wire protocol. It is a pure envelope\n * router: it validates `upload.*` envelopes, delegates the actual byte transfer\n * (server selection, rail authorization signing, the HTTP upload) to an injected\n * {@link Uploader}, and posts the correlated result / status messages back to the\n * napplet.\n *\n * The uploader is injected (options-as-bridge) so this service has no transport\n * or Nostr dependency and is fully unit-testable. NAP-UPLOAD is deliberately\n * abstract over the backend — the runtime decides *how* it uploads (NIP-96,\n * Blossom, …). A concrete HTTP-backed uploader ships alongside as\n * {@link createHttpUploader}.\n *\n * ──────────────────────────── Responsibilities ────────────────────────────\n * Inbound: upload.upload, upload.status\n * Outbound: upload.upload.result, upload.status.result, upload.status.changed\n *\n * The service owns the `uploadId` (generated per request, scoped to the\n * requesting napplet), tracks the latest {@link UploadStatus} per upload for\n * `upload.status` queries, and cleans up on window teardown. The shell owns\n * consent, policy, server selection, signing, and the HTTP upload — all behind\n * the {@link Uploader}.\n *\n * @example\n * ```ts\n * import { createUploadService, createHttpUploader } from '@kehto/services';\n *\n * const uploader = createHttpUploader({ rails: { nip96: { servers } }, signEvent });\n * runtime.registerService('upload', createUploadService({ uploader }));\n * ```\n *\n * @packageDocumentation\n */\n\nimport type { NappletMessage } from '@napplet/core';\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\n\n/** Upload service version — follows semver. */\nconst UPLOAD_SERVICE_VERSION = '1.0.0';\n\n/**\n * Storage rail. `nip96` (NIP-96 HTTP file storage) and `blossom` (Blossom blob\n * storage) are the first concrete backends; the open string keeps the API\n * stable as shells add rails (torrents, usenet, …).\n */\nexport type UploadRail = 'nip96' | 'blossom' | (string & {});\n\n/** Lifecycle state of an upload. */\nexport type UploadState = 'pending' | 'uploading' | 'complete' | 'failed' | 'cancelled';\n\n/** Pixel dimensions of an uploaded image/video. */\nexport interface UploadDimensions {\n width: number;\n height: number;\n}\n\n/**\n * A napplet's upload request. `data` crosses the postMessage boundary by\n * structured clone — shells never require base64 encoding.\n */\nexport interface UploadRequest {\n /** Storage rail; omit to let the shell pick a configured default. */\n rail?: UploadRail;\n /** The bytes to upload. */\n data: ArrayBuffer | Blob;\n /** MIME type; inferred from `data` when omitted. */\n mimeType?: string;\n /** Suggested filename. */\n filename?: string;\n /** Alt text / description for the file event. */\n caption?: string;\n /** Request the server not re-encode the file (NIP-96 `no_transform`). */\n noTransform?: boolean;\n /** Rail-specific or shell-specific extra metadata. */\n metadata?: Record<string, unknown>;\n}\n\n/** A single Nostr tag (NIP-94 / imeta entries are arrays of strings). */\nexport type NostrTag = string[];\n\n/** The result of an upload. */\nexport interface UploadResult {\n /** Whether the upload succeeded (or is progressing) vs failed/cancelled. */\n ok: boolean;\n /** Shell-generated id, scoped to the requesting napplet. */\n uploadId: string;\n /** Current lifecycle state. */\n status: UploadState;\n /** The rail the shell used. */\n rail: UploadRail;\n /** Primary download URL. */\n url?: string;\n /** Mirrors / alternative server URLs. */\n fallbackUrls?: string[];\n /** Hash of the stored blob (NIP-94 `x`). */\n sha256?: string;\n /** Hash before server transforms (NIP-94 `ox`). */\n originalSha256?: string;\n /** Size in bytes. */\n size?: number;\n /** Stored MIME type. */\n mimeType?: string;\n /** Image/video dimensions when known. */\n dimensions?: UploadDimensions;\n /** Blurhash placeholder when known. */\n blurhash?: string;\n /** Ready-to-attach NIP-94 / imeta tags. */\n nip94?: NostrTag[];\n /** Error reason when the upload failed or was cancelled. */\n error?: string;\n}\n\n/** A status snapshot for an upload, including progress counters. */\nexport interface UploadStatus extends UploadResult {\n /** Bytes sent so far (while uploading). */\n bytesSent?: number;\n /** Total bytes to send. */\n bytesTotal?: number;\n /** Unix ms timestamp of this status. */\n updatedAt: number;\n}\n\n/**\n * Context handed to an {@link Uploader} for a single upload. Carries the\n * service-owned `uploadId` and a sink for streaming progress / state changes.\n */\nexport interface UploaderContext {\n /** The service-generated upload id (authoritative; scoped to the napplet). */\n uploadId: string;\n /** The napplet window that requested the upload. */\n windowId: string;\n /**\n * Push a status update (progress, or a transition to complete/failed). The\n * service stamps `uploadId` and `updatedAt` before forwarding to the napplet\n * as `upload.status.changed`, and records it as the latest tracked status.\n */\n onStatus(status: UploadStatus): void;\n}\n\n/**\n * Abstract upload backend. Implementors own server selection, rail\n * authorization signing (NIP-98 for NIP-96, kind 24242 for Blossom), the HTTP\n * upload, and integrity-hash reporting. The service translates wire envelopes\n * into these calls and back. A concrete reference implementation ships as\n * {@link createHttpUploader}.\n */\nexport interface Uploader {\n /** Upload `request.data`, streaming progress through `ctx.onStatus`. */\n upload(request: UploadRequest, ctx: UploaderContext): Promise<UploadResult>;\n /** Optional: resolve the latest status for an upload the service is not tracking. */\n status?(uploadId: string): Promise<UploadStatus | undefined>;\n /** Optional: abort an in-flight upload (called on window teardown). */\n cancel?(uploadId: string): void;\n}\n\n/** Options for {@link createUploadService}. */\nexport interface UploadServiceOptions {\n /** The upload backend the shell uses. Required. */\n uploader: Uploader;\n /** Generate an upload id; defaults to `crypto.randomUUID()`. */\n generateId?: () => string;\n /** Current time in unix ms; defaults to `Date.now()`. */\n now?: () => number;\n}\n\ntype Send = (msg: NappletMessage) => void;\n\nconst UPLOAD_DESCRIPTOR: ServiceDescriptor = {\n name: 'upload',\n version: UPLOAD_SERVICE_VERSION,\n description: 'NAP-UPLOAD shell-mediated file/blob upload — upload/status with progress pushes',\n};\n\n/** Per-upload tracking entry, keyed by `windowId:uploadId`. */\ninterface UploadEntry {\n uploadId: string;\n status?: UploadStatus;\n}\n\ndeclare const crypto: { randomUUID(): string };\n\n/**\n * Create the NAP-UPLOAD service handler.\n *\n * @param options - Must provide an {@link Uploader}.\n * @returns A `ServiceHandler` ready for `runtime.registerService('upload', handler)`.\n * @throws If `options.uploader` is missing.\n */\nexport function createUploadService(options: UploadServiceOptions): ServiceHandler {\n if (!options || typeof options.uploader !== 'object' || options.uploader === null) {\n throw new Error('createUploadService: options.uploader is required');\n }\n const { uploader } = options;\n const generateId = options.generateId ?? (() => crypto.randomUUID());\n const now = options.now ?? (() => Date.now());\n\n // Tracked uploads keyed by `windowId:uploadId` for status lookup + cleanup.\n const entries = new Map<string, UploadEntry>();\n\n function handleUpload(windowId: string, msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { id?: string; request?: UploadRequest };\n const id = m.id ?? '';\n const request = m.request;\n if (!request || typeof request !== 'object' || request.data == null) {\n send({ type: 'upload.upload.result', id, error: 'invalid request' } as NappletMessage);\n return;\n }\n\n const uploadId = generateId();\n const key = `${windowId}:${uploadId}`;\n entries.set(key, { uploadId });\n\n const ctx: UploaderContext = {\n uploadId,\n windowId,\n onStatus: (status) => {\n const stamped: UploadStatus = { ...status, uploadId, updatedAt: status.updatedAt || now() };\n const entry = entries.get(key);\n if (entry) entry.status = stamped;\n send({ type: 'upload.status.changed', status: stamped } as NappletMessage);\n },\n };\n\n void uploader\n .upload(request, ctx)\n .then((result) => {\n const stamped: UploadResult = { ...result, uploadId };\n const entry = entries.get(key);\n if (entry) entry.status = { ...stamped, updatedAt: now() };\n send({ type: 'upload.upload.result', id, result: stamped } as NappletMessage);\n })\n .catch((err) => {\n entries.delete(key);\n send({ type: 'upload.upload.result', id, error: toErrorMessage(err) } as NappletMessage);\n });\n }\n\n function handleStatus(windowId: string, msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { id?: string; uploadId?: string };\n const id = m.id ?? '';\n const uploadId = m.uploadId;\n if (typeof uploadId !== 'string' || uploadId.length === 0) {\n send({ type: 'upload.status.result', id, error: 'invalid uploadId' } as NappletMessage);\n return;\n }\n\n const tracked = entries.get(`${windowId}:${uploadId}`)?.status;\n if (tracked) {\n send({ type: 'upload.status.result', id, status: tracked } as NappletMessage);\n return;\n }\n\n if (uploader.status) {\n void uploader\n .status(uploadId)\n .then((status) =>\n send(\n status\n ? ({ type: 'upload.status.result', id, status } as NappletMessage)\n : ({ type: 'upload.status.result', id, error: 'unknown upload' } as NappletMessage),\n ),\n )\n .catch((err) =>\n send({ type: 'upload.status.result', id, error: toErrorMessage(err) } as NappletMessage),\n );\n return;\n }\n\n send({ type: 'upload.status.result', id, error: 'unknown upload' } as NappletMessage);\n }\n\n return {\n descriptor: UPLOAD_DESCRIPTOR,\n handleMessage(windowId: string, message: NappletMessage, send: Send): void {\n switch (message.type) {\n case 'upload.upload':\n handleUpload(windowId, message, send);\n return;\n case 'upload.status':\n handleStatus(windowId, message, send);\n return;\n default:\n // Unknown upload.* action — silently ignored (forward-compatible).\n return;\n }\n },\n onWindowDestroyed(windowId: string): void {\n const prefix = `${windowId}:`;\n for (const [key, entry] of entries) {\n if (key.startsWith(prefix)) {\n uploader.cancel?.(entry.uploadId);\n entries.delete(key);\n }\n }\n },\n };\n}\n\nfunction toErrorMessage(err: unknown): string {\n if (err instanceof Error) return err.message;\n if (typeof err === 'string') return err;\n return 'upload request failed';\n}\n","/**\n * http-uploader.ts — NAP-UPLOAD concrete HTTP-backed {@link Uploader}.\n *\n * The reference upload backend for {@link createUploadService}. Implements two\n * storage rails over HTTP:\n *\n * - **NIP-96** — signs a NIP-98 (kind 27235) HTTP-auth event, POSTs the file as\n * `multipart/form-data`, and maps the returned NIP-94 event tags into an\n * {@link UploadResult}.\n * - **Blossom** — signs a kind 24242 authorization event, PUTs the raw bytes to\n * `<server>/upload`, and maps the returned blob descriptor.\n *\n * Signing (`signEvent`) and transport (`fetch`) are injected so the uploader\n * carries no Nostr or network dependency and is fully unit-testable. The shell\n * holds the signing key and never exposes it to napplets — the uploader only\n * receives a signing callback. Server URLs are shell configuration, not napplet\n * input: a napplet may *hint* a rail, but never a server.\n *\n * The configured server URL is used directly as the upload endpoint (the\n * NIP-96 `api_url` / Blossom base). Hosts that need `.well-known` discovery can\n * resolve it before constructing the uploader.\n *\n * @example\n * ```ts\n * const uploader = createHttpUploader({\n * rails: { nip96: { servers: ['https://nostr.build/api/v2/nip96/upload'] } },\n * signEvent: (tmpl) => signer.signEvent(tmpl),\n * });\n * runtime.registerService('upload', createUploadService({ uploader }));\n * ```\n *\n * @packageDocumentation\n */\n\nimport type { EventTemplate, NostrEvent } from '@napplet/core';\nimport type {\n NostrTag,\n UploadDimensions,\n UploadRail,\n UploadRequest,\n UploadResult,\n Uploader,\n UploaderContext,\n} from './upload-service.js';\n\n/** NIP-98 HTTP-auth event kind. */\nconst KIND_NIP98 = 27235;\n/** Blossom authorization event kind. */\nconst KIND_BLOSSOM_AUTH = 24242;\n/** Blossom auth-event lifetime, in seconds. */\nconst BLOSSOM_AUTH_TTL_S = 3600;\n\n/** Per-rail server configuration. The first server is the primary endpoint. */\nexport interface RailServerConfig {\n /** Ordered server endpoint URLs; index 0 is primary. */\n servers: string[];\n}\n\n/** Storage rails this uploader can serve. */\nexport interface HttpUploaderRails {\n /** NIP-96 HTTP file storage. */\n nip96?: RailServerConfig;\n /** Blossom blob storage. */\n blossom?: RailServerConfig;\n}\n\n/** Signs an event template on the user's behalf (shell holds the key). */\nexport type SignEvent = (template: EventTemplate) => Promise<NostrEvent>;\n\n/** Options for {@link createHttpUploader}. */\nexport interface HttpUploaderOptions {\n /** Configured rails + their servers. */\n rails: HttpUploaderRails;\n /** Rail to use when a request omits one; defaults to the first configured rail. */\n defaultRail?: UploadRail;\n /** Signs NIP-98 / Blossom auth events. Required. */\n signEvent: SignEvent;\n /** Fetch implementation; defaults to the global `fetch`. */\n fetch?: typeof fetch;\n /** Hex SHA-256 of the payload bytes; defaults to Web Crypto. */\n digestSha256?: (bytes: Uint8Array) => Promise<string>;\n /** Unix *seconds* clock for event timestamps; defaults to `Date.now()/1000`. */\n now?: () => number;\n}\n\ndeclare const btoa: (data: string) => string;\ndeclare const crypto: { subtle: { digest(alg: string, data: ArrayBuffer): Promise<ArrayBuffer> } };\n\n/**\n * Create the reference HTTP {@link Uploader} (NIP-96 + Blossom rails).\n *\n * @param options - Rails, server config, and the injected `signEvent`.\n * @returns An {@link Uploader} for `createUploadService({ uploader })`.\n * @throws If `options.signEvent` is missing.\n */\nexport function createHttpUploader(options: HttpUploaderOptions): Uploader {\n if (!options || typeof options.signEvent !== 'function') {\n throw new Error('createHttpUploader: options.signEvent is required');\n }\n const rails = options.rails ?? {};\n const signEvent = options.signEvent;\n const fetchFn = options.fetch ?? fetch;\n const digest = options.digestSha256 ?? defaultDigestSha256;\n const nowS = options.now ?? (() => Math.floor(Date.now() / 1000));\n const defaultRail = options.defaultRail ?? firstConfiguredRail(rails);\n\n async function upload(request: UploadRequest, ctx: UploaderContext): Promise<UploadResult> {\n const rail = request.rail ?? defaultRail;\n const config = rail === 'nip96' ? rails.nip96 : rail === 'blossom' ? rails.blossom : undefined;\n if (rail !== 'nip96' && rail !== 'blossom') {\n return failed(ctx.uploadId, rail ?? 'unknown', 'unsupported rail');\n }\n\n const server = config?.servers?.[0];\n if (!server) {\n return failed(ctx.uploadId, rail, 'no server configured');\n }\n\n const bytes = await toBytes(request.data);\n const sha256 = await digest(bytes);\n\n try {\n return rail === 'nip96'\n ? await uploadNip96({ request, ctx, server, bytes, sha256, signEvent, fetchFn, nowS })\n : await uploadBlossom({ request, ctx, server, bytes, sha256, signEvent, fetchFn, nowS });\n } catch (err) {\n return failed(ctx.uploadId, rail, toErrorMessage(err), sha256);\n }\n }\n\n return { upload };\n}\n\ninterface RailUploadArgs {\n request: UploadRequest;\n ctx: UploaderContext;\n server: string;\n bytes: Uint8Array;\n sha256: string;\n signEvent: SignEvent;\n fetchFn: typeof fetch;\n nowS: () => number;\n}\n\nasync function uploadNip96(args: RailUploadArgs): Promise<UploadResult> {\n const { request, ctx, server, bytes, sha256, signEvent, fetchFn, nowS } = args;\n\n const auth = await signEvent({\n kind: KIND_NIP98,\n created_at: nowS(),\n content: '',\n tags: [\n ['u', server],\n ['method', 'POST'],\n ['payload', sha256],\n ],\n });\n\n const form = new FormData();\n form.append('file', new Blob([bytesToArrayBuffer(bytes)], { type: request.mimeType }), request.filename ?? 'file');\n if (request.caption !== undefined) form.append('caption', request.caption);\n if (request.mimeType !== undefined) form.append('content_type', request.mimeType);\n if (request.noTransform) form.append('no_transform', 'true');\n\n const res = await fetchFn(server, {\n method: 'POST',\n headers: { Authorization: nostrAuthHeader(auth) },\n body: form,\n });\n\n if (!res.ok) {\n return failed(ctx.uploadId, 'nip96', `server rejected (HTTP ${res.status})`, sha256);\n }\n\n const body = (await res.json()) as Nip96Response;\n if (body.status === 'error') {\n return failed(ctx.uploadId, 'nip96', body.message ?? 'upload failed', sha256);\n }\n\n const tags = body.nip94_event?.tags ?? [];\n return fromNip94Tags(ctx.uploadId, 'nip96', tags, bytes.byteLength, sha256);\n}\n\ninterface Nip96Response {\n status?: 'success' | 'error' | string;\n message?: string;\n nip94_event?: { tags?: string[][] };\n}\n\n/** Map NIP-94 / imeta tags into an {@link UploadResult}. */\nfunction fromNip94Tags(\n uploadId: string,\n rail: UploadRail,\n tags: string[][],\n fallbackSize: number,\n fallbackSha: string,\n): UploadResult {\n const get = (name: string): string | undefined => tags.find((t) => t[0] === name)?.[1];\n const url = get('url');\n const result: UploadResult = {\n ok: Boolean(url),\n uploadId,\n status: url ? 'complete' : 'failed',\n rail,\n sha256: get('x') ?? fallbackSha,\n nip94: tags as NostrTag[],\n };\n if (url) result.url = url;\n const ox = get('ox');\n if (ox) result.originalSha256 = ox;\n const size = get('size');\n result.size = size ? Number(size) : fallbackSize;\n const m = get('m');\n if (m) result.mimeType = m;\n const dim = parseDimensions(get('dim'));\n if (dim) result.dimensions = dim;\n const blurhash = get('blurhash');\n if (blurhash) result.blurhash = blurhash;\n if (!url) result.error = 'server returned no url';\n return result;\n}\n\nasync function uploadBlossom(args: RailUploadArgs): Promise<UploadResult> {\n const { request, ctx, server, bytes, sha256, signEvent, fetchFn, nowS } = args;\n\n const auth = await signEvent({\n kind: KIND_BLOSSOM_AUTH,\n created_at: nowS(),\n content: `Upload ${request.filename ?? 'file'}`,\n tags: [\n ['t', 'upload'],\n ['x', sha256],\n ['expiration', String(nowS() + BLOSSOM_AUTH_TTL_S)],\n ],\n });\n\n const endpoint = `${trimTrailingSlash(server)}/upload`;\n const headers: Record<string, string> = { Authorization: nostrAuthHeader(auth) };\n if (request.mimeType) headers['Content-Type'] = request.mimeType;\n\n const res = await fetchFn(endpoint, {\n method: 'PUT',\n headers,\n body: bytesToArrayBuffer(bytes),\n });\n\n if (!res.ok) {\n return failed(ctx.uploadId, 'blossom', `server rejected (HTTP ${res.status})`, sha256);\n }\n\n const blob = (await res.json()) as BlossomDescriptor;\n if (!blob.url) {\n return failed(ctx.uploadId, 'blossom', 'server returned no url', sha256);\n }\n\n const result: UploadResult = {\n ok: true,\n uploadId: ctx.uploadId,\n status: 'complete',\n rail: 'blossom',\n url: blob.url,\n sha256: blob.sha256 ?? sha256,\n size: blob.size ?? bytes.byteLength,\n };\n if (blob.type) result.mimeType = blob.type;\n return result;\n}\n\ninterface BlossomDescriptor {\n url?: string;\n sha256?: string;\n size?: number;\n type?: string;\n}\n\nfunction failed(uploadId: string, rail: UploadRail, error: string, sha256?: string): UploadResult {\n return { ok: false, uploadId, status: 'failed', rail, error, ...(sha256 ? { sha256 } : {}) };\n}\n\nfunction firstConfiguredRail(rails: HttpUploaderRails): UploadRail | undefined {\n if (rails.nip96?.servers?.length) return 'nip96';\n if (rails.blossom?.servers?.length) return 'blossom';\n return undefined;\n}\n\nfunction nostrAuthHeader(event: NostrEvent): string {\n return `Nostr ${base64Utf8(JSON.stringify(event))}`;\n}\n\nfunction base64Utf8(s: string): string {\n // UTF-8 safe base64 (event content/tags may contain non-ASCII).\n return btoa(String.fromCharCode(...new TextEncoder().encode(s)));\n}\n\nfunction parseDimensions(dim: string | undefined): UploadDimensions | undefined {\n if (!dim) return undefined;\n const m = /^(\\d+)x(\\d+)$/.exec(dim);\n if (!m) return undefined;\n return { width: Number(m[1]), height: Number(m[2]) };\n}\n\nasync function toBytes(data: ArrayBuffer | Blob): Promise<Uint8Array> {\n if (data instanceof ArrayBuffer) return new Uint8Array(data);\n return new Uint8Array(await data.arrayBuffer());\n}\n\nfunction bytesToArrayBuffer(bytes: Uint8Array): ArrayBuffer {\n return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer;\n}\n\nfunction trimTrailingSlash(url: string): string {\n return url.endsWith('/') ? url.slice(0, -1) : url;\n}\n\nasync function defaultDigestSha256(bytes: Uint8Array): Promise<string> {\n const buf = await crypto.subtle.digest('SHA-256', bytesToArrayBuffer(bytes));\n return Array.from(new Uint8Array(buf))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n}\n\nfunction toErrorMessage(err: unknown): string {\n if (err instanceof Error) return err.message;\n if (typeof err === 'string') return err;\n return 'upload failed';\n}\n","/**\n * intent-service.ts — NAP-INTENT (archetype intent dispatch) reference service.\n *\n * Shell-side handler for the NAP-INTENT wire protocol. It is a pure envelope\n * router: it validates `intent.*` envelopes, delegates archetype resolution,\n * default handling, window lifecycle, and payload delivery to an injected\n * {@link IntentResolver}, and posts the correlated result / push messages\n * (echoing the request `id`) back to the napplet.\n *\n * The resolver is injected (options-as-bridge) so this service has no shell or\n * window-manager dependency and is fully unit-testable. A concrete\n * catalog-backed resolver ships alongside as {@link createCatalogIntentResolver}.\n *\n * ──────────────────────────── Responsibilities ────────────────────────────\n * Inbound: intent.invoke, intent.available, intent.handlers\n * Outbound: intent.invoke.result, intent.available.result,\n * intent.handlers.result, intent.changed\n *\n * The shell owns archetype→handler resolution, the user's default-handler\n * preference, the \"open with…\" chooser, and window creation/focus — all behind\n * the {@link IntentResolver}. This service only marshals the wire protocol and\n * fans `intent.changed` pushes out to the napplets it has served.\n *\n * @example\n * ```ts\n * import { createIntentService, createCatalogIntentResolver } from '@kehto/services';\n *\n * const resolver = createCatalogIntentResolver({ loadCatalog, windows });\n * runtime.registerService('intent', createIntentService({ resolver }));\n * ```\n *\n * @packageDocumentation\n */\n\nimport type { NappletMessage } from '@napplet/core';\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\nimport type { IntentAvailability, IntentRequest, IntentResult } from './intent-types.js';\n\n/** Intent service version — follows semver. */\nconst INTENT_SERVICE_VERSION = '1.0.0';\n\n/** Context passed to {@link IntentResolver.invoke} for trust/attribution. */\nexport interface IntentResolverContext {\n /** Window id of the napplet that issued the request. */\n windowId: string;\n}\n\n/**\n * Abstract intent resolver. Implementors own the installed-napplet catalog,\n * archetype→handler resolution, the user's default-handler preference, the\n * \"open with…\" chooser, window creation/focus, and payload delivery. The\n * service translates wire envelopes into these calls and back.\n */\nexport interface IntentResolver {\n /**\n * Resolve `request.archetype` to a handler, create or focus its window, and\n * deliver `payload`. Returns once the handler is resolved and the window\n * created; delivery MAY complete asynchronously.\n */\n invoke(request: IntentRequest, context: IntentResolverContext): IntentResult | Promise<IntentResult>;\n /** Report whether the runtime can currently satisfy `archetype`, and how. */\n available(archetype: string): IntentAvailability | Promise<IntentAvailability>;\n /** Report availability for every archetype the runtime can currently satisfy. */\n handlers(): IntentAvailability[] | Promise<IntentAvailability[]>;\n /**\n * Register for availability changes (a napplet installed/removed, or a default\n * handler changed). The service forwards each change to served napplets as an\n * `intent.changed` push. Returns an unsubscribe handle. Resolvers whose\n * catalog never changes at runtime MAY omit this.\n */\n onChanged?(listener: (availability: IntentAvailability) => void): () => void;\n}\n\n/** Options for {@link createIntentService}. */\nexport interface IntentServiceOptions {\n /** The intent resolver the shell uses to route archetypes. Required. */\n resolver: IntentResolver;\n}\n\ntype Send = (msg: NappletMessage) => void;\n\nconst INTENT_DESCRIPTOR: ServiceDescriptor = {\n name: 'intent',\n version: INTENT_SERVICE_VERSION,\n description: 'NAP-INTENT archetype intent dispatch — invoke/available/handlers',\n};\n\nfunction toErrorMessage(err: unknown): string {\n if (err instanceof Error) return err.message;\n if (typeof err === 'string') return err;\n return 'intent request failed';\n}\n\n/**\n * Create the NAP-INTENT service handler.\n *\n * @param options - Must provide an {@link IntentResolver}.\n * @returns A `ServiceHandler` ready for `runtime.registerService('intent', handler)`.\n * @throws If `options.resolver` is missing.\n */\nexport function createIntentService(options: IntentServiceOptions): ServiceHandler {\n if (!options || typeof options.resolver !== 'object' || options.resolver === null) {\n throw new Error('createIntentService: options.resolver is required');\n }\n const { resolver } = options;\n\n // Latest send callback per served window, used to fan `intent.changed`\n // pushes out to every napplet that has interacted with the intent domain.\n const windows = new Map<string, Send>();\n\n // Subscribe once for availability changes and broadcast them as pushes.\n resolver.onChanged?.((availability) => {\n for (const send of windows.values()) {\n send({ type: 'intent.changed', availability } as NappletMessage);\n }\n });\n\n /**\n * Run a resolver call and marshal its outcome onto `resultType`. The resolver\n * method may be sync or async — `settle` normalizes it to a promise and\n * catches a synchronous throw (e.g. a sync resolver that rejects an input)\n * the same way it catches an async rejection.\n */\n function settle<T>(\n call: () => T | Promise<T>,\n send: Send,\n resultType: string,\n id: string,\n onValue: (value: T) => NappletMessage,\n ): void {\n let pending: Promise<T>;\n try {\n pending = Promise.resolve(call());\n } catch (err) {\n send({ type: resultType, id, error: toErrorMessage(err) } as NappletMessage);\n return;\n }\n pending\n .then((value) => send(onValue(value)))\n .catch((err) => send({ type: resultType, id, error: toErrorMessage(err) } as NappletMessage));\n }\n\n function handleInvoke(windowId: string, msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { id?: string; request?: IntentRequest };\n const id = m.id ?? '';\n const request = m.request;\n if (!request || typeof request !== 'object' || typeof request.archetype !== 'string' || request.archetype.length === 0) {\n send({ type: 'intent.invoke.result', id, error: 'invalid request' } as NappletMessage);\n return;\n }\n settle(\n () => resolver.invoke(request, { windowId }),\n send, 'intent.invoke.result', id,\n (result) => ({ type: 'intent.invoke.result', id, result } as NappletMessage),\n );\n }\n\n function handleAvailable(msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { id?: string; archetype?: unknown };\n const id = m.id ?? '';\n if (typeof m.archetype !== 'string' || m.archetype.length === 0) {\n send({ type: 'intent.available.result', id, error: 'invalid archetype' } as NappletMessage);\n return;\n }\n const archetype = m.archetype;\n settle(\n () => resolver.available(archetype),\n send, 'intent.available.result', id,\n (availability) => ({ type: 'intent.available.result', id, availability } as NappletMessage),\n );\n }\n\n function handleHandlers(msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { id?: string };\n const id = m.id ?? '';\n settle(\n () => resolver.handlers(),\n send, 'intent.handlers.result', id,\n (handlers) => ({ type: 'intent.handlers.result', id, handlers } as NappletMessage),\n );\n }\n\n return {\n descriptor: INTENT_DESCRIPTOR,\n handleMessage(windowId: string, message: NappletMessage, send: Send): void {\n windows.set(windowId, send);\n switch (message.type) {\n case 'intent.invoke':\n handleInvoke(windowId, message, send);\n return;\n case 'intent.available':\n handleAvailable(message, send);\n return;\n case 'intent.handlers':\n handleHandlers(message, send);\n return;\n default:\n // Unknown intent.* action — silently ignored (forward-compatible).\n return;\n }\n },\n onWindowDestroyed(windowId: string): void {\n windows.delete(windowId);\n },\n };\n}\n","/**\n * catalog-intent-resolver.ts — NAP-INTENT concrete {@link IntentResolver}.\n *\n * A reference resolver that satisfies {@link IntentResolver} from an\n * installed-napplet catalog plus host-supplied policy: the user's\n * default-handler preference, an optional \"open with…\" chooser, the archetype's\n * recommended default protocol, and a window controller that creates or focuses\n * the handler window and delivers the payload.\n *\n * Resolution policy (mirrors an OS implicit-intent + default-app model):\n * 1. Gather candidates for the archetype from the catalog.\n * 2. Pick the handler — an explicit `handler` dTag, a user choice\n * (`handler: \"choose\"`), the user's default, the sole candidate, or a\n * deterministic first-candidate fallback.\n * 3. Validate the resolved handler supports the requested `action` and\n * `protocol` (or fall back to the archetype's recommended default protocol,\n * then the candidate's first accepted protocol).\n * 4. Open or focus the handler window and return its id.\n *\n * The catalog, defaults, chooser, and window controller are all injected, so\n * this resolver carries no shell, manifest, or DOM dependency and is fully\n * unit-testable.\n *\n * @packageDocumentation\n */\n\nimport type {\n IntentAvailability,\n IntentBehavior,\n IntentCandidate,\n IntentRequest,\n IntentResult,\n} from './intent-types.js';\nimport type { IntentResolver, IntentResolverContext } from './intent-service.js';\n\n/** The actions/protocols a napplet fulfills for a single archetype. */\nexport interface IntentArchetypeSupport {\n /** Verbs this napplet supports for the archetype (e.g. `[\"open\", \"edit\"]`). */\n actions: string[];\n /** NAP-N protocol ids this napplet accepts for the archetype. */\n protocols: string[];\n}\n\n/**\n * One installed napplet's intent surface, derived from its signed NIP-5A\n * manifest. Keyed by archetype slug so a single napplet can fulfill several\n * roles.\n */\nexport interface IntentCatalogEntry {\n /** The napplet's dTag. */\n dTag: string;\n /** Human-readable title from the manifest. */\n title?: string;\n /** Archetype slug → the actions/protocols this napplet fulfills for it. */\n archetypes: Record<string, IntentArchetypeSupport>;\n}\n\n/** Parameters handed to {@link IntentWindowController.open}. */\nexport interface IntentOpenParams {\n /** dTag of the resolved handler napplet. */\n dTag: string;\n /** The archetype being dispatched. */\n archetype: string;\n /** The resolved action (e.g. `\"open\"`). */\n action: string;\n /** The wire format the payload is delivered with, when one was resolved. */\n protocol?: string;\n /** The opaque payload to deliver. */\n payload?: unknown;\n /** Window-behavior hints from the request. */\n behavior?: IntentBehavior;\n /** Window id of the napplet that issued the intent. */\n callerWindowId: string;\n}\n\n/** Creates or focuses the handler window and delivers the intent payload. */\nexport interface IntentWindowController {\n /**\n * Create or focus the handler window and deliver `payload` via `protocol`.\n * Returns the window id the handler was created or focused in.\n */\n open(params: IntentOpenParams): { windowId: string } | Promise<{ windowId: string }>;\n}\n\n/** Options for {@link createCatalogIntentResolver}. */\nexport interface CatalogIntentResolverOptions {\n /** Return the installed-napplet catalog (signed NIP-5A manifests). Required. */\n loadCatalog(): IntentCatalogEntry[] | Promise<IntentCatalogEntry[]>;\n /** Window controller used to create/focus the resolved handler's window. Required. */\n windows: IntentWindowController;\n /**\n * The user's default handler dTag for an archetype, or `undefined` when none\n * is set. Default-handler settings are user state — never set by napplets.\n */\n getDefaultHandler?(archetype: string): string | undefined;\n /**\n * Resolve a `handler: \"choose\"` prompt (or a no-default ambiguity) to a dTag.\n * Returning `undefined` means the user cancelled. When omitted, ambiguous\n * resolution falls back to the first candidate.\n */\n chooseHandler?(\n archetype: string,\n candidates: IntentCandidate[],\n callerWindowId: string,\n ): string | undefined | Promise<string | undefined>;\n /** The archetype's recommended default protocol when the caller omits one. */\n defaultProtocol?(archetype: string): string | undefined;\n}\n\n/**\n * A {@link IntentResolver} backed by a catalog, with a host hook to announce\n * catalog/default changes.\n */\nexport interface CatalogIntentResolver extends IntentResolver {\n /**\n * Announce that the catalog or default handler for `archetype` changed. The\n * resolver recomputes availability and notifies `onChanged` listeners, which\n * the intent service forwards to napplets as `intent.changed` pushes.\n */\n notifyChanged(archetype: string): void;\n}\n\n/** Build the candidate list for an archetype, marking the user's default. */\nfunction candidatesFor(\n catalog: IntentCatalogEntry[],\n archetype: string,\n defaultHandler: string | undefined,\n): IntentCandidate[] {\n const candidates: IntentCandidate[] = [];\n for (const entry of catalog) {\n const support = entry.archetypes[archetype];\n if (!support) continue;\n candidates.push({\n dTag: entry.dTag,\n ...(entry.title === undefined ? {} : { title: entry.title }),\n actions: support.actions,\n protocols: support.protocols,\n ...(entry.dTag === defaultHandler ? { isDefault: true } : {}),\n });\n }\n return candidates;\n}\n\nfunction fail(archetype: string, action: string, error: string): IntentResult {\n return { ok: false, archetype, action, handled: false, error };\n}\n\n/**\n * Create a catalog-backed NAP-INTENT resolver.\n *\n * @param options - Catalog loader and window controller (required) plus\n * optional default-handler, chooser, and default-protocol policy hooks.\n * @returns A {@link CatalogIntentResolver} for `createIntentService({ resolver })`.\n * @throws If `options.loadCatalog` or `options.windows` is missing.\n *\n * @example\n * ```ts\n * const resolver = createCatalogIntentResolver({\n * loadCatalog: () => installedNapplets,\n * windows: { open: ({ dTag }) => ({ windowId: openWindow(dTag) }) },\n * getDefaultHandler: (a) => userDefaults[a],\n * });\n * ```\n */\nexport function createCatalogIntentResolver(options: CatalogIntentResolverOptions): CatalogIntentResolver {\n if (!options || typeof options.loadCatalog !== 'function') {\n throw new Error('createCatalogIntentResolver: options.loadCatalog is required');\n }\n if (!options.windows || typeof options.windows.open !== 'function') {\n throw new Error('createCatalogIntentResolver: options.windows is required');\n }\n const { loadCatalog, windows, getDefaultHandler, chooseHandler, defaultProtocol } = options;\n const listeners = new Set<(availability: IntentAvailability) => void>();\n\n async function availabilityFor(archetype: string): Promise<IntentAvailability> {\n const catalog = await loadCatalog();\n const def = getDefaultHandler?.(archetype);\n const candidates = candidatesFor(catalog, archetype, def);\n return {\n archetype,\n available: candidates.length > 0,\n candidates,\n hasDefault: def !== undefined && candidates.some((c) => c.dTag === def),\n };\n }\n\n /** Decide which candidate dTag should handle the request, or null to cancel. */\n async function pickHandler(\n archetype: string,\n candidates: IntentCandidate[],\n preference: IntentRequest['handler'],\n callerWindowId: string,\n ): Promise<string | null> {\n // Explicit dTag target (anything that isn't the two reserved keywords).\n if (typeof preference === 'string' && preference !== 'default' && preference !== 'choose') {\n return preference;\n }\n if (preference === 'choose') {\n const picked = await chooseHandler?.(archetype, candidates, callerWindowId);\n return picked ?? null;\n }\n // Default path: user's default, then the sole candidate, then chooser, then\n // a deterministic first-candidate fallback.\n const def = getDefaultHandler?.(archetype);\n if (def !== undefined && candidates.some((c) => c.dTag === def)) return def;\n if (candidates.length === 1) return candidates[0].dTag;\n if (chooseHandler) {\n const picked = await chooseHandler(archetype, candidates, callerWindowId);\n return picked ?? null;\n }\n return candidates[0].dTag;\n }\n\n async function invoke(request: IntentRequest, context: IntentResolverContext): Promise<IntentResult> {\n const archetype = request.archetype;\n const action = request.action ?? 'open';\n const catalog = await loadCatalog();\n const candidates = candidatesFor(catalog, archetype, getDefaultHandler?.(archetype));\n if (candidates.length === 0) return fail(archetype, action, 'no handler');\n\n const pickedDTag = await pickHandler(archetype, candidates, request.handler, context.windowId);\n if (pickedDTag === null) return fail(archetype, action, 'user cancelled');\n\n const handler = candidates.find((c) => c.dTag === pickedDTag);\n if (!handler) return fail(archetype, action, 'no handler');\n\n if (!handler.actions.includes(action)) return fail(archetype, action, 'unsupported action');\n\n const protocol = request.protocol ?? defaultProtocol?.(archetype) ?? handler.protocols[0];\n if (protocol !== undefined && handler.protocols.length > 0 && !handler.protocols.includes(protocol)) {\n return fail(archetype, action, 'unsupported protocol');\n }\n\n let windowId: string;\n try {\n const opened = await windows.open({\n dTag: handler.dTag,\n archetype,\n action,\n ...(protocol === undefined ? {} : { protocol }),\n ...(request.payload === undefined ? {} : { payload: request.payload }),\n ...(request.behavior === undefined ? {} : { behavior: request.behavior }),\n callerWindowId: context.windowId,\n });\n windowId = opened.windowId;\n } catch {\n return fail(archetype, action, 'invoke failed');\n }\n\n return {\n ok: true,\n archetype,\n action,\n handled: true,\n handler: handler.dTag,\n windowId,\n ...(protocol === undefined ? {} : { protocol }),\n };\n }\n\n async function handlers(): Promise<IntentAvailability[]> {\n const catalog = await loadCatalog();\n const archetypes = new Set<string>();\n for (const entry of catalog) {\n for (const slug of Object.keys(entry.archetypes)) archetypes.add(slug);\n }\n return Promise.all([...archetypes].map((a) => availabilityFor(a)));\n }\n\n return {\n invoke,\n available: availabilityFor,\n handlers,\n onChanged(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n notifyChanged(archetype) {\n if (listeners.size === 0) return;\n void availabilityFor(archetype).then((availability) => {\n for (const listener of listeners) listener(availability);\n });\n },\n };\n}\n","/**\n * manifest-intent-catalog.ts — signed-manifest → NAP-INTENT catalog adapter.\n *\n * Adapts a resolved NIP-5A/5D napplet manifest's archetype tags into an\n * {@link IntentCatalogEntry} — the shape `createCatalogIntentResolver.loadCatalog`\n * consumes. This lets NAP-INTENT availability and handler candidacy flow from\n * verified manifest tags rather than host-injected catalog data.\n *\n * To avoid a `@kehto/services → @kehto/nip` dependency cycle (services must stay\n * dependency-light and `@kehto/nip` is a lower-level NIP utility), the adapter\n * takes a minimal STRUCTURAL input {@link ManifestArchetypeInput} that the\n * `@kehto/nip/5d` `NappletManifest` satisfies by duck typing — callers pass\n * `resolved.manifest` directly without any package coupling.\n *\n * @packageDocumentation\n */\n\nimport type { IntentArchetypeSupport, IntentCatalogEntry } from './catalog-intent-resolver.js';\n\n/** NAP-INTENT default action when a manifest does not enumerate actions. */\nconst DEFAULT_ACTIONS: readonly string[] = ['open'];\n\n/**\n * The structural subset of `@kehto/nip/5d` `NappletManifest` the adapter needs.\n * Intentionally a duck-typed shape so the playground (or any caller) can pass a\n * resolved manifest without importing `@kehto/nip`.\n */\nexport interface ManifestArchetypeInput {\n /** The napplet's `d` identifier. */\n dTag: string;\n /** Optional human-readable title from the manifest. */\n title?: string;\n /**\n * Archetype slugs this napplet fulfills, from the manifest's `archetype` tags;\n * the optional `nap` is the recommended default wire protocol.\n */\n archetypes: Array<{ slug: string; nap?: string }>;\n}\n\n/**\n * Map a resolved napplet manifest's archetype data into an\n * {@link IntentCatalogEntry}.\n *\n * Each archetype `{ slug, nap }` becomes a keyed support record where `actions`\n * defaults to `['open']` (the NAP-INTENT default action — manifests do not\n * enumerate actions in this phase) and `protocols` is `[nap]` when a NAP-N is\n * present, else `[]`. Duplicate slugs keep the last occurrence.\n *\n * @param manifest - A resolved manifest's structural archetype data.\n * @returns The `IntentCatalogEntry` for `createCatalogIntentResolver`.\n *\n * @example\n * ```ts\n * manifestToIntentCatalogEntry({\n * dTag: 'profile-viewer',\n * title: 'Profile',\n * archetypes: [{ slug: 'profile', nap: 'NAP-1' }],\n * });\n * // → { dTag: 'profile-viewer', title: 'Profile',\n * // archetypes: { profile: { actions: ['open'], protocols: ['NAP-1'] } } }\n * ```\n */\nexport function manifestToIntentCatalogEntry(manifest: ManifestArchetypeInput): IntentCatalogEntry {\n const archetypes: Record<string, IntentArchetypeSupport> = {};\n for (const { slug, nap } of manifest.archetypes) {\n archetypes[slug] = {\n actions: [...DEFAULT_ACTIONS],\n protocols: nap ? [nap] : [],\n };\n }\n return {\n dTag: manifest.dTag,\n ...(manifest.title === undefined ? {} : { title: manifest.title }),\n archetypes,\n };\n}\n","/**\n * cvm-service.ts — NAP-CVM (ContextVM bridge) reference service.\n *\n * Shell-side handler for the NAP-CVM wire protocol. It is a pure envelope\n * router: it validates `cvm.*` envelopes, delegates the actual ContextVM /\n * MCP-over-Nostr work to an injected {@link CvmTransport}, and posts the\n * correlated `*.result` (echoing the request `id`) back to the napplet.\n *\n * The transport is injected (options-as-bridge) so this service has no Nostr\n * dependency and is fully unit-testable. A concrete ContextVM transport ships\n * separately at `@kehto/services/cvm-nostr-transport`.\n *\n * ──────────────────────────── Responsibilities ────────────────────────────\n * Inbound: cvm.discover, cvm.request, cvm.close\n * Outbound: cvm.discover.result, cvm.request.result, cvm.close.result,\n * cvm.event (server-pushed MCP notifications)\n *\n * MCP-level errors are returned inside `request.result.message.error`;\n * transport/shell-policy failures are returned in the envelope `error` field.\n *\n * `cvm.event` is fanned out to every window that holds an active session with\n * the originating server (a window opens a session by issuing a `cvm.request`\n * and closes it via `cvm.close` or window teardown).\n *\n * @example\n * ```ts\n * import { createCvmService } from '@kehto/services';\n * import { createNostrCvmTransport } from '@kehto/services/cvm-nostr-transport';\n *\n * const transport = createNostrCvmTransport({ defaultRelays: ['wss://relay.contextvm.org'] });\n * runtime.registerService('cvm', createCvmService({ transport }));\n * ```\n */\n\nimport type { NappletMessage } from '@napplet/core';\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\nimport type {\n CvmDiscoverQuery,\n CvmRequestOptions,\n CvmServer,\n CvmServerRef,\n McpMessage,\n} from './cvm-types.js';\n\n/** CVM service version — follows semver. */\nconst CVM_SERVICE_VERSION = '1.0.0';\n\n/**\n * Abstract ContextVM transport. Implementors own Nostr relay access, signing,\n * encryption (CEP-4 gift wrap), JSON-RPC correlation, and MCP initialization.\n */\nexport interface CvmTransport {\n /** Resolve public ContextVM server announcements matching the query. */\n discover(query?: CvmDiscoverQuery): Promise<CvmServer[]>;\n /** Send a raw MCP message to a server and resolve with the MCP response. */\n request(server: CvmServerRef, message: McpMessage, options?: CvmRequestOptions): Promise<McpMessage>;\n /** Release any session state held for a server (subscriptions, init cache). */\n close(server: CvmServerRef): Promise<void>;\n /**\n * Subscribe to server-pushed MCP messages not correlated to a single\n * request (e.g. notifications). Returns a handle whose `close()` detaches.\n */\n onEvent(handler: (server: CvmServerRef, message: McpMessage) => void): { close(): void };\n}\n\n/** Options for {@link createCvmService}. */\nexport interface CvmServiceOptions {\n /** The ContextVM transport the shell uses to reach servers. Required. */\n transport: CvmTransport;\n}\n\n/** The created CVM service, exposing the handler plus a disposal hook. */\nexport interface CvmService extends ServiceHandler {\n /** Detach the transport event subscription. Idempotent. */\n dispose(): void;\n}\n\ntype Send = (msg: NappletMessage) => void;\n\nconst CVM_DESCRIPTOR: ServiceDescriptor = {\n name: 'cvm',\n version: CVM_SERVICE_VERSION,\n description: 'NAP-CVM ContextVM bridge — MCP over Nostr',\n};\n\n/**\n * Create the NAP-CVM service handler.\n *\n * @param options - Must provide a {@link CvmTransport}.\n * @returns A {@link CvmService} (a `ServiceHandler` with a `dispose()` hook).\n * @throws If `options.transport` is missing.\n */\nexport function createCvmService(options: CvmServiceOptions): CvmService {\n if (!options || typeof options.transport !== 'object' || options.transport === null) {\n throw new Error('createCvmService: options.transport is required');\n }\n const { transport } = options;\n\n // Per-window send callbacks, captured at request time for cvm.event fan-out.\n const sendByWindow = new Map<string, Send>();\n // serverPubkey -> set of windowIds with an active session.\n const windowsByServer = new Map<string, Set<string>>();\n\n function openSession(windowId: string, server: CvmServerRef, send: Send): void {\n sendByWindow.set(windowId, send);\n let windows = windowsByServer.get(server.pubkey);\n if (!windows) {\n windows = new Set<string>();\n windowsByServer.set(server.pubkey, windows);\n }\n windows.add(windowId);\n }\n\n function closeSession(windowId: string, serverPubkey: string): void {\n const windows = windowsByServer.get(serverPubkey);\n if (windows) {\n windows.delete(windowId);\n if (windows.size === 0) windowsByServer.delete(serverPubkey);\n }\n }\n\n const eventSub = transport.onEvent((server, message) => {\n const windows = windowsByServer.get(server.pubkey);\n if (!windows) return;\n for (const windowId of windows) {\n const send = sendByWindow.get(windowId);\n send?.({ type: 'cvm.event', server, message } as NappletMessage);\n }\n });\n\n function handleDiscover(msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { id?: string; query?: CvmDiscoverQuery };\n const id = m.id ?? '';\n void transport\n .discover(m.query)\n .then((servers) => send({ type: 'cvm.discover.result', id, servers } as NappletMessage))\n .catch((err) =>\n send({ type: 'cvm.discover.result', id, servers: [], error: toErrorMessage(err) } as NappletMessage),\n );\n }\n\n function handleRequest(windowId: string, msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & {\n id?: string;\n server?: CvmServerRef;\n message?: McpMessage;\n options?: CvmRequestOptions;\n };\n const id = m.id ?? '';\n if (!m.server || typeof m.server.pubkey !== 'string' || m.server.pubkey.length === 0) {\n send({ type: 'cvm.request.result', id, error: 'server not found' } as NappletMessage);\n return;\n }\n if (!m.message || typeof m.message !== 'object') {\n send({ type: 'cvm.request.result', id, error: 'unsupported method' } as NappletMessage);\n return;\n }\n openSession(windowId, m.server, send);\n void transport\n .request(m.server, m.message, m.options)\n .then((message) => send({ type: 'cvm.request.result', id, message } as NappletMessage))\n .catch((err) =>\n send({ type: 'cvm.request.result', id, error: toErrorMessage(err) } as NappletMessage),\n );\n }\n\n function handleClose(windowId: string, msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { id?: string; server?: CvmServerRef };\n const id = m.id ?? '';\n if (!m.server || typeof m.server.pubkey !== 'string') {\n send({ type: 'cvm.close.result', id, error: 'server not found' } as NappletMessage);\n return;\n }\n closeSession(windowId, m.server.pubkey);\n void transport\n .close(m.server)\n .then(() => send({ type: 'cvm.close.result', id } as NappletMessage))\n .catch((err) => send({ type: 'cvm.close.result', id, error: toErrorMessage(err) } as NappletMessage));\n }\n\n return {\n descriptor: CVM_DESCRIPTOR,\n handleMessage(windowId: string, message: NappletMessage, send: Send): void {\n switch (message.type) {\n case 'cvm.discover':\n handleDiscover(message, send);\n return;\n case 'cvm.request':\n handleRequest(windowId, message, send);\n return;\n case 'cvm.close':\n handleClose(windowId, message, send);\n return;\n default:\n // Unknown cvm.* action — silently ignored (forward-compatible).\n return;\n }\n },\n onWindowDestroyed(windowId: string): void {\n sendByWindow.delete(windowId);\n for (const [pubkey, windows] of windowsByServer) {\n windows.delete(windowId);\n if (windows.size === 0) windowsByServer.delete(pubkey);\n }\n },\n dispose(): void {\n eventSub.close();\n },\n };\n}\n\nfunction toErrorMessage(err: unknown): string {\n if (err instanceof Error) return err.message;\n if (typeof err === 'string') return err;\n return 'cvm request failed';\n}\n","/**\n * link-service.ts — NAP-LINK reference service.\n *\n * Shell-mediated user-visible navigation. The napplet requests `link.open`;\n * the shell validates policy and hands navigation to a host-owned context.\n */\n\nimport type { LinkOpenErrorCode, LinkOpenOptions, LinkOpenResult, NappletMessage } from '@napplet/core';\nimport type { LinkOpenMessage, LinkOpenResultMessage } from '@napplet/nap/link/types';\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\n\nconst LINK_SERVICE_VERSION = '1.0.0';\nconst DEFAULT_ALLOWED_PROTOCOLS = ['http:', 'https:'] as const;\n\n/**\n * Host context passed to {@link LinkServiceOptions.open}.\n */\nexport interface LinkOpenContext {\n /** Window id of the requesting napplet. */\n windowId: string;\n /** Parsed absolute URL after validation. */\n url: URL;\n /** Optional untrusted napplet display hints. */\n options?: LinkOpenOptions;\n}\n\n/**\n * Options for {@link createLinkService}.\n */\nexport interface LinkServiceOptions {\n /**\n * Protocols allowed before host policy runs. Defaults to `http:` and `https:`.\n */\n allowedProtocols?: readonly string[];\n /**\n * Host-owned opener. Return `{ status: \"opened\" }` after handing navigation\n * to the host context, or `{ status: \"denied\" }` to reject by policy/user.\n * When omitted, all otherwise-valid URLs are denied.\n */\n open?: (context: LinkOpenContext) => LinkOpenResult | Promise<LinkOpenResult>;\n}\n\nfunction denied(id: string, error: LinkOpenErrorCode): LinkOpenResultMessage {\n return { type: 'link.open.result', id, status: 'denied', error };\n}\n\nfunction parseLinkUrl(id: string, rawUrl: string): URL | LinkOpenResultMessage {\n try {\n return new URL(rawUrl);\n } catch {\n return denied(id, 'invalid-url');\n }\n}\n\nfunction isProtocolAllowed(url: URL, allowedProtocols: readonly string[]): boolean {\n return allowedProtocols.includes(url.protocol);\n}\n\nasync function handleOpen(\n options: LinkServiceOptions,\n windowId: string,\n message: LinkOpenMessage,\n send: (msg: NappletMessage) => void,\n): Promise<void> {\n const parsed = parseLinkUrl(message.id, message.url);\n if ('type' in parsed) {\n send(parsed as NappletMessage);\n return;\n }\n\n const allowedProtocols = options.allowedProtocols ?? DEFAULT_ALLOWED_PROTOCOLS;\n if (!isProtocolAllowed(parsed, allowedProtocols)) {\n send(denied(message.id, 'unsupported-scheme') as NappletMessage);\n return;\n }\n\n if (!options.open) {\n send(denied(message.id, 'blocked-by-policy') as NappletMessage);\n return;\n }\n\n try {\n const result = await options.open({ windowId, url: parsed, options: message.options });\n send({\n type: 'link.open.result',\n id: message.id,\n status: result.status,\n ...(result.status === 'denied' ? { error: 'blocked-by-policy' as LinkOpenErrorCode } : {}),\n } as NappletMessage);\n } catch {\n send(denied(message.id, 'blocked-by-policy') as NappletMessage);\n }\n}\n\n/**\n * Create the NAP-LINK reference service.\n *\n * @param options - Host policy and opener hooks for link.open requests.\n * @returns A runtime service handler for the `link` domain.\n *\n * @example\n * ```ts\n * const service = createLinkService({\n * open: ({ url }) => ({ status: url.protocol === 'https:' ? 'opened' : 'denied' }),\n * });\n * ```\n */\nexport function createLinkService(options: LinkServiceOptions = {}): ServiceHandler {\n const descriptor: ServiceDescriptor = {\n name: 'link',\n version: LINK_SERVICE_VERSION,\n description: 'NAP-LINK reference handler for shell-mediated link opening',\n };\n\n return {\n descriptor,\n handleMessage(windowId: string, message: NappletMessage, send: (msg: NappletMessage) => void): void {\n if (message.type === 'link.open') {\n void handleOpen(options, windowId, message as LinkOpenMessage, send);\n return;\n }\n\n const id = (message as NappletMessage & { id?: string }).id ?? '';\n send({\n type: `${message.type}.error`,\n id,\n error: `Unknown link method: ${message.type}`,\n } as NappletMessage);\n },\n onWindowDestroyed(_windowId: string): void {\n /* no per-window state */\n },\n };\n}\n","/**\n * lists-service.ts — NAP-LISTS reference service.\n *\n * Shell-mediated NIP-51 list metadata and mutation hooks. The napplet requests\n * `lists.*`; the shell owns list lookup, validation, signing, publishing, and\n * private-item policy.\n */\n\nimport type {\n ListItem,\n ListMutationResult,\n ListOptions,\n ListRef,\n ListSupport,\n NappletMessage,\n} from '@napplet/core';\nimport type {\n ListsAddMessage,\n ListsRemoveMessage,\n} from '@napplet/nap/lists/types';\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\n\nconst LISTS_SERVICE_VERSION = '1.0.0';\n\n/** Context passed to host-provided NAP-LISTS hooks. */\nexport interface ListsServiceContext {\n /** Window id of the requesting napplet. */\n windowId: string;\n}\n\n/** Options for {@link createListsService}. */\nexport interface ListsServiceOptions {\n /** Host-owned supported list metadata. */\n supported?: (context: ListsServiceContext) => readonly ListSupport[] | Promise<readonly ListSupport[]>;\n /** Host-owned add mutation. */\n add?: (\n list: ListRef,\n items: readonly ListItem[],\n options: ListOptions | undefined,\n context: ListsServiceContext,\n ) => ListMutationResult | Promise<ListMutationResult>;\n /** Host-owned remove mutation. */\n remove?: (\n list: ListRef,\n items: readonly ListItem[],\n options: ListOptions | undefined,\n context: ListsServiceContext,\n ) => ListMutationResult | Promise<ListMutationResult>;\n}\n\ntype Send = (msg: NappletMessage) => void;\ntype MutationHook = NonNullable<ListsServiceOptions['add'] | ListsServiceOptions['remove']>;\n\nconst LISTS_DESCRIPTOR: ServiceDescriptor = {\n name: 'lists',\n version: LISTS_SERVICE_VERSION,\n description: 'NAP-LISTS reference handler for shell-mediated NIP-51 list mutations',\n};\n\nfunction errorMessage(err: unknown, fallback: string): string {\n if (err instanceof Error) return err.message;\n if (typeof err === 'string') return err;\n return fallback;\n}\n\nfunction settle<T>(\n call: () => T | Promise<T>,\n send: Send,\n okFalse: (error: string) => NappletMessage,\n onValue: (value: T) => NappletMessage,\n): void {\n let pending: Promise<T>;\n try {\n pending = Promise.resolve(call());\n } catch (err) {\n send(okFalse(errorMessage(err, 'lists request failed')));\n return;\n }\n pending\n .then((value) => send(onValue(value)))\n .catch((err) => send(okFalse(errorMessage(err, 'lists request failed'))));\n}\n\nfunction unsupportedMutation(resultType: string, id: string): NappletMessage {\n return {\n type: resultType,\n id,\n ok: false,\n error: 'unsupported',\n reason: `${resultType.replace('.result', '')} unavailable`,\n supported: [],\n } as NappletMessage;\n}\n\nfunction settleMutation(\n hook: MutationHook | undefined,\n message: ListsAddMessage | ListsRemoveMessage,\n context: ListsServiceContext,\n send: Send,\n resultType: 'lists.add.result' | 'lists.remove.result',\n): void {\n if (!hook) {\n send(unsupportedMutation(resultType, message.id));\n return;\n }\n settle(\n () => hook(message.list, message.items, message.options, context),\n send,\n (error) => ({ type: resultType, id: message.id, ok: false, error: 'list-unavailable', reason: error } as NappletMessage),\n (result) => ({ type: resultType, id: message.id, ...result } as NappletMessage),\n );\n}\n\n/**\n * Create the NAP-LISTS reference service.\n *\n * @param options - Host list metadata and mutation hooks for lists.* requests.\n * @returns A runtime service handler for the `lists` domain.\n */\nexport function createListsService(options: ListsServiceOptions = {}): ServiceHandler {\n return {\n descriptor: LISTS_DESCRIPTOR,\n handleMessage(windowId: string, message: NappletMessage, send: Send): void {\n const id = (message as NappletMessage & { id?: string }).id ?? '';\n const context = { windowId };\n\n if (message.type === 'lists.supported') {\n if (!options.supported) {\n send({ type: 'lists.supported.result', id, lists: [] } as NappletMessage);\n return;\n }\n settle(\n () => options.supported!(context),\n send,\n (error) => ({ type: 'lists.supported.result', id, error } as NappletMessage),\n (lists) => ({ type: 'lists.supported.result', id, lists: [...lists] } as NappletMessage),\n );\n return;\n }\n\n if (message.type === 'lists.add') {\n settleMutation(options.add, message as ListsAddMessage, context, send, 'lists.add.result');\n return;\n }\n\n if (message.type === 'lists.remove') {\n settleMutation(options.remove, message as ListsRemoveMessage, context, send, 'lists.remove.result');\n return;\n }\n\n send({\n type: `${message.type}.error`,\n id,\n error: `Unknown lists method: ${message.type}`,\n } as NappletMessage);\n },\n onWindowDestroyed(_windowId: string): void {\n /* no per-window state */\n },\n };\n}\n","/**\n * serial-service.ts — NAP-SERIAL reference service.\n *\n * Shell-mediated serial sessions. The napplet requests `serial.*`; the shell\n * owns device selection, permissions, session handles, reads, write ordering,\n * close events, and policy.\n */\n\nimport type {\n NappletMessage,\n SerialOpenRequest,\n SerialOpenResult,\n} from '@napplet/core';\nimport type {\n SerialCloseMessage,\n SerialOpenMessage,\n SerialWriteMessage,\n} from '@napplet/nap/serial/types';\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\n\nconst SERIAL_SERVICE_VERSION = '1.0.0';\n\n/** Context passed to host-provided NAP-SERIAL hooks. */\nexport interface SerialServiceContext {\n /** Window id of the requesting napplet. */\n windowId: string;\n}\n\n/** Options for {@link createSerialService}. */\nexport interface SerialServiceOptions {\n /** Host-owned serial open hook. */\n open?: (\n request: SerialOpenRequest,\n context: SerialServiceContext,\n ) => SerialOpenResult | Promise<SerialOpenResult>;\n /** Host-owned serial write hook. */\n write?: (\n sessionId: string,\n data: readonly number[],\n context: SerialServiceContext,\n ) => void | Promise<void>;\n /** Host-owned serial close hook. */\n close?: (\n sessionId: string,\n reason: string | undefined,\n context: SerialServiceContext,\n ) => void | Promise<void>;\n /** Optional host cleanup when a napplet window is destroyed. */\n destroyWindow?: (windowId: string) => void;\n}\n\ntype Send = (msg: NappletMessage) => void;\n\nconst SERIAL_DESCRIPTOR: ServiceDescriptor = {\n name: 'serial',\n version: SERIAL_SERVICE_VERSION,\n description: 'NAP-SERIAL reference handler for shell-mediated serial sessions',\n};\n\nfunction errorMessage(err: unknown, fallback: string): string {\n if (err instanceof Error) return err.message;\n if (typeof err === 'string') return err;\n return fallback;\n}\n\nfunction settle<T>(\n call: () => T | Promise<T>,\n send: Send,\n okFalse: (error: string) => NappletMessage,\n onValue: (value: T) => NappletMessage,\n): void {\n let pending: Promise<T>;\n try {\n pending = Promise.resolve(call());\n } catch (err) {\n send(okFalse(errorMessage(err, 'serial request failed')));\n return;\n }\n pending\n .then((value) => send(onValue(value)))\n .catch((err) => send(okFalse(errorMessage(err, 'serial request failed'))));\n}\n\nfunction unsupported(resultType: string, id: string): NappletMessage {\n return {\n type: resultType,\n id,\n error: `${resultType.replace('.result', '')} unavailable`,\n } as NappletMessage;\n}\n\n/**\n * Create the NAP-SERIAL reference service.\n *\n * @param options - Host serial session hooks for serial.* requests.\n * @returns A runtime service handler for the `serial` domain.\n */\nexport function createSerialService(options: SerialServiceOptions = {}): ServiceHandler {\n return {\n descriptor: SERIAL_DESCRIPTOR,\n handleMessage(windowId: string, message: NappletMessage, send: Send): void {\n const id = (message as NappletMessage & { id?: string }).id ?? '';\n const context = { windowId };\n\n if (message.type === 'serial.open') {\n if (!options.open) {\n send(unsupported('serial.open.result', id));\n return;\n }\n const serialMessage = message as SerialOpenMessage;\n settle(\n () => options.open!(serialMessage.request, context),\n send,\n (error) => ({ type: 'serial.open.result', id, error } as NappletMessage),\n (result) => ({ type: 'serial.open.result', id, session: result.session } as NappletMessage),\n );\n return;\n }\n\n if (message.type === 'serial.write') {\n if (!options.write) {\n send(unsupported('serial.write.result', id));\n return;\n }\n const serialMessage = message as SerialWriteMessage;\n settle(\n () => options.write!(serialMessage.sessionId, serialMessage.data, context),\n send,\n (error) => ({ type: 'serial.write.result', id, error } as NappletMessage),\n () => ({ type: 'serial.write.result', id } as NappletMessage),\n );\n return;\n }\n\n if (message.type === 'serial.close') {\n if (!options.close) {\n send(unsupported('serial.close.result', id));\n return;\n }\n const serialMessage = message as SerialCloseMessage;\n settle(\n () => options.close!(serialMessage.sessionId, serialMessage.reason, context),\n send,\n (error) => ({ type: 'serial.close.result', id, error } as NappletMessage),\n () => ({ type: 'serial.close.result', id } as NappletMessage),\n );\n return;\n }\n\n send({\n type: `${message.type}.error`,\n id,\n error: `Unknown serial method: ${message.type}`,\n } as NappletMessage);\n },\n onWindowDestroyed(windowId: string): void {\n options.destroyWindow?.(windowId);\n },\n };\n}\n","/**\n * ble-service.ts — NAP-BLE reference service.\n *\n * Shell-mediated BLE/GATT sessions. The napplet requests `ble.*`; the shell\n * owns chooser UI, permissions, GATT handles, notifications, and policy.\n */\n\nimport type {\n BleAttribute,\n BleOpenRequest,\n BleOpenResult,\n BleService,\n BleWriteOptions,\n NappletMessage,\n} from '@napplet/core';\nimport type {\n BleCloseMessage,\n BleOpenMessage,\n BleReadMessage,\n BleServicesMessage,\n BleSubscribeMessage,\n BleUnsubscribeMessage,\n BleWriteMessage,\n} from '@napplet/nap/ble/types';\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\n\nconst BLE_SERVICE_VERSION = '1.0.0';\n\n/** Context passed to host-provided NAP-BLE hooks. */\nexport interface BleServiceContext {\n /** Window id of the requesting napplet. */\n windowId: string;\n}\n\n/** Options for {@link createBleService}. */\nexport interface BleServiceOptions {\n /** Host-owned BLE open hook. */\n open?: (\n request: BleOpenRequest,\n context: BleServiceContext,\n ) => BleOpenResult | Promise<BleOpenResult>;\n /** Host-owned GATT service listing hook. */\n services?: (\n sessionId: string,\n context: BleServiceContext,\n ) => BleService[] | Promise<BleService[]>;\n /** Host-owned GATT read hook. */\n read?: (\n sessionId: string,\n target: BleAttribute,\n context: BleServiceContext,\n ) => number[] | Promise<number[]>;\n /** Host-owned GATT write hook. */\n write?: (\n sessionId: string,\n target: BleAttribute,\n data: readonly number[],\n options: BleWriteOptions | undefined,\n context: BleServiceContext,\n ) => void | Promise<void>;\n /** Host-owned notification subscription hook. */\n subscribe?: (\n sessionId: string,\n target: BleAttribute,\n context: BleServiceContext,\n ) => void | Promise<void>;\n /** Host-owned notification unsubscribe hook. */\n unsubscribe?: (\n sessionId: string,\n target: BleAttribute,\n context: BleServiceContext,\n ) => void | Promise<void>;\n /** Host-owned BLE close hook. */\n close?: (\n sessionId: string,\n reason: string | undefined,\n context: BleServiceContext,\n ) => void | Promise<void>;\n /** Optional host cleanup when a napplet window is destroyed. */\n destroyWindow?: (windowId: string) => void;\n}\n\ntype Send = (msg: NappletMessage) => void;\n\nconst BLE_DESCRIPTOR: ServiceDescriptor = {\n name: 'ble',\n version: BLE_SERVICE_VERSION,\n description: 'NAP-BLE reference handler for shell-mediated BLE/GATT sessions',\n};\n\nfunction errorMessage(err: unknown, fallback: string): string {\n if (err instanceof Error) return err.message;\n if (typeof err === 'string') return err;\n return fallback;\n}\n\nfunction settle<T>(\n call: () => T | Promise<T>,\n send: Send,\n okFalse: (error: string) => NappletMessage,\n onValue: (value: T) => NappletMessage,\n): void {\n let pending: Promise<T>;\n try {\n pending = Promise.resolve(call());\n } catch (err) {\n send(okFalse(errorMessage(err, 'ble request failed')));\n return;\n }\n pending\n .then((value) => send(onValue(value)))\n .catch((err) => send(okFalse(errorMessage(err, 'ble request failed'))));\n}\n\nfunction unsupported(resultType: string, id: string): NappletMessage {\n return {\n type: resultType,\n id,\n error: `${resultType.replace('.result', '')} unavailable`,\n } as NappletMessage;\n}\n\n/**\n * Create the NAP-BLE reference service.\n *\n * @param options - Host BLE session hooks for ble.* requests.\n * @returns A runtime service handler for the `ble` domain.\n */\nexport function createBleService(options: BleServiceOptions = {}): ServiceHandler {\n return {\n descriptor: BLE_DESCRIPTOR,\n handleMessage(windowId: string, message: NappletMessage, send: Send): void {\n const id = (message as NappletMessage & { id?: string }).id ?? '';\n const context = { windowId };\n\n if (message.type === 'ble.open') {\n if (!options.open) {\n send(unsupported('ble.open.result', id));\n return;\n }\n const bleMessage = message as BleOpenMessage;\n settle(\n () => options.open!(bleMessage.request, context),\n send,\n (error) => ({ type: 'ble.open.result', id, error } as NappletMessage),\n (result) => ({ type: 'ble.open.result', id, session: result.session } as NappletMessage),\n );\n return;\n }\n\n if (message.type === 'ble.services') {\n if (!options.services) {\n send(unsupported('ble.services.result', id));\n return;\n }\n const bleMessage = message as BleServicesMessage;\n settle(\n () => options.services!(bleMessage.sessionId, context),\n send,\n (error) => ({ type: 'ble.services.result', id, error } as NappletMessage),\n (services) => ({ type: 'ble.services.result', id, services } as NappletMessage),\n );\n return;\n }\n\n if (message.type === 'ble.read') {\n if (!options.read) {\n send(unsupported('ble.read.result', id));\n return;\n }\n const bleMessage = message as BleReadMessage;\n settle(\n () => options.read!(bleMessage.sessionId, bleMessage.target, context),\n send,\n (error) => ({ type: 'ble.read.result', id, error } as NappletMessage),\n (data) => ({ type: 'ble.read.result', id, data } as NappletMessage),\n );\n return;\n }\n\n if (message.type === 'ble.write') {\n if (!options.write) {\n send(unsupported('ble.write.result', id));\n return;\n }\n const bleMessage = message as BleWriteMessage;\n settle(\n () => options.write!(bleMessage.sessionId, bleMessage.target, bleMessage.data, bleMessage.options, context),\n send,\n (error) => ({ type: 'ble.write.result', id, error } as NappletMessage),\n () => ({ type: 'ble.write.result', id } as NappletMessage),\n );\n return;\n }\n\n if (message.type === 'ble.subscribe') {\n if (!options.subscribe) {\n send(unsupported('ble.subscribe.result', id));\n return;\n }\n const bleMessage = message as BleSubscribeMessage;\n settle(\n () => options.subscribe!(bleMessage.sessionId, bleMessage.target, context),\n send,\n (error) => ({ type: 'ble.subscribe.result', id, error } as NappletMessage),\n () => ({ type: 'ble.subscribe.result', id } as NappletMessage),\n );\n return;\n }\n\n if (message.type === 'ble.unsubscribe') {\n if (!options.unsubscribe) {\n send(unsupported('ble.unsubscribe.result', id));\n return;\n }\n const bleMessage = message as BleUnsubscribeMessage;\n settle(\n () => options.unsubscribe!(bleMessage.sessionId, bleMessage.target, context),\n send,\n (error) => ({ type: 'ble.unsubscribe.result', id, error } as NappletMessage),\n () => ({ type: 'ble.unsubscribe.result', id } as NappletMessage),\n );\n return;\n }\n\n if (message.type === 'ble.close') {\n if (!options.close) {\n send(unsupported('ble.close.result', id));\n return;\n }\n const bleMessage = message as BleCloseMessage;\n settle(\n () => options.close!(bleMessage.sessionId, bleMessage.reason, context),\n send,\n (error) => ({ type: 'ble.close.result', id, error } as NappletMessage),\n () => ({ type: 'ble.close.result', id } as NappletMessage),\n );\n return;\n }\n\n send({\n type: `${message.type}.error`,\n id,\n error: `Unknown ble method: ${message.type}`,\n } as NappletMessage);\n },\n onWindowDestroyed(windowId: string): void {\n options.destroyWindow?.(windowId);\n },\n };\n}\n","/**\n * webrtc-service.ts — NAP-WEBRTC reference service.\n *\n * Shell-mediated WebRTC sessions. The napplet requests `webrtc.*`; the shell\n * owns signaling, signing/encryption, SDP, ICE, peer connections, and policy.\n */\n\nimport type {\n NappletMessage,\n WebrtcEvent,\n WebrtcOpenRequest,\n WebrtcOpenResult,\n} from '@napplet/core';\nimport type {\n WebrtcCloseMessage,\n WebrtcOpenMessage,\n WebrtcSendMessage,\n} from '@napplet/nap/webrtc/types';\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\n\nconst WEBRTC_SERVICE_VERSION = '1.0.0';\n\n/** Context passed to host-provided NAP-WEBRTC hooks. */\nexport interface WebrtcServiceContext {\n /** Window id of the requesting napplet. */\n windowId: string;\n /** Emit a runtime-owned WebRTC event back to the requesting napplet. */\n emit(event: WebrtcEvent): void;\n}\n\n/** Options for {@link createWebrtcService}. */\nexport interface WebrtcServiceOptions {\n /** Host-owned WebRTC open hook. */\n open?: (\n request: WebrtcOpenRequest,\n context: WebrtcServiceContext,\n ) => WebrtcOpenResult | Promise<WebrtcOpenResult>;\n /** Host-owned WebRTC send hook. */\n send?: (\n sessionId: string,\n payload: unknown,\n context: WebrtcServiceContext,\n ) => void | Promise<void>;\n /** Host-owned WebRTC close hook. */\n close?: (\n sessionId: string,\n reason: string | undefined,\n context: WebrtcServiceContext,\n ) => void | Promise<void>;\n /** Optional host cleanup when a napplet window is destroyed. */\n destroyWindow?: (windowId: string) => void;\n}\n\ntype Send = (msg: NappletMessage) => void;\n\nconst WEBRTC_DESCRIPTOR: ServiceDescriptor = {\n name: 'webrtc',\n version: WEBRTC_SERVICE_VERSION,\n description: 'NAP-WEBRTC reference handler for shell-mediated WebRTC sessions',\n};\n\nfunction errorMessage(err: unknown, fallback: string): string {\n if (err instanceof Error) return err.message;\n if (typeof err === 'string') return err;\n return fallback;\n}\n\nfunction settle<T>(\n call: () => T | Promise<T>,\n send: Send,\n okFalse: (error: string) => NappletMessage,\n onValue: (value: T) => NappletMessage,\n): void {\n let pending: Promise<T>;\n try {\n pending = Promise.resolve(call());\n } catch (err) {\n send(okFalse(errorMessage(err, 'webrtc request failed')));\n return;\n }\n pending\n .then((value) => send(onValue(value)))\n .catch((err) => send(okFalse(errorMessage(err, 'webrtc request failed'))));\n}\n\nfunction unsupported(resultType: string, id: string): NappletMessage {\n return {\n type: resultType,\n id,\n error: `${resultType.replace('.result', '')} unavailable`,\n } as NappletMessage;\n}\n\nfunction createContext(windowId: string, send: Send): WebrtcServiceContext {\n return {\n windowId,\n emit(event) {\n send({ type: 'webrtc.event', event } as NappletMessage);\n },\n };\n}\n\n/**\n * Create the NAP-WEBRTC reference service.\n *\n * @param options - Host WebRTC session hooks for webrtc.* requests.\n * @returns A runtime service handler for the `webrtc` domain.\n */\nexport function createWebrtcService(options: WebrtcServiceOptions = {}): ServiceHandler {\n return {\n descriptor: WEBRTC_DESCRIPTOR,\n handleMessage(windowId: string, message: NappletMessage, send: Send): void {\n const id = (message as NappletMessage & { id?: string }).id ?? '';\n const context = createContext(windowId, send);\n\n if (message.type === 'webrtc.open') {\n if (!options.open) {\n send(unsupported('webrtc.open.result', id));\n return;\n }\n const webrtcMessage = message as WebrtcOpenMessage;\n settle(\n () => options.open!(webrtcMessage.request, context),\n send,\n (error) => ({ type: 'webrtc.open.result', id, error } as NappletMessage),\n (result) => ({ type: 'webrtc.open.result', id, session: result.session } as NappletMessage),\n );\n return;\n }\n\n if (message.type === 'webrtc.send') {\n if (!options.send) {\n send(unsupported('webrtc.send.result', id));\n return;\n }\n const webrtcMessage = message as WebrtcSendMessage;\n settle(\n () => options.send!(webrtcMessage.sessionId, webrtcMessage.payload, context),\n send,\n (error) => ({ type: 'webrtc.send.result', id, error } as NappletMessage),\n () => ({ type: 'webrtc.send.result', id } as NappletMessage),\n );\n return;\n }\n\n if (message.type === 'webrtc.close') {\n if (!options.close) {\n send(unsupported('webrtc.close.result', id));\n return;\n }\n const webrtcMessage = message as WebrtcCloseMessage;\n settle(\n () => options.close!(webrtcMessage.sessionId, webrtcMessage.reason, context),\n send,\n (error) => ({ type: 'webrtc.close.result', id, error } as NappletMessage),\n () => ({ type: 'webrtc.close.result', id } as NappletMessage),\n );\n return;\n }\n\n send({\n type: `${message.type}.error`,\n id,\n error: `Unknown webrtc method: ${message.type}`,\n } as NappletMessage);\n },\n onWindowDestroyed(windowId: string): void {\n options.destroyWindow?.(windowId);\n },\n };\n}\n","/**\n * common-service.ts — NAP-COMMON reference service.\n *\n * Shell-mediated public NIP-19 helpers and common social actions. The napplet\n * requests `common.*`; the shell owns identity, consent, event construction,\n * signing, publishing, relay access, and NIP-19 handling.\n */\n\nimport type {\n CommonActionResult,\n CommonFollowsResult,\n CommonNip19DecodeResult,\n CommonNip19EncodeInput,\n CommonNip19EncodeResult,\n CommonProfileResult,\n CommonProfileTarget,\n CommonReaction,\n CommonReportReason,\n CommonReportTarget,\n NappletMessage,\n} from '@napplet/core';\nimport type {\n CommonDecodeNip19Message,\n CommonEncodeNip19Message,\n CommonFollowMessage,\n CommonGetProfileMessage,\n CommonReactMessage,\n CommonReportMessage,\n CommonUnfollowMessage,\n} from '@napplet/nap/common/types';\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\nimport {\n decode,\n encodeBytes,\n naddrEncode,\n neventEncode,\n noteEncode,\n nprofileEncode,\n npubEncode,\n} from 'nostr-tools/nip19';\n\nconst COMMON_SERVICE_VERSION = '1.0.0';\nconst BECH32_CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l';\n\n/** Context passed to host-provided NAP-COMMON hooks. */\nexport interface CommonServiceContext {\n /** Window id of the requesting napplet. */\n windowId: string;\n}\n\n/** Options for {@link createCommonService}. */\nexport interface CommonServiceOptions {\n /** Host-owned profile lookup. */\n getProfile?: (target: CommonProfileTarget, context: CommonServiceContext) => CommonProfileResult | Promise<CommonProfileResult>;\n /** Host-owned current-user follows lookup. */\n follows?: (context: CommonServiceContext) => CommonFollowsResult | Promise<CommonFollowsResult>;\n /** Host-owned follow action. */\n follow?: (pubkeys: string[], context: CommonServiceContext) => CommonActionResult | Promise<CommonActionResult>;\n /** Host-owned unfollow action. */\n unfollow?: (pubkeys: string[], context: CommonServiceContext) => CommonActionResult | Promise<CommonActionResult>;\n /** Host-owned reaction action. */\n react?: (\n targetEventId: string,\n reaction: CommonReaction,\n customEmojiHref: string | undefined,\n context: CommonServiceContext,\n ) => CommonActionResult | Promise<CommonActionResult>;\n /** Host-owned report action. */\n report?: (\n target: CommonReportTarget,\n reason: CommonReportReason,\n text: string,\n context: CommonServiceContext,\n ) => CommonActionResult | Promise<CommonActionResult>;\n}\n\ntype Send = (msg: NappletMessage) => void;\n\nconst COMMON_DESCRIPTOR: ServiceDescriptor = {\n name: 'common',\n version: COMMON_SERVICE_VERSION,\n description: 'NAP-COMMON reference handler for shell-mediated social helpers',\n};\n\nfunction errorMessage(err: unknown, fallback: string): string {\n if (err instanceof Error) return err.message;\n if (typeof err === 'string') return err;\n return fallback;\n}\n\nfunction encodeNip19(input: CommonNip19EncodeInput): CommonNip19EncodeResult {\n try {\n switch (input.type) {\n case 'npub':\n return { ok: true, value: npubEncode(input.hex), nip19Type: input.type };\n case 'note':\n return { ok: true, value: noteEncode(input.hex), nip19Type: input.type };\n case 'nprofile':\n return { ok: true, value: nprofileEncode({ pubkey: input.pubkey, relays: input.relays }), nip19Type: input.type };\n case 'nevent':\n return {\n ok: true,\n value: neventEncode({ id: input.eventId, relays: input.relays, author: input.author, kind: input.kind }),\n nip19Type: input.type,\n };\n case 'naddr':\n return {\n ok: true,\n value: naddrEncode({ identifier: input.identifier, pubkey: input.pubkey, kind: input.kind, relays: input.relays }),\n nip19Type: input.type,\n };\n case 'nrelay':\n return { ok: true, value: encodeBytes('nrelay', new TextEncoder().encode(input.relay)), nip19Type: input.type };\n default:\n return { ok: false, error: 'unsupported NIP-19 type' };\n }\n } catch (err) {\n return { ok: false, error: errorMessage(err, 'NIP-19 encode failed') };\n }\n}\n\nfunction decodeNip19Value(value: string): CommonNip19DecodeResult {\n try {\n const decoded = decode(value);\n switch (decoded.type) {\n case 'npub':\n case 'note':\n return { ok: true, nip19Type: decoded.type, hex: decoded.data };\n case 'nprofile':\n return { ok: true, nip19Type: decoded.type, pubkey: decoded.data.pubkey, relays: decoded.data.relays };\n case 'nevent':\n return {\n ok: true,\n nip19Type: decoded.type,\n eventId: decoded.data.id,\n relays: decoded.data.relays,\n author: decoded.data.author,\n kind: decoded.data.kind,\n };\n case 'naddr':\n return {\n ok: true,\n nip19Type: decoded.type,\n identifier: decoded.data.identifier,\n pubkey: decoded.data.pubkey,\n kind: decoded.data.kind,\n relays: decoded.data.relays,\n };\n default:\n return { ok: false, error: 'unsupported NIP-19 type' };\n }\n } catch (err) {\n if (value.startsWith('nrelay1')) return decodeNrelay(value);\n return { ok: false, error: errorMessage(err, 'NIP-19 decode failed') };\n }\n}\n\nfunction decodeNrelay(value: string): CommonNip19DecodeResult {\n try {\n const separator = value.lastIndexOf('1');\n if (separator <= 0 || separator + 7 > value.length) return { ok: false, error: 'invalid nrelay value' };\n const dataPart = value.slice(separator + 1);\n const words = [...dataPart].map((char) => {\n const index = BECH32_CHARSET.indexOf(char);\n if (index === -1) throw new Error('invalid nrelay character');\n return index;\n });\n const payloadWords = words.slice(0, -6);\n const bytes = convertBits(payloadWords, 5, 8, false);\n return { ok: true, nip19Type: 'nrelay', relay: new TextDecoder().decode(Uint8Array.from(bytes)) };\n } catch (err) {\n return { ok: false, error: errorMessage(err, 'invalid nrelay value') };\n }\n}\n\nfunction convertBits(data: readonly number[], fromBits: number, toBits: number, pad: boolean): number[] {\n let acc = 0;\n let bits = 0;\n const result: number[] = [];\n const maxv = (1 << toBits) - 1;\n const maxAcc = (1 << (fromBits + toBits - 1)) - 1;\n for (const value of data) {\n if (value < 0 || value >> fromBits !== 0) throw new Error('invalid bech32 data');\n acc = ((acc << fromBits) | value) & maxAcc;\n bits += fromBits;\n while (bits >= toBits) {\n bits -= toBits;\n result.push((acc >> bits) & maxv);\n }\n }\n if (pad) {\n if (bits > 0) result.push((acc << (toBits - bits)) & maxv);\n } else if (bits >= fromBits || ((acc << (toBits - bits)) & maxv) !== 0) {\n throw new Error('invalid bech32 padding');\n }\n return result;\n}\n\nfunction settle<T>(\n call: () => T | Promise<T>,\n send: Send,\n okFalse: (error: string) => NappletMessage,\n onValue: (value: T) => NappletMessage,\n): void {\n let pending: Promise<T>;\n try {\n pending = Promise.resolve(call());\n } catch (err) {\n send(okFalse(errorMessage(err, 'common request failed')));\n return;\n }\n pending\n .then((value) => send(onValue(value)))\n .catch((err) => send(okFalse(errorMessage(err, 'common request failed'))));\n}\n\n/**\n * Create the NAP-COMMON reference service.\n *\n * @param options - Host profile/social hooks for common.* requests.\n * @returns A runtime service handler for the `common` domain.\n */\nexport function createCommonService(options: CommonServiceOptions = {}): ServiceHandler {\n return {\n descriptor: COMMON_DESCRIPTOR,\n handleMessage(windowId: string, message: NappletMessage, send: Send): void {\n const id = (message as NappletMessage & { id?: string }).id ?? '';\n const context = { windowId };\n\n if (message.type === 'common.encodeNip19') {\n const input = (message as CommonEncodeNip19Message).input;\n const result = input ? encodeNip19(input) : { ok: false, error: 'invalid input' };\n send({ type: 'common.encodeNip19.result', id, ...result } as NappletMessage);\n return;\n }\n\n if (message.type === 'common.decodeNip19') {\n const value = (message as CommonDecodeNip19Message).value;\n const result = typeof value === 'string' ? decodeNip19Value(value) : { ok: false, error: 'invalid value' };\n send({ type: 'common.decodeNip19.result', id, ...result } as NappletMessage);\n return;\n }\n\n if (message.type === 'common.getProfile') {\n const target = (message as CommonGetProfileMessage).target;\n if (!options.getProfile) {\n send({ type: 'common.getProfile.result', id, ok: false, pubkey: target ?? '', error: 'profile lookup unavailable' } as NappletMessage);\n return;\n }\n settle(\n () => options.getProfile!(target, context),\n send,\n (error) => ({ type: 'common.getProfile.result', id, ok: false, pubkey: target ?? '', error } as NappletMessage),\n (result) => ({ type: 'common.getProfile.result', id, ...result } as NappletMessage),\n );\n return;\n }\n\n if (message.type === 'common.follows') {\n if (!options.follows) {\n send({ type: 'common.follows.result', id, ok: false, pubkeys: [], error: 'follows lookup unavailable' } as NappletMessage);\n return;\n }\n settle(\n () => options.follows!(context),\n send,\n (error) => ({ type: 'common.follows.result', id, ok: false, pubkeys: [], error } as NappletMessage),\n (result) => ({ type: 'common.follows.result', id, ...result } as NappletMessage),\n );\n return;\n }\n\n if (message.type === 'common.follow') {\n const pubkeys = (message as CommonFollowMessage).pubkeys ?? [];\n settleAction(options.follow, pubkeys, context, send, 'common.follow.result', id, 'follow unavailable');\n return;\n }\n\n if (message.type === 'common.unfollow') {\n const pubkeys = (message as CommonUnfollowMessage).pubkeys ?? [];\n settleAction(options.unfollow, pubkeys, context, send, 'common.unfollow.result', id, 'unfollow unavailable');\n return;\n }\n\n if (message.type === 'common.react') {\n const m = message as CommonReactMessage;\n if (!options.react) {\n send({ type: 'common.react.result', id, ok: false, error: 'react unavailable' } as NappletMessage);\n return;\n }\n settle(\n () => options.react!(m.targetEventId, m.reaction, m.customEmojiHref, context),\n send,\n (error) => ({ type: 'common.react.result', id, ok: false, error } as NappletMessage),\n (result) => ({ type: 'common.react.result', id, ...result } as NappletMessage),\n );\n return;\n }\n\n if (message.type === 'common.report') {\n const m = message as CommonReportMessage;\n if (!options.report) {\n send({ type: 'common.report.result', id, ok: false, error: 'report unavailable' } as NappletMessage);\n return;\n }\n settle(\n () => options.report!(m.target, m.reason, m.text, context),\n send,\n (error) => ({ type: 'common.report.result', id, ok: false, error } as NappletMessage),\n (result) => ({ type: 'common.report.result', id, ...result } as NappletMessage),\n );\n return;\n }\n\n send({\n type: `${message.type}.error`,\n id,\n error: `Unknown common method: ${message.type}`,\n } as NappletMessage);\n },\n onWindowDestroyed(_windowId: string): void {\n /* no per-window state */\n },\n };\n}\n\nfunction settleAction(\n action: ((pubkeys: string[], context: CommonServiceContext) => CommonActionResult | Promise<CommonActionResult>) | undefined,\n pubkeys: string[],\n context: CommonServiceContext,\n send: Send,\n resultType: string,\n id: string,\n unavailable: string,\n): void {\n if (!action) {\n send({ type: resultType, id, ok: false, error: unavailable } as NappletMessage);\n return;\n }\n settle(\n () => action(pubkeys, context),\n send,\n (error) => ({ type: resultType, id, ok: false, error } as NappletMessage),\n (result) => ({ type: resultType, id, ...result } as NappletMessage),\n );\n}\n","export type JsonPrimitive = string | number | boolean | null;\nexport type JsonValue = JsonPrimitive | JsonValue[] | { readonly [key: string]: JsonValue };\nexport type JsonRecord = Record<string, JsonValue>;\n\nexport type PajaCapabilityDomain =\n | 'relay'\n | 'outbox'\n | 'storage'\n | 'identity'\n | 'keys'\n | 'config'\n | 'resource'\n | 'theme'\n | 'notify'\n | 'media'\n | 'upload'\n | 'intent'\n | 'link'\n | 'common'\n | 'lists'\n | 'serial'\n | 'ble'\n | 'webrtc'\n | 'cvm'\n | 'inc';\n\nexport interface PajaSimulationRawOptions {\n readonly capabilities?: {\n readonly domains?: Partial<Record<PajaCapabilityDomain, boolean>>;\n };\n readonly acl?: {\n readonly mode?: 'allow' | 'deny';\n };\n readonly firewall?: {\n readonly mode?: 'allow' | 'deny' | 'observe';\n };\n readonly identity?: {\n readonly mode?: 'anonymous' | 'fixed';\n readonly pubkey?: string;\n };\n readonly relay?: {\n readonly mode?: 'memory' | 'disabled';\n readonly urls?: readonly string[];\n readonly fixtures?: readonly JsonRecord[];\n };\n readonly storage?: {\n readonly mode?: 'local' | 'memory' | 'disabled';\n };\n readonly cache?: {\n readonly mode?: 'memory' | 'disabled';\n };\n readonly upload?: {\n readonly mode?: 'memory' | 'disabled';\n readonly rail?: string;\n };\n readonly media?: {\n readonly enabled?: boolean;\n };\n readonly notifications?: {\n readonly enabled?: boolean;\n readonly grant?: boolean;\n };\n readonly config?: {\n readonly values?: JsonRecord;\n };\n readonly theme?: {\n readonly mode?: 'dark' | 'light';\n readonly values?: JsonRecord;\n };\n readonly intent?: {\n readonly enabled?: boolean;\n };\n readonly cvm?: {\n readonly enabled?: boolean;\n };\n}\n\nexport interface PajaSimulation {\n readonly capabilities: {\n readonly domains: Record<PajaCapabilityDomain, boolean>;\n readonly disabledDomains: readonly PajaCapabilityDomain[];\n };\n readonly acl: {\n readonly mode: 'allow' | 'deny';\n };\n readonly firewall: {\n readonly mode: 'allow' | 'deny' | 'observe';\n };\n readonly identity: {\n readonly mode: 'anonymous' | 'fixed';\n readonly pubkey: string;\n };\n readonly relay: {\n readonly mode: 'memory' | 'disabled';\n readonly urls: readonly string[];\n readonly fixtures: readonly JsonRecord[];\n };\n readonly storage: {\n readonly mode: 'local' | 'memory' | 'disabled';\n };\n readonly cache: {\n readonly mode: 'memory' | 'disabled';\n };\n readonly upload: {\n readonly mode: 'memory' | 'disabled';\n readonly rail: string;\n };\n readonly media: {\n readonly enabled: boolean;\n };\n readonly notifications: {\n readonly enabled: boolean;\n readonly grant: boolean;\n };\n readonly config: {\n readonly values: JsonRecord;\n };\n readonly theme: {\n readonly mode: 'dark' | 'light';\n readonly values: JsonRecord;\n };\n readonly intent: {\n readonly enabled: boolean;\n };\n readonly cvm: {\n readonly enabled: boolean;\n };\n}\n\nexport class PajaSimulationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'PajaSimulationError';\n }\n}\n\nexport const PAJA_SIMULATION_DOMAINS: readonly PajaCapabilityDomain[] = [\n 'relay',\n 'outbox',\n 'storage',\n 'identity',\n 'keys',\n 'config',\n 'resource',\n 'theme',\n 'notify',\n 'media',\n 'upload',\n 'intent',\n 'link',\n 'common',\n 'lists',\n 'serial',\n 'ble',\n 'webrtc',\n 'cvm',\n 'inc',\n];\n\nconst DEFAULT_RELAY_URLS = ['wss://relay.kehto.dev'] as const;\nconst DEFAULT_CONFIG_VALUES: JsonRecord = {\n runtime: 'kehto paja',\n mode: 'development',\n target: 'single-window',\n};\n\nconst DEFAULT_THEME_VALUES: JsonRecord = {\n title: 'Kehto Paja',\n};\n\nexport function normalizePajaSimulation(\n raw: PajaSimulationRawOptions | undefined,\n): PajaSimulation {\n const domainOverrides = raw?.capabilities?.domains ?? {};\n const domains = Object.fromEntries(\n PAJA_SIMULATION_DOMAINS.map((domain) => [domain, domainOverrides[domain] ?? true]),\n ) as Record<PajaCapabilityDomain, boolean>;\n\n const relayMode = raw?.relay?.mode ?? (domains.relay ? 'memory' : 'disabled');\n if (!domains.relay && relayMode !== 'disabled') {\n throw new PajaSimulationError('Invalid simulation: relay.mode must be \"disabled\" when capabilities.domains.relay is false.');\n }\n if (!domains.outbox && relayMode !== 'disabled') {\n throw new PajaSimulationError('Invalid simulation: relay.mode must be \"disabled\" when capabilities.domains.outbox is false.');\n }\n if (relayMode === 'disabled') {\n domains.relay = false;\n domains.outbox = false;\n }\n\n const uploadMode = raw?.upload?.mode ?? (domains.upload ? 'memory' : 'disabled');\n if (!domains.upload && uploadMode !== 'disabled') {\n throw new PajaSimulationError('Invalid simulation: upload.mode must be \"disabled\" when capabilities.domains.upload is false.');\n }\n if (uploadMode === 'disabled') domains.upload = false;\n\n const intentEnabled = raw?.intent?.enabled ?? domains.intent;\n if (!domains.intent && intentEnabled) {\n throw new PajaSimulationError('Invalid simulation: intent.enabled must be false when capabilities.domains.intent is false.');\n }\n if (!intentEnabled) domains.intent = false;\n\n const mediaEnabled = raw?.media?.enabled ?? domains.media;\n if (!domains.media && mediaEnabled) {\n throw new PajaSimulationError('Invalid simulation: media.enabled must be false when capabilities.domains.media is false.');\n }\n if (!mediaEnabled) domains.media = false;\n\n const cvmEnabled = raw?.cvm?.enabled ?? domains.cvm;\n if (!domains.cvm && cvmEnabled) {\n throw new PajaSimulationError('Invalid simulation: cvm.enabled must be false when capabilities.domains.cvm is false.');\n }\n if (!cvmEnabled) domains.cvm = false;\n\n const notificationsEnabled = raw?.notifications?.enabled ?? domains.notify;\n if (!domains.notify && notificationsEnabled) {\n throw new PajaSimulationError('Invalid simulation: notifications.enabled must be false when capabilities.domains.notify is false.');\n }\n if (!notificationsEnabled) domains.notify = false;\n\n const storageMode = raw?.storage?.mode ?? (domains.storage ? 'local' : 'disabled');\n if (!domains.storage && storageMode !== 'disabled') {\n throw new PajaSimulationError('Invalid simulation: storage.mode must be \"disabled\" when capabilities.domains.storage is false.');\n }\n if (storageMode === 'disabled') domains.storage = false;\n\n const identityMode = raw?.identity?.mode ?? 'anonymous';\n const pubkey = raw?.identity?.pubkey?.trim() ?? '';\n if (identityMode === 'fixed' && !isHexPubkey(pubkey)) {\n throw new PajaSimulationError('Invalid simulation: identity.pubkey must be a 64-character hex string when identity.mode is \"fixed\".');\n }\n\n const relayUrls = raw?.relay?.urls ?? DEFAULT_RELAY_URLS;\n if (relayMode === 'memory' && relayUrls.length === 0) {\n throw new PajaSimulationError('Invalid simulation: relay.urls must contain at least one URL when relay.mode is \"memory\".');\n }\n for (const url of relayUrls) {\n if (typeof url !== 'string' || url.trim().length === 0) {\n throw new PajaSimulationError('Invalid simulation: relay.urls entries must be non-empty strings.');\n }\n }\n\n const uploadRail = raw?.upload?.rail?.trim() || 'dev-memory';\n if (uploadMode === 'memory' && uploadRail.length === 0) {\n throw new PajaSimulationError('Invalid simulation: upload.rail must be non-empty when upload.mode is \"memory\".');\n }\n\n return {\n capabilities: {\n domains,\n disabledDomains: PAJA_SIMULATION_DOMAINS.filter((domain) => !domains[domain]),\n },\n acl: {\n mode: raw?.acl?.mode ?? 'allow',\n },\n firewall: {\n mode: raw?.firewall?.mode ?? 'observe',\n },\n identity: {\n mode: identityMode,\n pubkey: identityMode === 'fixed' ? pubkey : '',\n },\n relay: {\n mode: relayMode,\n urls: relayUrls.map((url) => url.trim()),\n fixtures: raw?.relay?.fixtures ?? [],\n },\n storage: {\n mode: storageMode,\n },\n cache: {\n mode: raw?.cache?.mode ?? 'memory',\n },\n upload: {\n mode: uploadMode,\n rail: uploadRail,\n },\n media: {\n enabled: mediaEnabled,\n },\n notifications: {\n enabled: notificationsEnabled,\n grant: raw?.notifications?.grant ?? true,\n },\n config: {\n values: { ...DEFAULT_CONFIG_VALUES, ...raw?.config?.values },\n },\n theme: {\n mode: raw?.theme?.mode ?? 'dark',\n values: { ...DEFAULT_THEME_VALUES, ...raw?.theme?.values },\n },\n intent: {\n enabled: intentEnabled,\n },\n cvm: {\n enabled: cvmEnabled,\n },\n };\n}\n\nexport function summarizePajaSimulation(simulation: PajaSimulation): string {\n const relay = simulation.relay.mode === 'memory' ? `relay:${simulation.relay.urls.length}` : 'relay:off';\n const identity = simulation.identity.mode === 'fixed' ? 'identity:fixed' : 'identity:anon';\n const storage = `storage:${simulation.storage.mode}`;\n const theme = `theme:${simulation.theme.mode}`;\n const disabled = simulation.capabilities.disabledDomains.length > 0\n ? `off:${simulation.capabilities.disabledDomains.join(',')}`\n : 'off:none';\n return `${identity} ${relay} ${storage} ${theme} ${disabled}`;\n}\n\nfunction isHexPubkey(value: string): boolean {\n return /^[0-9a-fA-F]{64}$/.test(value);\n}\n","import type {\n BleAttribute,\n BleOpenRequest,\n BleOpenResult,\n BleService as CoreBleService,\n BleWriteOptions,\n ListItem,\n ListRef,\n ListSupport,\n SerialOpenRequest,\n SerialOpenResult,\n WebrtcEvent,\n WebrtcOpenRequest,\n WebrtcOpenResult,\n} from '@napplet/core';\n\nconst DEV_LISTS_EVENT_ID = '3'.repeat(64);\nconst DEV_SERIAL_LABEL = 'Paja serial';\nconst DEV_BLE_DEVICE_NAME = 'Paja BLE';\nconst DEV_BLE_SERVICE_UUID = 'battery_service';\nconst DEV_BLE_CHARACTERISTIC_UUID = 'battery_level';\nconst DEV_WEBRTC_PEER = '6'.repeat(64);\n\nconst DEV_LISTS_SUPPORT: ListSupport = {\n kind: 10003,\n type: 'bookmarks',\n addressable: false,\n supportedItemTypes: ['event', 'url'],\n};\n\nfunction destroyWindowSessions<T extends { windowId: string }>(\n sessions: Map<string, T>,\n windowId: string,\n): void {\n for (const [sessionId, session] of sessions) {\n if (session.windowId === windowId) sessions.delete(sessionId);\n }\n}\n\nexport function createDevListStore() {\n const values = new Set<string>();\n const itemKey = (item: ListItem) => `${item.itemType}:${item.value}`;\n const isSupported = (list: ListRef): boolean =>\n ('type' in list && list.type === DEV_LISTS_SUPPORT.type)\n || ('kind' in list && list.kind === DEV_LISTS_SUPPORT.kind);\n return {\n supported: () => [DEV_LISTS_SUPPORT],\n add(list: ListRef, items: readonly ListItem[]) {\n if (!isSupported(list)) return { ok: false, error: 'unsupported-list' as const, reason: 'unsupported list', supported: [DEV_LISTS_SUPPORT] };\n let added = 0;\n let skipped = 0;\n for (const item of items) {\n const key = itemKey(item);\n if (values.has(key)) skipped += 1;\n else {\n values.add(key);\n added += 1;\n }\n }\n return { ok: true, eventId: DEV_LISTS_EVENT_ID, added, skipped };\n },\n remove(list: ListRef, items: readonly ListItem[]) {\n if (!isSupported(list)) return { ok: false, error: 'unsupported-list' as const, reason: 'unsupported list', supported: [DEV_LISTS_SUPPORT] };\n let removed = 0;\n let skipped = 0;\n for (const item of items) {\n if (values.delete(itemKey(item))) removed += 1;\n else skipped += 1;\n }\n return { ok: true, eventId: DEV_LISTS_EVENT_ID, removed, skipped };\n },\n };\n}\n\nexport function createDevSerialController() {\n const sessions = new Map<string, { windowId: string; writes: number[][] }>();\n let nextSession = 1;\n\n return {\n open(_request: SerialOpenRequest, context: { windowId: string }): SerialOpenResult {\n const id = `paja-serial-${nextSession++}`;\n sessions.set(id, { windowId: context.windowId, writes: [] });\n return {\n session: {\n id,\n state: 'open',\n info: { displayName: DEV_SERIAL_LABEL },\n },\n };\n },\n write(sessionId: string, data: readonly number[]): void {\n const session = sessions.get(sessionId);\n if (!session) throw new Error('serial session not found');\n session.writes.push([...data]);\n },\n close(sessionId: string): void {\n if (!sessions.delete(sessionId)) throw new Error('serial session not found');\n },\n destroyWindow(windowId: string): void {\n destroyWindowSessions(sessions, windowId);\n },\n };\n}\n\nexport function createDevBleController() {\n const sessions = new Map<string, { windowId: string; writes: number[][]; subscriptions: Set<string> }>();\n let nextSession = 1;\n const service: CoreBleService = {\n uuid: DEV_BLE_SERVICE_UUID,\n characteristics: [{\n uuid: DEV_BLE_CHARACTERISTIC_UUID,\n properties: { read: true, write: true, notify: true },\n }],\n };\n const targetKey = (target: BleAttribute): string =>\n `${String(target.service)}:${String(target.characteristic)}:${String(target.descriptor ?? '')}`;\n const getSession = (sessionId: string) => {\n const session = sessions.get(sessionId);\n if (!session) throw new Error('ble session not found');\n return session;\n };\n\n return {\n open(_request: BleOpenRequest, context: { windowId: string }): BleOpenResult {\n const id = `paja-ble-${nextSession++}`;\n sessions.set(id, { windowId: context.windowId, writes: [], subscriptions: new Set() });\n return {\n session: {\n id,\n state: 'open',\n device: {\n id: 'paja-ble-device',\n name: DEV_BLE_DEVICE_NAME,\n services: [DEV_BLE_SERVICE_UUID],\n },\n },\n };\n },\n services(sessionId: string): CoreBleService[] {\n getSession(sessionId);\n return [service];\n },\n read(sessionId: string, _target: BleAttribute): number[] {\n getSession(sessionId);\n return [87];\n },\n write(sessionId: string, _target: BleAttribute, data: readonly number[], _options: BleWriteOptions | undefined): void {\n getSession(sessionId).writes.push([...data]);\n },\n subscribe(sessionId: string, target: BleAttribute): void {\n getSession(sessionId).subscriptions.add(targetKey(target));\n },\n unsubscribe(sessionId: string, target: BleAttribute): void {\n getSession(sessionId).subscriptions.delete(targetKey(target));\n },\n close(sessionId: string): void {\n if (!sessions.delete(sessionId)) throw new Error('ble session not found');\n },\n destroyWindow(windowId: string): void {\n destroyWindowSessions(sessions, windowId);\n },\n };\n}\n\nexport function createDevWebrtcController() {\n const sessions = new Map<string, { windowId: string; payloads: unknown[] }>();\n let nextSession = 1;\n const getSession = (sessionId: string) => {\n const session = sessions.get(sessionId);\n if (!session) throw new Error('webrtc session not found');\n return session;\n };\n\n return {\n open(request: WebrtcOpenRequest, context: { windowId: string; emit(event: WebrtcEvent): void }): WebrtcOpenResult {\n const id = `paja-webrtc-${nextSession++}`;\n const channel = request.channel ?? 'default';\n sessions.set(id, { windowId: context.windowId, payloads: [] });\n context.emit({ type: 'state', sessionId: id, state: 'open' });\n context.emit({ type: 'peer', sessionId: id, pubkey: DEV_WEBRTC_PEER, state: 'joined' });\n return {\n session: {\n id,\n scope: request.scope,\n channel,\n ...(request.protocol ? { protocol: request.protocol } : {}),\n state: 'open',\n },\n };\n },\n send(sessionId: string, payload: unknown, context: { emit(event: WebrtcEvent): void }): void {\n getSession(sessionId).payloads.push(payload);\n context.emit({ type: 'message', sessionId, from: DEV_WEBRTC_PEER, payload });\n },\n close(sessionId: string, reason: string | undefined, context: { emit(event: WebrtcEvent): void }): void {\n getSession(sessionId);\n sessions.delete(sessionId);\n context.emit({ type: 'closed', sessionId, ...(reason ? { reason } : {}) });\n },\n destroyWindow(windowId: string): void {\n destroyWindowSessions(sessions, windowId);\n },\n };\n}\n","import type { NostrEvent, NostrFilter } from '@napplet/core';\nimport {\n buildShellCapabilities,\n createShellBridge,\n originRegistry,\n type RelayPoolHooks,\n type RelayPoolLike,\n type ServiceHandler,\n type ShellAdapter,\n type ShellBridge,\n type ShellCapabilities,\n} from '@kehto/shell';\nimport {\n createConfigService,\n createCommonService,\n createCvmService,\n createIdentityService,\n createIntentService,\n createKeysService,\n createLinkService,\n createListsService,\n createMediaService,\n createNotificationService,\n createNotifyService,\n createOutboxService,\n createRelayPoolService,\n createResourceService,\n createBleService,\n createSerialService,\n createThemeService,\n createUploadService,\n createWebrtcService,\n type CvmServer,\n type CvmTransport,\n type IntentAvailability,\n type IntentRequest,\n type IntentResult,\n type McpMessage,\n type Uploader,\n type UploadRequest,\n type UploadResult,\n type UploadStatus,\n} from '@kehto/services';\nimport type { Theme } from '@napplet/nap/theme/types';\n\nimport type { PajaHostConfig } from './options.js';\nimport {\n summarizePajaSimulation,\n type PajaSimulation,\n} from './simulation.js';\nimport {\n createDevBleController,\n createDevListStore,\n createDevSerialController,\n createDevWebrtcController,\n} from './development-services.js';\n\ninterface PajaBrowserState {\n readonly config: PajaHostConfig;\n readonly capabilities: ShellCapabilities;\n readonly services: string[];\n simulation: PajaSimulation;\n generation: number;\n status: 'booting' | 'ready' | 'reloading' | 'error';\n reload(): void;\n setThemeMode(mode: PajaSimulation['theme']['mode']): void;\n getState(): {\n generation: number;\n status: PajaBrowserState['status'];\n iframeCount: number;\n initSent: boolean;\n services: string[];\n simulation: PajaSimulation;\n };\n}\n\ndeclare global {\n interface Window {\n __KEHTO_PAJA__?: PajaBrowserState;\n }\n}\n\nconst DEV_INTENT_ARCHETYPE = 'paja-target';\nconst DEV_COMMON_PUBKEY = '1'.repeat(64);\nconst DEV_COMMON_EVENT_ID = '2'.repeat(64);\nconst DEV_CVM_SERVER: CvmServer = {\n pubkey: '0'.repeat(64),\n name: 'Kehto Paja ContextVM',\n description: 'Deterministic development ContextVM adapter',\n relays: ['wss://relay.kehto.dev'],\n capabilities: ['echo'],\n};\n\nfunction readConfig(): PajaHostConfig {\n const script = document.getElementById('kehto-paja-config');\n if (!script?.textContent) {\n throw new Error('Missing Kehto Paja config.');\n }\n return JSON.parse(script.textContent) as PajaHostConfig;\n}\n\nfunction setStatus(state: PajaBrowserState, status: PajaBrowserState['status']): void {\n state.status = status;\n const statusEl = document.getElementById('lifecycle-status');\n if (statusEl) statusEl.textContent = status;\n}\n\nfunction setSimulationStatus(state: PajaBrowserState): void {\n const statusEl = document.getElementById('simulation-status');\n if (statusEl) statusEl.textContent = summarizePajaSimulation(state.simulation);\n const themeSelect = document.getElementById('simulation-theme');\n if (themeSelect instanceof HTMLSelectElement) themeSelect.value = state.simulation.theme.mode;\n}\n\nfunction getFrame(): HTMLIFrameElement {\n const frame = document.getElementById('napplet-frame');\n if (!(frame instanceof HTMLIFrameElement)) {\n throw new Error('Missing Kehto Paja iframe.');\n }\n frame.sandbox.add('allow-scripts');\n frame.sandbox.remove('allow-same-origin');\n return frame;\n}\n\nfunction matchesFilter(event: NostrEvent, filter: NostrFilter): boolean {\n const ids = filter.ids;\n if (ids && !ids.includes(event.id)) return false;\n const authors = filter.authors;\n if (authors && !authors.includes(event.pubkey)) return false;\n const kinds = filter.kinds;\n if (kinds && !kinds.includes(event.kind)) return false;\n return true;\n}\n\nfunction matchesAnyFilter(event: NostrEvent, filters: NostrFilter[]): boolean {\n return filters.length === 0 || filters.some((filter) => matchesFilter(event, filter));\n}\n\nfunction createMemoryRelayPool(getSimulation: () => PajaSimulation): RelayPoolLike {\n const events: NostrEvent[] = getSimulation().relay.fixtures.flatMap(toNostrEvent);\n const subscribers = new Set<{\n filters: NostrFilter[];\n next(item: NostrEvent | 'EOSE'): void;\n }>();\n\n return {\n subscription(_relayUrls: string[], filters: NostrFilter[]) {\n return {\n subscribe(next: (item: unknown) => void) {\n const subscriber = {\n filters,\n next: (item: NostrEvent | 'EOSE') => next(item),\n };\n subscribers.add(subscriber);\n for (const event of events) {\n if (matchesAnyFilter(event, filters)) next(event);\n }\n queueMicrotask(() => {\n if (subscribers.has(subscriber)) next('EOSE');\n });\n return {\n unsubscribe() {\n subscribers.delete(subscriber);\n },\n };\n },\n };\n },\n publish(_relayUrls: string[], event: NostrEvent): void {\n if (getSimulation().relay.mode === 'disabled') return;\n events.push(event);\n for (const subscriber of subscribers) {\n if (matchesAnyFilter(event, subscriber.filters)) subscriber.next(event);\n }\n },\n request(_relayUrls: string[], filters: NostrFilter[]) {\n return {\n subscribe(observer: { next: (event: unknown) => void; complete: () => void; error: () => void }) {\n for (const event of events) {\n if (matchesAnyFilter(event, filters)) observer.next(event);\n }\n queueMicrotask(() => observer.complete());\n return { unsubscribe() { /* no-op */ } };\n },\n };\n },\n };\n}\n\nfunction toNostrEvent(value: unknown): NostrEvent[] {\n if (\n typeof value === 'object'\n && value !== null\n && typeof (value as { id?: unknown }).id === 'string'\n && typeof (value as { pubkey?: unknown }).pubkey === 'string'\n && typeof (value as { kind?: unknown }).kind === 'number'\n && Array.isArray((value as { tags?: unknown }).tags)\n && typeof (value as { content?: unknown }).content === 'string'\n && typeof (value as { sig?: unknown }).sig === 'string'\n ) {\n return [value as NostrEvent];\n }\n return [];\n}\n\nfunction getRelayUrls(simulation: PajaSimulation): string[] {\n return simulation.relay.mode === 'memory' ? [...simulation.relay.urls] : [];\n}\n\nfunction createRelayHooks(pool: RelayPoolLike, getSimulation: () => PajaSimulation): RelayPoolHooks {\n const cleanups = new Map<string, () => void>();\n return {\n getRelayPool: () => pool,\n trackSubscription(subKey, cleanup) {\n cleanups.set(subKey, cleanup);\n },\n untrackSubscription(subKey) {\n cleanups.get(subKey)?.();\n cleanups.delete(subKey);\n },\n openScopedRelay: () => {},\n closeScopedRelay: () => {},\n publishToScopedRelay: (_windowId, event) => {\n if (getSimulation().relay.mode === 'disabled') return false;\n pool.publish(getRelayUrls(getSimulation()), event);\n return true;\n },\n selectRelayTier: () => getRelayUrls(getSimulation()),\n };\n}\n\nfunction createWorkerRelay(events: NostrEvent[]) {\n return {\n event(event: NostrEvent) {\n events.push(event);\n return Promise.resolve({ ok: true });\n },\n query(req: unknown): Promise<NostrEvent[]> {\n const filters = Array.isArray(req) ? req.slice(2).filter((item): item is NostrFilter => typeof item === 'object' && item !== null) : [];\n return Promise.resolve(events.filter((event) => matchesAnyFilter(event, filters)));\n },\n count(req: unknown): Promise<number> {\n return this.query(req).then((matched) => matched.length);\n },\n };\n}\n\nfunction createDevUploader(getSimulation: () => PajaSimulation): Uploader {\n return {\n async upload(request: UploadRequest, ctx): Promise<UploadResult> {\n const simulation = getSimulation();\n if (simulation.upload.mode === 'disabled') {\n throw new Error('upload simulation is disabled');\n }\n const size = request.data instanceof Blob ? request.data.size : request.data.byteLength;\n const result: UploadResult = {\n ok: true,\n uploadId: ctx.uploadId,\n status: 'complete',\n rail: request.rail ?? simulation.upload.rail,\n url: `kehto-dev://${simulation.upload.rail}/${ctx.uploadId}`,\n size,\n mimeType: request.mimeType ?? (request.data instanceof Blob ? request.data.type : undefined),\n };\n ctx.onStatus({ ...result, updatedAt: Date.now() });\n return result;\n },\n async status(uploadId: string): Promise<UploadStatus> {\n const simulation = getSimulation();\n return {\n ok: simulation.upload.mode !== 'disabled',\n uploadId,\n status: simulation.upload.mode === 'disabled' ? 'failed' : 'complete',\n rail: simulation.upload.rail,\n url: `kehto-dev://${simulation.upload.rail}/${uploadId}`,\n updatedAt: Date.now(),\n };\n },\n };\n}\n\nfunction createDevIntentAvailability(): IntentAvailability {\n return {\n archetype: DEV_INTENT_ARCHETYPE,\n available: true,\n hasDefault: true,\n candidates: [{\n dTag: 'dev-target',\n title: 'Dev runtime target',\n actions: ['open'],\n protocols: ['NAP-01'],\n isDefault: true,\n }],\n };\n}\n\nfunction createDevCvmTransport(getSimulation: () => PajaSimulation): CvmTransport {\n return {\n async discover() {\n if (!getSimulation().cvm.enabled) return [];\n return [{ ...DEV_CVM_SERVER, relays: getRelayUrls(getSimulation()) }];\n },\n async request(_server, message): Promise<McpMessage> {\n const id = typeof message.id === 'string' || typeof message.id === 'number' ? message.id : 'paja';\n return {\n jsonrpc: '2.0',\n id,\n result: {\n echoed: true,\n method: typeof message.method === 'string' ? message.method : null,\n },\n };\n },\n async close() {},\n onEvent() {\n return {\n close() {},\n };\n },\n };\n}\n\nfunction createOutboxRouter(pool: RelayPoolLike, getSimulation: () => PajaSimulation) {\n return {\n async query(filters: NostrFilter[]) {\n const relayUrls = getRelayUrls(getSimulation());\n const events = await new Promise<NostrEvent[]>((resolve) => {\n const out: NostrEvent[] = [];\n pool.request(relayUrls, filters).subscribe({\n next: (event) => out.push(event as NostrEvent),\n complete: () => resolve(out),\n error: () => resolve(out),\n });\n });\n return { events, relays: Object.fromEntries(events.map((event) => [event.id, relayUrls])) };\n },\n subscribe(filters: NostrFilter[], _options: unknown, sink: { event(event: NostrEvent, relay?: string): void; eose(): void; closed(reason?: string): void }) {\n const relayUrls = getRelayUrls(getSimulation());\n const sub = pool.subscription(relayUrls, filters).subscribe((item) => {\n if (item === 'EOSE') sink.eose();\n else sink.event(item as NostrEvent, relayUrls[0]);\n });\n return { close: () => sub.unsubscribe() };\n },\n async publish(template: Partial<NostrEvent>) {\n const event = {\n id: crypto.randomUUID().replace(/-/g, '').padEnd(64, '0').slice(0, 64),\n pubkey: '0'.repeat(64),\n created_at: Math.floor(Date.now() / 1000),\n kind: 1,\n tags: [],\n content: '',\n sig: '0'.repeat(128),\n ...template,\n } as NostrEvent;\n const relayUrls = getRelayUrls(getSimulation());\n pool.publish(relayUrls, event);\n return { ok: true, event, eventId: event.id, relays: { [relayUrls[0] ?? 'dev']: true } };\n },\n async resolveRelays() {\n return { relays: getRelayUrls(getSimulation()), source: 'policy' as const };\n },\n };\n}\n\nfunction createDevTheme(mode: PajaSimulation['theme']['mode'], values: PajaSimulation['theme']['values']): Theme {\n const defaultColors = mode === 'light'\n ? { background: '#f7f5ed', text: '#1d211d', primary: '#6a5a12' }\n : { background: '#101211', text: '#f4f0df', primary: '#d8c36a' };\n return {\n ...values,\n colors: {\n ...defaultColors,\n ...((typeof values.colors === 'object' && values.colors !== null && !Array.isArray(values.colors)) ? values.colors : {}),\n },\n } as Theme;\n}\n\nfunction createDevSigner(getSimulation: () => PajaSimulation) {\n return {\n getPublicKey: () => getSimulation().identity.pubkey,\n getRelays: () => Object.fromEntries(getRelayUrls(getSimulation()).map((relay) => [relay, { read: true, write: true }])),\n };\n}\n\nfunction createDevServices(\n pool: RelayPoolLike,\n getSimulation: () => PajaSimulation,\n onThemeService: (theme: ReturnType<typeof createThemeService>) => void,\n): Record<string, ServiceHandler> {\n const notification = createNotificationService({ maxPerWindow: 50 });\n const theme = createThemeService({\n initialTheme: createDevTheme(getSimulation().theme.mode, getSimulation().theme.values),\n onBroadcast: () => {},\n });\n onThemeService(theme);\n const config = createConfigService({\n getValues: () => ({\n ...getSimulation().config.values,\n simulation: {\n identity: getSimulation().identity.mode,\n relay: getSimulation().relay.mode,\n storage: getSimulation().storage.mode,\n cache: getSimulation().cache.mode,\n upload: getSimulation().upload.mode,\n theme: getSimulation().theme.mode,\n },\n }),\n });\n const services: Record<string, ServiceHandler> = {\n keys: createKeysService(),\n resource: createResourceService({\n fetch: (url, init) => fetch(url, init),\n isOriginGranted: () => true,\n getConnectGrants: () => ['*'],\n resolveIdentity: () => ({ dTag: 'dev-target', aggregateHash: 'paja' }),\n }),\n };\n\n if (getSimulation().relay.mode === 'memory') {\n services.relay = createRelayPoolService({\n subscribe: (filters, callback, relayUrls) => pool.subscription(relayUrls ?? getRelayUrls(getSimulation()), filters).subscribe((item) => {\n if (item === 'EOSE' || (typeof item === 'object' && item !== null)) {\n callback(item as NostrEvent | 'EOSE');\n }\n }),\n publish: (event) => pool.publish(getRelayUrls(getSimulation()), event),\n selectRelayTier: () => getRelayUrls(getSimulation()),\n isAvailable: () => getSimulation().relay.mode === 'memory',\n });\n services.outbox = createOutboxService({ router: createOutboxRouter(pool, getSimulation) });\n }\n\n if (getSimulation().identity.mode === 'fixed') {\n services.identity = createIdentityService({ getSigner: () => createDevSigner(getSimulation) });\n } else if (getSimulation().capabilities.domains.identity) {\n services.identity = createIdentityService({ getSigner: () => null });\n }\n if (getSimulation().notifications.enabled) {\n services.notifications = notification;\n services.notify = createNotifyService({ defaultGrant: getSimulation().notifications.grant });\n }\n if (getSimulation().media.enabled) services.media = createMediaService();\n if (getSimulation().capabilities.domains.theme) services.theme = theme.handler;\n if (getSimulation().capabilities.domains.config) services.config = config.handler;\n if (getSimulation().cvm.enabled) services.cvm = createCvmService({ transport: createDevCvmTransport(getSimulation) });\n if (getSimulation().upload.mode === 'memory') services.upload = createUploadService({ uploader: createDevUploader(getSimulation) });\n if (getSimulation().intent.enabled) {\n services.intent = createIntentService({\n resolver: {\n invoke(request: IntentRequest): IntentResult {\n return {\n ok: true,\n archetype: request.archetype,\n action: request.action ?? 'open',\n handled: request.archetype === DEV_INTENT_ARCHETYPE,\n handler: 'dev-target',\n windowId: 'kehto-paja-window',\n protocol: request.protocol ?? 'NAP-01',\n };\n },\n available: (archetype) => ({\n ...createDevIntentAvailability(),\n archetype,\n available: archetype === DEV_INTENT_ARCHETYPE,\n }),\n handlers: () => [createDevIntentAvailability()],\n },\n });\n }\n if (getSimulation().capabilities.domains.link) {\n services.link = createLinkService({\n open: ({ url }) => ({ status: url.protocol === 'https:' || url.protocol === 'http:' ? 'opened' : 'denied' }),\n });\n }\n if (getSimulation().capabilities.domains.common) {\n services.common = createCommonService({\n getProfile: (target) => ({\n ok: true,\n pubkey: target || getSimulation().identity.pubkey || DEV_COMMON_PUBKEY,\n profile: { name: 'paja', displayName: 'Kehto Paja' },\n relays: getRelayUrls(getSimulation()),\n }),\n follows: () => ({ ok: true, pubkeys: [getSimulation().identity.pubkey || DEV_COMMON_PUBKEY] }),\n follow: () => ({ ok: true, eventId: DEV_COMMON_EVENT_ID }),\n unfollow: () => ({ ok: true, eventId: DEV_COMMON_EVENT_ID }),\n react: () => ({ ok: true, eventId: DEV_COMMON_EVENT_ID }),\n report: () => ({ ok: true, eventId: DEV_COMMON_EVENT_ID }),\n });\n }\n if (getSimulation().capabilities.domains.lists) {\n const listStore = createDevListStore();\n services.lists = createListsService({\n supported: listStore.supported,\n add: listStore.add,\n remove: listStore.remove,\n });\n }\n if (getSimulation().capabilities.domains.serial) {\n services.serial = createSerialService(createDevSerialController());\n }\n if (getSimulation().capabilities.domains.ble) {\n services.ble = createBleService(createDevBleController());\n }\n if (getSimulation().capabilities.domains.webrtc) {\n services.webrtc = createWebrtcService(createDevWebrtcController());\n }\n\n return services;\n}\n\nfunction createPajaAdapter(\n config: PajaHostConfig,\n getSimulation: () => PajaSimulation,\n onThemeService: (theme: ReturnType<typeof createThemeService>) => void,\n): ShellAdapter {\n const pool = createMemoryRelayPool(getSimulation);\n const workerRelayEvents: NostrEvent[] = [];\n return {\n relayPool: createRelayHooks(pool, getSimulation),\n relayConfig: {\n addRelay: () => {},\n removeRelay: () => {},\n getRelayConfig: () => {\n const relays = getRelayUrls(getSimulation());\n return { discovery: relays, super: relays, outbox: relays };\n },\n getNip66Suggestions: () => getRelayUrls(getSimulation()),\n },\n windowManager: { createWindow: () => null },\n auth: {\n getUserPubkey: () => getSimulation().identity.pubkey,\n getSigner: () => (getSimulation().identity.mode === 'fixed' ? createDevSigner(getSimulation) : null),\n },\n services: createDevServices(pool, getSimulation, onThemeService),\n capabilities: { disabledDomains: getSimulation().capabilities.disabledDomains },\n config: { getNappUpdateBehavior: () => 'auto-grant' },\n hotkeys: { executeHotkeyFromForward: () => {} },\n workerRelay: { getWorkerRelay: () => createWorkerRelay(workerRelayEvents) },\n upload: getSimulation().upload.mode === 'memory' ? { getUploader: () => ({ rails: [getSimulation().upload.rail] }) } : undefined,\n intent: { isAvailable: () => getSimulation().intent.enabled },\n link: { isAvailable: () => getSimulation().capabilities.domains.link },\n common: { isAvailable: () => getSimulation().capabilities.domains.common },\n lists: { isAvailable: () => getSimulation().capabilities.domains.lists },\n serial: { isAvailable: () => getSimulation().capabilities.domains.serial },\n ble: { isAvailable: () => getSimulation().capabilities.domains.ble },\n webrtc: { isAvailable: () => getSimulation().capabilities.domains.webrtc },\n crypto: {\n verifyEvent: async () => true,\n },\n onNip5dIframeCreate: () => ({\n dTag: config.window.dTag,\n aggregateHash: config.window.aggregateHash,\n }),\n };\n}\n\nfunction registerFrameForGeneration(bridge: ShellBridge, frame: HTMLIFrameElement, config: PajaHostConfig, generation: number): string | null {\n const win = frame.contentWindow;\n if (!win) return null;\n const windowId = `${config.window.id}:${generation}`;\n originRegistry.register(win, windowId, {\n dTag: config.window.dTag,\n aggregateHash: config.window.aggregateHash,\n });\n return windowId;\n}\n\nfunction navigateFrame(bridge: ShellBridge, frame: HTMLIFrameElement, config: PajaHostConfig, generation: number): string | null {\n const windowId = registerFrameForGeneration(bridge, frame, config, generation);\n frame.src = config.target.url;\n return windowId;\n}\n\nfunction installPajaHost(): void {\n const config = readConfig();\n const frame = getFrame();\n let currentSimulation = config.simulation;\n const getSimulation = () => currentSimulation;\n let themeService: ReturnType<typeof createThemeService> | null = null;\n const adapter = createPajaAdapter(config, getSimulation, (theme) => {\n themeService = theme;\n });\n const bridge = createShellBridge(adapter);\n const capabilities = buildShellCapabilities(adapter);\n const services = Object.keys(adapter.services ?? {}).sort();\n let currentWindowId: string | null = null;\n let initReceivedGeneration = -1;\n\n const state: PajaBrowserState = {\n config,\n capabilities,\n services,\n simulation: currentSimulation,\n generation: 0,\n status: 'booting',\n reload() {\n if (currentWindowId) {\n bridge.runtime.destroyWindow(currentWindowId);\n bridge.runtime.sessionRegistry.unregister(currentWindowId);\n originRegistry.unregister(currentWindowId);\n }\n this.generation += 1;\n initReceivedGeneration = -1;\n setStatus(this, 'reloading');\n currentWindowId = navigateFrame(bridge, frame, config, this.generation);\n },\n setThemeMode(mode) {\n currentSimulation = {\n ...currentSimulation,\n theme: {\n ...currentSimulation.theme,\n mode,\n },\n };\n this.simulation = currentSimulation;\n themeService?.publishTheme(createDevTheme(currentSimulation.theme.mode, currentSimulation.theme.values));\n setSimulationStatus(this);\n },\n getState() {\n return {\n generation: this.generation,\n status: this.status,\n iframeCount: document.querySelectorAll('iframe').length,\n initSent: initReceivedGeneration === this.generation,\n services,\n simulation: currentSimulation,\n };\n },\n };\n\n window.__KEHTO_PAJA__ = state;\n\n window.addEventListener('message', (event) => {\n if (event.source !== frame.contentWindow) return;\n bridge.handleMessage(event);\n const data = event.data as { type?: unknown } | null;\n if (data && typeof data === 'object' && data.type === 'shell.ready') {\n initReceivedGeneration = state.generation;\n setStatus(state, 'ready');\n }\n });\n\n frame.addEventListener('load', () => {\n if (state.status === 'booting' || state.status === 'reloading') {\n registerFrameForGeneration(bridge, frame, config, state.generation);\n }\n });\n\n frame.addEventListener('error', () => {\n setStatus(state, 'error');\n });\n\n document.getElementById('reload-target')?.addEventListener('click', () => {\n state.reload();\n });\n\n document.getElementById('simulation-theme')?.addEventListener('change', (event) => {\n const target = event.target;\n if (!(target instanceof HTMLSelectElement)) return;\n if (target.value === 'dark' || target.value === 'light') {\n state.setThemeMode(target.value);\n }\n });\n\n setStatus(state, 'booting');\n setSimulationStatus(state);\n currentWindowId = navigateFrame(bridge, frame, config, state.generation);\n}\n\ntry {\n installPajaHost();\n} catch (error) {\n const statusEl = document.getElementById('lifecycle-status');\n if (statusEl) statusEl.textContent = 'error';\n console.error(error);\n}\n"],"mappings":"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","names":["toKey","createState","serialize","deserialize","registerNap","dispatch","getRegisteredDomains","byWindowId","byPubkey","pendingUpdates","CAP_IDENTITY_READ","CAP_KEYS_BIND","CAP_KEYS_FORWARD","CAP_MEDIA_CONTROL","CAP_NOTIFY_SEND","CAP_NOTIFY_CHANNEL","CAP_THEME_READ","CAP_CONFIG_READ","CAP_RESOURCE_FETCH","CAP_CVM_CALL","CAP_OUTBOX_READ","CAP_OUTBOX_WRITE","CAP_UPLOAD_WRITE","CAP_INTENT_READ","CAP_INTENT_WRITE","createState","serialize","deserialize","cache","cacheKey","sessionRegistry","cache","sessionRegistry","byPubkey","sessionRegistry","manifestCache","originRegistry","scopedKey","STORAGE_KEY","bytesToHex","hexToBytes","isBytes","decode","isBytes","isBytes","decode","bytesToHex","hexToBytes","store","handleMediaMessage","handleSubscribe","generateId","toErrorMessage","toErrorMessage","settle","toErrorMessage","errorMessage","settle","unsupported","destroyWindowSessions","matchesFilter","matchesAnyFilter"]}
|