@kehto/services 0.12.1 → 0.12.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -2
- package/dist/index.d.ts +3 -1
- package/dist/index.js +11 -3
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/audio-service.ts","../src/notification-service.ts","../src/identity-service.ts","../src/relay-pool-service.ts","../src/cache-service.ts","../src/coordinated-relay.ts","../src/keys-service.ts","../src/browser-media-bridge.ts","../src/media-service.ts","../src/notify-service.ts","../src/theme-service.ts","../src/config-service.ts","../src/resource-service.ts","../src/outbox-service.ts","../src/relay-pool-outbox-router.ts","../src/upload-service.ts","../src/http-uploader.ts","../src/intent-service.ts","../src/catalog-intent-resolver.ts","../src/manifest-intent-catalog.ts","../src/cvm-service.ts","../src/link-service.ts","../src/lists-service.ts","../src/serial-service.ts","../src/ble-service.ts","../src/webrtc-service.ts","../src/common-service.ts"],"sourcesContent":["/**\n * audio-service.ts — Audio source registry as a ServiceHandler.\n *\n * Tracks which napplet windows are producing audio. Shell hosts wire this\n * into the runtime via registerService('audio', createAudioService(opts)).\n * Browser-agnostic — no DOM, no window, no postMessage.\n */\n\nimport type { NappletMessage } from '@napplet/core';\n// DRIFT-CORE-06 — Phase 11-deviation: ServiceDescriptor dropped from @napplet/core\n// v0.2.0+ (napplet phase-81). Re-exported from @kehto/runtime (canonical home after Phase 24 DRIFT-01).\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\nimport type { AudioSource, AudioServiceOptions } from './types.js';\n\n/** Audio service version — follows semver. */\nconst AUDIO_SERVICE_VERSION = '1.0.0';\n\n/**\n * Create an audio service handler.\n *\n * The audio service is a state registry that tracks active audio sources\n * per napplet window. Napplets announce audio state via `audio:*` topic\n * events; the service tracks sources and can relay mute commands back.\n *\n * @param options - Optional configuration (onChange callback for UI updates)\n * @returns A ServiceHandler to register with the runtime\n *\n * @example\n * ```ts\n * import { createAudioService } from '@kehto/services';\n *\n * const audio = createAudioService({\n * onChange: (sources) => {\n * // Update UI with current audio sources\n * for (const [windowId, source] of sources) {\n * console.log(`${source.title} (${source.muted ? 'muted' : 'playing'})`);\n * }\n * },\n * });\n *\n * runtime.registerService('audio', audio);\n * ```\n */\nexport function createAudioService(options?: AudioServiceOptions): ServiceHandler {\n const sources = new Map<string, AudioSource>();\n const onChange = options?.onChange;\n\n function notify(): void {\n onChange?.(new Map(sources));\n }\n\n const descriptor: ServiceDescriptor = {\n name: 'audio',\n version: AUDIO_SERVICE_VERSION,\n description: 'Audio source registry — tracks active audio sources per napplet window',\n };\n\n return {\n descriptor,\n\n handleMessage(windowId: string, message: NappletMessage, send: (msg: NappletMessage) => void): void {\n if (message.type !== 'inc.emit') return;\n const incMessage = message as NappletMessage & { topic?: unknown; payload?: unknown };\n const topic = typeof incMessage.topic === 'string' ? incMessage.topic : undefined;\n if (!topic?.startsWith('audio:')) return;\n\n const action = topic.slice(6); // 'audio:'.length === 6\n const payload = incMessage.payload && typeof incMessage.payload === 'object'\n ? incMessage.payload as Record<string, unknown>\n : {};\n\n switch (action) {\n case 'register': {\n const nappletClass = typeof payload.nappletClass === 'string' ? payload.nappletClass : '';\n const title = typeof payload.title === 'string' ? payload.title : '';\n sources.set(windowId, { windowId, nappletClass, title, muted: false });\n notify();\n break;\n }\n\n case 'unregister': {\n if (sources.delete(windowId)) {\n notify();\n }\n break;\n }\n\n case 'state-changed': {\n const source = sources.get(windowId);\n if (!source) return;\n if (typeof payload.title === 'string') {\n source.title = payload.title;\n }\n notify();\n break;\n }\n\n case 'mute': {\n const targetWindowId = typeof payload.windowId === 'string'\n ? payload.windowId\n : windowId;\n const muted = payload.muted === true;\n\n const source = sources.get(targetWindowId);\n if (source) {\n source.muted = muted;\n notify();\n }\n\n send({ type: 'inc.event', topic: 'napplet:audio-muted', payload: { muted } } as NappletMessage);\n break;\n }\n\n default:\n // Unknown audio action — ignore\n break;\n }\n },\n\n onWindowDestroyed(windowId: string): void {\n if (sources.delete(windowId)) {\n notify();\n }\n },\n };\n}\n","\nimport type { NappletMessage } from '@napplet/core';\n// DRIFT-CORE-06 — Phase 11-deviation: ServiceDescriptor dropped from @napplet/core\n// v0.2.0+ (napplet phase-81). Re-exported from @kehto/runtime (canonical home after Phase 24 DRIFT-01).\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\nimport type { Notification, NotificationServiceOptions } from './types.js';\n\n/** Notification service version — follows semver. */\nconst NOTIFICATION_SERVICE_VERSION = '1.0.0';\n\n/** Default maximum notifications per window. */\nconst DEFAULT_MAX_PER_WINDOW = 100;\n\n/** Counter for generating unique notification IDs. */\nlet idCounter = 0;\n\n/**\n * Generate a unique notification ID.\n */\nfunction generateId(): string {\n idCounter++;\n return `notif-${Date.now()}-${idCounter}`;\n}\n\ninterface NotificationStore {\n notifications: Map<string, Notification[]>;\n onChange?: (notifications: Notification[]) => void;\n maxPerWindow: number;\n}\n\nfunction getAllNotifications(store: NotificationStore): Notification[] {\n const all: Notification[] = [];\n for (const windowNotifs of store.notifications.values()) {\n all.push(...windowNotifs);\n }\n return all;\n}\n\nfunction notify(store: NotificationStore): void {\n store.onChange?.(getAllNotifications(store));\n}\n\nfunction getWindowNotifications(store: NotificationStore, windowId: string): Notification[] {\n let list = store.notifications.get(windowId);\n if (!list) {\n list = [];\n store.notifications.set(windowId, list);\n }\n return list;\n}\n\nfunction enforceLimit(store: NotificationStore, list: Notification[]): void {\n while (list.length > store.maxPerWindow) {\n list.shift();\n }\n}\n\nfunction findById(store: NotificationStore, id: string): [string, Notification, number] | undefined {\n for (const [windowId, list] of store.notifications) {\n const index = list.findIndex((n) => n.id === id);\n if (index !== -1) {\n return [windowId, list[index], index];\n }\n }\n return undefined;\n}\n\nfunction createNotification(\n store: NotificationStore,\n windowId: string,\n title: string,\n body: string,\n): Notification {\n const notification: Notification = {\n id: generateId(),\n windowId,\n title,\n body,\n read: false,\n createdAt: Math.floor(Date.now() / 1000),\n };\n const list = getWindowNotifications(store, windowId);\n list.push(notification);\n enforceLimit(store, list);\n notify(store);\n return notification;\n}\n\nfunction dismissNotification(store: NotificationStore, id: string): void {\n const found = findById(store, id);\n if (!found) return;\n const [foundWindowId, , index] = found;\n const list = store.notifications.get(foundWindowId);\n if (!list) return;\n list.splice(index, 1);\n if (list.length === 0) store.notifications.delete(foundWindowId);\n notify(store);\n}\n\nfunction markNotificationRead(store: NotificationStore, id: string): void {\n const found = findById(store, id);\n if (!found) return;\n const [, notification] = found;\n if (!notification.read) {\n notification.read = true;\n notify(store);\n }\n}\n\nfunction handleNotifyEnvelope(\n store: NotificationStore,\n windowId: string,\n action: string,\n msg: NappletMessage & Record<string, unknown>,\n send: (msg: NappletMessage) => void,\n): void {\n switch (action) {\n case 'create': {\n const title = typeof msg.title === 'string' ? msg.title : '';\n const body = typeof msg.body === 'string' ? msg.body : '';\n const notification = createNotification(store, windowId, title, body);\n send({ type: 'notify.created', id: notification.id } as NappletMessage);\n break;\n }\n\n case 'dismiss': {\n const notifId = typeof msg.notificationId === 'string' ? msg.notificationId : '';\n if (notifId) dismissNotification(store, notifId);\n break;\n }\n\n case 'read': {\n const notifId = typeof msg.notificationId === 'string' ? msg.notificationId : '';\n if (notifId) markNotificationRead(store, notifId);\n break;\n }\n\n case 'list': {\n const windowNotifs = store.notifications.get(windowId) ?? [];\n send({ type: 'notify.listed', notifications: windowNotifs } as NappletMessage);\n break;\n }\n\n default:\n break;\n }\n}\n\nfunction handleIncNotification(\n store: NotificationStore,\n windowId: string,\n action: string,\n payload: Record<string, unknown>,\n send: (msg: NappletMessage) => void,\n): void {\n switch (action) {\n case 'create': {\n const title = typeof payload.title === 'string' ? payload.title : '';\n const body = typeof payload.body === 'string' ? payload.body : '';\n const notification = createNotification(store, windowId, title, body);\n send({ type: 'inc.event', topic: 'notifications:created', payload: { id: notification.id } } as NappletMessage);\n break;\n }\n\n case 'dismiss': {\n const id = typeof payload.id === 'string' ? payload.id : '';\n if (id) dismissNotification(store, id);\n break;\n }\n\n case 'read': {\n const id = typeof payload.id === 'string' ? payload.id : '';\n if (id) markNotificationRead(store, id);\n break;\n }\n\n case 'list': {\n const windowNotifs = store.notifications.get(windowId) ?? [];\n send({ type: 'inc.event', topic: 'notifications:listed', payload: { notifications: windowNotifs } } as NappletMessage);\n break;\n }\n\n default:\n break;\n }\n}\n\n/**\n * Create a notification service handler.\n *\n * The notification service is a state registry that tracks notifications\n * per napplet window. Napplets create and manage notifications via\n * `notifications:*` topic events; the shell host controls presentation\n * via the onChange callback.\n *\n * @param options - Optional configuration (onChange callback, maxPerWindow limit)\n * @returns A ServiceHandler to register with the runtime\n *\n * @example\n * ```ts\n * import { createNotificationService } from '@kehto/services';\n *\n * const notifications = createNotificationService({\n * onChange: (list) => {\n * const unread = list.filter(n => !n.read);\n * updateBadge(unread.length);\n * },\n * maxPerWindow: 50,\n * });\n *\n * runtime.registerService('notifications', notifications);\n * ```\n */\nexport function createNotificationService(options?: NotificationServiceOptions): ServiceHandler {\n const store: NotificationStore = {\n notifications: new Map<string, Notification[]>(),\n onChange: options?.onChange,\n maxPerWindow: options?.maxPerWindow ?? DEFAULT_MAX_PER_WINDOW,\n };\n\n const descriptor: ServiceDescriptor = {\n name: 'notifications',\n version: NOTIFICATION_SERVICE_VERSION,\n description: 'Notification state registry — tracks notifications per napplet window',\n };\n\n return {\n descriptor,\n\n handleMessage(windowId: string, message: NappletMessage, send: (msg: NappletMessage) => void): void {\n const msg = message as NappletMessage & Record<string, unknown>;\n\n if (message.type.startsWith('notify.')) {\n handleNotifyEnvelope(store, windowId, message.type.slice(7), msg, send);\n return;\n }\n\n if (message.type !== 'inc.emit') return;\n const topic = msg.topic as string | undefined;\n if (!topic?.startsWith('notifications:')) return;\n\n const payload = ((msg.payload ?? {}) as Record<string, unknown>);\n handleIncNotification(store, windowId, topic.slice(14), payload, send);\n },\n\n onWindowDestroyed(windowId: string): void {\n if (store.notifications.delete(windowId)) {\n notify(store);\n }\n },\n };\n}\n","/**\n * identity-service.ts — NIP-5D identity nap reference service.\n *\n * MIGRATION from signer-service (v1.1 -> v1.2):\n * - signer.getPublicKey -> identity.getPublicKey (same shell state)\n * - signer.getRelays -> identity.getRelays (same shell state)\n * - signer.signEvent -> DELETED (no napplet-visible path; shell signs\n * internally inside relay.publish)\n * - signer.nip04.encrypt/decrypt -> DELETED\n * - signer.nip44.encrypt/decrypt -> DELETED (shell encrypts internally\n * inside relay.publishEncrypted)\n *\n * Identity is strictly read-only per NAP-IDENTITY: napplets learn *about* the\n * user but cannot act *as* the user — no signing, encryption, or decryption.\n * (An `identity.decrypt` capability was briefly added in v1.8 and removed as a\n * spec violation; decryption belongs to the runtime, inline over the wire.)\n *\n * Handles 9 identity.* request types from @napplet/nap/identity. getPublicKey\n * and getRelays return real values sourced from hooks.auth.getSigner(); the\n * remaining read-only queries can be backed by optional host providers. When a\n * provider is absent, the service keeps returning spec-shaped empty results.\n */\n\nimport type { NappletMessage } from '@napplet/core';\nimport type { ServiceHandler, Signer } from '@kehto/runtime';\nimport type {\n IdentityGetBadgesMessage,\n IdentityGetBlockedMessage,\n IdentityGetFollowsMessage,\n IdentityGetListMessage,\n IdentityGetMutesMessage,\n IdentityGetProfileMessage,\n IdentityGetPublicKeyResultMessage,\n IdentityGetRelaysResultMessage,\n IdentityGetProfileResultMessage,\n IdentityGetFollowsResultMessage,\n IdentityGetListResultMessage,\n IdentityGetZapsResultMessage,\n IdentityGetZapsMessage,\n IdentityGetMutesResultMessage,\n IdentityGetBlockedResultMessage,\n IdentityGetBadgesResultMessage,\n RelayPermission,\n} from '@napplet/nap/identity/types';\n\n/** Identity service version — follows semver. */\nconst IDENTITY_SERVICE_VERSION = '1.0.0';\n\n/** A value that may be returned synchronously or through a Promise. */\nexport type MaybePromise<T> = T | Promise<T>;\n\ntype IdentityProviderResult = NappletMessage & { error?: string };\ntype SendIdentityMessage = (msg: NappletMessage) => void;\n\nfunction sendProviderError<T extends IdentityProviderResult>(\n send: SendIdentityMessage,\n result: T,\n fallback: string,\n err: unknown,\n): void {\n send({\n ...result,\n error: (err as Error)?.message ?? fallback,\n });\n}\n\nasync function getCurrentPubkey(options: IdentityServiceOptions): Promise<string> {\n const currentSigner = options.getSigner();\n if (!currentSigner?.getPublicKey) return '';\n try {\n return (await currentSigner.getPublicKey()) ?? '';\n } catch {\n return '';\n }\n}\n\nfunction sendOptionalProviderResult<T extends IdentityProviderResult>(\n options: IdentityServiceOptions,\n send: SendIdentityMessage,\n fallbackResult: T,\n errorFallback: string,\n buildResult?: (pubkey: string) => MaybePromise<T>,\n): void {\n if (!buildResult) {\n send(fallbackResult);\n return;\n }\n\n Promise.resolve(getCurrentPubkey(options))\n .then((pubkey) => buildResult(pubkey))\n .then((result) => send(result))\n .catch((err: unknown) => sendProviderError(send, fallbackResult, errorFallback, err));\n}\n\n/**\n * Options for creating the identity service.\n *\n * @example\n * ```ts\n * const identityService = createIdentityService({\n * getSigner: () => window.nostr ?? null,\n * });\n * runtime.registerService('identity', identityService);\n * ```\n */\nexport interface IdentityServiceOptions {\n /**\n * Return the NIP-07-compatible signer (or null) used to resolve\n * identity.getPublicKey / identity.getRelays. Called on every request —\n * availability can change dynamically.\n */\n getSigner: () => Signer | null;\n\n /**\n * Optional host-backed profile lookup for the current user.\n *\n * Kehto does not query relays itself; hosts that already maintain kind-0\n * metadata can provide it here without replacing the whole identity service.\n *\n * @param pubkey - Current signer pubkey, or \"\" when no signer is connected.\n * @param message - Original identity.getProfile request envelope.\n * @returns Profile metadata, or null when unavailable.\n */\n getProfile?: (\n pubkey: string,\n message: IdentityGetProfileMessage,\n ) => MaybePromise<IdentityGetProfileResultMessage['profile']>;\n\n /**\n * Optional host-backed follow-list lookup for the current user.\n *\n * @param pubkey - Current signer pubkey, or \"\" when no signer is connected.\n * @param message - Original identity.getFollows request envelope.\n * @returns Hex-encoded followed pubkeys.\n */\n getFollows?: (\n pubkey: string,\n message: IdentityGetFollowsMessage,\n ) => MaybePromise<IdentityGetFollowsResultMessage['pubkeys']>;\n\n /**\n * Optional host-backed categorized-list lookup for the current user.\n *\n * @param listType - Requested list category from the wire envelope.\n * @param pubkey - Current signer pubkey, or \"\" when no signer is connected.\n * @param message - Original identity.getList request envelope.\n * @returns List entries.\n */\n getList?: (\n listType: string,\n pubkey: string,\n message: IdentityGetListMessage,\n ) => MaybePromise<IdentityGetListResultMessage['entries']>;\n\n /**\n * Optional host-backed zap receipt lookup for the current user.\n *\n * @param pubkey - Current signer pubkey, or \"\" when no signer is connected.\n * @param message - Original identity.getZaps request envelope.\n * @returns Zap receipts.\n */\n getZaps?: (\n pubkey: string,\n message: IdentityGetZapsMessage,\n ) => MaybePromise<IdentityGetZapsResultMessage['zaps']>;\n\n /**\n * Optional host-backed mute-list lookup for the current user.\n *\n * @param pubkey - Current signer pubkey, or \"\" when no signer is connected.\n * @param message - Original identity.getMutes request envelope.\n * @returns Hex-encoded muted pubkeys.\n */\n getMutes?: (\n pubkey: string,\n message: IdentityGetMutesMessage,\n ) => MaybePromise<IdentityGetMutesResultMessage['pubkeys']>;\n\n /**\n * Optional host-backed block-list lookup for the current user.\n *\n * @param pubkey - Current signer pubkey, or \"\" when no signer is connected.\n * @param message - Original identity.getBlocked request envelope.\n * @returns Hex-encoded blocked pubkeys.\n */\n getBlocked?: (\n pubkey: string,\n message: IdentityGetBlockedMessage,\n ) => MaybePromise<IdentityGetBlockedResultMessage['pubkeys']>;\n\n /**\n * Optional host-backed badge lookup for the current user.\n *\n * @param pubkey - Current signer pubkey, or \"\" when no signer is connected.\n * @param message - Original identity.getBadges request envelope.\n * @returns Badges awarded to the user.\n */\n getBadges?: (\n pubkey: string,\n message: IdentityGetBadgesMessage,\n ) => MaybePromise<IdentityGetBadgesResultMessage['badges']>;\n}\n\nfunction sendIdentityError(\n send: SendIdentityMessage,\n id: string,\n typeBase: string,\n error: string,\n): void {\n send({ type: `${typeBase}.error`, id, error } as NappletMessage);\n}\n\nfunction sendSignerError(\n send: SendIdentityMessage,\n id: string,\n typeBase: string,\n fallback: string,\n err: unknown,\n): void {\n sendIdentityError(send, id, typeBase, (err as Error)?.message ?? fallback);\n}\n\nfunction handleGetPublicKey(options: IdentityServiceOptions, id: string, send: SendIdentityMessage): void {\n const signer = options.getSigner();\n if (!signer) {\n const result: IdentityGetPublicKeyResultMessage = {\n type: 'identity.getPublicKey.result',\n id,\n pubkey: '',\n };\n send(result);\n return;\n }\n\n Promise.resolve(signer.getPublicKey?.())\n .then((pubkey) => {\n const result: IdentityGetPublicKeyResultMessage = {\n type: 'identity.getPublicKey.result',\n id,\n pubkey: (pubkey as string) ?? '',\n };\n send(result);\n })\n .catch((err: unknown) => sendSignerError(send, id, 'identity.getPublicKey', 'getPublicKey failed', err));\n}\n\nfunction handleGetRelays(options: IdentityServiceOptions, id: string, send: SendIdentityMessage): void {\n const signer = options.getSigner();\n if (!signer) {\n sendIdentityError(send, id, 'identity.getRelays', 'no signer configured');\n return;\n }\n\n Promise.resolve(signer.getRelays?.() ?? {})\n .then((relays) => {\n const result: IdentityGetRelaysResultMessage = {\n type: 'identity.getRelays.result',\n id,\n relays: relays as Record<string, RelayPermission>,\n };\n send(result);\n })\n .catch((err: unknown) => sendSignerError(send, id, 'identity.getRelays', 'getRelays failed', err));\n}\n\nfunction handleReadProvider(\n options: IdentityServiceOptions,\n id: string,\n message: NappletMessage,\n send: SendIdentityMessage,\n): boolean {\n switch (message.type) {\n case 'identity.getProfile':\n sendOptionalProviderResult(\n options,\n send,\n { type: 'identity.getProfile.result', id, profile: null },\n 'getProfile failed',\n options.getProfile\n ? async (pubkey): Promise<IdentityGetProfileResultMessage> => ({\n type: 'identity.getProfile.result',\n id,\n profile: await options.getProfile!(pubkey, message as IdentityGetProfileMessage),\n })\n : undefined,\n );\n return true;\n\n case 'identity.getFollows':\n sendOptionalProviderResult(\n options,\n send,\n { type: 'identity.getFollows.result', id, pubkeys: [] },\n 'getFollows failed',\n options.getFollows\n ? async (pubkey): Promise<IdentityGetFollowsResultMessage> => ({\n type: 'identity.getFollows.result',\n id,\n pubkeys: await options.getFollows!(pubkey, message as IdentityGetFollowsMessage),\n })\n : undefined,\n );\n return true;\n\n case 'identity.getList':\n handleGetList(options, id, message as IdentityGetListMessage, send);\n return true;\n\n case 'identity.getZaps':\n sendOptionalProviderResult(\n options,\n send,\n { type: 'identity.getZaps.result', id, zaps: [] },\n 'getZaps failed',\n options.getZaps\n ? async (pubkey): Promise<IdentityGetZapsResultMessage> => ({\n type: 'identity.getZaps.result',\n id,\n zaps: await options.getZaps!(pubkey, message as IdentityGetZapsMessage),\n })\n : undefined,\n );\n return true;\n\n case 'identity.getMutes':\n sendOptionalProviderResult(\n options,\n send,\n { type: 'identity.getMutes.result', id, pubkeys: [] },\n 'getMutes failed',\n options.getMutes\n ? async (pubkey): Promise<IdentityGetMutesResultMessage> => ({\n type: 'identity.getMutes.result',\n id,\n pubkeys: await options.getMutes!(pubkey, message as IdentityGetMutesMessage),\n })\n : undefined,\n );\n return true;\n\n case 'identity.getBlocked':\n sendOptionalProviderResult(\n options,\n send,\n { type: 'identity.getBlocked.result', id, pubkeys: [] },\n 'getBlocked failed',\n options.getBlocked\n ? async (pubkey): Promise<IdentityGetBlockedResultMessage> => ({\n type: 'identity.getBlocked.result',\n id,\n pubkeys: await options.getBlocked!(pubkey, message as IdentityGetBlockedMessage),\n })\n : undefined,\n );\n return true;\n\n case 'identity.getBadges':\n sendOptionalProviderResult(\n options,\n send,\n { type: 'identity.getBadges.result', id, badges: [] },\n 'getBadges failed',\n options.getBadges\n ? async (pubkey): Promise<IdentityGetBadgesResultMessage> => ({\n type: 'identity.getBadges.result',\n id,\n badges: await options.getBadges!(pubkey, message as IdentityGetBadgesMessage),\n })\n : undefined,\n );\n return true;\n\n default:\n return false;\n }\n}\n\nfunction handleGetList(\n options: IdentityServiceOptions,\n id: string,\n message: IdentityGetListMessage,\n send: SendIdentityMessage,\n): void {\n sendOptionalProviderResult(\n options,\n send,\n { type: 'identity.getList.result', id, entries: [] },\n 'getList failed',\n options.getList\n ? async (pubkey): Promise<IdentityGetListResultMessage> => ({\n type: 'identity.getList.result',\n id,\n entries: await options.getList!(message.listType, pubkey, message),\n })\n : undefined,\n );\n}\n\nfunction handleIdentityServiceMessage(\n options: IdentityServiceOptions,\n message: NappletMessage,\n send: SendIdentityMessage,\n): void {\n const id = (message as NappletMessage & { id?: string }).id ?? '';\n\n switch (message.type) {\n case 'identity.getPublicKey':\n // Per NIP-5D, no signer resolves to the empty-pubkey sentinel, not an error.\n handleGetPublicKey(options, id, send);\n return;\n\n case 'identity.getRelays':\n handleGetRelays(options, id, send);\n return;\n\n default:\n if (!handleReadProvider(options, id, message, send)) {\n sendIdentityError(send, id, message.type, `Unknown identity method: ${message.type}`);\n }\n }\n}\n\n/**\n * Create an identity service that handles NIP-5D identity.* envelope messages.\n *\n * Supports the 9 read-only identity.* request types from @napplet/nap/identity.\n * The two nostr-info queries (getPublicKey, getRelays) resolve through the\n * caller-supplied signer; the remaining read-only queries resolve through\n * optional host providers or return default/empty payloads with spec-correct\n * envelope shapes so napplets always receive a result envelope.\n *\n * @param options - Identity service configuration (getSigner)\n * @returns A ServiceHandler ready for runtime.registerService('identity', handler)\n *\n * @example\n * ```ts\n * import { createIdentityService } from '@kehto/services';\n *\n * const identity = createIdentityService({\n * getSigner: () => mySignerAdapter,\n * });\n * runtime.registerService('identity', identity);\n * ```\n */\nexport function createIdentityService(options: IdentityServiceOptions): ServiceHandler {\n return {\n descriptor: {\n name: 'identity',\n version: IDENTITY_SERVICE_VERSION,\n description: 'NIP-5D identity NAP reference handler (9 read-only identity queries)',\n },\n\n handleMessage(\n _windowId: string,\n message: NappletMessage,\n send: (msg: NappletMessage) => void,\n ): void {\n handleIdentityServiceMessage(options, message, send);\n },\n\n // Identity service has no per-window state to clean up.\n onWindowDestroyed(_windowId: string): void {\n /* no-op */\n },\n };\n}\n","/**\n * relay-pool-service.ts — Relay pool as a ServiceHandler.\n *\n * Wraps an existing relay pool implementation (subscribe, publish,\n * selectRelayTier, isAvailable) as a ServiceHandler that receives\n * relay NAP envelope messages and manages subscription lifecycle.\n *\n * Handles: relay.subscribe, relay.close, relay.publish, relay.publishEncrypted.\n *\n * Note on `relay.publishEncrypted`: the canonical napplet→shell path routes\n * through @kehto/runtime handleRelayMessage, which performs shell-internal\n * NIP-44/NIP-04 encryption via the signer, then synthesizes a relay.publish\n * envelope handed to this service. The publishEncrypted branch here is a\n * fallback for alternate wirings; by the time the service sees the\n * envelope, content MUST already be ciphertext (the service never encrypts\n * or decrypts).\n */\n\nimport type { NostrEvent, NostrFilter, NappletMessage } from '@napplet/core';\nimport type { ServiceHandler } from '@kehto/runtime';\n\n// Timer globals available in all JS runtimes\ndeclare function setTimeout(callback: () => void, ms: number): unknown;\ndeclare function clearTimeout(id: unknown): void;\n\n/** EOSE fallback timeout in milliseconds. */\nconst EOSE_FALLBACK_MS = 15_000;\n\n/**\n * Options for creating a relay pool service.\n *\n * @example\n * ```ts\n * const relayPoolService = createRelayPoolService({\n * subscribe: (filters, cb, urls) => myPool.subscribe(filters, cb, urls),\n * publish: (event) => myPool.publish(event),\n * selectRelayTier: (filters) => myPool.selectRelays(filters),\n * isAvailable: () => myPool.connected,\n * });\n * ```\n */\nexport interface RelayPoolServiceOptions {\n /**\n * Subscribe to events matching filters. Returns handle with unsubscribe().\n *\n * @param filters - NIP-01 filter objects\n * @param callback - Receives matching events or 'EOSE'\n * @param relayUrls - Optional relay URL hints\n * @returns Handle to cancel the subscription\n */\n subscribe(\n filters: NostrFilter[],\n callback: (item: NostrEvent | 'EOSE') => void,\n relayUrls?: string[],\n ): { unsubscribe(): void };\n\n /**\n * Publish an event to relays.\n *\n * @param event - The event to publish\n */\n publish(event: NostrEvent): void;\n\n /**\n * Select relay URLs appropriate for the given filters.\n *\n * @param filters - NIP-01 filter objects\n * @returns Array of relay URLs\n */\n selectRelayTier(filters: NostrFilter[]): string[];\n\n /**\n * Whether the relay pool is available and connected.\n *\n * @returns true if the relay pool can handle requests\n */\n isAvailable(): boolean;\n}\n\n/** Internal subscription tracking entry. */\ninterface TrackedSubscription {\n handle: { unsubscribe(): void };\n eoseTimer: unknown;\n}\n\ntype RelayServiceMessage = NappletMessage & {\n subId?: unknown;\n filters?: unknown;\n event?: unknown;\n relay?: unknown;\n};\n\n/**\n * Create a relay pool service that wraps an existing relay pool\n * implementation as a ServiceHandler.\n *\n * Handles relay.subscribe, relay.close, and relay.publish envelopes.\n * Tracks subscriptions per windowId:subId for lifecycle management.\n * Sets a 15-second EOSE fallback timer on each subscription.\n *\n * @param options - Relay pool implementation to wrap\n * @returns A ServiceHandler ready for runtime.registerService('relay', handler)\n *\n * @example\n * ```ts\n * import { createRelayPoolService } from '@kehto/services';\n *\n * const pool = createRelayPoolService({\n * subscribe: (f, cb, urls) => applesauce.subscribe(f, cb, urls),\n * publish: (e) => applesauce.publish(e),\n * selectRelayTier: (f) => applesauce.getRelays(f),\n * isAvailable: () => applesauce.connected,\n * });\n * runtime.registerService('relay', pool);\n * ```\n */\nexport function createRelayPoolService(options: RelayPoolServiceOptions): ServiceHandler {\n const tracked = new Map<string, TrackedSubscription>();\n\n return {\n descriptor: {\n name: 'relay-pool',\n version: '1.0.0',\n description: 'Relay pool subscription and publishing',\n },\n\n handleMessage(windowId: string, message: NappletMessage, send: (msg: NappletMessage) => void): void {\n const relayMessage = message as RelayServiceMessage;\n if (message.type === 'relay.subscribe') {\n const subId = relayMessage.subId;\n if (typeof subId !== 'string') return;\n const filters = Array.isArray(relayMessage.filters)\n ? relayMessage.filters as NostrFilter[]\n : [];\n const subKey = `${windowId}:${subId}`;\n\n // Cancel existing subscription for this key if any\n const existing = tracked.get(subKey);\n if (existing) {\n existing.handle.unsubscribe();\n clearTimeout(existing.eoseTimer);\n tracked.delete(subKey);\n }\n\n if (!options.isAvailable()) {\n send({ type: 'relay.eose', subId } as NappletMessage);\n return;\n }\n\n const relayHint = typeof relayMessage.relay === 'string' && relayMessage.relay.length > 0\n ? relayMessage.relay\n : undefined;\n const relayUrls = relayHint ? [relayHint] : options.selectRelayTier(filters);\n let eoseSent = false;\n\n const eoseTimer = setTimeout(() => {\n if (!eoseSent) {\n eoseSent = true;\n send({ type: 'relay.eose', subId } as NappletMessage);\n }\n }, EOSE_FALLBACK_MS);\n\n const handle = options.subscribe(filters, (item) => {\n if (item === 'EOSE') {\n clearTimeout(eoseTimer);\n if (!eoseSent) {\n eoseSent = true;\n send({ type: 'relay.eose', subId } as NappletMessage);\n }\n return;\n }\n send({ type: 'relay.event', subId, event: item } as NappletMessage);\n }, relayUrls);\n\n tracked.set(subKey, { handle, eoseTimer });\n return;\n }\n\n if (message.type === 'relay.close') {\n const subId = relayMessage.subId;\n if (typeof subId !== 'string') return;\n const subKey = `${windowId}:${subId}`;\n const entry = tracked.get(subKey);\n if (entry) {\n entry.handle.unsubscribe();\n clearTimeout(entry.eoseTimer);\n tracked.delete(subKey);\n }\n return;\n }\n\n if (message.type === 'relay.publish') {\n const event = relayMessage.event as NostrEvent | undefined;\n if (event && typeof event === 'object' && options.isAvailable()) {\n options.publish(event);\n }\n return;\n }\n\n if (message.type === 'relay.publishEncrypted') {\n const event = relayMessage.event as NostrEvent | undefined;\n if (event && typeof event === 'object' && options.isAvailable()) {\n options.publish(event);\n }\n return;\n }\n },\n\n onWindowDestroyed(windowId: string): void {\n const prefix = `${windowId}:`;\n for (const [key, entry] of tracked) {\n if (key.startsWith(prefix)) {\n entry.handle.unsubscribe();\n clearTimeout(entry.eoseTimer);\n tracked.delete(key);\n }\n }\n },\n };\n}\n","\nimport type { NostrEvent, NostrFilter, NappletMessage } from '@napplet/core';\nimport type { ServiceHandler } from '@kehto/runtime';\n\ntype RelayServiceMessage = NappletMessage & {\n subId?: unknown;\n filters?: unknown;\n event?: unknown;\n};\n\n/**\n * Options for creating a cache service.\n *\n * @example\n * ```ts\n * const cacheService = createCacheService({\n * query: (filters) => myIndexedDB.query(filters),\n * store: (event) => myIndexedDB.store(event),\n * isAvailable: () => true,\n * });\n * ```\n */\nexport interface CacheServiceOptions {\n /**\n * Query cached events matching the given filters.\n *\n * @param filters - NIP-01 filter objects\n * @returns Promise resolving to matching cached events\n */\n query(filters: NostrFilter[]): Promise<NostrEvent[]>;\n\n /**\n * Store an event in cache. Best-effort, may silently fail.\n *\n * @param event - The event to store\n */\n store(event: NostrEvent): void;\n\n /**\n * Whether the cache is available.\n *\n * @returns true if the cache can handle requests\n */\n isAvailable(): boolean;\n}\n\n/**\n * @kehto/services cross-package naming-parity alias for {@link CacheServiceOptions}.\n *\n * `HostCacheBridge` matches the v1.4 `HostKeysBridge` / `HostMediaBridge`\n * convention — it is a pure type alias for `CacheServiceOptions`, NOT a new\n * type. Existing consumers of `CacheServiceOptions` continue to work\n * unchanged; new consumers may prefer `HostCacheBridge` for consistency\n * with the other Host*Bridge names in `@kehto/services`.\n *\n * Anti-feature note (PITFALLS.md M-02): `CacheServiceOptions` MUST remain\n * the primary export. This alias is additive; do not rename or delete\n * `CacheServiceOptions` when other Host*Bridge names eventually\n * stabilize.\n *\n * @example\n * ```ts\n * import type { HostCacheBridge } from '@kehto/services';\n * const cache: HostCacheBridge = {\n * query: (filters) => myIndexedDB.query(filters),\n * store: (event) => myIndexedDB.store(event),\n * isAvailable: () => true,\n * };\n * ```\n */\nexport type HostCacheBridge = CacheServiceOptions;\n\n/**\n * Create a cache service that wraps an existing cache implementation\n * as a ServiceHandler.\n *\n * Cache relay.subscribe subscriptions are one-shot — they query, deliver\n * results, send EOSE, and are done. No long-lived subscription tracking needed.\n * Cache query failures are best-effort: EOSE is sent even on failure.\n *\n * @param options - Cache implementation to wrap\n * @returns A ServiceHandler ready for runtime.registerService('cache', handler)\n *\n * @example\n * ```ts\n * import { createCacheService } from '@kehto/services';\n *\n * const cache = createCacheService({\n * query: (f) => workerRelay.query(f),\n * store: (e) => workerRelay.store(e),\n * isAvailable: () => workerRelay.ready,\n * });\n * runtime.registerService('cache', cache);\n * ```\n */\nexport function createCacheService(options: CacheServiceOptions): ServiceHandler {\n return {\n descriptor: {\n name: 'cache',\n version: '1.0.0',\n description: 'Local event cache (IndexedDB, worker relay, etc.)',\n },\n\n handleMessage(_windowId: string, message: NappletMessage, send: (msg: NappletMessage) => void): void {\n const relayMessage = message as RelayServiceMessage;\n if (message.type === 'relay.subscribe') {\n const subId = relayMessage.subId;\n if (typeof subId !== 'string') return;\n const filters = Array.isArray(relayMessage.filters)\n ? relayMessage.filters as NostrFilter[]\n : [];\n\n if (!options.isAvailable()) {\n send({ type: 'relay.eose', subId } as NappletMessage);\n return;\n }\n\n options\n .query(filters)\n .then((events) => {\n for (const event of events) {\n send({ type: 'relay.event', subId, event } as NappletMessage);\n }\n send({ type: 'relay.eose', subId } as NappletMessage);\n })\n .catch(() => {\n // Cache query is best-effort — send EOSE even on failure\n send({ type: 'relay.eose', subId } as NappletMessage);\n });\n return;\n }\n\n if (message.type === 'relay.publish') {\n const event = relayMessage.event as NostrEvent | undefined;\n if (event && typeof event === 'object' && options.isAvailable()) {\n try {\n options.store(event);\n } catch {\n /* Cache write is best-effort */\n }\n }\n return;\n }\n },\n\n // Cache has no per-window state to clean up\n onWindowDestroyed(_windowId: string): void {\n /* no-op */\n },\n };\n}\n","\nimport type { NostrEvent, NostrFilter, NappletMessage } from '@napplet/core';\nimport type { ServiceHandler } from '@kehto/runtime';\nimport type { RelayPoolServiceOptions } from './relay-pool-service.js';\nimport type { CacheServiceOptions } from './cache-service.js';\n\n// Timer globals available in all JS runtimes\ndeclare function setTimeout(callback: () => void, ms: number): unknown;\ndeclare function clearTimeout(id: unknown): void;\n\n/** Default EOSE fallback timeout. */\nconst DEFAULT_EOSE_TIMEOUT_MS = 15_000;\n\n/**\n * Options for creating a coordinated relay service.\n *\n * @example\n * ```ts\n * const relay = createCoordinatedRelay({\n * relayPool: {\n * subscribe: (f, cb, urls) => pool.subscribe(f, cb, urls),\n * publish: (e) => pool.publish(e),\n * selectRelayTier: (f) => pool.selectRelays(f),\n * isAvailable: () => pool.connected,\n * },\n * cache: {\n * query: (f) => db.query(f),\n * store: (e) => db.store(e),\n * isAvailable: () => db.ready,\n * },\n * });\n * runtime.registerService('relay', relay);\n * ```\n */\nexport interface CoordinatedRelayOptions {\n /**\n * Relay pool implementation.\n * Uses the same interface as RelayPoolServiceOptions.\n */\n relayPool: RelayPoolServiceOptions;\n\n /**\n * Local cache implementation.\n * Uses the same interface as CacheServiceOptions.\n */\n cache: CacheServiceOptions;\n\n /**\n * EOSE fallback timeout in milliseconds.\n * Sent if relay pool doesn't respond within this time.\n * Default: 15000 (15 seconds).\n */\n eoseTimeoutMs?: number;\n}\n\n/** Internal state for a tracked subscription. */\ninterface TrackedSub {\n seenIds: Set<string>;\n cacheEose: boolean;\n relayEose: boolean;\n eoseSent: boolean;\n eoseTimer: unknown;\n relayHandle: { unsubscribe(): void } | null;\n}\n\ntype RelayServiceMessage = NappletMessage & {\n subId?: unknown;\n filters?: unknown;\n event?: unknown;\n relay?: unknown;\n};\n\n/**\n * Create a coordinated relay service that combines relay pool and cache\n * into a single ServiceHandler with dedup and unified EOSE.\n *\n * On relay.subscribe: queries cache first, then subscribes to relay pool.\n * Events are deduplicated by ID. EOSE is sent after both sources complete.\n * On relay.publish: publishes to relay pool and stores in cache.\n * On relay.close: cancels relay pool subscription.\n *\n * @param options - Relay pool and cache implementations to coordinate\n * @returns A ServiceHandler ready for runtime.registerService('relay', handler)\n *\n * @example\n * ```ts\n * import { createCoordinatedRelay } from '@kehto/services';\n *\n * const relay = createCoordinatedRelay({ relayPool: myPool, cache: myCache });\n * runtime.registerService('relay', relay);\n * ```\n */\nexport function createCoordinatedRelay(options: CoordinatedRelayOptions): ServiceHandler {\n const timeoutMs = options.eoseTimeoutMs ?? DEFAULT_EOSE_TIMEOUT_MS;\n const subs = new Map<string, TrackedSub>();\n\n function maybeSendEose(subKey: string, subId: string, send: (msg: NappletMessage) => void): void {\n const sub = subs.get(subKey);\n if (!sub || sub.eoseSent) return;\n if (sub.cacheEose && sub.relayEose) {\n sub.eoseSent = true;\n clearTimeout(sub.eoseTimer);\n send({ type: 'relay.eose', subId } as NappletMessage);\n }\n }\n\n return {\n descriptor: {\n name: 'relay',\n version: '1.0.0',\n description: 'Coordinated relay pool + cache with dedup and unified EOSE',\n },\n\n handleMessage(windowId: string, message: NappletMessage, send: (msg: NappletMessage) => void): void {\n const relayMessage = message as RelayServiceMessage;\n if (message.type === 'relay.subscribe') {\n const subId = relayMessage.subId;\n if (typeof subId !== 'string') return;\n const filters = Array.isArray(relayMessage.filters)\n ? relayMessage.filters as NostrFilter[]\n : [];\n const subKey = `${windowId}:${subId}`;\n\n // Cancel existing subscription for this key\n const existing = subs.get(subKey);\n if (existing) {\n existing.relayHandle?.unsubscribe();\n clearTimeout(existing.eoseTimer);\n subs.delete(subKey);\n }\n\n const cacheAvailable = options.cache.isAvailable();\n const relayAvailable = options.relayPool.isAvailable();\n\n // Neither source available — send EOSE immediately\n if (!cacheAvailable && !relayAvailable) {\n send({ type: 'relay.eose', subId } as NappletMessage);\n return;\n }\n\n const tracked: TrackedSub = {\n seenIds: new Set(),\n cacheEose: !cacheAvailable, // mark done if cache not available\n relayEose: !relayAvailable, // mark done if relay not available\n eoseSent: false,\n eoseTimer: null as unknown,\n relayHandle: null,\n };\n subs.set(subKey, tracked);\n\n function deliver(event: NostrEvent): void {\n if (tracked.seenIds.has(event.id)) return;\n tracked.seenIds.add(event.id);\n if (subs.has(subKey)) send({ type: 'relay.event', subId, event } as NappletMessage);\n }\n\n // Query cache (async)\n if (cacheAvailable) {\n options.cache\n .query(filters)\n .then((events) => {\n for (const event of events) deliver(event);\n tracked.cacheEose = true;\n maybeSendEose(subKey, subId, send);\n })\n .catch(() => {\n // Cache query is best-effort\n tracked.cacheEose = true;\n maybeSendEose(subKey, subId, send);\n });\n }\n\n // Subscribe to relay pool\n if (relayAvailable) {\n tracked.eoseTimer = setTimeout(() => {\n if (!tracked.eoseSent) {\n tracked.relayEose = true;\n maybeSendEose(subKey, subId, send);\n }\n }, timeoutMs);\n\n const relayHint = typeof relayMessage.relay === 'string' && relayMessage.relay.length > 0\n ? relayMessage.relay\n : undefined;\n const relayUrls = relayHint ? [relayHint] : options.relayPool.selectRelayTier(filters);\n tracked.relayHandle = options.relayPool.subscribe(filters, (item) => {\n if (item === 'EOSE') {\n clearTimeout(tracked.eoseTimer);\n tracked.relayEose = true;\n maybeSendEose(subKey, subId, send);\n return;\n }\n deliver(item);\n // Store relay events in cache\n if (cacheAvailable) {\n try { options.cache.store(item); } catch { /* best-effort */ }\n }\n }, relayUrls);\n }\n return;\n }\n\n if (message.type === 'relay.close') {\n const subId = relayMessage.subId;\n if (typeof subId !== 'string') return;\n const subKey = `${windowId}:${subId}`;\n const entry = subs.get(subKey);\n if (entry) {\n entry.relayHandle?.unsubscribe();\n clearTimeout(entry.eoseTimer);\n subs.delete(subKey);\n }\n return;\n }\n\n if (message.type === 'relay.publish') {\n const event = relayMessage.event as NostrEvent | undefined;\n if (!event || typeof event !== 'object') return;\n // Publish to relay pool\n if (options.relayPool.isAvailable()) {\n options.relayPool.publish(event);\n }\n if (options.cache.isAvailable()) {\n try { options.cache.store(event); } catch { /* best-effort */ }\n }\n return;\n }\n },\n\n onWindowDestroyed(windowId: string): void {\n const prefix = `${windowId}:`;\n for (const [key, entry] of subs) {\n if (key.startsWith(prefix)) {\n entry.relayHandle?.unsubscribe();\n clearTimeout(entry.eoseTimer);\n subs.delete(key);\n }\n }\n },\n };\n}\n","/**\n * keys-service.ts — NIP-5D keys NAP reference document-level listener implementation.\n *\n * Handles the 3 napplet -> shell request types from @napplet/nap/keys:\n * - keys.forward -> invokes options.onForward (hotkey passthrough, fire-and-forget)\n * - keys.registerAction -> parses action.defaultKey into a chord spec, stores the\n * subscription in an in-memory registry keyed by actionId,\n * tracks windowId ownership so onWindowDestroyed can auto-\n * unsubscribe, and echoes { actionId, binding } as .result\n * - keys.unregisterAction -> removes the subscription; fire-and-forget (no envelope)\n *\n * Real listener: on service construction the handler attaches a single\n * `keydown` listener to `options.listenerTarget` (default: `document`). Each\n * keydown is matched against the chord-subscription registry; matches invoke\n * `options.onForward` with the DOM-shape payload AND push a canonical\n * `keys.action` envelope back to the owning napplet via the per-window `send`\n * callback captured at `keys.registerAction` time. Subscriptions persist\n * across messages; `onWindowDestroyed(windowId)` drops all subscriptions owned\n * by the destroyed window as well as its cached `send` handle.\n *\n * On each document keydown matching a registered action, the service\n * additionally emits a `keys.action` envelope to the action's owning napplet\n * via the per-window `send` callback — this is the canonical @napplet/nap/keys\n * surface the SDK's `keys.onAction(...)` helper consumes. The shape is a\n * superset of `KeysActionMessage`: `{ type, actionId, chord }` where `chord`\n * is the parsed `{ ctrl, alt, shift, meta, key }` struct (extension field;\n * base shape unchanged, downstream SDKs that only read `{ type, actionId }`\n * ignore `chord` silently).\n *\n * Field-name translation: @napplet/nap/keys uses the compact\n * { ctrl, alt, shift, meta } form on the wire; the shell's HotkeyHooks\n * (packages/shell/src/types.ts) expects the DOM-compatible\n * { ctrlKey, altKey, shiftKey, metaKey } form. This service performs the\n * translation so callers of `onForward` see the DOM shape.\n *\n * Shell -> napplet push envelopes `keys.bindings` remain the shell-side keys\n * forwarder's responsibility (DRIFT-SHELL-06, tracked under Plan 12-11 /\n * future phase); `keys.action` is emitted here per Plan 26-01.\n */\n\nimport type { NappletMessage } from '@napplet/core';\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\nimport type {\n KeysForwardMessage,\n KeysRegisterActionMessage,\n KeysRegisterActionResultMessage,\n KeysActionMessage,\n} from '@napplet/nap/keys/types';\n\n/**\n * Minimal structural subset of the DOM `KeyboardEvent` exposed to\n * `HostKeysBridge` subscribe callbacks. DOM `KeyboardEvent` satisfies this\n * structurally with no adapter needed. OS-bridge impls (Electron, Tauri —\n * out of v1.4 scope) synthesize this from native key events.\n */\nexport interface HostKeyEvent {\n key: string;\n code: string;\n ctrlKey: boolean;\n altKey: boolean;\n shiftKey: boolean;\n metaKey: boolean;\n /** True for OS autorepeat; the service filters these by default. */\n repeat?: boolean;\n}\n\n/**\n * Host-bridge contract for pluggable keyboard backends.\n *\n * The browser reference implementation (the default {@link createKeysService}\n * behaviour when `hostBridge` is omitted) registers a `document`-level keydown\n * listener and satisfies this interface structurally — it exposes\n * `subscribe(chord, callback) => unsubscribe` semantics but omits the two\n * OS-level optional fields (browsers cannot register global hotkeys without\n * privileged APIs).\n *\n * Host apps (Electron, Tauri) implement this interface in their own code and\n * pass it via `createKeysService({ hostBridge: myBridge })` — the service\n * then delegates subscription lifecycle to the bridge and remains browser-free.\n *\n * Reference implementations for Electron / Tauri are explicitly out of v1.4\n * scope and live in host-app examples / follow-up milestones (see\n * REQUIREMENTS.md \"Future Requirements\").\n *\n * @example\n * ```ts\n * // Host-app pseudocode (Electron main-process relay):\n * const electronBridge: HostKeysBridge = {\n * subscribe(chord, cb) {\n * const handle = globalShortcut.register(chord, () => cb({ key: '', code: '', ctrlKey: false, altKey: false, shiftKey: false, metaKey: false }));\n * return () => globalShortcut.unregister(chord);\n * },\n * registerGlobalHotkey: (chord) => globalShortcut.register(chord, () => {}),\n * onGlobalHotkey: (cb) => globalHotkeyBridge.on('global-hotkey', (_, chord) => cb(chord)),\n * };\n *\n * const keys = createKeysService({ hostBridge: electronBridge });\n * runtime.registerService('keys', keys);\n * ```\n */\nexport interface HostKeysBridge {\n /**\n * Subscribe a callback to a chord. Returns an unsubscribe handle.\n *\n * Implementations MUST:\n * - invoke `callback` exactly once per matching chord event (implementations\n * are responsible for any OS-autorepeat filtering)\n * - invoke `callback` synchronously during the event delivery\n * - accept the string chord format documented by @napplet/nap/keys\n * (e.g. `'Ctrl+Shift+K'`, `'Cmd+P'`)\n */\n subscribe(chord: string, callback: (event: KeyboardEvent | HostKeyEvent) => void): () => void;\n\n /**\n * Optional: register an OS-level global hotkey (works even when the host\n * window is not focused). Returns true on success, false if the chord\n * cannot be registered (e.g. already claimed by another app).\n *\n * Omitted by the browser reference implementation — browsers cannot\n * register OS-level global hotkeys without privileged APIs. Electron\n * (`globalShortcut`) and Tauri (`GlobalShortcut`) provide this.\n */\n registerGlobalHotkey?(chord: string): boolean;\n\n /**\n * Optional: subscribe to OS-level global hotkey events (regardless of\n * focus). Returns an unsubscribe handle.\n *\n * Omitted by the browser reference implementation. See\n * {@link HostKeysBridge.registerGlobalHotkey}.\n */\n onGlobalHotkey?(callback: (chord: string) => void): () => void;\n}\n\n/** Keys service version — follows semver. */\nconst KEYS_SERVICE_VERSION = '1.2.0';\n\n/**\n * Parsed chord struct — internal, never on the wire. The napplet-facing API\n * (and the `action.defaultKey` field) is a string like `\"Ctrl+Shift+K\"`;\n * `parseChord` lowers those strings into this struct for efficient matching\n * against `KeyboardEvent` modifier flags.\n */\ninterface ChordSpec {\n ctrl: boolean;\n alt: boolean;\n shift: boolean;\n meta: boolean;\n /** Normalized uppercase single character or DOM key name (e.g. 'K', 'Enter'). */\n key: string;\n}\n\n/** Registry entry — maps a registered actionId back to its owning window + chord. */\ninterface ActionEntry {\n chord: ChordSpec;\n /** Original chord string, preserved for the .result `binding` field. */\n chordString: string;\n windowId: string;\n}\n\n/**\n * Options for creating a keys service via createKeysService().\n *\n * @example\n * ```ts\n * const keys = createKeysService({\n * onForward: (event) => {\n * // event has DOM-compatible field names: ctrlKey, altKey, etc.\n * hotkeyDispatcher.dispatch(event);\n * },\n * });\n * ```\n */\nexport interface KeysServiceOptions {\n /**\n * Called on `keys.forward` (napplet-forwarded chord) AND on document keydown\n * matching a registered action. Receives the DOM-style field names\n * (ctrlKey/altKey/shiftKey/metaKey) to match the shell's HotkeyHooks\n * contract. The service translates from the wire shape\n * ({ ctrl, alt, shift, meta }) before invoking this callback.\n */\n onForward?: (event: {\n key: string;\n code: string;\n ctrlKey: boolean;\n altKey: boolean;\n shiftKey: boolean;\n metaKey: boolean;\n }) => void;\n /**\n * EventTarget to attach the default keydown listener to. Defaults to the\n * global `document` when running in a DOM environment, else an isolated\n * `new EventTarget()` (SSR / Node-test safe). Passing a fresh\n * `new EventTarget()` is useful for unit tests. Mirrors the pattern used\n * by `@kehto/shell`'s `createKeysForwarder`.\n *\n * Ignored when `hostBridge` is provided — the bridge owns subscription\n * lifecycle and no document listener is attached.\n */\n listenerTarget?: EventTarget;\n /**\n * Optional pluggable backend for chord subscription. When provided, the\n * service delegates `keys.registerAction` → `bridge.subscribe(chord, cb)`\n * and stores the returned unsubscribe handle keyed on `actionId`. The\n * default document-listener path is NOT attached when `hostBridge` is\n * provided — the bridge is authoritative. See {@link HostKeysBridge}.\n */\n hostBridge?: HostKeysBridge;\n /**\n * Optional set of shell-reserved chords. Strings in the `@napplet/nap/keys`\n * wire format (e.g. `'Ctrl+Shift+K'`, `'Cmd+P'`). When a napplet sends\n * `keys.forward` with a chord matching this set — or when a document\n * keydown matches a reserved chord — the service invokes `onForward`\n * (or the `hostBridge`-registered handler) but suppresses the\n * `keys.action` push to any napplet that registered the same chord via\n * `keys.registerAction`. Precedence: reserved > registered. The shell\n * WANTS the forward — that is why it reserved the chord.\n *\n * Normalized once at service construction via the same chord parser used\n * for `action.defaultKey` — `'Ctrl+K'` / `'Control+k'` / `'ctrl+K'` all\n * match. Static; no runtime mutation. For dynamic reservation see the\n * deferred `HostKeysBridge.reserveAbsolute(chords)` extension.\n *\n * @example\n * ```ts\n * const keys = createKeysService({\n * reservedChords: ['Ctrl+Alt+T', 'Super+Space'],\n * onForward: (event) => wm.dispatch(event),\n * });\n * ```\n */\n reservedChords?: ReadonlyArray<string>;\n}\n\n/** Modifier-token aliases accepted by `parseChord` (case-insensitive). */\nconst MODIFIER_ALIASES: Record<string, keyof Pick<ChordSpec, 'ctrl' | 'alt' | 'shift' | 'meta'>> = {\n ctrl: 'ctrl',\n control: 'ctrl',\n alt: 'alt',\n option: 'alt',\n shift: 'shift',\n meta: 'meta',\n cmd: 'meta',\n command: 'meta',\n win: 'meta',\n super: 'meta',\n};\n\n/**\n * Parse a chord string into a `ChordSpec`. Modifier tokens are case-insensitive\n * and recognize common aliases (Cmd/Command/Win/Super → meta, Control → ctrl,\n * Option → alt). Single-character keys are normalized to uppercase so chord\n * matching is case-insensitive; multi-character DOM key names (`Enter`,\n * `ArrowUp`, `F4`) preserve their original casing.\n *\n * Examples:\n * parseChord('Ctrl+Shift+K') → { ctrl: true, alt: false, shift: true, meta: false, key: 'K' }\n * parseChord('ctrl+s') → { ctrl: true, alt: false, shift: false, meta: false, key: 'S' }\n * parseChord('Cmd+P') → { ctrl: false, alt: false, shift: false, meta: true, key: 'P' }\n * parseChord('K') → { ctrl: false, alt: false, shift: false, meta: false, key: 'K' }\n * parseChord('Ctrl++') → { ctrl: true, alt: false, shift: false, meta: false, key: '+' }\n *\n * @throws Error('empty chord') when the input is the empty string.\n * @throws Error(`unknown modifier: ${tok}`) when a non-final token isn't a recognized modifier.\n * @throws Error(`empty key in chord: ${chord}`) when the final token is empty/whitespace.\n */\nfunction parseChord(chord: string): ChordSpec {\n if (chord.length === 0) throw new Error('empty chord');\n const parts = chord.split('+');\n const out: ChordSpec = { ctrl: false, alt: false, shift: false, meta: false, key: '' };\n // The final token is always the key — even if it is literally '+' (chord like 'Ctrl++').\n // All preceding tokens must be modifiers.\n for (let i = 0; i < parts.length - 1; i++) {\n const tok = parts[i].trim().toLowerCase();\n if (tok.length === 0) continue; // tolerate stray whitespace\n const slot = MODIFIER_ALIASES[tok];\n if (!slot) throw new Error(`unknown modifier: ${parts[i]}`);\n out[slot] = true;\n }\n const keyTok = parts[parts.length - 1].trim();\n if (keyTok.length === 0) throw new Error(`empty key in chord: ${chord}`);\n // Single characters normalize to uppercase for case-insensitive comparison;\n // multi-character DOM key names (Enter, ArrowUp, F4) preserve their original casing.\n out.key = keyTok.length === 1 ? keyTok.toUpperCase() : keyTok;\n return out;\n}\n\n/**\n * Create a keys service handler.\n *\n * Attaches a single `keydown` listener to `options.listenerTarget`\n * (default `document`). Matching chord subscriptions invoke `options.onForward`\n * with a DOM-shape payload AND push a `keys.action` envelope back to the\n * owning napplet via the per-window `send` callback captured at\n * `keys.registerAction` time. Returns a `ServiceHandler` augmented with a\n * `destroy()` method that detaches the listener and clears all registries.\n *\n * @param options - Optional configuration (onForward callback, listenerTarget)\n * @returns A ServiceHandler (with `destroy()`) to register with the runtime\n *\n * @example\n * ```ts\n * import { createKeysService } from '@kehto/services';\n *\n * const keys = createKeysService({\n * onForward: (event) => shellHotkeyDispatcher.execute(event),\n * });\n *\n * runtime.registerService('keys', keys);\n * // Later, on shell teardown:\n * keys.destroy();\n * ```\n */\nexport function createKeysService(\n options: KeysServiceOptions = {},\n): ServiceHandler & { destroy(): void } {\n const descriptor: ServiceDescriptor = {\n name: 'keys',\n version: KEYS_SERVICE_VERSION,\n description: options.hostBridge\n ? 'NIP-5D keys NAP reference handler (host-bridge delegated)'\n : 'NIP-5D keys NAP reference handler (document-level chord listener)',\n };\n\n function chordSpecKey(spec: {\n ctrl: boolean;\n alt: boolean;\n shift: boolean;\n meta: boolean;\n key: string;\n }): string {\n return `${spec.ctrl}|${spec.alt}|${spec.shift}|${spec.meta}|${spec.key}`;\n }\n const reservedChordKeys: Set<string> = new Set();\n if (options.reservedChords) {\n for (const chordStr of options.reservedChords) {\n // parseChord throws on malformed input — let it bubble up at construction\n // so misconfigured shells fail loudly at boot, not silently at runtime.\n reservedChordKeys.add(chordSpecKey(parseChord(chordStr)));\n }\n }\n // Canonicalize a wire-shape keys.forward payload into the same key.\n function forwardKey(m: {\n key: string;\n ctrl: boolean;\n alt: boolean;\n shift: boolean;\n meta: boolean;\n }): string {\n const k = m.key.length === 1 ? m.key.toUpperCase() : m.key;\n return `${m.ctrl}|${m.alt}|${m.shift}|${m.meta}|${k}`;\n }\n function forwardPayload(m: KeysForwardMessage): Omit<HostKeyEvent, 'repeat'> {\n return {\n key: m.key,\n code: m.code,\n ctrlKey: m.ctrl,\n altKey: m.alt,\n shiftKey: m.shift,\n metaKey: m.meta,\n };\n }\n // Canonicalize a DOM KeyboardEvent into the same key (for Branch B keydown listener).\n function eventKey(ev: KeyboardEvent): string {\n const k = ev.key.length === 1 ? ev.key.toUpperCase() : ev.key;\n return `${ev.ctrlKey}|${ev.altKey}|${ev.shiftKey}|${ev.metaKey}|${k}`;\n }\n\n if (options.hostBridge) {\n const bridge = options.hostBridge;\n // windowId → Set<actionId> — parallels Branch B for scoped cleanup.\n const bridgeWindowActions = new Map<string, Set<string>>();\n // actionId → unsubscribe handle returned from bridge.subscribe.\n const unsubscribeHandles = new Map<string, () => void>();\n\n return {\n descriptor,\n\n handleMessage(\n windowId: string,\n message: NappletMessage,\n send: (msg: NappletMessage) => void,\n ): void {\n switch (message.type) {\n case 'keys.forward': {\n // Legacy napplet-forwarded path still works identically — preserves wire contract.\n // Phase 33 / KEYS-04-05: reserved chords take precedence. The Branch-A\n // handler never dispatches keys.action to napplets on forward (bridge\n // owns chord → napplet routing via its own subscribe callback), so\n // reservation here is observationally identical to the base case —\n // but we compute and check the reservation explicitly to document\n // the contract for future edits and keep both branches uniform.\n const m = message as KeysForwardMessage;\n const reserved = reservedChordKeys.has(forwardKey(m));\n options.onForward?.(forwardPayload(m));\n if (reserved) {\n return;\n }\n return;\n }\n\n case 'keys.registerAction': {\n const m = message as KeysRegisterActionMessage;\n if (m.action.defaultKey) {\n try {\n const unsubscribe = bridge.subscribe(m.action.defaultKey, (ev) => {\n // Normalize either KeyboardEvent or HostKeyEvent to the DOM-shape onForward payload.\n const e = ev as KeyboardEvent | HostKeyEvent;\n // Bridges may not filter autorepeat — we do, matching Branch B semantics.\n if ('repeat' in e && e.repeat) return;\n options.onForward?.({\n key: e.key,\n code: e.code,\n ctrlKey: e.ctrlKey,\n altKey: e.altKey,\n shiftKey: e.shiftKey,\n metaKey: e.metaKey,\n });\n // Canonical shell→napplet push: emit keys.action to the owning\n // napplet. Shape matches Branch B (superset of KeysActionMessage);\n // the `chord` extension is omitted here because bridges deliver\n // pre-parsed chord events without the internal ChordSpec struct.\n const payload: KeysActionMessage = {\n type: 'keys.action',\n actionId: m.action.id,\n };\n send(payload as NappletMessage);\n });\n unsubscribeHandles.set(m.action.id, unsubscribe);\n if (!bridgeWindowActions.has(windowId)) bridgeWindowActions.set(windowId, new Set());\n bridgeWindowActions.get(windowId)!.add(m.action.id);\n } catch (err) {\n const id = m.id ?? '';\n send({\n type: 'keys.registerAction.error',\n id,\n error: `bridge subscribe failed: ${(err as Error).message}`,\n } as NappletMessage);\n return;\n }\n }\n const result: KeysRegisterActionResultMessage = {\n type: 'keys.registerAction.result',\n id: m.id,\n actionId: m.action.id,\n ...(m.action.defaultKey ? { binding: m.action.defaultKey } : {}),\n };\n send(result as NappletMessage);\n return;\n }\n\n case 'keys.unregisterAction': {\n const m = message as NappletMessage & { actionId?: string };\n if (m.actionId) {\n const unsubscribe = unsubscribeHandles.get(m.actionId);\n if (unsubscribe) {\n try {\n unsubscribe();\n } catch {\n /* best-effort */\n }\n unsubscribeHandles.delete(m.actionId);\n // Prune the bridgeWindowActions entry that owns this actionId.\n for (const [wid, set] of bridgeWindowActions.entries()) {\n if (set.delete(m.actionId) && set.size === 0) bridgeWindowActions.delete(wid);\n }\n }\n }\n return;\n }\n\n default: {\n const id = (message as NappletMessage & { id?: string }).id ?? '';\n send({\n type: `${message.type}.error`,\n id,\n error: `Unknown keys method: ${message.type}`,\n } as NappletMessage);\n return;\n }\n }\n },\n\n onWindowDestroyed(windowId: string): void {\n const actions = bridgeWindowActions.get(windowId);\n if (!actions) return;\n for (const actionId of actions) {\n const unsubscribe = unsubscribeHandles.get(actionId);\n if (unsubscribe) {\n try {\n unsubscribe();\n } catch {\n /* best-effort */\n }\n }\n unsubscribeHandles.delete(actionId);\n }\n bridgeWindowActions.delete(windowId);\n },\n\n destroy(): void {\n for (const unsubscribe of unsubscribeHandles.values()) {\n try {\n unsubscribe();\n } catch {\n /* best-effort */\n }\n }\n unsubscribeHandles.clear();\n bridgeWindowActions.clear();\n },\n };\n }\n\n const actionRegistry = new Map<string, ActionEntry>(); // actionId → {chord, chordString, windowId}\n const windowActions = new Map<string, Set<string>>(); // windowId → Set<actionId>\n // Per-window `send` callback captured at registerAction time. Used to push\n // keys.action envelopes back to the owning napplet on chord match — this is\n // the canonical @napplet/nap/keys surface the SDK's `keys.onAction(...)`\n // helper consumes.\n const sendHandles = new Map<string, (msg: NappletMessage) => void>(); // windowId → send\n\n // ─── Listener target (SSR / test-safe fallback, mirrors keys-forwarder.ts) ─\n const target: EventTarget =\n options.listenerTarget ??\n (typeof document !== 'undefined' ? document : new EventTarget());\n\n function chordMatches(spec: ChordSpec, ev: KeyboardEvent): boolean {\n if (spec.ctrl !== ev.ctrlKey) return false;\n if (spec.alt !== ev.altKey) return false;\n if (spec.shift !== ev.shiftKey) return false;\n if (spec.meta !== ev.metaKey) return false;\n const evKey = ev.key.length === 1 ? ev.key.toUpperCase() : ev.key;\n return spec.key === evKey;\n }\n\n const listener = (rawEv: Event): void => {\n const ev = rawEv as KeyboardEvent;\n if (ev.repeat) return; // ignore OS autorepeat — matches \"I pressed it once\" intent (CONTEXT Area 1)\n\n // Phase 33 / KEYS-04-05: reserved chords fire onForward but suppress\n // keys.action fan-out to napplets. Check ONCE up front.\n const isReserved = reservedChordKeys.has(eventKey(ev));\n\n // Determine if any registered action would match — needed to decide\n // whether to fire onForward on a non-reserved keydown (legacy parity).\n // A reserved chord fires onForward regardless of napplet registration\n // (WM-launcher case: shell declares chord, no napplet registers it).\n let anyMatch = false;\n for (const entry of actionRegistry.values()) {\n if (chordMatches(entry.chord, ev)) {\n anyMatch = true;\n break;\n }\n }\n\n if (isReserved || anyMatch) {\n options.onForward?.({\n key: ev.key,\n code: ev.code,\n ctrlKey: ev.ctrlKey,\n altKey: ev.altKey,\n shiftKey: ev.shiftKey,\n metaKey: ev.metaKey,\n });\n }\n\n if (isReserved) return; // reserved → no napplet fan-out\n\n // (2) Canonical shell→napplet push: emit keys.action to the owning\n // napplet via its captured send callback. The SDK's keys.onAction\n // helper subscribes to this envelope. We attach a `chord` extension\n // field so the demo napplet can display the fired chord without\n // reconstructing it from the original registration.\n for (const [actionId, entry] of actionRegistry.entries()) {\n if (chordMatches(entry.chord, ev)) {\n const send = sendHandles.get(entry.windowId);\n if (send) {\n const payload: KeysActionMessage & { chord: ChordSpec } = {\n type: 'keys.action',\n actionId,\n chord: entry.chord,\n };\n send(payload as NappletMessage);\n }\n // Intentionally no `break` — two actions subscribing to the same chord\n // both receive the event. Conflict resolution is an explicit v1.5+\n // concern per CONTEXT.md Deferred Ideas.\n }\n }\n };\n\n target.addEventListener('keydown', listener);\n\n return {\n descriptor,\n\n handleMessage(\n windowId: string,\n message: NappletMessage,\n send: (msg: NappletMessage) => void,\n ): void {\n switch (message.type) {\n case 'keys.forward': {\n // Legacy passthrough: napplet-forwarded keydown translation.\n // Preserved bit-for-bit from the stub — existing tests + the\n // keys-forwarder.ts -> service.handleMessage path depend on this shape.\n //\n // Phase 33 / KEYS-04-05: Branch B's keys.forward handler does NOT\n // emit keys.action (fan-out happens in the document keydown listener),\n // so reservation is observationally identical to the base case.\n // The explicit guard below pins the contract for future edits.\n const m = message as KeysForwardMessage;\n const reserved = reservedChordKeys.has(forwardKey(m));\n options.onForward?.(forwardPayload(m));\n if (reserved) return;\n return;\n }\n\n case 'keys.registerAction': {\n const m = message as KeysRegisterActionMessage;\n // Capture (or refresh) the per-window send callback. The runtime's\n // service-handler contract guarantees `send` remains valid for this\n // windowId until onWindowDestroyed(windowId) fires — we cache the\n // most recent invocation so the keydown listener can push\n // keys.action envelopes back to the owning napplet.\n sendHandles.set(windowId, send);\n\n if (m.action.defaultKey) {\n try {\n const chord = parseChord(m.action.defaultKey);\n actionRegistry.set(m.action.id, {\n chord,\n chordString: m.action.defaultKey,\n windowId,\n });\n if (!windowActions.has(windowId)) windowActions.set(windowId, new Set());\n windowActions.get(windowId)!.add(m.action.id);\n } catch (err) {\n // Parse failure: respond with .error envelope (unknown-method pattern).\n const id = m.id ?? '';\n send({\n type: 'keys.registerAction.error',\n id,\n error: `invalid chord: ${(err as Error).message}`,\n } as NappletMessage);\n return;\n }\n }\n const result: KeysRegisterActionResultMessage = {\n type: 'keys.registerAction.result',\n id: m.id,\n actionId: m.action.id,\n ...(m.action.defaultKey ? { binding: m.action.defaultKey } : {}),\n };\n send(result as NappletMessage);\n return;\n }\n\n case 'keys.unregisterAction': {\n // Fire-and-forget per @napplet/nap/keys spec. Remove subscription if present.\n const m = message as NappletMessage & { actionId?: string };\n if (m.actionId && actionRegistry.has(m.actionId)) {\n const entry = actionRegistry.get(m.actionId)!;\n actionRegistry.delete(m.actionId);\n const set = windowActions.get(entry.windowId);\n if (set) {\n set.delete(m.actionId);\n // If the window has no remaining actions, drop its cached send\n // that no longer subscribes to anything.\n if (set.size === 0) {\n windowActions.delete(entry.windowId);\n sendHandles.delete(entry.windowId);\n }\n }\n }\n return;\n }\n\n default: {\n const id = (message as NappletMessage & { id?: string }).id ?? '';\n send({\n type: `${message.type}.error`,\n id,\n error: `Unknown keys method: ${message.type}`,\n } as NappletMessage);\n return;\n }\n }\n },\n\n onWindowDestroyed(windowId: string): void {\n const actions = windowActions.get(windowId);\n if (actions) {\n for (const actionId of actions) actionRegistry.delete(actionId);\n windowActions.delete(windowId);\n }\n sendHandles.delete(windowId);\n },\n\n destroy(): void {\n target.removeEventListener('keydown', listener);\n actionRegistry.clear();\n windowActions.clear();\n sendHandles.clear();\n },\n };\n}\n","import type { MediaAction, MediaMetadata } from '@napplet/nap/media/types';\nimport type { HostMediaBridge } from './media-service.js';\n\n/** Silent-audio prime data URL (4 kHz silent WAV, 44 bytes, zero network dependency).\n * Browsers refuse to render OS media controls without a playing audio element —\n * this silent loop primes the MediaSession API. Per CONTEXT.md Area 1. */\nconst SILENT_AUDIO_DATA_URL =\n 'data:audio/wav;base64,UklGRiQAAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQAAAAA=';\n\n/** Default action set — all 5 nap-media transport actions. */\nexport const DEFAULT_MEDIA_ACTIONS: readonly MediaAction[] = ['play', 'pause', 'next', 'prev', 'seek'];\n\n/** Minimal subset of navigator.mediaSession the browser bridge depends on. Makes the bridge\n * Node/test-safe: unit tests pass a MockMediaSession via mediaSessionTarget.\n * The handler parameter uses `details?` (optional) so both the real DOM impl\n * (which always passes an object) and test mocks that omit details both satisfy\n * this type structurally. */\nexport type MediaSessionTarget = {\n metadata: MediaMetadataLike | null;\n playbackState: 'none' | 'playing' | 'paused';\n setActionHandler(action: string, handler: ((details?: { action?: string; seekTime?: number }) => void) | null): void;\n};\n\n/** Structural subset of the DOM MediaMetadata class — assignable from a plain object\n * with title/artist/album/artwork fields. The browser impl uses `new MediaMetadata({...})`;\n * tests can pass a plain object. */\nexport type MediaMetadataLike = { title?: string; artist?: string; album?: string; artwork?: unknown };\n\n/** Mapping from DOM MediaSession action names to nap-media MediaAction literals. */\nconst ACTION_MATRIX: ReadonlyArray<[string, MediaAction]> = [\n ['play', 'play'],\n ['pause', 'pause'],\n ['nexttrack', 'next'],\n ['previoustrack', 'prev'],\n ['seekto', 'seek'],\n];\n\n/**\n * Reference browser implementation of {@link HostMediaBridge}.\n *\n * Mirrors metadata to `navigator.mediaSession.metadata` (via the DOM\n * `MediaMetadata` constructor when available; plain-object fallback in test\n * envs). Mirrors playback state to `navigator.mediaSession.playbackState`\n * with the canonical mapping: 'playing' maps to 'playing', 'paused' maps to\n * 'paused', 'buffering' maps to 'paused', 'stopped' maps to 'none'. Installs\n * `setActionHandler` callbacks for play/pause/nexttrack/previoustrack/seekto\n * that fan into the onAction subscriber with the mapped `MediaAction` literal\n * (and `value` from `details.seekTime` for seekto). When `setActiveSession` is\n * called with a non-null `actions` parameter, only the declared actions get\n * active handlers — the remaining are cleared (matching the capabilities\n * narrowing behavior of Plan 27-01's inline implementation).\n *\n * Installs a silent-audio prime (4 kHz silent WAV data URL) when the first\n * session becomes active (setActiveSession called with a non-null sessionId) —\n * browsers refuse to render OS media controls without a playing audio element.\n * Removes the element when destroySession brings the active session count to\n * zero.\n *\n * @param opts.mediaSessionTarget - Override navigator.mediaSession (tests).\n * @param opts.documentTarget - Override document (tests; pass null to disable silent-audio prime).\n */\nexport function createBrowserMediaBridge(opts: {\n mediaSessionTarget?: MediaSessionTarget;\n documentTarget?: Document | null;\n} = {}): HostMediaBridge {\n const ms: MediaSessionTarget | null =\n opts.mediaSessionTarget\n ?? (typeof navigator !== 'undefined' && 'mediaSession' in navigator\n ? (navigator.mediaSession as MediaSessionTarget)\n : null);\n const doc: Document | null =\n opts.documentTarget !== undefined\n ? opts.documentTarget\n : (typeof document !== 'undefined' ? document : null);\n\n let silentAudioEl: HTMLAudioElement | null = null;\n let activeSessionId: string | null = null;\n let sessionsActive = 0;\n const actionCallbacks = new Set<(sessionId: string, action: MediaAction, value?: number) => void>();\n\n function primeSilentAudio(): void {\n if (silentAudioEl || !doc) return;\n const el = doc.createElement('audio') as HTMLAudioElement;\n el.src = SILENT_AUDIO_DATA_URL;\n el.loop = true;\n el.style.display = 'none';\n (el as HTMLAudioElement).setAttribute('data-kehto-silent-audio-prime', 'true');\n doc.body.appendChild(el);\n void el.play().catch(() => { /* autoplay refused — metadata mirror still works */ });\n silentAudioEl = el;\n }\n\n function teardownSilentAudio(): void {\n if (!silentAudioEl) return;\n try { silentAudioEl.pause(); } catch { /* best-effort */ }\n try { silentAudioEl.remove(); } catch { /* best-effort */ }\n silentAudioEl = null;\n }\n\n /**\n * Install action handlers for the given set of nap-media actions. Actions not\n * in the set get their handler cleared. Matches Plan 27-01's installActionHandlersFor\n * behavior exactly so capabilities-narrowing tests continue to pass.\n */\n function applyActionHandlers(actions: readonly MediaAction[] = DEFAULT_MEDIA_ACTIONS): void {\n if (!ms) return;\n for (const [domAction, napAction] of ACTION_MATRIX) {\n if (!actions.includes(napAction)) {\n try { ms.setActionHandler(domAction, null); } catch { /* best-effort */ }\n continue;\n }\n ms.setActionHandler(domAction, (details) => {\n if (!activeSessionId) return;\n const value = napAction === 'seek' && typeof details?.seekTime === 'number' ? details.seekTime : undefined;\n for (const cb of actionCallbacks) {\n cb(activeSessionId, napAction, value);\n }\n });\n }\n }\n\n function writeMetadata(metadata: MediaMetadata | undefined): void {\n if (!ms) return;\n if (!metadata) { ms.metadata = null; return; }\n const artwork = metadata.artwork?.url ? [{ src: metadata.artwork.url }] : undefined;\n const init: MediaMetadataLike & { artwork?: unknown } = {\n title: metadata.title ?? '',\n artist: metadata.artist ?? '',\n album: metadata.album ?? '',\n ...(artwork ? { artwork } : {}),\n };\n try {\n const ctor = (globalThis as typeof globalThis & { MediaMetadata?: new (init: MediaMetadataLike) => MediaMetadataLike }).MediaMetadata;\n ms.metadata = ctor ? new ctor(init) : (init as MediaMetadataLike);\n } catch {\n ms.metadata = init as MediaMetadataLike;\n }\n }\n\n return {\n setMetadata(sessionId, metadata) {\n if (sessionId === activeSessionId) writeMetadata(metadata);\n },\n setPlaybackState(sessionId, state) {\n if (!ms || sessionId !== activeSessionId) return;\n ms.playbackState =\n state === 'playing' ? 'playing'\n : state === 'paused' || state === 'buffering' ? 'paused'\n : 'none';\n },\n onAction(callback) {\n actionCallbacks.add(callback);\n return () => { actionCallbacks.delete(callback); };\n },\n setActiveSession(sessionId, actions) {\n activeSessionId = sessionId;\n if (!sessionId) {\n if (ms) {\n ms.metadata = null;\n ms.playbackState = 'none';\n for (const [domAction] of ACTION_MATRIX) {\n try { ms.setActionHandler(domAction, null); } catch { /* best-effort */ }\n }\n }\n return;\n }\n // Prime silent-audio on first session becoming active.\n if (sessionsActive === 0) { primeSilentAudio(); sessionsActive = 1; }\n applyActionHandlers(actions ?? DEFAULT_MEDIA_ACTIONS);\n },\n destroySession(_sessionId) {\n sessionsActive = Math.max(0, sessionsActive - 1);\n if (sessionsActive === 0) teardownSilentAudio();\n },\n };\n}\n","/**\n * media-service.ts — NIP-5D media NAP reference service (navigator.mediaSession\n * reference implementation).\n *\n * Handles the napplet-owned subset of @napplet/nap/media:\n * media.session.create (result), media.session.update, media.session.destroy,\n * media.state, media.capabilities.\n *\n * HostMediaBridge contract: {@link HostMediaBridge} defines the pluggable backend\n * contract for metadata/state mirroring + action routing. The browser reference\n * implementation is {@link createBrowserMediaBridge} (mirrors to navigator.mediaSession\n * with setActionHandler matrix). When no hostBridge option is passed, createMediaService\n * internally uses createBrowserMediaBridge as the default — behavior is identical\n * to the Plan 27-01 single-path implementation.\n *\n * navigator.mediaSession mirroring: on session.create the browser bridge mirrors the\n * napplet-supplied metadata to navigator.mediaSession.metadata via new MediaMetadata()\n * and installs setActionHandler callbacks for the 5 OS transport actions\n * (play / pause / nexttrack / previoustrack / seekto). Each callback emits a canonical\n * media.command envelope to the owning napplet — that is the @napplet/nap/media\n * MediaCommandMessage shape consumed by the SDK's mediaOnCommand() helper.\n *\n * NAP-MEDIA now distinguishes napplet-owned playback from shell-owned\n * playback. This reference backend supports `owner: \"napplet\"` because it\n * mirrors a napplet's own media element to navigator.mediaSession. It rejects\n * `owner: \"shell\"` until a host bridge provides policy-checked source fetching\n * and playback.\n *\n * media.command push: when the OS user clicks a media control (hardware key, lock-screen\n * transport, OS media overlay), the bridge's onAction callback fires. The service looks\n * up the active session's owning napplet, invokes the per-window send callback captured\n * at session.create time, and delivers:\n * { type: 'media.command', sessionId, action, value? }\n * where action is the nap-media MediaAction literal (play|pause|next|prev|seek) and\n * value is the seekTime (seconds) for seek.\n *\n * Silent-audio prime: the browser bridge creates a hidden <audio> element with a\n * 4 kHz silent WAV data URL and plays it when the first session becomes active\n * (setActiveSession with a non-null sessionId). Without a playing audio element in\n * the host page, most browsers refuse to render OS media controls. The element is\n * cleaned up when the last session is destroyed (via bridge.destroySession).\n *\n * Multi-session registry with last-active-wins semantics: every session.create is\n * tracked in a Map<sessionId, SessionEntry>; any media.state report promotes that\n * session to active. The active session's metadata and playback state are reflected\n * via bridge.setMetadata / bridge.setPlaybackState. When the active session is\n * destroyed, the next-most-recently-touched session is promoted automatically.\n *\n * Note: this is SEPARATE from packages/services/src/audio-service.ts, which\n * is the legacy inc-topic-based audio source registry (audio:* topic events\n * over inc.emit). media-service is the canonical @napplet/nap/media NIP-5D\n * path and they coexist — audio-service continues to track audio sources for\n * shell UI, while media-service handles the NAP protocol envelope surface.\n *\n * Shell -> Napplet push types (media.command) are emitted here when\n * bridge.onAction callbacks fire — this is the canonical Phase 27 real backend.\n */\n\nimport type { NappletMessage } from '@napplet/core';\n// DRIFT-CORE-06 — Phase 11-deviation: ServiceDescriptor dropped from @napplet/core\n// v0.2.0+ (napplet phase-81). Re-exported from @kehto/runtime (canonical home after Phase 24 DRIFT-01).\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\nimport type {\n MediaAction,\n MediaCapabilitiesMessage,\n MediaCommandMessage,\n MediaMetadata,\n MediaSessionDestroyMessage,\n MediaSessionUpdateMessage,\n MediaStateMessage,\n} from '@napplet/nap/media/types';\nimport {\n createBrowserMediaBridge,\n DEFAULT_MEDIA_ACTIONS,\n type MediaSessionTarget,\n} from './browser-media-bridge.js';\n\nexport { createBrowserMediaBridge } from './browser-media-bridge.js';\nexport type {\n MediaMetadataLike,\n MediaSessionTarget,\n} from './browser-media-bridge.js';\n\n/** Registry entry — maps a sessionId back to its owning window, metadata snapshot,\n * state snapshot, and declared capabilities. Internal; never on the wire. */\ninterface SessionEntry {\n sessionId: string;\n windowId: string;\n owner: MediaPlaybackOwner;\n source: MediaSourceRef | undefined;\n metadata: MediaMetadata | undefined;\n state: { status: 'playing' | 'paused' | 'stopped' | 'buffering'; position?: number; duration?: number; volume?: number } | undefined;\n actions: readonly MediaAction[]; // from media.capabilities; defaults to ['play','pause','next','prev','seek']\n /** Monotonic tick updated on every session.create / session.update / state — used for last-active-wins resolution when the current active session is destroyed. */\n lastTouched: number;\n}\n\n/** Media service version — follows semver. */\nconst MEDIA_SERVICE_VERSION = '1.1.0';\n\nexport type MediaPlaybackOwner = 'shell' | 'napplet';\n\nexport interface MediaSourceRef {\n url?: string;\n blossomHash?: string;\n nostr?: {\n eventId?: string;\n address?: string;\n relays?: string[];\n };\n mimeType?: string;\n}\n\nexport interface MediaSessionCreateOptions {\n owner: MediaPlaybackOwner;\n sessionId?: string;\n source?: MediaSourceRef;\n metadata?: MediaMetadata;\n capabilities?: MediaAction[];\n autoplay?: boolean;\n live?: boolean;\n}\n\ntype MediaSessionCreateEnvelope = NappletMessage & Partial<MediaSessionCreateOptions> & {\n type: 'media.session.create';\n id?: string;\n};\n\ntype MediaSessionCreateResultEnvelope = NappletMessage & {\n type: 'media.session.create.result';\n id: string;\n sessionId?: string;\n owner?: MediaPlaybackOwner;\n error?: string;\n};\n\n/**\n * Host-bridge contract for pluggable media backends.\n *\n * The browser reference implementation ({@link createBrowserMediaBridge}) mirrors\n * napplet-reported metadata/state to `navigator.mediaSession` and installs\n * `setActionHandler` callbacks that fan into the bridge's onAction subscribers.\n * It satisfies this interface with all 5 fields implemented (setActiveSession\n * switches the active session and optionally re-applies action-handler narrowing;\n * destroySession tears down the silent-audio prime on last-session teardown).\n *\n * Host apps (Electron, Tauri) implement this interface in their own code and\n * pass it via `createMediaService({ hostBridge: myBridge })` — the service\n * then delegates metadata/state mirroring + action routing to the bridge and\n * remains browser-free. Session-ownership bookkeeping (which windowId owns\n * which sessionId, which send callback routes media.command back to which\n * napplet) stays in the service layer — that's wire-protocol concern, not a\n * bridge concern.\n *\n * Reference implementations for Electron / Tauri are explicitly out of v1.4\n * scope and live in host-app examples / follow-up milestones (see\n * REQUIREMENTS.md \"Future Requirements\").\n *\n * @example\n * ```ts\n * // Host-app pseudocode (Electron main-process relay):\n * const electronBridge: HostMediaBridge = {\n * setMetadata(sessionId, md) { mediaBridge.sendMetadata({ sessionId, md }); },\n * setPlaybackState(sessionId, state) { mediaBridge.sendPlaybackState({ sessionId, state }); },\n * onAction(cb) {\n * const handler = (_: unknown, msg: { sessionId: string; action: MediaAction; value?: number }) =>\n * cb(msg.sessionId, msg.action, msg.value);\n * mediaBridge.onAction(handler);\n * return () => mediaBridge.offAction(handler);\n * },\n * };\n * const media = createMediaService({ hostBridge: electronBridge });\n * runtime.registerService('media', media);\n * ```\n */\nexport interface HostMediaBridge {\n /**\n * Set the metadata displayed on the OS transport surface for a session.\n * Called on session.create (with initial metadata) and on session.update\n * (with merged metadata) whenever the session is the active session.\n * Implementations MUST be idempotent.\n */\n setMetadata(sessionId: string, metadata: MediaMetadata): void;\n\n /**\n * Set the playback state for a session. Called on media.state reports\n * whenever the session is the active session. State strings match\n * nap-media MediaState.status exactly. Implementations MUST be idempotent.\n */\n setPlaybackState(sessionId: string, state: 'playing' | 'paused' | 'stopped' | 'buffering'): void;\n\n /**\n * Subscribe to OS-level action events (user clicks play/pause/seek/next/prev\n * on the transport surface). Returns an unsubscribe handle.\n *\n * The callback receives `(sessionId, action, value?)`. `sessionId` is the\n * bridge's currently-active session (the browser impl tracks this internally\n * via setActionHandler-at-fire-time; native impls track via setActiveSession).\n * `value` is populated for `action === 'seek'` (seek target in seconds) and\n * for `action === 'volume'` (0.0-1.0). The service dispatches the resulting\n * `media.command` envelope to the owning napplet of that session.\n */\n onAction(callback: (sessionId: string, action: MediaAction, value?: number) => void): () => void;\n\n /**\n * Optional: notify the bridge that the active session has changed. The\n * browser reference impl uses this to switch which session's metadata/state\n * is mirrored to the singleton navigator.mediaSession and to install (or\n * clear) action handlers for the session's declared capabilities.\n *\n * The optional `actions` parameter carries the session's declared capability\n * set so the bridge can narrow which OS transport buttons are active. When\n * omitted, the bridge applies its default set. Native OS bridges that track\n * active-session state internally may omit this field entirely.\n */\n setActiveSession?(sessionId: string | null, actions?: readonly MediaAction[]): void;\n\n /**\n * Optional: tear down per-session resources. The browser reference impl\n * uses this to remove the silent-audio prime element when the last session\n * is destroyed. Bridges that need no per-session teardown may omit this field.\n */\n destroySession?(sessionId: string): void;\n}\n\n/**\n * Optional host callbacks for the media service.\n *\n * Host shells can hook session creation and state updates to drive\n * their own UI (e.g., now-playing banner) without replacing the\n * ServiceHandler wholesale.\n *\n * @example\n * ```ts\n * const media = createMediaService({\n * onSessionCreate: (windowId, sessionId, metadata) => {\n * console.log(`[${windowId}] created session ${sessionId}`, metadata);\n * },\n * onState: (windowId, sessionId, state) => {\n * nowPlaying.update(windowId, state);\n * },\n * });\n *\n * runtime.registerService('media', media);\n * ```\n */\nexport interface MediaServiceOptions {\n /** Called when a napplet creates a session. May inspect/record the session. */\n onSessionCreate?: (windowId: string, sessionId: string, metadata?: unknown) => void;\n /** Called on media.state updates — high-frequency; keep handler work minimal. */\n onState?: (windowId: string, sessionId: string, state: unknown) => void;\n /** Called when a napplet destroys a session. */\n onSessionDestroy?: (windowId: string, sessionId: string) => void;\n /** Called when a napplet updates session metadata. */\n onSessionUpdate?: (windowId: string, sessionId: string, metadata: unknown) => void;\n /** Called when a napplet declares capabilities for a session. */\n onCapabilities?: (windowId: string, sessionId: string, actions: unknown) => void;\n\n /**\n * MediaSession target override (used by default browser bridge only).\n * Defaults to `navigator.mediaSession` when running in a browser. Pass a\n * MockMediaSession in unit tests. Ignored when `hostBridge` is provided.\n */\n mediaSessionTarget?: MediaSessionTarget;\n\n /**\n * DOM document override (used by default browser bridge only).\n * Defaults to `document` when available. Set to null to disable the silent-audio\n * prime entirely — useful in unit tests. Ignored when `hostBridge` is provided.\n */\n documentTarget?: Document | null;\n\n /**\n * Optional pluggable backend for metadata/state mirroring + OS action handling.\n * When provided, the service delegates setMetadata / setPlaybackState / onAction\n * to the bridge and skips navigator.mediaSession entirely. When omitted, the\n * service internally uses {@link createBrowserMediaBridge} as the default.\n * See {@link HostMediaBridge}.\n */\n hostBridge?: HostMediaBridge;\n}\n\ninterface MediaServiceState {\n bridge: HostMediaBridge;\n options: MediaServiceOptions;\n sessionRegistry: Map<string, SessionEntry>;\n windowSessions: Map<string, Set<string>>;\n sendHandles: Map<string, (msg: NappletMessage) => void>;\n activeSessionId: string | null;\n touchCounter: number;\n sessionCounter: number;\n}\n\nfunction createMediaServiceState(options: MediaServiceOptions, bridge: HostMediaBridge): MediaServiceState {\n return {\n bridge,\n options,\n sessionRegistry: new Map<string, SessionEntry>(),\n windowSessions: new Map<string, Set<string>>(),\n sendHandles: new Map<string, (msg: NappletMessage) => void>(),\n activeSessionId: null,\n touchCounter: 0,\n sessionCounter: 0,\n };\n}\n\nfunction setActive(state: MediaServiceState, sessionId: string | null, actions?: readonly MediaAction[]): void {\n state.activeSessionId = sessionId;\n state.bridge.setActiveSession?.(sessionId, actions);\n if (!sessionId) return;\n const entry = state.sessionRegistry.get(sessionId);\n if (!entry) return;\n if (entry.metadata) state.bridge.setMetadata(sessionId, entry.metadata);\n if (entry.state) state.bridge.setPlaybackState(sessionId, entry.state.status);\n}\n\nfunction promoteNextActiveOrClear(state: MediaServiceState): void {\n if (state.sessionRegistry.size === 0) {\n setActive(state, null);\n return;\n }\n let latest: SessionEntry | null = null;\n for (const entry of state.sessionRegistry.values()) {\n if (!latest || entry.lastTouched > latest.lastTouched) latest = entry;\n }\n setActive(state, latest ? latest.sessionId : null, latest?.actions);\n}\n\nfunction sendMediaCommand(state: MediaServiceState, sessionId: string, action: MediaAction, value?: number): void {\n const entry = state.sessionRegistry.get(sessionId);\n if (!entry) return;\n const send = state.sendHandles.get(entry.windowId);\n if (!send) return;\n const payload: MediaCommandMessage = {\n type: 'media.command',\n sessionId,\n action,\n ...(typeof value === 'number' ? { value } : {}),\n };\n send(payload as NappletMessage);\n}\n\nfunction registerWindowSession(state: MediaServiceState, windowId: string, sessionId: string): void {\n if (!state.windowSessions.has(windowId)) state.windowSessions.set(windowId, new Set());\n state.windowSessions.get(windowId)!.add(sessionId);\n}\n\nfunction sendSessionCreateResult(\n send: (msg: NappletMessage) => void,\n id: string | undefined,\n fields: Omit<MediaSessionCreateResultEnvelope, 'type' | 'id'>,\n): void {\n send({\n type: 'media.session.create.result',\n id: id ?? '',\n ...fields,\n } as NappletMessage);\n}\n\nfunction isMediaPlaybackOwner(value: unknown): value is MediaPlaybackOwner {\n return value === 'shell' || value === 'napplet';\n}\n\nfunction hasSourceRef(source: MediaSourceRef | undefined): boolean {\n if (!source) return false;\n if (typeof source.url === 'string' && source.url.length > 0) return true;\n if (typeof source.blossomHash === 'string' && source.blossomHash.length > 0) return true;\n if (source.nostr) {\n return Boolean(source.nostr.eventId || source.nostr.address);\n }\n return false;\n}\n\nfunction canonicalizeSessionId(\n state: MediaServiceState,\n windowId: string,\n preferredSessionId: string | undefined,\n): string {\n const trimmed = typeof preferredSessionId === 'string' ? preferredSessionId.trim() : '';\n const hint = trimmed || `session-${++state.sessionCounter}`;\n if (!state.sessionRegistry.has(hint)) return hint;\n\n let next: string;\n do {\n next = `${windowId}:${hint}:${++state.sessionCounter}`;\n } while (state.sessionRegistry.has(next));\n return next;\n}\n\nfunction handleSessionCreate(\n state: MediaServiceState,\n windowId: string,\n message: MediaSessionCreateEnvelope,\n send: (msg: NappletMessage) => void,\n): void {\n if (!isMediaPlaybackOwner(message.owner)) {\n sendSessionCreateResult(send, message.id, { error: 'missing owner' });\n return;\n }\n\n if (message.owner === 'shell') {\n if (!hasSourceRef(message.source)) {\n sendSessionCreateResult(send, message.id, { owner: 'shell', error: 'missing source' });\n return;\n }\n sendSessionCreateResult(send, message.id, { owner: 'shell', error: 'unsupported owner mode' });\n return;\n }\n\n state.sendHandles.set(windowId, send);\n const sessionId = canonicalizeSessionId(state, windowId, message.sessionId);\n const entry: SessionEntry = {\n sessionId,\n windowId,\n owner: message.owner,\n source: message.source,\n metadata: message.metadata,\n state: undefined,\n actions: message.capabilities ?? DEFAULT_MEDIA_ACTIONS,\n lastTouched: ++state.touchCounter,\n };\n state.sessionRegistry.set(sessionId, entry);\n registerWindowSession(state, windowId, sessionId);\n setActive(state, sessionId, entry.actions);\n state.options.onSessionCreate?.(windowId, sessionId, message.metadata);\n sendSessionCreateResult(send, message.id, { sessionId, owner: message.owner });\n}\n\nfunction handleSessionUpdate(state: MediaServiceState, windowId: string, message: MediaSessionUpdateMessage): void {\n const entry = state.sessionRegistry.get(message.sessionId);\n if (entry) {\n entry.metadata = { ...entry.metadata, ...message.metadata };\n entry.lastTouched = ++state.touchCounter;\n if (entry.owner === 'napplet' && message.sessionId === state.activeSessionId && entry.metadata) {\n state.bridge.setMetadata(message.sessionId, entry.metadata);\n }\n }\n state.options.onSessionUpdate?.(windowId, message.sessionId, message.metadata);\n}\n\nfunction handleSessionDestroy(state: MediaServiceState, windowId: string, message: MediaSessionDestroyMessage): void {\n const entry = state.sessionRegistry.get(message.sessionId);\n if (entry) {\n state.sessionRegistry.delete(message.sessionId);\n const set = state.windowSessions.get(entry.windowId);\n if (set) {\n set.delete(message.sessionId);\n if (set.size === 0) state.windowSessions.delete(entry.windowId);\n }\n state.bridge.destroySession?.(message.sessionId);\n if (message.sessionId === state.activeSessionId) promoteNextActiveOrClear(state);\n }\n state.options.onSessionDestroy?.(windowId, message.sessionId);\n}\n\nfunction handleMediaState(state: MediaServiceState, windowId: string, message: MediaStateMessage): void {\n const entry = state.sessionRegistry.get(message.sessionId);\n if (entry?.owner === 'napplet') {\n entry.state = {\n status: message.status,\n position: message.position,\n duration: message.duration,\n volume: message.volume,\n };\n entry.lastTouched = ++state.touchCounter;\n if (state.activeSessionId !== message.sessionId) setActive(state, message.sessionId, entry.actions);\n else state.bridge.setPlaybackState(message.sessionId, message.status);\n }\n state.options.onState?.(windowId, message.sessionId, message);\n}\n\nfunction handleMediaCapabilities(\n state: MediaServiceState,\n windowId: string,\n message: MediaCapabilitiesMessage,\n): void {\n const entry = state.sessionRegistry.get(message.sessionId);\n if (entry?.owner === 'napplet') {\n entry.actions = message.actions;\n entry.lastTouched = ++state.touchCounter;\n if (message.sessionId === state.activeSessionId) {\n state.bridge.setActiveSession?.(message.sessionId, entry.actions);\n }\n }\n state.options.onCapabilities?.(windowId, message.sessionId, message.actions);\n}\n\nfunction handleMediaMessage(\n state: MediaServiceState,\n windowId: string,\n message: NappletMessage,\n send: (msg: NappletMessage) => void,\n): void {\n switch (message.type) {\n case 'media.session.create':\n handleSessionCreate(state, windowId, message as MediaSessionCreateEnvelope, send);\n return;\n case 'media.session.update':\n handleSessionUpdate(state, windowId, message as MediaSessionUpdateMessage);\n return;\n case 'media.session.destroy':\n handleSessionDestroy(state, windowId, message as MediaSessionDestroyMessage);\n return;\n case 'media.state':\n handleMediaState(state, windowId, message as MediaStateMessage);\n return;\n case 'media.capabilities':\n handleMediaCapabilities(state, windowId, message as MediaCapabilitiesMessage);\n return;\n default: {\n const id = (message as NappletMessage & { id?: string }).id ?? '';\n send({\n type: `${message.type}.error`,\n id,\n error: `Unknown media method: ${message.type}`,\n } as NappletMessage);\n }\n }\n}\n\nfunction destroyWindowSessions(state: MediaServiceState, windowId: string): void {\n const sessions = state.windowSessions.get(windowId);\n if (sessions) {\n const ownedActive = state.activeSessionId !== null && sessions.has(state.activeSessionId);\n for (const sessionId of sessions) {\n state.sessionRegistry.delete(sessionId);\n state.bridge.destroySession?.(sessionId);\n }\n state.windowSessions.delete(windowId);\n if (ownedActive) promoteNextActiveOrClear(state);\n }\n state.sendHandles.delete(windowId);\n}\n\nfunction destroyMediaState(state: MediaServiceState, unsubscribeAction: () => void): void {\n unsubscribeAction();\n for (const sessionId of state.sessionRegistry.keys()) state.bridge.destroySession?.(sessionId);\n state.bridge.setActiveSession?.(null);\n state.sessionRegistry.clear();\n state.windowSessions.clear();\n state.sendHandles.clear();\n state.activeSessionId = null;\n state.touchCounter = 0;\n state.sessionCounter = 0;\n}\n\n/**\n * Create a media NAP service handler with navigator.mediaSession integration.\n *\n * Implements the 5 napplet->shell media.* request types defined in\n * `@napplet/nap/media`. Only `media.session.create` produces a reply\n * envelope (`media.session.create.result`) — the remaining four\n * (`session.update`, `session.destroy`, `state`, `capabilities`) are\n * fire-and-forget per the NAP spec.\n *\n * Unknown `media.*` actions produce a `<type>.error` envelope so\n * napplets are never left hanging on a malformed request.\n *\n * @param options - Optional host callbacks for session lifecycle + state, plus\n * mediaSessionTarget and documentTarget for test injection, and an optional\n * hostBridge for native-OS media backend delegation.\n * @returns A ServiceHandler (with `destroy()`) to register with the runtime\n *\n * @example\n * ```ts\n * import { createMediaService } from '@kehto/services';\n *\n * const media = createMediaService();\n * runtime.registerService('media', media);\n * // Later, on shell teardown:\n * media.destroy();\n * ```\n */\nexport function createMediaService(options: MediaServiceOptions = {}): ServiceHandler & { destroy(): void } {\n const descriptor: ServiceDescriptor = {\n name: 'media',\n version: MEDIA_SERVICE_VERSION,\n description: options.hostBridge\n ? 'NIP-5D media NAP reference handler (host-bridge delegated)'\n : 'NIP-5D media NAP reference handler (navigator.mediaSession mirror)',\n };\n\n const bridge: HostMediaBridge = options.hostBridge\n ?? createBrowserMediaBridge({\n mediaSessionTarget: options.mediaSessionTarget,\n documentTarget: options.documentTarget,\n });\n const state = createMediaServiceState(options, bridge);\n\n const unsubscribeAction = bridge.onAction((sessionId, action, value) => {\n sendMediaCommand(state, sessionId, action, value);\n });\n\n return {\n descriptor,\n\n handleMessage(windowId: string, message: NappletMessage, send: (msg: NappletMessage) => void): void {\n handleMediaMessage(state, windowId, message, send);\n },\n\n onWindowDestroyed(windowId: string): void {\n destroyWindowSessions(state, windowId);\n },\n\n destroy(): void {\n destroyMediaState(state, unsubscribeAction);\n },\n };\n}\n","/**\n * notify-service.ts — NIP-5D notify NAP reference service (stub-level).\n *\n * Handles the 5 napplet -> shell request types from `@napplet/nap/notify`:\n * - `notify.send` -> `notify.send.result` (shell-assigned id)\n * - `notify.dismiss` -> fire-and-forget\n * - `notify.badge` -> fire-and-forget\n * - `notify.channel.register` -> fire-and-forget\n * - `notify.permission.request` -> `notify.permission.result { granted }`\n *\n * Stub-level: no real Notification API calls, no real channel registry.\n * Host apps wire a real backend via\n * `runtime.registerService('notify', realHandler)`.\n *\n * Coexistence note: this is SEPARATE from\n * `packages/services/src/notification-service.ts`, which is the legacy\n * inc-topic-based notification registry (operates on `inc.emit` topics\n * under `notifications:*`). `notify-service.ts` is the canonical\n * `@napplet/nap/notify` NIP-5D path and lives alongside the legacy module.\n * If the host app registers this service via\n * `runtime.registerService('notify', ...)`, @napplet/nap/notify messages\n * land here; the legacy inc-emit topic path remains untouched.\n *\n * Shell -> napplet push messages (`notify.action`, `notify.clicked`,\n * `notify.dismissed`, `notify.controls`) are not emitted by this stub —\n * they are the host app's responsibility and are deferred to a future plan.\n */\n\nimport type { NappletMessage } from '@napplet/core';\n// DRIFT-CORE-06 — Phase 11-deviation: ServiceDescriptor dropped from @napplet/core\n// v0.2.0+ (napplet phase-81). Re-exported from @kehto/runtime (canonical home after Phase 24 DRIFT-01).\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\nimport type {\n NotifySendMessage,\n NotifySendResultMessage,\n NotifyPermissionRequestMessage,\n NotifyPermissionResultMessage,\n} from '@napplet/nap/notify/types';\n\n/** Notify service version — follows semver. */\nconst NOTIFY_SERVICE_VERSION = '1.0.0';\n\n/**\n * Optional configuration for `createNotifyService`.\n *\n * @example\n * ```ts\n * const notify = createNotifyService({\n * generateId: () => crypto.randomUUID(),\n * defaultGrant: false,\n * onSend: (windowId, msg) => console.log(`napplet ${windowId} sent ${msg.title}`),\n * });\n * runtime.registerService('notify', notify);\n * ```\n */\nexport interface NotifyServiceOptions {\n /**\n * Generate a shell-assigned notification ID for `notify.send.result`.\n * Default: a monotonically increasing `shell-<n>` counter.\n */\n generateId?: () => string;\n /**\n * Default permission grant for `notify.permission.request`.\n * Host apps that want real permission prompts should replace this\n * service with a real backend via `runtime.registerService`.\n * Default: `true`.\n */\n defaultGrant?: boolean;\n /**\n * Called synchronously when a napplet dispatches `notify.send`.\n * Intended for host-app plumbing (UI toast, logging, etc.) without\n * requiring a full backend replacement.\n */\n onSend?: (windowId: string, payload: NotifySendMessage) => void;\n}\n\n/**\n * Create a stub-level notify service handler.\n *\n * Answers the 5 napplet->shell request types from `@napplet/nap/notify`.\n * Does NOT implement a real backend (no DOM Notification API, no channel\n * registry, no permission prompt). Host apps replace this via\n * `runtime.registerService('notify', realHandler)` when a real backend is\n * needed.\n *\n * @param options - Optional service configuration (see NotifyServiceOptions)\n * @returns A ServiceHandler to register with the runtime under domain `notify`\n *\n * @example\n * ```ts\n * import { createNotifyService } from '@kehto/services';\n *\n * const notify = createNotifyService();\n * runtime.registerService('notify', notify);\n * ```\n */\nexport function createNotifyService(options: NotifyServiceOptions = {}): ServiceHandler {\n let counter = 0;\n const gen = options.generateId ?? ((): string => {\n counter += 1;\n return `shell-${counter}`;\n });\n const defaultGrant = options.defaultGrant ?? true;\n\n const descriptor: ServiceDescriptor = {\n name: 'notify',\n version: NOTIFY_SERVICE_VERSION,\n description: 'NIP-5D notify NAP reference handler (stub)',\n };\n\n return {\n descriptor,\n\n handleMessage(windowId: string, message: NappletMessage, send: (msg: NappletMessage) => void): void {\n switch (message.type) {\n case 'notify.send': {\n const m = message as NotifySendMessage;\n options.onSend?.(windowId, m);\n const result: NotifySendResultMessage = {\n type: 'notify.send.result',\n id: m.id,\n notificationId: gen(),\n };\n send(result as NappletMessage);\n return;\n }\n\n case 'notify.dismiss':\n case 'notify.badge':\n case 'notify.channel.register':\n // state to mutate; host apps with real backends override.\n return;\n\n case 'notify.permission.request': {\n const m = message as NotifyPermissionRequestMessage;\n const result: NotifyPermissionResultMessage = {\n type: 'notify.permission.result',\n id: m.id,\n granted: defaultGrant,\n };\n send(result as NappletMessage);\n return;\n }\n\n default: {\n const id = (message as NappletMessage & { id?: string }).id ?? '';\n send({\n type: `${message.type}.error`,\n id,\n error: `Unknown notify method: ${message.type}`,\n } as NappletMessage);\n }\n }\n },\n\n onWindowDestroyed(_windowId: string): void {\n // No per-window state in the stub. Host apps with real backends\n // override to clean up pending notifications / channel registrations.\n },\n };\n}\n","/**\n * theme-service.ts — NIP-5D theme NAP reference service.\n *\n * Handles the single napplet->shell request type from @napplet/nap/theme:\n * - `theme.get` -> `theme.get.result { theme }` (current theme)\n *\n * Exposes a host-facing `publishTheme(theme)` handle that:\n * 1. Replaces the service's internal current theme.\n * 2. Invokes `options.onBroadcast(envelope)` synchronously with a\n * `theme.changed` envelope so the shell adapter (Plan 13-02) can\n * fan-out the push to every registered napplet.\n * 3. Returns the envelope so callers can use it directly if they prefer.\n *\n * The default theme values are centralized here and mirrored in the runtime's\n * fallback path (`packages/runtime/src/runtime.ts`) — runtime does NOT import\n * from @kehto/services because services depends on runtime (one-way only).\n *\n * Host apps replace this via `runtime.registerService('theme', realHandler)`\n * when real CSS injection / storage / dark-mode logic is needed.\n *\n * @example\n * ```ts\n * import { createThemeService } from '@kehto/services';\n *\n * const theme = createThemeService({\n * onBroadcast: (envelope) => broadcastToAllNapplets(envelope),\n * });\n * runtime.registerService('theme', theme.handler);\n *\n * // Later, when the user flips dark/light mode:\n * theme.publishTheme(newTheme);\n * ```\n */\n\nimport type { NappletMessage } from '@napplet/core';\n// DRIFT-CORE-06 — Phase 11-deviation: ServiceDescriptor dropped from\n// @napplet/core v0.2.0+ (napplet phase-81). Re-exported from\n// @kehto/runtime (canonical home after Phase 24 DRIFT-01).\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\nimport type {\n Theme,\n ThemeChangedMessage,\n ThemeGetResultMessage,\n} from '@napplet/nap/theme/types';\n\n/** Theme service version — follows semver. */\nconst THEME_SERVICE_VERSION = '1.0.0';\n\n/**\n * Canonical default theme values.\n *\n * Must stay synchronized with the runtime fallback constant at\n * `packages/runtime/src/runtime.ts` (THEME_FALLBACK_DEFAULT). The runtime\n * keeps a local copy instead of importing from @kehto/services to avoid a\n * runtime->services dependency (services depends on runtime; one-way).\n */\nconst DEFAULT_THEME: Theme = {\n colors: {\n background: '#0a0a0a',\n text: '#e0e0e0',\n primary: '#7aa2f7',\n },\n // fonts, background, title intentionally undefined — all optional per\n // @napplet/nap/theme Theme interface.\n};\n\n/**\n * Configuration for `createThemeService`.\n *\n * @example\n * ```ts\n * const theme = createThemeService({\n * initialTheme: { colors: { background: '#fff', text: '#000', primary: '#00f' } },\n * onBroadcast: (envelope) => shellBridge.broadcastToAll(envelope),\n * });\n * ```\n */\nexport interface ThemeServiceOptions {\n /**\n * Override the default theme payload. If omitted, the service starts with\n * the canonical defaults (`#0a0a0a / #e0e0e0 / #7aa2f7`).\n */\n initialTheme?: Theme;\n\n /**\n * Called synchronously from `publishTheme(theme)` with a `theme.changed`\n * envelope. Intended for the shell adapter (Plan 13-02) to fan-out the\n * push to every registered napplet via the runtime's sendToNapplet\n * primitive.\n *\n * Keep this callback shape framework-agnostic — the service does NOT\n * import any shell / browser APIs.\n */\n onBroadcast?: (envelope: ThemeChangedMessage) => void;\n}\n\n/**\n * A theme service bundle — the ServiceHandler that handles `theme.*`\n * envelopes, the host-facing `publishTheme(theme)` handle for theme-change\n * broadcasts, and a `getCurrentTheme()` accessor for host-side reads.\n */\nexport interface ThemeService {\n /** Register this with the runtime via `runtime.registerService('theme', handler)`. */\n handler: ServiceHandler;\n\n /**\n * Publish a theme-change to the shell adapter. Updates the service's\n * internal current theme, invokes `options.onBroadcast` with a\n * `theme.changed` envelope, and returns the envelope.\n *\n * @param theme - The new theme payload\n * @returns A `theme.changed` envelope (same one passed to onBroadcast)\n */\n publishTheme: (theme: Theme) => ThemeChangedMessage;\n\n /** Return the current theme. Equivalent to the payload a napplet's `theme.get` would receive. */\n getCurrentTheme: () => Theme;\n}\n\n/**\n * Create a theme service that handles the NIP-5D `theme.*` NAP.\n *\n * Answers `theme.get` with the current theme (default or\n * `options.initialTheme`). Exposes `publishTheme(theme)` for the host app to\n * broadcast theme changes to every registered napplet — the shell adapter\n * (Plan 13-02) wires `onBroadcast` to `runtime.sendToNapplet` fan-out.\n *\n * @param options - Optional service configuration (see ThemeServiceOptions)\n * @returns A ThemeService bundle ready for `runtime.registerService('theme', service.handler)`\n *\n * @example\n * ```ts\n * import { createThemeService } from '@kehto/services';\n *\n * const theme = createThemeService();\n * runtime.registerService('theme', theme.handler);\n * theme.publishTheme({ colors: { background: '#fff', text: '#000', primary: '#00f' } });\n * ```\n */\nexport function createThemeService(options: ThemeServiceOptions = {}): ThemeService {\n let currentTheme: Theme = options.initialTheme ?? DEFAULT_THEME;\n\n const descriptor: ServiceDescriptor = {\n name: 'theme',\n version: THEME_SERVICE_VERSION,\n description: 'NIP-5D theme NAP reference handler',\n };\n\n const handler: ServiceHandler = {\n descriptor,\n\n handleMessage(\n _windowId: string,\n message: NappletMessage,\n send: (msg: NappletMessage) => void,\n ): void {\n const id = (message as NappletMessage & { id?: string }).id ?? '';\n\n if (message.type === 'theme.get') {\n const result: ThemeGetResultMessage = {\n type: 'theme.get.result',\n id,\n theme: currentTheme,\n };\n send(result as NappletMessage);\n return;\n }\n\n send({\n type: `${message.type}.error`,\n id,\n error: `Unknown theme method: ${message.type}`,\n } as NappletMessage);\n },\n\n // Theme service has no per-window state to clean up.\n onWindowDestroyed(_windowId: string): void {\n /* no-op */\n },\n };\n\n function publishTheme(theme: Theme): ThemeChangedMessage {\n currentTheme = theme;\n const envelope: ThemeChangedMessage = { type: 'theme.changed', theme };\n options.onBroadcast?.(envelope);\n return envelope;\n }\n\n function getCurrentTheme(): Theme {\n return currentTheme;\n }\n\n return { handler, publishTheme, getCurrentTheme };\n}\n","/**\n * config-service.ts — NAP-CONFIG reference service (9th NAP domain, v1.7 Phase 39).\n *\n * Shell-side reference implementation for the canonical NAP-CONFIG wire\n * protocol (`@napplet/nap/config`, published at `^0.3.0`). Handles the full\n * 8-message discriminated union: 5 napplet→shell request types + 3\n * shell→napplet result/push types.\n *\n * ──────────────────────────── SCOPE BOUNDARY (CONFIG-04) ─────────────────────────\n * NAP-CONFIG is **shell-managed per-napplet configuration**. Napplets observe\n * values via `config.get` (one-shot) or `config.subscribe` (snapshot + live\n * push). The shell is the **sole writer** — there is intentionally **NO**\n * `config.set` wire message. Napplets cannot mutate configuration values;\n * the shell owns persistence and the update flow.\n *\n * Do NOT use this service as a general key-value store. NAP-STORAGE\n * (`state:read` / `state:write`) remains the general KV surface. Using\n * NAP-CONFIG to store e.g. `{ lastScrollPosition: 420 }` is an anti-pattern\n * (H-07 in PITFALLS.md) — such state belongs in NAP-STORAGE.\n * ──────────────────────────────────────────────────────────────────────────────────\n *\n * Host integration: provide `getValues()` returning the current\n * `ConfigValues` snapshot. Call the returned `publishValues(newValues)`\n * whenever the configuration changes — the service fans the new snapshot\n * out to every napplet that has an active `config.subscribe`.\n *\n * Optional: provide `registerSchema` to accept napplet-declared schemas at\n * runtime (the ref impl does a minimal shape check using the Core Subset\n * validator; use `ajv` in host impls that need strict draft-07 conformance).\n * Provide `openSettings` to open a shell-side UI for the napplet (no\n * response envelope — fire-and-forget UI hook).\n *\n * @example\n * ```ts\n * import { createConfigService } from '@kehto/services';\n *\n * const configFixtures = { theme: 'dark', density: 'compact', recentSearches: [] };\n * const config = createConfigService({\n * getValues: () => ({ ...configFixtures }),\n * });\n * runtime.registerService('config', config.handler);\n *\n * // Later, when shell-side values change:\n * configFixtures.theme = 'light';\n * config.publishValues({ ...configFixtures });\n * ```\n */\n\nimport type { NappletMessage } from '@napplet/core';\n// DRIFT-CORE-06 — Phase 11-deviation: ServiceDescriptor dropped from @napplet/core\n// v0.2.0+ (napplet phase-81). Re-exported from @kehto/runtime (canonical home after Phase 24 DRIFT-01).\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\nimport type {\n ConfigGetMessage,\n ConfigRegisterSchemaMessage,\n ConfigOpenSettingsMessage,\n ConfigValues,\n ConfigValuesMessage,\n ConfigRegisterSchemaResultMessage,\n ConfigSchemaErrorCode,\n NappletConfigSchema,\n} from '@napplet/nap/config/types';\n\n/** Config service version — follows semver. */\nconst CONFIG_SERVICE_VERSION = '1.0.0';\n\n/**\n * Shape returned by a successful `registerSchema` result (ok=true) or a\n * rejection (ok=false + code + error). Mirrors the wire envelope fields.\n */\nexport type ConfigSchemaValidation =\n | { ok: true }\n | { ok: false; code: ConfigSchemaErrorCode; error: string };\n\n/**\n * Configuration options for `createConfigService` (options-as-bridge\n * per v1.6 Decision 18).\n *\n * @example\n * ```ts\n * const config = createConfigService({\n * getValues: () => ({ theme: 'dark', density: 'compact' }),\n * openSettings: (windowId, section) => showSettingsPanel(windowId, section),\n * });\n * ```\n */\nexport interface ConfigServiceOptions {\n /**\n * Returns the current configuration values snapshot.\n * Called on every `config.get` and at every `config.subscribe` initial push.\n * Implementations should return a fresh object (not a mutable reference).\n */\n getValues(): ConfigValues;\n\n /**\n * Optional: receive notification when a napplet subscribes to config updates.\n * Fire-and-forget — the service tracks the subscription internally regardless.\n */\n onSubscribe?: (windowId: string) => void;\n\n /**\n * Optional: receive notification when a napplet unsubscribes.\n */\n onUnsubscribe?: (windowId: string) => void;\n\n /**\n * Optional: validate and store a napplet-provided schema.\n *\n * If omitted, the ref impl runs its own Core Subset check (hand-coded\n * validator; 30-50 lines) and returns ok/reject. Hosts that need strict\n * draft-07 conformance should provide an ajv-backed implementation.\n *\n * Return shape mirrors `config.registerSchema.result` wire envelope\n * (minus the `id` — the dispatch layer correlates).\n */\n registerSchema?: (\n windowId: string,\n schema: NappletConfigSchema,\n version: number | undefined,\n ) => ConfigSchemaValidation;\n\n /**\n * Optional: open the shell-side settings UI for this napplet.\n * Fire-and-forget — no response envelope per the wire spec.\n * If omitted, `config.openSettings` is silently dropped (D10 allows\n * the config-demo napplet to function without a settings UI).\n */\n openSettings?: (windowId: string, section: string | undefined) => void;\n}\n\n/**\n * NAP-CONFIG reference service bundle — `handler` to register with the\n * runtime, `publishValues` for the host app to push updates live to all\n * subscribed napplets.\n */\nexport interface ConfigService {\n /** Register this with the runtime via `runtime.registerService('config', handler)`. */\n handler: ServiceHandler;\n\n /**\n * Broadcast a new values snapshot to every napplet with an active\n * `config.subscribe`. Each subscriber receives a `config.values` envelope\n * with no `id` (push form per wire spec — absence of `id` distinguishes\n * push from correlated `config.get` response).\n *\n * @param values - The new configuration snapshot (full object, not a diff)\n */\n publishValues(values: ConfigValues): void;\n}\n\n/**\n * Minimal JSON Schema validator covering the NAP-CONFIG Core Subset:\n * type: object / string / number / boolean / array, required[], default, properties.\n *\n * Explicitly rejects: $ref, pattern, oneOf/anyOf/allOf/not, if/then/else.\n * Returns { ok: true } on shape sanity, otherwise an error code per the\n * canonical ConfigSchemaErrorCode union.\n *\n * Host apps that need strict draft-07 conformance should supply a custom\n * `registerSchema` callback backed by ajv@8.\n */\nfunction validateCoreSubset(schema: unknown): ConfigSchemaValidation {\n if (typeof schema !== 'object' || schema === null || Array.isArray(schema)) {\n return { ok: false, code: 'invalid-schema', error: 'schema root must be an object' };\n }\n const s = schema as Record<string, unknown>;\n\n // Reject forbidden keywords (NAP-CONFIG Core Subset limits per spec).\n if ('$ref' in s) {\n return { ok: false, code: 'ref-not-allowed', error: '$ref is not permitted in the Core Subset' };\n }\n if ('pattern' in s) {\n return {\n ok: false,\n code: 'pattern-not-allowed',\n error: 'pattern is not permitted in the Core Subset',\n };\n }\n if ('oneOf' in s || 'anyOf' in s || 'allOf' in s || 'not' in s) {\n return {\n ok: false,\n code: 'invalid-schema',\n error: 'oneOf/anyOf/allOf/not are not permitted in the Core Subset',\n };\n }\n if ('if' in s || 'then' in s || 'else' in s) {\n return {\n ok: false,\n code: 'invalid-schema',\n error: 'if/then/else are not permitted in the Core Subset',\n };\n }\n if (s.type !== 'object') {\n return { ok: false, code: 'invalid-schema', error: 'schema root must have type: \"object\"' };\n }\n\n // Shallow properties check: each declared property must use a supported type.\n const props = s.properties;\n if (props !== undefined && (typeof props !== 'object' || props === null)) {\n return { ok: false, code: 'invalid-schema', error: 'properties must be an object' };\n }\n if (props) {\n for (const [key, val] of Object.entries(props as Record<string, unknown>)) {\n if (typeof val !== 'object' || val === null) {\n return {\n ok: false,\n code: 'invalid-schema',\n error: `property \"${key}\" must be an object schema`,\n };\n }\n const pv = val as Record<string, unknown>;\n const ALLOWED_TYPES = new Set(['string', 'number', 'boolean', 'array', 'object']);\n if (pv.type !== undefined && !ALLOWED_TYPES.has(pv.type as string)) {\n return {\n ok: false,\n code: 'invalid-schema',\n error: `property \"${key}\" must have type: string|number|boolean|array|object`,\n };\n }\n }\n }\n\n return { ok: true };\n}\n\n/**\n * Create a NAP-CONFIG reference service.\n *\n * Shell-writes, napplet-reads. Handles the full `@napplet/nap/config` wire\n * protocol: `config.get` (correlated snapshot), `config.subscribe` /\n * `config.unsubscribe` (live push stream), `config.registerSchema` (optional\n * schema registration + Core Subset validation), `config.openSettings`\n * (optional UI deep-link, fire-and-forget).\n *\n * Returns a `ConfigService` bundle: `{ handler, publishValues }`.\n * Register `handler` with the runtime; call `publishValues(newValues)` from\n * the shell whenever config state changes.\n *\n * @param options - Host-supplied implementation hooks (options-as-bridge,\n * v1.6 Decision 18). `getValues` is required; all other fields are optional.\n * @returns A ConfigService bundle.\n *\n * @see ConfigServiceOptions for the options shape.\n * @see packages/services/src/theme-service.ts for the sibling pattern.\n * @see SCOPE BOUNDARY comment at the top of this file re: NAP-STORAGE separation.\n *\n * @example\n * ```ts\n * import { createConfigService } from '@kehto/services';\n *\n * const config = createConfigService({\n * getValues: () => ({ theme: 'dark', density: 'compact' }),\n * openSettings: (windowId, section) => openSettingsUI(section),\n * });\n * runtime.registerService('config', config.handler);\n *\n * // Push a live update to all subscribers:\n * config.publishValues({ theme: 'light', density: 'compact' });\n * ```\n */\nexport function createConfigService(options: ConfigServiceOptions): ConfigService {\n /**\n * Per-window subscriber set. Maps windowId → the send callback captured at\n * `config.subscribe` time. `publishValues` fans out to every entry.\n */\n const subscribers = new Map<string, (msg: NappletMessage) => void>();\n\n const descriptor: ServiceDescriptor = {\n name: 'config',\n version: CONFIG_SERVICE_VERSION,\n description: 'NAP-CONFIG reference service — shell-writes, napplet-reads configuration',\n };\n\n const handler: ServiceHandler = {\n descriptor,\n\n handleMessage(\n windowId: string,\n message: NappletMessage,\n send: (msg: NappletMessage) => void,\n ): void {\n switch (message.type) {\n case 'config.get': {\n const m = message as ConfigGetMessage;\n const reply: ConfigValuesMessage = {\n type: 'config.values',\n id: m.id,\n values: options.getValues(),\n };\n send(reply as NappletMessage);\n return;\n }\n\n case 'config.subscribe': {\n // Capture the send callback so publishValues can fan pushes out.\n subscribers.set(windowId, send);\n // Immediate initial snapshot push — no `id` (push form per wire spec).\n const push: ConfigValuesMessage = {\n type: 'config.values',\n values: options.getValues(),\n };\n send(push as NappletMessage);\n options.onSubscribe?.(windowId);\n return;\n }\n\n case 'config.unsubscribe': {\n subscribers.delete(windowId);\n options.onUnsubscribe?.(windowId);\n return;\n }\n\n case 'config.registerSchema': {\n const m = message as ConfigRegisterSchemaMessage;\n // Delegate to host-supplied validator if present; otherwise use\n // the built-in Core Subset hand-coded validator (D12).\n const validation: ConfigSchemaValidation = options.registerSchema\n ? options.registerSchema(windowId, m.schema, m.version)\n : validateCoreSubset(m.schema);\n\n const result: ConfigRegisterSchemaResultMessage = validation.ok\n ? { type: 'config.registerSchema.result', id: m.id, ok: true }\n : {\n type: 'config.registerSchema.result',\n id: m.id,\n ok: false,\n code: validation.code,\n error: validation.error,\n };\n send(result as NappletMessage);\n return;\n }\n\n case 'config.openSettings': {\n const m = message as ConfigOpenSettingsMessage;\n // Silently dropped if openSettings hook not provided (D10).\n options.openSettings?.(windowId, m.section);\n return;\n }\n\n default:\n // Unknown config.* message — silently ignored per NIP-5D.\n return;\n }\n },\n\n onWindowDestroyed(windowId: string): void {\n // A napplet iframe was destroyed — drop any active subscription so we\n // don't retain the stale send callback in the subscribers map.\n subscribers.delete(windowId);\n },\n };\n\n /**\n * Broadcast a new config values snapshot to every subscribed napplet.\n * Each subscriber receives a `config.values` push envelope (no `id` —\n * absence of `id` distinguishes a push from a correlated `config.get`\n * response per the NAP-CONFIG wire spec).\n */\n function publishValues(values: ConfigValues): void {\n const envelope: ConfigValuesMessage = {\n type: 'config.values',\n values,\n };\n for (const send of subscribers.values()) {\n try {\n send(envelope as NappletMessage);\n } catch {\n // Subscriber's send callback threw (e.g., iframe gone without\n // onWindowDestroyed firing yet). Best-effort — drop silently.\n }\n }\n }\n\n return { handler, publishValues };\n}\n","/**\n * resource-service.ts — NAP-RESOURCE reference service (10th NAP domain, v1.7 Phase 40).\n *\n * Shell-side reference implementation for the canonical NAP-RESOURCE wire\n * protocol (`internal-resource.ts` in @kehto/shell/src/types; kehto-internal\n * model per PROJECT.md Decision #31. Kehto keeps legacy single-fetch fields\n * for existing callers and also emits the current NAP-RESOURCE fields.\n * Handles:\n * Inbound: resource.bytes, resource.bytesMany, resource.cancel\n * Outbound: resource.bytes.result, resource.bytes.error,\n * resource.bytesMany.result, resource.bytesMany.error\n *\n * ──────────────────────── SCOPE BOUNDARY (RESOURCE-01) ────────────────────────\n * NAP-RESOURCE is an **authenticated fetch proxy** — read-only, atomic.\n *\n * This service is NOT responsible for:\n * - Streaming / chunked responses (host-app concern)\n * - Response caching / conditional requests (host-app concern)\n * - Upload / POST body construction (NAP-RESOURCE v1.7 is read-only)\n * - Redirect limits, MIME sniffing, SVG rasterization (host-fetch concern)\n * - Private-IP blocking, SSRF mitigation (host-provided-fetch responsibility)\n *\n * These belong to the host-app's `fetch` implementation per D7 and\n * SHELL-RESOURCE-POLICY.md (Phase 40 Plan 40-03). Kehto ships a reference\n * service; production hardening is the host app's concern.\n * ──────────────────────────────────────────────────────────────────────────────\n *\n * Host integration: provide `fetch`, `isOriginGranted`, `getConnectGrants`,\n * and `resolveIdentity`. ALL FOUR are required from day one (H-03 prevention).\n *\n * @example\n * ```ts\n * import { createResourceService } from '@kehto/services';\n *\n * const resourceSvc = createResourceService({\n * fetch: (url, init) => globalThis.fetch(url, init),\n * isOriginGranted: (origin, grants) => grants.includes(origin),\n * getConnectGrants: (dTag, hash) => myOriginGrantStore.getOrigins(dTag, hash),\n * resolveIdentity: (windowId) => sessionRegistry.getEntryByWindowId(windowId) ?? null,\n * });\n * runtime.registerService('resource', resourceSvc);\n * ```\n */\n\nimport type { NappletMessage } from '@napplet/core';\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\n\n/** Resource service version — follows semver. */\nconst RESOURCE_SERVICE_VERSION = '1.0.0';\n\n/**\n * Options for `createResourceService` (options-as-bridge per v1.6 Decision 18).\n *\n * ALL FOUR fields are required. The factory throws at construction if any is\n * missing — H-03 prevention: the grants source (`getConnectGrants`) MUST be\n * wired from day one so there is no window where resource requests bypass the\n * grant check.\n *\n * @see PITFALLS.md:228 (H-03) — grants-source coupling must be present at construction\n */\nexport interface ResourceServiceOptions {\n /**\n * Host-supplied fetch implementation. Receives the URL, a partial init\n * (method, headers, signal), and must return a `Response`-compatible promise.\n *\n * The host's `fetch` is the ONLY place to implement redirect limits, MIME\n * sniffing, SVG rasterization, private-IP / SSRF blocking, etc.\n * This service does NOT filter: it proxies transparently.\n *\n * @param url - The URL from the resource.bytes request\n * @param init - Method, headers (from napplet), and an AbortSignal\n */\n fetch(\n url: string,\n init: { method?: string; headers?: Record<string, string>; signal: AbortSignal }\n ): Promise<Response>;\n\n /**\n * Returns true if `origin` is present in `grants` (the list returned by\n * `getConnectGrants` for the napplet's dTag + aggregateHash).\n *\n * The reference implementation is simply `grants.includes(origin)`. Host apps\n * may provide normalized-origin comparison if needed.\n *\n * @param origin - Parsed origin of the requested URL (scheme + host + port)\n * @param grants - Readonly list from getConnectGrants for this napplet identity\n */\n isOriginGranted(origin: string, grants: readonly string[]): boolean;\n\n /**\n * Returns the list of allowed fetch origins for the given napplet identity.\n * Called on every `resource.bytes` request — must be synchronous and fast.\n *\n * Host-supplied grant source (e.g. a static per-dTag allowlist map, or any\n * other host-controlled policy). Returns an empty array to deny all origins.\n *\n * H-03 prevention: REQUIRED from day one — factory throws on construction\n * if omitted.\n *\n * @param dTag - The napplet's d-tag (from session registry)\n * @param aggregateHash - The napplet's aggregate hash (from session registry)\n */\n getConnectGrants(dTag: string, aggregateHash: string): readonly string[];\n\n /**\n * Resolve a windowId to the napplet's identity (dTag + aggregateHash).\n * Returns null if the window is not in the session registry.\n *\n * Typically wraps `sessionRegistry.getEntryByWindowId(windowId)`.\n *\n * @param windowId - The iframe window identifier\n */\n resolveIdentity(windowId: string): { dTag: string; aggregateHash: string } | null;\n}\n\n/**\n * Type alias for the ServiceHandler returned by `createResourceService`.\n * Exported for host apps that need to type-annotate the handler reference.\n */\nexport type ResourceService = ServiceHandler;\n\n/**\n * Convert an ArrayBuffer to base64 string, safe for both browser and Node.\n * Chunked in 0x8000-byte slices to avoid `String.fromCharCode(...largeArray)`\n * stack overflow on large responses.\n */\nfunction arrayBufferToBase64(buf: ArrayBuffer): string {\n const bytes = new Uint8Array(buf);\n const CHUNK = 0x8000; // 32 KB\n let binary = '';\n for (let i = 0; i < bytes.length; i += CHUNK) {\n binary += String.fromCharCode(...bytes.subarray(i, i + CHUNK));\n }\n return btoa(binary);\n}\n\ninterface ResourceRequestState {\n inFlight: Map<string, { controller: AbortController; windowId: string }>;\n perWindow: Map<string, Set<string>>;\n}\n\ntype LegacyResourceErrorCode = 'denied' | 'invalid-url' | 'canceled' | 'network-error';\n\ntype ResourceErrorCode =\n | 'invalid-request'\n | 'not-found'\n | 'blocked-by-policy'\n | 'timeout'\n | 'too-large'\n | 'unsupported-scheme'\n | 'decode-failed'\n | 'network-error'\n | 'quota-exceeded';\n\ntype ResourceFetchSuccess = {\n ok: true;\n url: string;\n blob: Blob;\n mime: string;\n status: number;\n headers: Record<string, string>;\n bodyBase64: string;\n};\n\ntype ResourceFetchFailure = {\n ok: false;\n url: string;\n error: ResourceErrorCode;\n code: LegacyResourceErrorCode;\n message: string;\n};\n\ntype ResourceFetchItem = ResourceFetchSuccess | ResourceFetchFailure;\n\nfunction assertResourceOptions(options: ResourceServiceOptions): void {\n if (\n typeof options?.fetch !== 'function' ||\n typeof options?.isOriginGranted !== 'function' ||\n typeof options?.getConnectGrants !== 'function' ||\n typeof options?.resolveIdentity !== 'function'\n ) {\n throw new Error(\n '[RESOURCE-01 / H-03] createResourceService requires {fetch, isOriginGranted, getConnectGrants, resolveIdentity} ' +\n '— all four options are required from day one. ' +\n 'The grants source (getConnectGrants) MUST be wired at construction time to prevent unguarded fetch proxying.',\n );\n }\n}\n\nfunction trackRequest(\n state: ResourceRequestState,\n requestId: string,\n windowId: string,\n controller: AbortController,\n): void {\n state.inFlight.set(requestId, { controller, windowId });\n if (!state.perWindow.has(windowId)) {\n state.perWindow.set(windowId, new Set());\n }\n state.perWindow.get(windowId)!.add(requestId);\n}\n\nfunction untrackRequest(state: ResourceRequestState, requestId: string): void {\n const entry = state.inFlight.get(requestId);\n if (entry) {\n state.inFlight.delete(requestId);\n state.perWindow.get(entry.windowId)?.delete(requestId);\n }\n}\n\nfunction sendResourceError(\n send: (m: NappletMessage) => void,\n requestId: string,\n code: LegacyResourceErrorCode,\n message: string,\n error: ResourceErrorCode = toResourceError(code),\n type: 'resource.bytes.error' | 'resource.bytesMany.error' = 'resource.bytes.error',\n): void {\n send({\n type,\n id: requestId,\n requestId,\n error,\n code,\n message,\n } as NappletMessage);\n}\n\nfunction sendBytesManyError(\n send: (m: NappletMessage) => void,\n requestId: string,\n code: LegacyResourceErrorCode,\n message: string,\n error: ResourceErrorCode,\n): void {\n sendResourceError(send, requestId, code, message, error, 'resource.bytesMany.error');\n}\n\nfunction toResourceError(code: LegacyResourceErrorCode): ResourceErrorCode {\n switch (code) {\n case 'denied':\n return 'blocked-by-policy';\n case 'invalid-url':\n return 'invalid-request';\n case 'canceled':\n return 'timeout';\n case 'network-error':\n return 'network-error';\n }\n}\n\nfunction parseResourceUrl(url: string): URL | null {\n try {\n return new URL(url);\n } catch {\n return null;\n }\n}\n\nfunction collectResponseHeaders(response: Response): Record<string, string> {\n const headers: Record<string, string> = {};\n response.headers.forEach((value, key) => {\n headers[key] = value;\n });\n return headers;\n}\n\nfunction responseMime(response: Response): string {\n return response.headers.get('content-type') || 'application/octet-stream';\n}\n\nfunction responseBlob(buffer: ArrayBuffer, mime: string): Blob {\n return new Blob([buffer], { type: mime });\n}\n\nfunction requestIdFromMessage(message: NappletMessage & { id?: unknown; requestId?: unknown }): string | null {\n if (typeof message.id === 'string' && message.id.length > 0) return message.id;\n if (typeof message.requestId === 'string' && message.requestId.length > 0) return message.requestId;\n return null;\n}\n\nfunction resourceInvalidRequest(url: string, message: string): ResourceFetchFailure {\n return {\n ok: false,\n url,\n error: 'invalid-request',\n code: 'invalid-url',\n message,\n };\n}\n\nasync function fetchResourceItem(\n options: ResourceServiceOptions,\n identity: { dTag: string; aggregateHash: string },\n url: string,\n init: { method?: string; headers?: Readonly<Record<string, string>> } | undefined,\n signal: AbortSignal,\n): Promise<ResourceFetchItem> {\n const parsedUrl = parseResourceUrl(url);\n if (!parsedUrl) return resourceInvalidRequest(url, `invalid URL: ${url}`);\n const origin = parsedUrl.origin;\n const grants = options.getConnectGrants(identity.dTag, identity.aggregateHash);\n if (!options.isOriginGranted(origin, grants)) {\n return {\n ok: false,\n url,\n error: 'blocked-by-policy',\n code: 'denied',\n message: `origin ${origin} not granted`,\n };\n }\n\n try {\n const response = await options.fetch(url, {\n method: init?.method,\n headers: init?.headers ? { ...init.headers } : undefined,\n signal,\n });\n const buffer = await response.arrayBuffer();\n const headers = collectResponseHeaders(response);\n const mime = responseMime(response);\n return {\n ok: true,\n url,\n blob: responseBlob(buffer, mime),\n mime,\n status: response.status,\n headers,\n bodyBase64: arrayBufferToBase64(buffer),\n };\n } catch (err: unknown) {\n const isAbort =\n signal.aborted ||\n (err instanceof Error && (err.name === 'AbortError' || err.name === 'DOMException'));\n return {\n ok: false,\n url,\n error: isAbort ? 'timeout' : 'network-error',\n code: isAbort ? 'canceled' : 'network-error',\n message: err instanceof Error ? err.message : String(err),\n };\n }\n}\n\nasync function handleBytes(\n options: ResourceServiceOptions,\n state: ResourceRequestState,\n windowId: string,\n msg: { requestId: string; url: string; init?: { method?: string; headers?: Readonly<Record<string, string>> } },\n send: (m: NappletMessage) => void,\n): Promise<void> {\n const { requestId, url, init } = msg;\n const identity = options.resolveIdentity(windowId);\n if (!identity) {\n sendResourceError(send, requestId, 'denied', 'napplet identity not resolvable', 'blocked-by-policy');\n return;\n }\n\n const controller = new AbortController();\n trackRequest(state, requestId, windowId, controller);\n\n try {\n const item = await fetchResourceItem(options, identity, url, init, controller.signal);\n if (!item.ok) {\n sendResourceError(send, requestId, item.code, item.message, item.error);\n return;\n }\n send({\n type: 'resource.bytes.result',\n id: requestId,\n requestId,\n blob: item.blob,\n mime: item.mime,\n status: item.status,\n headers: item.headers,\n bodyBase64: item.bodyBase64,\n } as NappletMessage);\n } finally {\n untrackRequest(state, requestId);\n }\n}\n\nasync function handleBytesMany(\n options: ResourceServiceOptions,\n state: ResourceRequestState,\n windowId: string,\n msg: { requestId: string; urls: readonly string[]; init?: { method?: string; headers?: Readonly<Record<string, string>> } },\n send: (m: NappletMessage) => void,\n): Promise<void> {\n const { requestId, urls, init } = msg;\n if (!Array.isArray(urls) || urls.length === 0 || urls.some((url) => typeof url !== 'string')) {\n sendBytesManyError(send, requestId, 'invalid-url', 'resource.bytesMany requires a non-empty urls array', 'invalid-request');\n return;\n }\n\n const identity = options.resolveIdentity(windowId);\n if (!identity) {\n sendBytesManyError(send, requestId, 'denied', 'napplet identity not resolvable', 'blocked-by-policy');\n return;\n }\n\n const controller = new AbortController();\n trackRequest(state, requestId, windowId, controller);\n try {\n const items: Array<Record<string, unknown>> = [];\n for (const url of urls) {\n const item = await fetchResourceItem(options, identity, url, init, controller.signal);\n if (item.ok) {\n items.push({\n url: item.url,\n ok: true,\n blob: item.blob,\n mime: item.mime,\n });\n } else {\n items.push({\n url: item.url,\n ok: false,\n error: item.error,\n code: item.code,\n message: item.message,\n });\n }\n }\n send({\n type: 'resource.bytesMany.result',\n id: requestId,\n requestId,\n items,\n } as NappletMessage);\n } finally {\n untrackRequest(state, requestId);\n }\n}\n\nfunction handleCancel(state: ResourceRequestState, requestId: string): void {\n const entry = state.inFlight.get(requestId);\n if (entry) {\n entry.controller.abort();\n }\n}\n\nfunction destroyWindowRequests(state: ResourceRequestState, windowId: string): void {\n const requestIds = state.perWindow.get(windowId);\n if (!requestIds) return;\n for (const requestId of requestIds) {\n const entry = state.inFlight.get(requestId);\n if (entry) {\n entry.controller.abort();\n state.inFlight.delete(requestId);\n }\n }\n state.perWindow.delete(windowId);\n}\n\n/**\n * Create a NAP-RESOURCE reference service.\n *\n * Implements the NAP-RESOURCE request/response protocol: `resource.bytes`,\n * `resource.bytesMany`, `resource.cancel`, and their result/error envelopes.\n *\n * On-construction guard (H-03 prevention): all four options are validated at\n * factory call time. If any is missing, the factory throws immediately with a\n * message containing `[RESOURCE-01 / H-03]` so misconfigured shell apps fail\n * loudly at startup rather than silently at first dispatch.\n *\n * Returns a `ServiceHandler` (no `publishValues`-style surface — resource has\n * no shell-initiated push beyond the response/error path).\n *\n * @param options - REQUIRED: fetch, isOriginGranted, getConnectGrants, resolveIdentity\n * @returns ServiceHandler to register via `runtime.registerService('resource', handler)`\n *\n * @example\n * ```ts\n * import { createResourceService } from '@kehto/services';\n *\n * const svc = createResourceService({\n * fetch: (url, init) => globalThis.fetch(url, init),\n * isOriginGranted: (origin, grants) => grants.includes(origin),\n * getConnectGrants: (dTag, hash) => myOriginGrantStore.getOrigins(dTag, hash),\n * resolveIdentity: (windowId) => sessionRegistry.getEntryByWindowId(windowId) ?? null,\n * });\n * runtime.registerService('resource', svc);\n * ```\n */\nexport function createResourceService(options: ResourceServiceOptions): ResourceService {\n assertResourceOptions(options);\n const state: ResourceRequestState = {\n inFlight: new Map<string, { controller: AbortController; windowId: string }>(),\n perWindow: new Map<string, Set<string>>(),\n };\n\n const descriptor: ServiceDescriptor = {\n name: 'resource',\n version: RESOURCE_SERVICE_VERSION,\n description:\n 'NAP-RESOURCE reference service — shell-proxied authenticated fetch (RESOURCE-01..06)',\n };\n\n const handler: ServiceHandler = {\n descriptor,\n\n handleMessage(\n windowId: string,\n message: NappletMessage,\n send: (msg: NappletMessage) => void,\n ): void {\n switch (message.type) {\n case 'resource.bytes': {\n const m = message as NappletMessage & {\n id?: string;\n requestId?: string;\n url: string;\n init?: { method?: string; headers?: Readonly<Record<string, string>> };\n };\n const requestId = requestIdFromMessage(m);\n if (!requestId || typeof m.url !== 'string') return;\n handleBytes(options, state, windowId, { requestId, url: m.url, init: m.init }, send).catch(() => { /* errors surface via send() */ });\n return;\n }\n\n case 'resource.bytesMany': {\n const m = message as NappletMessage & {\n id?: string;\n requestId?: string;\n urls?: readonly string[];\n init?: { method?: string; headers?: Readonly<Record<string, string>> };\n };\n const requestId = requestIdFromMessage(m);\n if (!requestId) return;\n handleBytesMany(options, state, windowId, { requestId, urls: m.urls ?? [], init: m.init }, send).catch(() => { /* errors surface via send() */ });\n return;\n }\n\n case 'resource.cancel': {\n const m = message as NappletMessage & { id?: string; requestId?: string };\n const requestId = requestIdFromMessage(m);\n if (requestId) handleCancel(state, requestId);\n return;\n }\n\n default:\n // Unknown resource.* message — silently ignored per NIP-5D.\n return;\n }\n },\n\n onWindowDestroyed(windowId: string): void {\n destroyWindowRequests(state, windowId);\n },\n };\n\n return handler;\n}\n","/**\n * outbox-service.ts — NAP-OUTBOX (outbox-aware relay routing) reference service.\n *\n * Shell-side handler for the NAP-OUTBOX wire protocol. It is a pure envelope\n * router: it validates `outbox.*` envelopes, delegates the actual relay\n * discovery / routing / dedup / publish-fanout work to an injected\n * {@link OutboxRouter}, and posts the correlated result / lifecycle messages\n * (echoing the request `id` or `subId`) back to the napplet.\n *\n * The router is injected (options-as-bridge) so this service has no Nostr\n * dependency and is fully unit-testable. A concrete relay-pool-backed router\n * ships alongside as {@link createRelayPoolOutboxRouter}.\n *\n * ──────────────────────────── Responsibilities ────────────────────────────\n * Inbound: outbox.query, outbox.subscribe, outbox.close, outbox.publish,\n * outbox.resolveRelays\n * Outbound: outbox.query.result, outbox.event, outbox.eose, outbox.closed,\n * outbox.publish.result, outbox.resolveRelays.result\n *\n * The shell owns relay discovery, routing, fallback, deduplication, signature\n * validation, signing, and publish fanout policy — all of which live behind\n * the {@link OutboxRouter}. This service only marshals the wire protocol.\n *\n * @example\n * ```ts\n * import { createOutboxService, createRelayPoolOutboxRouter } from '@kehto/services';\n *\n * const router = createRelayPoolOutboxRouter({ relayPool, loadRelayLists, fallbackRelays });\n * runtime.registerService('outbox', createOutboxService({ router }));\n * ```\n *\n * @packageDocumentation\n */\n\nimport type { EventTemplate, NappletMessage, NostrEvent, NostrFilter } from '@napplet/core';\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\n\n/** Outbox service version — follows semver. */\nconst OUTBOX_SERVICE_VERSION = '1.0.0';\n\n/**\n * Relay-selection strategy:\n * - `outbox` — query/publish via author write relays (the outbox model)\n * - `inbox` — query/publish via recipient read relays (the inbox model)\n * - `auto` — let the shell choose per its policy and relay intelligence\n */\nexport type OutboxStrategy = 'outbox' | 'inbox' | 'auto';\n\n/** Options for a one-shot outbox query. */\nexport interface OutboxQueryOptions {\n /** Explicit author hints (augment/override authors derived from filters). */\n authors?: string[];\n /** Relay hints; treated as a hint subject to shell validation, not a bypass. */\n relays?: string[];\n /** Relay-selection strategy. */\n strategy?: OutboxStrategy;\n /** Maximum events to collect. */\n limit?: number;\n /** Wall-clock budget for the query, in milliseconds. */\n timeoutMs?: number;\n}\n\n/** Options for a live outbox subscription. */\nexport interface OutboxSubscribeOptions extends OutboxQueryOptions {\n /** Keep the subscription open for real-time events after EOSE. */\n live?: boolean;\n}\n\n/** Options for an outbox publish. */\nexport interface OutboxPublishOptions {\n /** Relay hints; treated as a hint subject to shell validation. */\n relays?: string[];\n /** Recipient authors whose inbox relays should be included for directed events. */\n targetAuthors?: string[];\n /** Relay-selection strategy. */\n strategy?: OutboxStrategy;\n}\n\n/** A read/write target for relay-plan resolution. */\nexport interface OutboxTarget {\n /** Authors to resolve relays for. */\n authors?: string[];\n /** Single pubkey to resolve relays for. */\n pubkey?: string;\n /** Whether the plan is for reading (their write relays) or writing (their read relays). */\n direction?: 'read' | 'write';\n /** Relay-selection strategy. */\n strategy?: OutboxStrategy;\n}\n\n/** The relay plan the shell would use for a target. */\nexport interface OutboxRelayPlan {\n /** Resolved relay URLs. */\n relays: string[];\n /** Where the plan came from. */\n source: 'nip65' | 'cache' | 'policy' | 'fallback';\n /** Authors for which no relay list could be resolved. */\n missingAuthors?: string[];\n}\n\n/** Outcome of an outbox query, as returned by the {@link OutboxRouter}. */\nexport interface OutboxResult {\n /** Deduplicated, signature-validated events. */\n events: NostrEvent[];\n /** Map of event id -> relay URLs where the shell observed the event. */\n relays: Record<string, string[]>;\n /** True when some relay lists or connections failed and results are partial. */\n incomplete?: boolean;\n /** Error reason when the query could not complete. */\n error?: string;\n}\n\n/** Outcome of an outbox publish, as returned by the {@link OutboxRouter}. */\nexport interface OutboxPublishResult {\n /** Whether the publish succeeded on at least the required relays. */\n ok: boolean;\n /** The signed event returned by the shell. */\n event?: NostrEvent;\n /** The published event id. */\n eventId?: string;\n /** Map of relay URL -> per-relay publish success. */\n relays?: Record<string, boolean>;\n /** Error reason when the publish failed. */\n error?: string;\n}\n\n/** Sink an {@link OutboxRouter} streams subscription lifecycle through. */\nexport interface OutboxSubscriptionSink {\n /** Deliver a matching event; `relay` is the relay it was observed on, when known. */\n event(event: NostrEvent, relay?: string): void;\n /** Signal end-of-stored-events. */\n eose(): void;\n /** Signal that the subscription was closed upstream; `reason` is optional. */\n closed(reason?: string): void;\n}\n\n/** Handle to a router-owned subscription. */\nexport interface OutboxRouterSubscription {\n /** Stop the subscription and release its relay connections. */\n close(): void;\n}\n\n/**\n * Abstract outbox router. Implementors own relay discovery (NIP-65 / NIP-66),\n * routing, fallback, deduplication, signature validation, signing, and publish\n * fanout. The service translates wire envelopes into these calls and back.\n */\nexport interface OutboxRouter {\n /** Resolve relays, query them, dedup by id, validate signatures, collect events. */\n query(filters: NostrFilter[], options?: OutboxQueryOptions): Promise<OutboxResult>;\n /** Open a live outbox-aware subscription, streaming through `sink`. */\n subscribe(\n filters: NostrFilter[],\n options: OutboxSubscribeOptions | undefined,\n sink: OutboxSubscriptionSink,\n ): OutboxRouterSubscription;\n /** Sign `template` and fan it out to the relevant write/inbox relays. */\n publish(template: EventTemplate, options?: OutboxPublishOptions): Promise<OutboxPublishResult>;\n /** Return the relay plan the shell would use for a read/write target. */\n resolveRelays(target: OutboxTarget): Promise<OutboxRelayPlan>;\n}\n\n/** Options for {@link createOutboxService}. */\nexport interface OutboxServiceOptions {\n /** The outbox router the shell uses to reach relays. Required. */\n router: OutboxRouter;\n}\n\ntype Send = (msg: NappletMessage) => void;\n\nconst OUTBOX_DESCRIPTOR: ServiceDescriptor = {\n name: 'outbox',\n version: OUTBOX_SERVICE_VERSION,\n description: 'NAP-OUTBOX outbox-aware relay routing — query/subscribe/publish/resolveRelays',\n};\n\n/**\n * Normalize a wire `filters` field (a single NIP-01 filter or an array) into a\n * filter array. Returns `null` when the input is missing or has no usable\n * filter objects.\n */\nfunction normalizeFilters(raw: unknown): NostrFilter[] | null {\n if (Array.isArray(raw)) {\n const filters = raw.filter((f): f is NostrFilter => typeof f === 'object' && f !== null);\n return filters.length > 0 ? filters : null;\n }\n if (typeof raw === 'object' && raw !== null) return [raw as NostrFilter];\n return null;\n}\n\n/**\n * Create the NAP-OUTBOX service handler.\n *\n * @param options - Must provide an {@link OutboxRouter}.\n * @returns A `ServiceHandler` ready for `runtime.registerService('outbox', handler)`.\n * @throws If `options.router` is missing.\n */\nexport function createOutboxService(options: OutboxServiceOptions): ServiceHandler {\n if (!options || typeof options.router !== 'object' || options.router === null) {\n throw new Error('createOutboxService: options.router is required');\n }\n const { router } = options;\n\n // Active subscriptions keyed by `windowId:subId` for lifecycle management.\n const subscriptions = new Map<string, OutboxRouterSubscription>();\n\n function handleQuery(msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { id?: string; filters?: unknown; options?: OutboxQueryOptions };\n const id = m.id ?? '';\n const filters = normalizeFilters(m.filters);\n if (!filters) {\n send({ type: 'outbox.query.result', id, events: [], relays: {}, error: 'invalid filter' } as NappletMessage);\n return;\n }\n void router\n .query(filters, m.options)\n .then((result) =>\n send({\n type: 'outbox.query.result',\n id,\n events: result.events,\n relays: result.relays,\n ...(result.incomplete === undefined ? {} : { incomplete: result.incomplete }),\n ...(result.error === undefined ? {} : { error: result.error }),\n } as NappletMessage),\n )\n .catch((err) =>\n send({ type: 'outbox.query.result', id, events: [], relays: {}, error: toErrorMessage(err) } as NappletMessage),\n );\n }\n\n function handleSubscribe(windowId: string, msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { subId?: string; filters?: unknown; options?: OutboxSubscribeOptions };\n const subId = m.subId;\n if (typeof subId !== 'string' || subId.length === 0) return;\n const subKey = `${windowId}:${subId}`;\n\n // Replace any existing subscription for this key.\n subscriptions.get(subKey)?.close();\n subscriptions.delete(subKey);\n\n const filters = normalizeFilters(m.filters);\n if (!filters) {\n send({ type: 'outbox.closed', subId, reason: 'invalid filter' } as NappletMessage);\n return;\n }\n\n const sink: OutboxSubscriptionSink = {\n event: (event, relay) =>\n send({ type: 'outbox.event', subId, event, ...(relay === undefined ? {} : { relay }) } as NappletMessage),\n eose: () => send({ type: 'outbox.eose', subId } as NappletMessage),\n closed: (reason) => {\n subscriptions.delete(subKey);\n send({ type: 'outbox.closed', subId, ...(reason === undefined ? {} : { reason }) } as NappletMessage);\n },\n };\n\n subscriptions.set(subKey, router.subscribe(filters, m.options, sink));\n }\n\n function handleClose(windowId: string, msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { subId?: string };\n const subId = m.subId;\n if (typeof subId !== 'string') return;\n const subKey = `${windowId}:${subId}`;\n subscriptions.get(subKey)?.close();\n subscriptions.delete(subKey);\n send({ type: 'outbox.closed', subId } as NappletMessage);\n }\n\n function handlePublish(msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { id?: string; event?: EventTemplate; options?: OutboxPublishOptions };\n const id = m.id ?? '';\n if (!m.event || typeof m.event !== 'object') {\n send({ type: 'outbox.publish.result', id, ok: false, error: 'invalid filter' } as NappletMessage);\n return;\n }\n void router\n .publish(m.event, m.options)\n .then((result) =>\n send({\n type: 'outbox.publish.result',\n id,\n ok: result.ok,\n ...(result.event === undefined ? {} : { event: result.event }),\n ...(result.eventId === undefined ? {} : { eventId: result.eventId }),\n ...(result.relays === undefined ? {} : { relays: result.relays }),\n ...(result.error === undefined ? {} : { error: result.error }),\n } as NappletMessage),\n )\n .catch((err) =>\n send({ type: 'outbox.publish.result', id, ok: false, error: toErrorMessage(err) } as NappletMessage),\n );\n }\n\n function handleResolveRelays(msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { id?: string; target?: OutboxTarget };\n const id = m.id ?? '';\n if (!m.target || typeof m.target !== 'object') {\n send({ type: 'outbox.resolveRelays.result', id, error: 'invalid filter' } as NappletMessage);\n return;\n }\n void router\n .resolveRelays(m.target)\n .then((plan) => send({ type: 'outbox.resolveRelays.result', id, plan } as NappletMessage))\n .catch((err) =>\n send({ type: 'outbox.resolveRelays.result', id, error: toErrorMessage(err) } as NappletMessage),\n );\n }\n\n return {\n descriptor: OUTBOX_DESCRIPTOR,\n handleMessage(windowId: string, message: NappletMessage, send: Send): void {\n switch (message.type) {\n case 'outbox.query':\n handleQuery(message, send);\n return;\n case 'outbox.subscribe':\n handleSubscribe(windowId, message, send);\n return;\n case 'outbox.close':\n handleClose(windowId, message, send);\n return;\n case 'outbox.publish':\n handlePublish(message, send);\n return;\n case 'outbox.resolveRelays':\n handleResolveRelays(message, send);\n return;\n default:\n // Unknown outbox.* action — silently ignored (forward-compatible).\n return;\n }\n },\n onWindowDestroyed(windowId: string): void {\n const prefix = `${windowId}:`;\n for (const [key, sub] of subscriptions) {\n if (key.startsWith(prefix)) {\n sub.close();\n subscriptions.delete(key);\n }\n }\n },\n };\n}\n\nfunction toErrorMessage(err: unknown): string {\n if (err instanceof Error) return err.message;\n if (typeof err === 'string') return err;\n return 'outbox request failed';\n}\n","/**\n * relay-pool-outbox-router.ts — concrete {@link OutboxRouter} backed by a relay pool.\n *\n * Implements the outbox-model routing that NAP-OUTBOX centralizes so napplets\n * don't each reinvent it: derive authors, resolve their NIP-65 relays, fan a\n * per-relay subscription out across the plan, deduplicate by event id (while\n * recording every relay an event was observed on), validate signatures, and —\n * for publish — sign the template and fan it out to the relevant write/inbox\n * relays.\n *\n * NIP-65 relay-list *fetching* is the host's concern (it may come from a\n * kind-10002 cache, a NIP-66 indexer via `@kehto/nip/66`, or a live query), so\n * it is injected via {@link RelayPoolOutboxRouterOptions.loadRelayLists}. The\n * relay pool, signer, and signature verifier are injected too — keeping this\n * router browser-agnostic and unit-testable with mocks.\n *\n * Relay-selection model (per the outbox model):\n * - reading an author's events → their **write** relays (where they publish)\n * - writing to reach an author → their **read** relays (their inbox)\n *\n * `strategy` overrides the direction default: `outbox` forces write relays,\n * `inbox` forces read relays, `auto` (default) follows the read/write direction.\n *\n * @example\n * ```ts\n * import { createOutboxService, createRelayPoolOutboxRouter } from '@kehto/services';\n *\n * const router = createRelayPoolOutboxRouter({\n * relayPool: myOutboxPool,\n * loadRelayLists: (pubkeys) => relayListCache.getMany(pubkeys),\n * fallbackRelays: ['wss://relay.damus.io', 'wss://nos.lol'],\n * signEvent: (tmpl) => signer.signEvent(tmpl),\n * verifyEvent: (ev) => verifyEvent(ev),\n * });\n * runtime.registerService('outbox', createOutboxService({ router }));\n * ```\n *\n * @packageDocumentation\n */\n\nimport type { EventTemplate, NostrEvent, NostrFilter } from '@napplet/core';\nimport type {\n OutboxRouter,\n OutboxResult,\n OutboxPublishResult,\n OutboxRelayPlan,\n OutboxQueryOptions,\n OutboxSubscribeOptions,\n OutboxPublishOptions,\n OutboxStrategy,\n OutboxTarget,\n OutboxSubscriptionSink,\n OutboxRouterSubscription,\n} from './outbox-service.js';\n\n// Timer globals available in all JS runtimes.\ndeclare function setTimeout(callback: () => void, ms: number): unknown;\ndeclare function clearTimeout(id: unknown): void;\n\n/** Default per-query wall-clock budget when `options.timeoutMs` is unset. */\nconst DEFAULT_QUERY_TIMEOUT_MS = 4000;\n\n/** A NIP-65 relay list for a single pubkey. */\nexport interface RelayListEntry {\n /** Relays the author reads from (their inbox). */\n read: string[];\n /** Relays the author writes to (where their events land). */\n write: string[];\n}\n\n/**\n * Relay pool contract the router drives. Implementors adapt their pool library\n * (nostr-tools SimplePool, applesauce-relay, etc.). Unlike the lower-level\n * relay NAP pool, both methods take an explicit relay-URL set so the router\n * controls outbox routing and can attribute events to the relay they arrived on.\n */\nexport interface OutboxRelayPool {\n /**\n * Subscribe to `filters` on exactly `relayUrls`. The callback receives each\n * matching event or the literal `'EOSE'` once stored events are exhausted.\n * Returns a handle to cancel the subscription.\n */\n subscribe(\n filters: NostrFilter[],\n relayUrls: string[],\n callback: (item: NostrEvent | 'EOSE') => void,\n ): { unsubscribe(): void };\n /**\n * Publish `event` to `relayUrls`. May return a per-relay success map; a\n * `void`/missing return is treated as optimistic success on every target.\n */\n publish(\n event: NostrEvent,\n relayUrls: string[],\n ): Promise<Record<string, boolean>> | Record<string, boolean> | void;\n /** Whether the relay pool is connected and able to handle requests. */\n isAvailable(): boolean;\n}\n\n/** Options for {@link createRelayPoolOutboxRouter}. */\nexport interface RelayPoolOutboxRouterOptions {\n /** Relay pool the router subscribes/publishes through. Required. */\n relayPool: OutboxRelayPool;\n /**\n * Resolve NIP-65 relay lists for a set of pubkeys. Pubkeys with no known\n * list are simply omitted from the returned map (they become `missingAuthors`).\n */\n loadRelayLists(pubkeys: string[]): Promise<Map<string, RelayListEntry>> | Map<string, RelayListEntry>;\n /** Relays to fall back to when NIP-65 data is absent, stale, or empty. Required. */\n fallbackRelays: string[];\n /**\n * Sign a template before publish (shell-mediated; napplets never sign). When\n * omitted, `publish` resolves with `{ ok: false, error: 'publish denied' }`.\n */\n signEvent?(template: EventTemplate): Promise<NostrEvent>;\n /**\n * Validate an event signature before delivering it to a napplet. May be sync\n * or async. Defaults to accepting every event (host pools often pre-verify).\n */\n verifyEvent?(event: NostrEvent): Promise<boolean> | boolean;\n /**\n * Gate relay URLs (e.g. block private-network hosts). Defaults to allowing\n * only `ws://` / `wss://` URLs — `options.relays` hints pass through this too.\n */\n isRelayAllowed?(url: string): boolean;\n /** Default query timeout when `options.timeoutMs` is unset. Default 4000ms. */\n defaultTimeoutMs?: number;\n}\n\n/** Resolved router dependencies threaded into the module-level helpers. */\ninterface RouterCtx {\n relayPool: OutboxRelayPool;\n loadRelayLists: RelayPoolOutboxRouterOptions['loadRelayLists'];\n fallbackRelays: string[];\n signEvent?: RelayPoolOutboxRouterOptions['signEvent'];\n isRelayAllowed: (url: string) => boolean;\n defaultTimeoutMs: number;\n verify(event: NostrEvent): Promise<boolean>;\n}\n\n/** Default relay gate: only ws(s):// URLs are permitted. */\nfunction defaultRelayAllowed(url: string): boolean {\n return typeof url === 'string' && (url.startsWith('wss://') || url.startsWith('ws://'));\n}\n\n/** Collect a deduplicated author set from filters + explicit option hints. */\nfunction deriveAuthors(filters: NostrFilter[], optionAuthors?: string[]): string[] {\n const authors = new Set<string>();\n for (const filter of filters) {\n for (const author of filter.authors ?? []) authors.add(author);\n }\n for (const author of optionAuthors ?? []) authors.add(author);\n return [...authors];\n}\n\n/**\n * Whether a resolved plan should use authors' write relays (true) or read\n * relays (false), given the read/write direction and an explicit strategy.\n */\nfunction wantsWriteRelays(direction: 'read' | 'write', strategy: OutboxStrategy): boolean {\n if (strategy === 'outbox') return true;\n if (strategy === 'inbox') return false;\n return direction === 'read'; // auto: reading → author write relays\n}\n\n/** Apply the relay gate to a candidate set, deduplicating. */\nfunction allowed(ctx: RouterCtx, urls: Iterable<string>): string[] {\n const out = new Set<string>();\n for (const url of urls) {\n if (ctx.isRelayAllowed(url)) out.add(url);\n }\n return [...out];\n}\n\n/**\n * Resolve the relay plan for a set of pubkeys. Returns the allowed relay set,\n * its provenance, and any pubkeys whose relay list was unavailable.\n */\nasync function resolvePlan(\n ctx: RouterCtx,\n pubkeys: string[],\n direction: 'read' | 'write',\n strategy: OutboxStrategy,\n relayHints?: string[],\n): Promise<OutboxRelayPlan> {\n const useWrite = wantsWriteRelays(direction, strategy);\n const collected = new Set<string>();\n const missingAuthors: string[] = [];\n let sawNip65 = false;\n\n if (pubkeys.length > 0) {\n const lists = await ctx.loadRelayLists(pubkeys);\n for (const pubkey of pubkeys) {\n const entry = lists.get(pubkey);\n const relays = entry ? (useWrite ? entry.write : entry.read) : undefined;\n if (relays && relays.length > 0) {\n sawNip65 = true;\n for (const url of relays) collected.add(url);\n } else {\n missingAuthors.push(pubkey);\n }\n }\n }\n\n // Relay hints from the napplet augment the plan, subject to the relay gate.\n for (const url of relayHints ?? []) collected.add(url);\n\n let relays = allowed(ctx, collected);\n let source: OutboxRelayPlan['source'];\n if (relays.length === 0) {\n relays = allowed(ctx, ctx.fallbackRelays);\n source = 'fallback';\n } else {\n // nip65 if any author list contributed; otherwise only hints did (policy).\n source = sawNip65 ? 'nip65' : 'policy';\n }\n\n const plan: OutboxRelayPlan = { relays, source };\n if (missingAuthors.length > 0) plan.missingAuthors = missingAuthors;\n return plan;\n}\n\n/** Mutable accumulator for a one-shot fan-out collection. */\ninterface Collector {\n seen: Map<string, NostrEvent>;\n relayMap: Map<string, Set<string>>;\n verifications: Promise<void>[];\n}\n\n/** Record that `id` was observed on `relayUrl`. */\nfunction recordRelay(collector: Collector, id: string, relayUrl: string): void {\n let set = collector.relayMap.get(id);\n if (!set) { set = new Set<string>(); collector.relayMap.set(id, set); }\n set.add(relayUrl);\n}\n\n/** Verify a freshly-seen event and admit it (or drop its sightings) once settled. */\nfunction admitEvent(ctx: RouterCtx, collector: Collector, event: NostrEvent): void {\n if (collector.seen.has(event.id)) return;\n collector.verifications.push(\n ctx.verify(event).then((ok) => {\n if (ok && !collector.seen.has(event.id)) collector.seen.set(event.id, event);\n else if (!ok) collector.relayMap.delete(event.id);\n }),\n );\n}\n\n/** Build the final query outcome from a settled collector. */\nfunction buildCollectResult(\n collector: Collector,\n timedOut: boolean,\n): { events: NostrEvent[]; relayMap: Record<string, string[]>; incomplete: boolean } {\n const events = [...collector.seen.values()];\n const relayObj: Record<string, string[]> = {};\n for (const event of events) relayObj[event.id] = [...(collector.relayMap.get(event.id) ?? [])];\n return { events, relayMap: relayObj, incomplete: timedOut };\n}\n\n/**\n * Fan a one-shot query out across `relayUrls` (one subscription per relay so\n * events can be attributed to their source relay), dedup by id, validate\n * signatures, and finalize on all-EOSE or timeout.\n */\nfunction collectFromRelays(\n ctx: RouterCtx,\n filters: NostrFilter[],\n relayUrls: string[],\n timeoutMs: number,\n): Promise<{ events: NostrEvent[]; relayMap: Record<string, string[]>; incomplete: boolean }> {\n return new Promise((resolve) => {\n const collector: Collector = { seen: new Map(), relayMap: new Map(), verifications: [] };\n const handles: { unsubscribe(): void }[] = [];\n let eoseCount = 0;\n let finished = false;\n let timedOut = false;\n\n function finalize(): void {\n if (finished) return;\n finished = true;\n clearTimeout(timer);\n for (const handle of handles) {\n try { handle.unsubscribe(); } catch { /* best-effort */ }\n }\n void Promise.all(collector.verifications).then(() => resolve(buildCollectResult(collector, timedOut)));\n }\n\n const timer = setTimeout(() => { timedOut = true; finalize(); }, timeoutMs);\n\n for (const relayUrl of relayUrls) {\n handles.push(relayPoolSubscribe(ctx, filters, relayUrl, (item) => {\n if (finished) return;\n if (item === 'EOSE') {\n eoseCount += 1;\n if (eoseCount >= relayUrls.length) finalize();\n return;\n }\n recordRelay(collector, item.id, relayUrl);\n admitEvent(ctx, collector, item);\n }));\n }\n\n if (relayUrls.length === 0) finalize();\n });\n}\n\n/** Thin wrapper so callers read as a single-relay subscribe. */\nfunction relayPoolSubscribe(\n ctx: RouterCtx,\n filters: NostrFilter[],\n relayUrl: string,\n cb: (item: NostrEvent | 'EOSE') => void,\n): { unsubscribe(): void } {\n return ctx.relayPool.subscribe(filters, [relayUrl], cb);\n}\n\nasync function queryImpl(ctx: RouterCtx, filters: NostrFilter[], options?: OutboxQueryOptions): Promise<OutboxResult> {\n if (!ctx.relayPool.isAvailable()) {\n return { events: [], relays: {}, incomplete: true, error: 'relay list unavailable' };\n }\n const strategy = options?.strategy ?? 'auto';\n const authors = deriveAuthors(filters, options?.authors);\n const plan = await resolvePlan(ctx, authors, 'read', strategy, options?.relays);\n if (plan.relays.length === 0) {\n return { events: [], relays: {}, incomplete: true, error: 'relay list unavailable' };\n }\n\n const timeoutMs = options?.timeoutMs ?? ctx.defaultTimeoutMs;\n const collected = await collectFromRelays(ctx, filters, plan.relays, timeoutMs);\n\n const incomplete = collected.incomplete || (plan.missingAuthors?.length ?? 0) > 0;\n let events = collected.events;\n if (options?.limit !== undefined && events.length > options.limit) {\n events = [...events].sort((a, b) => b.created_at - a.created_at).slice(0, options.limit);\n }\n const result: OutboxResult = { events, relays: collected.relayMap };\n if (incomplete) result.incomplete = true;\n return result;\n}\n\n/** Tracks a live/one-shot subscription across its per-relay fan-out. */\ninterface LiveSub {\n handles: { unsubscribe(): void }[];\n seen: Set<string>;\n closed: boolean;\n eoseCount: number;\n relayCount: number;\n eoseSent: boolean;\n}\n\nfunction closeLiveSub(sub: LiveSub): void {\n for (const handle of sub.handles) {\n try { handle.unsubscribe(); } catch { /* best-effort */ }\n }\n sub.handles.length = 0;\n}\n\n/** Wire one relay's subscription into a live subscription's event/eose flow. */\nfunction attachLiveRelay(\n ctx: RouterCtx,\n sub: LiveSub,\n filters: NostrFilter[],\n relayUrl: string,\n live: boolean,\n sink: OutboxSubscriptionSink,\n): void {\n sub.handles.push(relayPoolSubscribe(ctx, filters, relayUrl, (item) => {\n if (sub.closed) return;\n if (item === 'EOSE') {\n sub.eoseCount += 1;\n if (!sub.eoseSent && sub.eoseCount >= sub.relayCount) {\n sub.eoseSent = true;\n sink.eose();\n if (!live) { sub.closed = true; closeLiveSub(sub); sink.closed(); }\n }\n return;\n }\n if (sub.seen.has(item.id)) return;\n void ctx.verify(item).then((ok) => {\n if (!ok || sub.closed || sub.seen.has(item.id)) return;\n sub.seen.add(item.id);\n sink.event(item, relayUrl);\n });\n }));\n}\n\nfunction startSubscription(\n ctx: RouterCtx,\n filters: NostrFilter[],\n options: OutboxSubscribeOptions | undefined,\n sink: OutboxSubscriptionSink,\n): OutboxRouterSubscription {\n const live = options?.live ?? true;\n const strategy = options?.strategy ?? 'auto';\n const authors = deriveAuthors(filters, options?.authors);\n const sub: LiveSub = { handles: [], seen: new Set(), closed: false, eoseCount: 0, relayCount: 0, eoseSent: false };\n\n void resolvePlan(ctx, authors, 'read', strategy, options?.relays)\n .then((plan) => {\n if (sub.closed) return;\n if (plan.relays.length === 0) { sink.closed('relay list unavailable'); return; }\n sub.relayCount = plan.relays.length;\n for (const relayUrl of plan.relays) attachLiveRelay(ctx, sub, filters, relayUrl, live, sink);\n })\n .catch((err) => {\n if (!sub.closed) sink.closed(err instanceof Error ? err.message : 'subscribe failed');\n });\n\n return {\n close(): void {\n if (sub.closed) return;\n sub.closed = true;\n closeLiveSub(sub);\n },\n };\n}\n\n/** Resolve the full write/inbox/hint relay set a publish should fan out to. */\nasync function resolvePublishTargets(\n ctx: RouterCtx,\n signed: NostrEvent,\n options?: OutboxPublishOptions,\n): Promise<string[]> {\n const strategy = options?.strategy ?? 'auto';\n const targets = new Set<string>();\n\n // The author's own write relays (outbox model for the user's own event).\n const authorPlan = await resolvePlan(ctx, [signed.pubkey], 'read', strategy === 'inbox' ? 'auto' : 'outbox');\n for (const url of authorPlan.relays) targets.add(url);\n\n // Directed events: include recipients' read relays (their inbox).\n if (options?.targetAuthors && options.targetAuthors.length > 0) {\n const inboxPlan = await resolvePlan(ctx, options.targetAuthors, 'write', 'inbox');\n for (const url of inboxPlan.relays) targets.add(url);\n }\n\n for (const url of options?.relays ?? []) targets.add(url);\n return allowed(ctx, targets);\n}\n\nasync function publishImpl(ctx: RouterCtx, template: EventTemplate, options?: OutboxPublishOptions): Promise<OutboxPublishResult> {\n if (!ctx.signEvent) return { ok: false, error: 'publish denied' };\n if (!ctx.relayPool.isAvailable()) return { ok: false, error: 'relay list unavailable' };\n\n let signed: NostrEvent;\n try {\n signed = await ctx.signEvent(template);\n } catch (err) {\n return { ok: false, error: err instanceof Error ? err.message : 'sign failed' };\n }\n\n const relayUrls = await resolvePublishTargets(ctx, signed, options);\n if (relayUrls.length === 0) return { ok: false, event: signed, eventId: signed.id, error: 'relay list unavailable' };\n\n let relays: Record<string, boolean>;\n try {\n relays = normalizePublishResult(await ctx.relayPool.publish(signed, relayUrls), relayUrls);\n } catch (err) {\n return { ok: false, event: signed, eventId: signed.id, error: err instanceof Error ? err.message : 'publish failed' };\n }\n\n const ok = Object.values(relays).some(Boolean);\n const result: OutboxPublishResult = { ok, event: signed, eventId: signed.id, relays };\n if (!ok) result.error = 'publish denied';\n return result;\n}\n\n/**\n * Create a relay-pool-backed {@link OutboxRouter}.\n *\n * @param options - Relay pool, NIP-65 loader, fallback relays, and optional\n * signer / verifier / relay gate / timeout.\n * @returns An {@link OutboxRouter} for {@link createOutboxService}.\n * @throws If `relayPool`, `loadRelayLists`, or `fallbackRelays` are missing.\n */\nexport function createRelayPoolOutboxRouter(options: RelayPoolOutboxRouterOptions): OutboxRouter {\n if (!options || typeof options.relayPool !== 'object' || options.relayPool === null) {\n throw new Error('createRelayPoolOutboxRouter: options.relayPool is required');\n }\n if (typeof options.loadRelayLists !== 'function') {\n throw new Error('createRelayPoolOutboxRouter: options.loadRelayLists is required');\n }\n if (!Array.isArray(options.fallbackRelays)) {\n throw new Error('createRelayPoolOutboxRouter: options.fallbackRelays is required');\n }\n\n const verifyEvent = options.verifyEvent;\n const ctx: RouterCtx = {\n relayPool: options.relayPool,\n loadRelayLists: options.loadRelayLists,\n fallbackRelays: options.fallbackRelays,\n signEvent: options.signEvent,\n isRelayAllowed: options.isRelayAllowed ?? defaultRelayAllowed,\n defaultTimeoutMs: options.defaultTimeoutMs ?? DEFAULT_QUERY_TIMEOUT_MS,\n async verify(event: NostrEvent): Promise<boolean> {\n if (!verifyEvent) return true;\n try {\n return await verifyEvent(event);\n } catch {\n return false;\n }\n },\n };\n\n return {\n query: (filters, queryOptions) => queryImpl(ctx, filters, queryOptions),\n subscribe: (filters, subscribeOptions, sink) => startSubscription(ctx, filters, subscribeOptions, sink),\n publish: (template, publishOptions) => publishImpl(ctx, template, publishOptions),\n resolveRelays: (target: OutboxTarget) => {\n const pubkeys = target.authors ?? (target.pubkey ? [target.pubkey] : []);\n return resolvePlan(ctx, pubkeys, target.direction ?? 'read', target.strategy ?? 'auto');\n },\n };\n}\n\n/** Normalize a pool publish return into a per-relay success map. */\nfunction normalizePublishResult(\n res: Record<string, boolean> | void,\n relayUrls: string[],\n): Record<string, boolean> {\n const out: Record<string, boolean> = {};\n if (res && typeof res === 'object') {\n for (const url of relayUrls) out[url] = res[url] ?? false;\n } else {\n // void return → optimistic success on every targeted relay.\n for (const url of relayUrls) out[url] = true;\n }\n return out;\n}\n","/**\n * upload-service.ts — NAP-UPLOAD (shell-mediated file/blob upload) reference service.\n *\n * Shell-side handler for the NAP-UPLOAD wire protocol. It is a pure envelope\n * router: it validates `upload.*` envelopes, delegates the actual byte transfer\n * (server selection, rail authorization signing, the HTTP upload) to an injected\n * {@link Uploader}, and posts the correlated result / status messages back to the\n * napplet.\n *\n * The uploader is injected (options-as-bridge) so this service has no transport\n * or Nostr dependency and is fully unit-testable. NAP-UPLOAD is deliberately\n * abstract over the backend — the runtime decides *how* it uploads (NIP-96,\n * Blossom, …). A concrete HTTP-backed uploader ships alongside as\n * {@link createHttpUploader}.\n *\n * ──────────────────────────── Responsibilities ────────────────────────────\n * Inbound: upload.upload, upload.status\n * Outbound: upload.upload.result, upload.status.result, upload.status.changed\n *\n * The service owns the `uploadId` (generated per request, scoped to the\n * requesting napplet), tracks the latest {@link UploadStatus} per upload for\n * `upload.status` queries, and cleans up on window teardown. The shell owns\n * consent, policy, server selection, signing, and the HTTP upload — all behind\n * the {@link Uploader}.\n *\n * @example\n * ```ts\n * import { createUploadService, createHttpUploader } from '@kehto/services';\n *\n * const uploader = createHttpUploader({ rails: { nip96: { servers } }, signEvent });\n * runtime.registerService('upload', createUploadService({ uploader }));\n * ```\n *\n * @packageDocumentation\n */\n\nimport type { NappletMessage } from '@napplet/core';\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\n\n/** Upload service version — follows semver. */\nconst UPLOAD_SERVICE_VERSION = '1.0.0';\n\n/**\n * Storage rail. `nip96` (NIP-96 HTTP file storage) and `blossom` (Blossom blob\n * storage) are the first concrete backends; the open string keeps the API\n * stable as shells add rails (torrents, usenet, …).\n */\nexport type UploadRail = 'nip96' | 'blossom' | (string & {});\n\n/** Lifecycle state of an upload. */\nexport type UploadState = 'pending' | 'uploading' | 'complete' | 'failed' | 'cancelled';\n\n/** Pixel dimensions of an uploaded image/video. */\nexport interface UploadDimensions {\n width: number;\n height: number;\n}\n\n/**\n * A napplet's upload request. `data` crosses the postMessage boundary by\n * structured clone — shells never require base64 encoding.\n */\nexport interface UploadRequest {\n /** Storage rail; omit to let the shell pick a configured default. */\n rail?: UploadRail;\n /** The bytes to upload. */\n data: ArrayBuffer | Blob;\n /** MIME type; inferred from `data` when omitted. */\n mimeType?: string;\n /** Suggested filename. */\n filename?: string;\n /** Alt text / description for the file event. */\n caption?: string;\n /** Request the server not re-encode the file (NIP-96 `no_transform`). */\n noTransform?: boolean;\n /** Rail-specific or shell-specific extra metadata. */\n metadata?: Record<string, unknown>;\n}\n\n/** A single Nostr tag (NIP-94 / imeta entries are arrays of strings). */\nexport type NostrTag = string[];\n\n/** The result of an upload. */\nexport interface UploadResult {\n /** Whether the upload succeeded (or is progressing) vs failed/cancelled. */\n ok: boolean;\n /** Shell-generated id, scoped to the requesting napplet. */\n uploadId: string;\n /** Current lifecycle state. */\n status: UploadState;\n /** The rail the shell used. */\n rail: UploadRail;\n /** Primary download URL. */\n url?: string;\n /** Mirrors / alternative server URLs. */\n fallbackUrls?: string[];\n /** Hash of the stored blob (NIP-94 `x`). */\n sha256?: string;\n /** Hash before server transforms (NIP-94 `ox`). */\n originalSha256?: string;\n /** Size in bytes. */\n size?: number;\n /** Stored MIME type. */\n mimeType?: string;\n /** Image/video dimensions when known. */\n dimensions?: UploadDimensions;\n /** Blurhash placeholder when known. */\n blurhash?: string;\n /** Ready-to-attach NIP-94 / imeta tags. */\n nip94?: NostrTag[];\n /** Error reason when the upload failed or was cancelled. */\n error?: string;\n}\n\n/** A status snapshot for an upload, including progress counters. */\nexport interface UploadStatus extends UploadResult {\n /** Bytes sent so far (while uploading). */\n bytesSent?: number;\n /** Total bytes to send. */\n bytesTotal?: number;\n /** Unix ms timestamp of this status. */\n updatedAt: number;\n}\n\n/**\n * Context handed to an {@link Uploader} for a single upload. Carries the\n * service-owned `uploadId` and a sink for streaming progress / state changes.\n */\nexport interface UploaderContext {\n /** The service-generated upload id (authoritative; scoped to the napplet). */\n uploadId: string;\n /** The napplet window that requested the upload. */\n windowId: string;\n /**\n * Push a status update (progress, or a transition to complete/failed). The\n * service stamps `uploadId` and `updatedAt` before forwarding to the napplet\n * as `upload.status.changed`, and records it as the latest tracked status.\n */\n onStatus(status: UploadStatus): void;\n}\n\n/**\n * Abstract upload backend. Implementors own server selection, rail\n * authorization signing (NIP-98 for NIP-96, kind 24242 for Blossom), the HTTP\n * upload, and integrity-hash reporting. The service translates wire envelopes\n * into these calls and back. A concrete reference implementation ships as\n * {@link createHttpUploader}.\n */\nexport interface Uploader {\n /** Upload `request.data`, streaming progress through `ctx.onStatus`. */\n upload(request: UploadRequest, ctx: UploaderContext): Promise<UploadResult>;\n /** Optional: resolve the latest status for an upload the service is not tracking. */\n status?(uploadId: string): Promise<UploadStatus | undefined>;\n /** Optional: abort an in-flight upload (called on window teardown). */\n cancel?(uploadId: string): void;\n}\n\n/** Options for {@link createUploadService}. */\nexport interface UploadServiceOptions {\n /** The upload backend the shell uses. Required. */\n uploader: Uploader;\n /** Generate an upload id; defaults to `crypto.randomUUID()`. */\n generateId?: () => string;\n /** Current time in unix ms; defaults to `Date.now()`. */\n now?: () => number;\n}\n\ntype Send = (msg: NappletMessage) => void;\n\nconst UPLOAD_DESCRIPTOR: ServiceDescriptor = {\n name: 'upload',\n version: UPLOAD_SERVICE_VERSION,\n description: 'NAP-UPLOAD shell-mediated file/blob upload — upload/status with progress pushes',\n};\n\n/** Per-upload tracking entry, keyed by `windowId:uploadId`. */\ninterface UploadEntry {\n uploadId: string;\n status?: UploadStatus;\n}\n\ndeclare const crypto: { randomUUID(): string };\n\n/**\n * Create the NAP-UPLOAD service handler.\n *\n * @param options - Must provide an {@link Uploader}.\n * @returns A `ServiceHandler` ready for `runtime.registerService('upload', handler)`.\n * @throws If `options.uploader` is missing.\n */\nexport function createUploadService(options: UploadServiceOptions): ServiceHandler {\n if (!options || typeof options.uploader !== 'object' || options.uploader === null) {\n throw new Error('createUploadService: options.uploader is required');\n }\n const { uploader } = options;\n const generateId = options.generateId ?? (() => crypto.randomUUID());\n const now = options.now ?? (() => Date.now());\n\n // Tracked uploads keyed by `windowId:uploadId` for status lookup + cleanup.\n const entries = new Map<string, UploadEntry>();\n\n function handleUpload(windowId: string, msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { id?: string; request?: UploadRequest };\n const id = m.id ?? '';\n const request = m.request;\n if (!request || typeof request !== 'object' || request.data == null) {\n send({ type: 'upload.upload.result', id, error: 'invalid request' } as NappletMessage);\n return;\n }\n\n const uploadId = generateId();\n const key = `${windowId}:${uploadId}`;\n entries.set(key, { uploadId });\n\n const ctx: UploaderContext = {\n uploadId,\n windowId,\n onStatus: (status) => {\n const stamped: UploadStatus = { ...status, uploadId, updatedAt: status.updatedAt || now() };\n const entry = entries.get(key);\n if (entry) entry.status = stamped;\n send({ type: 'upload.status.changed', status: stamped } as NappletMessage);\n },\n };\n\n void uploader\n .upload(request, ctx)\n .then((result) => {\n const stamped: UploadResult = { ...result, uploadId };\n const entry = entries.get(key);\n if (entry) entry.status = { ...stamped, updatedAt: now() };\n send({ type: 'upload.upload.result', id, result: stamped } as NappletMessage);\n })\n .catch((err) => {\n entries.delete(key);\n send({ type: 'upload.upload.result', id, error: toErrorMessage(err) } as NappletMessage);\n });\n }\n\n function handleStatus(windowId: string, msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { id?: string; uploadId?: string };\n const id = m.id ?? '';\n const uploadId = m.uploadId;\n if (typeof uploadId !== 'string' || uploadId.length === 0) {\n send({ type: 'upload.status.result', id, error: 'invalid uploadId' } as NappletMessage);\n return;\n }\n\n const tracked = entries.get(`${windowId}:${uploadId}`)?.status;\n if (tracked) {\n send({ type: 'upload.status.result', id, status: tracked } as NappletMessage);\n return;\n }\n\n if (uploader.status) {\n void uploader\n .status(uploadId)\n .then((status) =>\n send(\n status\n ? ({ type: 'upload.status.result', id, status } as NappletMessage)\n : ({ type: 'upload.status.result', id, error: 'unknown upload' } as NappletMessage),\n ),\n )\n .catch((err) =>\n send({ type: 'upload.status.result', id, error: toErrorMessage(err) } as NappletMessage),\n );\n return;\n }\n\n send({ type: 'upload.status.result', id, error: 'unknown upload' } as NappletMessage);\n }\n\n return {\n descriptor: UPLOAD_DESCRIPTOR,\n handleMessage(windowId: string, message: NappletMessage, send: Send): void {\n switch (message.type) {\n case 'upload.upload':\n handleUpload(windowId, message, send);\n return;\n case 'upload.status':\n handleStatus(windowId, message, send);\n return;\n default:\n // Unknown upload.* action — silently ignored (forward-compatible).\n return;\n }\n },\n onWindowDestroyed(windowId: string): void {\n const prefix = `${windowId}:`;\n for (const [key, entry] of entries) {\n if (key.startsWith(prefix)) {\n uploader.cancel?.(entry.uploadId);\n entries.delete(key);\n }\n }\n },\n };\n}\n\nfunction toErrorMessage(err: unknown): string {\n if (err instanceof Error) return err.message;\n if (typeof err === 'string') return err;\n return 'upload request failed';\n}\n","/**\n * http-uploader.ts — NAP-UPLOAD concrete HTTP-backed {@link Uploader}.\n *\n * The reference upload backend for {@link createUploadService}. Implements two\n * storage rails over HTTP:\n *\n * - **NIP-96** — signs a NIP-98 (kind 27235) HTTP-auth event, POSTs the file as\n * `multipart/form-data`, and maps the returned NIP-94 event tags into an\n * {@link UploadResult}.\n * - **Blossom** — signs a kind 24242 authorization event, PUTs the raw bytes to\n * `<server>/upload`, and maps the returned blob descriptor.\n *\n * Signing (`signEvent`) and transport (`fetch`) are injected so the uploader\n * carries no Nostr or network dependency and is fully unit-testable. The shell\n * holds the signing key and never exposes it to napplets — the uploader only\n * receives a signing callback. Server URLs are shell configuration, not napplet\n * input: a napplet may *hint* a rail, but never a server.\n *\n * The configured server URL is used directly as the upload endpoint (the\n * NIP-96 `api_url` / Blossom base). Hosts that need `.well-known` discovery can\n * resolve it before constructing the uploader.\n *\n * @example\n * ```ts\n * const uploader = createHttpUploader({\n * rails: { nip96: { servers: ['https://nostr.build/api/v2/nip96/upload'] } },\n * signEvent: (tmpl) => signer.signEvent(tmpl),\n * });\n * runtime.registerService('upload', createUploadService({ uploader }));\n * ```\n *\n * @packageDocumentation\n */\n\nimport type { EventTemplate, NostrEvent } from '@napplet/core';\nimport type {\n NostrTag,\n UploadDimensions,\n UploadRail,\n UploadRequest,\n UploadResult,\n Uploader,\n UploaderContext,\n} from './upload-service.js';\n\n/** NIP-98 HTTP-auth event kind. */\nconst KIND_NIP98 = 27235;\n/** Blossom authorization event kind. */\nconst KIND_BLOSSOM_AUTH = 24242;\n/** Blossom auth-event lifetime, in seconds. */\nconst BLOSSOM_AUTH_TTL_S = 3600;\n\n/** Per-rail server configuration. The first server is the primary endpoint. */\nexport interface RailServerConfig {\n /** Ordered server endpoint URLs; index 0 is primary. */\n servers: string[];\n}\n\n/** Storage rails this uploader can serve. */\nexport interface HttpUploaderRails {\n /** NIP-96 HTTP file storage. */\n nip96?: RailServerConfig;\n /** Blossom blob storage. */\n blossom?: RailServerConfig;\n}\n\n/** Signs an event template on the user's behalf (shell holds the key). */\nexport type SignEvent = (template: EventTemplate) => Promise<NostrEvent>;\n\n/** Options for {@link createHttpUploader}. */\nexport interface HttpUploaderOptions {\n /** Configured rails + their servers. */\n rails: HttpUploaderRails;\n /** Rail to use when a request omits one; defaults to the first configured rail. */\n defaultRail?: UploadRail;\n /** Signs NIP-98 / Blossom auth events. Required. */\n signEvent: SignEvent;\n /** Fetch implementation; defaults to the global `fetch`. */\n fetch?: typeof fetch;\n /** Hex SHA-256 of the payload bytes; defaults to Web Crypto. */\n digestSha256?: (bytes: Uint8Array) => Promise<string>;\n /** Unix *seconds* clock for event timestamps; defaults to `Date.now()/1000`. */\n now?: () => number;\n}\n\ndeclare const btoa: (data: string) => string;\ndeclare const crypto: { subtle: { digest(alg: string, data: ArrayBuffer): Promise<ArrayBuffer> } };\n\n/**\n * Create the reference HTTP {@link Uploader} (NIP-96 + Blossom rails).\n *\n * @param options - Rails, server config, and the injected `signEvent`.\n * @returns An {@link Uploader} for `createUploadService({ uploader })`.\n * @throws If `options.signEvent` is missing.\n */\nexport function createHttpUploader(options: HttpUploaderOptions): Uploader {\n if (!options || typeof options.signEvent !== 'function') {\n throw new Error('createHttpUploader: options.signEvent is required');\n }\n const rails = options.rails ?? {};\n const signEvent = options.signEvent;\n const fetchFn = options.fetch ?? fetch;\n const digest = options.digestSha256 ?? defaultDigestSha256;\n const nowS = options.now ?? (() => Math.floor(Date.now() / 1000));\n const defaultRail = options.defaultRail ?? firstConfiguredRail(rails);\n\n async function upload(request: UploadRequest, ctx: UploaderContext): Promise<UploadResult> {\n const rail = request.rail ?? defaultRail;\n const config = rail === 'nip96' ? rails.nip96 : rail === 'blossom' ? rails.blossom : undefined;\n if (rail !== 'nip96' && rail !== 'blossom') {\n return failed(ctx.uploadId, rail ?? 'unknown', 'unsupported rail');\n }\n\n const server = config?.servers?.[0];\n if (!server) {\n return failed(ctx.uploadId, rail, 'no server configured');\n }\n\n const bytes = await toBytes(request.data);\n const sha256 = await digest(bytes);\n\n try {\n return rail === 'nip96'\n ? await uploadNip96({ request, ctx, server, bytes, sha256, signEvent, fetchFn, nowS })\n : await uploadBlossom({ request, ctx, server, bytes, sha256, signEvent, fetchFn, nowS });\n } catch (err) {\n return failed(ctx.uploadId, rail, toErrorMessage(err), sha256);\n }\n }\n\n return { upload };\n}\n\ninterface RailUploadArgs {\n request: UploadRequest;\n ctx: UploaderContext;\n server: string;\n bytes: Uint8Array;\n sha256: string;\n signEvent: SignEvent;\n fetchFn: typeof fetch;\n nowS: () => number;\n}\n\nasync function uploadNip96(args: RailUploadArgs): Promise<UploadResult> {\n const { request, ctx, server, bytes, sha256, signEvent, fetchFn, nowS } = args;\n\n const auth = await signEvent({\n kind: KIND_NIP98,\n created_at: nowS(),\n content: '',\n tags: [\n ['u', server],\n ['method', 'POST'],\n ['payload', sha256],\n ],\n });\n\n const form = new FormData();\n form.append('file', new Blob([bytesToArrayBuffer(bytes)], { type: request.mimeType }), request.filename ?? 'file');\n if (request.caption !== undefined) form.append('caption', request.caption);\n if (request.mimeType !== undefined) form.append('content_type', request.mimeType);\n if (request.noTransform) form.append('no_transform', 'true');\n\n const res = await fetchFn(server, {\n method: 'POST',\n headers: { Authorization: nostrAuthHeader(auth) },\n body: form,\n });\n\n if (!res.ok) {\n return failed(ctx.uploadId, 'nip96', `server rejected (HTTP ${res.status})`, sha256);\n }\n\n const body = (await res.json()) as Nip96Response;\n if (body.status === 'error') {\n return failed(ctx.uploadId, 'nip96', body.message ?? 'upload failed', sha256);\n }\n\n const tags = body.nip94_event?.tags ?? [];\n return fromNip94Tags(ctx.uploadId, 'nip96', tags, bytes.byteLength, sha256);\n}\n\ninterface Nip96Response {\n status?: 'success' | 'error' | string;\n message?: string;\n nip94_event?: { tags?: string[][] };\n}\n\n/** Map NIP-94 / imeta tags into an {@link UploadResult}. */\nfunction fromNip94Tags(\n uploadId: string,\n rail: UploadRail,\n tags: string[][],\n fallbackSize: number,\n fallbackSha: string,\n): UploadResult {\n const get = (name: string): string | undefined => tags.find((t) => t[0] === name)?.[1];\n const url = get('url');\n const result: UploadResult = {\n ok: Boolean(url),\n uploadId,\n status: url ? 'complete' : 'failed',\n rail,\n sha256: get('x') ?? fallbackSha,\n nip94: tags as NostrTag[],\n };\n if (url) result.url = url;\n const ox = get('ox');\n if (ox) result.originalSha256 = ox;\n const size = get('size');\n result.size = size ? Number(size) : fallbackSize;\n const m = get('m');\n if (m) result.mimeType = m;\n const dim = parseDimensions(get('dim'));\n if (dim) result.dimensions = dim;\n const blurhash = get('blurhash');\n if (blurhash) result.blurhash = blurhash;\n if (!url) result.error = 'server returned no url';\n return result;\n}\n\nasync function uploadBlossom(args: RailUploadArgs): Promise<UploadResult> {\n const { request, ctx, server, bytes, sha256, signEvent, fetchFn, nowS } = args;\n\n const auth = await signEvent({\n kind: KIND_BLOSSOM_AUTH,\n created_at: nowS(),\n content: `Upload ${request.filename ?? 'file'}`,\n tags: [\n ['t', 'upload'],\n ['x', sha256],\n ['expiration', String(nowS() + BLOSSOM_AUTH_TTL_S)],\n ],\n });\n\n const endpoint = `${trimTrailingSlash(server)}/upload`;\n const headers: Record<string, string> = { Authorization: nostrAuthHeader(auth) };\n if (request.mimeType) headers['Content-Type'] = request.mimeType;\n\n const res = await fetchFn(endpoint, {\n method: 'PUT',\n headers,\n body: bytesToArrayBuffer(bytes),\n });\n\n if (!res.ok) {\n return failed(ctx.uploadId, 'blossom', `server rejected (HTTP ${res.status})`, sha256);\n }\n\n const blob = (await res.json()) as BlossomDescriptor;\n if (!blob.url) {\n return failed(ctx.uploadId, 'blossom', 'server returned no url', sha256);\n }\n\n const result: UploadResult = {\n ok: true,\n uploadId: ctx.uploadId,\n status: 'complete',\n rail: 'blossom',\n url: blob.url,\n sha256: blob.sha256 ?? sha256,\n size: blob.size ?? bytes.byteLength,\n };\n if (blob.type) result.mimeType = blob.type;\n return result;\n}\n\ninterface BlossomDescriptor {\n url?: string;\n sha256?: string;\n size?: number;\n type?: string;\n}\n\nfunction failed(uploadId: string, rail: UploadRail, error: string, sha256?: string): UploadResult {\n return { ok: false, uploadId, status: 'failed', rail, error, ...(sha256 ? { sha256 } : {}) };\n}\n\nfunction firstConfiguredRail(rails: HttpUploaderRails): UploadRail | undefined {\n if (rails.nip96?.servers?.length) return 'nip96';\n if (rails.blossom?.servers?.length) return 'blossom';\n return undefined;\n}\n\nfunction nostrAuthHeader(event: NostrEvent): string {\n return `Nostr ${base64Utf8(JSON.stringify(event))}`;\n}\n\nfunction base64Utf8(s: string): string {\n // UTF-8 safe base64 (event content/tags may contain non-ASCII).\n return btoa(String.fromCharCode(...new TextEncoder().encode(s)));\n}\n\nfunction parseDimensions(dim: string | undefined): UploadDimensions | undefined {\n if (!dim) return undefined;\n const m = /^(\\d+)x(\\d+)$/.exec(dim);\n if (!m) return undefined;\n return { width: Number(m[1]), height: Number(m[2]) };\n}\n\nasync function toBytes(data: ArrayBuffer | Blob): Promise<Uint8Array> {\n if (data instanceof ArrayBuffer) return new Uint8Array(data);\n return new Uint8Array(await data.arrayBuffer());\n}\n\nfunction bytesToArrayBuffer(bytes: Uint8Array): ArrayBuffer {\n return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer;\n}\n\nfunction trimTrailingSlash(url: string): string {\n return url.endsWith('/') ? url.slice(0, -1) : url;\n}\n\nasync function defaultDigestSha256(bytes: Uint8Array): Promise<string> {\n const buf = await crypto.subtle.digest('SHA-256', bytesToArrayBuffer(bytes));\n return Array.from(new Uint8Array(buf))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n}\n\nfunction toErrorMessage(err: unknown): string {\n if (err instanceof Error) return err.message;\n if (typeof err === 'string') return err;\n return 'upload failed';\n}\n","/**\n * intent-service.ts — NAP-INTENT (archetype intent dispatch) reference service.\n *\n * Shell-side handler for the NAP-INTENT wire protocol. It is a pure envelope\n * router: it validates `intent.*` envelopes, delegates archetype resolution,\n * default handling, window lifecycle, and payload delivery to an injected\n * {@link IntentResolver}, and posts the correlated result / push messages\n * (echoing the request `id`) back to the napplet.\n *\n * The resolver is injected (options-as-bridge) so this service has no shell or\n * window-manager dependency and is fully unit-testable. A concrete\n * catalog-backed resolver ships alongside as {@link createCatalogIntentResolver}.\n *\n * ──────────────────────────── Responsibilities ────────────────────────────\n * Inbound: intent.invoke, intent.available, intent.handlers\n * Outbound: intent.invoke.result, intent.available.result,\n * intent.handlers.result, intent.changed\n *\n * The shell owns archetype→handler resolution, the user's default-handler\n * preference, the \"open with…\" chooser, and window creation/focus — all behind\n * the {@link IntentResolver}. This service only marshals the wire protocol and\n * fans `intent.changed` pushes out to the napplets it has served.\n *\n * @example\n * ```ts\n * import { createIntentService, createCatalogIntentResolver } from '@kehto/services';\n *\n * const resolver = createCatalogIntentResolver({ loadCatalog, windows });\n * runtime.registerService('intent', createIntentService({ resolver }));\n * ```\n *\n * @packageDocumentation\n */\n\nimport type { NappletMessage } from '@napplet/core';\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\nimport type { IntentAvailability, IntentRequest, IntentResult } from './intent-types.js';\n\n/** Intent service version — follows semver. */\nconst INTENT_SERVICE_VERSION = '1.0.0';\n\n/** Context passed to {@link IntentResolver.invoke} for trust/attribution. */\nexport interface IntentResolverContext {\n /** Window id of the napplet that issued the request. */\n windowId: string;\n}\n\n/**\n * Abstract intent resolver. Implementors own the installed-napplet catalog,\n * archetype→handler resolution, the user's default-handler preference, the\n * \"open with…\" chooser, window creation/focus, and payload delivery. The\n * service translates wire envelopes into these calls and back.\n */\nexport interface IntentResolver {\n /**\n * Resolve `request.archetype` to a handler, create or focus its window, and\n * deliver `payload`. Returns once the handler is resolved and the window\n * created; delivery MAY complete asynchronously.\n */\n invoke(request: IntentRequest, context: IntentResolverContext): IntentResult | Promise<IntentResult>;\n /** Report whether the runtime can currently satisfy `archetype`, and how. */\n available(archetype: string): IntentAvailability | Promise<IntentAvailability>;\n /** Report availability for every archetype the runtime can currently satisfy. */\n handlers(): IntentAvailability[] | Promise<IntentAvailability[]>;\n /**\n * Register for availability changes (a napplet installed/removed, or a default\n * handler changed). The service forwards each change to served napplets as an\n * `intent.changed` push. Returns an unsubscribe handle. Resolvers whose\n * catalog never changes at runtime MAY omit this.\n */\n onChanged?(listener: (availability: IntentAvailability) => void): () => void;\n}\n\n/** Options for {@link createIntentService}. */\nexport interface IntentServiceOptions {\n /** The intent resolver the shell uses to route archetypes. Required. */\n resolver: IntentResolver;\n}\n\ntype Send = (msg: NappletMessage) => void;\n\nconst INTENT_DESCRIPTOR: ServiceDescriptor = {\n name: 'intent',\n version: INTENT_SERVICE_VERSION,\n description: 'NAP-INTENT archetype intent dispatch — invoke/available/handlers',\n};\n\nfunction toErrorMessage(err: unknown): string {\n if (err instanceof Error) return err.message;\n if (typeof err === 'string') return err;\n return 'intent request failed';\n}\n\n/**\n * Create the NAP-INTENT service handler.\n *\n * @param options - Must provide an {@link IntentResolver}.\n * @returns A `ServiceHandler` ready for `runtime.registerService('intent', handler)`.\n * @throws If `options.resolver` is missing.\n */\nexport function createIntentService(options: IntentServiceOptions): ServiceHandler {\n if (!options || typeof options.resolver !== 'object' || options.resolver === null) {\n throw new Error('createIntentService: options.resolver is required');\n }\n const { resolver } = options;\n\n // Latest send callback per served window, used to fan `intent.changed`\n // pushes out to every napplet that has interacted with the intent domain.\n const windows = new Map<string, Send>();\n\n // Subscribe once for availability changes and broadcast them as pushes.\n resolver.onChanged?.((availability) => {\n for (const send of windows.values()) {\n send({ type: 'intent.changed', availability } as NappletMessage);\n }\n });\n\n /**\n * Run a resolver call and marshal its outcome onto `resultType`. The resolver\n * method may be sync or async — `settle` normalizes it to a promise and\n * catches a synchronous throw (e.g. a sync resolver that rejects an input)\n * the same way it catches an async rejection.\n */\n function settle<T>(\n call: () => T | Promise<T>,\n send: Send,\n resultType: string,\n id: string,\n onValue: (value: T) => NappletMessage,\n ): void {\n let pending: Promise<T>;\n try {\n pending = Promise.resolve(call());\n } catch (err) {\n send({ type: resultType, id, error: toErrorMessage(err) } as NappletMessage);\n return;\n }\n pending\n .then((value) => send(onValue(value)))\n .catch((err) => send({ type: resultType, id, error: toErrorMessage(err) } as NappletMessage));\n }\n\n function handleInvoke(windowId: string, msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { id?: string; request?: IntentRequest };\n const id = m.id ?? '';\n const request = m.request;\n if (!request || typeof request !== 'object' || typeof request.archetype !== 'string' || request.archetype.length === 0) {\n send({ type: 'intent.invoke.result', id, error: 'invalid request' } as NappletMessage);\n return;\n }\n settle(\n () => resolver.invoke(request, { windowId }),\n send, 'intent.invoke.result', id,\n (result) => ({ type: 'intent.invoke.result', id, result } as NappletMessage),\n );\n }\n\n function handleAvailable(msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { id?: string; archetype?: unknown };\n const id = m.id ?? '';\n if (typeof m.archetype !== 'string' || m.archetype.length === 0) {\n send({ type: 'intent.available.result', id, error: 'invalid archetype' } as NappletMessage);\n return;\n }\n const archetype = m.archetype;\n settle(\n () => resolver.available(archetype),\n send, 'intent.available.result', id,\n (availability) => ({ type: 'intent.available.result', id, availability } as NappletMessage),\n );\n }\n\n function handleHandlers(msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { id?: string };\n const id = m.id ?? '';\n settle(\n () => resolver.handlers(),\n send, 'intent.handlers.result', id,\n (handlers) => ({ type: 'intent.handlers.result', id, handlers } as NappletMessage),\n );\n }\n\n return {\n descriptor: INTENT_DESCRIPTOR,\n handleMessage(windowId: string, message: NappletMessage, send: Send): void {\n windows.set(windowId, send);\n switch (message.type) {\n case 'intent.invoke':\n handleInvoke(windowId, message, send);\n return;\n case 'intent.available':\n handleAvailable(message, send);\n return;\n case 'intent.handlers':\n handleHandlers(message, send);\n return;\n default:\n // Unknown intent.* action — silently ignored (forward-compatible).\n return;\n }\n },\n onWindowDestroyed(windowId: string): void {\n windows.delete(windowId);\n },\n };\n}\n","/**\n * catalog-intent-resolver.ts — NAP-INTENT concrete {@link IntentResolver}.\n *\n * A reference resolver that satisfies {@link IntentResolver} from an\n * installed-napplet catalog plus host-supplied policy: the user's\n * default-handler preference, an optional \"open with…\" chooser, the archetype's\n * recommended default protocol, and a window controller that creates or focuses\n * the handler window and delivers the payload.\n *\n * Resolution policy (mirrors an OS implicit-intent + default-app model):\n * 1. Gather candidates for the archetype from the catalog.\n * 2. Pick the handler — an explicit `handler` dTag, a user choice\n * (`handler: \"choose\"`), the user's default, the sole candidate, or a\n * deterministic first-candidate fallback.\n * 3. Validate the resolved handler supports the requested `action` and\n * `protocol` (or fall back to the archetype's recommended default protocol,\n * then the candidate's first accepted protocol).\n * 4. Open or focus the handler window and return its id.\n *\n * The catalog, defaults, chooser, and window controller are all injected, so\n * this resolver carries no shell, manifest, or DOM dependency and is fully\n * unit-testable.\n *\n * @packageDocumentation\n */\n\nimport type {\n IntentAvailability,\n IntentBehavior,\n IntentCandidate,\n IntentRequest,\n IntentResult,\n} from './intent-types.js';\nimport type { IntentResolver, IntentResolverContext } from './intent-service.js';\n\n/** The actions/protocols a napplet fulfills for a single archetype. */\nexport interface IntentArchetypeSupport {\n /** Verbs this napplet supports for the archetype (e.g. `[\"open\", \"edit\"]`). */\n actions: string[];\n /** NAP-N protocol ids this napplet accepts for the archetype. */\n protocols: string[];\n}\n\n/**\n * One installed napplet's intent surface, derived from its signed NIP-5A\n * manifest. Keyed by archetype slug so a single napplet can fulfill several\n * roles.\n */\nexport interface IntentCatalogEntry {\n /** The napplet's dTag. */\n dTag: string;\n /** Human-readable title from the manifest. */\n title?: string;\n /** Archetype slug → the actions/protocols this napplet fulfills for it. */\n archetypes: Record<string, IntentArchetypeSupport>;\n}\n\n/** Parameters handed to {@link IntentWindowController.open}. */\nexport interface IntentOpenParams {\n /** dTag of the resolved handler napplet. */\n dTag: string;\n /** The archetype being dispatched. */\n archetype: string;\n /** The resolved action (e.g. `\"open\"`). */\n action: string;\n /** The wire format the payload is delivered with, when one was resolved. */\n protocol?: string;\n /** The opaque payload to deliver. */\n payload?: unknown;\n /** Window-behavior hints from the request. */\n behavior?: IntentBehavior;\n /** Window id of the napplet that issued the intent. */\n callerWindowId: string;\n}\n\n/** Creates or focuses the handler window and delivers the intent payload. */\nexport interface IntentWindowController {\n /**\n * Create or focus the handler window and deliver `payload` via `protocol`.\n * Returns the window id the handler was created or focused in.\n */\n open(params: IntentOpenParams): { windowId: string } | Promise<{ windowId: string }>;\n}\n\n/** Options for {@link createCatalogIntentResolver}. */\nexport interface CatalogIntentResolverOptions {\n /** Return the installed-napplet catalog (signed NIP-5A manifests). Required. */\n loadCatalog(): IntentCatalogEntry[] | Promise<IntentCatalogEntry[]>;\n /** Window controller used to create/focus the resolved handler's window. Required. */\n windows: IntentWindowController;\n /**\n * The user's default handler dTag for an archetype, or `undefined` when none\n * is set. Default-handler settings are user state — never set by napplets.\n */\n getDefaultHandler?(archetype: string): string | undefined;\n /**\n * Resolve a `handler: \"choose\"` prompt (or a no-default ambiguity) to a dTag.\n * Returning `undefined` means the user cancelled. When omitted, ambiguous\n * resolution falls back to the first candidate.\n */\n chooseHandler?(\n archetype: string,\n candidates: IntentCandidate[],\n callerWindowId: string,\n ): string | undefined | Promise<string | undefined>;\n /** The archetype's recommended default protocol when the caller omits one. */\n defaultProtocol?(archetype: string): string | undefined;\n}\n\n/**\n * A {@link IntentResolver} backed by a catalog, with a host hook to announce\n * catalog/default changes.\n */\nexport interface CatalogIntentResolver extends IntentResolver {\n /**\n * Announce that the catalog or default handler for `archetype` changed. The\n * resolver recomputes availability and notifies `onChanged` listeners, which\n * the intent service forwards to napplets as `intent.changed` pushes.\n */\n notifyChanged(archetype: string): void;\n}\n\n/** Build the candidate list for an archetype, marking the user's default. */\nfunction candidatesFor(\n catalog: IntentCatalogEntry[],\n archetype: string,\n defaultHandler: string | undefined,\n): IntentCandidate[] {\n const candidates: IntentCandidate[] = [];\n for (const entry of catalog) {\n const support = entry.archetypes[archetype];\n if (!support) continue;\n candidates.push({\n dTag: entry.dTag,\n ...(entry.title === undefined ? {} : { title: entry.title }),\n actions: support.actions,\n protocols: support.protocols,\n ...(entry.dTag === defaultHandler ? { isDefault: true } : {}),\n });\n }\n return candidates;\n}\n\nfunction fail(archetype: string, action: string, error: string): IntentResult {\n return { ok: false, archetype, action, handled: false, error };\n}\n\n/**\n * Create a catalog-backed NAP-INTENT resolver.\n *\n * @param options - Catalog loader and window controller (required) plus\n * optional default-handler, chooser, and default-protocol policy hooks.\n * @returns A {@link CatalogIntentResolver} for `createIntentService({ resolver })`.\n * @throws If `options.loadCatalog` or `options.windows` is missing.\n *\n * @example\n * ```ts\n * const resolver = createCatalogIntentResolver({\n * loadCatalog: () => installedNapplets,\n * windows: { open: ({ dTag }) => ({ windowId: openWindow(dTag) }) },\n * getDefaultHandler: (a) => userDefaults[a],\n * });\n * ```\n */\nexport function createCatalogIntentResolver(options: CatalogIntentResolverOptions): CatalogIntentResolver {\n if (!options || typeof options.loadCatalog !== 'function') {\n throw new Error('createCatalogIntentResolver: options.loadCatalog is required');\n }\n if (!options.windows || typeof options.windows.open !== 'function') {\n throw new Error('createCatalogIntentResolver: options.windows is required');\n }\n const { loadCatalog, windows, getDefaultHandler, chooseHandler, defaultProtocol } = options;\n const listeners = new Set<(availability: IntentAvailability) => void>();\n\n async function availabilityFor(archetype: string): Promise<IntentAvailability> {\n const catalog = await loadCatalog();\n const def = getDefaultHandler?.(archetype);\n const candidates = candidatesFor(catalog, archetype, def);\n return {\n archetype,\n available: candidates.length > 0,\n candidates,\n hasDefault: def !== undefined && candidates.some((c) => c.dTag === def),\n };\n }\n\n /** Decide which candidate dTag should handle the request, or null to cancel. */\n async function pickHandler(\n archetype: string,\n candidates: IntentCandidate[],\n preference: IntentRequest['handler'],\n callerWindowId: string,\n ): Promise<string | null> {\n // Explicit dTag target (anything that isn't the two reserved keywords).\n if (typeof preference === 'string' && preference !== 'default' && preference !== 'choose') {\n return preference;\n }\n if (preference === 'choose') {\n const picked = await chooseHandler?.(archetype, candidates, callerWindowId);\n return picked ?? null;\n }\n // Default path: user's default, then the sole candidate, then chooser, then\n // a deterministic first-candidate fallback.\n const def = getDefaultHandler?.(archetype);\n if (def !== undefined && candidates.some((c) => c.dTag === def)) return def;\n if (candidates.length === 1) return candidates[0].dTag;\n if (chooseHandler) {\n const picked = await chooseHandler(archetype, candidates, callerWindowId);\n return picked ?? null;\n }\n return candidates[0].dTag;\n }\n\n async function invoke(request: IntentRequest, context: IntentResolverContext): Promise<IntentResult> {\n const archetype = request.archetype;\n const action = request.action ?? 'open';\n const catalog = await loadCatalog();\n const candidates = candidatesFor(catalog, archetype, getDefaultHandler?.(archetype));\n if (candidates.length === 0) return fail(archetype, action, 'no handler');\n\n const pickedDTag = await pickHandler(archetype, candidates, request.handler, context.windowId);\n if (pickedDTag === null) return fail(archetype, action, 'user cancelled');\n\n const handler = candidates.find((c) => c.dTag === pickedDTag);\n if (!handler) return fail(archetype, action, 'no handler');\n\n if (!handler.actions.includes(action)) return fail(archetype, action, 'unsupported action');\n\n const protocol = request.protocol ?? defaultProtocol?.(archetype) ?? handler.protocols[0];\n if (protocol !== undefined && handler.protocols.length > 0 && !handler.protocols.includes(protocol)) {\n return fail(archetype, action, 'unsupported protocol');\n }\n\n let windowId: string;\n try {\n const opened = await windows.open({\n dTag: handler.dTag,\n archetype,\n action,\n ...(protocol === undefined ? {} : { protocol }),\n ...(request.payload === undefined ? {} : { payload: request.payload }),\n ...(request.behavior === undefined ? {} : { behavior: request.behavior }),\n callerWindowId: context.windowId,\n });\n windowId = opened.windowId;\n } catch {\n return fail(archetype, action, 'invoke failed');\n }\n\n return {\n ok: true,\n archetype,\n action,\n handled: true,\n handler: handler.dTag,\n windowId,\n ...(protocol === undefined ? {} : { protocol }),\n };\n }\n\n async function handlers(): Promise<IntentAvailability[]> {\n const catalog = await loadCatalog();\n const archetypes = new Set<string>();\n for (const entry of catalog) {\n for (const slug of Object.keys(entry.archetypes)) archetypes.add(slug);\n }\n return Promise.all([...archetypes].map((a) => availabilityFor(a)));\n }\n\n return {\n invoke,\n available: availabilityFor,\n handlers,\n onChanged(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n notifyChanged(archetype) {\n if (listeners.size === 0) return;\n void availabilityFor(archetype).then((availability) => {\n for (const listener of listeners) listener(availability);\n });\n },\n };\n}\n","/**\n * manifest-intent-catalog.ts — signed-manifest → NAP-INTENT catalog adapter.\n *\n * Adapts a resolved NIP-5A/5D napplet manifest's archetype tags into an\n * {@link IntentCatalogEntry} — the shape `createCatalogIntentResolver.loadCatalog`\n * consumes. This lets NAP-INTENT availability and handler candidacy flow from\n * verified manifest tags rather than host-injected catalog data.\n *\n * To avoid a `@kehto/services → @kehto/nip` dependency cycle (services must stay\n * dependency-light and `@kehto/nip` is a lower-level NIP utility), the adapter\n * takes a minimal STRUCTURAL input {@link ManifestArchetypeInput} that the\n * `@kehto/nip/5d` `NappletManifest` satisfies by duck typing — callers pass\n * `resolved.manifest` directly without any package coupling.\n *\n * @packageDocumentation\n */\n\nimport type { IntentArchetypeSupport, IntentCatalogEntry } from './catalog-intent-resolver.js';\n\n/** NAP-INTENT default action when a manifest does not enumerate actions. */\nconst DEFAULT_ACTIONS: readonly string[] = ['open'];\n\n/**\n * The structural subset of `@kehto/nip/5d` `NappletManifest` the adapter needs.\n * Intentionally a duck-typed shape so the playground (or any caller) can pass a\n * resolved manifest without importing `@kehto/nip`.\n */\nexport interface ManifestArchetypeInput {\n /** The napplet's `d` identifier. */\n dTag: string;\n /** Optional human-readable title from the manifest. */\n title?: string;\n /**\n * Archetype slugs this napplet fulfills, from the manifest's `archetype` tags;\n * the optional `nap` is the recommended default wire protocol.\n */\n archetypes: Array<{ slug: string; nap?: string }>;\n}\n\n/**\n * Map a resolved napplet manifest's archetype data into an\n * {@link IntentCatalogEntry}.\n *\n * Each archetype `{ slug, nap }` becomes a keyed support record where `actions`\n * defaults to `['open']` (the NAP-INTENT default action — manifests do not\n * enumerate actions in this phase) and `protocols` is `[nap]` when a NAP-N is\n * present, else `[]`. Duplicate slugs keep the last occurrence.\n *\n * @param manifest - A resolved manifest's structural archetype data.\n * @returns The `IntentCatalogEntry` for `createCatalogIntentResolver`.\n *\n * @example\n * ```ts\n * manifestToIntentCatalogEntry({\n * dTag: 'profile-viewer',\n * title: 'Profile',\n * archetypes: [{ slug: 'profile', nap: 'NAP-1' }],\n * });\n * // → { dTag: 'profile-viewer', title: 'Profile',\n * // archetypes: { profile: { actions: ['open'], protocols: ['NAP-1'] } } }\n * ```\n */\nexport function manifestToIntentCatalogEntry(manifest: ManifestArchetypeInput): IntentCatalogEntry {\n const archetypes: Record<string, IntentArchetypeSupport> = {};\n for (const { slug, nap } of manifest.archetypes) {\n archetypes[slug] = {\n actions: [...DEFAULT_ACTIONS],\n protocols: nap ? [nap] : [],\n };\n }\n return {\n dTag: manifest.dTag,\n ...(manifest.title === undefined ? {} : { title: manifest.title }),\n archetypes,\n };\n}\n","/**\n * cvm-service.ts — NAP-CVM (ContextVM bridge) reference service.\n *\n * Shell-side handler for the NAP-CVM wire protocol. It is a pure envelope\n * router: it validates `cvm.*` envelopes, delegates the actual ContextVM /\n * MCP-over-Nostr work to an injected {@link CvmTransport}, and posts the\n * correlated `*.result` (echoing the request `id`) back to the napplet.\n *\n * The transport is injected (options-as-bridge) so this service has no Nostr\n * dependency and is fully unit-testable. A concrete ContextVM transport ships\n * separately at `@kehto/services/cvm-nostr-transport`.\n *\n * ──────────────────────────── Responsibilities ────────────────────────────\n * Inbound: cvm.discover, cvm.request, cvm.close\n * Outbound: cvm.discover.result, cvm.request.result, cvm.close.result,\n * cvm.event (server-pushed MCP notifications)\n *\n * MCP-level errors are returned inside `request.result.message.error`;\n * transport/shell-policy failures are returned in the envelope `error` field.\n *\n * `cvm.event` is fanned out to every window that holds an active session with\n * the originating server (a window opens a session by issuing a `cvm.request`\n * and closes it via `cvm.close` or window teardown).\n *\n * @example\n * ```ts\n * import { createCvmService } from '@kehto/services';\n * import { createNostrCvmTransport } from '@kehto/services/cvm-nostr-transport';\n *\n * const transport = createNostrCvmTransport({ defaultRelays: ['wss://relay.contextvm.org'] });\n * runtime.registerService('cvm', createCvmService({ transport }));\n * ```\n */\n\nimport type { NappletMessage } from '@napplet/core';\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\nimport type {\n CvmDiscoverQuery,\n CvmRequestOptions,\n CvmServer,\n CvmServerRef,\n McpMessage,\n} from './cvm-types.js';\n\n/** CVM service version — follows semver. */\nconst CVM_SERVICE_VERSION = '1.0.0';\n\n/**\n * Abstract ContextVM transport. Implementors own Nostr relay access, signing,\n * encryption (CEP-4 gift wrap), JSON-RPC correlation, and MCP initialization.\n */\nexport interface CvmTransport {\n /** Resolve public ContextVM server announcements matching the query. */\n discover(query?: CvmDiscoverQuery): Promise<CvmServer[]>;\n /** Send a raw MCP message to a server and resolve with the MCP response. */\n request(server: CvmServerRef, message: McpMessage, options?: CvmRequestOptions): Promise<McpMessage>;\n /** Release any session state held for a server (subscriptions, init cache). */\n close(server: CvmServerRef): Promise<void>;\n /**\n * Subscribe to server-pushed MCP messages not correlated to a single\n * request (e.g. notifications). Returns a handle whose `close()` detaches.\n */\n onEvent(handler: (server: CvmServerRef, message: McpMessage) => void): { close(): void };\n}\n\n/** Options for {@link createCvmService}. */\nexport interface CvmServiceOptions {\n /** The ContextVM transport the shell uses to reach servers. Required. */\n transport: CvmTransport;\n}\n\n/** The created CVM service, exposing the handler plus a disposal hook. */\nexport interface CvmService extends ServiceHandler {\n /** Detach the transport event subscription. Idempotent. */\n dispose(): void;\n}\n\ntype Send = (msg: NappletMessage) => void;\n\nconst CVM_DESCRIPTOR: ServiceDescriptor = {\n name: 'cvm',\n version: CVM_SERVICE_VERSION,\n description: 'NAP-CVM ContextVM bridge — MCP over Nostr',\n};\n\n/**\n * Create the NAP-CVM service handler.\n *\n * @param options - Must provide a {@link CvmTransport}.\n * @returns A {@link CvmService} (a `ServiceHandler` with a `dispose()` hook).\n * @throws If `options.transport` is missing.\n */\nexport function createCvmService(options: CvmServiceOptions): CvmService {\n if (!options || typeof options.transport !== 'object' || options.transport === null) {\n throw new Error('createCvmService: options.transport is required');\n }\n const { transport } = options;\n\n // Per-window send callbacks, captured at request time for cvm.event fan-out.\n const sendByWindow = new Map<string, Send>();\n // serverPubkey -> set of windowIds with an active session.\n const windowsByServer = new Map<string, Set<string>>();\n\n function openSession(windowId: string, server: CvmServerRef, send: Send): void {\n sendByWindow.set(windowId, send);\n let windows = windowsByServer.get(server.pubkey);\n if (!windows) {\n windows = new Set<string>();\n windowsByServer.set(server.pubkey, windows);\n }\n windows.add(windowId);\n }\n\n function closeSession(windowId: string, serverPubkey: string): void {\n const windows = windowsByServer.get(serverPubkey);\n if (windows) {\n windows.delete(windowId);\n if (windows.size === 0) windowsByServer.delete(serverPubkey);\n }\n }\n\n const eventSub = transport.onEvent((server, message) => {\n const windows = windowsByServer.get(server.pubkey);\n if (!windows) return;\n for (const windowId of windows) {\n const send = sendByWindow.get(windowId);\n send?.({ type: 'cvm.event', server, message } as NappletMessage);\n }\n });\n\n function handleDiscover(msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { id?: string; query?: CvmDiscoverQuery };\n const id = m.id ?? '';\n void transport\n .discover(m.query)\n .then((servers) => send({ type: 'cvm.discover.result', id, servers } as NappletMessage))\n .catch((err) =>\n send({ type: 'cvm.discover.result', id, servers: [], error: toErrorMessage(err) } as NappletMessage),\n );\n }\n\n function handleRequest(windowId: string, msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & {\n id?: string;\n server?: CvmServerRef;\n message?: McpMessage;\n options?: CvmRequestOptions;\n };\n const id = m.id ?? '';\n if (!m.server || typeof m.server.pubkey !== 'string' || m.server.pubkey.length === 0) {\n send({ type: 'cvm.request.result', id, error: 'server not found' } as NappletMessage);\n return;\n }\n if (!m.message || typeof m.message !== 'object') {\n send({ type: 'cvm.request.result', id, error: 'unsupported method' } as NappletMessage);\n return;\n }\n openSession(windowId, m.server, send);\n void transport\n .request(m.server, m.message, m.options)\n .then((message) => send({ type: 'cvm.request.result', id, message } as NappletMessage))\n .catch((err) =>\n send({ type: 'cvm.request.result', id, error: toErrorMessage(err) } as NappletMessage),\n );\n }\n\n function handleClose(windowId: string, msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { id?: string; server?: CvmServerRef };\n const id = m.id ?? '';\n if (!m.server || typeof m.server.pubkey !== 'string') {\n send({ type: 'cvm.close.result', id, error: 'server not found' } as NappletMessage);\n return;\n }\n closeSession(windowId, m.server.pubkey);\n void transport\n .close(m.server)\n .then(() => send({ type: 'cvm.close.result', id } as NappletMessage))\n .catch((err) => send({ type: 'cvm.close.result', id, error: toErrorMessage(err) } as NappletMessage));\n }\n\n return {\n descriptor: CVM_DESCRIPTOR,\n handleMessage(windowId: string, message: NappletMessage, send: Send): void {\n switch (message.type) {\n case 'cvm.discover':\n handleDiscover(message, send);\n return;\n case 'cvm.request':\n handleRequest(windowId, message, send);\n return;\n case 'cvm.close':\n handleClose(windowId, message, send);\n return;\n default:\n // Unknown cvm.* action — silently ignored (forward-compatible).\n return;\n }\n },\n onWindowDestroyed(windowId: string): void {\n sendByWindow.delete(windowId);\n for (const [pubkey, windows] of windowsByServer) {\n windows.delete(windowId);\n if (windows.size === 0) windowsByServer.delete(pubkey);\n }\n },\n dispose(): void {\n eventSub.close();\n },\n };\n}\n\nfunction toErrorMessage(err: unknown): string {\n if (err instanceof Error) return err.message;\n if (typeof err === 'string') return err;\n return 'cvm request failed';\n}\n","/**\n * link-service.ts — NAP-LINK reference service.\n *\n * Shell-mediated user-visible navigation. The napplet requests `link.open`;\n * the shell validates policy and hands navigation to a host-owned context.\n */\n\nimport type { LinkOpenErrorCode, LinkOpenOptions, LinkOpenResult, NappletMessage } from '@napplet/core';\nimport type { LinkOpenMessage, LinkOpenResultMessage } from '@napplet/nap/link/types';\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\n\nconst LINK_SERVICE_VERSION = '1.0.0';\nconst DEFAULT_ALLOWED_PROTOCOLS = ['http:', 'https:'] as const;\n\n/**\n * Host context passed to {@link LinkServiceOptions.open}.\n */\nexport interface LinkOpenContext {\n /** Window id of the requesting napplet. */\n windowId: string;\n /** Parsed absolute URL after validation. */\n url: URL;\n /** Optional untrusted napplet display hints. */\n options?: LinkOpenOptions;\n}\n\n/**\n * Options for {@link createLinkService}.\n */\nexport interface LinkServiceOptions {\n /**\n * Protocols allowed before host policy runs. Defaults to `http:` and `https:`.\n */\n allowedProtocols?: readonly string[];\n /**\n * Host-owned opener. Return `{ status: \"opened\" }` after handing navigation\n * to the host context, or `{ status: \"denied\" }` to reject by policy/user.\n * When omitted, all otherwise-valid URLs are denied.\n */\n open?: (context: LinkOpenContext) => LinkOpenResult | Promise<LinkOpenResult>;\n}\n\nfunction denied(id: string, error: LinkOpenErrorCode): LinkOpenResultMessage {\n return { type: 'link.open.result', id, status: 'denied', error };\n}\n\nfunction parseLinkUrl(id: string, rawUrl: string): URL | LinkOpenResultMessage {\n try {\n return new URL(rawUrl);\n } catch {\n return denied(id, 'invalid-url');\n }\n}\n\nfunction isProtocolAllowed(url: URL, allowedProtocols: readonly string[]): boolean {\n return allowedProtocols.includes(url.protocol);\n}\n\nasync function handleOpen(\n options: LinkServiceOptions,\n windowId: string,\n message: LinkOpenMessage,\n send: (msg: NappletMessage) => void,\n): Promise<void> {\n const parsed = parseLinkUrl(message.id, message.url);\n if ('type' in parsed) {\n send(parsed as NappletMessage);\n return;\n }\n\n const allowedProtocols = options.allowedProtocols ?? DEFAULT_ALLOWED_PROTOCOLS;\n if (!isProtocolAllowed(parsed, allowedProtocols)) {\n send(denied(message.id, 'unsupported-scheme') as NappletMessage);\n return;\n }\n\n if (!options.open) {\n send(denied(message.id, 'blocked-by-policy') as NappletMessage);\n return;\n }\n\n try {\n const result = await options.open({ windowId, url: parsed, options: message.options });\n send({\n type: 'link.open.result',\n id: message.id,\n status: result.status,\n ...(result.status === 'denied' ? { error: 'blocked-by-policy' as LinkOpenErrorCode } : {}),\n } as NappletMessage);\n } catch {\n send(denied(message.id, 'blocked-by-policy') as NappletMessage);\n }\n}\n\n/**\n * Create the NAP-LINK reference service.\n *\n * @param options - Host policy and opener hooks for link.open requests.\n * @returns A runtime service handler for the `link` domain.\n *\n * @example\n * ```ts\n * const service = createLinkService({\n * open: ({ url }) => ({ status: url.protocol === 'https:' ? 'opened' : 'denied' }),\n * });\n * ```\n */\nexport function createLinkService(options: LinkServiceOptions = {}): ServiceHandler {\n const descriptor: ServiceDescriptor = {\n name: 'link',\n version: LINK_SERVICE_VERSION,\n description: 'NAP-LINK reference handler for shell-mediated link opening',\n };\n\n return {\n descriptor,\n handleMessage(windowId: string, message: NappletMessage, send: (msg: NappletMessage) => void): void {\n if (message.type === 'link.open') {\n void handleOpen(options, windowId, message as LinkOpenMessage, send);\n return;\n }\n\n const id = (message as NappletMessage & { id?: string }).id ?? '';\n send({\n type: `${message.type}.error`,\n id,\n error: `Unknown link method: ${message.type}`,\n } as NappletMessage);\n },\n onWindowDestroyed(_windowId: string): void {\n /* no per-window state */\n },\n };\n}\n","/**\n * lists-service.ts — NAP-LISTS reference service.\n *\n * Shell-mediated NIP-51 list metadata and mutation hooks. The napplet requests\n * `lists.*`; the shell owns list lookup, validation, signing, publishing, and\n * private-item policy.\n */\n\nimport type {\n ListItem,\n ListMutationResult,\n ListOptions,\n ListRef,\n ListSupport,\n NappletMessage,\n} from '@napplet/core';\nimport type {\n ListsAddMessage,\n ListsRemoveMessage,\n} from '@napplet/nap/lists/types';\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\n\nconst LISTS_SERVICE_VERSION = '1.0.0';\n\n/** Context passed to host-provided NAP-LISTS hooks. */\nexport interface ListsServiceContext {\n /** Window id of the requesting napplet. */\n windowId: string;\n}\n\n/** Options for {@link createListsService}. */\nexport interface ListsServiceOptions {\n /** Host-owned supported list metadata. */\n supported?: (context: ListsServiceContext) => readonly ListSupport[] | Promise<readonly ListSupport[]>;\n /** Host-owned add mutation. */\n add?: (\n list: ListRef,\n items: readonly ListItem[],\n options: ListOptions | undefined,\n context: ListsServiceContext,\n ) => ListMutationResult | Promise<ListMutationResult>;\n /** Host-owned remove mutation. */\n remove?: (\n list: ListRef,\n items: readonly ListItem[],\n options: ListOptions | undefined,\n context: ListsServiceContext,\n ) => ListMutationResult | Promise<ListMutationResult>;\n}\n\ntype Send = (msg: NappletMessage) => void;\ntype MutationHook = NonNullable<ListsServiceOptions['add'] | ListsServiceOptions['remove']>;\n\nconst LISTS_DESCRIPTOR: ServiceDescriptor = {\n name: 'lists',\n version: LISTS_SERVICE_VERSION,\n description: 'NAP-LISTS reference handler for shell-mediated NIP-51 list mutations',\n};\n\nfunction errorMessage(err: unknown, fallback: string): string {\n if (err instanceof Error) return err.message;\n if (typeof err === 'string') return err;\n return fallback;\n}\n\nfunction settle<T>(\n call: () => T | Promise<T>,\n send: Send,\n okFalse: (error: string) => NappletMessage,\n onValue: (value: T) => NappletMessage,\n): void {\n let pending: Promise<T>;\n try {\n pending = Promise.resolve(call());\n } catch (err) {\n send(okFalse(errorMessage(err, 'lists request failed')));\n return;\n }\n pending\n .then((value) => send(onValue(value)))\n .catch((err) => send(okFalse(errorMessage(err, 'lists request failed'))));\n}\n\nfunction unsupportedMutation(resultType: string, id: string): NappletMessage {\n return {\n type: resultType,\n id,\n ok: false,\n error: 'unsupported',\n reason: `${resultType.replace('.result', '')} unavailable`,\n supported: [],\n } as NappletMessage;\n}\n\nfunction settleMutation(\n hook: MutationHook | undefined,\n message: ListsAddMessage | ListsRemoveMessage,\n context: ListsServiceContext,\n send: Send,\n resultType: 'lists.add.result' | 'lists.remove.result',\n): void {\n if (!hook) {\n send(unsupportedMutation(resultType, message.id));\n return;\n }\n settle(\n () => hook(message.list, message.items, message.options, context),\n send,\n (error) => ({ type: resultType, id: message.id, ok: false, error: 'list-unavailable', reason: error } as NappletMessage),\n (result) => ({ type: resultType, id: message.id, ...result } as NappletMessage),\n );\n}\n\n/**\n * Create the NAP-LISTS reference service.\n *\n * @param options - Host list metadata and mutation hooks for lists.* requests.\n * @returns A runtime service handler for the `lists` domain.\n */\nexport function createListsService(options: ListsServiceOptions = {}): ServiceHandler {\n return {\n descriptor: LISTS_DESCRIPTOR,\n handleMessage(windowId: string, message: NappletMessage, send: Send): void {\n const id = (message as NappletMessage & { id?: string }).id ?? '';\n const context = { windowId };\n\n if (message.type === 'lists.supported') {\n if (!options.supported) {\n send({ type: 'lists.supported.result', id, lists: [] } as NappletMessage);\n return;\n }\n settle(\n () => options.supported!(context),\n send,\n (error) => ({ type: 'lists.supported.result', id, error } as NappletMessage),\n (lists) => ({ type: 'lists.supported.result', id, lists: [...lists] } as NappletMessage),\n );\n return;\n }\n\n if (message.type === 'lists.add') {\n settleMutation(options.add, message as ListsAddMessage, context, send, 'lists.add.result');\n return;\n }\n\n if (message.type === 'lists.remove') {\n settleMutation(options.remove, message as ListsRemoveMessage, context, send, 'lists.remove.result');\n return;\n }\n\n send({\n type: `${message.type}.error`,\n id,\n error: `Unknown lists method: ${message.type}`,\n } as NappletMessage);\n },\n onWindowDestroyed(_windowId: string): void {\n /* no per-window state */\n },\n };\n}\n","/**\n * serial-service.ts — NAP-SERIAL reference service.\n *\n * Shell-mediated serial sessions. The napplet requests `serial.*`; the shell\n * owns device selection, permissions, session handles, reads, write ordering,\n * close events, and policy.\n */\n\nimport type {\n NappletMessage,\n SerialOpenRequest,\n SerialOpenResult,\n} from '@napplet/core';\nimport type {\n SerialCloseMessage,\n SerialOpenMessage,\n SerialWriteMessage,\n} from '@napplet/nap/serial/types';\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\n\nconst SERIAL_SERVICE_VERSION = '1.0.0';\n\n/** Context passed to host-provided NAP-SERIAL hooks. */\nexport interface SerialServiceContext {\n /** Window id of the requesting napplet. */\n windowId: string;\n}\n\n/** Options for {@link createSerialService}. */\nexport interface SerialServiceOptions {\n /** Host-owned serial open hook. */\n open?: (\n request: SerialOpenRequest,\n context: SerialServiceContext,\n ) => SerialOpenResult | Promise<SerialOpenResult>;\n /** Host-owned serial write hook. */\n write?: (\n sessionId: string,\n data: readonly number[],\n context: SerialServiceContext,\n ) => void | Promise<void>;\n /** Host-owned serial close hook. */\n close?: (\n sessionId: string,\n reason: string | undefined,\n context: SerialServiceContext,\n ) => void | Promise<void>;\n /** Optional host cleanup when a napplet window is destroyed. */\n destroyWindow?: (windowId: string) => void;\n}\n\ntype Send = (msg: NappletMessage) => void;\n\nconst SERIAL_DESCRIPTOR: ServiceDescriptor = {\n name: 'serial',\n version: SERIAL_SERVICE_VERSION,\n description: 'NAP-SERIAL reference handler for shell-mediated serial sessions',\n};\n\nfunction errorMessage(err: unknown, fallback: string): string {\n if (err instanceof Error) return err.message;\n if (typeof err === 'string') return err;\n return fallback;\n}\n\nfunction settle<T>(\n call: () => T | Promise<T>,\n send: Send,\n okFalse: (error: string) => NappletMessage,\n onValue: (value: T) => NappletMessage,\n): void {\n let pending: Promise<T>;\n try {\n pending = Promise.resolve(call());\n } catch (err) {\n send(okFalse(errorMessage(err, 'serial request failed')));\n return;\n }\n pending\n .then((value) => send(onValue(value)))\n .catch((err) => send(okFalse(errorMessage(err, 'serial request failed'))));\n}\n\nfunction unsupported(resultType: string, id: string): NappletMessage {\n return {\n type: resultType,\n id,\n error: `${resultType.replace('.result', '')} unavailable`,\n } as NappletMessage;\n}\n\n/**\n * Create the NAP-SERIAL reference service.\n *\n * @param options - Host serial session hooks for serial.* requests.\n * @returns A runtime service handler for the `serial` domain.\n */\nexport function createSerialService(options: SerialServiceOptions = {}): ServiceHandler {\n return {\n descriptor: SERIAL_DESCRIPTOR,\n handleMessage(windowId: string, message: NappletMessage, send: Send): void {\n const id = (message as NappletMessage & { id?: string }).id ?? '';\n const context = { windowId };\n\n if (message.type === 'serial.open') {\n if (!options.open) {\n send(unsupported('serial.open.result', id));\n return;\n }\n const serialMessage = message as SerialOpenMessage;\n settle(\n () => options.open!(serialMessage.request, context),\n send,\n (error) => ({ type: 'serial.open.result', id, error } as NappletMessage),\n (result) => ({ type: 'serial.open.result', id, session: result.session } as NappletMessage),\n );\n return;\n }\n\n if (message.type === 'serial.write') {\n if (!options.write) {\n send(unsupported('serial.write.result', id));\n return;\n }\n const serialMessage = message as SerialWriteMessage;\n settle(\n () => options.write!(serialMessage.sessionId, serialMessage.data, context),\n send,\n (error) => ({ type: 'serial.write.result', id, error } as NappletMessage),\n () => ({ type: 'serial.write.result', id } as NappletMessage),\n );\n return;\n }\n\n if (message.type === 'serial.close') {\n if (!options.close) {\n send(unsupported('serial.close.result', id));\n return;\n }\n const serialMessage = message as SerialCloseMessage;\n settle(\n () => options.close!(serialMessage.sessionId, serialMessage.reason, context),\n send,\n (error) => ({ type: 'serial.close.result', id, error } as NappletMessage),\n () => ({ type: 'serial.close.result', id } as NappletMessage),\n );\n return;\n }\n\n send({\n type: `${message.type}.error`,\n id,\n error: `Unknown serial method: ${message.type}`,\n } as NappletMessage);\n },\n onWindowDestroyed(windowId: string): void {\n options.destroyWindow?.(windowId);\n },\n };\n}\n","/**\n * ble-service.ts — NAP-BLE reference service.\n *\n * Shell-mediated BLE/GATT sessions. The napplet requests `ble.*`; the shell\n * owns chooser UI, permissions, GATT handles, notifications, and policy.\n */\n\nimport type {\n BleAttribute,\n BleOpenRequest,\n BleOpenResult,\n BleService,\n BleWriteOptions,\n NappletMessage,\n} from '@napplet/core';\nimport type {\n BleCloseMessage,\n BleOpenMessage,\n BleReadMessage,\n BleServicesMessage,\n BleSubscribeMessage,\n BleUnsubscribeMessage,\n BleWriteMessage,\n} from '@napplet/nap/ble/types';\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\n\nconst BLE_SERVICE_VERSION = '1.0.0';\n\n/** Context passed to host-provided NAP-BLE hooks. */\nexport interface BleServiceContext {\n /** Window id of the requesting napplet. */\n windowId: string;\n}\n\n/** Options for {@link createBleService}. */\nexport interface BleServiceOptions {\n /** Host-owned BLE open hook. */\n open?: (\n request: BleOpenRequest,\n context: BleServiceContext,\n ) => BleOpenResult | Promise<BleOpenResult>;\n /** Host-owned GATT service listing hook. */\n services?: (\n sessionId: string,\n context: BleServiceContext,\n ) => BleService[] | Promise<BleService[]>;\n /** Host-owned GATT read hook. */\n read?: (\n sessionId: string,\n target: BleAttribute,\n context: BleServiceContext,\n ) => number[] | Promise<number[]>;\n /** Host-owned GATT write hook. */\n write?: (\n sessionId: string,\n target: BleAttribute,\n data: readonly number[],\n options: BleWriteOptions | undefined,\n context: BleServiceContext,\n ) => void | Promise<void>;\n /** Host-owned notification subscription hook. */\n subscribe?: (\n sessionId: string,\n target: BleAttribute,\n context: BleServiceContext,\n ) => void | Promise<void>;\n /** Host-owned notification unsubscribe hook. */\n unsubscribe?: (\n sessionId: string,\n target: BleAttribute,\n context: BleServiceContext,\n ) => void | Promise<void>;\n /** Host-owned BLE close hook. */\n close?: (\n sessionId: string,\n reason: string | undefined,\n context: BleServiceContext,\n ) => void | Promise<void>;\n /** Optional host cleanup when a napplet window is destroyed. */\n destroyWindow?: (windowId: string) => void;\n}\n\ntype Send = (msg: NappletMessage) => void;\n\nconst BLE_DESCRIPTOR: ServiceDescriptor = {\n name: 'ble',\n version: BLE_SERVICE_VERSION,\n description: 'NAP-BLE reference handler for shell-mediated BLE/GATT sessions',\n};\n\nfunction errorMessage(err: unknown, fallback: string): string {\n if (err instanceof Error) return err.message;\n if (typeof err === 'string') return err;\n return fallback;\n}\n\nfunction settle<T>(\n call: () => T | Promise<T>,\n send: Send,\n okFalse: (error: string) => NappletMessage,\n onValue: (value: T) => NappletMessage,\n): void {\n let pending: Promise<T>;\n try {\n pending = Promise.resolve(call());\n } catch (err) {\n send(okFalse(errorMessage(err, 'ble request failed')));\n return;\n }\n pending\n .then((value) => send(onValue(value)))\n .catch((err) => send(okFalse(errorMessage(err, 'ble request failed'))));\n}\n\nfunction unsupported(resultType: string, id: string): NappletMessage {\n return {\n type: resultType,\n id,\n error: `${resultType.replace('.result', '')} unavailable`,\n } as NappletMessage;\n}\n\n/**\n * Create the NAP-BLE reference service.\n *\n * @param options - Host BLE session hooks for ble.* requests.\n * @returns A runtime service handler for the `ble` domain.\n */\nexport function createBleService(options: BleServiceOptions = {}): ServiceHandler {\n return {\n descriptor: BLE_DESCRIPTOR,\n handleMessage(windowId: string, message: NappletMessage, send: Send): void {\n const id = (message as NappletMessage & { id?: string }).id ?? '';\n const context = { windowId };\n\n if (message.type === 'ble.open') {\n if (!options.open) {\n send(unsupported('ble.open.result', id));\n return;\n }\n const bleMessage = message as BleOpenMessage;\n settle(\n () => options.open!(bleMessage.request, context),\n send,\n (error) => ({ type: 'ble.open.result', id, error } as NappletMessage),\n (result) => ({ type: 'ble.open.result', id, session: result.session } as NappletMessage),\n );\n return;\n }\n\n if (message.type === 'ble.services') {\n if (!options.services) {\n send(unsupported('ble.services.result', id));\n return;\n }\n const bleMessage = message as BleServicesMessage;\n settle(\n () => options.services!(bleMessage.sessionId, context),\n send,\n (error) => ({ type: 'ble.services.result', id, error } as NappletMessage),\n (services) => ({ type: 'ble.services.result', id, services } as NappletMessage),\n );\n return;\n }\n\n if (message.type === 'ble.read') {\n if (!options.read) {\n send(unsupported('ble.read.result', id));\n return;\n }\n const bleMessage = message as BleReadMessage;\n settle(\n () => options.read!(bleMessage.sessionId, bleMessage.target, context),\n send,\n (error) => ({ type: 'ble.read.result', id, error } as NappletMessage),\n (data) => ({ type: 'ble.read.result', id, data } as NappletMessage),\n );\n return;\n }\n\n if (message.type === 'ble.write') {\n if (!options.write) {\n send(unsupported('ble.write.result', id));\n return;\n }\n const bleMessage = message as BleWriteMessage;\n settle(\n () => options.write!(bleMessage.sessionId, bleMessage.target, bleMessage.data, bleMessage.options, context),\n send,\n (error) => ({ type: 'ble.write.result', id, error } as NappletMessage),\n () => ({ type: 'ble.write.result', id } as NappletMessage),\n );\n return;\n }\n\n if (message.type === 'ble.subscribe') {\n if (!options.subscribe) {\n send(unsupported('ble.subscribe.result', id));\n return;\n }\n const bleMessage = message as BleSubscribeMessage;\n settle(\n () => options.subscribe!(bleMessage.sessionId, bleMessage.target, context),\n send,\n (error) => ({ type: 'ble.subscribe.result', id, error } as NappletMessage),\n () => ({ type: 'ble.subscribe.result', id } as NappletMessage),\n );\n return;\n }\n\n if (message.type === 'ble.unsubscribe') {\n if (!options.unsubscribe) {\n send(unsupported('ble.unsubscribe.result', id));\n return;\n }\n const bleMessage = message as BleUnsubscribeMessage;\n settle(\n () => options.unsubscribe!(bleMessage.sessionId, bleMessage.target, context),\n send,\n (error) => ({ type: 'ble.unsubscribe.result', id, error } as NappletMessage),\n () => ({ type: 'ble.unsubscribe.result', id } as NappletMessage),\n );\n return;\n }\n\n if (message.type === 'ble.close') {\n if (!options.close) {\n send(unsupported('ble.close.result', id));\n return;\n }\n const bleMessage = message as BleCloseMessage;\n settle(\n () => options.close!(bleMessage.sessionId, bleMessage.reason, context),\n send,\n (error) => ({ type: 'ble.close.result', id, error } as NappletMessage),\n () => ({ type: 'ble.close.result', id } as NappletMessage),\n );\n return;\n }\n\n send({\n type: `${message.type}.error`,\n id,\n error: `Unknown ble method: ${message.type}`,\n } as NappletMessage);\n },\n onWindowDestroyed(windowId: string): void {\n options.destroyWindow?.(windowId);\n },\n };\n}\n","/**\n * webrtc-service.ts — NAP-WEBRTC reference service.\n *\n * Shell-mediated WebRTC sessions. The napplet requests `webrtc.*`; the shell\n * owns signaling, signing/encryption, SDP, ICE, peer connections, and policy.\n */\n\nimport type {\n NappletMessage,\n WebrtcEvent,\n WebrtcOpenRequest,\n WebrtcOpenResult,\n} from '@napplet/core';\nimport type {\n WebrtcCloseMessage,\n WebrtcOpenMessage,\n WebrtcSendMessage,\n} from '@napplet/nap/webrtc/types';\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\n\nconst WEBRTC_SERVICE_VERSION = '1.0.0';\n\n/** Context passed to host-provided NAP-WEBRTC hooks. */\nexport interface WebrtcServiceContext {\n /** Window id of the requesting napplet. */\n windowId: string;\n /** Emit a runtime-owned WebRTC event back to the requesting napplet. */\n emit(event: WebrtcEvent): void;\n}\n\n/** Options for {@link createWebrtcService}. */\nexport interface WebrtcServiceOptions {\n /** Host-owned WebRTC open hook. */\n open?: (\n request: WebrtcOpenRequest,\n context: WebrtcServiceContext,\n ) => WebrtcOpenResult | Promise<WebrtcOpenResult>;\n /** Host-owned WebRTC send hook. */\n send?: (\n sessionId: string,\n payload: unknown,\n context: WebrtcServiceContext,\n ) => void | Promise<void>;\n /** Host-owned WebRTC close hook. */\n close?: (\n sessionId: string,\n reason: string | undefined,\n context: WebrtcServiceContext,\n ) => void | Promise<void>;\n /** Optional host cleanup when a napplet window is destroyed. */\n destroyWindow?: (windowId: string) => void;\n}\n\ntype Send = (msg: NappletMessage) => void;\n\nconst WEBRTC_DESCRIPTOR: ServiceDescriptor = {\n name: 'webrtc',\n version: WEBRTC_SERVICE_VERSION,\n description: 'NAP-WEBRTC reference handler for shell-mediated WebRTC sessions',\n};\n\nfunction errorMessage(err: unknown, fallback: string): string {\n if (err instanceof Error) return err.message;\n if (typeof err === 'string') return err;\n return fallback;\n}\n\nfunction settle<T>(\n call: () => T | Promise<T>,\n send: Send,\n okFalse: (error: string) => NappletMessage,\n onValue: (value: T) => NappletMessage,\n): void {\n let pending: Promise<T>;\n try {\n pending = Promise.resolve(call());\n } catch (err) {\n send(okFalse(errorMessage(err, 'webrtc request failed')));\n return;\n }\n pending\n .then((value) => send(onValue(value)))\n .catch((err) => send(okFalse(errorMessage(err, 'webrtc request failed'))));\n}\n\nfunction unsupported(resultType: string, id: string): NappletMessage {\n return {\n type: resultType,\n id,\n error: `${resultType.replace('.result', '')} unavailable`,\n } as NappletMessage;\n}\n\nfunction createContext(windowId: string, send: Send): WebrtcServiceContext {\n return {\n windowId,\n emit(event) {\n send({ type: 'webrtc.event', event } as NappletMessage);\n },\n };\n}\n\n/**\n * Create the NAP-WEBRTC reference service.\n *\n * @param options - Host WebRTC session hooks for webrtc.* requests.\n * @returns A runtime service handler for the `webrtc` domain.\n */\nexport function createWebrtcService(options: WebrtcServiceOptions = {}): ServiceHandler {\n return {\n descriptor: WEBRTC_DESCRIPTOR,\n handleMessage(windowId: string, message: NappletMessage, send: Send): void {\n const id = (message as NappletMessage & { id?: string }).id ?? '';\n const context = createContext(windowId, send);\n\n if (message.type === 'webrtc.open') {\n if (!options.open) {\n send(unsupported('webrtc.open.result', id));\n return;\n }\n const webrtcMessage = message as WebrtcOpenMessage;\n settle(\n () => options.open!(webrtcMessage.request, context),\n send,\n (error) => ({ type: 'webrtc.open.result', id, error } as NappletMessage),\n (result) => ({ type: 'webrtc.open.result', id, session: result.session } as NappletMessage),\n );\n return;\n }\n\n if (message.type === 'webrtc.send') {\n if (!options.send) {\n send(unsupported('webrtc.send.result', id));\n return;\n }\n const webrtcMessage = message as WebrtcSendMessage;\n settle(\n () => options.send!(webrtcMessage.sessionId, webrtcMessage.payload, context),\n send,\n (error) => ({ type: 'webrtc.send.result', id, error } as NappletMessage),\n () => ({ type: 'webrtc.send.result', id } as NappletMessage),\n );\n return;\n }\n\n if (message.type === 'webrtc.close') {\n if (!options.close) {\n send(unsupported('webrtc.close.result', id));\n return;\n }\n const webrtcMessage = message as WebrtcCloseMessage;\n settle(\n () => options.close!(webrtcMessage.sessionId, webrtcMessage.reason, context),\n send,\n (error) => ({ type: 'webrtc.close.result', id, error } as NappletMessage),\n () => ({ type: 'webrtc.close.result', id } as NappletMessage),\n );\n return;\n }\n\n send({\n type: `${message.type}.error`,\n id,\n error: `Unknown webrtc method: ${message.type}`,\n } as NappletMessage);\n },\n onWindowDestroyed(windowId: string): void {\n options.destroyWindow?.(windowId);\n },\n };\n}\n","/**\n * common-service.ts — NAP-COMMON reference service.\n *\n * Shell-mediated public NIP-19 helpers and common social actions. The napplet\n * requests `common.*`; the shell owns identity, consent, event construction,\n * signing, publishing, relay access, and NIP-19 handling.\n */\n\nimport type {\n CommonActionResult,\n CommonFollowsResult,\n CommonNip19DecodeResult,\n CommonNip19EncodeInput,\n CommonNip19EncodeResult,\n CommonProfileResult,\n CommonProfileTarget,\n CommonReaction,\n CommonReportReason,\n CommonReportTarget,\n NappletMessage,\n} from '@napplet/core';\nimport type {\n CommonDecodeNip19Message,\n CommonEncodeNip19Message,\n CommonFollowMessage,\n CommonGetProfileMessage,\n CommonReactMessage,\n CommonReportMessage,\n CommonUnfollowMessage,\n} from '@napplet/nap/common/types';\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\nimport {\n decode,\n encodeBytes,\n naddrEncode,\n neventEncode,\n noteEncode,\n nprofileEncode,\n npubEncode,\n} from 'nostr-tools/nip19';\n\nconst COMMON_SERVICE_VERSION = '1.0.0';\nconst BECH32_CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l';\n\n/** Context passed to host-provided NAP-COMMON hooks. */\nexport interface CommonServiceContext {\n /** Window id of the requesting napplet. */\n windowId: string;\n}\n\n/** Options for {@link createCommonService}. */\nexport interface CommonServiceOptions {\n /** Host-owned profile lookup. */\n getProfile?: (target: CommonProfileTarget, context: CommonServiceContext) => CommonProfileResult | Promise<CommonProfileResult>;\n /** Host-owned current-user follows lookup. */\n follows?: (context: CommonServiceContext) => CommonFollowsResult | Promise<CommonFollowsResult>;\n /** Host-owned follow action. */\n follow?: (pubkeys: string[], context: CommonServiceContext) => CommonActionResult | Promise<CommonActionResult>;\n /** Host-owned unfollow action. */\n unfollow?: (pubkeys: string[], context: CommonServiceContext) => CommonActionResult | Promise<CommonActionResult>;\n /** Host-owned reaction action. */\n react?: (\n targetEventId: string,\n reaction: CommonReaction,\n customEmojiHref: string | undefined,\n context: CommonServiceContext,\n ) => CommonActionResult | Promise<CommonActionResult>;\n /** Host-owned report action. */\n report?: (\n target: CommonReportTarget,\n reason: CommonReportReason,\n text: string,\n context: CommonServiceContext,\n ) => CommonActionResult | Promise<CommonActionResult>;\n}\n\ntype Send = (msg: NappletMessage) => void;\n\nconst COMMON_DESCRIPTOR: ServiceDescriptor = {\n name: 'common',\n version: COMMON_SERVICE_VERSION,\n description: 'NAP-COMMON reference handler for shell-mediated social helpers',\n};\n\nfunction errorMessage(err: unknown, fallback: string): string {\n if (err instanceof Error) return err.message;\n if (typeof err === 'string') return err;\n return fallback;\n}\n\nfunction encodeNip19(input: CommonNip19EncodeInput): CommonNip19EncodeResult {\n try {\n switch (input.type) {\n case 'npub':\n return { ok: true, value: npubEncode(input.hex), nip19Type: input.type };\n case 'note':\n return { ok: true, value: noteEncode(input.hex), nip19Type: input.type };\n case 'nprofile':\n return { ok: true, value: nprofileEncode({ pubkey: input.pubkey, relays: input.relays }), nip19Type: input.type };\n case 'nevent':\n return {\n ok: true,\n value: neventEncode({ id: input.eventId, relays: input.relays, author: input.author, kind: input.kind }),\n nip19Type: input.type,\n };\n case 'naddr':\n return {\n ok: true,\n value: naddrEncode({ identifier: input.identifier, pubkey: input.pubkey, kind: input.kind, relays: input.relays }),\n nip19Type: input.type,\n };\n case 'nrelay':\n return { ok: true, value: encodeBytes('nrelay', new TextEncoder().encode(input.relay)), nip19Type: input.type };\n default:\n return { ok: false, error: 'unsupported NIP-19 type' };\n }\n } catch (err) {\n return { ok: false, error: errorMessage(err, 'NIP-19 encode failed') };\n }\n}\n\nfunction decodeNip19Value(value: string): CommonNip19DecodeResult {\n try {\n const decoded = decode(value);\n switch (decoded.type) {\n case 'npub':\n case 'note':\n return { ok: true, nip19Type: decoded.type, hex: decoded.data };\n case 'nprofile':\n return { ok: true, nip19Type: decoded.type, pubkey: decoded.data.pubkey, relays: decoded.data.relays };\n case 'nevent':\n return {\n ok: true,\n nip19Type: decoded.type,\n eventId: decoded.data.id,\n relays: decoded.data.relays,\n author: decoded.data.author,\n kind: decoded.data.kind,\n };\n case 'naddr':\n return {\n ok: true,\n nip19Type: decoded.type,\n identifier: decoded.data.identifier,\n pubkey: decoded.data.pubkey,\n kind: decoded.data.kind,\n relays: decoded.data.relays,\n };\n default:\n return { ok: false, error: 'unsupported NIP-19 type' };\n }\n } catch (err) {\n if (value.startsWith('nrelay1')) return decodeNrelay(value);\n return { ok: false, error: errorMessage(err, 'NIP-19 decode failed') };\n }\n}\n\nfunction decodeNrelay(value: string): CommonNip19DecodeResult {\n try {\n const separator = value.lastIndexOf('1');\n if (separator <= 0 || separator + 7 > value.length) return { ok: false, error: 'invalid nrelay value' };\n const dataPart = value.slice(separator + 1);\n const words = [...dataPart].map((char) => {\n const index = BECH32_CHARSET.indexOf(char);\n if (index === -1) throw new Error('invalid nrelay character');\n return index;\n });\n const payloadWords = words.slice(0, -6);\n const bytes = convertBits(payloadWords, 5, 8, false);\n return { ok: true, nip19Type: 'nrelay', relay: new TextDecoder().decode(Uint8Array.from(bytes)) };\n } catch (err) {\n return { ok: false, error: errorMessage(err, 'invalid nrelay value') };\n }\n}\n\nfunction convertBits(data: readonly number[], fromBits: number, toBits: number, pad: boolean): number[] {\n let acc = 0;\n let bits = 0;\n const result: number[] = [];\n const maxv = (1 << toBits) - 1;\n const maxAcc = (1 << (fromBits + toBits - 1)) - 1;\n for (const value of data) {\n if (value < 0 || value >> fromBits !== 0) throw new Error('invalid bech32 data');\n acc = ((acc << fromBits) | value) & maxAcc;\n bits += fromBits;\n while (bits >= toBits) {\n bits -= toBits;\n result.push((acc >> bits) & maxv);\n }\n }\n if (pad) {\n if (bits > 0) result.push((acc << (toBits - bits)) & maxv);\n } else if (bits >= fromBits || ((acc << (toBits - bits)) & maxv) !== 0) {\n throw new Error('invalid bech32 padding');\n }\n return result;\n}\n\nfunction settle<T>(\n call: () => T | Promise<T>,\n send: Send,\n okFalse: (error: string) => NappletMessage,\n onValue: (value: T) => NappletMessage,\n): void {\n let pending: Promise<T>;\n try {\n pending = Promise.resolve(call());\n } catch (err) {\n send(okFalse(errorMessage(err, 'common request failed')));\n return;\n }\n pending\n .then((value) => send(onValue(value)))\n .catch((err) => send(okFalse(errorMessage(err, 'common request failed'))));\n}\n\n/**\n * Create the NAP-COMMON reference service.\n *\n * @param options - Host profile/social hooks for common.* requests.\n * @returns A runtime service handler for the `common` domain.\n */\nexport function createCommonService(options: CommonServiceOptions = {}): ServiceHandler {\n return {\n descriptor: COMMON_DESCRIPTOR,\n handleMessage(windowId: string, message: NappletMessage, send: Send): void {\n const id = (message as NappletMessage & { id?: string }).id ?? '';\n const context = { windowId };\n\n if (message.type === 'common.encodeNip19') {\n const input = (message as CommonEncodeNip19Message).input;\n const result = input ? encodeNip19(input) : { ok: false, error: 'invalid input' };\n send({ type: 'common.encodeNip19.result', id, ...result } as NappletMessage);\n return;\n }\n\n if (message.type === 'common.decodeNip19') {\n const value = (message as CommonDecodeNip19Message).value;\n const result = typeof value === 'string' ? decodeNip19Value(value) : { ok: false, error: 'invalid value' };\n send({ type: 'common.decodeNip19.result', id, ...result } as NappletMessage);\n return;\n }\n\n if (message.type === 'common.getProfile') {\n const target = (message as CommonGetProfileMessage).target;\n if (!options.getProfile) {\n send({ type: 'common.getProfile.result', id, ok: false, pubkey: target ?? '', error: 'profile lookup unavailable' } as NappletMessage);\n return;\n }\n settle(\n () => options.getProfile!(target, context),\n send,\n (error) => ({ type: 'common.getProfile.result', id, ok: false, pubkey: target ?? '', error } as NappletMessage),\n (result) => ({ type: 'common.getProfile.result', id, ...result } as NappletMessage),\n );\n return;\n }\n\n if (message.type === 'common.follows') {\n if (!options.follows) {\n send({ type: 'common.follows.result', id, ok: false, pubkeys: [], error: 'follows lookup unavailable' } as NappletMessage);\n return;\n }\n settle(\n () => options.follows!(context),\n send,\n (error) => ({ type: 'common.follows.result', id, ok: false, pubkeys: [], error } as NappletMessage),\n (result) => ({ type: 'common.follows.result', id, ...result } as NappletMessage),\n );\n return;\n }\n\n if (message.type === 'common.follow') {\n const pubkeys = (message as CommonFollowMessage).pubkeys ?? [];\n settleAction(options.follow, pubkeys, context, send, 'common.follow.result', id, 'follow unavailable');\n return;\n }\n\n if (message.type === 'common.unfollow') {\n const pubkeys = (message as CommonUnfollowMessage).pubkeys ?? [];\n settleAction(options.unfollow, pubkeys, context, send, 'common.unfollow.result', id, 'unfollow unavailable');\n return;\n }\n\n if (message.type === 'common.react') {\n const m = message as CommonReactMessage;\n if (!options.react) {\n send({ type: 'common.react.result', id, ok: false, error: 'react unavailable' } as NappletMessage);\n return;\n }\n settle(\n () => options.react!(m.targetEventId, m.reaction, m.customEmojiHref, context),\n send,\n (error) => ({ type: 'common.react.result', id, ok: false, error } as NappletMessage),\n (result) => ({ type: 'common.react.result', id, ...result } as NappletMessage),\n );\n return;\n }\n\n if (message.type === 'common.report') {\n const m = message as CommonReportMessage;\n if (!options.report) {\n send({ type: 'common.report.result', id, ok: false, error: 'report unavailable' } as NappletMessage);\n return;\n }\n settle(\n () => options.report!(m.target, m.reason, m.text, context),\n send,\n (error) => ({ type: 'common.report.result', id, ok: false, error } as NappletMessage),\n (result) => ({ type: 'common.report.result', id, ...result } as NappletMessage),\n );\n return;\n }\n\n send({\n type: `${message.type}.error`,\n id,\n error: `Unknown common method: ${message.type}`,\n } as NappletMessage);\n },\n onWindowDestroyed(_windowId: string): void {\n /* no per-window state */\n },\n };\n}\n\nfunction settleAction(\n action: ((pubkeys: string[], context: CommonServiceContext) => CommonActionResult | Promise<CommonActionResult>) | undefined,\n pubkeys: string[],\n context: CommonServiceContext,\n send: Send,\n resultType: string,\n id: string,\n unavailable: string,\n): void {\n if (!action) {\n send({ type: resultType, id, ok: false, error: unavailable } as NappletMessage);\n return;\n }\n settle(\n () => action(pubkeys, context),\n send,\n (error) => ({ type: resultType, id, ok: false, error } as NappletMessage),\n (result) => ({ type: resultType, id, ...result } as NappletMessage),\n );\n}\n"],"mappings":"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","names":["notify","deliver","relays","generateId","toErrorMessage","toErrorMessage","toErrorMessage","settle","toErrorMessage","errorMessage","settle","errorMessage","settle","unsupported","errorMessage","settle","unsupported","errorMessage","settle"]}
|
|
1
|
+
{"version":3,"sources":["../src/audio-service.ts","../src/notification-service.ts","../src/identity-service.ts","../src/relay-pool-service.ts","../src/cache-service.ts","../src/coordinated-relay.ts","../src/keys-service.ts","../src/browser-media-bridge.ts","../src/media-service.ts","../src/notify-service.ts","../src/theme-service.ts","../src/config-service.ts","../src/resource-service.ts","../src/outbox-service.ts","../src/relay-pool-outbox-router.ts","../src/upload-service.ts","../src/http-uploader.ts","../src/intent-service.ts","../src/catalog-intent-resolver.ts","../src/manifest-intent-catalog.ts","../src/cvm-service.ts","../src/link-service.ts","../src/lists-service.ts","../src/serial-service.ts","../src/ble-service.ts","../src/webrtc-service.ts","../src/common-service.ts"],"sourcesContent":["/**\n * audio-service.ts — Audio source registry as a ServiceHandler.\n *\n * Tracks which napplet windows are producing audio. Shell hosts wire this\n * into the runtime via registerService('audio', createAudioService(opts)).\n * Browser-agnostic — no DOM, no window, no postMessage.\n */\n\nimport type { NappletMessage } from '@napplet/core';\n// DRIFT-CORE-06 — Phase 11-deviation: ServiceDescriptor dropped from @napplet/core\n// v0.2.0+ (napplet phase-81). Re-exported from @kehto/runtime (canonical home after Phase 24 DRIFT-01).\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\nimport type { AudioSource, AudioServiceOptions } from './types.js';\n\n/** Audio service version — follows semver. */\nconst AUDIO_SERVICE_VERSION = '1.0.0';\n\n/**\n * Create an audio service handler.\n *\n * The audio service is a state registry that tracks active audio sources\n * per napplet window. Napplets announce audio state via `audio:*` topic\n * events; the service tracks sources and can relay mute commands back.\n *\n * @param options - Optional configuration (onChange callback for UI updates)\n * @returns A ServiceHandler to register with the runtime\n *\n * @example\n * ```ts\n * import { createAudioService } from '@kehto/services';\n *\n * const audio = createAudioService({\n * onChange: (sources) => {\n * // Update UI with current audio sources\n * for (const [windowId, source] of sources) {\n * console.log(`${source.title} (${source.muted ? 'muted' : 'playing'})`);\n * }\n * },\n * });\n *\n * runtime.registerService('audio', audio);\n * ```\n */\nexport function createAudioService(options?: AudioServiceOptions): ServiceHandler {\n const sources = new Map<string, AudioSource>();\n const onChange = options?.onChange;\n\n function notify(): void {\n onChange?.(new Map(sources));\n }\n\n const descriptor: ServiceDescriptor = {\n name: 'audio',\n version: AUDIO_SERVICE_VERSION,\n description: 'Audio source registry — tracks active audio sources per napplet window',\n };\n\n return {\n descriptor,\n\n handleMessage(windowId: string, message: NappletMessage, send: (msg: NappletMessage) => void): void {\n if (message.type !== 'inc.emit') return;\n const incMessage = message as NappletMessage & { topic?: unknown; payload?: unknown };\n const topic = typeof incMessage.topic === 'string' ? incMessage.topic : undefined;\n if (!topic?.startsWith('audio:')) return;\n\n const action = topic.slice(6); // 'audio:'.length === 6\n const payload = incMessage.payload && typeof incMessage.payload === 'object'\n ? incMessage.payload as Record<string, unknown>\n : {};\n\n switch (action) {\n case 'register': {\n const nappletClass = typeof payload.nappletClass === 'string' ? payload.nappletClass : '';\n const title = typeof payload.title === 'string' ? payload.title : '';\n sources.set(windowId, { windowId, nappletClass, title, muted: false });\n notify();\n break;\n }\n\n case 'unregister': {\n if (sources.delete(windowId)) {\n notify();\n }\n break;\n }\n\n case 'state-changed': {\n const source = sources.get(windowId);\n if (!source) return;\n if (typeof payload.title === 'string') {\n source.title = payload.title;\n }\n notify();\n break;\n }\n\n case 'mute': {\n const targetWindowId = typeof payload.windowId === 'string'\n ? payload.windowId\n : windowId;\n const muted = payload.muted === true;\n\n const source = sources.get(targetWindowId);\n if (source) {\n source.muted = muted;\n notify();\n }\n\n send({ type: 'inc.event', topic: 'napplet:audio-muted', payload: { muted } } as NappletMessage);\n break;\n }\n\n default:\n // Unknown audio action — ignore\n break;\n }\n },\n\n onWindowDestroyed(windowId: string): void {\n if (sources.delete(windowId)) {\n notify();\n }\n },\n };\n}\n","\nimport type { NappletMessage } from '@napplet/core';\n// DRIFT-CORE-06 — Phase 11-deviation: ServiceDescriptor dropped from @napplet/core\n// v0.2.0+ (napplet phase-81). Re-exported from @kehto/runtime (canonical home after Phase 24 DRIFT-01).\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\nimport type { Notification, NotificationServiceOptions } from './types.js';\n\n/** Notification service version — follows semver. */\nconst NOTIFICATION_SERVICE_VERSION = '1.0.0';\n\n/** Default maximum notifications per window. */\nconst DEFAULT_MAX_PER_WINDOW = 100;\n\n/** Counter for generating unique notification IDs. */\nlet idCounter = 0;\n\n/**\n * Generate a unique notification ID.\n */\nfunction generateId(): string {\n idCounter++;\n return `notif-${Date.now()}-${idCounter}`;\n}\n\ninterface NotificationStore {\n notifications: Map<string, Notification[]>;\n onChange?: (notifications: Notification[]) => void;\n maxPerWindow: number;\n}\n\nfunction getAllNotifications(store: NotificationStore): Notification[] {\n const all: Notification[] = [];\n for (const windowNotifs of store.notifications.values()) {\n all.push(...windowNotifs);\n }\n return all;\n}\n\nfunction notify(store: NotificationStore): void {\n store.onChange?.(getAllNotifications(store));\n}\n\nfunction getWindowNotifications(store: NotificationStore, windowId: string): Notification[] {\n let list = store.notifications.get(windowId);\n if (!list) {\n list = [];\n store.notifications.set(windowId, list);\n }\n return list;\n}\n\nfunction enforceLimit(store: NotificationStore, list: Notification[]): void {\n while (list.length > store.maxPerWindow) {\n list.shift();\n }\n}\n\nfunction findById(store: NotificationStore, id: string): [string, Notification, number] | undefined {\n for (const [windowId, list] of store.notifications) {\n const index = list.findIndex((n) => n.id === id);\n if (index !== -1) {\n return [windowId, list[index], index];\n }\n }\n return undefined;\n}\n\nfunction createNotification(\n store: NotificationStore,\n windowId: string,\n title: string,\n body: string,\n): Notification {\n const notification: Notification = {\n id: generateId(),\n windowId,\n title,\n body,\n read: false,\n createdAt: Math.floor(Date.now() / 1000),\n };\n const list = getWindowNotifications(store, windowId);\n list.push(notification);\n enforceLimit(store, list);\n notify(store);\n return notification;\n}\n\nfunction dismissNotification(store: NotificationStore, id: string): void {\n const found = findById(store, id);\n if (!found) return;\n const [foundWindowId, , index] = found;\n const list = store.notifications.get(foundWindowId);\n if (!list) return;\n list.splice(index, 1);\n if (list.length === 0) store.notifications.delete(foundWindowId);\n notify(store);\n}\n\nfunction markNotificationRead(store: NotificationStore, id: string): void {\n const found = findById(store, id);\n if (!found) return;\n const [, notification] = found;\n if (!notification.read) {\n notification.read = true;\n notify(store);\n }\n}\n\nfunction handleNotifyEnvelope(\n store: NotificationStore,\n windowId: string,\n action: string,\n msg: NappletMessage & Record<string, unknown>,\n send: (msg: NappletMessage) => void,\n): void {\n switch (action) {\n case 'create': {\n const title = typeof msg.title === 'string' ? msg.title : '';\n const body = typeof msg.body === 'string' ? msg.body : '';\n const notification = createNotification(store, windowId, title, body);\n send({ type: 'notify.created', id: notification.id } as NappletMessage);\n break;\n }\n\n case 'dismiss': {\n const notifId = typeof msg.notificationId === 'string' ? msg.notificationId : '';\n if (notifId) dismissNotification(store, notifId);\n break;\n }\n\n case 'read': {\n const notifId = typeof msg.notificationId === 'string' ? msg.notificationId : '';\n if (notifId) markNotificationRead(store, notifId);\n break;\n }\n\n case 'list': {\n const windowNotifs = store.notifications.get(windowId) ?? [];\n send({ type: 'notify.listed', notifications: windowNotifs } as NappletMessage);\n break;\n }\n\n default:\n break;\n }\n}\n\nfunction handleIncNotification(\n store: NotificationStore,\n windowId: string,\n action: string,\n payload: Record<string, unknown>,\n send: (msg: NappletMessage) => void,\n): void {\n switch (action) {\n case 'create': {\n const title = typeof payload.title === 'string' ? payload.title : '';\n const body = typeof payload.body === 'string' ? payload.body : '';\n const notification = createNotification(store, windowId, title, body);\n send({ type: 'inc.event', topic: 'notifications:created', payload: { id: notification.id } } as NappletMessage);\n break;\n }\n\n case 'dismiss': {\n const id = typeof payload.id === 'string' ? payload.id : '';\n if (id) dismissNotification(store, id);\n break;\n }\n\n case 'read': {\n const id = typeof payload.id === 'string' ? payload.id : '';\n if (id) markNotificationRead(store, id);\n break;\n }\n\n case 'list': {\n const windowNotifs = store.notifications.get(windowId) ?? [];\n send({ type: 'inc.event', topic: 'notifications:listed', payload: { notifications: windowNotifs } } as NappletMessage);\n break;\n }\n\n default:\n break;\n }\n}\n\n/**\n * Create a notification service handler.\n *\n * The notification service is a state registry that tracks notifications\n * per napplet window. Napplets create and manage notifications via\n * `notifications:*` topic events; the shell host controls presentation\n * via the onChange callback.\n *\n * @param options - Optional configuration (onChange callback, maxPerWindow limit)\n * @returns A ServiceHandler to register with the runtime\n *\n * @example\n * ```ts\n * import { createNotificationService } from '@kehto/services';\n *\n * const notifications = createNotificationService({\n * onChange: (list) => {\n * const unread = list.filter(n => !n.read);\n * updateBadge(unread.length);\n * },\n * maxPerWindow: 50,\n * });\n *\n * runtime.registerService('notifications', notifications);\n * ```\n */\nexport function createNotificationService(options?: NotificationServiceOptions): ServiceHandler {\n const store: NotificationStore = {\n notifications: new Map<string, Notification[]>(),\n onChange: options?.onChange,\n maxPerWindow: options?.maxPerWindow ?? DEFAULT_MAX_PER_WINDOW,\n };\n\n const descriptor: ServiceDescriptor = {\n name: 'notifications',\n version: NOTIFICATION_SERVICE_VERSION,\n description: 'Notification state registry — tracks notifications per napplet window',\n };\n\n return {\n descriptor,\n\n handleMessage(windowId: string, message: NappletMessage, send: (msg: NappletMessage) => void): void {\n const msg = message as NappletMessage & Record<string, unknown>;\n\n if (message.type.startsWith('notify.')) {\n handleNotifyEnvelope(store, windowId, message.type.slice(7), msg, send);\n return;\n }\n\n if (message.type !== 'inc.emit') return;\n const topic = msg.topic as string | undefined;\n if (!topic?.startsWith('notifications:')) return;\n\n const payload = ((msg.payload ?? {}) as Record<string, unknown>);\n handleIncNotification(store, windowId, topic.slice(14), payload, send);\n },\n\n onWindowDestroyed(windowId: string): void {\n if (store.notifications.delete(windowId)) {\n notify(store);\n }\n },\n };\n}\n","/**\n * identity-service.ts — NIP-5D identity nap reference service.\n *\n * MIGRATION from signer-service (v1.1 -> v1.2):\n * - signer.getPublicKey -> identity.getPublicKey (same shell state)\n * - signer.getRelays -> identity.getRelays (same shell state)\n * - signer.signEvent -> DELETED (no napplet-visible path; shell signs\n * internally inside relay.publish)\n * - signer.nip04.encrypt/decrypt -> DELETED\n * - signer.nip44.encrypt/decrypt -> DELETED (shell encrypts internally\n * inside relay.publishEncrypted)\n *\n * Identity is strictly read-only per NAP-IDENTITY: napplets learn *about* the\n * user but cannot act *as* the user — no signing, encryption, or decryption.\n * (An `identity.decrypt` capability was briefly added in v1.8 and removed as a\n * spec violation; decryption belongs to the runtime, inline over the wire.)\n *\n * Handles 9 identity.* request types from @napplet/nap/identity. getPublicKey\n * and getRelays return real values sourced from hooks.auth.getSigner(); the\n * remaining read-only queries can be backed by optional host providers. When a\n * provider is absent, the service keeps returning spec-shaped empty results.\n */\n\nimport type { NappletMessage } from '@napplet/core';\nimport type { ServiceHandler, Signer } from '@kehto/runtime';\nimport type {\n IdentityGetBadgesMessage,\n IdentityGetBlockedMessage,\n IdentityGetFollowsMessage,\n IdentityGetListMessage,\n IdentityGetMutesMessage,\n IdentityGetProfileMessage,\n IdentityGetPublicKeyResultMessage,\n IdentityGetRelaysResultMessage,\n IdentityGetProfileResultMessage,\n IdentityGetFollowsResultMessage,\n IdentityGetListResultMessage,\n IdentityGetZapsResultMessage,\n IdentityGetZapsMessage,\n IdentityGetMutesResultMessage,\n IdentityGetBlockedResultMessage,\n IdentityGetBadgesResultMessage,\n RelayPermission,\n} from '@napplet/nap/identity/types';\n\n/** Identity service version — follows semver. */\nconst IDENTITY_SERVICE_VERSION = '1.0.0';\n\n/** A value that may be returned synchronously or through a Promise. */\nexport type MaybePromise<T> = T | Promise<T>;\n\ntype IdentityProviderResult = NappletMessage & { error?: string };\ntype SendIdentityMessage = (msg: NappletMessage) => void;\n\nfunction sendProviderError<T extends IdentityProviderResult>(\n send: SendIdentityMessage,\n result: T,\n fallback: string,\n err: unknown,\n): void {\n send({\n ...result,\n error: (err as Error)?.message ?? fallback,\n });\n}\n\nasync function getCurrentPubkey(options: IdentityServiceOptions): Promise<string> {\n const currentSigner = options.getSigner();\n if (!currentSigner?.getPublicKey) return '';\n try {\n return (await currentSigner.getPublicKey()) ?? '';\n } catch {\n return '';\n }\n}\n\nfunction sendOptionalProviderResult<T extends IdentityProviderResult>(\n options: IdentityServiceOptions,\n send: SendIdentityMessage,\n fallbackResult: T,\n errorFallback: string,\n buildResult?: (pubkey: string) => MaybePromise<T>,\n): void {\n if (!buildResult) {\n send(fallbackResult);\n return;\n }\n\n Promise.resolve(getCurrentPubkey(options))\n .then((pubkey) => buildResult(pubkey))\n .then((result) => send(result))\n .catch((err: unknown) => sendProviderError(send, fallbackResult, errorFallback, err));\n}\n\n/**\n * Options for creating the identity service.\n *\n * @example\n * ```ts\n * const identityService = createIdentityService({\n * getSigner: () => window.nostr ?? null,\n * });\n * runtime.registerService('identity', identityService);\n * ```\n */\nexport interface IdentityServiceOptions {\n /**\n * Return the NIP-07-compatible signer (or null) used to resolve\n * identity.getPublicKey / identity.getRelays. Called on every request —\n * availability can change dynamically.\n */\n getSigner: () => Signer | null;\n\n /**\n * Optional host-backed profile lookup for the current user.\n *\n * Kehto does not query relays itself; hosts that already maintain kind-0\n * metadata can provide it here without replacing the whole identity service.\n *\n * @param pubkey - Current signer pubkey, or \"\" when no signer is connected.\n * @param message - Original identity.getProfile request envelope.\n * @returns Profile metadata, or null when unavailable.\n */\n getProfile?: (\n pubkey: string,\n message: IdentityGetProfileMessage,\n ) => MaybePromise<IdentityGetProfileResultMessage['profile']>;\n\n /**\n * Optional host-backed follow-list lookup for the current user.\n *\n * @param pubkey - Current signer pubkey, or \"\" when no signer is connected.\n * @param message - Original identity.getFollows request envelope.\n * @returns Hex-encoded followed pubkeys.\n */\n getFollows?: (\n pubkey: string,\n message: IdentityGetFollowsMessage,\n ) => MaybePromise<IdentityGetFollowsResultMessage['pubkeys']>;\n\n /**\n * Optional host-backed categorized-list lookup for the current user.\n *\n * @param listType - Requested list category from the wire envelope.\n * @param pubkey - Current signer pubkey, or \"\" when no signer is connected.\n * @param message - Original identity.getList request envelope.\n * @returns List entries.\n */\n getList?: (\n listType: string,\n pubkey: string,\n message: IdentityGetListMessage,\n ) => MaybePromise<IdentityGetListResultMessage['entries']>;\n\n /**\n * Optional host-backed zap receipt lookup for the current user.\n *\n * @param pubkey - Current signer pubkey, or \"\" when no signer is connected.\n * @param message - Original identity.getZaps request envelope.\n * @returns Zap receipts.\n */\n getZaps?: (\n pubkey: string,\n message: IdentityGetZapsMessage,\n ) => MaybePromise<IdentityGetZapsResultMessage['zaps']>;\n\n /**\n * Optional host-backed mute-list lookup for the current user.\n *\n * @param pubkey - Current signer pubkey, or \"\" when no signer is connected.\n * @param message - Original identity.getMutes request envelope.\n * @returns Hex-encoded muted pubkeys.\n */\n getMutes?: (\n pubkey: string,\n message: IdentityGetMutesMessage,\n ) => MaybePromise<IdentityGetMutesResultMessage['pubkeys']>;\n\n /**\n * Optional host-backed block-list lookup for the current user.\n *\n * @param pubkey - Current signer pubkey, or \"\" when no signer is connected.\n * @param message - Original identity.getBlocked request envelope.\n * @returns Hex-encoded blocked pubkeys.\n */\n getBlocked?: (\n pubkey: string,\n message: IdentityGetBlockedMessage,\n ) => MaybePromise<IdentityGetBlockedResultMessage['pubkeys']>;\n\n /**\n * Optional host-backed badge lookup for the current user.\n *\n * @param pubkey - Current signer pubkey, or \"\" when no signer is connected.\n * @param message - Original identity.getBadges request envelope.\n * @returns Badges awarded to the user.\n */\n getBadges?: (\n pubkey: string,\n message: IdentityGetBadgesMessage,\n ) => MaybePromise<IdentityGetBadgesResultMessage['badges']>;\n}\n\nfunction sendIdentityError(\n send: SendIdentityMessage,\n id: string,\n typeBase: string,\n error: string,\n): void {\n send({ type: `${typeBase}.error`, id, error } as NappletMessage);\n}\n\nfunction sendSignerError(\n send: SendIdentityMessage,\n id: string,\n typeBase: string,\n fallback: string,\n err: unknown,\n): void {\n sendIdentityError(send, id, typeBase, (err as Error)?.message ?? fallback);\n}\n\nfunction handleGetPublicKey(options: IdentityServiceOptions, id: string, send: SendIdentityMessage): void {\n const signer = options.getSigner();\n if (!signer) {\n const result: IdentityGetPublicKeyResultMessage = {\n type: 'identity.getPublicKey.result',\n id,\n pubkey: '',\n };\n send(result);\n return;\n }\n\n Promise.resolve(signer.getPublicKey?.())\n .then((pubkey) => {\n const result: IdentityGetPublicKeyResultMessage = {\n type: 'identity.getPublicKey.result',\n id,\n pubkey: (pubkey as string) ?? '',\n };\n send(result);\n })\n .catch((err: unknown) => sendSignerError(send, id, 'identity.getPublicKey', 'getPublicKey failed', err));\n}\n\nfunction handleGetRelays(options: IdentityServiceOptions, id: string, send: SendIdentityMessage): void {\n const signer = options.getSigner();\n if (!signer) {\n sendIdentityError(send, id, 'identity.getRelays', 'no signer configured');\n return;\n }\n\n Promise.resolve(signer.getRelays?.() ?? {})\n .then((relays) => {\n const result: IdentityGetRelaysResultMessage = {\n type: 'identity.getRelays.result',\n id,\n relays: relays as Record<string, RelayPermission>,\n };\n send(result);\n })\n .catch((err: unknown) => sendSignerError(send, id, 'identity.getRelays', 'getRelays failed', err));\n}\n\nfunction handleReadProvider(\n options: IdentityServiceOptions,\n id: string,\n message: NappletMessage,\n send: SendIdentityMessage,\n): boolean {\n switch (message.type) {\n case 'identity.getProfile':\n sendOptionalProviderResult(\n options,\n send,\n { type: 'identity.getProfile.result', id, profile: null },\n 'getProfile failed',\n options.getProfile\n ? async (pubkey): Promise<IdentityGetProfileResultMessage> => ({\n type: 'identity.getProfile.result',\n id,\n profile: await options.getProfile!(pubkey, message as IdentityGetProfileMessage),\n })\n : undefined,\n );\n return true;\n\n case 'identity.getFollows':\n sendOptionalProviderResult(\n options,\n send,\n { type: 'identity.getFollows.result', id, pubkeys: [] },\n 'getFollows failed',\n options.getFollows\n ? async (pubkey): Promise<IdentityGetFollowsResultMessage> => ({\n type: 'identity.getFollows.result',\n id,\n pubkeys: await options.getFollows!(pubkey, message as IdentityGetFollowsMessage),\n })\n : undefined,\n );\n return true;\n\n case 'identity.getList':\n handleGetList(options, id, message as IdentityGetListMessage, send);\n return true;\n\n case 'identity.getZaps':\n sendOptionalProviderResult(\n options,\n send,\n { type: 'identity.getZaps.result', id, zaps: [] },\n 'getZaps failed',\n options.getZaps\n ? async (pubkey): Promise<IdentityGetZapsResultMessage> => ({\n type: 'identity.getZaps.result',\n id,\n zaps: await options.getZaps!(pubkey, message as IdentityGetZapsMessage),\n })\n : undefined,\n );\n return true;\n\n case 'identity.getMutes':\n sendOptionalProviderResult(\n options,\n send,\n { type: 'identity.getMutes.result', id, pubkeys: [] },\n 'getMutes failed',\n options.getMutes\n ? async (pubkey): Promise<IdentityGetMutesResultMessage> => ({\n type: 'identity.getMutes.result',\n id,\n pubkeys: await options.getMutes!(pubkey, message as IdentityGetMutesMessage),\n })\n : undefined,\n );\n return true;\n\n case 'identity.getBlocked':\n sendOptionalProviderResult(\n options,\n send,\n { type: 'identity.getBlocked.result', id, pubkeys: [] },\n 'getBlocked failed',\n options.getBlocked\n ? async (pubkey): Promise<IdentityGetBlockedResultMessage> => ({\n type: 'identity.getBlocked.result',\n id,\n pubkeys: await options.getBlocked!(pubkey, message as IdentityGetBlockedMessage),\n })\n : undefined,\n );\n return true;\n\n case 'identity.getBadges':\n sendOptionalProviderResult(\n options,\n send,\n { type: 'identity.getBadges.result', id, badges: [] },\n 'getBadges failed',\n options.getBadges\n ? async (pubkey): Promise<IdentityGetBadgesResultMessage> => ({\n type: 'identity.getBadges.result',\n id,\n badges: await options.getBadges!(pubkey, message as IdentityGetBadgesMessage),\n })\n : undefined,\n );\n return true;\n\n default:\n return false;\n }\n}\n\nfunction handleGetList(\n options: IdentityServiceOptions,\n id: string,\n message: IdentityGetListMessage,\n send: SendIdentityMessage,\n): void {\n sendOptionalProviderResult(\n options,\n send,\n { type: 'identity.getList.result', id, entries: [] },\n 'getList failed',\n options.getList\n ? async (pubkey): Promise<IdentityGetListResultMessage> => ({\n type: 'identity.getList.result',\n id,\n entries: await options.getList!(message.listType, pubkey, message),\n })\n : undefined,\n );\n}\n\nfunction handleIdentityServiceMessage(\n options: IdentityServiceOptions,\n message: NappletMessage,\n send: SendIdentityMessage,\n): void {\n const id = (message as NappletMessage & { id?: string }).id ?? '';\n\n switch (message.type) {\n case 'identity.getPublicKey':\n // Per NIP-5D, no signer resolves to the empty-pubkey sentinel, not an error.\n handleGetPublicKey(options, id, send);\n return;\n\n case 'identity.getRelays':\n handleGetRelays(options, id, send);\n return;\n\n default:\n if (!handleReadProvider(options, id, message, send)) {\n sendIdentityError(send, id, message.type, `Unknown identity method: ${message.type}`);\n }\n }\n}\n\n/**\n * Create an identity service that handles NIP-5D identity.* envelope messages.\n *\n * Supports the 9 read-only identity.* request types from @napplet/nap/identity.\n * The two nostr-info queries (getPublicKey, getRelays) resolve through the\n * caller-supplied signer; the remaining read-only queries resolve through\n * optional host providers or return default/empty payloads with spec-correct\n * envelope shapes so napplets always receive a result envelope.\n *\n * @param options - Identity service configuration (getSigner)\n * @returns A ServiceHandler ready for runtime.registerService('identity', handler)\n *\n * @example\n * ```ts\n * import { createIdentityService } from '@kehto/services';\n *\n * const identity = createIdentityService({\n * getSigner: () => mySignerAdapter,\n * });\n * runtime.registerService('identity', identity);\n * ```\n */\nexport function createIdentityService(options: IdentityServiceOptions): ServiceHandler {\n return {\n descriptor: {\n name: 'identity',\n version: IDENTITY_SERVICE_VERSION,\n description: 'NIP-5D identity NAP reference handler (9 read-only identity queries)',\n },\n\n handleMessage(\n _windowId: string,\n message: NappletMessage,\n send: (msg: NappletMessage) => void,\n ): void {\n handleIdentityServiceMessage(options, message, send);\n },\n\n // Identity service has no per-window state to clean up.\n onWindowDestroyed(_windowId: string): void {\n /* no-op */\n },\n };\n}\n","/**\n * relay-pool-service.ts — Relay pool as a ServiceHandler.\n *\n * Wraps an existing relay pool implementation (subscribe, publish,\n * selectRelayTier, isAvailable) as a ServiceHandler that receives\n * relay NAP envelope messages and manages subscription lifecycle.\n *\n * Handles: relay.subscribe, relay.close, relay.publish, relay.publishEncrypted.\n *\n * Note on `relay.publishEncrypted`: the canonical napplet→shell path routes\n * through @kehto/runtime handleRelayMessage, which performs shell-internal\n * NIP-44/NIP-04 encryption via the signer, then synthesizes a relay.publish\n * envelope handed to this service. The publishEncrypted branch here is a\n * fallback for alternate wirings; by the time the service sees the\n * envelope, content MUST already be ciphertext (the service never encrypts\n * or decrypts).\n */\n\nimport type { NostrEvent, NostrFilter, NappletMessage } from '@napplet/core';\nimport type { ServiceHandler } from '@kehto/runtime';\n\n// Timer globals available in all JS runtimes\ndeclare function setTimeout(callback: () => void, ms: number): unknown;\ndeclare function clearTimeout(id: unknown): void;\n\n/** EOSE fallback timeout in milliseconds. */\nconst EOSE_FALLBACK_MS = 15_000;\n\n/**\n * Options for creating a relay pool service.\n *\n * @example\n * ```ts\n * const relayPoolService = createRelayPoolService({\n * subscribe: (filters, cb, urls) => myPool.subscribe(filters, cb, urls),\n * publish: (event) => myPool.publish(event),\n * selectRelayTier: (filters) => myPool.selectRelays(filters),\n * isAvailable: () => myPool.connected,\n * });\n * ```\n */\nexport interface RelayPoolServiceOptions {\n /**\n * Subscribe to events matching filters. Returns handle with unsubscribe().\n *\n * @param filters - NIP-01 filter objects\n * @param callback - Receives matching events or 'EOSE'\n * @param relayUrls - Optional relay URL hints\n * @returns Handle to cancel the subscription\n */\n subscribe(\n filters: NostrFilter[],\n callback: (item: NostrEvent | 'EOSE') => void,\n relayUrls?: string[],\n ): { unsubscribe(): void };\n\n /**\n * Publish an event to relays.\n *\n * @param event - The event to publish\n */\n publish(event: NostrEvent): void;\n\n /**\n * Select relay URLs appropriate for the given filters.\n *\n * @param filters - NIP-01 filter objects\n * @returns Array of relay URLs\n */\n selectRelayTier(filters: NostrFilter[]): string[];\n\n /**\n * Whether the relay pool is available and connected.\n *\n * @returns true if the relay pool can handle requests\n */\n isAvailable(): boolean;\n}\n\n/** Internal subscription tracking entry. */\ninterface TrackedSubscription {\n handle: { unsubscribe(): void };\n eoseTimer: unknown;\n}\n\ntype RelayServiceMessage = NappletMessage & {\n subId?: unknown;\n filters?: unknown;\n event?: unknown;\n relay?: unknown;\n};\n\n/**\n * Create a relay pool service that wraps an existing relay pool\n * implementation as a ServiceHandler.\n *\n * Handles relay.subscribe, relay.close, and relay.publish envelopes.\n * Tracks subscriptions per windowId:subId for lifecycle management.\n * Sets a 15-second EOSE fallback timer on each subscription.\n *\n * @param options - Relay pool implementation to wrap\n * @returns A ServiceHandler ready for runtime.registerService('relay', handler)\n *\n * @example\n * ```ts\n * import { createRelayPoolService } from '@kehto/services';\n *\n * const pool = createRelayPoolService({\n * subscribe: (f, cb, urls) => applesauce.subscribe(f, cb, urls),\n * publish: (e) => applesauce.publish(e),\n * selectRelayTier: (f) => applesauce.getRelays(f),\n * isAvailable: () => applesauce.connected,\n * });\n * runtime.registerService('relay', pool);\n * ```\n */\nexport function createRelayPoolService(options: RelayPoolServiceOptions): ServiceHandler {\n const tracked = new Map<string, TrackedSubscription>();\n\n return {\n descriptor: {\n name: 'relay-pool',\n version: '1.0.0',\n description: 'Relay pool subscription and publishing',\n },\n\n handleMessage(windowId: string, message: NappletMessage, send: (msg: NappletMessage) => void): void {\n const relayMessage = message as RelayServiceMessage;\n if (message.type === 'relay.subscribe') {\n const subId = relayMessage.subId;\n if (typeof subId !== 'string') return;\n const filters = Array.isArray(relayMessage.filters)\n ? relayMessage.filters as NostrFilter[]\n : [];\n const subKey = `${windowId}:${subId}`;\n\n // Cancel existing subscription for this key if any\n const existing = tracked.get(subKey);\n if (existing) {\n existing.handle.unsubscribe();\n clearTimeout(existing.eoseTimer);\n tracked.delete(subKey);\n }\n\n if (!options.isAvailable()) {\n send({ type: 'relay.eose', subId } as NappletMessage);\n return;\n }\n\n const relayHint = typeof relayMessage.relay === 'string' && relayMessage.relay.length > 0\n ? relayMessage.relay\n : undefined;\n const relayUrls = relayHint ? [relayHint] : options.selectRelayTier(filters);\n let eoseSent = false;\n\n const eoseTimer = setTimeout(() => {\n if (!eoseSent) {\n eoseSent = true;\n send({ type: 'relay.eose', subId } as NappletMessage);\n }\n }, EOSE_FALLBACK_MS);\n\n const handle = options.subscribe(filters, (item) => {\n if (item === 'EOSE') {\n clearTimeout(eoseTimer);\n if (!eoseSent) {\n eoseSent = true;\n send({ type: 'relay.eose', subId } as NappletMessage);\n }\n return;\n }\n send({ type: 'relay.event', subId, event: item } as NappletMessage);\n }, relayUrls);\n\n tracked.set(subKey, { handle, eoseTimer });\n return;\n }\n\n if (message.type === 'relay.close') {\n const subId = relayMessage.subId;\n if (typeof subId !== 'string') return;\n const subKey = `${windowId}:${subId}`;\n const entry = tracked.get(subKey);\n if (entry) {\n entry.handle.unsubscribe();\n clearTimeout(entry.eoseTimer);\n tracked.delete(subKey);\n }\n return;\n }\n\n if (message.type === 'relay.publish') {\n const event = relayMessage.event as NostrEvent | undefined;\n if (event && typeof event === 'object' && options.isAvailable()) {\n options.publish(event);\n }\n return;\n }\n\n if (message.type === 'relay.publishEncrypted') {\n const event = relayMessage.event as NostrEvent | undefined;\n if (event && typeof event === 'object' && options.isAvailable()) {\n options.publish(event);\n }\n return;\n }\n },\n\n onWindowDestroyed(windowId: string): void {\n const prefix = `${windowId}:`;\n for (const [key, entry] of tracked) {\n if (key.startsWith(prefix)) {\n entry.handle.unsubscribe();\n clearTimeout(entry.eoseTimer);\n tracked.delete(key);\n }\n }\n },\n };\n}\n","\nimport type { NostrEvent, NostrFilter, NappletMessage } from '@napplet/core';\nimport type { ServiceHandler } from '@kehto/runtime';\n\ntype RelayServiceMessage = NappletMessage & {\n subId?: unknown;\n filters?: unknown;\n event?: unknown;\n};\n\n/**\n * Options for creating a cache service.\n *\n * @example\n * ```ts\n * const cacheService = createCacheService({\n * query: (filters) => myIndexedDB.query(filters),\n * store: (event) => myIndexedDB.store(event),\n * isAvailable: () => true,\n * });\n * ```\n */\nexport interface CacheServiceOptions {\n /**\n * Query cached events matching the given filters.\n *\n * @param filters - NIP-01 filter objects\n * @returns Promise resolving to matching cached events\n */\n query(filters: NostrFilter[]): Promise<NostrEvent[]>;\n\n /**\n * Store an event in cache. Best-effort, may silently fail.\n *\n * @param event - The event to store\n */\n store(event: NostrEvent): void;\n\n /**\n * Whether the cache is available.\n *\n * @returns true if the cache can handle requests\n */\n isAvailable(): boolean;\n}\n\n/**\n * @kehto/services cross-package naming-parity alias for {@link CacheServiceOptions}.\n *\n * `HostCacheBridge` matches the v1.4 `HostKeysBridge` / `HostMediaBridge`\n * convention — it is a pure type alias for `CacheServiceOptions`, NOT a new\n * type. Existing consumers of `CacheServiceOptions` continue to work\n * unchanged; new consumers may prefer `HostCacheBridge` for consistency\n * with the other Host*Bridge names in `@kehto/services`.\n *\n * Anti-feature note (PITFALLS.md M-02): `CacheServiceOptions` MUST remain\n * the primary export. This alias is additive; do not rename or delete\n * `CacheServiceOptions` when other Host*Bridge names eventually\n * stabilize.\n *\n * @example\n * ```ts\n * import type { HostCacheBridge } from '@kehto/services';\n * const cache: HostCacheBridge = {\n * query: (filters) => myIndexedDB.query(filters),\n * store: (event) => myIndexedDB.store(event),\n * isAvailable: () => true,\n * };\n * ```\n */\nexport type HostCacheBridge = CacheServiceOptions;\n\n/**\n * Create a cache service that wraps an existing cache implementation\n * as a ServiceHandler.\n *\n * Cache relay.subscribe subscriptions are one-shot — they query, deliver\n * results, send EOSE, and are done. No long-lived subscription tracking needed.\n * Cache query failures are best-effort: EOSE is sent even on failure.\n *\n * @param options - Cache implementation to wrap\n * @returns A ServiceHandler ready for runtime.registerService('cache', handler)\n *\n * @example\n * ```ts\n * import { createCacheService } from '@kehto/services';\n *\n * const cache = createCacheService({\n * query: (f) => workerRelay.query(f),\n * store: (e) => workerRelay.store(e),\n * isAvailable: () => workerRelay.ready,\n * });\n * runtime.registerService('cache', cache);\n * ```\n */\nexport function createCacheService(options: CacheServiceOptions): ServiceHandler {\n return {\n descriptor: {\n name: 'cache',\n version: '1.0.0',\n description: 'Local event cache (IndexedDB, worker relay, etc.)',\n },\n\n handleMessage(_windowId: string, message: NappletMessage, send: (msg: NappletMessage) => void): void {\n const relayMessage = message as RelayServiceMessage;\n if (message.type === 'relay.subscribe') {\n const subId = relayMessage.subId;\n if (typeof subId !== 'string') return;\n const filters = Array.isArray(relayMessage.filters)\n ? relayMessage.filters as NostrFilter[]\n : [];\n\n if (!options.isAvailable()) {\n send({ type: 'relay.eose', subId } as NappletMessage);\n return;\n }\n\n options\n .query(filters)\n .then((events) => {\n for (const event of events) {\n send({ type: 'relay.event', subId, event } as NappletMessage);\n }\n send({ type: 'relay.eose', subId } as NappletMessage);\n })\n .catch(() => {\n // Cache query is best-effort — send EOSE even on failure\n send({ type: 'relay.eose', subId } as NappletMessage);\n });\n return;\n }\n\n if (message.type === 'relay.publish') {\n const event = relayMessage.event as NostrEvent | undefined;\n if (event && typeof event === 'object' && options.isAvailable()) {\n try {\n options.store(event);\n } catch {\n /* Cache write is best-effort */\n }\n }\n return;\n }\n },\n\n // Cache has no per-window state to clean up\n onWindowDestroyed(_windowId: string): void {\n /* no-op */\n },\n };\n}\n","\nimport type { NostrEvent, NostrFilter, NappletMessage } from '@napplet/core';\nimport type { ServiceHandler } from '@kehto/runtime';\nimport type { RelayPoolServiceOptions } from './relay-pool-service.js';\nimport type { CacheServiceOptions } from './cache-service.js';\n\n// Timer globals available in all JS runtimes\ndeclare function setTimeout(callback: () => void, ms: number): unknown;\ndeclare function clearTimeout(id: unknown): void;\n\n/** Default EOSE fallback timeout. */\nconst DEFAULT_EOSE_TIMEOUT_MS = 15_000;\n\n/**\n * Options for creating a coordinated relay service.\n *\n * @example\n * ```ts\n * const relay = createCoordinatedRelay({\n * relayPool: {\n * subscribe: (f, cb, urls) => pool.subscribe(f, cb, urls),\n * publish: (e) => pool.publish(e),\n * selectRelayTier: (f) => pool.selectRelays(f),\n * isAvailable: () => pool.connected,\n * },\n * cache: {\n * query: (f) => db.query(f),\n * store: (e) => db.store(e),\n * isAvailable: () => db.ready,\n * },\n * });\n * runtime.registerService('relay', relay);\n * ```\n */\nexport interface CoordinatedRelayOptions {\n /**\n * Relay pool implementation.\n * Uses the same interface as RelayPoolServiceOptions.\n */\n relayPool: RelayPoolServiceOptions;\n\n /**\n * Local cache implementation.\n * Uses the same interface as CacheServiceOptions.\n */\n cache: CacheServiceOptions;\n\n /**\n * EOSE fallback timeout in milliseconds.\n * Sent if relay pool doesn't respond within this time.\n * Default: 15000 (15 seconds).\n */\n eoseTimeoutMs?: number;\n}\n\n/** Internal state for a tracked subscription. */\ninterface TrackedSub {\n seenIds: Set<string>;\n cacheEose: boolean;\n relayEose: boolean;\n eoseSent: boolean;\n eoseTimer: unknown;\n relayHandle: { unsubscribe(): void } | null;\n}\n\ntype RelayServiceMessage = NappletMessage & {\n subId?: unknown;\n filters?: unknown;\n event?: unknown;\n relay?: unknown;\n};\n\n/**\n * Create a coordinated relay service that combines relay pool and cache\n * into a single ServiceHandler with dedup and unified EOSE.\n *\n * On relay.subscribe: queries cache first, then subscribes to relay pool.\n * Events are deduplicated by ID. EOSE is sent after both sources complete.\n * On relay.publish: publishes to relay pool and stores in cache.\n * On relay.close: cancels relay pool subscription.\n *\n * @param options - Relay pool and cache implementations to coordinate\n * @returns A ServiceHandler ready for runtime.registerService('relay', handler)\n *\n * @example\n * ```ts\n * import { createCoordinatedRelay } from '@kehto/services';\n *\n * const relay = createCoordinatedRelay({ relayPool: myPool, cache: myCache });\n * runtime.registerService('relay', relay);\n * ```\n */\nexport function createCoordinatedRelay(options: CoordinatedRelayOptions): ServiceHandler {\n const timeoutMs = options.eoseTimeoutMs ?? DEFAULT_EOSE_TIMEOUT_MS;\n const subs = new Map<string, TrackedSub>();\n\n function maybeSendEose(subKey: string, subId: string, send: (msg: NappletMessage) => void): void {\n const sub = subs.get(subKey);\n if (!sub || sub.eoseSent) return;\n if (sub.cacheEose && sub.relayEose) {\n sub.eoseSent = true;\n clearTimeout(sub.eoseTimer);\n send({ type: 'relay.eose', subId } as NappletMessage);\n }\n }\n\n return {\n descriptor: {\n name: 'relay',\n version: '1.0.0',\n description: 'Coordinated relay pool + cache with dedup and unified EOSE',\n },\n\n handleMessage(windowId: string, message: NappletMessage, send: (msg: NappletMessage) => void): void {\n const relayMessage = message as RelayServiceMessage;\n if (message.type === 'relay.subscribe') {\n const subId = relayMessage.subId;\n if (typeof subId !== 'string') return;\n const filters = Array.isArray(relayMessage.filters)\n ? relayMessage.filters as NostrFilter[]\n : [];\n const subKey = `${windowId}:${subId}`;\n\n // Cancel existing subscription for this key\n const existing = subs.get(subKey);\n if (existing) {\n existing.relayHandle?.unsubscribe();\n clearTimeout(existing.eoseTimer);\n subs.delete(subKey);\n }\n\n const cacheAvailable = options.cache.isAvailable();\n const relayAvailable = options.relayPool.isAvailable();\n\n // Neither source available — send EOSE immediately\n if (!cacheAvailable && !relayAvailable) {\n send({ type: 'relay.eose', subId } as NappletMessage);\n return;\n }\n\n const tracked: TrackedSub = {\n seenIds: new Set(),\n cacheEose: !cacheAvailable, // mark done if cache not available\n relayEose: !relayAvailable, // mark done if relay not available\n eoseSent: false,\n eoseTimer: null as unknown,\n relayHandle: null,\n };\n subs.set(subKey, tracked);\n\n function deliver(event: NostrEvent): void {\n if (tracked.seenIds.has(event.id)) return;\n tracked.seenIds.add(event.id);\n if (subs.has(subKey)) send({ type: 'relay.event', subId, event } as NappletMessage);\n }\n\n // Query cache (async)\n if (cacheAvailable) {\n options.cache\n .query(filters)\n .then((events) => {\n for (const event of events) deliver(event);\n tracked.cacheEose = true;\n maybeSendEose(subKey, subId, send);\n })\n .catch(() => {\n // Cache query is best-effort\n tracked.cacheEose = true;\n maybeSendEose(subKey, subId, send);\n });\n }\n\n // Subscribe to relay pool\n if (relayAvailable) {\n tracked.eoseTimer = setTimeout(() => {\n if (!tracked.eoseSent) {\n tracked.relayEose = true;\n maybeSendEose(subKey, subId, send);\n }\n }, timeoutMs);\n\n const relayHint = typeof relayMessage.relay === 'string' && relayMessage.relay.length > 0\n ? relayMessage.relay\n : undefined;\n const relayUrls = relayHint ? [relayHint] : options.relayPool.selectRelayTier(filters);\n tracked.relayHandle = options.relayPool.subscribe(filters, (item) => {\n if (item === 'EOSE') {\n clearTimeout(tracked.eoseTimer);\n tracked.relayEose = true;\n maybeSendEose(subKey, subId, send);\n return;\n }\n deliver(item);\n // Store relay events in cache\n if (cacheAvailable) {\n try { options.cache.store(item); } catch { /* best-effort */ }\n }\n }, relayUrls);\n }\n return;\n }\n\n if (message.type === 'relay.close') {\n const subId = relayMessage.subId;\n if (typeof subId !== 'string') return;\n const subKey = `${windowId}:${subId}`;\n const entry = subs.get(subKey);\n if (entry) {\n entry.relayHandle?.unsubscribe();\n clearTimeout(entry.eoseTimer);\n subs.delete(subKey);\n }\n return;\n }\n\n if (message.type === 'relay.publish') {\n const event = relayMessage.event as NostrEvent | undefined;\n if (!event || typeof event !== 'object') return;\n // Publish to relay pool\n if (options.relayPool.isAvailable()) {\n options.relayPool.publish(event);\n }\n if (options.cache.isAvailable()) {\n try { options.cache.store(event); } catch { /* best-effort */ }\n }\n return;\n }\n },\n\n onWindowDestroyed(windowId: string): void {\n const prefix = `${windowId}:`;\n for (const [key, entry] of subs) {\n if (key.startsWith(prefix)) {\n entry.relayHandle?.unsubscribe();\n clearTimeout(entry.eoseTimer);\n subs.delete(key);\n }\n }\n },\n };\n}\n","/**\n * keys-service.ts — NIP-5D keys NAP reference document-level listener implementation.\n *\n * Handles the 3 napplet -> shell request types from @napplet/nap/keys:\n * - keys.forward -> invokes options.onForward (hotkey passthrough, fire-and-forget)\n * - keys.registerAction -> parses action.defaultKey into a chord spec, stores the\n * subscription in an in-memory registry keyed by actionId,\n * tracks windowId ownership so onWindowDestroyed can auto-\n * unsubscribe, and echoes { actionId, binding } as .result\n * - keys.unregisterAction -> removes the subscription; fire-and-forget (no envelope)\n *\n * Real listener: on service construction the handler attaches a single\n * `keydown` listener to `options.listenerTarget` (default: `document`). Each\n * keydown is matched against the chord-subscription registry; matches invoke\n * `options.onForward` with the DOM-shape payload AND push a canonical\n * `keys.action` envelope back to the owning napplet via the per-window `send`\n * callback captured at `keys.registerAction` time. Subscriptions persist\n * across messages; `onWindowDestroyed(windowId)` drops all subscriptions owned\n * by the destroyed window as well as its cached `send` handle.\n *\n * On each document keydown matching a registered action, the service\n * additionally emits a `keys.action` envelope to the action's owning napplet\n * via the per-window `send` callback — this is the canonical @napplet/nap/keys\n * surface the SDK's `keys.onAction(...)` helper consumes. The shape is a\n * superset of `KeysActionMessage`: `{ type, actionId, chord }` where `chord`\n * is the parsed `{ ctrl, alt, shift, meta, key }` struct (extension field;\n * base shape unchanged, downstream SDKs that only read `{ type, actionId }`\n * ignore `chord` silently).\n *\n * Field-name translation: @napplet/nap/keys uses the compact\n * { ctrl, alt, shift, meta } form on the wire; the shell's HotkeyHooks\n * (packages/shell/src/types.ts) expects the DOM-compatible\n * { ctrlKey, altKey, shiftKey, metaKey } form. This service performs the\n * translation so callers of `onForward` see the DOM shape.\n *\n * Shell -> napplet push envelopes `keys.bindings` remain the shell-side keys\n * forwarder's responsibility (DRIFT-SHELL-06, tracked under Plan 12-11 /\n * future phase); `keys.action` is emitted here per Plan 26-01.\n */\n\nimport type { NappletMessage } from '@napplet/core';\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\nimport type {\n KeysForwardMessage,\n KeysRegisterActionMessage,\n KeysRegisterActionResultMessage,\n KeysActionMessage,\n} from '@napplet/nap/keys/types';\n\n/**\n * Minimal structural subset of the DOM `KeyboardEvent` exposed to\n * `HostKeysBridge` subscribe callbacks. DOM `KeyboardEvent` satisfies this\n * structurally with no adapter needed. OS-bridge impls (Electron, Tauri —\n * out of v1.4 scope) synthesize this from native key events.\n */\nexport interface HostKeyEvent {\n key: string;\n code: string;\n ctrlKey: boolean;\n altKey: boolean;\n shiftKey: boolean;\n metaKey: boolean;\n /** True for OS autorepeat; the service filters these by default. */\n repeat?: boolean;\n}\n\n/**\n * Host-bridge contract for pluggable keyboard backends.\n *\n * The browser reference implementation (the default {@link createKeysService}\n * behaviour when `hostBridge` is omitted) registers a `document`-level keydown\n * listener and satisfies this interface structurally — it exposes\n * `subscribe(chord, callback) => unsubscribe` semantics but omits the two\n * OS-level optional fields (browsers cannot register global hotkeys without\n * privileged APIs).\n *\n * Host apps (Electron, Tauri) implement this interface in their own code and\n * pass it via `createKeysService({ hostBridge: myBridge })` — the service\n * then delegates subscription lifecycle to the bridge and remains browser-free.\n *\n * Reference implementations for Electron / Tauri are explicitly out of v1.4\n * scope and live in host-app examples / follow-up milestones (see\n * REQUIREMENTS.md \"Future Requirements\").\n *\n * @example\n * ```ts\n * // Host-app pseudocode (Electron main-process relay):\n * const electronBridge: HostKeysBridge = {\n * subscribe(chord, cb) {\n * const handle = globalShortcut.register(chord, () => cb({ key: '', code: '', ctrlKey: false, altKey: false, shiftKey: false, metaKey: false }));\n * return () => globalShortcut.unregister(chord);\n * },\n * registerGlobalHotkey: (chord) => globalShortcut.register(chord, () => {}),\n * onGlobalHotkey: (cb) => globalHotkeyBridge.on('global-hotkey', (_, chord) => cb(chord)),\n * };\n *\n * const keys = createKeysService({ hostBridge: electronBridge });\n * runtime.registerService('keys', keys);\n * ```\n */\nexport interface HostKeysBridge {\n /**\n * Subscribe a callback to a chord. Returns an unsubscribe handle.\n *\n * Implementations MUST:\n * - invoke `callback` exactly once per matching chord event (implementations\n * are responsible for any OS-autorepeat filtering)\n * - invoke `callback` synchronously during the event delivery\n * - accept the string chord format documented by @napplet/nap/keys\n * (e.g. `'Ctrl+Shift+K'`, `'Cmd+P'`)\n */\n subscribe(chord: string, callback: (event: KeyboardEvent | HostKeyEvent) => void): () => void;\n\n /**\n * Optional: register an OS-level global hotkey (works even when the host\n * window is not focused). Returns true on success, false if the chord\n * cannot be registered (e.g. already claimed by another app).\n *\n * Omitted by the browser reference implementation — browsers cannot\n * register OS-level global hotkeys without privileged APIs. Electron\n * (`globalShortcut`) and Tauri (`GlobalShortcut`) provide this.\n */\n registerGlobalHotkey?(chord: string): boolean;\n\n /**\n * Optional: subscribe to OS-level global hotkey events (regardless of\n * focus). Returns an unsubscribe handle.\n *\n * Omitted by the browser reference implementation. See\n * {@link HostKeysBridge.registerGlobalHotkey}.\n */\n onGlobalHotkey?(callback: (chord: string) => void): () => void;\n}\n\n/** Keys service version — follows semver. */\nconst KEYS_SERVICE_VERSION = '1.2.0';\n\n/**\n * Parsed chord struct — internal, never on the wire. The napplet-facing API\n * (and the `action.defaultKey` field) is a string like `\"Ctrl+Shift+K\"`;\n * `parseChord` lowers those strings into this struct for efficient matching\n * against `KeyboardEvent` modifier flags.\n */\ninterface ChordSpec {\n ctrl: boolean;\n alt: boolean;\n shift: boolean;\n meta: boolean;\n /** Normalized uppercase single character or DOM key name (e.g. 'K', 'Enter'). */\n key: string;\n}\n\n/** Registry entry — maps a registered actionId back to its owning window + chord. */\ninterface ActionEntry {\n chord: ChordSpec;\n /** Original chord string, preserved for the .result `binding` field. */\n chordString: string;\n windowId: string;\n}\n\n/**\n * Options for creating a keys service via createKeysService().\n *\n * @example\n * ```ts\n * const keys = createKeysService({\n * onForward: (event) => {\n * // event has DOM-compatible field names: ctrlKey, altKey, etc.\n * hotkeyDispatcher.dispatch(event);\n * },\n * });\n * ```\n */\nexport interface KeysServiceOptions {\n /**\n * Called on `keys.forward` (napplet-forwarded chord) AND on document keydown\n * matching a registered action. Receives the DOM-style field names\n * (ctrlKey/altKey/shiftKey/metaKey) to match the shell's HotkeyHooks\n * contract. The service translates from the wire shape\n * ({ ctrl, alt, shift, meta }) before invoking this callback.\n */\n onForward?: (event: {\n key: string;\n code: string;\n ctrlKey: boolean;\n altKey: boolean;\n shiftKey: boolean;\n metaKey: boolean;\n }) => void;\n /**\n * EventTarget to attach the default keydown listener to. Defaults to the\n * global `document` when running in a DOM environment, else an isolated\n * `new EventTarget()` (SSR / Node-test safe). Passing a fresh\n * `new EventTarget()` is useful for unit tests. Mirrors the pattern used\n * by `@kehto/shell`'s `createKeysForwarder`.\n *\n * Ignored when `hostBridge` is provided — the bridge owns subscription\n * lifecycle and no document listener is attached.\n */\n listenerTarget?: EventTarget;\n /**\n * Optional pluggable backend for chord subscription. When provided, the\n * service delegates `keys.registerAction` → `bridge.subscribe(chord, cb)`\n * and stores the returned unsubscribe handle keyed on `actionId`. The\n * default document-listener path is NOT attached when `hostBridge` is\n * provided — the bridge is authoritative. See {@link HostKeysBridge}.\n */\n hostBridge?: HostKeysBridge;\n /**\n * Optional set of shell-reserved chords. Strings in the `@napplet/nap/keys`\n * wire format (e.g. `'Ctrl+Shift+K'`, `'Cmd+P'`). When a napplet sends\n * `keys.forward` with a chord matching this set — or when a document\n * keydown matches a reserved chord — the service invokes `onForward`\n * (or the `hostBridge`-registered handler) but suppresses the\n * `keys.action` push to any napplet that registered the same chord via\n * `keys.registerAction`. Precedence: reserved > registered. The shell\n * WANTS the forward — that is why it reserved the chord.\n *\n * Normalized once at service construction via the same chord parser used\n * for `action.defaultKey` — `'Ctrl+K'` / `'Control+k'` / `'ctrl+K'` all\n * match. Static; no runtime mutation. For dynamic reservation see the\n * deferred `HostKeysBridge.reserveAbsolute(chords)` extension.\n *\n * @example\n * ```ts\n * const keys = createKeysService({\n * reservedChords: ['Ctrl+Alt+T', 'Super+Space'],\n * onForward: (event) => wm.dispatch(event),\n * });\n * ```\n */\n reservedChords?: ReadonlyArray<string>;\n}\n\n/** Modifier-token aliases accepted by `parseChord` (case-insensitive). */\nconst MODIFIER_ALIASES: Record<string, keyof Pick<ChordSpec, 'ctrl' | 'alt' | 'shift' | 'meta'>> = {\n ctrl: 'ctrl',\n control: 'ctrl',\n alt: 'alt',\n option: 'alt',\n shift: 'shift',\n meta: 'meta',\n cmd: 'meta',\n command: 'meta',\n win: 'meta',\n super: 'meta',\n};\n\n/**\n * Parse a chord string into a `ChordSpec`. Modifier tokens are case-insensitive\n * and recognize common aliases (Cmd/Command/Win/Super → meta, Control → ctrl,\n * Option → alt). Single-character keys are normalized to uppercase so chord\n * matching is case-insensitive; multi-character DOM key names (`Enter`,\n * `ArrowUp`, `F4`) preserve their original casing.\n *\n * Examples:\n * parseChord('Ctrl+Shift+K') → { ctrl: true, alt: false, shift: true, meta: false, key: 'K' }\n * parseChord('ctrl+s') → { ctrl: true, alt: false, shift: false, meta: false, key: 'S' }\n * parseChord('Cmd+P') → { ctrl: false, alt: false, shift: false, meta: true, key: 'P' }\n * parseChord('K') → { ctrl: false, alt: false, shift: false, meta: false, key: 'K' }\n * parseChord('Ctrl++') → { ctrl: true, alt: false, shift: false, meta: false, key: '+' }\n *\n * @throws Error('empty chord') when the input is the empty string.\n * @throws Error(`unknown modifier: ${tok}`) when a non-final token isn't a recognized modifier.\n * @throws Error(`empty key in chord: ${chord}`) when the final token is empty/whitespace.\n */\nfunction parseChord(chord: string): ChordSpec {\n if (chord.length === 0) throw new Error('empty chord');\n const parts = chord.split('+');\n const out: ChordSpec = { ctrl: false, alt: false, shift: false, meta: false, key: '' };\n // The final token is always the key — even if it is literally '+' (chord like 'Ctrl++').\n // All preceding tokens must be modifiers.\n for (let i = 0; i < parts.length - 1; i++) {\n const tok = parts[i].trim().toLowerCase();\n if (tok.length === 0) continue; // tolerate stray whitespace\n const slot = MODIFIER_ALIASES[tok];\n if (!slot) throw new Error(`unknown modifier: ${parts[i]}`);\n out[slot] = true;\n }\n const keyTok = parts[parts.length - 1].trim();\n if (keyTok.length === 0) throw new Error(`empty key in chord: ${chord}`);\n // Single characters normalize to uppercase for case-insensitive comparison;\n // multi-character DOM key names (Enter, ArrowUp, F4) preserve their original casing.\n out.key = keyTok.length === 1 ? keyTok.toUpperCase() : keyTok;\n return out;\n}\n\n/**\n * Create a keys service handler.\n *\n * Attaches a single `keydown` listener to `options.listenerTarget`\n * (default `document`). Matching chord subscriptions invoke `options.onForward`\n * with a DOM-shape payload AND push a `keys.action` envelope back to the\n * owning napplet via the per-window `send` callback captured at\n * `keys.registerAction` time. Returns a `ServiceHandler` augmented with a\n * `destroy()` method that detaches the listener and clears all registries.\n *\n * @param options - Optional configuration (onForward callback, listenerTarget)\n * @returns A ServiceHandler (with `destroy()`) to register with the runtime\n *\n * @example\n * ```ts\n * import { createKeysService } from '@kehto/services';\n *\n * const keys = createKeysService({\n * onForward: (event) => shellHotkeyDispatcher.execute(event),\n * });\n *\n * runtime.registerService('keys', keys);\n * // Later, on shell teardown:\n * keys.destroy();\n * ```\n */\nexport function createKeysService(\n options: KeysServiceOptions = {},\n): ServiceHandler & { destroy(): void } {\n const descriptor: ServiceDescriptor = {\n name: 'keys',\n version: KEYS_SERVICE_VERSION,\n description: options.hostBridge\n ? 'NIP-5D keys NAP reference handler (host-bridge delegated)'\n : 'NIP-5D keys NAP reference handler (document-level chord listener)',\n };\n\n function chordSpecKey(spec: {\n ctrl: boolean;\n alt: boolean;\n shift: boolean;\n meta: boolean;\n key: string;\n }): string {\n return `${spec.ctrl}|${spec.alt}|${spec.shift}|${spec.meta}|${spec.key}`;\n }\n const reservedChordKeys: Set<string> = new Set();\n if (options.reservedChords) {\n for (const chordStr of options.reservedChords) {\n // parseChord throws on malformed input — let it bubble up at construction\n // so misconfigured shells fail loudly at boot, not silently at runtime.\n reservedChordKeys.add(chordSpecKey(parseChord(chordStr)));\n }\n }\n // Canonicalize a wire-shape keys.forward payload into the same key.\n function forwardKey(m: {\n key: string;\n ctrl: boolean;\n alt: boolean;\n shift: boolean;\n meta: boolean;\n }): string {\n const k = m.key.length === 1 ? m.key.toUpperCase() : m.key;\n return `${m.ctrl}|${m.alt}|${m.shift}|${m.meta}|${k}`;\n }\n function forwardPayload(m: KeysForwardMessage): Omit<HostKeyEvent, 'repeat'> {\n return {\n key: m.key,\n code: m.code,\n ctrlKey: m.ctrl,\n altKey: m.alt,\n shiftKey: m.shift,\n metaKey: m.meta,\n };\n }\n // Canonicalize a DOM KeyboardEvent into the same key (for Branch B keydown listener).\n function eventKey(ev: KeyboardEvent): string {\n const k = ev.key.length === 1 ? ev.key.toUpperCase() : ev.key;\n return `${ev.ctrlKey}|${ev.altKey}|${ev.shiftKey}|${ev.metaKey}|${k}`;\n }\n\n if (options.hostBridge) {\n const bridge = options.hostBridge;\n // windowId → Set<actionId> — parallels Branch B for scoped cleanup.\n const bridgeWindowActions = new Map<string, Set<string>>();\n // actionId → unsubscribe handle returned from bridge.subscribe.\n const unsubscribeHandles = new Map<string, () => void>();\n\n return {\n descriptor,\n\n handleMessage(\n windowId: string,\n message: NappletMessage,\n send: (msg: NappletMessage) => void,\n ): void {\n switch (message.type) {\n case 'keys.forward': {\n // Legacy napplet-forwarded path still works identically — preserves wire contract.\n // Phase 33 / KEYS-04-05: reserved chords take precedence. The Branch-A\n // handler never dispatches keys.action to napplets on forward (bridge\n // owns chord → napplet routing via its own subscribe callback), so\n // reservation here is observationally identical to the base case —\n // but we compute and check the reservation explicitly to document\n // the contract for future edits and keep both branches uniform.\n const m = message as KeysForwardMessage;\n const reserved = reservedChordKeys.has(forwardKey(m));\n options.onForward?.(forwardPayload(m));\n if (reserved) {\n return;\n }\n return;\n }\n\n case 'keys.registerAction': {\n const m = message as KeysRegisterActionMessage;\n if (m.action.defaultKey) {\n try {\n const unsubscribe = bridge.subscribe(m.action.defaultKey, (ev) => {\n // Normalize either KeyboardEvent or HostKeyEvent to the DOM-shape onForward payload.\n const e = ev as KeyboardEvent | HostKeyEvent;\n // Bridges may not filter autorepeat — we do, matching Branch B semantics.\n if ('repeat' in e && e.repeat) return;\n options.onForward?.({\n key: e.key,\n code: e.code,\n ctrlKey: e.ctrlKey,\n altKey: e.altKey,\n shiftKey: e.shiftKey,\n metaKey: e.metaKey,\n });\n // Canonical shell→napplet push: emit keys.action to the owning\n // napplet. Shape matches Branch B (superset of KeysActionMessage);\n // the `chord` extension is omitted here because bridges deliver\n // pre-parsed chord events without the internal ChordSpec struct.\n const payload: KeysActionMessage = {\n type: 'keys.action',\n actionId: m.action.id,\n };\n send(payload as NappletMessage);\n });\n unsubscribeHandles.set(m.action.id, unsubscribe);\n if (!bridgeWindowActions.has(windowId)) bridgeWindowActions.set(windowId, new Set());\n bridgeWindowActions.get(windowId)!.add(m.action.id);\n } catch (err) {\n const id = m.id ?? '';\n send({\n type: 'keys.registerAction.error',\n id,\n error: `bridge subscribe failed: ${(err as Error).message}`,\n } as NappletMessage);\n return;\n }\n }\n const result: KeysRegisterActionResultMessage = {\n type: 'keys.registerAction.result',\n id: m.id,\n actionId: m.action.id,\n ...(m.action.defaultKey ? { binding: m.action.defaultKey } : {}),\n };\n send(result as NappletMessage);\n return;\n }\n\n case 'keys.unregisterAction': {\n const m = message as NappletMessage & { actionId?: string };\n if (m.actionId) {\n const unsubscribe = unsubscribeHandles.get(m.actionId);\n if (unsubscribe) {\n try {\n unsubscribe();\n } catch {\n /* best-effort */\n }\n unsubscribeHandles.delete(m.actionId);\n // Prune the bridgeWindowActions entry that owns this actionId.\n for (const [wid, set] of bridgeWindowActions.entries()) {\n if (set.delete(m.actionId) && set.size === 0) bridgeWindowActions.delete(wid);\n }\n }\n }\n return;\n }\n\n default: {\n const id = (message as NappletMessage & { id?: string }).id ?? '';\n send({\n type: `${message.type}.error`,\n id,\n error: `Unknown keys method: ${message.type}`,\n } as NappletMessage);\n return;\n }\n }\n },\n\n onWindowDestroyed(windowId: string): void {\n const actions = bridgeWindowActions.get(windowId);\n if (!actions) return;\n for (const actionId of actions) {\n const unsubscribe = unsubscribeHandles.get(actionId);\n if (unsubscribe) {\n try {\n unsubscribe();\n } catch {\n /* best-effort */\n }\n }\n unsubscribeHandles.delete(actionId);\n }\n bridgeWindowActions.delete(windowId);\n },\n\n destroy(): void {\n for (const unsubscribe of unsubscribeHandles.values()) {\n try {\n unsubscribe();\n } catch {\n /* best-effort */\n }\n }\n unsubscribeHandles.clear();\n bridgeWindowActions.clear();\n },\n };\n }\n\n const actionRegistry = new Map<string, ActionEntry>(); // actionId → {chord, chordString, windowId}\n const windowActions = new Map<string, Set<string>>(); // windowId → Set<actionId>\n // Per-window `send` callback captured at registerAction time. Used to push\n // keys.action envelopes back to the owning napplet on chord match — this is\n // the canonical @napplet/nap/keys surface the SDK's `keys.onAction(...)`\n // helper consumes.\n const sendHandles = new Map<string, (msg: NappletMessage) => void>(); // windowId → send\n\n // ─── Listener target (SSR / test-safe fallback, mirrors keys-forwarder.ts) ─\n const target: EventTarget =\n options.listenerTarget ??\n (typeof document !== 'undefined' ? document : new EventTarget());\n\n function chordMatches(spec: ChordSpec, ev: KeyboardEvent): boolean {\n if (spec.ctrl !== ev.ctrlKey) return false;\n if (spec.alt !== ev.altKey) return false;\n if (spec.shift !== ev.shiftKey) return false;\n if (spec.meta !== ev.metaKey) return false;\n const evKey = ev.key.length === 1 ? ev.key.toUpperCase() : ev.key;\n return spec.key === evKey;\n }\n\n const listener = (rawEv: Event): void => {\n const ev = rawEv as KeyboardEvent;\n if (ev.repeat) return; // ignore OS autorepeat — matches \"I pressed it once\" intent (CONTEXT Area 1)\n\n // Phase 33 / KEYS-04-05: reserved chords fire onForward but suppress\n // keys.action fan-out to napplets. Check ONCE up front.\n const isReserved = reservedChordKeys.has(eventKey(ev));\n\n // Determine if any registered action would match — needed to decide\n // whether to fire onForward on a non-reserved keydown (legacy parity).\n // A reserved chord fires onForward regardless of napplet registration\n // (WM-launcher case: shell declares chord, no napplet registers it).\n let anyMatch = false;\n for (const entry of actionRegistry.values()) {\n if (chordMatches(entry.chord, ev)) {\n anyMatch = true;\n break;\n }\n }\n\n if (isReserved || anyMatch) {\n options.onForward?.({\n key: ev.key,\n code: ev.code,\n ctrlKey: ev.ctrlKey,\n altKey: ev.altKey,\n shiftKey: ev.shiftKey,\n metaKey: ev.metaKey,\n });\n }\n\n if (isReserved) return; // reserved → no napplet fan-out\n\n // (2) Canonical shell→napplet push: emit keys.action to the owning\n // napplet via its captured send callback. The SDK's keys.onAction\n // helper subscribes to this envelope. We attach a `chord` extension\n // field so the demo napplet can display the fired chord without\n // reconstructing it from the original registration.\n for (const [actionId, entry] of actionRegistry.entries()) {\n if (chordMatches(entry.chord, ev)) {\n const send = sendHandles.get(entry.windowId);\n if (send) {\n const payload: KeysActionMessage & { chord: ChordSpec } = {\n type: 'keys.action',\n actionId,\n chord: entry.chord,\n };\n send(payload as NappletMessage);\n }\n // Intentionally no `break` — two actions subscribing to the same chord\n // both receive the event. Conflict resolution is an explicit v1.5+\n // concern per CONTEXT.md Deferred Ideas.\n }\n }\n };\n\n target.addEventListener('keydown', listener);\n\n return {\n descriptor,\n\n handleMessage(\n windowId: string,\n message: NappletMessage,\n send: (msg: NappletMessage) => void,\n ): void {\n switch (message.type) {\n case 'keys.forward': {\n // Legacy passthrough: napplet-forwarded keydown translation.\n // Preserved bit-for-bit from the stub — existing tests + the\n // keys-forwarder.ts -> service.handleMessage path depend on this shape.\n //\n // Phase 33 / KEYS-04-05: Branch B's keys.forward handler does NOT\n // emit keys.action (fan-out happens in the document keydown listener),\n // so reservation is observationally identical to the base case.\n // The explicit guard below pins the contract for future edits.\n const m = message as KeysForwardMessage;\n const reserved = reservedChordKeys.has(forwardKey(m));\n options.onForward?.(forwardPayload(m));\n if (reserved) return;\n return;\n }\n\n case 'keys.registerAction': {\n const m = message as KeysRegisterActionMessage;\n // Capture (or refresh) the per-window send callback. The runtime's\n // service-handler contract guarantees `send` remains valid for this\n // windowId until onWindowDestroyed(windowId) fires — we cache the\n // most recent invocation so the keydown listener can push\n // keys.action envelopes back to the owning napplet.\n sendHandles.set(windowId, send);\n\n if (m.action.defaultKey) {\n try {\n const chord = parseChord(m.action.defaultKey);\n actionRegistry.set(m.action.id, {\n chord,\n chordString: m.action.defaultKey,\n windowId,\n });\n if (!windowActions.has(windowId)) windowActions.set(windowId, new Set());\n windowActions.get(windowId)!.add(m.action.id);\n } catch (err) {\n // Parse failure: respond with .error envelope (unknown-method pattern).\n const id = m.id ?? '';\n send({\n type: 'keys.registerAction.error',\n id,\n error: `invalid chord: ${(err as Error).message}`,\n } as NappletMessage);\n return;\n }\n }\n const result: KeysRegisterActionResultMessage = {\n type: 'keys.registerAction.result',\n id: m.id,\n actionId: m.action.id,\n ...(m.action.defaultKey ? { binding: m.action.defaultKey } : {}),\n };\n send(result as NappletMessage);\n return;\n }\n\n case 'keys.unregisterAction': {\n // Fire-and-forget per @napplet/nap/keys spec. Remove subscription if present.\n const m = message as NappletMessage & { actionId?: string };\n if (m.actionId && actionRegistry.has(m.actionId)) {\n const entry = actionRegistry.get(m.actionId)!;\n actionRegistry.delete(m.actionId);\n const set = windowActions.get(entry.windowId);\n if (set) {\n set.delete(m.actionId);\n // If the window has no remaining actions, drop its cached send\n // that no longer subscribes to anything.\n if (set.size === 0) {\n windowActions.delete(entry.windowId);\n sendHandles.delete(entry.windowId);\n }\n }\n }\n return;\n }\n\n default: {\n const id = (message as NappletMessage & { id?: string }).id ?? '';\n send({\n type: `${message.type}.error`,\n id,\n error: `Unknown keys method: ${message.type}`,\n } as NappletMessage);\n return;\n }\n }\n },\n\n onWindowDestroyed(windowId: string): void {\n const actions = windowActions.get(windowId);\n if (actions) {\n for (const actionId of actions) actionRegistry.delete(actionId);\n windowActions.delete(windowId);\n }\n sendHandles.delete(windowId);\n },\n\n destroy(): void {\n target.removeEventListener('keydown', listener);\n actionRegistry.clear();\n windowActions.clear();\n sendHandles.clear();\n },\n };\n}\n","import type { MediaAction, MediaMetadata } from '@napplet/nap/media/types';\nimport type { HostMediaBridge } from './media-service.js';\n\n/** Silent-audio prime data URL (4 kHz silent WAV, 44 bytes, zero network dependency).\n * Browsers refuse to render OS media controls without a playing audio element —\n * this silent loop primes the MediaSession API. Per CONTEXT.md Area 1. */\nconst SILENT_AUDIO_DATA_URL =\n 'data:audio/wav;base64,UklGRiQAAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQAAAAA=';\n\n/** Default action set — all 5 nap-media transport actions. */\nexport const DEFAULT_MEDIA_ACTIONS: readonly MediaAction[] = ['play', 'pause', 'next', 'prev', 'seek'];\n\n/** Minimal subset of navigator.mediaSession the browser bridge depends on. Makes the bridge\n * Node/test-safe: unit tests pass a MockMediaSession via mediaSessionTarget.\n * The handler parameter uses `details?` (optional) so both the real DOM impl\n * (which always passes an object) and test mocks that omit details both satisfy\n * this type structurally. */\nexport type MediaSessionTarget = {\n metadata: MediaMetadataLike | null;\n playbackState: 'none' | 'playing' | 'paused';\n setActionHandler(action: string, handler: ((details?: { action?: string; seekTime?: number }) => void) | null): void;\n};\n\n/** Structural subset of the DOM MediaMetadata class — assignable from a plain object\n * with title/artist/album/artwork fields. The browser impl uses `new MediaMetadata({...})`;\n * tests can pass a plain object. */\nexport type MediaMetadataLike = { title?: string; artist?: string; album?: string; artwork?: unknown };\n\n/** Mapping from DOM MediaSession action names to nap-media MediaAction literals. */\nconst ACTION_MATRIX: ReadonlyArray<[string, MediaAction]> = [\n ['play', 'play'],\n ['pause', 'pause'],\n ['nexttrack', 'next'],\n ['previoustrack', 'prev'],\n ['seekto', 'seek'],\n];\n\n/**\n * Reference browser implementation of {@link HostMediaBridge}.\n *\n * Mirrors metadata to `navigator.mediaSession.metadata` (via the DOM\n * `MediaMetadata` constructor when available; plain-object fallback in test\n * envs). Mirrors playback state to `navigator.mediaSession.playbackState`\n * with the canonical mapping: 'playing' maps to 'playing', 'paused' maps to\n * 'paused', 'buffering' maps to 'paused', 'stopped' maps to 'none'. Installs\n * `setActionHandler` callbacks for play/pause/nexttrack/previoustrack/seekto\n * that fan into the onAction subscriber with the mapped `MediaAction` literal\n * (and `value` from `details.seekTime` for seekto). When `setActiveSession` is\n * called with a non-null `actions` parameter, only the declared actions get\n * active handlers — the remaining are cleared (matching the capabilities\n * narrowing behavior of Plan 27-01's inline implementation).\n *\n * Installs a silent-audio prime (4 kHz silent WAV data URL) when the first\n * session becomes active (setActiveSession called with a non-null sessionId) —\n * browsers refuse to render OS media controls without a playing audio element.\n * Removes the element when destroySession brings the active session count to\n * zero.\n *\n * @param opts.mediaSessionTarget - Override navigator.mediaSession (tests).\n * @param opts.documentTarget - Override document (tests; pass null to disable silent-audio prime).\n */\nexport function createBrowserMediaBridge(opts: {\n mediaSessionTarget?: MediaSessionTarget;\n documentTarget?: Document | null;\n} = {}): HostMediaBridge {\n const ms: MediaSessionTarget | null =\n opts.mediaSessionTarget\n ?? (typeof navigator !== 'undefined' && 'mediaSession' in navigator\n ? (navigator.mediaSession as MediaSessionTarget)\n : null);\n const doc: Document | null =\n opts.documentTarget !== undefined\n ? opts.documentTarget\n : (typeof document !== 'undefined' ? document : null);\n\n let silentAudioEl: HTMLAudioElement | null = null;\n let activeSessionId: string | null = null;\n let sessionsActive = 0;\n const actionCallbacks = new Set<(sessionId: string, action: MediaAction, value?: number) => void>();\n\n function primeSilentAudio(): void {\n if (silentAudioEl || !doc) return;\n const el = doc.createElement('audio') as HTMLAudioElement;\n el.src = SILENT_AUDIO_DATA_URL;\n el.loop = true;\n el.style.display = 'none';\n (el as HTMLAudioElement).setAttribute('data-kehto-silent-audio-prime', 'true');\n doc.body.appendChild(el);\n void el.play().catch(() => { /* autoplay refused — metadata mirror still works */ });\n silentAudioEl = el;\n }\n\n function teardownSilentAudio(): void {\n if (!silentAudioEl) return;\n try { silentAudioEl.pause(); } catch { /* best-effort */ }\n try { silentAudioEl.remove(); } catch { /* best-effort */ }\n silentAudioEl = null;\n }\n\n /**\n * Install action handlers for the given set of nap-media actions. Actions not\n * in the set get their handler cleared. Matches Plan 27-01's installActionHandlersFor\n * behavior exactly so capabilities-narrowing tests continue to pass.\n */\n function applyActionHandlers(actions: readonly MediaAction[] = DEFAULT_MEDIA_ACTIONS): void {\n if (!ms) return;\n for (const [domAction, napAction] of ACTION_MATRIX) {\n if (!actions.includes(napAction)) {\n try { ms.setActionHandler(domAction, null); } catch { /* best-effort */ }\n continue;\n }\n ms.setActionHandler(domAction, (details) => {\n if (!activeSessionId) return;\n const value = napAction === 'seek' && typeof details?.seekTime === 'number' ? details.seekTime : undefined;\n for (const cb of actionCallbacks) {\n cb(activeSessionId, napAction, value);\n }\n });\n }\n }\n\n function writeMetadata(metadata: MediaMetadata | undefined): void {\n if (!ms) return;\n if (!metadata) { ms.metadata = null; return; }\n const artwork = metadata.artwork?.url ? [{ src: metadata.artwork.url }] : undefined;\n const init: MediaMetadataLike & { artwork?: unknown } = {\n title: metadata.title ?? '',\n artist: metadata.artist ?? '',\n album: metadata.album ?? '',\n ...(artwork ? { artwork } : {}),\n };\n try {\n const ctor = (globalThis as typeof globalThis & { MediaMetadata?: new (init: MediaMetadataLike) => MediaMetadataLike }).MediaMetadata;\n ms.metadata = ctor ? new ctor(init) : (init as MediaMetadataLike);\n } catch {\n ms.metadata = init as MediaMetadataLike;\n }\n }\n\n return {\n setMetadata(sessionId, metadata) {\n if (sessionId === activeSessionId) writeMetadata(metadata);\n },\n setPlaybackState(sessionId, state) {\n if (!ms || sessionId !== activeSessionId) return;\n ms.playbackState =\n state === 'playing' ? 'playing'\n : state === 'paused' || state === 'buffering' ? 'paused'\n : 'none';\n },\n onAction(callback) {\n actionCallbacks.add(callback);\n return () => { actionCallbacks.delete(callback); };\n },\n setActiveSession(sessionId, actions) {\n activeSessionId = sessionId;\n if (!sessionId) {\n if (ms) {\n ms.metadata = null;\n ms.playbackState = 'none';\n for (const [domAction] of ACTION_MATRIX) {\n try { ms.setActionHandler(domAction, null); } catch { /* best-effort */ }\n }\n }\n return;\n }\n // Prime silent-audio on first session becoming active.\n if (sessionsActive === 0) { primeSilentAudio(); sessionsActive = 1; }\n applyActionHandlers(actions ?? DEFAULT_MEDIA_ACTIONS);\n },\n destroySession(_sessionId) {\n sessionsActive = Math.max(0, sessionsActive - 1);\n if (sessionsActive === 0) teardownSilentAudio();\n },\n };\n}\n","/**\n * media-service.ts — NIP-5D media NAP reference service (navigator.mediaSession\n * reference implementation).\n *\n * Handles the napplet-owned subset of @napplet/nap/media:\n * media.session.create (result), media.session.update, media.session.destroy,\n * media.state, media.capabilities.\n *\n * HostMediaBridge contract: {@link HostMediaBridge} defines the pluggable backend\n * contract for metadata/state mirroring + action routing. The browser reference\n * implementation is {@link createBrowserMediaBridge} (mirrors to navigator.mediaSession\n * with setActionHandler matrix). When no hostBridge option is passed, createMediaService\n * internally uses createBrowserMediaBridge as the default — behavior is identical\n * to the Plan 27-01 single-path implementation.\n *\n * navigator.mediaSession mirroring: on session.create the browser bridge mirrors the\n * napplet-supplied metadata to navigator.mediaSession.metadata via new MediaMetadata()\n * and installs setActionHandler callbacks for the 5 OS transport actions\n * (play / pause / nexttrack / previoustrack / seekto). Each callback emits a canonical\n * media.command envelope to the owning napplet — that is the @napplet/nap/media\n * MediaCommandMessage shape consumed by the SDK's mediaOnCommand() helper.\n *\n * NAP-MEDIA now distinguishes napplet-owned playback from shell-owned\n * playback. This reference backend supports `owner: \"napplet\"` because it\n * mirrors a napplet's own media element to navigator.mediaSession. It rejects\n * `owner: \"shell\"` until a host bridge provides policy-checked source fetching\n * and playback.\n *\n * media.command push: when the OS user clicks a media control (hardware key, lock-screen\n * transport, OS media overlay), the bridge's onAction callback fires. The service looks\n * up the active session's owning napplet, invokes the per-window send callback captured\n * at session.create time, and delivers:\n * { type: 'media.command', sessionId, action, value? }\n * where action is the nap-media MediaAction literal (play|pause|next|prev|seek) and\n * value is the seekTime (seconds) for seek.\n *\n * Silent-audio prime: the browser bridge creates a hidden <audio> element with a\n * 4 kHz silent WAV data URL and plays it when the first session becomes active\n * (setActiveSession with a non-null sessionId). Without a playing audio element in\n * the host page, most browsers refuse to render OS media controls. The element is\n * cleaned up when the last session is destroyed (via bridge.destroySession).\n *\n * Multi-session registry with last-active-wins semantics: every session.create is\n * tracked in a Map<sessionId, SessionEntry>; any media.state report promotes that\n * session to active. The active session's metadata and playback state are reflected\n * via bridge.setMetadata / bridge.setPlaybackState. When the active session is\n * destroyed, the next-most-recently-touched session is promoted automatically.\n *\n * Note: this is SEPARATE from packages/services/src/audio-service.ts, which\n * is the legacy inc-topic-based audio source registry (audio:* topic events\n * over inc.emit). media-service is the canonical @napplet/nap/media NIP-5D\n * path and they coexist — audio-service continues to track audio sources for\n * shell UI, while media-service handles the NAP protocol envelope surface.\n *\n * Shell -> Napplet push types (media.command) are emitted here when\n * bridge.onAction callbacks fire — this is the canonical Phase 27 real backend.\n */\n\nimport type { NappletMessage } from '@napplet/core';\n// DRIFT-CORE-06 — Phase 11-deviation: ServiceDescriptor dropped from @napplet/core\n// v0.2.0+ (napplet phase-81). Re-exported from @kehto/runtime (canonical home after Phase 24 DRIFT-01).\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\nimport type {\n MediaAction,\n MediaCapabilitiesMessage,\n MediaCommandMessage,\n MediaMetadata,\n MediaSessionDestroyMessage,\n MediaSessionUpdateMessage,\n MediaStateMessage,\n} from '@napplet/nap/media/types';\nimport {\n createBrowserMediaBridge,\n DEFAULT_MEDIA_ACTIONS,\n type MediaSessionTarget,\n} from './browser-media-bridge.js';\n\nexport { createBrowserMediaBridge } from './browser-media-bridge.js';\nexport type {\n MediaMetadataLike,\n MediaSessionTarget,\n} from './browser-media-bridge.js';\n\n/** Registry entry — maps a sessionId back to its owning window, metadata snapshot,\n * state snapshot, and declared capabilities. Internal; never on the wire. */\ninterface SessionEntry {\n sessionId: string;\n windowId: string;\n owner: MediaPlaybackOwner;\n source: MediaSourceRef | undefined;\n metadata: MediaMetadata | undefined;\n state: { status: 'playing' | 'paused' | 'stopped' | 'buffering'; position?: number; duration?: number; volume?: number } | undefined;\n actions: readonly MediaAction[]; // from media.capabilities; defaults to ['play','pause','next','prev','seek']\n /** Monotonic tick updated on every session.create / session.update / state — used for last-active-wins resolution when the current active session is destroyed. */\n lastTouched: number;\n}\n\n/** Media service version — follows semver. */\nconst MEDIA_SERVICE_VERSION = '1.1.0';\n\nexport type MediaPlaybackOwner = 'shell' | 'napplet';\n\nexport interface MediaSourceRef {\n url?: string;\n blossomHash?: string;\n nostr?: {\n eventId?: string;\n address?: string;\n relays?: string[];\n };\n mimeType?: string;\n}\n\nexport interface MediaSessionCreateOptions {\n owner: MediaPlaybackOwner;\n sessionId?: string;\n source?: MediaSourceRef;\n metadata?: MediaMetadata;\n capabilities?: MediaAction[];\n autoplay?: boolean;\n live?: boolean;\n}\n\ntype MediaSessionCreateEnvelope = NappletMessage & Partial<MediaSessionCreateOptions> & {\n type: 'media.session.create';\n id?: string;\n};\n\ntype MediaSessionCreateResultEnvelope = NappletMessage & {\n type: 'media.session.create.result';\n id: string;\n sessionId?: string;\n owner?: MediaPlaybackOwner;\n error?: string;\n};\n\n/**\n * Host-bridge contract for pluggable media backends.\n *\n * The browser reference implementation ({@link createBrowserMediaBridge}) mirrors\n * napplet-reported metadata/state to `navigator.mediaSession` and installs\n * `setActionHandler` callbacks that fan into the bridge's onAction subscribers.\n * It satisfies this interface with all 5 fields implemented (setActiveSession\n * switches the active session and optionally re-applies action-handler narrowing;\n * destroySession tears down the silent-audio prime on last-session teardown).\n *\n * Host apps (Electron, Tauri) implement this interface in their own code and\n * pass it via `createMediaService({ hostBridge: myBridge })` — the service\n * then delegates metadata/state mirroring + action routing to the bridge and\n * remains browser-free. Session-ownership bookkeeping (which windowId owns\n * which sessionId, which send callback routes media.command back to which\n * napplet) stays in the service layer — that's wire-protocol concern, not a\n * bridge concern.\n *\n * Reference implementations for Electron / Tauri are explicitly out of v1.4\n * scope and live in host-app examples / follow-up milestones (see\n * REQUIREMENTS.md \"Future Requirements\").\n *\n * @example\n * ```ts\n * // Host-app pseudocode (Electron main-process relay):\n * const electronBridge: HostMediaBridge = {\n * setMetadata(sessionId, md) { mediaBridge.sendMetadata({ sessionId, md }); },\n * setPlaybackState(sessionId, state) { mediaBridge.sendPlaybackState({ sessionId, state }); },\n * onAction(cb) {\n * const handler = (_: unknown, msg: { sessionId: string; action: MediaAction; value?: number }) =>\n * cb(msg.sessionId, msg.action, msg.value);\n * mediaBridge.onAction(handler);\n * return () => mediaBridge.offAction(handler);\n * },\n * };\n * const media = createMediaService({ hostBridge: electronBridge });\n * runtime.registerService('media', media);\n * ```\n */\nexport interface HostMediaBridge {\n /**\n * Set the metadata displayed on the OS transport surface for a session.\n * Called on session.create (with initial metadata) and on session.update\n * (with merged metadata) whenever the session is the active session.\n * Implementations MUST be idempotent.\n */\n setMetadata(sessionId: string, metadata: MediaMetadata): void;\n\n /**\n * Set the playback state for a session. Called on media.state reports\n * whenever the session is the active session. State strings match\n * nap-media MediaState.status exactly. Implementations MUST be idempotent.\n */\n setPlaybackState(sessionId: string, state: 'playing' | 'paused' | 'stopped' | 'buffering'): void;\n\n /**\n * Subscribe to OS-level action events (user clicks play/pause/seek/next/prev\n * on the transport surface). Returns an unsubscribe handle.\n *\n * The callback receives `(sessionId, action, value?)`. `sessionId` is the\n * bridge's currently-active session (the browser impl tracks this internally\n * via setActionHandler-at-fire-time; native impls track via setActiveSession).\n * `value` is populated for `action === 'seek'` (seek target in seconds) and\n * for `action === 'volume'` (0.0-1.0). The service dispatches the resulting\n * `media.command` envelope to the owning napplet of that session.\n */\n onAction(callback: (sessionId: string, action: MediaAction, value?: number) => void): () => void;\n\n /**\n * Optional: notify the bridge that the active session has changed. The\n * browser reference impl uses this to switch which session's metadata/state\n * is mirrored to the singleton navigator.mediaSession and to install (or\n * clear) action handlers for the session's declared capabilities.\n *\n * The optional `actions` parameter carries the session's declared capability\n * set so the bridge can narrow which OS transport buttons are active. When\n * omitted, the bridge applies its default set. Native OS bridges that track\n * active-session state internally may omit this field entirely.\n */\n setActiveSession?(sessionId: string | null, actions?: readonly MediaAction[]): void;\n\n /**\n * Optional: tear down per-session resources. The browser reference impl\n * uses this to remove the silent-audio prime element when the last session\n * is destroyed. Bridges that need no per-session teardown may omit this field.\n */\n destroySession?(sessionId: string): void;\n}\n\n/**\n * Optional host callbacks for the media service.\n *\n * Host shells can hook session creation and state updates to drive\n * their own UI (e.g., now-playing banner) without replacing the\n * ServiceHandler wholesale.\n *\n * @example\n * ```ts\n * const media = createMediaService({\n * onSessionCreate: (windowId, sessionId, metadata) => {\n * console.log(`[${windowId}] created session ${sessionId}`, metadata);\n * },\n * onState: (windowId, sessionId, state) => {\n * nowPlaying.update(windowId, state);\n * },\n * });\n *\n * runtime.registerService('media', media);\n * ```\n */\nexport interface MediaServiceOptions {\n /** Called when a napplet creates a session. May inspect/record the session. */\n onSessionCreate?: (windowId: string, sessionId: string, metadata?: unknown) => void;\n /** Called on media.state updates — high-frequency; keep handler work minimal. */\n onState?: (windowId: string, sessionId: string, state: unknown) => void;\n /** Called when a napplet destroys a session. */\n onSessionDestroy?: (windowId: string, sessionId: string) => void;\n /** Called when a napplet updates session metadata. */\n onSessionUpdate?: (windowId: string, sessionId: string, metadata: unknown) => void;\n /** Called when a napplet declares capabilities for a session. */\n onCapabilities?: (windowId: string, sessionId: string, actions: unknown) => void;\n\n /**\n * MediaSession target override (used by default browser bridge only).\n * Defaults to `navigator.mediaSession` when running in a browser. Pass a\n * MockMediaSession in unit tests. Ignored when `hostBridge` is provided.\n */\n mediaSessionTarget?: MediaSessionTarget;\n\n /**\n * DOM document override (used by default browser bridge only).\n * Defaults to `document` when available. Set to null to disable the silent-audio\n * prime entirely — useful in unit tests. Ignored when `hostBridge` is provided.\n */\n documentTarget?: Document | null;\n\n /**\n * Optional pluggable backend for metadata/state mirroring + OS action handling.\n * When provided, the service delegates setMetadata / setPlaybackState / onAction\n * to the bridge and skips navigator.mediaSession entirely. When omitted, the\n * service internally uses {@link createBrowserMediaBridge} as the default.\n * See {@link HostMediaBridge}.\n */\n hostBridge?: HostMediaBridge;\n}\n\ninterface MediaServiceState {\n bridge: HostMediaBridge;\n options: MediaServiceOptions;\n sessionRegistry: Map<string, SessionEntry>;\n windowSessions: Map<string, Set<string>>;\n sendHandles: Map<string, (msg: NappletMessage) => void>;\n activeSessionId: string | null;\n touchCounter: number;\n sessionCounter: number;\n}\n\nfunction createMediaServiceState(options: MediaServiceOptions, bridge: HostMediaBridge): MediaServiceState {\n return {\n bridge,\n options,\n sessionRegistry: new Map<string, SessionEntry>(),\n windowSessions: new Map<string, Set<string>>(),\n sendHandles: new Map<string, (msg: NappletMessage) => void>(),\n activeSessionId: null,\n touchCounter: 0,\n sessionCounter: 0,\n };\n}\n\nfunction setActive(state: MediaServiceState, sessionId: string | null, actions?: readonly MediaAction[]): void {\n state.activeSessionId = sessionId;\n state.bridge.setActiveSession?.(sessionId, actions);\n if (!sessionId) return;\n const entry = state.sessionRegistry.get(sessionId);\n if (!entry) return;\n if (entry.metadata) state.bridge.setMetadata(sessionId, entry.metadata);\n if (entry.state) state.bridge.setPlaybackState(sessionId, entry.state.status);\n}\n\nfunction promoteNextActiveOrClear(state: MediaServiceState): void {\n if (state.sessionRegistry.size === 0) {\n setActive(state, null);\n return;\n }\n let latest: SessionEntry | null = null;\n for (const entry of state.sessionRegistry.values()) {\n if (!latest || entry.lastTouched > latest.lastTouched) latest = entry;\n }\n setActive(state, latest ? latest.sessionId : null, latest?.actions);\n}\n\nfunction sendMediaCommand(state: MediaServiceState, sessionId: string, action: MediaAction, value?: number): void {\n const entry = state.sessionRegistry.get(sessionId);\n if (!entry) return;\n const send = state.sendHandles.get(entry.windowId);\n if (!send) return;\n const payload: MediaCommandMessage = {\n type: 'media.command',\n sessionId,\n action,\n ...(typeof value === 'number' ? { value } : {}),\n };\n send(payload as NappletMessage);\n}\n\nfunction registerWindowSession(state: MediaServiceState, windowId: string, sessionId: string): void {\n if (!state.windowSessions.has(windowId)) state.windowSessions.set(windowId, new Set());\n state.windowSessions.get(windowId)!.add(sessionId);\n}\n\nfunction sendSessionCreateResult(\n send: (msg: NappletMessage) => void,\n id: string | undefined,\n fields: Omit<MediaSessionCreateResultEnvelope, 'type' | 'id'>,\n): void {\n send({\n type: 'media.session.create.result',\n id: id ?? '',\n ...fields,\n } as NappletMessage);\n}\n\nfunction isMediaPlaybackOwner(value: unknown): value is MediaPlaybackOwner {\n return value === 'shell' || value === 'napplet';\n}\n\nfunction hasSourceRef(source: MediaSourceRef | undefined): boolean {\n if (!source) return false;\n if (typeof source.url === 'string' && source.url.length > 0) return true;\n if (typeof source.blossomHash === 'string' && source.blossomHash.length > 0) return true;\n if (source.nostr) {\n return Boolean(source.nostr.eventId || source.nostr.address);\n }\n return false;\n}\n\nfunction canonicalizeSessionId(\n state: MediaServiceState,\n windowId: string,\n preferredSessionId: string | undefined,\n): string {\n const trimmed = typeof preferredSessionId === 'string' ? preferredSessionId.trim() : '';\n const hint = trimmed || `session-${++state.sessionCounter}`;\n if (!state.sessionRegistry.has(hint)) return hint;\n\n let next: string;\n do {\n next = `${windowId}:${hint}:${++state.sessionCounter}`;\n } while (state.sessionRegistry.has(next));\n return next;\n}\n\nfunction handleSessionCreate(\n state: MediaServiceState,\n windowId: string,\n message: MediaSessionCreateEnvelope,\n send: (msg: NappletMessage) => void,\n): void {\n if (!isMediaPlaybackOwner(message.owner)) {\n sendSessionCreateResult(send, message.id, { error: 'missing owner' });\n return;\n }\n\n if (message.owner === 'shell') {\n if (!hasSourceRef(message.source)) {\n sendSessionCreateResult(send, message.id, { owner: 'shell', error: 'missing source' });\n return;\n }\n sendSessionCreateResult(send, message.id, { owner: 'shell', error: 'unsupported owner mode' });\n return;\n }\n\n state.sendHandles.set(windowId, send);\n const sessionId = canonicalizeSessionId(state, windowId, message.sessionId);\n const entry: SessionEntry = {\n sessionId,\n windowId,\n owner: message.owner,\n source: message.source,\n metadata: message.metadata,\n state: undefined,\n actions: message.capabilities ?? DEFAULT_MEDIA_ACTIONS,\n lastTouched: ++state.touchCounter,\n };\n state.sessionRegistry.set(sessionId, entry);\n registerWindowSession(state, windowId, sessionId);\n setActive(state, sessionId, entry.actions);\n state.options.onSessionCreate?.(windowId, sessionId, message.metadata);\n sendSessionCreateResult(send, message.id, { sessionId, owner: message.owner });\n}\n\nfunction handleSessionUpdate(state: MediaServiceState, windowId: string, message: MediaSessionUpdateMessage): void {\n const entry = state.sessionRegistry.get(message.sessionId);\n if (entry) {\n entry.metadata = { ...entry.metadata, ...message.metadata };\n entry.lastTouched = ++state.touchCounter;\n if (entry.owner === 'napplet' && message.sessionId === state.activeSessionId && entry.metadata) {\n state.bridge.setMetadata(message.sessionId, entry.metadata);\n }\n }\n state.options.onSessionUpdate?.(windowId, message.sessionId, message.metadata);\n}\n\nfunction handleSessionDestroy(state: MediaServiceState, windowId: string, message: MediaSessionDestroyMessage): void {\n const entry = state.sessionRegistry.get(message.sessionId);\n if (entry) {\n state.sessionRegistry.delete(message.sessionId);\n const set = state.windowSessions.get(entry.windowId);\n if (set) {\n set.delete(message.sessionId);\n if (set.size === 0) state.windowSessions.delete(entry.windowId);\n }\n state.bridge.destroySession?.(message.sessionId);\n if (message.sessionId === state.activeSessionId) promoteNextActiveOrClear(state);\n }\n state.options.onSessionDestroy?.(windowId, message.sessionId);\n}\n\nfunction handleMediaState(state: MediaServiceState, windowId: string, message: MediaStateMessage): void {\n const entry = state.sessionRegistry.get(message.sessionId);\n if (entry?.owner === 'napplet') {\n entry.state = {\n status: message.status,\n position: message.position,\n duration: message.duration,\n volume: message.volume,\n };\n entry.lastTouched = ++state.touchCounter;\n if (state.activeSessionId !== message.sessionId) setActive(state, message.sessionId, entry.actions);\n else state.bridge.setPlaybackState(message.sessionId, message.status);\n }\n state.options.onState?.(windowId, message.sessionId, message);\n}\n\nfunction handleMediaCapabilities(\n state: MediaServiceState,\n windowId: string,\n message: MediaCapabilitiesMessage,\n): void {\n const entry = state.sessionRegistry.get(message.sessionId);\n if (entry?.owner === 'napplet') {\n entry.actions = message.actions;\n entry.lastTouched = ++state.touchCounter;\n if (message.sessionId === state.activeSessionId) {\n state.bridge.setActiveSession?.(message.sessionId, entry.actions);\n }\n }\n state.options.onCapabilities?.(windowId, message.sessionId, message.actions);\n}\n\nfunction handleMediaMessage(\n state: MediaServiceState,\n windowId: string,\n message: NappletMessage,\n send: (msg: NappletMessage) => void,\n): void {\n switch (message.type) {\n case 'media.session.create':\n handleSessionCreate(state, windowId, message as MediaSessionCreateEnvelope, send);\n return;\n case 'media.session.update':\n handleSessionUpdate(state, windowId, message as MediaSessionUpdateMessage);\n return;\n case 'media.session.destroy':\n handleSessionDestroy(state, windowId, message as MediaSessionDestroyMessage);\n return;\n case 'media.state':\n handleMediaState(state, windowId, message as MediaStateMessage);\n return;\n case 'media.capabilities':\n handleMediaCapabilities(state, windowId, message as MediaCapabilitiesMessage);\n return;\n default: {\n const id = (message as NappletMessage & { id?: string }).id ?? '';\n send({\n type: `${message.type}.error`,\n id,\n error: `Unknown media method: ${message.type}`,\n } as NappletMessage);\n }\n }\n}\n\nfunction destroyWindowSessions(state: MediaServiceState, windowId: string): void {\n const sessions = state.windowSessions.get(windowId);\n if (sessions) {\n const ownedActive = state.activeSessionId !== null && sessions.has(state.activeSessionId);\n for (const sessionId of sessions) {\n state.sessionRegistry.delete(sessionId);\n state.bridge.destroySession?.(sessionId);\n }\n state.windowSessions.delete(windowId);\n if (ownedActive) promoteNextActiveOrClear(state);\n }\n state.sendHandles.delete(windowId);\n}\n\nfunction destroyMediaState(state: MediaServiceState, unsubscribeAction: () => void): void {\n unsubscribeAction();\n for (const sessionId of state.sessionRegistry.keys()) state.bridge.destroySession?.(sessionId);\n state.bridge.setActiveSession?.(null);\n state.sessionRegistry.clear();\n state.windowSessions.clear();\n state.sendHandles.clear();\n state.activeSessionId = null;\n state.touchCounter = 0;\n state.sessionCounter = 0;\n}\n\n/**\n * Create a media NAP service handler with navigator.mediaSession integration.\n *\n * Implements the 5 napplet->shell media.* request types defined in\n * `@napplet/nap/media`. Only `media.session.create` produces a reply\n * envelope (`media.session.create.result`) — the remaining four\n * (`session.update`, `session.destroy`, `state`, `capabilities`) are\n * fire-and-forget per the NAP spec.\n *\n * Unknown `media.*` actions produce a `<type>.error` envelope so\n * napplets are never left hanging on a malformed request.\n *\n * @param options - Optional host callbacks for session lifecycle + state, plus\n * mediaSessionTarget and documentTarget for test injection, and an optional\n * hostBridge for native-OS media backend delegation.\n * @returns A ServiceHandler (with `destroy()`) to register with the runtime\n *\n * @example\n * ```ts\n * import { createMediaService } from '@kehto/services';\n *\n * const media = createMediaService();\n * runtime.registerService('media', media);\n * // Later, on shell teardown:\n * media.destroy();\n * ```\n */\nexport function createMediaService(options: MediaServiceOptions = {}): ServiceHandler & { destroy(): void } {\n const descriptor: ServiceDescriptor = {\n name: 'media',\n version: MEDIA_SERVICE_VERSION,\n description: options.hostBridge\n ? 'NIP-5D media NAP reference handler (host-bridge delegated)'\n : 'NIP-5D media NAP reference handler (navigator.mediaSession mirror)',\n };\n\n const bridge: HostMediaBridge = options.hostBridge\n ?? createBrowserMediaBridge({\n mediaSessionTarget: options.mediaSessionTarget,\n documentTarget: options.documentTarget,\n });\n const state = createMediaServiceState(options, bridge);\n\n const unsubscribeAction = bridge.onAction((sessionId, action, value) => {\n sendMediaCommand(state, sessionId, action, value);\n });\n\n return {\n descriptor,\n\n handleMessage(windowId: string, message: NappletMessage, send: (msg: NappletMessage) => void): void {\n handleMediaMessage(state, windowId, message, send);\n },\n\n onWindowDestroyed(windowId: string): void {\n destroyWindowSessions(state, windowId);\n },\n\n destroy(): void {\n destroyMediaState(state, unsubscribeAction);\n },\n };\n}\n","/**\n * notify-service.ts — NIP-5D notify NAP reference service (stub-level).\n *\n * Handles the 5 napplet -> shell request types from `@napplet/nap/notify`:\n * - `notify.send` -> `notify.send.result` (shell-assigned id)\n * - `notify.dismiss` -> fire-and-forget\n * - `notify.badge` -> fire-and-forget\n * - `notify.channel.register` -> fire-and-forget\n * - `notify.permission.request` -> `notify.permission.result { granted }`\n *\n * Stub-level: no real Notification API calls, no real channel registry.\n * Host apps wire a real backend via\n * `runtime.registerService('notify', realHandler)`.\n *\n * Coexistence note: this is SEPARATE from\n * `packages/services/src/notification-service.ts`, which is the legacy\n * inc-topic-based notification registry (operates on `inc.emit` topics\n * under `notifications:*`). `notify-service.ts` is the canonical\n * `@napplet/nap/notify` NIP-5D path and lives alongside the legacy module.\n * If the host app registers this service via\n * `runtime.registerService('notify', ...)`, @napplet/nap/notify messages\n * land here; the legacy inc-emit topic path remains untouched.\n *\n * Shell -> napplet push messages (`notify.action`, `notify.clicked`,\n * `notify.dismissed`, `notify.controls`) are not emitted by this stub —\n * they are the host app's responsibility and are deferred to a future plan.\n */\n\nimport type { NappletMessage } from '@napplet/core';\n// DRIFT-CORE-06 — Phase 11-deviation: ServiceDescriptor dropped from @napplet/core\n// v0.2.0+ (napplet phase-81). Re-exported from @kehto/runtime (canonical home after Phase 24 DRIFT-01).\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\nimport type {\n NotifySendMessage,\n NotifySendResultMessage,\n NotifyPermissionRequestMessage,\n NotifyPermissionResultMessage,\n} from '@napplet/nap/notify/types';\n\n/** Notify service version — follows semver. */\nconst NOTIFY_SERVICE_VERSION = '1.0.0';\n\n/**\n * Optional configuration for `createNotifyService`.\n *\n * @example\n * ```ts\n * const notify = createNotifyService({\n * generateId: () => crypto.randomUUID(),\n * defaultGrant: false,\n * onSend: (windowId, msg) => console.log(`napplet ${windowId} sent ${msg.title}`),\n * });\n * runtime.registerService('notify', notify);\n * ```\n */\nexport interface NotifyServiceOptions {\n /**\n * Generate a shell-assigned notification ID for `notify.send.result`.\n * Default: a monotonically increasing `shell-<n>` counter.\n */\n generateId?: () => string;\n /**\n * Default permission grant for `notify.permission.request`.\n * Host apps that want real permission prompts should replace this\n * service with a real backend via `runtime.registerService`.\n * Default: `true`.\n */\n defaultGrant?: boolean;\n /**\n * Called synchronously when a napplet dispatches `notify.send`.\n * Intended for host-app plumbing (UI toast, logging, etc.) without\n * requiring a full backend replacement.\n */\n onSend?: (windowId: string, payload: NotifySendMessage) => void;\n}\n\n/**\n * Create a stub-level notify service handler.\n *\n * Answers the 5 napplet->shell request types from `@napplet/nap/notify`.\n * Does NOT implement a real backend (no DOM Notification API, no channel\n * registry, no permission prompt). Host apps replace this via\n * `runtime.registerService('notify', realHandler)` when a real backend is\n * needed.\n *\n * @param options - Optional service configuration (see NotifyServiceOptions)\n * @returns A ServiceHandler to register with the runtime under domain `notify`\n *\n * @example\n * ```ts\n * import { createNotifyService } from '@kehto/services';\n *\n * const notify = createNotifyService();\n * runtime.registerService('notify', notify);\n * ```\n */\nexport function createNotifyService(options: NotifyServiceOptions = {}): ServiceHandler {\n let counter = 0;\n const gen = options.generateId ?? ((): string => {\n counter += 1;\n return `shell-${counter}`;\n });\n const defaultGrant = options.defaultGrant ?? true;\n\n const descriptor: ServiceDescriptor = {\n name: 'notify',\n version: NOTIFY_SERVICE_VERSION,\n description: 'NIP-5D notify NAP reference handler (stub)',\n };\n\n return {\n descriptor,\n\n handleMessage(windowId: string, message: NappletMessage, send: (msg: NappletMessage) => void): void {\n switch (message.type) {\n case 'notify.send': {\n const m = message as NotifySendMessage;\n options.onSend?.(windowId, m);\n const result: NotifySendResultMessage = {\n type: 'notify.send.result',\n id: m.id,\n notificationId: gen(),\n };\n send(result as NappletMessage);\n return;\n }\n\n case 'notify.dismiss':\n case 'notify.badge':\n case 'notify.channel.register':\n // state to mutate; host apps with real backends override.\n return;\n\n case 'notify.permission.request': {\n const m = message as NotifyPermissionRequestMessage;\n const result: NotifyPermissionResultMessage = {\n type: 'notify.permission.result',\n id: m.id,\n granted: defaultGrant,\n };\n send(result as NappletMessage);\n return;\n }\n\n default: {\n const id = (message as NappletMessage & { id?: string }).id ?? '';\n send({\n type: `${message.type}.error`,\n id,\n error: `Unknown notify method: ${message.type}`,\n } as NappletMessage);\n }\n }\n },\n\n onWindowDestroyed(_windowId: string): void {\n // No per-window state in the stub. Host apps with real backends\n // override to clean up pending notifications / channel registrations.\n },\n };\n}\n","/**\n * theme-service.ts — NIP-5D theme NAP reference service.\n *\n * Handles the single napplet->shell request type from @napplet/nap/theme:\n * - `theme.get` -> `theme.get.result { theme }` (current theme)\n *\n * Exposes a host-facing `publishTheme(theme)` handle that:\n * 1. Replaces the service's internal current theme.\n * 2. Invokes `options.onBroadcast(envelope)` synchronously with a\n * `theme.changed` envelope so the shell adapter (Plan 13-02) can\n * fan-out the push to every registered napplet.\n * 3. Returns the envelope so callers can use it directly if they prefer.\n *\n * The default theme values are centralized here and mirrored in the runtime's\n * fallback path (`packages/runtime/src/runtime.ts`) — runtime does NOT import\n * from @kehto/services because services depends on runtime (one-way only).\n *\n * Host apps replace this via `runtime.registerService('theme', realHandler)`\n * when real CSS injection / storage / dark-mode logic is needed.\n *\n * @example\n * ```ts\n * import { createThemeService } from '@kehto/services';\n *\n * const theme = createThemeService({\n * onBroadcast: (envelope) => broadcastToAllNapplets(envelope),\n * });\n * runtime.registerService('theme', theme.handler);\n *\n * // Later, when the user flips dark/light mode:\n * theme.publishTheme(newTheme);\n * ```\n */\n\nimport type { NappletMessage } from '@napplet/core';\n// DRIFT-CORE-06 — Phase 11-deviation: ServiceDescriptor dropped from\n// @napplet/core v0.2.0+ (napplet phase-81). Re-exported from\n// @kehto/runtime (canonical home after Phase 24 DRIFT-01).\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\nimport type {\n Theme,\n ThemeChangedMessage,\n ThemeGetResultMessage,\n} from '@napplet/nap/theme/types';\n\n/** Theme service version — follows semver. */\nconst THEME_SERVICE_VERSION = '1.0.0';\n\n/**\n * Canonical default theme values.\n *\n * Must stay synchronized with the runtime fallback constant at\n * `packages/runtime/src/runtime.ts` (THEME_FALLBACK_DEFAULT). The runtime\n * keeps a local copy instead of importing from @kehto/services to avoid a\n * runtime->services dependency (services depends on runtime; one-way).\n */\nconst DEFAULT_THEME: Theme = {\n colors: {\n background: '#0a0a0a',\n text: '#e0e0e0',\n primary: '#7aa2f7',\n },\n // fonts, background, title intentionally undefined — all optional per\n // @napplet/nap/theme Theme interface.\n};\n\n/**\n * Configuration for `createThemeService`.\n *\n * @example\n * ```ts\n * const theme = createThemeService({\n * initialTheme: { colors: { background: '#fff', text: '#000', primary: '#00f' } },\n * onBroadcast: (envelope) => shellBridge.broadcastToAll(envelope),\n * });\n * ```\n */\nexport interface ThemeServiceOptions {\n /**\n * Override the default theme payload. If omitted, the service starts with\n * the canonical defaults (`#0a0a0a / #e0e0e0 / #7aa2f7`).\n */\n initialTheme?: Theme;\n\n /**\n * Called synchronously from `publishTheme(theme)` with a `theme.changed`\n * envelope. Intended for the shell adapter (Plan 13-02) to fan-out the\n * push to every registered napplet via the runtime's sendToNapplet\n * primitive.\n *\n * Keep this callback shape framework-agnostic — the service does NOT\n * import any shell / browser APIs.\n */\n onBroadcast?: (envelope: ThemeChangedMessage) => void;\n}\n\n/**\n * A theme service bundle — the ServiceHandler that handles `theme.*`\n * envelopes, the host-facing `publishTheme(theme)` handle for theme-change\n * broadcasts, and a `getCurrentTheme()` accessor for host-side reads.\n */\nexport interface ThemeService {\n /** Register this with the runtime via `runtime.registerService('theme', handler)`. */\n handler: ServiceHandler;\n\n /**\n * Publish a theme-change to the shell adapter. Updates the service's\n * internal current theme, invokes `options.onBroadcast` with a\n * `theme.changed` envelope, and returns the envelope.\n *\n * @param theme - The new theme payload\n * @returns A `theme.changed` envelope (same one passed to onBroadcast)\n */\n publishTheme: (theme: Theme) => ThemeChangedMessage;\n\n /** Return the current theme. Equivalent to the payload a napplet's `theme.get` would receive. */\n getCurrentTheme: () => Theme;\n}\n\n/**\n * Create a theme service that handles the NIP-5D `theme.*` NAP.\n *\n * Answers `theme.get` with the current theme (default or\n * `options.initialTheme`). Exposes `publishTheme(theme)` for the host app to\n * broadcast theme changes to every registered napplet — the shell adapter\n * (Plan 13-02) wires `onBroadcast` to `runtime.sendToNapplet` fan-out.\n *\n * @param options - Optional service configuration (see ThemeServiceOptions)\n * @returns A ThemeService bundle ready for `runtime.registerService('theme', service.handler)`\n *\n * @example\n * ```ts\n * import { createThemeService } from '@kehto/services';\n *\n * const theme = createThemeService();\n * runtime.registerService('theme', theme.handler);\n * theme.publishTheme({ colors: { background: '#fff', text: '#000', primary: '#00f' } });\n * ```\n */\nexport function createThemeService(options: ThemeServiceOptions = {}): ThemeService {\n let currentTheme: Theme = options.initialTheme ?? DEFAULT_THEME;\n\n const descriptor: ServiceDescriptor = {\n name: 'theme',\n version: THEME_SERVICE_VERSION,\n description: 'NIP-5D theme NAP reference handler',\n };\n\n const handler: ServiceHandler = {\n descriptor,\n\n handleMessage(\n _windowId: string,\n message: NappletMessage,\n send: (msg: NappletMessage) => void,\n ): void {\n const id = (message as NappletMessage & { id?: string }).id ?? '';\n\n if (message.type === 'theme.get') {\n const result: ThemeGetResultMessage = {\n type: 'theme.get.result',\n id,\n theme: currentTheme,\n };\n send(result as NappletMessage);\n return;\n }\n\n send({\n type: `${message.type}.error`,\n id,\n error: `Unknown theme method: ${message.type}`,\n } as NappletMessage);\n },\n\n // Theme service has no per-window state to clean up.\n onWindowDestroyed(_windowId: string): void {\n /* no-op */\n },\n };\n\n function publishTheme(theme: Theme): ThemeChangedMessage {\n currentTheme = theme;\n const envelope: ThemeChangedMessage = { type: 'theme.changed', theme };\n options.onBroadcast?.(envelope);\n return envelope;\n }\n\n function getCurrentTheme(): Theme {\n return currentTheme;\n }\n\n return { handler, publishTheme, getCurrentTheme };\n}\n","/**\n * config-service.ts — NAP-CONFIG reference service (9th NAP domain, v1.7 Phase 39).\n *\n * Shell-side reference implementation for the canonical NAP-CONFIG wire\n * protocol (`@napplet/nap/config`, published at `^0.3.0`). Handles the full\n * 8-message discriminated union: 5 napplet→shell request types + 3\n * shell→napplet result/push types.\n *\n * ──────────────────────────── SCOPE BOUNDARY (CONFIG-04) ─────────────────────────\n * NAP-CONFIG is **shell-managed per-napplet configuration**. Napplets observe\n * values via `config.get` (one-shot) or `config.subscribe` (snapshot + live\n * push). The shell is the **sole writer** — there is intentionally **NO**\n * `config.set` wire message. Napplets cannot mutate configuration values;\n * the shell owns persistence and the update flow.\n *\n * Do NOT use this service as a general key-value store. NAP-STORAGE\n * (`state:read` / `state:write`) remains the general KV surface. Using\n * NAP-CONFIG to store e.g. `{ lastScrollPosition: 420 }` is an anti-pattern\n * (H-07 in PITFALLS.md) — such state belongs in NAP-STORAGE.\n * ──────────────────────────────────────────────────────────────────────────────────\n *\n * Host integration: provide `getValues()` returning the current\n * `ConfigValues` snapshot. Call the returned `publishValues(newValues)`\n * whenever the configuration changes — the service fans the new snapshot\n * out to every napplet that has an active `config.subscribe`.\n *\n * Optional: provide `registerSchema` to accept napplet-declared schemas at\n * runtime (the ref impl does a minimal shape check using the Core Subset\n * validator; use `ajv` in host impls that need strict draft-07 conformance).\n * Provide `openSettings` to open a shell-side UI for the napplet (no\n * response envelope — fire-and-forget UI hook).\n *\n * @example\n * ```ts\n * import { createConfigService } from '@kehto/services';\n *\n * const configFixtures = { theme: 'dark', density: 'compact', recentSearches: [] };\n * const config = createConfigService({\n * getValues: () => ({ ...configFixtures }),\n * });\n * runtime.registerService('config', config.handler);\n *\n * // Later, when shell-side values change:\n * configFixtures.theme = 'light';\n * config.publishValues({ ...configFixtures });\n * ```\n */\n\nimport type { NappletMessage } from '@napplet/core';\n// DRIFT-CORE-06 — Phase 11-deviation: ServiceDescriptor dropped from @napplet/core\n// v0.2.0+ (napplet phase-81). Re-exported from @kehto/runtime (canonical home after Phase 24 DRIFT-01).\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\nimport type {\n ConfigGetMessage,\n ConfigRegisterSchemaMessage,\n ConfigOpenSettingsMessage,\n ConfigValues,\n ConfigValuesMessage,\n ConfigRegisterSchemaResultMessage,\n ConfigSchemaErrorCode,\n NappletConfigSchema,\n} from '@napplet/nap/config/types';\n\n/** Config service version — follows semver. */\nconst CONFIG_SERVICE_VERSION = '1.0.0';\n\n/**\n * Shape returned by a successful `registerSchema` result (ok=true) or a\n * rejection (ok=false + code + error). Mirrors the wire envelope fields.\n */\nexport type ConfigSchemaValidation =\n | { ok: true }\n | { ok: false; code: ConfigSchemaErrorCode; error: string };\n\n/**\n * Configuration options for `createConfigService` (options-as-bridge\n * per v1.6 Decision 18).\n *\n * @example\n * ```ts\n * const config = createConfigService({\n * getValues: () => ({ theme: 'dark', density: 'compact' }),\n * openSettings: (windowId, section) => showSettingsPanel(windowId, section),\n * });\n * ```\n */\nexport interface ConfigServiceOptions {\n /**\n * Returns the current configuration values snapshot.\n * Called on every `config.get` and at every `config.subscribe` initial push.\n * Implementations should return a fresh object (not a mutable reference).\n */\n getValues(): ConfigValues;\n\n /**\n * Optional: receive notification when a napplet subscribes to config updates.\n * Fire-and-forget — the service tracks the subscription internally regardless.\n */\n onSubscribe?: (windowId: string) => void;\n\n /**\n * Optional: receive notification when a napplet unsubscribes.\n */\n onUnsubscribe?: (windowId: string) => void;\n\n /**\n * Optional: validate and store a napplet-provided schema.\n *\n * If omitted, the ref impl runs its own Core Subset check (hand-coded\n * validator; 30-50 lines) and returns ok/reject. Hosts that need strict\n * draft-07 conformance should provide an ajv-backed implementation.\n *\n * Return shape mirrors `config.registerSchema.result` wire envelope\n * (minus the `id` — the dispatch layer correlates).\n */\n registerSchema?: (\n windowId: string,\n schema: NappletConfigSchema,\n version: number | undefined,\n ) => ConfigSchemaValidation;\n\n /**\n * Optional: open the shell-side settings UI for this napplet.\n * Fire-and-forget — no response envelope per the wire spec.\n * If omitted, `config.openSettings` is silently dropped (D10 allows\n * the config-demo napplet to function without a settings UI).\n */\n openSettings?: (windowId: string, section: string | undefined) => void;\n}\n\n/**\n * NAP-CONFIG reference service bundle — `handler` to register with the\n * runtime, `publishValues` for the host app to push updates live to all\n * subscribed napplets.\n */\nexport interface ConfigService {\n /** Register this with the runtime via `runtime.registerService('config', handler)`. */\n handler: ServiceHandler;\n\n /**\n * Broadcast a new values snapshot to every napplet with an active\n * `config.subscribe`. Each subscriber receives a `config.values` envelope\n * with no `id` (push form per wire spec — absence of `id` distinguishes\n * push from correlated `config.get` response).\n *\n * @param values - The new configuration snapshot (full object, not a diff)\n */\n publishValues(values: ConfigValues): void;\n}\n\n/**\n * Minimal JSON Schema validator covering the NAP-CONFIG Core Subset:\n * type: object / string / number / boolean / array, required[], default, properties.\n *\n * Explicitly rejects: $ref, pattern, oneOf/anyOf/allOf/not, if/then/else.\n * Returns { ok: true } on shape sanity, otherwise an error code per the\n * canonical ConfigSchemaErrorCode union.\n *\n * Host apps that need strict draft-07 conformance should supply a custom\n * `registerSchema` callback backed by ajv@8.\n */\nfunction validateCoreSubset(schema: unknown): ConfigSchemaValidation {\n if (typeof schema !== 'object' || schema === null || Array.isArray(schema)) {\n return { ok: false, code: 'invalid-schema', error: 'schema root must be an object' };\n }\n const s = schema as Record<string, unknown>;\n\n // Reject forbidden keywords (NAP-CONFIG Core Subset limits per spec).\n if ('$ref' in s) {\n return { ok: false, code: 'ref-not-allowed', error: '$ref is not permitted in the Core Subset' };\n }\n if ('pattern' in s) {\n return {\n ok: false,\n code: 'pattern-not-allowed',\n error: 'pattern is not permitted in the Core Subset',\n };\n }\n if ('oneOf' in s || 'anyOf' in s || 'allOf' in s || 'not' in s) {\n return {\n ok: false,\n code: 'invalid-schema',\n error: 'oneOf/anyOf/allOf/not are not permitted in the Core Subset',\n };\n }\n if ('if' in s || 'then' in s || 'else' in s) {\n return {\n ok: false,\n code: 'invalid-schema',\n error: 'if/then/else are not permitted in the Core Subset',\n };\n }\n if (s.type !== 'object') {\n return { ok: false, code: 'invalid-schema', error: 'schema root must have type: \"object\"' };\n }\n\n // Shallow properties check: each declared property must use a supported type.\n const props = s.properties;\n if (props !== undefined && (typeof props !== 'object' || props === null)) {\n return { ok: false, code: 'invalid-schema', error: 'properties must be an object' };\n }\n if (props) {\n for (const [key, val] of Object.entries(props as Record<string, unknown>)) {\n if (typeof val !== 'object' || val === null) {\n return {\n ok: false,\n code: 'invalid-schema',\n error: `property \"${key}\" must be an object schema`,\n };\n }\n const pv = val as Record<string, unknown>;\n const ALLOWED_TYPES = new Set(['string', 'number', 'boolean', 'array', 'object']);\n if (pv.type !== undefined && !ALLOWED_TYPES.has(pv.type as string)) {\n return {\n ok: false,\n code: 'invalid-schema',\n error: `property \"${key}\" must have type: string|number|boolean|array|object`,\n };\n }\n }\n }\n\n return { ok: true };\n}\n\n/**\n * Create a NAP-CONFIG reference service.\n *\n * Shell-writes, napplet-reads. Handles the full `@napplet/nap/config` wire\n * protocol: `config.get` (correlated snapshot), `config.subscribe` /\n * `config.unsubscribe` (live push stream), `config.registerSchema` (optional\n * schema registration + Core Subset validation), `config.openSettings`\n * (optional UI deep-link, fire-and-forget).\n *\n * Returns a `ConfigService` bundle: `{ handler, publishValues }`.\n * Register `handler` with the runtime; call `publishValues(newValues)` from\n * the shell whenever config state changes.\n *\n * @param options - Host-supplied implementation hooks (options-as-bridge,\n * v1.6 Decision 18). `getValues` is required; all other fields are optional.\n * @returns A ConfigService bundle.\n *\n * @see ConfigServiceOptions for the options shape.\n * @see packages/services/src/theme-service.ts for the sibling pattern.\n * @see SCOPE BOUNDARY comment at the top of this file re: NAP-STORAGE separation.\n *\n * @example\n * ```ts\n * import { createConfigService } from '@kehto/services';\n *\n * const config = createConfigService({\n * getValues: () => ({ theme: 'dark', density: 'compact' }),\n * openSettings: (windowId, section) => openSettingsUI(section),\n * });\n * runtime.registerService('config', config.handler);\n *\n * // Push a live update to all subscribers:\n * config.publishValues({ theme: 'light', density: 'compact' });\n * ```\n */\nexport function createConfigService(options: ConfigServiceOptions): ConfigService {\n /**\n * Per-window subscriber set. Maps windowId → the send callback captured at\n * `config.subscribe` time. `publishValues` fans out to every entry.\n */\n const subscribers = new Map<string, (msg: NappletMessage) => void>();\n\n const descriptor: ServiceDescriptor = {\n name: 'config',\n version: CONFIG_SERVICE_VERSION,\n description: 'NAP-CONFIG reference service — shell-writes, napplet-reads configuration',\n };\n\n const handler: ServiceHandler = {\n descriptor,\n\n handleMessage(\n windowId: string,\n message: NappletMessage,\n send: (msg: NappletMessage) => void,\n ): void {\n switch (message.type) {\n case 'config.get': {\n const m = message as ConfigGetMessage;\n const reply: ConfigValuesMessage = {\n type: 'config.values',\n id: m.id,\n values: options.getValues(),\n };\n send(reply as NappletMessage);\n return;\n }\n\n case 'config.subscribe': {\n // Capture the send callback so publishValues can fan pushes out.\n subscribers.set(windowId, send);\n // Immediate initial snapshot push — no `id` (push form per wire spec).\n const push: ConfigValuesMessage = {\n type: 'config.values',\n values: options.getValues(),\n };\n send(push as NappletMessage);\n options.onSubscribe?.(windowId);\n return;\n }\n\n case 'config.unsubscribe': {\n subscribers.delete(windowId);\n options.onUnsubscribe?.(windowId);\n return;\n }\n\n case 'config.registerSchema': {\n const m = message as ConfigRegisterSchemaMessage;\n // Delegate to host-supplied validator if present; otherwise use\n // the built-in Core Subset hand-coded validator (D12).\n const validation: ConfigSchemaValidation = options.registerSchema\n ? options.registerSchema(windowId, m.schema, m.version)\n : validateCoreSubset(m.schema);\n\n const result: ConfigRegisterSchemaResultMessage = validation.ok\n ? { type: 'config.registerSchema.result', id: m.id, ok: true }\n : {\n type: 'config.registerSchema.result',\n id: m.id,\n ok: false,\n code: validation.code,\n error: validation.error,\n };\n send(result as NappletMessage);\n return;\n }\n\n case 'config.openSettings': {\n const m = message as ConfigOpenSettingsMessage;\n // Silently dropped if openSettings hook not provided (D10).\n options.openSettings?.(windowId, m.section);\n return;\n }\n\n default:\n // Unknown config.* message — silently ignored per NIP-5D.\n return;\n }\n },\n\n onWindowDestroyed(windowId: string): void {\n // A napplet iframe was destroyed — drop any active subscription so we\n // don't retain the stale send callback in the subscribers map.\n subscribers.delete(windowId);\n },\n };\n\n /**\n * Broadcast a new config values snapshot to every subscribed napplet.\n * Each subscriber receives a `config.values` push envelope (no `id` —\n * absence of `id` distinguishes a push from a correlated `config.get`\n * response per the NAP-CONFIG wire spec).\n */\n function publishValues(values: ConfigValues): void {\n const envelope: ConfigValuesMessage = {\n type: 'config.values',\n values,\n };\n for (const send of subscribers.values()) {\n try {\n send(envelope as NappletMessage);\n } catch {\n // Subscriber's send callback threw (e.g., iframe gone without\n // onWindowDestroyed firing yet). Best-effort — drop silently.\n }\n }\n }\n\n return { handler, publishValues };\n}\n","/**\n * resource-service.ts — NAP-RESOURCE reference service (10th NAP domain, v1.7 Phase 40).\n *\n * Shell-side reference implementation for the canonical NAP-RESOURCE wire\n * protocol (`internal-resource.ts` in @kehto/shell/src/types; kehto-internal\n * model per PROJECT.md Decision #31. Kehto keeps legacy single-fetch fields\n * for existing callers and also emits the current NAP-RESOURCE fields.\n * Handles:\n * Inbound: resource.bytes, resource.bytesMany, resource.cancel\n * Outbound: resource.bytes.result, resource.bytes.error,\n * resource.bytesMany.result, resource.bytesMany.error\n *\n * ──────────────────────── SCOPE BOUNDARY (RESOURCE-01) ────────────────────────\n * NAP-RESOURCE is an **authenticated fetch proxy** — read-only, atomic.\n *\n * This service is NOT responsible for:\n * - Streaming / chunked responses (host-app concern)\n * - Response caching / conditional requests (host-app concern)\n * - Upload / POST body construction (NAP-RESOURCE v1.7 is read-only)\n * - Redirect limits, MIME sniffing, SVG rasterization (host-fetch concern)\n * - Private-IP blocking, SSRF mitigation (host-provided-fetch responsibility)\n *\n * These belong to the host-app's `fetch` implementation per D7 and\n * SHELL-RESOURCE-POLICY.md (Phase 40 Plan 40-03). Kehto ships a reference\n * service; production hardening is the host app's concern.\n * ──────────────────────────────────────────────────────────────────────────────\n *\n * Host integration: provide `fetch`, `isOriginGranted`, `getConnectGrants`,\n * and `resolveIdentity`. ALL FOUR are required from day one (H-03 prevention).\n *\n * @example\n * ```ts\n * import { createResourceService } from '@kehto/services';\n *\n * const resourceSvc = createResourceService({\n * fetch: (url, init) => globalThis.fetch(url, init),\n * isOriginGranted: (origin, grants) => grants.includes(origin),\n * getConnectGrants: (dTag, hash) => myOriginGrantStore.getOrigins(dTag, hash),\n * resolveIdentity: (windowId) => sessionRegistry.getEntryByWindowId(windowId) ?? null,\n * });\n * runtime.registerService('resource', resourceSvc);\n * ```\n */\n\nimport type { NappletMessage } from '@napplet/core';\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\n\n/** Resource service version — follows semver. */\nconst RESOURCE_SERVICE_VERSION = '1.0.0';\n\n/**\n * Options for `createResourceService` (options-as-bridge per v1.6 Decision 18).\n *\n * ALL FOUR fields are required. The factory throws at construction if any is\n * missing — H-03 prevention: the grants source (`getConnectGrants`) MUST be\n * wired from day one so there is no window where resource requests bypass the\n * grant check.\n *\n * @see PITFALLS.md:228 (H-03) — grants-source coupling must be present at construction\n */\nexport interface ResourceServiceOptions {\n /**\n * Host-supplied fetch implementation. Receives the URL, a partial init\n * (method, headers, signal), and must return a `Response`-compatible promise.\n *\n * The host's `fetch` is the ONLY place to implement redirect limits, MIME\n * sniffing, SVG rasterization, private-IP / SSRF blocking, etc.\n * This service does NOT filter: it proxies transparently.\n *\n * @param url - The URL from the resource.bytes request\n * @param init - Method, headers (from napplet), and an AbortSignal\n */\n fetch(\n url: string,\n init: { method?: string; headers?: Record<string, string>; signal: AbortSignal }\n ): Promise<Response>;\n\n /**\n * Returns true if `origin` is present in `grants` (the list returned by\n * `getConnectGrants` for the napplet's dTag + aggregateHash).\n *\n * The reference implementation is simply `grants.includes(origin)`. Host apps\n * may provide normalized-origin comparison if needed.\n *\n * @param origin - Parsed origin of the requested URL (scheme + host + port)\n * @param grants - Readonly list from getConnectGrants for this napplet identity\n */\n isOriginGranted(origin: string, grants: readonly string[]): boolean;\n\n /**\n * Returns the list of allowed fetch origins for the given napplet identity.\n * Called on every `resource.bytes` request — must be synchronous and fast.\n *\n * Host-supplied grant source (e.g. a static per-dTag allowlist map, or any\n * other host-controlled policy). Returns an empty array to deny all origins.\n *\n * H-03 prevention: REQUIRED from day one — factory throws on construction\n * if omitted.\n *\n * @param dTag - The napplet's d-tag (from session registry)\n * @param aggregateHash - The napplet's aggregate hash (from session registry)\n */\n getConnectGrants(dTag: string, aggregateHash: string): readonly string[];\n\n /**\n * Resolve a windowId to the napplet's identity (dTag + aggregateHash).\n * Returns null if the window is not in the session registry.\n *\n * Typically wraps `sessionRegistry.getEntryByWindowId(windowId)`.\n *\n * @param windowId - The iframe window identifier\n */\n resolveIdentity(windowId: string): { dTag: string; aggregateHash: string } | null;\n}\n\n/**\n * Type alias for the ServiceHandler returned by `createResourceService`.\n * Exported for host apps that need to type-annotate the handler reference.\n */\nexport type ResourceService = ServiceHandler;\n\n/**\n * Convert an ArrayBuffer to base64 string, safe for both browser and Node.\n * Chunked in 0x8000-byte slices to avoid `String.fromCharCode(...largeArray)`\n * stack overflow on large responses.\n */\nfunction arrayBufferToBase64(buf: ArrayBuffer): string {\n const bytes = new Uint8Array(buf);\n const CHUNK = 0x8000; // 32 KB\n let binary = '';\n for (let i = 0; i < bytes.length; i += CHUNK) {\n binary += String.fromCharCode(...bytes.subarray(i, i + CHUNK));\n }\n return btoa(binary);\n}\n\ninterface ResourceRequestState {\n inFlight: Map<string, { controller: AbortController; windowId: string }>;\n perWindow: Map<string, Set<string>>;\n}\n\ntype LegacyResourceErrorCode = 'denied' | 'invalid-url' | 'canceled' | 'network-error';\n\ntype ResourceErrorCode =\n | 'invalid-request'\n | 'not-found'\n | 'blocked-by-policy'\n | 'timeout'\n | 'too-large'\n | 'unsupported-scheme'\n | 'decode-failed'\n | 'network-error'\n | 'quota-exceeded';\n\ntype ResourceFetchSuccess = {\n ok: true;\n url: string;\n blob: Blob;\n mime: string;\n status: number;\n headers: Record<string, string>;\n bodyBase64: string;\n};\n\ntype ResourceFetchFailure = {\n ok: false;\n url: string;\n error: ResourceErrorCode;\n code: LegacyResourceErrorCode;\n message: string;\n};\n\ntype ResourceFetchItem = ResourceFetchSuccess | ResourceFetchFailure;\n\nfunction assertResourceOptions(options: ResourceServiceOptions): void {\n if (\n typeof options?.fetch !== 'function' ||\n typeof options?.isOriginGranted !== 'function' ||\n typeof options?.getConnectGrants !== 'function' ||\n typeof options?.resolveIdentity !== 'function'\n ) {\n throw new Error(\n '[RESOURCE-01 / H-03] createResourceService requires {fetch, isOriginGranted, getConnectGrants, resolveIdentity} ' +\n '— all four options are required from day one. ' +\n 'The grants source (getConnectGrants) MUST be wired at construction time to prevent unguarded fetch proxying.',\n );\n }\n}\n\nfunction trackRequest(\n state: ResourceRequestState,\n requestId: string,\n windowId: string,\n controller: AbortController,\n): void {\n state.inFlight.set(requestId, { controller, windowId });\n if (!state.perWindow.has(windowId)) {\n state.perWindow.set(windowId, new Set());\n }\n state.perWindow.get(windowId)!.add(requestId);\n}\n\nfunction untrackRequest(state: ResourceRequestState, requestId: string): void {\n const entry = state.inFlight.get(requestId);\n if (entry) {\n state.inFlight.delete(requestId);\n state.perWindow.get(entry.windowId)?.delete(requestId);\n }\n}\n\nfunction sendResourceError(\n send: (m: NappletMessage) => void,\n requestId: string,\n code: LegacyResourceErrorCode,\n message: string,\n error: ResourceErrorCode = toResourceError(code),\n type: 'resource.bytes.error' | 'resource.bytesMany.error' = 'resource.bytes.error',\n): void {\n send({\n type,\n id: requestId,\n requestId,\n error,\n code,\n message,\n } as NappletMessage);\n}\n\nfunction sendBytesManyError(\n send: (m: NappletMessage) => void,\n requestId: string,\n code: LegacyResourceErrorCode,\n message: string,\n error: ResourceErrorCode,\n): void {\n sendResourceError(send, requestId, code, message, error, 'resource.bytesMany.error');\n}\n\nfunction toResourceError(code: LegacyResourceErrorCode): ResourceErrorCode {\n switch (code) {\n case 'denied':\n return 'blocked-by-policy';\n case 'invalid-url':\n return 'invalid-request';\n case 'canceled':\n return 'timeout';\n case 'network-error':\n return 'network-error';\n }\n}\n\nfunction parseResourceUrl(url: string): URL | null {\n try {\n return new URL(url);\n } catch {\n return null;\n }\n}\n\nfunction collectResponseHeaders(response: Response): Record<string, string> {\n const headers: Record<string, string> = {};\n response.headers.forEach((value, key) => {\n headers[key] = value;\n });\n return headers;\n}\n\nfunction responseMime(response: Response): string {\n return response.headers.get('content-type') || 'application/octet-stream';\n}\n\nfunction responseBlob(buffer: ArrayBuffer, mime: string): Blob {\n return new Blob([buffer], { type: mime });\n}\n\nfunction requestIdFromMessage(message: NappletMessage & { id?: unknown; requestId?: unknown }): string | null {\n if (typeof message.id === 'string' && message.id.length > 0) return message.id;\n if (typeof message.requestId === 'string' && message.requestId.length > 0) return message.requestId;\n return null;\n}\n\nfunction resourceInvalidRequest(url: string, message: string): ResourceFetchFailure {\n return {\n ok: false,\n url,\n error: 'invalid-request',\n code: 'invalid-url',\n message,\n };\n}\n\nasync function fetchResourceItem(\n options: ResourceServiceOptions,\n identity: { dTag: string; aggregateHash: string },\n url: string,\n init: { method?: string; headers?: Readonly<Record<string, string>> } | undefined,\n signal: AbortSignal,\n): Promise<ResourceFetchItem> {\n const parsedUrl = parseResourceUrl(url);\n if (!parsedUrl) return resourceInvalidRequest(url, `invalid URL: ${url}`);\n const origin = parsedUrl.origin;\n const grants = options.getConnectGrants(identity.dTag, identity.aggregateHash);\n if (!options.isOriginGranted(origin, grants)) {\n return {\n ok: false,\n url,\n error: 'blocked-by-policy',\n code: 'denied',\n message: `origin ${origin} not granted`,\n };\n }\n\n try {\n const response = await options.fetch(url, {\n method: init?.method,\n headers: init?.headers ? { ...init.headers } : undefined,\n signal,\n });\n const buffer = await response.arrayBuffer();\n const headers = collectResponseHeaders(response);\n const mime = responseMime(response);\n return {\n ok: true,\n url,\n blob: responseBlob(buffer, mime),\n mime,\n status: response.status,\n headers,\n bodyBase64: arrayBufferToBase64(buffer),\n };\n } catch (err: unknown) {\n const isAbort =\n signal.aborted ||\n (err instanceof Error && (err.name === 'AbortError' || err.name === 'DOMException'));\n return {\n ok: false,\n url,\n error: isAbort ? 'timeout' : 'network-error',\n code: isAbort ? 'canceled' : 'network-error',\n message: err instanceof Error ? err.message : String(err),\n };\n }\n}\n\nasync function handleBytes(\n options: ResourceServiceOptions,\n state: ResourceRequestState,\n windowId: string,\n msg: { requestId: string; url: string; init?: { method?: string; headers?: Readonly<Record<string, string>> } },\n send: (m: NappletMessage) => void,\n): Promise<void> {\n const { requestId, url, init } = msg;\n const identity = options.resolveIdentity(windowId);\n if (!identity) {\n sendResourceError(send, requestId, 'denied', 'napplet identity not resolvable', 'blocked-by-policy');\n return;\n }\n\n const controller = new AbortController();\n trackRequest(state, requestId, windowId, controller);\n\n try {\n const item = await fetchResourceItem(options, identity, url, init, controller.signal);\n if (!item.ok) {\n sendResourceError(send, requestId, item.code, item.message, item.error);\n return;\n }\n send({\n type: 'resource.bytes.result',\n id: requestId,\n requestId,\n blob: item.blob,\n mime: item.mime,\n status: item.status,\n headers: item.headers,\n bodyBase64: item.bodyBase64,\n } as NappletMessage);\n } finally {\n untrackRequest(state, requestId);\n }\n}\n\nasync function handleBytesMany(\n options: ResourceServiceOptions,\n state: ResourceRequestState,\n windowId: string,\n msg: { requestId: string; urls: readonly string[]; init?: { method?: string; headers?: Readonly<Record<string, string>> } },\n send: (m: NappletMessage) => void,\n): Promise<void> {\n const { requestId, urls, init } = msg;\n if (!Array.isArray(urls) || urls.length === 0 || urls.some((url) => typeof url !== 'string')) {\n sendBytesManyError(send, requestId, 'invalid-url', 'resource.bytesMany requires a non-empty urls array', 'invalid-request');\n return;\n }\n\n const identity = options.resolveIdentity(windowId);\n if (!identity) {\n sendBytesManyError(send, requestId, 'denied', 'napplet identity not resolvable', 'blocked-by-policy');\n return;\n }\n\n const controller = new AbortController();\n trackRequest(state, requestId, windowId, controller);\n try {\n const items: Array<Record<string, unknown>> = [];\n for (const url of urls) {\n const item = await fetchResourceItem(options, identity, url, init, controller.signal);\n if (item.ok) {\n items.push({\n url: item.url,\n ok: true,\n blob: item.blob,\n mime: item.mime,\n });\n } else {\n items.push({\n url: item.url,\n ok: false,\n error: item.error,\n code: item.code,\n message: item.message,\n });\n }\n }\n send({\n type: 'resource.bytesMany.result',\n id: requestId,\n requestId,\n items,\n } as NappletMessage);\n } finally {\n untrackRequest(state, requestId);\n }\n}\n\nfunction handleCancel(state: ResourceRequestState, requestId: string): void {\n const entry = state.inFlight.get(requestId);\n if (entry) {\n entry.controller.abort();\n }\n}\n\nfunction destroyWindowRequests(state: ResourceRequestState, windowId: string): void {\n const requestIds = state.perWindow.get(windowId);\n if (!requestIds) return;\n for (const requestId of requestIds) {\n const entry = state.inFlight.get(requestId);\n if (entry) {\n entry.controller.abort();\n state.inFlight.delete(requestId);\n }\n }\n state.perWindow.delete(windowId);\n}\n\n/**\n * Create a NAP-RESOURCE reference service.\n *\n * Implements the NAP-RESOURCE request/response protocol: `resource.bytes`,\n * `resource.bytesMany`, `resource.cancel`, and their result/error envelopes.\n *\n * On-construction guard (H-03 prevention): all four options are validated at\n * factory call time. If any is missing, the factory throws immediately with a\n * message containing `[RESOURCE-01 / H-03]` so misconfigured shell apps fail\n * loudly at startup rather than silently at first dispatch.\n *\n * Returns a `ServiceHandler` (no `publishValues`-style surface — resource has\n * no shell-initiated push beyond the response/error path).\n *\n * @param options - REQUIRED: fetch, isOriginGranted, getConnectGrants, resolveIdentity\n * @returns ServiceHandler to register via `runtime.registerService('resource', handler)`\n *\n * @example\n * ```ts\n * import { createResourceService } from '@kehto/services';\n *\n * const svc = createResourceService({\n * fetch: (url, init) => globalThis.fetch(url, init),\n * isOriginGranted: (origin, grants) => grants.includes(origin),\n * getConnectGrants: (dTag, hash) => myOriginGrantStore.getOrigins(dTag, hash),\n * resolveIdentity: (windowId) => sessionRegistry.getEntryByWindowId(windowId) ?? null,\n * });\n * runtime.registerService('resource', svc);\n * ```\n */\nexport function createResourceService(options: ResourceServiceOptions): ResourceService {\n assertResourceOptions(options);\n const state: ResourceRequestState = {\n inFlight: new Map<string, { controller: AbortController; windowId: string }>(),\n perWindow: new Map<string, Set<string>>(),\n };\n\n const descriptor: ServiceDescriptor = {\n name: 'resource',\n version: RESOURCE_SERVICE_VERSION,\n description:\n 'NAP-RESOURCE reference service — shell-proxied authenticated fetch (RESOURCE-01..06)',\n };\n\n const handler: ServiceHandler = {\n descriptor,\n\n handleMessage(\n windowId: string,\n message: NappletMessage,\n send: (msg: NappletMessage) => void,\n ): void {\n switch (message.type) {\n case 'resource.bytes': {\n const m = message as NappletMessage & {\n id?: string;\n requestId?: string;\n url: string;\n init?: { method?: string; headers?: Readonly<Record<string, string>> };\n };\n const requestId = requestIdFromMessage(m);\n if (!requestId || typeof m.url !== 'string') return;\n handleBytes(options, state, windowId, { requestId, url: m.url, init: m.init }, send).catch(() => { /* errors surface via send() */ });\n return;\n }\n\n case 'resource.bytesMany': {\n const m = message as NappletMessage & {\n id?: string;\n requestId?: string;\n urls?: readonly string[];\n init?: { method?: string; headers?: Readonly<Record<string, string>> };\n };\n const requestId = requestIdFromMessage(m);\n if (!requestId) return;\n handleBytesMany(options, state, windowId, { requestId, urls: m.urls ?? [], init: m.init }, send).catch(() => { /* errors surface via send() */ });\n return;\n }\n\n case 'resource.cancel': {\n const m = message as NappletMessage & { id?: string; requestId?: string };\n const requestId = requestIdFromMessage(m);\n if (requestId) handleCancel(state, requestId);\n return;\n }\n\n default:\n // Unknown resource.* message — silently ignored per NIP-5D.\n return;\n }\n },\n\n onWindowDestroyed(windowId: string): void {\n destroyWindowRequests(state, windowId);\n },\n };\n\n return handler;\n}\n","/**\n * outbox-service.ts — NAP-OUTBOX (outbox-aware relay routing) reference service.\n *\n * Shell-side handler for the NAP-OUTBOX wire protocol. It is a pure envelope\n * router: it validates `outbox.*` envelopes, delegates the actual relay\n * discovery / routing / dedup / publish-fanout work to an injected\n * {@link OutboxRouter}, and posts the correlated result / lifecycle messages\n * (echoing the request `id` or `subId`) back to the napplet.\n *\n * The router is injected (options-as-bridge) so this service has no Nostr\n * dependency and is fully unit-testable. A concrete relay-pool-backed router\n * ships alongside as {@link createRelayPoolOutboxRouter}.\n *\n * ──────────────────────────── Responsibilities ────────────────────────────\n * Inbound: outbox.query, outbox.subscribe, outbox.close, outbox.publish,\n * outbox.resolveRelays\n * Outbound: outbox.query.result, outbox.event, outbox.eose, outbox.closed,\n * outbox.publish.result, outbox.resolveRelays.result\n *\n * The shell owns relay discovery, routing, fallback, deduplication, signature\n * validation, signing, and publish fanout policy — all of which live behind\n * the {@link OutboxRouter}. This service only marshals the wire protocol.\n *\n * @example\n * ```ts\n * import { createOutboxService, createRelayPoolOutboxRouter } from '@kehto/services';\n *\n * const router = createRelayPoolOutboxRouter({ relayPool, loadRelayLists, fallbackRelays });\n * runtime.registerService('outbox', createOutboxService({ router }));\n * ```\n *\n * @packageDocumentation\n */\n\nimport type { EventTemplate, NappletMessage, NostrEvent, NostrFilter } from '@napplet/core';\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\n\n/** Outbox service version — follows semver. */\nconst OUTBOX_SERVICE_VERSION = '1.0.0';\n\n/**\n * Relay-selection strategy:\n * - `outbox` — query/publish via author write relays (the outbox model)\n * - `inbox` — query/publish via recipient read relays (the inbox model)\n * - `auto` — let the shell choose per its policy and relay intelligence\n */\nexport type OutboxStrategy = 'outbox' | 'inbox' | 'auto';\n\n/** Options for a one-shot outbox query. */\nexport interface OutboxQueryOptions {\n /** Explicit author hints (augment/override authors derived from filters). */\n authors?: string[];\n /** Relay hints; treated as a hint subject to shell validation, not a bypass. */\n relays?: string[];\n /** Relay-selection strategy. */\n strategy?: OutboxStrategy;\n /** Maximum events to collect. */\n limit?: number;\n /** Wall-clock budget for the query, in milliseconds. */\n timeoutMs?: number;\n}\n\n/** Options for a live outbox subscription. */\nexport interface OutboxSubscribeOptions extends OutboxQueryOptions {\n /** Keep the subscription open for real-time events after EOSE. */\n live?: boolean;\n}\n\n/** Options for an outbox publish. */\nexport interface OutboxPublishOptions {\n /** Relay hints; treated as a hint subject to shell validation. */\n relays?: string[];\n /** Recipient authors whose inbox relays should be included for directed events. */\n targetAuthors?: string[];\n /** Relay-selection strategy. */\n strategy?: OutboxStrategy;\n}\n\n/** A read/write target for relay-plan resolution. */\nexport interface OutboxTarget {\n /** Authors to resolve relays for. */\n authors?: string[];\n /** Single pubkey to resolve relays for. */\n pubkey?: string;\n /** Whether the plan is for reading (their write relays) or writing (their read relays). */\n direction?: 'read' | 'write';\n /** Relay-selection strategy. */\n strategy?: OutboxStrategy;\n}\n\n/** The relay plan the shell would use for a target. */\nexport interface OutboxRelayPlan {\n /** Resolved relay URLs. */\n relays: string[];\n /** Where the plan came from. */\n source: 'nip65' | 'cache' | 'policy' | 'fallback';\n /** Authors for which no relay list could be resolved. */\n missingAuthors?: string[];\n}\n\n/** Outcome of an outbox query, as returned by the {@link OutboxRouter}. */\nexport interface OutboxResult {\n /** Deduplicated, signature-validated events. */\n events: NostrEvent[];\n /** Map of event id -> relay URLs where the shell observed the event. */\n relays: Record<string, string[]>;\n /** True when some relay lists or connections failed and results are partial. */\n incomplete?: boolean;\n /** Error reason when the query could not complete. */\n error?: string;\n}\n\n/** Outcome of an outbox publish, as returned by the {@link OutboxRouter}. */\nexport interface OutboxPublishResult {\n /** Whether the publish succeeded on at least the required relays. */\n ok: boolean;\n /** The signed event returned by the shell. */\n event?: NostrEvent;\n /** The published event id. */\n eventId?: string;\n /** Map of relay URL -> per-relay publish success. */\n relays?: Record<string, boolean>;\n /** Error reason when the publish failed. */\n error?: string;\n}\n\n/** Sink an {@link OutboxRouter} streams subscription lifecycle through. */\nexport interface OutboxSubscriptionSink {\n /** Deliver a matching event; `relay` is the relay it was observed on, when known. */\n event(event: NostrEvent, relay?: string): void;\n /** Signal end-of-stored-events. */\n eose(): void;\n /** Signal that the subscription was closed upstream; `reason` is optional. */\n closed(reason?: string): void;\n}\n\n/** Handle to a router-owned subscription. */\nexport interface OutboxRouterSubscription {\n /** Stop the subscription and release its relay connections. */\n close(): void;\n}\n\n/**\n * Abstract outbox router. Implementors own relay discovery (NIP-65 / NIP-66),\n * routing, fallback, deduplication, signature validation, signing, and publish\n * fanout. The service translates wire envelopes into these calls and back.\n */\nexport interface OutboxRouter {\n /** Resolve relays, query them, dedup by id, validate signatures, collect events. */\n query(filters: NostrFilter[], options?: OutboxQueryOptions): Promise<OutboxResult>;\n /** Open a live outbox-aware subscription, streaming through `sink`. */\n subscribe(\n filters: NostrFilter[],\n options: OutboxSubscribeOptions | undefined,\n sink: OutboxSubscriptionSink,\n ): OutboxRouterSubscription;\n /** Sign `template` and fan it out to the relevant write/inbox relays. */\n publish(template: EventTemplate, options?: OutboxPublishOptions): Promise<OutboxPublishResult>;\n /** Return the relay plan the shell would use for a read/write target. */\n resolveRelays(target: OutboxTarget): Promise<OutboxRelayPlan>;\n}\n\n/** Options for {@link createOutboxService}. */\nexport interface OutboxServiceOptions {\n /** The outbox router the shell uses to reach relays. Required. */\n router: OutboxRouter;\n}\n\ntype Send = (msg: NappletMessage) => void;\n\nconst OUTBOX_DESCRIPTOR: ServiceDescriptor = {\n name: 'outbox',\n version: OUTBOX_SERVICE_VERSION,\n description: 'NAP-OUTBOX outbox-aware relay routing — query/subscribe/publish/resolveRelays',\n};\n\n/**\n * Normalize a wire `filters` field (a single NIP-01 filter or an array) into a\n * filter array. Returns `null` when the input is missing or has no usable\n * filter objects.\n */\nfunction normalizeFilters(raw: unknown): NostrFilter[] | null {\n if (Array.isArray(raw)) {\n const filters = raw.filter((f): f is NostrFilter => typeof f === 'object' && f !== null);\n return filters.length > 0 ? filters : null;\n }\n if (typeof raw === 'object' && raw !== null) return [raw as NostrFilter];\n return null;\n}\n\n/**\n * Create the NAP-OUTBOX service handler.\n *\n * @param options - Must provide an {@link OutboxRouter}.\n * @returns A `ServiceHandler` ready for `runtime.registerService('outbox', handler)`.\n * @throws If `options.router` is missing.\n */\nexport function createOutboxService(options: OutboxServiceOptions): ServiceHandler {\n if (!options || typeof options.router !== 'object' || options.router === null) {\n throw new Error('createOutboxService: options.router is required');\n }\n const { router } = options;\n\n // Active subscriptions keyed by `windowId:subId` for lifecycle management.\n const subscriptions = new Map<string, OutboxRouterSubscription>();\n\n function handleQuery(msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { id?: string; filters?: unknown; options?: OutboxQueryOptions };\n const id = m.id ?? '';\n const filters = normalizeFilters(m.filters);\n if (!filters) {\n send({ type: 'outbox.query.result', id, events: [], relays: {}, error: 'invalid filter' } as NappletMessage);\n return;\n }\n void router\n .query(filters, m.options)\n .then((result) =>\n send({\n type: 'outbox.query.result',\n id,\n events: result.events,\n relays: result.relays,\n ...(result.incomplete === undefined ? {} : { incomplete: result.incomplete }),\n ...(result.error === undefined ? {} : { error: result.error }),\n } as NappletMessage),\n )\n .catch((err) =>\n send({ type: 'outbox.query.result', id, events: [], relays: {}, error: toErrorMessage(err) } as NappletMessage),\n );\n }\n\n function handleSubscribe(windowId: string, msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { subId?: string; filters?: unknown; options?: OutboxSubscribeOptions };\n const subId = m.subId;\n if (typeof subId !== 'string' || subId.length === 0) return;\n const subKey = `${windowId}:${subId}`;\n\n // Replace any existing subscription for this key.\n subscriptions.get(subKey)?.close();\n subscriptions.delete(subKey);\n\n const filters = normalizeFilters(m.filters);\n if (!filters) {\n send({ type: 'outbox.closed', subId, reason: 'invalid filter' } as NappletMessage);\n return;\n }\n\n const sink: OutboxSubscriptionSink = {\n event: (event, relay) =>\n send({ type: 'outbox.event', subId, event, ...(relay === undefined ? {} : { relay }) } as NappletMessage),\n eose: () => send({ type: 'outbox.eose', subId } as NappletMessage),\n closed: (reason) => {\n subscriptions.delete(subKey);\n send({ type: 'outbox.closed', subId, ...(reason === undefined ? {} : { reason }) } as NappletMessage);\n },\n };\n\n subscriptions.set(subKey, router.subscribe(filters, m.options, sink));\n }\n\n function handleClose(windowId: string, msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { subId?: string };\n const subId = m.subId;\n if (typeof subId !== 'string') return;\n const subKey = `${windowId}:${subId}`;\n subscriptions.get(subKey)?.close();\n subscriptions.delete(subKey);\n send({ type: 'outbox.closed', subId } as NappletMessage);\n }\n\n function handlePublish(msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { id?: string; event?: EventTemplate; options?: OutboxPublishOptions };\n const id = m.id ?? '';\n if (!m.event || typeof m.event !== 'object') {\n send({ type: 'outbox.publish.result', id, ok: false, error: 'invalid filter' } as NappletMessage);\n return;\n }\n void router\n .publish(m.event, m.options)\n .then((result) =>\n send({\n type: 'outbox.publish.result',\n id,\n ok: result.ok,\n ...(result.event === undefined ? {} : { event: result.event }),\n ...(result.eventId === undefined ? {} : { eventId: result.eventId }),\n ...(result.relays === undefined ? {} : { relays: result.relays }),\n ...(result.error === undefined ? {} : { error: result.error }),\n } as NappletMessage),\n )\n .catch((err) =>\n send({ type: 'outbox.publish.result', id, ok: false, error: toErrorMessage(err) } as NappletMessage),\n );\n }\n\n function handleResolveRelays(msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { id?: string; target?: OutboxTarget };\n const id = m.id ?? '';\n if (!m.target || typeof m.target !== 'object') {\n send({ type: 'outbox.resolveRelays.result', id, error: 'invalid filter' } as NappletMessage);\n return;\n }\n void router\n .resolveRelays(m.target)\n .then((plan) => send({ type: 'outbox.resolveRelays.result', id, plan } as NappletMessage))\n .catch((err) =>\n send({ type: 'outbox.resolveRelays.result', id, error: toErrorMessage(err) } as NappletMessage),\n );\n }\n\n return {\n descriptor: OUTBOX_DESCRIPTOR,\n handleMessage(windowId: string, message: NappletMessage, send: Send): void {\n switch (message.type) {\n case 'outbox.query':\n handleQuery(message, send);\n return;\n case 'outbox.subscribe':\n handleSubscribe(windowId, message, send);\n return;\n case 'outbox.close':\n handleClose(windowId, message, send);\n return;\n case 'outbox.publish':\n handlePublish(message, send);\n return;\n case 'outbox.resolveRelays':\n handleResolveRelays(message, send);\n return;\n default:\n // Unknown outbox.* action — silently ignored (forward-compatible).\n return;\n }\n },\n onWindowDestroyed(windowId: string): void {\n const prefix = `${windowId}:`;\n for (const [key, sub] of subscriptions) {\n if (key.startsWith(prefix)) {\n sub.close();\n subscriptions.delete(key);\n }\n }\n },\n };\n}\n\nfunction toErrorMessage(err: unknown): string {\n if (err instanceof Error) return err.message;\n if (typeof err === 'string') return err;\n return 'outbox request failed';\n}\n","/**\n * relay-pool-outbox-router.ts — concrete {@link OutboxRouter} backed by a relay pool.\n *\n * Implements the outbox-model routing that NAP-OUTBOX centralizes so napplets\n * don't each reinvent it: derive authors, resolve their NIP-65 relays, fan a\n * per-relay subscription out across the plan, deduplicate by event id (while\n * recording every relay an event was observed on), validate signatures, and —\n * for publish — sign the template and fan it out to the relevant write/inbox\n * relays.\n *\n * NIP-65 relay-list *fetching* is the host's concern (it may come from a\n * kind-10002 cache, a NIP-66 indexer via `@kehto/nip/66`, or a live query), so\n * it is injected via {@link RelayPoolOutboxRouterOptions.loadRelayLists}. The\n * relay pool, signer, and signature verifier are injected too — keeping this\n * router browser-agnostic and unit-testable with mocks.\n *\n * Relay-selection model (per the outbox model):\n * - reading an author's events → their **write** relays (where they publish)\n * - writing to reach an author → their **read** relays (their inbox)\n *\n * `strategy` overrides the direction default: `outbox` forces write relays,\n * `inbox` forces read relays, `auto` (default) follows the read/write direction.\n *\n * @example\n * ```ts\n * import { createOutboxService, createRelayPoolOutboxRouter } from '@kehto/services';\n *\n * const router = createRelayPoolOutboxRouter({\n * relayPool: myOutboxPool,\n * loadRelayLists: (pubkeys) => relayListCache.getMany(pubkeys),\n * fallbackRelays: ['wss://relay.damus.io', 'wss://nos.lol'],\n * signEvent: (tmpl) => signer.signEvent(tmpl),\n * verifyEvent: (ev) => verifyEvent(ev),\n * });\n * runtime.registerService('outbox', createOutboxService({ router }));\n * ```\n *\n * @packageDocumentation\n */\n\nimport type { EventTemplate, NostrEvent, NostrFilter } from '@napplet/core';\nimport type {\n OutboxRouter,\n OutboxResult,\n OutboxPublishResult,\n OutboxRelayPlan,\n OutboxQueryOptions,\n OutboxSubscribeOptions,\n OutboxPublishOptions,\n OutboxStrategy,\n OutboxTarget,\n OutboxSubscriptionSink,\n OutboxRouterSubscription,\n} from './outbox-service.js';\n\n// Timer globals available in all JS runtimes.\ndeclare function setTimeout(callback: () => void, ms: number): unknown;\ndeclare function clearTimeout(id: unknown): void;\n\n/** Default per-query wall-clock budget when `options.timeoutMs` is unset. */\nconst DEFAULT_QUERY_TIMEOUT_MS = 4000;\n\n/** A NIP-65 relay list for a single pubkey. */\nexport interface RelayListEntry {\n /** Relays the author reads from (their inbox). */\n read: string[];\n /** Relays the author writes to (where their events land). */\n write: string[];\n}\n\n/**\n * Relay pool contract the router drives. Implementors adapt their pool library\n * (nostr-tools SimplePool, applesauce-relay, etc.). Unlike the lower-level\n * relay NAP pool, both methods take an explicit relay-URL set so the router\n * controls outbox routing and can attribute events to the relay they arrived on.\n */\nexport interface OutboxRelayPool {\n /**\n * Subscribe to `filters` on exactly `relayUrls`. The callback receives each\n * matching event or the literal `'EOSE'` once stored events are exhausted.\n * Returns a handle to cancel the subscription.\n */\n subscribe(\n filters: NostrFilter[],\n relayUrls: string[],\n callback: (item: NostrEvent | 'EOSE') => void,\n ): { unsubscribe(): void };\n /**\n * Publish `event` to `relayUrls`. May return a per-relay success map; a\n * `void`/missing return is treated as optimistic success on every target.\n */\n publish(\n event: NostrEvent,\n relayUrls: string[],\n ): Promise<Record<string, boolean>> | Record<string, boolean> | void;\n /** Whether the relay pool is connected and able to handle requests. */\n isAvailable(): boolean;\n}\n\n/** Options for {@link createRelayPoolOutboxRouter}. */\nexport interface RelayPoolOutboxRouterOptions {\n /** Relay pool the router subscribes/publishes through. Required. */\n relayPool: OutboxRelayPool;\n /**\n * Resolve NIP-65 relay lists for a set of pubkeys. Pubkeys with no known\n * list are simply omitted from the returned map (they become `missingAuthors`).\n */\n loadRelayLists(pubkeys: string[]): Promise<Map<string, RelayListEntry>> | Map<string, RelayListEntry>;\n /** Relays to fall back to when NIP-65 data is absent, stale, or empty. Required. */\n fallbackRelays: string[];\n /**\n * Sign a template before publish (shell-mediated; napplets never sign). When\n * omitted, `publish` resolves with `{ ok: false, error: 'publish denied' }`.\n */\n signEvent?(template: EventTemplate): Promise<NostrEvent>;\n /**\n * Validate an event signature before delivering it to a napplet. May be sync\n * or async. Defaults to accepting every event (host pools often pre-verify).\n */\n verifyEvent?(event: NostrEvent): Promise<boolean> | boolean;\n /**\n * Gate relay URLs (e.g. block private-network hosts). Defaults to allowing\n * only `ws://` / `wss://` URLs — `options.relays` hints pass through this too.\n */\n isRelayAllowed?(url: string): boolean;\n /** Default query timeout when `options.timeoutMs` is unset. Default 4000ms. */\n defaultTimeoutMs?: number;\n}\n\n/** Resolved router dependencies threaded into the module-level helpers. */\ninterface RouterCtx {\n relayPool: OutboxRelayPool;\n loadRelayLists: RelayPoolOutboxRouterOptions['loadRelayLists'];\n fallbackRelays: string[];\n signEvent?: RelayPoolOutboxRouterOptions['signEvent'];\n isRelayAllowed: (url: string) => boolean;\n defaultTimeoutMs: number;\n verify(event: NostrEvent): Promise<boolean>;\n}\n\n/** Default relay gate: only ws(s):// URLs are permitted. */\nfunction defaultRelayAllowed(url: string): boolean {\n return typeof url === 'string' && (url.startsWith('wss://') || url.startsWith('ws://'));\n}\n\n/** Collect a deduplicated author set from filters + explicit option hints. */\nfunction deriveAuthors(filters: NostrFilter[], optionAuthors?: string[]): string[] {\n const authors = new Set<string>();\n for (const filter of filters) {\n for (const author of filter.authors ?? []) authors.add(author);\n }\n for (const author of optionAuthors ?? []) authors.add(author);\n return [...authors];\n}\n\n/**\n * Whether a resolved plan should use authors' write relays (true) or read\n * relays (false), given the read/write direction and an explicit strategy.\n */\nfunction wantsWriteRelays(direction: 'read' | 'write', strategy: OutboxStrategy): boolean {\n if (strategy === 'outbox') return true;\n if (strategy === 'inbox') return false;\n return direction === 'read'; // auto: reading → author write relays\n}\n\n/** Apply the relay gate to a candidate set, deduplicating. */\nfunction allowed(ctx: RouterCtx, urls: Iterable<string>): string[] {\n const out = new Set<string>();\n for (const url of urls) {\n if (ctx.isRelayAllowed(url)) out.add(url);\n }\n return [...out];\n}\n\n/**\n * Resolve the relay plan for a set of pubkeys. Returns the allowed relay set,\n * its provenance, and any pubkeys whose relay list was unavailable.\n */\nasync function resolvePlan(\n ctx: RouterCtx,\n pubkeys: string[],\n direction: 'read' | 'write',\n strategy: OutboxStrategy,\n relayHints?: string[],\n): Promise<OutboxRelayPlan> {\n const useWrite = wantsWriteRelays(direction, strategy);\n const collected = new Set<string>();\n const missingAuthors: string[] = [];\n let sawNip65 = false;\n\n if (pubkeys.length > 0) {\n const lists = await ctx.loadRelayLists(pubkeys);\n for (const pubkey of pubkeys) {\n const entry = lists.get(pubkey);\n const relays = entry ? (useWrite ? entry.write : entry.read) : undefined;\n if (relays && relays.length > 0) {\n sawNip65 = true;\n for (const url of relays) collected.add(url);\n } else {\n missingAuthors.push(pubkey);\n }\n }\n }\n\n // Relay hints from the napplet augment the plan, subject to the relay gate.\n for (const url of relayHints ?? []) collected.add(url);\n\n let relays = allowed(ctx, collected);\n let source: OutboxRelayPlan['source'];\n if (relays.length === 0) {\n relays = allowed(ctx, ctx.fallbackRelays);\n source = 'fallback';\n } else {\n // nip65 if any author list contributed; otherwise only hints did (policy).\n source = sawNip65 ? 'nip65' : 'policy';\n }\n\n const plan: OutboxRelayPlan = { relays, source };\n if (missingAuthors.length > 0) plan.missingAuthors = missingAuthors;\n return plan;\n}\n\n/** Mutable accumulator for a one-shot fan-out collection. */\ninterface Collector {\n seen: Map<string, NostrEvent>;\n relayMap: Map<string, Set<string>>;\n verifications: Promise<void>[];\n}\n\n/** Record that `id` was observed on `relayUrl`. */\nfunction recordRelay(collector: Collector, id: string, relayUrl: string): void {\n let set = collector.relayMap.get(id);\n if (!set) { set = new Set<string>(); collector.relayMap.set(id, set); }\n set.add(relayUrl);\n}\n\n/** Verify a freshly-seen event and admit it (or drop its sightings) once settled. */\nfunction admitEvent(ctx: RouterCtx, collector: Collector, event: NostrEvent): void {\n if (collector.seen.has(event.id)) return;\n collector.verifications.push(\n ctx.verify(event).then((ok) => {\n if (ok && !collector.seen.has(event.id)) collector.seen.set(event.id, event);\n else if (!ok) collector.relayMap.delete(event.id);\n }),\n );\n}\n\n/** Build the final query outcome from a settled collector. */\nfunction buildCollectResult(\n collector: Collector,\n timedOut: boolean,\n): { events: NostrEvent[]; relayMap: Record<string, string[]>; incomplete: boolean } {\n const events = [...collector.seen.values()];\n const relayObj: Record<string, string[]> = {};\n for (const event of events) relayObj[event.id] = [...(collector.relayMap.get(event.id) ?? [])];\n return { events, relayMap: relayObj, incomplete: timedOut };\n}\n\n/**\n * Fan a one-shot query out across `relayUrls` (one subscription per relay so\n * events can be attributed to their source relay), dedup by id, validate\n * signatures, and finalize on all-EOSE or timeout.\n */\nfunction collectFromRelays(\n ctx: RouterCtx,\n filters: NostrFilter[],\n relayUrls: string[],\n timeoutMs: number,\n): Promise<{ events: NostrEvent[]; relayMap: Record<string, string[]>; incomplete: boolean }> {\n return new Promise((resolve) => {\n const collector: Collector = { seen: new Map(), relayMap: new Map(), verifications: [] };\n const handles: { unsubscribe(): void }[] = [];\n let eoseCount = 0;\n let finished = false;\n let timedOut = false;\n\n function finalize(): void {\n if (finished) return;\n finished = true;\n clearTimeout(timer);\n for (const handle of handles) {\n try { handle.unsubscribe(); } catch { /* best-effort */ }\n }\n void Promise.all(collector.verifications).then(() => resolve(buildCollectResult(collector, timedOut)));\n }\n\n const timer = setTimeout(() => { timedOut = true; finalize(); }, timeoutMs);\n\n for (const relayUrl of relayUrls) {\n handles.push(relayPoolSubscribe(ctx, filters, relayUrl, (item) => {\n if (finished) return;\n if (item === 'EOSE') {\n eoseCount += 1;\n if (eoseCount >= relayUrls.length) finalize();\n return;\n }\n recordRelay(collector, item.id, relayUrl);\n admitEvent(ctx, collector, item);\n }));\n }\n\n if (relayUrls.length === 0) finalize();\n });\n}\n\n/** Thin wrapper so callers read as a single-relay subscribe. */\nfunction relayPoolSubscribe(\n ctx: RouterCtx,\n filters: NostrFilter[],\n relayUrl: string,\n cb: (item: NostrEvent | 'EOSE') => void,\n): { unsubscribe(): void } {\n return ctx.relayPool.subscribe(filters, [relayUrl], cb);\n}\n\nasync function queryImpl(ctx: RouterCtx, filters: NostrFilter[], options?: OutboxQueryOptions): Promise<OutboxResult> {\n if (!ctx.relayPool.isAvailable()) {\n return { events: [], relays: {}, incomplete: true, error: 'relay list unavailable' };\n }\n const strategy = options?.strategy ?? 'auto';\n const authors = deriveAuthors(filters, options?.authors);\n const plan = await resolvePlan(ctx, authors, 'read', strategy, options?.relays);\n if (plan.relays.length === 0) {\n return { events: [], relays: {}, incomplete: true, error: 'relay list unavailable' };\n }\n\n const timeoutMs = options?.timeoutMs ?? ctx.defaultTimeoutMs;\n const collected = await collectFromRelays(ctx, filters, plan.relays, timeoutMs);\n\n const incomplete = collected.incomplete || (plan.missingAuthors?.length ?? 0) > 0;\n let events = collected.events;\n if (options?.limit !== undefined && events.length > options.limit) {\n events = [...events].sort((a, b) => b.created_at - a.created_at).slice(0, options.limit);\n }\n const result: OutboxResult = { events, relays: collected.relayMap };\n if (incomplete) result.incomplete = true;\n return result;\n}\n\n/** Tracks a live/one-shot subscription across its per-relay fan-out. */\ninterface LiveSub {\n handles: { unsubscribe(): void }[];\n seen: Set<string>;\n closed: boolean;\n eoseCount: number;\n relayCount: number;\n eoseSent: boolean;\n}\n\nfunction closeLiveSub(sub: LiveSub): void {\n for (const handle of sub.handles) {\n try { handle.unsubscribe(); } catch { /* best-effort */ }\n }\n sub.handles.length = 0;\n}\n\n/** Wire one relay's subscription into a live subscription's event/eose flow. */\nfunction attachLiveRelay(\n ctx: RouterCtx,\n sub: LiveSub,\n filters: NostrFilter[],\n relayUrl: string,\n live: boolean,\n sink: OutboxSubscriptionSink,\n): void {\n sub.handles.push(relayPoolSubscribe(ctx, filters, relayUrl, (item) => {\n if (sub.closed) return;\n if (item === 'EOSE') {\n sub.eoseCount += 1;\n if (!sub.eoseSent && sub.eoseCount >= sub.relayCount) {\n sub.eoseSent = true;\n sink.eose();\n if (!live) { sub.closed = true; closeLiveSub(sub); sink.closed(); }\n }\n return;\n }\n if (sub.seen.has(item.id)) return;\n void ctx.verify(item).then((ok) => {\n if (!ok || sub.closed || sub.seen.has(item.id)) return;\n sub.seen.add(item.id);\n sink.event(item, relayUrl);\n });\n }));\n}\n\nfunction startSubscription(\n ctx: RouterCtx,\n filters: NostrFilter[],\n options: OutboxSubscribeOptions | undefined,\n sink: OutboxSubscriptionSink,\n): OutboxRouterSubscription {\n const live = options?.live ?? true;\n const strategy = options?.strategy ?? 'auto';\n const authors = deriveAuthors(filters, options?.authors);\n const sub: LiveSub = { handles: [], seen: new Set(), closed: false, eoseCount: 0, relayCount: 0, eoseSent: false };\n\n void resolvePlan(ctx, authors, 'read', strategy, options?.relays)\n .then((plan) => {\n if (sub.closed) return;\n if (plan.relays.length === 0) { sink.closed('relay list unavailable'); return; }\n sub.relayCount = plan.relays.length;\n for (const relayUrl of plan.relays) attachLiveRelay(ctx, sub, filters, relayUrl, live, sink);\n })\n .catch((err) => {\n if (!sub.closed) sink.closed(err instanceof Error ? err.message : 'subscribe failed');\n });\n\n return {\n close(): void {\n if (sub.closed) return;\n sub.closed = true;\n closeLiveSub(sub);\n },\n };\n}\n\n/** Resolve the full write/inbox/hint relay set a publish should fan out to. */\nasync function resolvePublishTargets(\n ctx: RouterCtx,\n signed: NostrEvent,\n options?: OutboxPublishOptions,\n): Promise<string[]> {\n const strategy = options?.strategy ?? 'auto';\n const targets = new Set<string>();\n\n // The author's own write relays (outbox model for the user's own event).\n const authorPlan = await resolvePlan(ctx, [signed.pubkey], 'read', strategy === 'inbox' ? 'auto' : 'outbox');\n for (const url of authorPlan.relays) targets.add(url);\n\n // Directed events: include recipients' read relays (their inbox).\n if (options?.targetAuthors && options.targetAuthors.length > 0) {\n const inboxPlan = await resolvePlan(ctx, options.targetAuthors, 'write', 'inbox');\n for (const url of inboxPlan.relays) targets.add(url);\n }\n\n for (const url of options?.relays ?? []) targets.add(url);\n return allowed(ctx, targets);\n}\n\nasync function publishImpl(ctx: RouterCtx, template: EventTemplate, options?: OutboxPublishOptions): Promise<OutboxPublishResult> {\n if (!ctx.signEvent) return { ok: false, error: 'publish denied' };\n if (!ctx.relayPool.isAvailable()) return { ok: false, error: 'relay list unavailable' };\n\n let signed: NostrEvent;\n try {\n signed = await ctx.signEvent(template);\n } catch (err) {\n return { ok: false, error: err instanceof Error ? err.message : 'sign failed' };\n }\n\n const relayUrls = await resolvePublishTargets(ctx, signed, options);\n if (relayUrls.length === 0) return { ok: false, event: signed, eventId: signed.id, error: 'relay list unavailable' };\n\n let relays: Record<string, boolean>;\n try {\n relays = normalizePublishResult(await ctx.relayPool.publish(signed, relayUrls), relayUrls);\n } catch (err) {\n return { ok: false, event: signed, eventId: signed.id, error: err instanceof Error ? err.message : 'publish failed' };\n }\n\n const ok = Object.values(relays).some(Boolean);\n const result: OutboxPublishResult = { ok, event: signed, eventId: signed.id, relays };\n if (!ok) result.error = 'publish denied';\n return result;\n}\n\n/**\n * Create a relay-pool-backed {@link OutboxRouter}.\n *\n * @param options - Relay pool, NIP-65 loader, fallback relays, and optional\n * signer / verifier / relay gate / timeout.\n * @returns An {@link OutboxRouter} for {@link createOutboxService}.\n * @throws If `relayPool`, `loadRelayLists`, or `fallbackRelays` are missing.\n */\nexport function createRelayPoolOutboxRouter(options: RelayPoolOutboxRouterOptions): OutboxRouter {\n if (!options || typeof options.relayPool !== 'object' || options.relayPool === null) {\n throw new Error('createRelayPoolOutboxRouter: options.relayPool is required');\n }\n if (typeof options.loadRelayLists !== 'function') {\n throw new Error('createRelayPoolOutboxRouter: options.loadRelayLists is required');\n }\n if (!Array.isArray(options.fallbackRelays)) {\n throw new Error('createRelayPoolOutboxRouter: options.fallbackRelays is required');\n }\n\n const verifyEvent = options.verifyEvent;\n const ctx: RouterCtx = {\n relayPool: options.relayPool,\n loadRelayLists: options.loadRelayLists,\n fallbackRelays: options.fallbackRelays,\n signEvent: options.signEvent,\n isRelayAllowed: options.isRelayAllowed ?? defaultRelayAllowed,\n defaultTimeoutMs: options.defaultTimeoutMs ?? DEFAULT_QUERY_TIMEOUT_MS,\n async verify(event: NostrEvent): Promise<boolean> {\n if (!verifyEvent) return true;\n try {\n return await verifyEvent(event);\n } catch {\n return false;\n }\n },\n };\n\n return {\n query: (filters, queryOptions) => queryImpl(ctx, filters, queryOptions),\n subscribe: (filters, subscribeOptions, sink) => startSubscription(ctx, filters, subscribeOptions, sink),\n publish: (template, publishOptions) => publishImpl(ctx, template, publishOptions),\n resolveRelays: (target: OutboxTarget) => {\n const pubkeys = target.authors ?? (target.pubkey ? [target.pubkey] : []);\n return resolvePlan(ctx, pubkeys, target.direction ?? 'read', target.strategy ?? 'auto');\n },\n };\n}\n\n/** Normalize a pool publish return into a per-relay success map. */\nfunction normalizePublishResult(\n res: Record<string, boolean> | void,\n relayUrls: string[],\n): Record<string, boolean> {\n const out: Record<string, boolean> = {};\n if (res && typeof res === 'object') {\n for (const url of relayUrls) out[url] = res[url] ?? false;\n } else {\n // void return → optimistic success on every targeted relay.\n for (const url of relayUrls) out[url] = true;\n }\n return out;\n}\n","/**\n * upload-service.ts — NAP-UPLOAD (shell-mediated file/blob upload) reference service.\n *\n * Shell-side handler for the NAP-UPLOAD wire protocol. It is a pure envelope\n * router: it validates `upload.*` envelopes, delegates the actual byte transfer\n * (server selection, rail authorization signing, the HTTP upload) to an injected\n * {@link Uploader}, and posts the correlated result / status messages back to the\n * napplet.\n *\n * The uploader is injected (options-as-bridge) so this service has no transport\n * or Nostr dependency and is fully unit-testable. NAP-UPLOAD is deliberately\n * abstract over the backend — the runtime decides *how* it uploads (NIP-96,\n * Blossom, …). A concrete HTTP-backed uploader ships alongside as\n * {@link createHttpUploader}.\n *\n * ──────────────────────────── Responsibilities ────────────────────────────\n * Inbound: upload.upload, upload.status\n * Outbound: upload.upload.result, upload.status.result, upload.status.changed\n *\n * The service owns the `uploadId` (generated per request, scoped to the\n * requesting napplet), tracks the latest {@link UploadStatus} per upload for\n * `upload.status` queries, and cleans up on window teardown. The shell owns\n * consent, policy, server selection, signing, and the HTTP upload — all behind\n * the {@link Uploader}.\n *\n * @example\n * ```ts\n * import { createUploadService, createHttpUploader } from '@kehto/services';\n *\n * const uploader = createHttpUploader({ rails: { nip96: { servers } }, signEvent });\n * runtime.registerService('upload', createUploadService({ uploader }));\n * ```\n *\n * @packageDocumentation\n */\n\nimport type { NappletMessage } from '@napplet/core';\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\n\n/** Upload service version — follows semver. */\nconst UPLOAD_SERVICE_VERSION = '1.0.0';\n\n/**\n * Storage rail. `nip96` (NIP-96 HTTP file storage) and `blossom` (Blossom blob\n * storage) are the first concrete backends; the open string keeps the API\n * stable as shells add rails (torrents, usenet, …).\n */\nexport type UploadRail = 'nip96' | 'blossom' | (string & {});\n\n/** Lifecycle state of an upload. */\nexport type UploadState = 'pending' | 'uploading' | 'complete' | 'failed' | 'cancelled';\n\n/** Pixel dimensions of an uploaded image/video. */\nexport interface UploadDimensions {\n width: number;\n height: number;\n}\n\n/**\n * A napplet's upload request. `data` crosses the postMessage boundary by\n * structured clone — shells never require base64 encoding.\n */\nexport interface UploadRequest {\n /** Storage rail; omit to let the shell pick a configured default. */\n rail?: UploadRail;\n /** The bytes to upload. */\n data: ArrayBuffer | Blob;\n /** MIME type; inferred from `data` when omitted. */\n mimeType?: string;\n /** Suggested filename. */\n filename?: string;\n /** Alt text / description for the file event. */\n caption?: string;\n /** Request the server not re-encode the file (NIP-96 `no_transform`). */\n noTransform?: boolean;\n /** Rail-specific or shell-specific extra metadata. */\n metadata?: Record<string, unknown>;\n}\n\n/** A single Nostr tag (NIP-94 / imeta entries are arrays of strings). */\nexport type NostrTag = string[];\n\n/** The result of an upload. */\nexport interface UploadResult {\n /** Whether the upload succeeded (or is progressing) vs failed/cancelled. */\n ok: boolean;\n /** Shell-generated id, scoped to the requesting napplet. */\n uploadId: string;\n /** Current lifecycle state. */\n status: UploadState;\n /** The rail the shell used. */\n rail: UploadRail;\n /** Primary download URL. */\n url?: string;\n /** Mirrors / alternative server URLs. */\n fallbackUrls?: string[];\n /** Hash of the stored blob (NIP-94 `x`). */\n sha256?: string;\n /** Hash before server transforms (NIP-94 `ox`). */\n originalSha256?: string;\n /** Size in bytes. */\n size?: number;\n /** Stored MIME type. */\n mimeType?: string;\n /** Image/video dimensions when known. */\n dimensions?: UploadDimensions;\n /** Blurhash placeholder when known. */\n blurhash?: string;\n /** Ready-to-attach NIP-94 / imeta tags. */\n nip94?: NostrTag[];\n /** Error reason when the upload failed or was cancelled. */\n error?: string;\n}\n\n/** A status snapshot for an upload, including progress counters. */\nexport interface UploadStatus extends UploadResult {\n /** Bytes sent so far (while uploading). */\n bytesSent?: number;\n /** Total bytes to send. */\n bytesTotal?: number;\n /** Unix ms timestamp of this status. */\n updatedAt: number;\n}\n\n/**\n * Context handed to an {@link Uploader} for a single upload. Carries the\n * service-owned `uploadId` and a sink for streaming progress / state changes.\n */\nexport interface UploaderContext {\n /** The service-generated upload id (authoritative; scoped to the napplet). */\n uploadId: string;\n /** The napplet window that requested the upload. */\n windowId: string;\n /**\n * Push a status update (progress, or a transition to complete/failed). The\n * service stamps `uploadId` and `updatedAt` before forwarding to the napplet\n * as `upload.status.changed`, and records it as the latest tracked status.\n */\n onStatus(status: UploadStatus): void;\n}\n\n/**\n * Abstract upload backend. Implementors own server selection, rail\n * authorization signing (NIP-98 for NIP-96, kind 24242 for Blossom), the HTTP\n * upload, and integrity-hash reporting. The service translates wire envelopes\n * into these calls and back. A concrete reference implementation ships as\n * {@link createHttpUploader}.\n */\nexport interface Uploader {\n /** Upload `request.data`, streaming progress through `ctx.onStatus`. */\n upload(request: UploadRequest, ctx: UploaderContext): Promise<UploadResult>;\n /** Optional: resolve the latest status for an upload the service is not tracking. */\n status?(uploadId: string): Promise<UploadStatus | undefined>;\n /** Optional: abort an in-flight upload (called on window teardown). */\n cancel?(uploadId: string): void;\n}\n\n/** Options for {@link createUploadService}. */\nexport interface UploadServiceOptions {\n /** The upload backend the shell uses. Required. */\n uploader: Uploader;\n /** Generate an upload id; defaults to `crypto.randomUUID()`. */\n generateId?: () => string;\n /** Current time in unix ms; defaults to `Date.now()`. */\n now?: () => number;\n}\n\ntype Send = (msg: NappletMessage) => void;\n\nconst UPLOAD_DESCRIPTOR: ServiceDescriptor = {\n name: 'upload',\n version: UPLOAD_SERVICE_VERSION,\n description: 'NAP-UPLOAD shell-mediated file/blob upload — upload/status with progress pushes',\n};\n\n/** Per-upload tracking entry, keyed by `windowId:uploadId`. */\ninterface UploadEntry {\n uploadId: string;\n status?: UploadStatus;\n}\n\ndeclare const crypto: { randomUUID(): string };\n\n/**\n * Create the NAP-UPLOAD service handler.\n *\n * @param options - Must provide an {@link Uploader}.\n * @returns A `ServiceHandler` ready for `runtime.registerService('upload', handler)`.\n * @throws If `options.uploader` is missing.\n */\nexport function createUploadService(options: UploadServiceOptions): ServiceHandler {\n if (!options || typeof options.uploader !== 'object' || options.uploader === null) {\n throw new Error('createUploadService: options.uploader is required');\n }\n const { uploader } = options;\n const generateId = options.generateId ?? (() => crypto.randomUUID());\n const now = options.now ?? (() => Date.now());\n\n // Tracked uploads keyed by `windowId:uploadId` for status lookup + cleanup.\n const entries = new Map<string, UploadEntry>();\n\n function handleUpload(windowId: string, msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { id?: string; request?: UploadRequest };\n const id = m.id ?? '';\n const request = m.request;\n if (!request || typeof request !== 'object' || request.data == null) {\n send({ type: 'upload.upload.result', id, error: 'invalid request' } as NappletMessage);\n return;\n }\n\n const uploadId = generateId();\n const key = `${windowId}:${uploadId}`;\n entries.set(key, { uploadId });\n\n const ctx: UploaderContext = {\n uploadId,\n windowId,\n onStatus: (status) => {\n const stamped: UploadStatus = { ...status, uploadId, updatedAt: status.updatedAt || now() };\n const entry = entries.get(key);\n if (entry) entry.status = stamped;\n send({ type: 'upload.status.changed', status: stamped } as NappletMessage);\n },\n };\n\n void uploader\n .upload(request, ctx)\n .then((result) => {\n const stamped: UploadResult = { ...result, uploadId };\n const entry = entries.get(key);\n if (entry) entry.status = { ...stamped, updatedAt: now() };\n send({ type: 'upload.upload.result', id, result: stamped } as NappletMessage);\n })\n .catch((err) => {\n entries.delete(key);\n send({ type: 'upload.upload.result', id, error: toErrorMessage(err) } as NappletMessage);\n });\n }\n\n function handleStatus(windowId: string, msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { id?: string; uploadId?: string };\n const id = m.id ?? '';\n const uploadId = m.uploadId;\n if (typeof uploadId !== 'string' || uploadId.length === 0) {\n send({ type: 'upload.status.result', id, error: 'invalid uploadId' } as NappletMessage);\n return;\n }\n\n const tracked = entries.get(`${windowId}:${uploadId}`)?.status;\n if (tracked) {\n send({ type: 'upload.status.result', id, status: tracked } as NappletMessage);\n return;\n }\n\n if (uploader.status) {\n void uploader\n .status(uploadId)\n .then((status) =>\n send(\n status\n ? ({ type: 'upload.status.result', id, status } as NappletMessage)\n : ({ type: 'upload.status.result', id, error: 'unknown upload' } as NappletMessage),\n ),\n )\n .catch((err) =>\n send({ type: 'upload.status.result', id, error: toErrorMessage(err) } as NappletMessage),\n );\n return;\n }\n\n send({ type: 'upload.status.result', id, error: 'unknown upload' } as NappletMessage);\n }\n\n return {\n descriptor: UPLOAD_DESCRIPTOR,\n handleMessage(windowId: string, message: NappletMessage, send: Send): void {\n switch (message.type) {\n case 'upload.upload':\n handleUpload(windowId, message, send);\n return;\n case 'upload.status':\n handleStatus(windowId, message, send);\n return;\n default:\n // Unknown upload.* action — silently ignored (forward-compatible).\n return;\n }\n },\n onWindowDestroyed(windowId: string): void {\n const prefix = `${windowId}:`;\n for (const [key, entry] of entries) {\n if (key.startsWith(prefix)) {\n uploader.cancel?.(entry.uploadId);\n entries.delete(key);\n }\n }\n },\n };\n}\n\nfunction toErrorMessage(err: unknown): string {\n if (err instanceof Error) return err.message;\n if (typeof err === 'string') return err;\n return 'upload request failed';\n}\n","/**\n * http-uploader.ts — NAP-UPLOAD concrete HTTP-backed {@link Uploader}.\n *\n * The reference upload backend for {@link createUploadService}. Implements two\n * storage rails over HTTP:\n *\n * - **NIP-96** — signs a NIP-98 (kind 27235) HTTP-auth event, POSTs the file as\n * `multipart/form-data`, and maps the returned NIP-94 event tags into an\n * {@link UploadResult}.\n * - **Blossom** — signs a kind 24242 authorization event, PUTs the raw bytes to\n * `<server>/upload`, and maps the returned blob descriptor.\n *\n * Signing (`signEvent`) and transport (`fetch`) are injected so the uploader\n * carries no Nostr or network dependency and is fully unit-testable. The shell\n * holds the signing key and never exposes it to napplets — the uploader only\n * receives a signing callback. Server URLs are shell configuration, not napplet\n * input: a napplet may *hint* a rail, but never a server.\n *\n * The configured server URL is used directly as the upload endpoint (the\n * NIP-96 `api_url` / Blossom base). Hosts that need `.well-known` discovery can\n * resolve it before constructing the uploader.\n *\n * @example\n * ```ts\n * const uploader = createHttpUploader({\n * rails: { nip96: { servers: ['https://nostr.build/api/v2/nip96/upload'] } },\n * signEvent: (tmpl) => signer.signEvent(tmpl),\n * });\n * runtime.registerService('upload', createUploadService({ uploader }));\n * ```\n *\n * @packageDocumentation\n */\n\nimport type { EventTemplate, NostrEvent } from '@napplet/core';\nimport type {\n NostrTag,\n UploadDimensions,\n UploadRail,\n UploadRequest,\n UploadResult,\n Uploader,\n UploaderContext,\n} from './upload-service.js';\n\n/** NIP-98 HTTP-auth event kind. */\nconst KIND_NIP98 = 27235;\n/** Blossom authorization event kind. */\nconst KIND_BLOSSOM_AUTH = 24242;\n/** Blossom auth-event lifetime, in seconds. */\nconst BLOSSOM_AUTH_TTL_S = 3600;\n\n/** Per-rail server configuration. The first server is the primary endpoint. */\nexport interface RailServerConfig {\n /** Ordered server endpoint URLs; index 0 is primary. */\n servers: string[];\n}\n\n/** Storage rails this uploader can serve. */\nexport interface HttpUploaderRails {\n /** NIP-96 HTTP file storage. */\n nip96?: RailServerConfig;\n /** Blossom blob storage. */\n blossom?: RailServerConfig;\n}\n\n/** Signs an event template on the user's behalf (shell holds the key). */\nexport type SignEvent = (template: EventTemplate) => Promise<NostrEvent>;\n\n/** Options for {@link createHttpUploader}. */\nexport interface HttpUploaderOptions {\n /** Configured rails + their servers. */\n rails: HttpUploaderRails;\n /** Rail to use when a request omits one; defaults to the first configured rail. */\n defaultRail?: UploadRail;\n /** Signs NIP-98 / Blossom auth events. Required. */\n signEvent: SignEvent;\n /** Fetch implementation; defaults to the global `fetch`. */\n fetch?: typeof fetch;\n /** Hex SHA-256 of the payload bytes; defaults to Web Crypto. */\n digestSha256?: (bytes: Uint8Array) => Promise<string>;\n /** Unix *seconds* clock for event timestamps; defaults to `Date.now()/1000`. */\n now?: () => number;\n}\n\ndeclare const btoa: (data: string) => string;\ndeclare const crypto: { subtle: { digest(alg: string, data: ArrayBuffer): Promise<ArrayBuffer> } };\n\n/**\n * Create the reference HTTP {@link Uploader} (NIP-96 + Blossom rails).\n *\n * @param options - Rails, server config, and the injected `signEvent`.\n * @returns An {@link Uploader} for `createUploadService({ uploader })`.\n * @throws If `options.signEvent` is missing.\n */\nexport function createHttpUploader(options: HttpUploaderOptions): Uploader {\n if (!options || typeof options.signEvent !== 'function') {\n throw new Error('createHttpUploader: options.signEvent is required');\n }\n const rails = options.rails ?? {};\n const signEvent = options.signEvent;\n const fetchFn = options.fetch ?? fetch;\n const digest = options.digestSha256 ?? defaultDigestSha256;\n const nowS = options.now ?? (() => Math.floor(Date.now() / 1000));\n const defaultRail = options.defaultRail ?? firstConfiguredRail(rails);\n\n async function upload(request: UploadRequest, ctx: UploaderContext): Promise<UploadResult> {\n const rail = request.rail ?? defaultRail;\n const config = rail === 'nip96' ? rails.nip96 : rail === 'blossom' ? rails.blossom : undefined;\n if (rail !== 'nip96' && rail !== 'blossom') {\n return failed(ctx.uploadId, rail ?? 'unknown', 'unsupported rail');\n }\n\n const server = config?.servers?.[0];\n if (!server) {\n return failed(ctx.uploadId, rail, 'no server configured');\n }\n\n const bytes = await toBytes(request.data);\n const sha256 = await digest(bytes);\n\n try {\n return rail === 'nip96'\n ? await uploadNip96({ request, ctx, server, bytes, sha256, signEvent, fetchFn, nowS })\n : await uploadBlossom({ request, ctx, server, bytes, sha256, signEvent, fetchFn, nowS });\n } catch (err) {\n return failed(ctx.uploadId, rail, toErrorMessage(err), sha256);\n }\n }\n\n return { upload };\n}\n\ninterface RailUploadArgs {\n request: UploadRequest;\n ctx: UploaderContext;\n server: string;\n bytes: Uint8Array;\n sha256: string;\n signEvent: SignEvent;\n fetchFn: typeof fetch;\n nowS: () => number;\n}\n\nasync function uploadNip96(args: RailUploadArgs): Promise<UploadResult> {\n const { request, ctx, server, bytes, sha256, signEvent, fetchFn, nowS } = args;\n\n const auth = await signEvent({\n kind: KIND_NIP98,\n created_at: nowS(),\n content: '',\n tags: [\n ['u', server],\n ['method', 'POST'],\n ['payload', sha256],\n ],\n });\n\n const form = new FormData();\n form.append('file', new Blob([bytesToArrayBuffer(bytes)], { type: request.mimeType }), request.filename ?? 'file');\n if (request.caption !== undefined) form.append('caption', request.caption);\n if (request.mimeType !== undefined) form.append('content_type', request.mimeType);\n if (request.noTransform) form.append('no_transform', 'true');\n\n const res = await fetchFn(server, {\n method: 'POST',\n headers: { Authorization: nostrAuthHeader(auth) },\n body: form,\n });\n\n if (!res.ok) {\n return failed(ctx.uploadId, 'nip96', `server rejected (HTTP ${res.status})`, sha256);\n }\n\n const body = (await res.json()) as Nip96Response;\n if (body.status === 'error') {\n return failed(ctx.uploadId, 'nip96', body.message ?? 'upload failed', sha256);\n }\n\n const tags = body.nip94_event?.tags ?? [];\n return fromNip94Tags(ctx.uploadId, 'nip96', tags, bytes.byteLength, sha256);\n}\n\ninterface Nip96Response {\n status?: 'success' | 'error' | string;\n message?: string;\n nip94_event?: { tags?: string[][] };\n}\n\n/** Map NIP-94 / imeta tags into an {@link UploadResult}. */\nfunction fromNip94Tags(\n uploadId: string,\n rail: UploadRail,\n tags: string[][],\n fallbackSize: number,\n fallbackSha: string,\n): UploadResult {\n const get = (name: string): string | undefined => tags.find((t) => t[0] === name)?.[1];\n const url = get('url');\n const result: UploadResult = {\n ok: Boolean(url),\n uploadId,\n status: url ? 'complete' : 'failed',\n rail,\n sha256: get('x') ?? fallbackSha,\n nip94: tags as NostrTag[],\n };\n if (url) result.url = url;\n const ox = get('ox');\n if (ox) result.originalSha256 = ox;\n const size = get('size');\n result.size = size ? Number(size) : fallbackSize;\n const m = get('m');\n if (m) result.mimeType = m;\n const dim = parseDimensions(get('dim'));\n if (dim) result.dimensions = dim;\n const blurhash = get('blurhash');\n if (blurhash) result.blurhash = blurhash;\n if (!url) result.error = 'server returned no url';\n return result;\n}\n\nasync function uploadBlossom(args: RailUploadArgs): Promise<UploadResult> {\n const { request, ctx, server, bytes, sha256, signEvent, fetchFn, nowS } = args;\n\n const auth = await signEvent({\n kind: KIND_BLOSSOM_AUTH,\n created_at: nowS(),\n content: `Upload ${request.filename ?? 'file'}`,\n tags: [\n ['t', 'upload'],\n ['x', sha256],\n ['expiration', String(nowS() + BLOSSOM_AUTH_TTL_S)],\n ],\n });\n\n const endpoint = `${trimTrailingSlash(server)}/upload`;\n const headers: Record<string, string> = { Authorization: nostrAuthHeader(auth) };\n if (request.mimeType) headers['Content-Type'] = request.mimeType;\n\n const res = await fetchFn(endpoint, {\n method: 'PUT',\n headers,\n body: bytesToArrayBuffer(bytes),\n });\n\n if (!res.ok) {\n return failed(ctx.uploadId, 'blossom', `server rejected (HTTP ${res.status})`, sha256);\n }\n\n const blob = (await res.json()) as BlossomDescriptor;\n if (!blob.url) {\n return failed(ctx.uploadId, 'blossom', 'server returned no url', sha256);\n }\n\n const result: UploadResult = {\n ok: true,\n uploadId: ctx.uploadId,\n status: 'complete',\n rail: 'blossom',\n url: blob.url,\n sha256: blob.sha256 ?? sha256,\n size: blob.size ?? bytes.byteLength,\n };\n if (blob.type) result.mimeType = blob.type;\n return result;\n}\n\ninterface BlossomDescriptor {\n url?: string;\n sha256?: string;\n size?: number;\n type?: string;\n}\n\nfunction failed(uploadId: string, rail: UploadRail, error: string, sha256?: string): UploadResult {\n return { ok: false, uploadId, status: 'failed', rail, error, ...(sha256 ? { sha256 } : {}) };\n}\n\nfunction firstConfiguredRail(rails: HttpUploaderRails): UploadRail | undefined {\n if (rails.nip96?.servers?.length) return 'nip96';\n if (rails.blossom?.servers?.length) return 'blossom';\n return undefined;\n}\n\nfunction nostrAuthHeader(event: NostrEvent): string {\n return `Nostr ${base64Utf8(JSON.stringify(event))}`;\n}\n\nfunction base64Utf8(s: string): string {\n // UTF-8 safe base64 (event content/tags may contain non-ASCII).\n return btoa(String.fromCharCode(...new TextEncoder().encode(s)));\n}\n\nfunction parseDimensions(dim: string | undefined): UploadDimensions | undefined {\n if (!dim) return undefined;\n const m = /^(\\d+)x(\\d+)$/.exec(dim);\n if (!m) return undefined;\n return { width: Number(m[1]), height: Number(m[2]) };\n}\n\nasync function toBytes(data: ArrayBuffer | Blob): Promise<Uint8Array> {\n if (data instanceof ArrayBuffer) return new Uint8Array(data);\n return new Uint8Array(await data.arrayBuffer());\n}\n\nfunction bytesToArrayBuffer(bytes: Uint8Array): ArrayBuffer {\n return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer;\n}\n\nfunction trimTrailingSlash(url: string): string {\n return url.endsWith('/') ? url.slice(0, -1) : url;\n}\n\nasync function defaultDigestSha256(bytes: Uint8Array): Promise<string> {\n const buf = await crypto.subtle.digest('SHA-256', bytesToArrayBuffer(bytes));\n return Array.from(new Uint8Array(buf))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n}\n\nfunction toErrorMessage(err: unknown): string {\n if (err instanceof Error) return err.message;\n if (typeof err === 'string') return err;\n return 'upload failed';\n}\n","/**\n * intent-service.ts — NAP-INTENT (archetype intent dispatch) reference service.\n *\n * Shell-side handler for the NAP-INTENT wire protocol. It is a pure envelope\n * router: it validates `intent.*` envelopes, delegates archetype resolution,\n * default handling, window lifecycle, and payload delivery to an injected\n * {@link IntentResolver}, and posts the correlated result / push messages\n * (echoing the request `id`) back to the napplet.\n *\n * The resolver is injected (options-as-bridge) so this service has no shell or\n * window-manager dependency and is fully unit-testable. A concrete\n * catalog-backed resolver ships alongside as {@link createCatalogIntentResolver}.\n *\n * ──────────────────────────── Responsibilities ────────────────────────────\n * Inbound: intent.invoke, intent.available, intent.handlers\n * Outbound: intent.invoke.result, intent.available.result,\n * intent.handlers.result, intent.changed\n *\n * The shell owns archetype→handler resolution, the user's default-handler\n * preference, the \"open with…\" chooser, and window creation/focus — all behind\n * the {@link IntentResolver}. This service only marshals the wire protocol and\n * fans `intent.changed` pushes out to the napplets it has served.\n *\n * @example\n * ```ts\n * import { createIntentService, createCatalogIntentResolver } from '@kehto/services';\n *\n * const resolver = createCatalogIntentResolver({ loadCatalog, windows });\n * runtime.registerService('intent', createIntentService({ resolver }));\n * ```\n *\n * @packageDocumentation\n */\n\nimport type { NappletMessage } from '@napplet/core';\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\nimport type { IntentAvailability, IntentRequest, IntentResult } from './intent-types.js';\n\n/** Intent service version — follows semver. */\nconst INTENT_SERVICE_VERSION = '1.0.0';\n\n/** Context passed to {@link IntentResolver.invoke} for trust/attribution. */\nexport interface IntentResolverContext {\n /** Window id of the napplet that issued the request. */\n windowId: string;\n}\n\n/**\n * Abstract intent resolver. Implementors own the installed-napplet catalog,\n * archetype→handler resolution, the user's default-handler preference, the\n * \"open with…\" chooser, window creation/focus, and payload delivery. The\n * service translates wire envelopes into these calls and back.\n */\nexport interface IntentResolver {\n /**\n * Resolve `request.archetype` to a handler, create or focus its window, and\n * deliver `payload`. Returns once the handler is resolved and the window\n * created; delivery MAY complete asynchronously.\n */\n invoke(request: IntentRequest, context: IntentResolverContext): IntentResult | Promise<IntentResult>;\n /** Report whether the runtime can currently satisfy `archetype`, and how. */\n available(archetype: string): IntentAvailability | Promise<IntentAvailability>;\n /** Report availability for every archetype the runtime can currently satisfy. */\n handlers(): IntentAvailability[] | Promise<IntentAvailability[]>;\n /**\n * Register for availability changes (a napplet installed/removed, or a default\n * handler changed). The service forwards each change to served napplets as an\n * `intent.changed` push. Returns an unsubscribe handle. Resolvers whose\n * catalog never changes at runtime MAY omit this.\n */\n onChanged?(listener: (availability: IntentAvailability) => void): () => void;\n}\n\n/** Options for {@link createIntentService}. */\nexport interface IntentServiceOptions {\n /** The intent resolver the shell uses to route archetypes. Required. */\n resolver: IntentResolver;\n}\n\ntype Send = (msg: NappletMessage) => void;\n\nconst INTENT_DESCRIPTOR: ServiceDescriptor = {\n name: 'intent',\n version: INTENT_SERVICE_VERSION,\n description: 'NAP-INTENT archetype intent dispatch — invoke/available/handlers',\n};\n\nfunction toErrorMessage(err: unknown): string {\n if (err instanceof Error) return err.message;\n if (typeof err === 'string') return err;\n return 'intent request failed';\n}\n\n/**\n * Create the NAP-INTENT service handler.\n *\n * @param options - Must provide an {@link IntentResolver}.\n * @returns A `ServiceHandler` ready for `runtime.registerService('intent', handler)`.\n * @throws If `options.resolver` is missing.\n */\nexport function createIntentService(options: IntentServiceOptions): ServiceHandler {\n if (!options || typeof options.resolver !== 'object' || options.resolver === null) {\n throw new Error('createIntentService: options.resolver is required');\n }\n const { resolver } = options;\n\n // Latest send callback per served window, used to fan `intent.changed`\n // pushes out to every napplet that has interacted with the intent domain.\n const windows = new Map<string, Send>();\n\n // Subscribe once for availability changes and broadcast them as pushes.\n resolver.onChanged?.((availability) => {\n for (const send of windows.values()) {\n send({ type: 'intent.changed', availability } as NappletMessage);\n }\n });\n\n /**\n * Run a resolver call and marshal its outcome onto `resultType`. The resolver\n * method may be sync or async — `settle` normalizes it to a promise and\n * catches a synchronous throw (e.g. a sync resolver that rejects an input)\n * the same way it catches an async rejection.\n */\n function settle<T>(\n call: () => T | Promise<T>,\n send: Send,\n resultType: string,\n id: string,\n onValue: (value: T) => NappletMessage,\n ): void {\n let pending: Promise<T>;\n try {\n pending = Promise.resolve(call());\n } catch (err) {\n send({ type: resultType, id, error: toErrorMessage(err) } as NappletMessage);\n return;\n }\n pending\n .then((value) => send(onValue(value)))\n .catch((err) => send({ type: resultType, id, error: toErrorMessage(err) } as NappletMessage));\n }\n\n function handleInvoke(windowId: string, msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { id?: string; request?: IntentRequest };\n const id = m.id ?? '';\n const request = m.request;\n if (!request || typeof request !== 'object' || typeof request.archetype !== 'string' || request.archetype.length === 0) {\n send({ type: 'intent.invoke.result', id, error: 'invalid request' } as NappletMessage);\n return;\n }\n settle(\n () => resolver.invoke(request, { windowId }),\n send, 'intent.invoke.result', id,\n (result) => ({ type: 'intent.invoke.result', id, result } as NappletMessage),\n );\n }\n\n function handleAvailable(msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { id?: string; archetype?: unknown };\n const id = m.id ?? '';\n if (typeof m.archetype !== 'string' || m.archetype.length === 0) {\n send({ type: 'intent.available.result', id, error: 'invalid archetype' } as NappletMessage);\n return;\n }\n const archetype = m.archetype;\n settle(\n () => resolver.available(archetype),\n send, 'intent.available.result', id,\n (availability) => ({ type: 'intent.available.result', id, availability } as NappletMessage),\n );\n }\n\n function handleHandlers(msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { id?: string };\n const id = m.id ?? '';\n settle(\n () => resolver.handlers(),\n send, 'intent.handlers.result', id,\n (handlers) => ({ type: 'intent.handlers.result', id, handlers } as NappletMessage),\n );\n }\n\n return {\n descriptor: INTENT_DESCRIPTOR,\n handleMessage(windowId: string, message: NappletMessage, send: Send): void {\n windows.set(windowId, send);\n switch (message.type) {\n case 'intent.invoke':\n handleInvoke(windowId, message, send);\n return;\n case 'intent.available':\n handleAvailable(message, send);\n return;\n case 'intent.handlers':\n handleHandlers(message, send);\n return;\n default:\n // Unknown intent.* action — silently ignored (forward-compatible).\n return;\n }\n },\n onWindowDestroyed(windowId: string): void {\n windows.delete(windowId);\n },\n };\n}\n","/**\n * catalog-intent-resolver.ts — NAP-INTENT concrete {@link IntentResolver}.\n *\n * A reference resolver that satisfies {@link IntentResolver} from an\n * installed-napplet catalog plus host-supplied policy: the user's\n * default-handler preference, an optional \"open with…\" chooser, the archetype's\n * recommended default protocol, and a window controller that creates or focuses\n * the handler window and delivers the payload.\n *\n * Resolution policy (mirrors an OS implicit-intent + default-app model):\n * 1. Gather candidates for the archetype from the catalog.\n * 2. Pick the handler — an explicit `handler` dTag, a user choice\n * (`handler: \"choose\"`), the user's default, the sole candidate, or a\n * deterministic first-candidate fallback.\n * 3. Validate the resolved handler supports the requested `action` and\n * `protocol` (or fall back to the archetype's recommended default protocol,\n * then the candidate's first accepted protocol).\n * 4. Open or focus the handler window and return its id.\n *\n * The catalog, defaults, chooser, and window controller are all injected, so\n * this resolver carries no shell, manifest, or DOM dependency and is fully\n * unit-testable.\n *\n * @packageDocumentation\n */\n\nimport type {\n IntentAvailability,\n IntentBehavior,\n IntentCandidate,\n IntentRequest,\n IntentResult,\n} from './intent-types.js';\nimport type { IntentResolver, IntentResolverContext } from './intent-service.js';\n\n/** The actions/protocols a napplet fulfills for a single archetype. */\nexport interface IntentArchetypeSupport {\n /** Verbs this napplet supports for the archetype (e.g. `[\"open\", \"edit\"]`). */\n actions: string[];\n /** NAP-N protocol ids this napplet accepts for the archetype. */\n protocols: string[];\n}\n\n/**\n * One installed napplet's intent surface, derived from its signed NIP-5A\n * manifest. Keyed by archetype slug so a single napplet can fulfill several\n * roles.\n */\nexport interface IntentCatalogEntry {\n /** The napplet's dTag. */\n dTag: string;\n /** Human-readable title from the manifest. */\n title?: string;\n /** Archetype slug → the actions/protocols this napplet fulfills for it. */\n archetypes: Record<string, IntentArchetypeSupport>;\n}\n\n/** Parameters handed to {@link IntentWindowController.open}. */\nexport interface IntentOpenParams {\n /** dTag of the resolved handler napplet. */\n dTag: string;\n /** The archetype being dispatched. */\n archetype: string;\n /** The resolved action (e.g. `\"open\"`). */\n action: string;\n /** The wire format the payload is delivered with, when one was resolved. */\n protocol?: string;\n /** The opaque payload to deliver. */\n payload?: unknown;\n /** Window-behavior hints from the request. */\n behavior?: IntentBehavior;\n /** Window id of the napplet that issued the intent. */\n callerWindowId: string;\n}\n\n/** Creates or focuses the handler window and delivers the intent payload. */\nexport interface IntentWindowController {\n /**\n * Create or focus the handler window and deliver `payload` via `protocol`.\n * Returns the window id the handler was created or focused in.\n */\n open(params: IntentOpenParams): { windowId: string } | Promise<{ windowId: string }>;\n}\n\n/** Options for {@link createCatalogIntentResolver}. */\nexport interface CatalogIntentResolverOptions {\n /** Return the installed-napplet catalog (signed NIP-5A manifests). Required. */\n loadCatalog(): IntentCatalogEntry[] | Promise<IntentCatalogEntry[]>;\n /** Window controller used to create/focus the resolved handler's window. Required. */\n windows: IntentWindowController;\n /**\n * The user's default handler dTag for an archetype, or `undefined` when none\n * is set. Default-handler settings are user state — never set by napplets.\n */\n getDefaultHandler?(archetype: string): string | undefined;\n /**\n * Resolve a `handler: \"choose\"` prompt (or a no-default ambiguity) to a dTag.\n * Returning `undefined` means the user cancelled. When omitted, ambiguous\n * resolution falls back to the first candidate.\n */\n chooseHandler?(\n archetype: string,\n candidates: IntentCandidate[],\n callerWindowId: string,\n ): string | undefined | Promise<string | undefined>;\n /** The archetype's recommended default protocol when the caller omits one. */\n defaultProtocol?(archetype: string): string | undefined;\n}\n\n/**\n * A {@link IntentResolver} backed by a catalog, with a host hook to announce\n * catalog/default changes.\n */\nexport interface CatalogIntentResolver extends IntentResolver {\n /**\n * Announce that the catalog or default handler for `archetype` changed. The\n * resolver recomputes availability and notifies `onChanged` listeners, which\n * the intent service forwards to napplets as `intent.changed` pushes.\n */\n notifyChanged(archetype: string): void;\n}\n\n/** Build the candidate list for an archetype, marking the user's default. */\nfunction candidatesFor(\n catalog: IntentCatalogEntry[],\n archetype: string,\n defaultHandler: string | undefined,\n): IntentCandidate[] {\n const candidates: IntentCandidate[] = [];\n for (const entry of catalog) {\n const support = entry.archetypes[archetype];\n if (!support) continue;\n candidates.push({\n dTag: entry.dTag,\n ...(entry.title === undefined ? {} : { title: entry.title }),\n actions: support.actions,\n protocols: support.protocols,\n ...(entry.dTag === defaultHandler ? { isDefault: true } : {}),\n });\n }\n return candidates;\n}\n\nfunction fail(archetype: string, action: string, error: string): IntentResult {\n return { ok: false, archetype, action, handled: false, error };\n}\n\n/**\n * Create a catalog-backed NAP-INTENT resolver.\n *\n * @param options - Catalog loader and window controller (required) plus\n * optional default-handler, chooser, and default-protocol policy hooks.\n * @returns A {@link CatalogIntentResolver} for `createIntentService({ resolver })`.\n * @throws If `options.loadCatalog` or `options.windows` is missing.\n *\n * @example\n * ```ts\n * const resolver = createCatalogIntentResolver({\n * loadCatalog: () => installedNapplets,\n * windows: { open: ({ dTag }) => ({ windowId: openWindow(dTag) }) },\n * getDefaultHandler: (a) => userDefaults[a],\n * });\n * ```\n */\nexport function createCatalogIntentResolver(options: CatalogIntentResolverOptions): CatalogIntentResolver {\n if (!options || typeof options.loadCatalog !== 'function') {\n throw new Error('createCatalogIntentResolver: options.loadCatalog is required');\n }\n if (!options.windows || typeof options.windows.open !== 'function') {\n throw new Error('createCatalogIntentResolver: options.windows is required');\n }\n const { loadCatalog, windows, getDefaultHandler, chooseHandler, defaultProtocol } = options;\n const listeners = new Set<(availability: IntentAvailability) => void>();\n\n async function availabilityFor(archetype: string): Promise<IntentAvailability> {\n const catalog = await loadCatalog();\n const def = getDefaultHandler?.(archetype);\n const candidates = candidatesFor(catalog, archetype, def);\n return {\n archetype,\n available: candidates.length > 0,\n candidates,\n hasDefault: def !== undefined && candidates.some((c) => c.dTag === def),\n };\n }\n\n /** Decide which candidate dTag should handle the request, or null to cancel. */\n async function pickHandler(\n archetype: string,\n candidates: IntentCandidate[],\n preference: IntentRequest['handler'],\n callerWindowId: string,\n ): Promise<string | null> {\n // Explicit dTag target (anything that isn't the two reserved keywords).\n if (typeof preference === 'string' && preference !== 'default' && preference !== 'choose') {\n return preference;\n }\n if (preference === 'choose') {\n const picked = await chooseHandler?.(archetype, candidates, callerWindowId);\n return picked ?? null;\n }\n // Default path: user's default, then the sole candidate, then chooser, then\n // a deterministic first-candidate fallback.\n const def = getDefaultHandler?.(archetype);\n if (def !== undefined && candidates.some((c) => c.dTag === def)) return def;\n if (candidates.length === 1) return candidates[0].dTag;\n if (chooseHandler) {\n const picked = await chooseHandler(archetype, candidates, callerWindowId);\n return picked ?? null;\n }\n return candidates[0].dTag;\n }\n\n async function invoke(request: IntentRequest, context: IntentResolverContext): Promise<IntentResult> {\n const archetype = request.archetype;\n const action = request.action ?? 'open';\n const catalog = await loadCatalog();\n const candidates = candidatesFor(catalog, archetype, getDefaultHandler?.(archetype));\n if (candidates.length === 0) return fail(archetype, action, 'no handler');\n\n const pickedDTag = await pickHandler(archetype, candidates, request.handler, context.windowId);\n if (pickedDTag === null) return fail(archetype, action, 'user cancelled');\n\n const handler = candidates.find((c) => c.dTag === pickedDTag);\n if (!handler) return fail(archetype, action, 'no handler');\n\n if (!handler.actions.includes(action)) return fail(archetype, action, 'unsupported action');\n\n const protocol = request.protocol ?? defaultProtocol?.(archetype) ?? handler.protocols[0];\n if (protocol !== undefined && handler.protocols.length > 0 && !handler.protocols.includes(protocol)) {\n return fail(archetype, action, 'unsupported protocol');\n }\n\n let windowId: string;\n try {\n const opened = await windows.open({\n dTag: handler.dTag,\n archetype,\n action,\n ...(protocol === undefined ? {} : { protocol }),\n ...(request.payload === undefined ? {} : { payload: request.payload }),\n ...(request.behavior === undefined ? {} : { behavior: request.behavior }),\n callerWindowId: context.windowId,\n });\n windowId = opened.windowId;\n } catch {\n return fail(archetype, action, 'invoke failed');\n }\n\n return {\n ok: true,\n archetype,\n action,\n handled: true,\n handler: handler.dTag,\n windowId,\n ...(protocol === undefined ? {} : { protocol }),\n };\n }\n\n async function handlers(): Promise<IntentAvailability[]> {\n const catalog = await loadCatalog();\n const archetypes = new Set<string>();\n for (const entry of catalog) {\n for (const slug of Object.keys(entry.archetypes)) archetypes.add(slug);\n }\n return Promise.all([...archetypes].map((a) => availabilityFor(a)));\n }\n\n return {\n invoke,\n available: availabilityFor,\n handlers,\n onChanged(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n notifyChanged(archetype) {\n if (listeners.size === 0) return;\n void availabilityFor(archetype).then((availability) => {\n for (const listener of listeners) listener(availability);\n });\n },\n };\n}\n","/**\n * manifest-intent-catalog.ts — signed-manifest → NAP-INTENT catalog adapter.\n *\n * Adapts a resolved NIP-5A/5D napplet manifest's archetype tags into an\n * {@link IntentCatalogEntry} — the shape `createCatalogIntentResolver.loadCatalog`\n * consumes. This lets NAP-INTENT availability and handler candidacy flow from\n * verified manifest tags rather than host-injected catalog data.\n *\n * To avoid a `@kehto/services → @kehto/nip` dependency cycle (services must stay\n * dependency-light and `@kehto/nip` is a lower-level NIP utility), the adapter\n * takes a minimal STRUCTURAL input {@link ManifestArchetypeInput} that the\n * `@kehto/nip/5d` `NappletManifest` satisfies by duck typing — callers pass\n * `resolved.manifest` directly without any package coupling.\n *\n * @packageDocumentation\n */\n\nimport type { IntentArchetypeSupport, IntentCatalogEntry } from './catalog-intent-resolver.js';\n\n/** NAP-INTENT default action when a manifest does not enumerate actions. */\nconst DEFAULT_ACTIONS: readonly string[] = ['open'];\n\n/**\n * The structural subset of `@kehto/nip/5d` `NappletManifest` the adapter needs.\n * Intentionally a duck-typed shape so the playground (or any caller) can pass a\n * resolved manifest without importing `@kehto/nip`.\n */\nexport interface ManifestArchetypeInput {\n /** The napplet's `d` identifier. */\n dTag: string;\n /** Optional human-readable title from the manifest. */\n title?: string;\n /**\n * Archetype slugs this napplet fulfills, from the manifest's `archetype` tags;\n * the optional `nap` is the recommended default wire protocol.\n */\n archetypes: Array<{ slug: string; nap?: string }>;\n}\n\n/**\n * Map a resolved napplet manifest's archetype data into an\n * {@link IntentCatalogEntry}.\n *\n * Each archetype `{ slug, nap }` becomes a keyed support record where `actions`\n * defaults to `['open']` (the NAP-INTENT default action — manifests do not\n * enumerate actions in this phase) and `protocols` is `[nap]` when a NAP-N is\n * present, else `[]`. Duplicate slugs keep the last occurrence.\n *\n * @param manifest - A resolved manifest's structural archetype data.\n * @returns The `IntentCatalogEntry` for `createCatalogIntentResolver`.\n *\n * @example\n * ```ts\n * manifestToIntentCatalogEntry({\n * dTag: 'profile-viewer',\n * title: 'Profile',\n * archetypes: [{ slug: 'profile', nap: 'NAP-1' }],\n * });\n * // → { dTag: 'profile-viewer', title: 'Profile',\n * // archetypes: { profile: { actions: ['open'], protocols: ['NAP-1'] } } }\n * ```\n */\nexport function manifestToIntentCatalogEntry(manifest: ManifestArchetypeInput): IntentCatalogEntry {\n const archetypes: Record<string, IntentArchetypeSupport> = {};\n for (const { slug, nap } of manifest.archetypes) {\n archetypes[slug] = {\n actions: [...DEFAULT_ACTIONS],\n protocols: nap ? [nap] : [],\n };\n }\n return {\n dTag: manifest.dTag,\n ...(manifest.title === undefined ? {} : { title: manifest.title }),\n archetypes,\n };\n}\n","/**\n * cvm-service.ts — NAP-CVM (ContextVM bridge) reference service.\n *\n * Shell-side handler for the NAP-CVM wire protocol. It is a pure envelope\n * router: it validates `cvm.*` envelopes, delegates the actual ContextVM /\n * MCP-over-Nostr work to an injected {@link CvmTransport}, and posts the\n * correlated `*.result` (echoing the request `id`) back to the napplet.\n *\n * The transport is injected (options-as-bridge) so this service has no Nostr\n * dependency and is fully unit-testable. A concrete ContextVM transport ships\n * separately at `@kehto/services/cvm-nostr-transport`.\n *\n * ──────────────────────────── Responsibilities ────────────────────────────\n * Inbound: cvm.discover, cvm.request, cvm.close\n * Outbound: cvm.discover.result, cvm.request.result, cvm.close.result,\n * cvm.event (server-pushed MCP notifications)\n *\n * MCP-level errors are returned inside `request.result.message.error`;\n * transport/shell-policy failures are returned in the envelope `error` field.\n *\n * `cvm.event` is fanned out to every window that holds an active session with\n * the originating server (a window opens a session by issuing a `cvm.request`\n * and closes it via `cvm.close` or window teardown).\n *\n * @example\n * ```ts\n * import { createCvmService } from '@kehto/services';\n * import { createNostrCvmTransport } from '@kehto/services/cvm-nostr-transport';\n *\n * const transport = createNostrCvmTransport({ defaultRelays: ['wss://relay.contextvm.org'] });\n * runtime.registerService('cvm', createCvmService({ transport }));\n * ```\n */\n\nimport type { NappletMessage } from '@napplet/core';\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\nimport type {\n CvmDiscoverQuery,\n CvmRequestOptions,\n CvmServer,\n CvmServerRef,\n McpMessage,\n} from './cvm-types.js';\n\n/** CVM service version — follows semver. */\nconst CVM_SERVICE_VERSION = '1.0.0';\n\n/**\n * Abstract ContextVM transport. Implementors own Nostr relay access, signing,\n * encryption (CEP-4 gift wrap), JSON-RPC correlation, and MCP initialization.\n */\nexport interface CvmTransport {\n /** Resolve public ContextVM server announcements matching the query. */\n discover(query?: CvmDiscoverQuery): Promise<CvmServer[]>;\n /** Send a raw MCP message to a server and resolve with the MCP response. */\n request(server: CvmServerRef, message: McpMessage, options?: CvmRequestOptions): Promise<McpMessage>;\n /** Release any session state held for a server (subscriptions, init cache). */\n close(server: CvmServerRef): Promise<void>;\n /**\n * Subscribe to server-pushed MCP messages not correlated to a single\n * request (e.g. notifications). Returns a handle whose `close()` detaches.\n */\n onEvent(handler: (server: CvmServerRef, message: McpMessage) => void): { close(): void };\n}\n\n/** Options for {@link createCvmService}. */\nexport interface CvmServiceOptions {\n /** The ContextVM transport the shell uses to reach servers. Required. */\n transport: CvmTransport;\n}\n\n/** The created CVM service, exposing the handler plus a disposal hook. */\nexport interface CvmService extends ServiceHandler {\n /** Detach the transport event subscription. Idempotent. */\n dispose(): void;\n}\n\ntype Send = (msg: NappletMessage) => void;\n\nconst CVM_DESCRIPTOR: ServiceDescriptor = {\n name: 'cvm',\n version: CVM_SERVICE_VERSION,\n description: 'NAP-CVM ContextVM bridge — MCP over Nostr',\n};\n\n/**\n * Create the NAP-CVM service handler.\n *\n * @param options - Must provide a {@link CvmTransport}.\n * @returns A {@link CvmService} (a `ServiceHandler` with a `dispose()` hook).\n * @throws If `options.transport` is missing.\n */\nexport function createCvmService(options: CvmServiceOptions): CvmService {\n if (!options || typeof options.transport !== 'object' || options.transport === null) {\n throw new Error('createCvmService: options.transport is required');\n }\n const { transport } = options;\n\n // Per-window send callbacks, captured at request time for cvm.event fan-out.\n const sendByWindow = new Map<string, Send>();\n // serverPubkey -> set of windowIds with an active session.\n const windowsByServer = new Map<string, Set<string>>();\n\n function openSession(windowId: string, server: CvmServerRef, send: Send): void {\n sendByWindow.set(windowId, send);\n let windows = windowsByServer.get(server.pubkey);\n if (!windows) {\n windows = new Set<string>();\n windowsByServer.set(server.pubkey, windows);\n }\n windows.add(windowId);\n }\n\n function closeSession(windowId: string, serverPubkey: string): void {\n const windows = windowsByServer.get(serverPubkey);\n if (windows) {\n windows.delete(windowId);\n if (windows.size === 0) windowsByServer.delete(serverPubkey);\n }\n }\n\n const eventSub = transport.onEvent((server, message) => {\n const windows = windowsByServer.get(server.pubkey);\n if (!windows) return;\n for (const windowId of windows) {\n const send = sendByWindow.get(windowId);\n send?.({ type: 'cvm.event', server, message } as NappletMessage);\n }\n });\n\n function handleDiscover(msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { id?: string; query?: CvmDiscoverQuery };\n const id = m.id ?? '';\n void transport\n .discover(m.query)\n .then((servers) => send({ type: 'cvm.discover.result', id, servers } as NappletMessage))\n .catch((err) =>\n send({ type: 'cvm.discover.result', id, servers: [], error: toErrorMessage(err) } as NappletMessage),\n );\n }\n\n function handleRequest(windowId: string, msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & {\n id?: string;\n server?: CvmServerRef;\n message?: McpMessage;\n options?: CvmRequestOptions;\n };\n const id = m.id ?? '';\n if (!m.server || typeof m.server.pubkey !== 'string' || m.server.pubkey.length === 0) {\n send({ type: 'cvm.request.result', id, error: 'server not found' } as NappletMessage);\n return;\n }\n if (!m.message || typeof m.message !== 'object') {\n send({ type: 'cvm.request.result', id, error: 'unsupported method' } as NappletMessage);\n return;\n }\n openSession(windowId, m.server, send);\n void transport\n .request(m.server, m.message, m.options)\n .then((message) => send({ type: 'cvm.request.result', id, message } as NappletMessage))\n .catch((err) =>\n send({ type: 'cvm.request.result', id, error: toErrorMessage(err) } as NappletMessage),\n );\n }\n\n function handleClose(windowId: string, msg: NappletMessage, send: Send): void {\n const m = msg as NappletMessage & { id?: string; server?: CvmServerRef };\n const id = m.id ?? '';\n if (!m.server || typeof m.server.pubkey !== 'string') {\n send({ type: 'cvm.close.result', id, error: 'server not found' } as NappletMessage);\n return;\n }\n closeSession(windowId, m.server.pubkey);\n void transport\n .close(m.server)\n .then(() => send({ type: 'cvm.close.result', id } as NappletMessage))\n .catch((err) => send({ type: 'cvm.close.result', id, error: toErrorMessage(err) } as NappletMessage));\n }\n\n return {\n descriptor: CVM_DESCRIPTOR,\n handleMessage(windowId: string, message: NappletMessage, send: Send): void {\n switch (message.type) {\n case 'cvm.discover':\n handleDiscover(message, send);\n return;\n case 'cvm.request':\n handleRequest(windowId, message, send);\n return;\n case 'cvm.close':\n handleClose(windowId, message, send);\n return;\n default:\n // Unknown cvm.* action — silently ignored (forward-compatible).\n return;\n }\n },\n onWindowDestroyed(windowId: string): void {\n sendByWindow.delete(windowId);\n for (const [pubkey, windows] of windowsByServer) {\n windows.delete(windowId);\n if (windows.size === 0) windowsByServer.delete(pubkey);\n }\n },\n dispose(): void {\n eventSub.close();\n },\n };\n}\n\nfunction toErrorMessage(err: unknown): string {\n if (err instanceof Error) return err.message;\n if (typeof err === 'string') return err;\n return 'cvm request failed';\n}\n","/**\n * link-service.ts — NAP-LINK reference service.\n *\n * Shell-mediated user-visible navigation. The napplet requests `link.open`;\n * the shell validates policy and hands navigation to a host-owned context.\n */\n\nimport type { LinkOpenErrorCode, LinkOpenOptions, LinkOpenResult, NappletMessage } from '@napplet/core';\nimport type { LinkOpenMessage, LinkOpenResultMessage } from '@napplet/nap/link/types';\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\n\nconst LINK_SERVICE_VERSION = '1.0.0';\nconst DEFAULT_ALLOWED_PROTOCOLS = ['http:', 'https:'] as const;\n\n/**\n * Host context passed to {@link LinkServiceOptions.open}.\n */\nexport interface LinkOpenContext {\n /** Window id of the requesting napplet. */\n windowId: string;\n /** Parsed absolute URL after validation. */\n url: URL;\n /** Optional untrusted napplet display hints. */\n options?: LinkOpenOptions;\n}\n\n/**\n * Options for {@link createLinkService}.\n */\nexport interface LinkServiceOptions {\n /**\n * Protocols allowed before host policy runs. Defaults to `http:` and `https:`.\n */\n allowedProtocols?: readonly string[];\n /**\n * Host-owned opener. Return `{ status: \"opened\" }` after handing navigation\n * to the host context, or `{ status: \"denied\" }` to reject by policy/user.\n * When omitted, all otherwise-valid URLs are denied.\n */\n open?: (context: LinkOpenContext) => LinkOpenResult | Promise<LinkOpenResult>;\n}\n\nfunction denied(id: string, error: LinkOpenErrorCode): LinkOpenResultMessage {\n return { type: 'link.open.result', id, status: 'denied', error };\n}\n\nfunction parseLinkUrl(id: string, rawUrl: string): URL | LinkOpenResultMessage {\n try {\n return new URL(rawUrl);\n } catch {\n return denied(id, 'invalid-url');\n }\n}\n\nfunction isProtocolAllowed(url: URL, allowedProtocols: readonly string[]): boolean {\n return allowedProtocols.includes(url.protocol);\n}\n\nasync function handleOpen(\n options: LinkServiceOptions,\n windowId: string,\n message: LinkOpenMessage,\n send: (msg: NappletMessage) => void,\n): Promise<void> {\n const parsed = parseLinkUrl(message.id, message.url);\n if ('type' in parsed) {\n send(parsed as NappletMessage);\n return;\n }\n\n const allowedProtocols = options.allowedProtocols ?? DEFAULT_ALLOWED_PROTOCOLS;\n if (!isProtocolAllowed(parsed, allowedProtocols)) {\n send(denied(message.id, 'unsupported-scheme') as NappletMessage);\n return;\n }\n\n if (!options.open) {\n send(denied(message.id, 'blocked-by-policy') as NappletMessage);\n return;\n }\n\n try {\n const result = await options.open({ windowId, url: parsed, options: message.options });\n send({\n type: 'link.open.result',\n id: message.id,\n status: result.status,\n ...(result.status === 'denied' ? { error: 'blocked-by-policy' as LinkOpenErrorCode } : {}),\n } as NappletMessage);\n } catch {\n send(denied(message.id, 'blocked-by-policy') as NappletMessage);\n }\n}\n\n/**\n * Create the NAP-LINK reference service.\n *\n * @param options - Host policy and opener hooks for link.open requests.\n * @returns A runtime service handler for the `link` domain.\n *\n * @example\n * ```ts\n * const service = createLinkService({\n * open: ({ url }) => ({ status: url.protocol === 'https:' ? 'opened' : 'denied' }),\n * });\n * ```\n */\nexport function createLinkService(options: LinkServiceOptions = {}): ServiceHandler {\n const descriptor: ServiceDescriptor = {\n name: 'link',\n version: LINK_SERVICE_VERSION,\n description: 'NAP-LINK reference handler for shell-mediated link opening',\n };\n\n return {\n descriptor,\n handleMessage(windowId: string, message: NappletMessage, send: (msg: NappletMessage) => void): void {\n if (message.type === 'link.open') {\n void handleOpen(options, windowId, message as LinkOpenMessage, send);\n return;\n }\n\n const id = (message as NappletMessage & { id?: string }).id ?? '';\n send({\n type: `${message.type}.error`,\n id,\n error: `Unknown link method: ${message.type}`,\n } as NappletMessage);\n },\n onWindowDestroyed(_windowId: string): void {\n /* no per-window state */\n },\n };\n}\n","/**\n * lists-service.ts — NAP-LISTS reference service.\n *\n * Shell-mediated NIP-51 list metadata and mutation hooks. The napplet requests\n * `lists.*`; the shell owns list lookup, validation, signing, publishing, and\n * private-item policy.\n */\n\nimport type {\n ListItem,\n ListMutationResult,\n ListOptions,\n ListRef,\n ListSupport,\n NappletMessage,\n} from '@napplet/core';\nimport type {\n ListsAddMessage,\n ListsRemoveMessage,\n} from '@napplet/nap/lists/types';\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\n\nconst LISTS_SERVICE_VERSION = '1.0.0';\n\n/** Context passed to host-provided NAP-LISTS hooks. */\nexport interface ListsServiceContext {\n /** Window id of the requesting napplet. */\n windowId: string;\n}\n\n/** Options for {@link createListsService}. */\nexport interface ListsServiceOptions {\n /** Host-owned supported list metadata. */\n supported?: (context: ListsServiceContext) => readonly ListSupport[] | Promise<readonly ListSupport[]>;\n /** Host-owned add mutation. */\n add?: (\n list: ListRef,\n items: readonly ListItem[],\n options: ListOptions | undefined,\n context: ListsServiceContext,\n ) => ListMutationResult | Promise<ListMutationResult>;\n /** Host-owned remove mutation. */\n remove?: (\n list: ListRef,\n items: readonly ListItem[],\n options: ListOptions | undefined,\n context: ListsServiceContext,\n ) => ListMutationResult | Promise<ListMutationResult>;\n}\n\ntype Send = (msg: NappletMessage) => void;\ntype MutationHook = NonNullable<ListsServiceOptions['add'] | ListsServiceOptions['remove']>;\n\nconst LISTS_DESCRIPTOR: ServiceDescriptor = {\n name: 'lists',\n version: LISTS_SERVICE_VERSION,\n description: 'NAP-LISTS reference handler for shell-mediated NIP-51 list mutations',\n};\n\nfunction errorMessage(err: unknown, fallback: string): string {\n if (err instanceof Error) return err.message;\n if (typeof err === 'string') return err;\n return fallback;\n}\n\nfunction settle<T>(\n call: () => T | Promise<T>,\n send: Send,\n okFalse: (error: string) => NappletMessage,\n onValue: (value: T) => NappletMessage,\n): void {\n let pending: Promise<T>;\n try {\n pending = Promise.resolve(call());\n } catch (err) {\n send(okFalse(errorMessage(err, 'lists request failed')));\n return;\n }\n pending\n .then((value) => send(onValue(value)))\n .catch((err) => send(okFalse(errorMessage(err, 'lists request failed'))));\n}\n\nfunction unsupportedMutation(resultType: string, id: string): NappletMessage {\n return {\n type: resultType,\n id,\n ok: false,\n error: 'unsupported',\n reason: `${resultType.replace('.result', '')} unavailable`,\n supported: [],\n } as NappletMessage;\n}\n\nfunction settleMutation(\n hook: MutationHook | undefined,\n message: ListsAddMessage | ListsRemoveMessage,\n context: ListsServiceContext,\n send: Send,\n resultType: 'lists.add.result' | 'lists.remove.result',\n): void {\n if (!hook) {\n send(unsupportedMutation(resultType, message.id));\n return;\n }\n settle(\n () => hook(message.list, message.items, message.options, context),\n send,\n (error) => ({ type: resultType, id: message.id, ok: false, error: 'list-unavailable', reason: error } as NappletMessage),\n (result) => ({ type: resultType, id: message.id, ...result } as NappletMessage),\n );\n}\n\n/**\n * Create the NAP-LISTS reference service.\n *\n * @param options - Host list metadata and mutation hooks for lists.* requests.\n * @returns A runtime service handler for the `lists` domain.\n */\nexport function createListsService(options: ListsServiceOptions = {}): ServiceHandler {\n return {\n descriptor: LISTS_DESCRIPTOR,\n handleMessage(windowId: string, message: NappletMessage, send: Send): void {\n const id = (message as NappletMessage & { id?: string }).id ?? '';\n const context = { windowId };\n\n if (message.type === 'lists.supported') {\n if (!options.supported) {\n send({ type: 'lists.supported.result', id, lists: [] } as NappletMessage);\n return;\n }\n settle(\n () => options.supported!(context),\n send,\n (error) => ({ type: 'lists.supported.result', id, error } as NappletMessage),\n (lists) => ({ type: 'lists.supported.result', id, lists: [...lists] } as NappletMessage),\n );\n return;\n }\n\n if (message.type === 'lists.add') {\n settleMutation(options.add, message as ListsAddMessage, context, send, 'lists.add.result');\n return;\n }\n\n if (message.type === 'lists.remove') {\n settleMutation(options.remove, message as ListsRemoveMessage, context, send, 'lists.remove.result');\n return;\n }\n\n send({\n type: `${message.type}.error`,\n id,\n error: `Unknown lists method: ${message.type}`,\n } as NappletMessage);\n },\n onWindowDestroyed(_windowId: string): void {\n /* no per-window state */\n },\n };\n}\n","/**\n * serial-service.ts — NAP-SERIAL reference service.\n *\n * Shell-mediated serial sessions. The napplet requests `serial.*`; the shell\n * owns device selection, permissions, session handles, reads, write ordering,\n * close events, and policy.\n */\n\nimport type {\n NappletMessage,\n SerialOpenRequest,\n SerialOpenResult,\n} from '@napplet/core';\nimport type {\n SerialCloseMessage,\n SerialOpenMessage,\n SerialWriteMessage,\n} from '@napplet/nap/serial/types';\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\n\nconst SERIAL_SERVICE_VERSION = '1.0.0';\n\n/** Context passed to host-provided NAP-SERIAL hooks. */\nexport interface SerialServiceContext {\n /** Window id of the requesting napplet. */\n windowId: string;\n}\n\n/** Options for {@link createSerialService}. */\nexport interface SerialServiceOptions {\n /** Host-owned serial open hook. */\n open?: (\n request: SerialOpenRequest,\n context: SerialServiceContext,\n ) => SerialOpenResult | Promise<SerialOpenResult>;\n /** Host-owned serial write hook. */\n write?: (\n sessionId: string,\n data: readonly number[],\n context: SerialServiceContext,\n ) => void | Promise<void>;\n /** Host-owned serial close hook. */\n close?: (\n sessionId: string,\n reason: string | undefined,\n context: SerialServiceContext,\n ) => void | Promise<void>;\n /** Optional host cleanup when a napplet window is destroyed. */\n destroyWindow?: (windowId: string) => void;\n}\n\ntype Send = (msg: NappletMessage) => void;\n\nconst SERIAL_DESCRIPTOR: ServiceDescriptor = {\n name: 'serial',\n version: SERIAL_SERVICE_VERSION,\n description: 'NAP-SERIAL reference handler for shell-mediated serial sessions',\n};\n\nfunction errorMessage(err: unknown, fallback: string): string {\n if (err instanceof Error) return err.message;\n if (typeof err === 'string') return err;\n return fallback;\n}\n\nfunction settle<T>(\n call: () => T | Promise<T>,\n send: Send,\n okFalse: (error: string) => NappletMessage,\n onValue: (value: T) => NappletMessage,\n): void {\n let pending: Promise<T>;\n try {\n pending = Promise.resolve(call());\n } catch (err) {\n send(okFalse(errorMessage(err, 'serial request failed')));\n return;\n }\n pending\n .then((value) => send(onValue(value)))\n .catch((err) => send(okFalse(errorMessage(err, 'serial request failed'))));\n}\n\nfunction unsupported(resultType: string, id: string): NappletMessage {\n return {\n type: resultType,\n id,\n error: `${resultType.replace('.result', '')} unavailable`,\n } as NappletMessage;\n}\n\n/**\n * Create the NAP-SERIAL reference service.\n *\n * @param options - Host serial session hooks for serial.* requests.\n * @returns A runtime service handler for the `serial` domain.\n */\nexport function createSerialService(options: SerialServiceOptions = {}): ServiceHandler {\n return {\n descriptor: SERIAL_DESCRIPTOR,\n handleMessage(windowId: string, message: NappletMessage, send: Send): void {\n const id = (message as NappletMessage & { id?: string }).id ?? '';\n const context = { windowId };\n\n if (message.type === 'serial.open') {\n if (!options.open) {\n send(unsupported('serial.open.result', id));\n return;\n }\n const serialMessage = message as SerialOpenMessage;\n settle(\n () => options.open!(serialMessage.request, context),\n send,\n (error) => ({ type: 'serial.open.result', id, error } as NappletMessage),\n (result) => ({ type: 'serial.open.result', id, session: result.session } as NappletMessage),\n );\n return;\n }\n\n if (message.type === 'serial.write') {\n if (!options.write) {\n send(unsupported('serial.write.result', id));\n return;\n }\n const serialMessage = message as SerialWriteMessage;\n settle(\n () => options.write!(serialMessage.sessionId, serialMessage.data, context),\n send,\n (error) => ({ type: 'serial.write.result', id, error } as NappletMessage),\n () => ({ type: 'serial.write.result', id } as NappletMessage),\n );\n return;\n }\n\n if (message.type === 'serial.close') {\n if (!options.close) {\n send(unsupported('serial.close.result', id));\n return;\n }\n const serialMessage = message as SerialCloseMessage;\n settle(\n () => options.close!(serialMessage.sessionId, serialMessage.reason, context),\n send,\n (error) => ({ type: 'serial.close.result', id, error } as NappletMessage),\n () => ({ type: 'serial.close.result', id } as NappletMessage),\n );\n return;\n }\n\n send({\n type: `${message.type}.error`,\n id,\n error: `Unknown serial method: ${message.type}`,\n } as NappletMessage);\n },\n onWindowDestroyed(windowId: string): void {\n options.destroyWindow?.(windowId);\n },\n };\n}\n","/**\n * ble-service.ts — NAP-BLE reference service.\n *\n * Shell-mediated BLE/GATT sessions. The napplet requests `ble.*`; the shell\n * owns chooser UI, permissions, GATT handles, notifications, and policy.\n */\n\nimport type {\n BleAttribute,\n BleEvent,\n BleOpenRequest,\n BleOpenResult,\n BleService,\n BleWriteOptions,\n NappletMessage,\n} from '@napplet/core';\nimport type {\n BleCloseMessage,\n BleOpenMessage,\n BleReadMessage,\n BleServicesMessage,\n BleSubscribeMessage,\n BleUnsubscribeMessage,\n BleWriteMessage,\n} from '@napplet/nap/ble/types';\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\n\nconst BLE_SERVICE_VERSION = '1.0.0';\n\n/** Context passed to host-provided NAP-BLE hooks. */\nexport interface BleServiceContext {\n /** Window id of the requesting napplet. */\n windowId: string;\n /** Emit a runtime-owned BLE event back to the requesting napplet. */\n emit(event: BleEvent): void;\n}\n\n/** Options for {@link createBleService}. */\nexport interface BleServiceOptions {\n /** Host-owned BLE open hook. */\n open?: (\n request: BleOpenRequest,\n context: BleServiceContext,\n ) => BleOpenResult | Promise<BleOpenResult>;\n /** Host-owned GATT service listing hook. */\n services?: (\n sessionId: string,\n context: BleServiceContext,\n ) => BleService[] | Promise<BleService[]>;\n /** Host-owned GATT read hook. */\n read?: (\n sessionId: string,\n target: BleAttribute,\n context: BleServiceContext,\n ) => number[] | Promise<number[]>;\n /** Host-owned GATT write hook. */\n write?: (\n sessionId: string,\n target: BleAttribute,\n data: readonly number[],\n options: BleWriteOptions | undefined,\n context: BleServiceContext,\n ) => void | Promise<void>;\n /** Host-owned notification subscription hook. */\n subscribe?: (\n sessionId: string,\n target: BleAttribute,\n context: BleServiceContext,\n ) => void | Promise<void>;\n /** Host-owned notification unsubscribe hook. */\n unsubscribe?: (\n sessionId: string,\n target: BleAttribute,\n context: BleServiceContext,\n ) => void | Promise<void>;\n /** Host-owned BLE close hook. */\n close?: (\n sessionId: string,\n reason: string | undefined,\n context: BleServiceContext,\n ) => void | Promise<void>;\n /** Optional host cleanup when a napplet window is destroyed. */\n destroyWindow?: (windowId: string) => void;\n}\n\ntype Send = (msg: NappletMessage) => void;\n\nconst BLE_DESCRIPTOR: ServiceDescriptor = {\n name: 'ble',\n version: BLE_SERVICE_VERSION,\n description: 'NAP-BLE reference handler for shell-mediated BLE/GATT sessions',\n};\n\nfunction errorMessage(err: unknown, fallback: string): string {\n if (err instanceof Error) return err.message;\n if (typeof err === 'string') return err;\n return fallback;\n}\n\nfunction settle<T>(\n call: () => T | Promise<T>,\n send: Send,\n okFalse: (error: string) => NappletMessage,\n onValue: (value: T) => NappletMessage,\n): void {\n let pending: Promise<T>;\n try {\n pending = Promise.resolve(call());\n } catch (err) {\n send(okFalse(errorMessage(err, 'ble request failed')));\n return;\n }\n pending\n .then((value) => send(onValue(value)))\n .catch((err) => send(okFalse(errorMessage(err, 'ble request failed'))));\n}\n\nfunction unsupported(resultType: string, id: string): NappletMessage {\n return {\n type: resultType,\n id,\n error: `${resultType.replace('.result', '')} unavailable`,\n } as NappletMessage;\n}\n\nfunction createContext(windowId: string, send: Send): BleServiceContext {\n return {\n windowId,\n emit(event) {\n send({ type: 'ble.event', event } as NappletMessage);\n },\n };\n}\n\n/**\n * Create the NAP-BLE reference service.\n *\n * @param options - Host BLE session hooks for ble.* requests.\n * @returns A runtime service handler for the `ble` domain.\n */\nexport function createBleService(options: BleServiceOptions = {}): ServiceHandler {\n return {\n descriptor: BLE_DESCRIPTOR,\n handleMessage(windowId: string, message: NappletMessage, send: Send): void {\n const id = (message as NappletMessage & { id?: string }).id ?? '';\n const context = createContext(windowId, send);\n\n if (message.type === 'ble.open') {\n if (!options.open) {\n send(unsupported('ble.open.result', id));\n return;\n }\n const bleMessage = message as BleOpenMessage;\n settle(\n () => options.open!(bleMessage.request, context),\n send,\n (error) => ({ type: 'ble.open.result', id, error } as NappletMessage),\n (result) => ({ type: 'ble.open.result', id, session: result.session } as NappletMessage),\n );\n return;\n }\n\n if (message.type === 'ble.services') {\n if (!options.services) {\n send(unsupported('ble.services.result', id));\n return;\n }\n const bleMessage = message as BleServicesMessage;\n settle(\n () => options.services!(bleMessage.sessionId, context),\n send,\n (error) => ({ type: 'ble.services.result', id, error } as NappletMessage),\n (services) => ({ type: 'ble.services.result', id, services } as NappletMessage),\n );\n return;\n }\n\n if (message.type === 'ble.read') {\n if (!options.read) {\n send(unsupported('ble.read.result', id));\n return;\n }\n const bleMessage = message as BleReadMessage;\n settle(\n () => options.read!(bleMessage.sessionId, bleMessage.target, context),\n send,\n (error) => ({ type: 'ble.read.result', id, error } as NappletMessage),\n (data) => ({ type: 'ble.read.result', id, data } as NappletMessage),\n );\n return;\n }\n\n if (message.type === 'ble.write') {\n if (!options.write) {\n send(unsupported('ble.write.result', id));\n return;\n }\n const bleMessage = message as BleWriteMessage;\n settle(\n () => options.write!(bleMessage.sessionId, bleMessage.target, bleMessage.data, bleMessage.options, context),\n send,\n (error) => ({ type: 'ble.write.result', id, error } as NappletMessage),\n () => ({ type: 'ble.write.result', id } as NappletMessage),\n );\n return;\n }\n\n if (message.type === 'ble.subscribe') {\n if (!options.subscribe) {\n send(unsupported('ble.subscribe.result', id));\n return;\n }\n const bleMessage = message as BleSubscribeMessage;\n settle(\n () => options.subscribe!(bleMessage.sessionId, bleMessage.target, context),\n send,\n (error) => ({ type: 'ble.subscribe.result', id, error } as NappletMessage),\n () => ({ type: 'ble.subscribe.result', id } as NappletMessage),\n );\n return;\n }\n\n if (message.type === 'ble.unsubscribe') {\n if (!options.unsubscribe) {\n send(unsupported('ble.unsubscribe.result', id));\n return;\n }\n const bleMessage = message as BleUnsubscribeMessage;\n settle(\n () => options.unsubscribe!(bleMessage.sessionId, bleMessage.target, context),\n send,\n (error) => ({ type: 'ble.unsubscribe.result', id, error } as NappletMessage),\n () => ({ type: 'ble.unsubscribe.result', id } as NappletMessage),\n );\n return;\n }\n\n if (message.type === 'ble.close') {\n if (!options.close) {\n send(unsupported('ble.close.result', id));\n return;\n }\n const bleMessage = message as BleCloseMessage;\n settle(\n () => options.close!(bleMessage.sessionId, bleMessage.reason, context),\n send,\n (error) => ({ type: 'ble.close.result', id, error } as NappletMessage),\n () => ({ type: 'ble.close.result', id } as NappletMessage),\n );\n return;\n }\n\n send({\n type: `${message.type}.error`,\n id,\n error: `Unknown ble method: ${message.type}`,\n } as NappletMessage);\n },\n onWindowDestroyed(windowId: string): void {\n options.destroyWindow?.(windowId);\n },\n };\n}\n","/**\n * webrtc-service.ts — NAP-WEBRTC reference service.\n *\n * Shell-mediated WebRTC sessions. The napplet requests `webrtc.*`; the shell\n * owns signaling, signing/encryption, SDP, ICE, peer connections, and policy.\n */\n\nimport type {\n NappletMessage,\n WebrtcEvent,\n WebrtcOpenRequest,\n WebrtcOpenResult,\n} from '@napplet/core';\nimport type {\n WebrtcCloseMessage,\n WebrtcOpenMessage,\n WebrtcSendMessage,\n} from '@napplet/nap/webrtc/types';\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\n\nconst WEBRTC_SERVICE_VERSION = '1.0.0';\n\n/** Context passed to host-provided NAP-WEBRTC hooks. */\nexport interface WebrtcServiceContext {\n /** Window id of the requesting napplet. */\n windowId: string;\n /** Emit a runtime-owned WebRTC event back to the requesting napplet. */\n emit(event: WebrtcEvent): void;\n}\n\n/** Options for {@link createWebrtcService}. */\nexport interface WebrtcServiceOptions {\n /** Host-owned WebRTC open hook. */\n open?: (\n request: WebrtcOpenRequest,\n context: WebrtcServiceContext,\n ) => WebrtcOpenResult | Promise<WebrtcOpenResult>;\n /** Host-owned WebRTC send hook. */\n send?: (\n sessionId: string,\n payload: unknown,\n context: WebrtcServiceContext,\n ) => void | Promise<void>;\n /** Host-owned WebRTC close hook. */\n close?: (\n sessionId: string,\n reason: string | undefined,\n context: WebrtcServiceContext,\n ) => void | Promise<void>;\n /** Optional host cleanup when a napplet window is destroyed. */\n destroyWindow?: (windowId: string) => void;\n}\n\ntype Send = (msg: NappletMessage) => void;\n\nconst WEBRTC_DESCRIPTOR: ServiceDescriptor = {\n name: 'webrtc',\n version: WEBRTC_SERVICE_VERSION,\n description: 'NAP-WEBRTC reference handler for shell-mediated WebRTC sessions',\n};\n\nfunction errorMessage(err: unknown, fallback: string): string {\n if (err instanceof Error) return err.message;\n if (typeof err === 'string') return err;\n return fallback;\n}\n\nfunction settle<T>(\n call: () => T | Promise<T>,\n send: Send,\n okFalse: (error: string) => NappletMessage,\n onValue: (value: T) => NappletMessage,\n): void {\n let pending: Promise<T>;\n try {\n pending = Promise.resolve(call());\n } catch (err) {\n send(okFalse(errorMessage(err, 'webrtc request failed')));\n return;\n }\n pending\n .then((value) => send(onValue(value)))\n .catch((err) => send(okFalse(errorMessage(err, 'webrtc request failed'))));\n}\n\nfunction unsupported(resultType: string, id: string): NappletMessage {\n return {\n type: resultType,\n id,\n error: `${resultType.replace('.result', '')} unavailable`,\n } as NappletMessage;\n}\n\nfunction createContext(windowId: string, send: Send): WebrtcServiceContext {\n return {\n windowId,\n emit(event) {\n send({ type: 'webrtc.event', event } as NappletMessage);\n },\n };\n}\n\n/**\n * Create the NAP-WEBRTC reference service.\n *\n * @param options - Host WebRTC session hooks for webrtc.* requests.\n * @returns A runtime service handler for the `webrtc` domain.\n */\nexport function createWebrtcService(options: WebrtcServiceOptions = {}): ServiceHandler {\n return {\n descriptor: WEBRTC_DESCRIPTOR,\n handleMessage(windowId: string, message: NappletMessage, send: Send): void {\n const id = (message as NappletMessage & { id?: string }).id ?? '';\n const context = createContext(windowId, send);\n\n if (message.type === 'webrtc.open') {\n if (!options.open) {\n send(unsupported('webrtc.open.result', id));\n return;\n }\n const webrtcMessage = message as WebrtcOpenMessage;\n settle(\n () => options.open!(webrtcMessage.request, context),\n send,\n (error) => ({ type: 'webrtc.open.result', id, error } as NappletMessage),\n (result) => ({ type: 'webrtc.open.result', id, session: result.session } as NappletMessage),\n );\n return;\n }\n\n if (message.type === 'webrtc.send') {\n if (!options.send) {\n send(unsupported('webrtc.send.result', id));\n return;\n }\n const webrtcMessage = message as WebrtcSendMessage;\n settle(\n () => options.send!(webrtcMessage.sessionId, webrtcMessage.payload, context),\n send,\n (error) => ({ type: 'webrtc.send.result', id, error } as NappletMessage),\n () => ({ type: 'webrtc.send.result', id } as NappletMessage),\n );\n return;\n }\n\n if (message.type === 'webrtc.close') {\n if (!options.close) {\n send(unsupported('webrtc.close.result', id));\n return;\n }\n const webrtcMessage = message as WebrtcCloseMessage;\n settle(\n () => options.close!(webrtcMessage.sessionId, webrtcMessage.reason, context),\n send,\n (error) => ({ type: 'webrtc.close.result', id, error } as NappletMessage),\n () => ({ type: 'webrtc.close.result', id } as NappletMessage),\n );\n return;\n }\n\n send({\n type: `${message.type}.error`,\n id,\n error: `Unknown webrtc method: ${message.type}`,\n } as NappletMessage);\n },\n onWindowDestroyed(windowId: string): void {\n options.destroyWindow?.(windowId);\n },\n };\n}\n","/**\n * common-service.ts — NAP-COMMON reference service.\n *\n * Shell-mediated public NIP-19 helpers and common social actions. The napplet\n * requests `common.*`; the shell owns identity, consent, event construction,\n * signing, publishing, relay access, and NIP-19 handling.\n */\n\nimport type {\n CommonActionResult,\n CommonFollowsResult,\n CommonNip19DecodeResult,\n CommonNip19EncodeInput,\n CommonNip19EncodeResult,\n CommonProfileResult,\n CommonProfileTarget,\n CommonReaction,\n CommonReportReason,\n CommonReportTarget,\n NappletMessage,\n} from '@napplet/core';\nimport type {\n CommonDecodeNip19Message,\n CommonEncodeNip19Message,\n CommonFollowMessage,\n CommonGetProfileMessage,\n CommonReactMessage,\n CommonReportMessage,\n CommonUnfollowMessage,\n} from '@napplet/nap/common/types';\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\nimport {\n decode,\n encodeBytes,\n naddrEncode,\n neventEncode,\n noteEncode,\n nprofileEncode,\n npubEncode,\n} from 'nostr-tools/nip19';\n\nconst COMMON_SERVICE_VERSION = '1.0.0';\nconst BECH32_CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l';\n\n/** Context passed to host-provided NAP-COMMON hooks. */\nexport interface CommonServiceContext {\n /** Window id of the requesting napplet. */\n windowId: string;\n}\n\n/** Options for {@link createCommonService}. */\nexport interface CommonServiceOptions {\n /** Host-owned profile lookup. */\n getProfile?: (target: CommonProfileTarget, context: CommonServiceContext) => CommonProfileResult | Promise<CommonProfileResult>;\n /** Host-owned current-user follows lookup. */\n follows?: (context: CommonServiceContext) => CommonFollowsResult | Promise<CommonFollowsResult>;\n /** Host-owned follow action. */\n follow?: (pubkeys: string[], context: CommonServiceContext) => CommonActionResult | Promise<CommonActionResult>;\n /** Host-owned unfollow action. */\n unfollow?: (pubkeys: string[], context: CommonServiceContext) => CommonActionResult | Promise<CommonActionResult>;\n /** Host-owned reaction action. */\n react?: (\n targetEventId: string,\n reaction: CommonReaction,\n customEmojiHref: string | undefined,\n context: CommonServiceContext,\n ) => CommonActionResult | Promise<CommonActionResult>;\n /** Host-owned report action. */\n report?: (\n target: CommonReportTarget,\n reason: CommonReportReason,\n text: string,\n context: CommonServiceContext,\n ) => CommonActionResult | Promise<CommonActionResult>;\n}\n\ntype Send = (msg: NappletMessage) => void;\n\nconst COMMON_DESCRIPTOR: ServiceDescriptor = {\n name: 'common',\n version: COMMON_SERVICE_VERSION,\n description: 'NAP-COMMON reference handler for shell-mediated social helpers',\n};\n\nfunction errorMessage(err: unknown, fallback: string): string {\n if (err instanceof Error) return err.message;\n if (typeof err === 'string') return err;\n return fallback;\n}\n\nfunction encodeNip19(input: CommonNip19EncodeInput): CommonNip19EncodeResult {\n try {\n switch (input.type) {\n case 'npub':\n return { ok: true, value: npubEncode(input.hex), nip19Type: input.type };\n case 'note':\n return { ok: true, value: noteEncode(input.hex), nip19Type: input.type };\n case 'nprofile':\n return { ok: true, value: nprofileEncode({ pubkey: input.pubkey, relays: input.relays }), nip19Type: input.type };\n case 'nevent':\n return {\n ok: true,\n value: neventEncode({ id: input.eventId, relays: input.relays, author: input.author, kind: input.kind }),\n nip19Type: input.type,\n };\n case 'naddr':\n return {\n ok: true,\n value: naddrEncode({ identifier: input.identifier, pubkey: input.pubkey, kind: input.kind, relays: input.relays }),\n nip19Type: input.type,\n };\n case 'nrelay':\n return { ok: true, value: encodeBytes('nrelay', new TextEncoder().encode(input.relay)), nip19Type: input.type };\n default:\n return { ok: false, error: 'unsupported NIP-19 type' };\n }\n } catch (err) {\n return { ok: false, error: errorMessage(err, 'NIP-19 encode failed') };\n }\n}\n\nfunction decodeNip19Value(value: string): CommonNip19DecodeResult {\n try {\n const decoded = decode(value);\n switch (decoded.type) {\n case 'npub':\n case 'note':\n return { ok: true, nip19Type: decoded.type, hex: decoded.data };\n case 'nprofile':\n return { ok: true, nip19Type: decoded.type, pubkey: decoded.data.pubkey, relays: decoded.data.relays };\n case 'nevent':\n return {\n ok: true,\n nip19Type: decoded.type,\n eventId: decoded.data.id,\n relays: decoded.data.relays,\n author: decoded.data.author,\n kind: decoded.data.kind,\n };\n case 'naddr':\n return {\n ok: true,\n nip19Type: decoded.type,\n identifier: decoded.data.identifier,\n pubkey: decoded.data.pubkey,\n kind: decoded.data.kind,\n relays: decoded.data.relays,\n };\n default:\n return { ok: false, error: 'unsupported NIP-19 type' };\n }\n } catch (err) {\n if (value.startsWith('nrelay1')) return decodeNrelay(value);\n return { ok: false, error: errorMessage(err, 'NIP-19 decode failed') };\n }\n}\n\nfunction decodeNrelay(value: string): CommonNip19DecodeResult {\n try {\n const separator = value.lastIndexOf('1');\n if (separator <= 0 || separator + 7 > value.length) return { ok: false, error: 'invalid nrelay value' };\n const dataPart = value.slice(separator + 1);\n const words = [...dataPart].map((char) => {\n const index = BECH32_CHARSET.indexOf(char);\n if (index === -1) throw new Error('invalid nrelay character');\n return index;\n });\n const payloadWords = words.slice(0, -6);\n const bytes = convertBits(payloadWords, 5, 8, false);\n return { ok: true, nip19Type: 'nrelay', relay: new TextDecoder().decode(Uint8Array.from(bytes)) };\n } catch (err) {\n return { ok: false, error: errorMessage(err, 'invalid nrelay value') };\n }\n}\n\nfunction convertBits(data: readonly number[], fromBits: number, toBits: number, pad: boolean): number[] {\n let acc = 0;\n let bits = 0;\n const result: number[] = [];\n const maxv = (1 << toBits) - 1;\n const maxAcc = (1 << (fromBits + toBits - 1)) - 1;\n for (const value of data) {\n if (value < 0 || value >> fromBits !== 0) throw new Error('invalid bech32 data');\n acc = ((acc << fromBits) | value) & maxAcc;\n bits += fromBits;\n while (bits >= toBits) {\n bits -= toBits;\n result.push((acc >> bits) & maxv);\n }\n }\n if (pad) {\n if (bits > 0) result.push((acc << (toBits - bits)) & maxv);\n } else if (bits >= fromBits || ((acc << (toBits - bits)) & maxv) !== 0) {\n throw new Error('invalid bech32 padding');\n }\n return result;\n}\n\nfunction settle<T>(\n call: () => T | Promise<T>,\n send: Send,\n okFalse: (error: string) => NappletMessage,\n onValue: (value: T) => NappletMessage,\n): void {\n let pending: Promise<T>;\n try {\n pending = Promise.resolve(call());\n } catch (err) {\n send(okFalse(errorMessage(err, 'common request failed')));\n return;\n }\n pending\n .then((value) => send(onValue(value)))\n .catch((err) => send(okFalse(errorMessage(err, 'common request failed'))));\n}\n\n/**\n * Create the NAP-COMMON reference service.\n *\n * @param options - Host profile/social hooks for common.* requests.\n * @returns A runtime service handler for the `common` domain.\n */\nexport function createCommonService(options: CommonServiceOptions = {}): ServiceHandler {\n return {\n descriptor: COMMON_DESCRIPTOR,\n handleMessage(windowId: string, message: NappletMessage, send: Send): void {\n const id = (message as NappletMessage & { id?: string }).id ?? '';\n const context = { windowId };\n\n if (message.type === 'common.encodeNip19') {\n const input = (message as CommonEncodeNip19Message).input;\n const result = input ? encodeNip19(input) : { ok: false, error: 'invalid input' };\n send({ type: 'common.encodeNip19.result', id, ...result } as NappletMessage);\n return;\n }\n\n if (message.type === 'common.decodeNip19') {\n const value = (message as CommonDecodeNip19Message).value;\n const result = typeof value === 'string' ? decodeNip19Value(value) : { ok: false, error: 'invalid value' };\n send({ type: 'common.decodeNip19.result', id, ...result } as NappletMessage);\n return;\n }\n\n if (message.type === 'common.getProfile') {\n const target = (message as CommonGetProfileMessage).target;\n if (!options.getProfile) {\n send({ type: 'common.getProfile.result', id, ok: false, pubkey: target ?? '', error: 'profile lookup unavailable' } as NappletMessage);\n return;\n }\n settle(\n () => options.getProfile!(target, context),\n send,\n (error) => ({ type: 'common.getProfile.result', id, ok: false, pubkey: target ?? '', error } as NappletMessage),\n (result) => ({ type: 'common.getProfile.result', id, ...result } as NappletMessage),\n );\n return;\n }\n\n if (message.type === 'common.follows') {\n if (!options.follows) {\n send({ type: 'common.follows.result', id, ok: false, pubkeys: [], error: 'follows lookup unavailable' } as NappletMessage);\n return;\n }\n settle(\n () => options.follows!(context),\n send,\n (error) => ({ type: 'common.follows.result', id, ok: false, pubkeys: [], error } as NappletMessage),\n (result) => ({ type: 'common.follows.result', id, ...result } as NappletMessage),\n );\n return;\n }\n\n if (message.type === 'common.follow') {\n const pubkeys = (message as CommonFollowMessage).pubkeys ?? [];\n settleAction(options.follow, pubkeys, context, send, 'common.follow.result', id, 'follow unavailable');\n return;\n }\n\n if (message.type === 'common.unfollow') {\n const pubkeys = (message as CommonUnfollowMessage).pubkeys ?? [];\n settleAction(options.unfollow, pubkeys, context, send, 'common.unfollow.result', id, 'unfollow unavailable');\n return;\n }\n\n if (message.type === 'common.react') {\n const m = message as CommonReactMessage;\n if (!options.react) {\n send({ type: 'common.react.result', id, ok: false, error: 'react unavailable' } as NappletMessage);\n return;\n }\n settle(\n () => options.react!(m.targetEventId, m.reaction, m.customEmojiHref, context),\n send,\n (error) => ({ type: 'common.react.result', id, ok: false, error } as NappletMessage),\n (result) => ({ type: 'common.react.result', id, ...result } as NappletMessage),\n );\n return;\n }\n\n if (message.type === 'common.report') {\n const m = message as CommonReportMessage;\n if (!options.report) {\n send({ type: 'common.report.result', id, ok: false, error: 'report unavailable' } as NappletMessage);\n return;\n }\n settle(\n () => options.report!(m.target, m.reason, m.text, context),\n send,\n (error) => ({ type: 'common.report.result', id, ok: false, error } as NappletMessage),\n (result) => ({ type: 'common.report.result', id, ...result } as NappletMessage),\n );\n return;\n }\n\n send({\n type: `${message.type}.error`,\n id,\n error: `Unknown common method: ${message.type}`,\n } as NappletMessage);\n },\n onWindowDestroyed(_windowId: string): void {\n /* no per-window state */\n },\n };\n}\n\nfunction settleAction(\n action: ((pubkeys: string[], context: CommonServiceContext) => CommonActionResult | Promise<CommonActionResult>) | undefined,\n pubkeys: string[],\n context: CommonServiceContext,\n send: Send,\n resultType: string,\n id: string,\n unavailable: string,\n): void {\n if (!action) {\n send({ type: resultType, id, ok: false, error: unavailable } as NappletMessage);\n return;\n }\n settle(\n () => action(pubkeys, context),\n send,\n (error) => ({ type: resultType, id, ok: false, error } as NappletMessage),\n (result) => ({ type: resultType, id, ...result } as NappletMessage),\n );\n}\n"],"mappings":"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","names":["notify","deliver","relays","generateId","toErrorMessage","toErrorMessage","toErrorMessage","settle","toErrorMessage","errorMessage","settle","errorMessage","settle","unsupported","errorMessage","settle","unsupported","createContext","errorMessage","settle"]}
|