@kehto/runtime 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +103 -0
- package/dist/index.d.ts +1259 -0
- package/dist/index.js +1492 -0
- package/dist/index.js.map +1 -0
- package/package.json +70 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/enforce.ts","../src/session-registry.ts","../src/acl-state.ts","../src/manifest-cache.ts","../src/replay.ts","../src/event-buffer.ts","../src/runtime.ts","../src/service-dispatch.ts","../src/state-handler.ts","../src/index.ts"],"sourcesContent":["/**\n * enforce.ts — Single ACL enforcement gate for the NIP-5D runtime.\n *\n * All NIP-5D NappletMessage envelopes pass through createNubEnforceGate()\n * before any handler acts. resolveCapabilitiesNub() (re-exported from\n * @kehto/acl) maps NUB message types to required capabilities. No NIP-01\n * dispatch path remains — v1.4 Phase 24 DRIFT-02 deleted the legacy\n * capability-resolution function along with its dead kind + topic\n * dispatch table.\n */\n\nimport type { Capability } from '@kehto/acl/capabilities';\nimport type { NubMessage } from '@kehto/acl';\nimport type { AclCheckEvent } from './types.js';\n\n// Re-export NUB capability resolution for consumers who import through enforce.ts\nexport { resolveCapabilitiesNub } from '@kehto/acl';\nexport type { NubMessage } from '@kehto/acl';\n\n// ─── Enforcement ──────────────────────────────────────────────────────────────\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 */\nexport interface EnforceResult {\n allowed: boolean;\n capability: Capability;\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\n if (onAclCheck) {\n onAclCheck({ identity, capability, decision, message });\n }\n\n return { allowed, capability };\n };\n}\n\n// ─── NUB Enforcement Gate (NIP-5D) ───────────────────────────────────────────\n\n/**\n * Enforcement gate configuration for NIP-5D NUB 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 enforceNub() check\n */\nexport interface NubEnforceConfig {\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 NUB 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 - NUB enforcement configuration\n * @returns An enforceNub function that resolves identity by windowId\n *\n * @example\n * ```ts\n * const gate = createNubEnforceGate({\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 createNubEnforceGate(config: NubEnforceConfig): (windowId: string, capability: Capability, message?: NubMessage) => EnforceResult {\n const { checkAcl, resolveIdentityByWindowId, onAclCheck } = config;\n\n return function enforceNub(windowId: string, capability: Capability, message?: NubMessage): 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\n if (onAclCheck) {\n onAclCheck({ identity, capability, decision, message });\n }\n\n return { allowed, capability };\n };\n}\n\n// ─── Denial Response Helpers ──────────────────────────────────────────────────\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","/**\n * SessionRegistry — windowId to verified napplet pubkey bidirectional mapping.\n *\n * After a successful AUTH handshake, the runtime registers the napplet's\n * verified pubkey here. Both mappings are kept in sync.\n *\n * Unlike the shell singleton version, this is a factory that accepts\n * an optional notifier callback instead of using window.dispatchEvent.\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, setQuota, 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, CAP_ALL,\n} from '@kehto/acl';\nimport type { AclPersistence, AclEntryExternal } from './types.js';\n\n// ─── Capability String-to-Bit Mapping ──────────────────────────────────────\n//\n// Plan 12-10: signer caps (sign:event, sign:nip04, sign:nip44) removed from\n// the canonical 8-domain Capability union. The seven v1.2 additions\n// (identity:read, keys:bind, keys:forward, media:control, notify:send,\n// notify:channel, theme:read) reuse the signer bit slots (bits 5-7) plus\n// three new slots (bits 10-12). Bits must stay unique; entries deserialized\n// from v1.1 storage will have bits 5-7 meaning signer caps under the old\n// interpretation and identity:read / keys:bind / keys:forward under the\n// new interpretation. v1.2 migration tolerates this aliasing because the\n// v1.1 signer surface is no longer reachable by napplets.\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\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};\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\n// ─── Identity Helper ───────────────────────────────────────────────────────\n\nfunction toIdentity(pubkey: string, dTag: string, hash: string): Identity {\n return { pubkey, dTag, hash };\n}\n\n// ─── AclStateContainer Interface ───────────────────────────────────────────\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 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 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 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 state = block(state, id);\n },\n\n unblock(pubkey: string, dTag: string, aggregateHash: string): void {\n const id = toIdentity(pubkey, dTag, aggregateHash);\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 CAP_ALL\n // If blocked, check returns false even for all caps\n return !check(state, id, 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(([key, entry]) => {\n const parts = key.split(':');\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 * 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 // Purge stale entries\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 // Check relay:read ACL on the recipient at delivery time\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","/**\n * runtime.ts — The napplet protocol engine factory.\n *\n * createRuntime(hooks) creates the complete protocol engine that handles\n * NIP-5D NUB domain dispatch, ACL enforcement, subscription lifecycle,\n * signer proxying, and shell command routing.\n *\n * No browser APIs. No DOM. No localStorage. No postMessage.\n * All I/O is delegated to RuntimeAdapter.\n */\n\nimport type { NostrEvent, NostrFilter, NappletMessage } from '@napplet/core';\nimport { createDispatch } from '@napplet/core';\nimport type { NubHandler } from '@napplet/core';\nimport type { Capability } from '@kehto/acl/capabilities';\nimport { ALL_CAPABILITIES } from '@kehto/acl/capabilities';\n\n// NUB message types — types-only imports from @napplet/nub-* peer deps (v1.2).\n// Phase 11-02 / DRIFT-CORE-05: replaces hand-copied widening casts with real\n// upstream unions. Phase 12 handler rewrites narrow per-branch against the\n// canonical discriminated unions.\nimport type { StorageMessage } from '@napplet/nub-storage';\nimport type { IfcMessage } from '@napplet/nub-ifc';\nimport type { RelayNubMessage } from '@napplet/nub-relay';\n/** Alias to match the canonical nub-relay union name used by callers (`RelayMessage` is\n * the base interface; `RelayNubMessage` is the full discriminated union). */\ntype RelayMessage = RelayNubMessage;\n\n// Timer globals are available in all JS runtimes (Node.js, Deno, Bun, browsers)\n// but not included in the ES2022 lib. Declare them to avoid needing DOM lib.\ndeclare function setTimeout(callback: () => void, ms: number): unknown;\ndeclare function clearTimeout(id: unknown): void;\nimport type {\n RuntimeAdapter, ConsentRequest, ConsentHandler,\n ServiceHandler, ServiceRegistry, CompatibilityReport, ServiceInfo,\n} from './types.js';\nimport { routeServiceMessage, notifyServiceWindowDestroyed } from './service-dispatch.js';\nimport { createSessionRegistry } from './session-registry.js';\nimport type { SessionRegistry } from './session-registry.js';\nimport { createAclState } from './acl-state.js';\nimport type { AclStateContainer } from './acl-state.js';\nimport { createManifestCache } from './manifest-cache.js';\nimport type { ManifestCache } from './manifest-cache.js';\nimport { createReplayDetector } from './replay.js';\nimport { createEventBuffer, matchesAnyFilter, RING_BUFFER_SIZE } from './event-buffer.js';\nimport type { SubscriptionEntry } from './event-buffer.js';\nimport { createEnforceGate, createNubEnforceGate, resolveCapabilitiesNub, formatDenialReason } from './enforce.js';\nimport type { NubEnforceConfig } from './enforce.js';\nimport { handleStorageNub } from './state-handler.js';\n\n// ─── Runtime Interface ─────────────────────────────────────────────────────\n\n/**\n * The napplet protocol engine — handles NIP-5D NUB 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 manifest cache. */\n readonly manifestCache: ManifestCache;\n}\n\n// ─── Factory ───────────────────────────────────────────────────────────────\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 // ─── Module-level state ──────────────────────────────────────────────────\n\n const subscriptions = new Map<string, SubscriptionEntry>();\n /** IFC topic subscriptions: Map<topic, Set<windowId>> */\n const ifcSubscriptions = new Map<string, Set<string>>();\n /** IFC channel registry: channelId -> { peerA (opener), peerB (target) }. */\n interface IfcChannel { channelId: string; peerA: string; peerB: string; }\n const ifcChannels = new Map<string, IfcChannel>();\n /** Fast lookup: windowId -> channelIds the window participates in. */\n const ifcChannelsByWindow = new Map<string, Set<string>>();\n let _consentHandler: ConsentHandler | null = null;\n\n // ─── Service Registry (static from hooks + dynamic from registerService) ──\n const serviceRegistry: ServiceRegistry = { ...(hooks.services ?? {}) };\n\n // ─── Registered Services (for compatibility checks) ───────────────────────\n // Tracks service name → ServiceInfo for compatibility checks (Phase 22).\n // Populated by registerService / unregisterService.\n const registeredServices = new Map<string, ServiceInfo>();\n // Pre-populate from static hooks.services\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\n // ─── Undeclared Service Consent Cache (Phase 22) ──────────────────────────\n /** Tracks consented undeclared service usage per session: \"windowId:serviceName\" */\n const undeclaredServiceConsents = new Set<string>();\n\n // ─── Sub-module instances ────────────────────────────────────────────────\n\n const sessionRegistry = createSessionRegistry(hooks.onPendingUpdate);\n const aclState = createAclState(hooks.aclPersistence);\n const manifestCache = createManifestCache(hooks.manifestPersistence);\n const replayDetector = createReplayDetector(\n hooks.getConfigOverrides\n ? () => hooks.getConfigOverrides!().replayWindowSeconds\n : undefined,\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 enforceNub = createNubEnforceGate({\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 ? { dTag: entry.dTag, aggregateHash: entry.aggregateHash } : undefined;\n },\n onAclCheck: hooks.onAclCheck,\n });\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 // Load persisted state\n aclState.load();\n manifestCache.load();\n\n // ─── Compatibility Check (Phase 22) ─────────────────────────────────────\n\n /**\n * Check if a napplet's declared service requirements are satisfied.\n * Called after session establishment but before message dispatch.\n *\n * Returns true if compatible (or permissive mode allows loading).\n * Returns false only in strict mode when required services are missing.\n */\n function checkCompatibility(\n requires: string[],\n windowId: string,\n eventId: string,\n ): boolean {\n if (requires.length === 0) return true;\n\n const available: ServiceInfo[] = Array.from(registeredServices.values());\n const registeredNames = new Set(registeredServices.keys());\n const missing = requires.filter((name) => !registeredNames.has(name));\n const compatible = missing.length === 0;\n\n if (!compatible) {\n const report: CompatibilityReport = { available, missing, compatible };\n hooks.onCompatibilityIssue?.(report);\n\n if (hooks.strictMode) {\n hooks.sendToNapplet(windowId, [\n 'OK', eventId, false,\n `blocked: missing required services: ${missing.join(', ')}`,\n ]);\n return false;\n }\n }\n\n return true;\n }\n\n // ─── Undeclared Service Check (Phase 22) ──────────────────────────────────\n\n /**\n * Check if a napplet is using a service it did not declare in its manifest.\n * If undeclared, raises a consent request via the consent handler.\n *\n * Returns true if the service is declared or consent was previously granted.\n * Returns false if consent is needed (async — caller must wait for resolve).\n * Calls onApproved when consent is granted, allowing the caller to proceed.\n */\n function checkUndeclaredService(\n windowId: string,\n pubkey: string,\n serviceName: string,\n event: NostrEvent,\n onApproved: () => void,\n ): boolean {\n // If the service is not registered, this is not our concern — let normal dispatch handle it\n if (!registeredServices.has(serviceName)) return true;\n\n // Look up the napplet's declared requires via two-step registry lookup\n const nappletPubkey = sessionRegistry.getPubkey(windowId);\n if (!nappletPubkey) return true; // No identity yet — skip check\n const nappletEntry = sessionRegistry.getEntry(nappletPubkey);\n if (!nappletEntry) return true;\n\n const requires = manifestCache.getRequires(nappletEntry.pubkey, nappletEntry.dTag);\n\n // If the service IS declared in requires, no consent needed\n if (requires.includes(serviceName)) return true;\n\n // Check consent cache — already approved this session\n const consentKey = `${windowId}:${serviceName}`;\n if (undeclaredServiceConsents.has(consentKey)) return true;\n\n // Raise consent request\n if (_consentHandler) {\n _consentHandler({\n type: 'undeclared-service',\n windowId,\n pubkey,\n event,\n serviceName,\n resolve: (allowed: boolean) => {\n if (allowed) {\n undeclaredServiceConsents.add(consentKey);\n onApproved();\n }\n // If denied, event is silently dropped\n },\n });\n return false; // Async — caller should not proceed synchronously\n }\n\n // No consent handler registered — silently drop undeclared service usage\n return false;\n }\n\n // ─── Hotkey Forward Handler ──────────────────────────────────────────────\n\n function handleHotkeyForward(event: NostrEvent): void {\n const keyData = {\n key: event.tags?.find((t) => t[0] === 'key')?.[1] ?? '',\n code: event.tags?.find((t) => t[0] === 'code')?.[1] ?? '',\n ctrlKey: event.tags?.find((t) => t[0] === 'ctrl')?.[1] === '1',\n altKey: event.tags?.find((t) => t[0] === 'alt')?.[1] === '1',\n shiftKey: event.tags?.find((t) => t[0] === 'shift')?.[1] === '1',\n metaKey: event.tags?.find((t) => t[0] === 'meta')?.[1] === '1',\n };\n hooks.hotkeys.executeHotkeyFromForward(keyData);\n }\n\n // ─── Shell Command Handler ───────────────────────────────────────────────\n\n function handleShellCommand(event: NostrEvent, windowId: string, topic: string): void {\n function sendOk(success: boolean, reason: string): void {\n hooks.sendToNapplet(windowId, ['OK', event.id, success, reason]);\n }\n\n function sendInterPaneReply(replyTopic: string, content: string): void {\n const responseEvent: Partial<NostrEvent> = {\n kind: 29000, // IPC_PEER — inlined numeric after Phase 24 shim deletion\n pubkey: '',\n created_at: Math.floor(Date.now() / 1000),\n tags: [['t', replyTopic]], content, id: '', sig: '',\n };\n hooks.sendToNapplet(windowId, ['EVENT', '__shell__', responseEvent]);\n sendOk(true, '');\n }\n\n switch (topic) {\n case 'shell:acl-get': {\n const aclEntries = aclState.getAllEntries();\n const nappletEntries = sessionRegistry.getAllEntries();\n const nappletInfoMap: Record<string, { type: string; registeredAt: number }> = {};\n for (const e of nappletEntries) nappletInfoMap[e.pubkey] = { type: e.type, registeredAt: e.registeredAt };\n const merged = [...aclEntries];\n for (const e of nappletEntries) {\n if (!merged.find((a) => a.pubkey === e.pubkey)) {\n merged.push({ pubkey: e.pubkey, capabilities: [...ALL_CAPABILITIES], blocked: false });\n }\n }\n const display = merged.map((e) => ({\n ...e, type: nappletInfoMap[e.pubkey]?.type ?? 'unknown',\n registeredAt: nappletInfoMap[e.pubkey]?.registeredAt ?? 0,\n }));\n sendInterPaneReply('shell:acl-current', JSON.stringify({ entries: display }));\n break;\n }\n case 'shell:acl-revoke': case 'shell:acl-grant': case 'shell:acl-block': case 'shell:acl-unblock': {\n const pk = event.tags?.find((t) => t[0] === 'pubkey')?.[1];\n const cap = event.tags?.find((t) => t[0] === 'cap')?.[1];\n if (!pk) { sendOk(false, 'error: missing pubkey tag'); break; }\n const ne = sessionRegistry.getEntry(pk);\n if (topic === 'shell:acl-revoke' && cap) aclState.revoke(pk, ne?.dTag ?? '', ne?.aggregateHash ?? '', cap as Capability);\n else if (topic === 'shell:acl-grant' && cap) aclState.grant(pk, ne?.dTag ?? '', ne?.aggregateHash ?? '', cap as Capability);\n else if (topic === 'shell:acl-block') aclState.block(pk, ne?.dTag ?? '', ne?.aggregateHash ?? '');\n else if (topic === 'shell:acl-unblock') aclState.unblock(pk, ne?.dTag ?? '', ne?.aggregateHash ?? '');\n aclState.persist();\n const ae = aclState.getEntry(pk, ne?.dTag ?? '', ne?.aggregateHash ?? '');\n sendInterPaneReply('shell:acl-current', JSON.stringify({ entries: ae ? [ae] : [] }));\n break;\n }\n case 'shell:relay-get':\n sendInterPaneReply('shell:relay-current', JSON.stringify(hooks.relayConfig.getRelayConfig()));\n break;\n case 'shell:relay-add': {\n const tier = event.tags?.find((t) => t[0] === 'tier')?.[1];\n const url = event.tags?.find((t) => t[0] === 'url')?.[1];\n if (tier && url) { hooks.relayConfig.addRelay(tier, url); sendInterPaneReply('shell:relay-current', JSON.stringify(hooks.relayConfig.getRelayConfig())); }\n else sendOk(false, 'error: missing tier/url');\n break;\n }\n case 'shell:relay-remove': {\n const tier = event.tags?.find((t) => t[0] === 'tier')?.[1];\n const url = event.tags?.find((t) => t[0] === 'url')?.[1];\n if (tier && url) { hooks.relayConfig.removeRelay(tier, url); sendInterPaneReply('shell:relay-current', JSON.stringify(hooks.relayConfig.getRelayConfig())); }\n else sendOk(false, 'error: missing tier/url');\n break;\n }\n case 'shell:relay-nip66':\n sendInterPaneReply('shell:relay-nip66-data', JSON.stringify(hooks.relayConfig.getNip66Suggestions()));\n break;\n case 'shell:relay-scoped-connect': {\n const url = event.tags?.find((t) => t[0] === 'url')?.[1];\n const subId = event.tags?.find((t) => t[0] === 'sub-id')?.[1];\n const filtersTag = event.tags?.find((t) => t[0] === 'filters')?.[1];\n if (!url || !subId || !filtersTag) { sendOk(false, 'error: missing tags'); break; }\n try {\n const filters = JSON.parse(filtersTag) as NostrFilter[];\n hooks.relayPool?.openScopedRelay(windowId, url, subId, filters, hooks.sendToNapplet);\n sendOk(true, '');\n } catch { sendOk(false, 'error: invalid filters'); }\n break;\n }\n case 'shell:relay-scoped-close':\n hooks.relayPool?.closeScopedRelay(windowId);\n sendOk(true, '');\n break;\n case 'shell:relay-scoped-publish': {\n const et = event.tags?.find((t) => t[0] === 'event')?.[1];\n if (!et) { sendOk(false, 'error: missing event tag'); break; }\n try {\n const signed = JSON.parse(et) as NostrEvent;\n const ok = hooks.relayPool?.publishToScopedRelay(windowId, signed) ?? false;\n sendOk(ok, ok ? '' : 'error: no active scoped relay');\n } catch { sendOk(false, 'error: invalid event JSON'); }\n break;\n }\n case 'shell:create-window': {\n try {\n const payload = JSON.parse(event.content) as { title?: string; class?: string; iframeSrc?: string };\n if (!payload.title || !payload.class) { sendOk(false, 'error: requires title and class'); break; }\n const id = hooks.windowManager.createWindow({ title: payload.title, class: payload.class, iframeSrc: payload.iframeSrc });\n sendOk(!!id, id ? '' : 'error: window creation failed');\n } catch { sendOk(false, 'error: invalid JSON'); }\n break;\n }\n case 'shell:send-dm': {\n if (hooks.dm) {\n const corrId = event.tags?.find((t) => t[0] === 'id')?.[1] ?? '';\n const recipient = event.tags?.find((t) => t[0] === 'p')?.[1];\n let message: string | undefined;\n try { message = JSON.parse(event.content).message; } catch { /* Malformed DM content */ }\n if (!recipient || !message) { sendOk(false, 'error: missing recipient or message'); break; }\n hooks.dm.sendDm(recipient, message).then((result) => {\n const payload = result.success\n ? { success: true, ...(result.eventId ? { eventId: result.eventId } : {}) }\n : { success: false, error: result.error ?? 'unknown error' };\n const response: Partial<NostrEvent> = {\n kind: 29000, // IPC_PEER — inlined numeric after Phase 24 shim deletion\n pubkey: '',\n created_at: Math.floor(Date.now() / 1000),\n tags: [['t', 'shell:send-dm-result'], ['id', corrId]],\n content: JSON.stringify(payload), id: '', sig: '',\n };\n hooks.sendToNapplet(windowId, ['EVENT', '__shell__', response]);\n sendOk(result.success, result.success ? '' : `error: ${result.error}`);\n }).catch(() => { sendOk(false, 'error: DM send failed'); });\n } else sendOk(false, 'error: DM hooks not configured');\n break;\n }\n default:\n sendOk(true, '');\n break;\n }\n }\n\n // ─── NUB Domain Handlers (NIP-5D envelope dispatch) ──────────────────────\n\n function handleRelayMessage(windowId: string, msg: NappletMessage): void {\n // Handler dispatches by sub-action string rather than by msg.type\n // discriminant, so we widen to `unknown` through RelayMessage and access\n // fields per-branch. Each case narrows against the canonical\n // RelayNubMessage discriminant union (subscribe, close, publish,\n // publishEncrypted, query).\n const m = msg as unknown as RelayMessage & {\n subId?: string;\n filters?: NostrFilter[];\n event?: NostrEvent;\n id?: string;\n };\n const dotIdx = msg.type.indexOf('.');\n const action = msg.type.slice(dotIdx + 1);\n\n switch (action) {\n case 'subscribe': {\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 // Replay buffered events\n for (const bufferedEvent of eventBuffer.getBufferedEvents()) {\n if (matchesAnyFilter(bufferedEvent, filters)) deliver(bufferedEvent);\n }\n\n const isShellKind = filters.length > 0 && filters.every((f) => f.kinds?.every((k) => k >= 29000 && k < 30000));\n\n // Service dispatch path\n if (!isShellKind) {\n const relayService = serviceRegistry['relay'] ?? serviceRegistry['relay-pool'];\n const cacheService = !serviceRegistry['relay'] ? serviceRegistry['cache'] : undefined;\n\n if (relayService) {\n relayService.handleMessage(windowId, msg, (resp: NappletMessage) => {\n if (!subscriptions.has(subKey)) return;\n hooks.sendToNapplet(windowId, resp);\n });\n if (cacheService) cacheService.handleMessage(windowId, msg, (resp: NappletMessage) => {\n if (!subscriptions.has(subKey)) return;\n hooks.sendToNapplet(windowId, resp);\n });\n return;\n }\n }\n\n // Fallback: internal relay pool + cache hooks\n const cache = hooks.cache;\n if (cache?.isAvailable() && !isShellKind) {\n cache.query(filters)\n .then((cachedEvents) => { for (const event of cachedEvents) deliver(event); })\n .catch(() => {});\n }\n\n const pool = hooks.relayPool;\n if (pool?.isAvailable() && !isShellKind) {\n const relayUrls = pool.selectRelayTier(filters);\n let eoseSent = false;\n const eoseFallbackTimer = setTimeout(() => {\n if (!eoseSent) { eoseSent = true; hooks.sendToNapplet(windowId, { type: 'relay.eose', subId } as NappletMessage); }\n }, 15_000);\n\n const subscription = pool.subscribe(filters, (item) => {\n if (item === 'EOSE') {\n clearTimeout(eoseFallbackTimer);\n if (!eoseSent) { eoseSent = true; hooks.sendToNapplet(windowId, { type: 'relay.eose', subId } as NappletMessage); }\n return;\n }\n deliver(item as NostrEvent);\n if (cache?.isAvailable() && !isShellKind) {\n try { cache.store(item as NostrEvent); } catch { /* best-effort */ }\n }\n }, relayUrls);\n\n pool.trackSubscription(subKey, () => {\n clearTimeout(eoseFallbackTimer);\n subscription.unsubscribe();\n });\n } else if (!isShellKind) {\n hooks.sendToNapplet(windowId, { type: 'relay.eose', subId } as NappletMessage);\n }\n break;\n }\n\n case 'close': {\n const subId = m.subId ?? '';\n if (!subId) return;\n const subKey = `${windowId}:${subId}`;\n subscriptions.delete(subKey);\n\n const relayService = serviceRegistry['relay'] ?? serviceRegistry['relay-pool'];\n if (relayService) {\n relayService.handleMessage(windowId, msg, () => {});\n }\n hooks.relayPool?.untrackSubscription(subKey);\n hooks.sendToNapplet(windowId, { type: 'relay.closed', subId, message: '' } as NappletMessage);\n break;\n }\n\n case 'publish': {\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 = serviceRegistry['relay'] ?? serviceRegistry['relay-pool'];\n if (relayService) {\n relayService.handleMessage(windowId, msg, (resp: NappletMessage) => {\n hooks.sendToNapplet(windowId, resp);\n });\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 // Buffer and deliver to local subscribers\n eventBuffer.bufferAndDeliver(event, windowId);\n break;\n }\n\n // Shell-mediated encryption path (NUB-08 / SH-C03). Napplets hand the\n // shell a plaintext EventTemplate plus a recipient pubkey; the shell\n // encrypts via its own signer (nip44 default, nip04 opt-in), signs,\n // publishes, and returns a relay.publishEncrypted.result. The signer's\n // nip04/nip44 primitives are SHELL-INTERNAL — no napplet-visible\n // message surface reaches them (SignerProxy was deleted in Plan 12-01).\n case 'publishEncrypted': {\n const id = m.id ?? '';\n const eventTemplate = m.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 function replyPe(ok: boolean, extra: Record<string, unknown> = {}): void {\n hooks.sendToNapplet(windowId, {\n type: 'relay.publishEncrypted.result', id, ok, ...extra,\n } as NappletMessage);\n }\n\n if (!recipient) { replyPe(false, { error: 'missing recipient' }); break; }\n if (encryption !== 'nip44' && encryption !== 'nip04') {\n replyPe(false, { error: `unsupported encryption scheme: ${encryption}` });\n break;\n }\n const peSigner = hooks.auth.getSigner();\n if (!peSigner) { replyPe(false, { error: 'no signer configured' }); break; }\n if (!eventTemplate || typeof eventTemplate !== 'object') {\n replyPe(false, { error: 'invalid event template' });\n break;\n }\n\n // Async encrypt → sign → publish → reply. We drive this off the\n // message tick so the handler remains synchronous from the dispatch\n // switch's perspective (the tests await a microtask to observe reply).\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 // Delegate publishing to the relay service if present — we hand\n // the service a synthesized relay.publish envelope with the\n // signed (already-encrypted) event. The service's reply shape\n // (.result with accepted/ok) is translated back into the\n // publishEncrypted result envelope so napplets see the canonical\n // nub-relay contract.\n const relayService = serviceRegistry['relay'] ?? serviceRegistry['relay-pool'];\n if (relayService) {\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')) {\n const okVal = r.ok ?? r.accepted ?? false;\n replied = true;\n replyPe(okVal, {\n event: signed,\n eventId: signed.id,\n ...(okVal ? {} : { error: r.error ?? r.message ?? 'publish failed' }),\n });\n }\n });\n // If the service never called back synchronously, assume the\n // publish was accepted (fire-and-forget semantics match the\n // existing relay.publish fallback when no service responds).\n if (!replied) {\n replied = true;\n replyPe(true, { event: signed, eventId: signed.id });\n }\n } else 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\n // Also buffer + deliver to local subscribers. The delivered\n // event carries ciphertext only — plaintext never leaves the\n // handler. Swallow buffer errors so they do not mask the reply.\n try { eventBuffer.bufferAndDeliver(signed, windowId); } catch { /* best-effort */ }\n } catch (err) {\n replyPe(false, { error: (err as Error)?.message ?? 'encryption failed' });\n }\n })();\n\n break;\n }\n\n case 'query': {\n const id = m.id ?? '';\n const filters = m.filters ?? [];\n let count = 0;\n for (const event of eventBuffer.getBufferedEvents()) {\n if (matchesAnyFilter(event, filters)) count++;\n }\n hooks.sendToNapplet(windowId, { type: 'relay.query.result', id, count } as NappletMessage);\n break;\n }\n\n default: break;\n }\n }\n\n function handleIdentityMessage(windowId: string, msg: NappletMessage): void {\n // If a host-supplied identity service is registered, route to it.\n const identityService = serviceRegistry['identity'];\n if (identityService) {\n identityService.handleMessage(windowId, msg, (resp: NappletMessage) => {\n hooks.sendToNapplet(windowId, resp);\n });\n return;\n }\n\n // No identity service registered — fall back to hooks.auth.getSigner() for the\n // two read-only identity-mapping cases (getPublicKey/getRelays) so existing hosts\n // do not break. The other 7 actions return spec-correct default/empty payloads\n // so napplets always receive a result envelope rather than a silent drop.\n const id = (msg as NappletMessage & { id?: string }).id ?? '';\n const action = msg.type.slice('identity.'.length);\n const signer = hooks.auth.getSigner();\n\n function sendError(error: string): void {\n hooks.sendToNapplet(windowId, { type: `${msg.type}.error`, id, error } as NappletMessage);\n }\n function sendResult(payload: Record<string, unknown>): void {\n hooks.sendToNapplet(windowId, { type: `${msg.type}.result`, id, ...payload } as NappletMessage);\n }\n\n switch (action) {\n case 'getPublicKey': {\n if (!signer) { sendError('no signer configured'); return; }\n Promise.resolve(signer.getPublicKey?.())\n .then((pubkey) => sendResult({ pubkey }))\n .catch((err: unknown) => sendError((err as Error)?.message ?? 'getPublicKey failed'));\n return;\n }\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 }\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 function handleStorageMessage(windowId: string, msg: NappletMessage): void {\n handleStorageNub(windowId, msg, hooks.sendToNapplet, sessionRegistry, aclState, hooks.statePersistence);\n }\n\n // ─── IFC Channel Registry Helpers ────────────────────────────────────────\n // Per-runtime point-to-point channel bookkeeping used by handleIfcMessage\n // for the channel.* sub-protocol (@napplet/nub-ifc).\n\n function ifcAddChannel(channelId: string, peerA: string, peerB: string): void {\n ifcChannels.set(channelId, { channelId, peerA, peerB });\n for (const w of [peerA, peerB]) {\n let set = ifcChannelsByWindow.get(w);\n if (!set) { set = new Set(); ifcChannelsByWindow.set(w, set); }\n set.add(channelId);\n }\n }\n\n function ifcRemoveChannel(channelId: string): void {\n const ch = ifcChannels.get(channelId);\n if (!ch) return;\n ifcChannels.delete(channelId);\n for (const w of [ch.peerA, ch.peerB]) {\n const set = ifcChannelsByWindow.get(w);\n if (set) { set.delete(channelId); if (set.size === 0) ifcChannelsByWindow.delete(w); }\n }\n }\n\n function ifcPeerOf(channelId: string, self: string): string | null {\n const ch = ifcChannels.get(channelId);\n if (!ch) return null;\n if (ch.peerA === self) return ch.peerB;\n if (ch.peerB === self) return ch.peerA;\n return null;\n }\n\n function ifcGenerateChannelId(): string {\n return hooks.crypto.randomUUID().replace(/-/g, '').slice(0, 32);\n }\n\n /**\n * Resolve an ifc.channel.open target string to a windowId.\n * Accepts either a direct windowId match or a pubkey match against a\n * registered session. Returns null if unresolved.\n */\n function ifcResolveTarget(target: string): string | null {\n if (sessionRegistry.getEntryByWindowId(target)) return target;\n const entries = sessionRegistry.getAllEntries();\n const byPubkey = entries.find((e) => e.pubkey === target);\n return byPubkey?.windowId ?? null;\n }\n\n function handleIfcMessage(windowId: string, msg: NappletMessage): void {\n const m = msg as unknown as IfcMessage & {\n id?: string;\n topic?: string;\n payload?: unknown;\n target?: string;\n channelId?: string;\n };\n const dotIdx = msg.type.indexOf('.');\n const action = msg.type.slice(dotIdx + 1);\n\n switch (action) {\n case 'emit': {\n const topic = m.topic ?? '';\n const payload = m.payload;\n if (!topic) return;\n\n // Deliver to all subscribers of this topic except sender\n const subscribers = ifcSubscriptions.get(topic);\n if (subscribers) {\n for (const subscriberWindowId of subscribers) {\n if (subscriberWindowId === windowId) continue; // don't echo to sender\n hooks.sendToNapplet(subscriberWindowId, { type: 'ifc.event', topic, payload, sender: windowId } as NappletMessage);\n }\n }\n return;\n }\n case 'subscribe': {\n const id = m.id ?? '';\n const topic = m.topic ?? '';\n if (!topic) {\n hooks.sendToNapplet(windowId, { type: 'ifc.subscribe.result', id, error: 'missing topic' } as NappletMessage);\n return;\n }\n let subs = ifcSubscriptions.get(topic);\n if (!subs) { subs = new Set(); ifcSubscriptions.set(topic, subs); }\n subs.add(windowId);\n hooks.sendToNapplet(windowId, { type: 'ifc.subscribe.result', id } as NappletMessage);\n return;\n }\n case 'unsubscribe': {\n const topic = m.topic ?? '';\n if (!topic) return;\n const subs = ifcSubscriptions.get(topic);\n if (subs) {\n subs.delete(windowId);\n if (subs.size === 0) ifcSubscriptions.delete(topic);\n }\n return;\n }\n\n case 'channel.open': {\n const id = m.id ?? '';\n const target = m.target ?? '';\n const peerWindow = ifcResolveTarget(target);\n if (!peerWindow) {\n hooks.sendToNapplet(windowId, { type: 'ifc.channel.open.result', id, error: 'target not found' } as NappletMessage);\n return;\n }\n const channelId = ifcGenerateChannelId();\n ifcAddChannel(channelId, windowId, peerWindow);\n hooks.sendToNapplet(windowId, { type: 'ifc.channel.open.result', id, channelId, peer: peerWindow } as NappletMessage);\n return;\n }\n case 'channel.emit': {\n const channelId = m.channelId ?? '';\n const peer = ifcPeerOf(channelId, windowId);\n if (!peer) return; // unknown channel — silently drop\n hooks.sendToNapplet(peer, { type: 'ifc.channel.event', channelId, sender: windowId, payload: m.payload } as NappletMessage);\n return;\n }\n case 'channel.broadcast': {\n const channels = ifcChannelsByWindow.get(windowId);\n if (!channels) return;\n for (const channelId of channels) {\n const peer = ifcPeerOf(channelId, windowId);\n if (peer) hooks.sendToNapplet(peer, { type: 'ifc.channel.event', channelId, sender: windowId, payload: m.payload } as NappletMessage);\n }\n return;\n }\n case 'channel.list': {\n const id = m.id ?? '';\n const channels: Array<{ id: string; peer: string }> = [];\n const set = ifcChannelsByWindow.get(windowId);\n if (set) {\n for (const channelId of set) {\n const peer = ifcPeerOf(channelId, windowId);\n if (peer) channels.push({ id: channelId, peer });\n }\n }\n hooks.sendToNapplet(windowId, { type: 'ifc.channel.list.result', id, channels } as NappletMessage);\n return;\n }\n case 'channel.close': {\n const channelId = m.channelId ?? '';\n const peer = ifcPeerOf(channelId, windowId);\n if (!peer) return;\n hooks.sendToNapplet(windowId, { type: 'ifc.channel.closed', channelId } as NappletMessage);\n hooks.sendToNapplet(peer, { type: 'ifc.channel.closed', channelId } as NappletMessage);\n ifcRemoveChannel(channelId);\n return;\n }\n\n default: return;\n }\n }\n\n function handleMediaMessage(windowId: string, msg: NappletMessage): void {\n const mediaService = serviceRegistry['media'];\n if (mediaService) {\n mediaService.handleMessage(windowId, msg, (resp: NappletMessage) => {\n hooks.sendToNapplet(windowId, resp);\n });\n return;\n }\n // Fallback: emit spec-correct result for media.session.create so napplets\n // get an envelope even without a registered media service. Other media.*\n // actions are fire-and-forget per @napplet/nub-media and silently dropped.\n if (msg.type === 'media.session.create') {\n const m = msg as NappletMessage & { id?: string; sessionId?: string };\n hooks.sendToNapplet(windowId, {\n type: 'media.session.create.result',\n id: m.id ?? '',\n sessionId: m.sessionId ?? '',\n } as NappletMessage);\n }\n }\n\n function handleKeysMessage(windowId: string, msg: NappletMessage): void {\n const keysService = serviceRegistry['keys'];\n if (keysService) {\n keysService.handleMessage(windowId, msg, (resp: NappletMessage) => {\n hooks.sendToNapplet(windowId, resp);\n });\n return;\n }\n\n // Fallback: route keys.forward directly to hooks.hotkeys.executeHotkeyFromForward\n // so existing hosts continue to receive forwarded hotkeys even without a registered\n // 'keys' service. Translates wire-shape (ctrl/alt/shift/meta) -> DOM-shape\n // (ctrlKey/altKey/shiftKey/metaKey) to match the HotkeyAdapter contract.\n if (msg.type === 'keys.forward') {\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 return;\n }\n\n // Fallback: emit spec-correct keys.registerAction.result so napplets see a\n // reply envelope even without a 'keys' service. Echoes the action.defaultKey\n // as the binding hint (stub behavior — real shell-side bindings arrive later\n // via DRIFT-SHELL-06 keys-forwarder / future shell push path).\n if (msg.type === 'keys.registerAction') {\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 return;\n }\n\n // keys.unregisterAction: fire-and-forget per @napplet/nub-keys — nothing to emit.\n // Unknown keys.* sub-actions: silently drop (spec-consistent with default branch).\n }\n\n function handleNotifyMessage(windowId: string, msg: NappletMessage): void {\n const notifyService = serviceRegistry['notify'];\n if (notifyService) {\n notifyService.handleMessage(windowId, msg, (resp: NappletMessage) => {\n hooks.sendToNapplet(windowId, resp);\n });\n return;\n }\n // Fallback: emit spec-correct result envelopes for notify.send and\n // notify.permission.request so napplets see a reply even without a\n // registered 'notify' service. Other actions (dismiss/badge/channel.register)\n // are fire-and-forget per @napplet/nub-notify and produce no envelope.\n if (msg.type === 'notify.send') {\n const m = msg as NappletMessage & { id?: string };\n hooks.sendToNapplet(windowId, {\n type: 'notify.send.result',\n id: m.id ?? '',\n notificationId: `shell-${Date.now()}`,\n } as NappletMessage);\n } else if (msg.type === 'notify.permission.request') {\n const m = msg as NappletMessage & { id?: string };\n hooks.sendToNapplet(windowId, {\n type: 'notify.permission.result',\n id: m.id ?? '',\n granted: true,\n } as NappletMessage);\n }\n }\n\n // Canonical theme fallback — keep values synchronized with\n // @kehto/services's DEFAULT_THEME constant (packages/services/src/theme-service.ts).\n // Not imported from @kehto/services to avoid a runtime->services dependency\n // (services already depends on runtime; one-way only).\n const THEME_FALLBACK_DEFAULT = {\n colors: { background: '#0a0a0a', text: '#e0e0e0', primary: '#7aa2f7' },\n } as const;\n\n function handleThemeMessage(windowId: string, msg: NappletMessage): void {\n const themeService = serviceRegistry['theme'];\n if (themeService) {\n themeService.handleMessage(windowId, msg, (resp: NappletMessage) => {\n hooks.sendToNapplet(windowId, resp);\n });\n return;\n }\n // Fallback: emit spec-correct theme.get.result with the canonical default\n // theme so napplets always see a reply envelope even without a registered\n // 'theme' service. theme.changed is shell-initiated (Plan 13-02) —\n // napplets should never send it; silently drop if they do.\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\n // ─── NUB dispatch (Phase 14 / DISPATCH-01/02/03) ─────────────────────────\n // Per-runtime createDispatch() instance — isolated from module-level singleton\n // to match kehto's closed-over-state pattern (serviceRegistry, aclState, etc.).\n // currentWindowId is set inside handleMessage() before nubDispatch.dispatch(),\n // cleared via try/finally. Named adapters preserve stack traces on error paths.\n let currentWindowId: string | null = null;\n const nubDispatch = createDispatch();\n\n const relayAdapter: NubHandler = (msg) => {\n if (currentWindowId === null) return;\n handleRelayMessage(currentWindowId, msg);\n };\n const identityAdapter: NubHandler = (msg) => {\n if (currentWindowId === null) return;\n handleIdentityMessage(currentWindowId, msg);\n };\n const keysAdapter: NubHandler = (msg) => {\n if (currentWindowId === null) return;\n handleKeysMessage(currentWindowId, msg);\n };\n const mediaAdapter: NubHandler = (msg) => {\n if (currentWindowId === null) return;\n handleMediaMessage(currentWindowId, msg);\n };\n const notifyAdapter: NubHandler = (msg) => {\n if (currentWindowId === null) return;\n handleNotifyMessage(currentWindowId, msg);\n };\n const storageAdapter: NubHandler = (msg) => {\n if (currentWindowId === null) return;\n handleStorageMessage(currentWindowId, msg);\n };\n const ifcAdapter: NubHandler = (msg) => {\n if (currentWindowId === null) return;\n handleIfcMessage(currentWindowId, msg);\n };\n const themeAdapter: NubHandler = (msg) => {\n if (currentWindowId === null) return;\n handleThemeMessage(currentWindowId, msg);\n };\n\n nubDispatch.registerNub('relay', relayAdapter);\n nubDispatch.registerNub('identity', identityAdapter);\n nubDispatch.registerNub('keys', keysAdapter);\n nubDispatch.registerNub('media', mediaAdapter);\n nubDispatch.registerNub('notify', notifyAdapter);\n nubDispatch.registerNub('storage', storageAdapter);\n nubDispatch.registerNub('ifc', ifcAdapter);\n nubDispatch.registerNub('theme', themeAdapter);\n\n // ─── Main message handler ────────────────────────────────────────────────\n\n function handleMessage(windowId: string, msg: unknown): void {\n // NIP-5D envelope-only dispatch — no legacy array support (clean break)\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; // malformed type — no domain.action\n\n // ACL enforcement: resolve capability requirement, enforce if non-null\n const caps = resolveCapabilitiesNub(envelope);\n if (caps.senderCap) {\n const result = enforceNub(windowId, caps.senderCap as Capability, envelope);\n if (!result.allowed) {\n const id = (envelope as NappletMessage & { id?: string }).id ?? '';\n hooks.sendToNapplet(windowId, { type: `${envelope.type}.error`, id, error: formatDenialReason(result.capability) } as NappletMessage);\n return;\n }\n }\n\n // Phase 14 / DISPATCH-03: delegate to createDispatch() — no domain-specific\n // branching. dispatch() returns false for unknown domains; matches the old\n // switch default (silent drop per NIP-5D spec). currentWindowId is a runtime-\n // scoped closure variable read by each adapter; try/finally ensures it is\n // always cleared even if a handler throws.\n currentWindowId = windowId;\n try {\n nubDispatch.dispatch(envelope);\n } finally {\n currentWindowId = null;\n }\n }\n\n // ─── Public interface ────────────────────────────────────────────────────\n\n const runtimeInstance: Runtime = {\n handleMessage,\n\n injectEvent(topic: string, payload: unknown): void {\n const uuid = hooks.crypto.randomUUID().replace(/-/g, '').slice(0, 64).padEnd(64, '0');\n const event: NostrEvent = {\n id: uuid,\n pubkey: '0'.repeat(64),\n created_at: Math.floor(Date.now() / 1000),\n kind: 29000, // IPC_PEER — inlined numeric after Phase 24 shim deletion\n tags: [['t', topic]],\n content: JSON.stringify(payload),\n sig: '0'.repeat(128),\n };\n eventBuffer.bufferAndDeliver(event, null);\n },\n\n destroy(): void {\n manifestCache.persist();\n aclState.persist();\n replayDetector.clear();\n subscriptions.clear();\n ifcSubscriptions.clear();\n ifcChannels.clear();\n ifcChannelsByWindow.clear();\n eventBuffer.clear();\n registeredServices.clear();\n undeclaredServiceConsents.clear();\n },\n\n registerConsentHandler(handler: ConsentHandler): void {\n _consentHandler = handler;\n },\n\n registerService(name: string, handler: ServiceHandler): void {\n serviceRegistry[name] = handler;\n // Populate registeredServices Map for compatibility checks (Phase 22)\n registeredServices.set(name, {\n name: handler.descriptor.name,\n version: handler.descriptor.version,\n description: handler.descriptor.description,\n });\n },\n\n unregisterService(name: string): void {\n delete serviceRegistry[name];\n // Remove from registeredServices Map for compatibility checks (Phase 22)\n registeredServices.delete(name);\n },\n\n destroyWindow(windowId: string): void {\n // Clean up subscriptions for this window\n for (const [key] of subscriptions) {\n if (key.startsWith(`${windowId}:`)) {\n subscriptions.delete(key);\n hooks.relayPool?.untrackSubscription(key);\n }\n }\n // Clean up IFC subscriptions for this window\n for (const [topic, subs] of ifcSubscriptions) {\n subs.delete(windowId);\n if (subs.size === 0) ifcSubscriptions.delete(topic);\n }\n // Clean up IFC channels this window participates in — notify each peer\n // with ifc.channel.closed before removing the channel entry.\n const channelIds = ifcChannelsByWindow.get(windowId);\n if (channelIds) {\n // Snapshot because ifcRemoveChannel mutates the set during iteration.\n for (const channelId of [...channelIds]) {\n const peer = ifcPeerOf(channelId, windowId);\n if (peer) {\n hooks.sendToNapplet(peer, { type: 'ifc.channel.closed', channelId } as NappletMessage);\n }\n ifcRemoveChannel(channelId);\n }\n }\n // Notify service handlers\n notifyServiceWindowDestroyed(windowId, serviceRegistry);\n },\n\n get sessionRegistry() { return sessionRegistry; },\n get aclState() { return aclState; },\n get manifestCache() { return manifestCache; },\n };\n\n return runtimeInstance;\n}\n","/**\n * service-dispatch.ts — NIP-5D envelope routing for registered services.\n *\n * Routes NappletMessage envelopes to the correct ServiceHandler based on the\n * message type domain prefix. NUB-domain services (signer.*, relay.*, storage.*)\n * are routed by the domain part of message.type. IFC-routed services (audio,\n * notifications) receive ifc.emit messages and are routed by topic prefix.\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 * NUB-domain services are routed by the domain prefix of message.type\n * (e.g., 'signer.signEvent' -> 'signer' service).\n *\n * IFC-routed services receive ifc.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 const send = (msg: NappletMessage): void => sendToNapplet(windowId, msg);\n\n // NUB-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, send);\n return true;\n }\n\n // IFC-routed services: audio and notifications receive ifc.emit with topic prefix\n if (message.type === 'ifc.emit' && typeof (message as any).topic === 'string') {\n const prefix = ((message as any).topic as string).split(':')[0];\n const ifcHandler = services[prefix];\n if (ifcHandler) {\n ifcHandler.handleMessage(windowId, message, send);\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","/**\n * state-handler.ts — Storage NUB request handler using persistence hooks.\n *\n * Handles napplet storage operations (get, set, remove, keys) via the\n * canonical `@napplet/nub-storage` NIP-5D envelope surface. Delegates\n * storage to StatePersistence. No localStorage, no legacy NIP-01 dispatch.\n */\n\nimport type { NappletMessage } from '@napplet/core';\n// Phase 11-02 / DRIFT-CORE-05: narrow storage dispatch to canonical nub-storage union.\nimport type { StorageMessage } from '@napplet/nub-storage';\nimport type { SendToNapplet, StatePersistence } from './types.js';\nimport type { SessionRegistry } from './session-registry.js';\nimport type { AclStateContainer } from './acl-state.js';\n\nfunction scopedKey(dTag: string, aggregateHash: string, userKey: string): string {\n return `napplet-state:${dTag}:${aggregateHash}:${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 NUB storage message from a napplet.\n * Routes to the canonical four `@napplet/nub-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/nub-storage` union (it was a\n * kehto unilateral extension); it now produces a `storage.clear.error` envelope.\n * Internal lifecycle cleanup still uses the `cleanupNappState()` helper below —\n * it is not napplet-reachable.\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/nub-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 { handleStorageNub } from '@kehto/runtime';\n *\n * handleStorageNub(windowId, { type: 'storage.get', id: 'q1', key: 'draft' },\n * sendToNapplet, sessionRegistry, aclState, statePersistence);\n * ```\n */\nexport function handleStorageNub(\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 };\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 sendErrorNub(error: string): void {\n sendToNapplet(windowId, { type: `${msg.type}.error`, id, error } as NappletMessage);\n }\n\n // Identity lookup via windowId (NIP-5D path)\n const entry = sessionRegistry.getEntryByWindowId(windowId);\n if (!entry) { sendErrorNub('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 switch (action) {\n case 'get': {\n const key = m.key as string;\n if (!key) { sendErrorNub('missing key'); return; }\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/nub-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) { sendErrorNub('missing key'); return; }\n const quota = aclState.getStateQuota(pubkey ?? '', dTag, aggregateHash);\n const sk = scopedKey(dTag, aggregateHash, key);\n const newWriteBytes = byteLength(sk + value);\n const existingBytes = statePersistence.calculateBytes(prefix, key);\n if (existingBytes + newWriteBytes > quota) {\n sendErrorNub(`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) { sendErrorNub('missing key'); return; }\n statePersistence.remove(scopedKey(dTag, aggregateHash, 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/nub-storage union. Napplets hitting this branch receive an\n // explicit `storage.clear.error` envelope. Internal lifecycle cleanup uses\n // cleanupNappState() below (not napplet-reachable).\n sendErrorNub('storage.clear is not in @napplet/nub-storage; action not supported');\n break;\n }\n case 'keys': {\n const newKeys = statePersistence.keys(prefix);\n const legacyKeys = legacyPrefix ? statePersistence.keys(legacyPrefix) : [];\n const userKeySet = new Set<string>();\n for (const k of newKeys) userKeySet.add(k.startsWith(prefix) ? k.slice(prefix.length) : k);\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 sendErrorNub(`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","// @kehto/runtime — Browser-agnostic protocol engine for the napplet protocol.\n\n// ─── Types ─────────────────────────────────────────────────────────────────\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 ServiceHandler,\n ServiceRegistry,\n CompatibilityReport,\n ServiceInfo,\n ShellSecretPersistence,\n GuidPersistence,\n HashVerifierAdapter,\n VerificationCacheEntry,\n RuntimeConfigOverrides,\n NappletMessage,\n} from './types.js';\n\n// ─── Enforcement Gate ──────────────────────────────────────────────────────\nexport { createEnforceGate, createNubEnforceGate, resolveCapabilitiesNub, formatDenialReason } from './enforce.js';\nexport type { EnforceResult, EnforceConfig, NubEnforceConfig, IdentityResolver, AclChecker, NubMessage } from './enforce.js';\n\n// ─── SessionRegistry ──────────────────────────────────────────────────────\nexport { createSessionRegistry, createNappKeyRegistry } from './session-registry.js';\nexport type { SessionRegistry, NappKeyRegistry } from './session-registry.js';\n\n// ─── ACL State Container ──────────────────────────────────────────────────\nexport { createAclState } from './acl-state.js';\nexport type { AclStateContainer } from './acl-state.js';\n\n// ─── Manifest Cache ────────────────────────────────────────────────────────\nexport { createManifestCache } from './manifest-cache.js';\nexport type { ManifestCache } from './manifest-cache.js';\n\n// ─── Replay Detection ──────────────────────────────────────────────────────\nexport { createReplayDetector } from './replay.js';\nexport type { ReplayDetector } from './replay.js';\n\n// ─── Event Buffer ──────────────────────────────────────────────────────────\nexport { createEventBuffer, matchesFilter, matchesAnyFilter, RING_BUFFER_SIZE } from './event-buffer.js';\nexport type { EventBuffer, SubscriptionEntry } from './event-buffer.js';\n\n// ─── Runtime Factory (primary entry point) ─────────────────────────────────\nexport { createRuntime } from './runtime.js';\nexport type { Runtime } from './runtime.js';\n\n// ─── State Handler ─────────────────────────────────────────────────────────\nexport { handleStorageNub, cleanupNappState } from './state-handler.js';\n\n// ─── Service Dispatch ─────────────────────────────────────────────────────\nexport { routeServiceMessage, notifyServiceWindowDestroyed } from './service-dispatch.js';\n\n// ─── Re-exports from canonical homes (Phase 24 DRIFT-01) ───────────────────\n// The former @napplet/core compatibility shim was deleted in Phase 24 after\n// @napplet/core @0.2.0 stabilized on npm. Live types are now re-exported from\n// their rightful homes:\n// - Capability → @kehto/acl/capabilities (canonical string union + ALL_CAPABILITIES)\n// - ServiceDescriptor → ./types.js (runtime-internal service metadata)\n// The legacy bus-kind value-union type and the NIP-01 kind + topic + auth +\n// destructive-kind constants are no longer exported — all dead after the\n// v1.2 NIP-5D migration (Phase 24 DRIFT-02 deleted the remaining callers).\nexport type { Capability } from '@kehto/acl/capabilities';\nexport { ALL_CAPABILITIES } from '@kehto/acl/capabilities';\nexport type { ServiceDescriptor } from './types.js';\n"],"mappings":"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","names":["deliver","replyPe","ALL_CAPABILITIES"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kehto/runtime",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Browser-agnostic protocol engine for the napplet protocol \u2014 message dispatch, ACL enforcement, AUTH handshake",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"sideEffects": false,
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@kehto/acl": "workspace:*",
|
|
24
|
+
"@noble/hashes": "^2.0.0",
|
|
25
|
+
"@noble/curves": "^2.0.0"
|
|
26
|
+
},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"@napplet/core": "^0.2.1",
|
|
29
|
+
"@napplet/nub-identity": "^0.2.1",
|
|
30
|
+
"@napplet/nub-ifc": "^0.2.1",
|
|
31
|
+
"@napplet/nub-keys": "^0.2.1",
|
|
32
|
+
"@napplet/nub-media": "^0.2.1",
|
|
33
|
+
"@napplet/nub-notify": "^0.2.1",
|
|
34
|
+
"@napplet/nub-relay": "^0.2.1",
|
|
35
|
+
"@napplet/nub-storage": "^0.2.1",
|
|
36
|
+
"@napplet/nub-theme": "^0.2.1"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@napplet/core": "^0.2.1",
|
|
40
|
+
"@napplet/nub-identity": "^0.2.1",
|
|
41
|
+
"@napplet/nub-ifc": "^0.2.1",
|
|
42
|
+
"@napplet/nub-keys": "^0.2.1",
|
|
43
|
+
"@napplet/nub-media": "^0.2.1",
|
|
44
|
+
"@napplet/nub-notify": "^0.2.1",
|
|
45
|
+
"@napplet/nub-relay": "^0.2.1",
|
|
46
|
+
"@napplet/nub-storage": "^0.2.1",
|
|
47
|
+
"@napplet/nub-theme": "^0.2.1",
|
|
48
|
+
"tsup": "^8.5.0",
|
|
49
|
+
"typescript": "^5.9.3",
|
|
50
|
+
"vitest": "^4.1.2"
|
|
51
|
+
},
|
|
52
|
+
"scripts": {
|
|
53
|
+
"build": "tsup",
|
|
54
|
+
"type-check": "tsc --noEmit",
|
|
55
|
+
"test:unit": "echo 'no unit tests yet'"
|
|
56
|
+
},
|
|
57
|
+
"license": "MIT",
|
|
58
|
+
"repository": {
|
|
59
|
+
"type": "git",
|
|
60
|
+
"url": "git+https://github.com/kehto/runtime.git",
|
|
61
|
+
"directory": "packages/runtime"
|
|
62
|
+
},
|
|
63
|
+
"keywords": [
|
|
64
|
+
"nostr",
|
|
65
|
+
"napplet",
|
|
66
|
+
"kehto",
|
|
67
|
+
"runtime",
|
|
68
|
+
"protocol-engine"
|
|
69
|
+
]
|
|
70
|
+
}
|