@kehto/services 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +85 -0
- package/dist/index.d.ts +810 -0
- package/dist/index.js +885 -0
- package/dist/index.js.map +1 -0
- package/package.json +69 -0
|
@@ -0,0 +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/media-service.ts","../src/notify-service.ts","../src/theme-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 !== 'ifc.emit') return;\n const topic = (message as any).topic as string | undefined;\n if (!topic?.startsWith('audio:')) return;\n\n const action = topic.slice(6); // 'audio:'.length === 6\n const payload = ((message as any).payload ?? {}) as Record<string, unknown>;\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: 'ifc.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","/**\n * notification-service.ts — Notification state registry as a ServiceHandler.\n *\n * Tracks notifications created by napplet windows. Shell hosts wire this\n * into the runtime via registerService('notifications', createNotificationService(opts)).\n * The shell host decides presentation (toast, badge, OS notification, etc.)\n * through the onChange callback. Browser-agnostic — no DOM, no window.\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\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 notifications = new Map<string, Notification[]>();\n const onChange = options?.onChange;\n const maxPerWindow = options?.maxPerWindow ?? DEFAULT_MAX_PER_WINDOW;\n\n /**\n * Get a flat list of all notifications across all windows.\n */\n function getAllNotifications(): Notification[] {\n const all: Notification[] = [];\n for (const windowNotifs of notifications.values()) {\n all.push(...windowNotifs);\n }\n return all;\n }\n\n function notify(): void {\n onChange?.(getAllNotifications());\n }\n\n /**\n * Ensure a window's notification list exists.\n */\n function getWindowNotifications(windowId: string): Notification[] {\n let list = notifications.get(windowId);\n if (!list) {\n list = [];\n notifications.set(windowId, list);\n }\n return list;\n }\n\n /**\n * Enforce maxPerWindow limit by evicting oldest notifications.\n */\n function enforceLimit(list: Notification[]): void {\n while (list.length > maxPerWindow) {\n list.shift(); // Remove oldest (FIFO eviction)\n }\n }\n\n /**\n * Find a notification by ID across all windows.\n * Returns [windowId, notification, index] or undefined.\n */\n function findById(id: string): [string, Notification, number] | undefined {\n for (const [windowId, list] of 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\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 unknown as Record<string, unknown>;\n\n // ── NIP-5D canonical notify.* envelope format (Phase 17+) ───────────────\n // Host-originated and napplet-originated notify.* envelopes from Phase 18+.\n if (message.type.startsWith('notify.')) {\n const action = message.type.slice(7); // 'notify.'.length === 7\n\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\n const id = generateId();\n const notification: Notification = {\n id,\n windowId,\n title,\n body,\n read: false,\n createdAt: Math.floor(Date.now() / 1000),\n };\n\n const list = getWindowNotifications(windowId);\n list.push(notification);\n enforceLimit(list);\n notify();\n\n send({ type: 'notify.created', id } as unknown as NappletMessage);\n break;\n }\n\n case 'dismiss': {\n const notifId = typeof msg.notificationId === 'string' ? msg.notificationId : '';\n if (!notifId) return;\n\n const found = findById(notifId);\n if (found) {\n const [foundWindowId, , index] = found;\n const list = notifications.get(foundWindowId);\n if (list) {\n list.splice(index, 1);\n if (list.length === 0) notifications.delete(foundWindowId);\n notify();\n }\n }\n break;\n }\n\n case 'read': {\n const notifId = typeof msg.notificationId === 'string' ? msg.notificationId : '';\n if (!notifId) return;\n\n const found = findById(notifId);\n if (found) {\n const [, notification] = found;\n if (!notification.read) {\n notification.read = true;\n notify();\n }\n }\n break;\n }\n\n case 'list': {\n const windowNotifs = notifications.get(windowId) ?? [];\n send({ type: 'notify.listed', notifications: windowNotifs } as unknown as NappletMessage);\n break;\n }\n\n default:\n // Unknown notify.* action — ignore\n break;\n }\n return;\n }\n\n // ── Legacy ifc.emit format (Phase <17 napplets — still used by chat/bot) ─\n // Kept for backward compat until napplets migrate to NIP-5D in Phase 18.\n if (message.type !== 'ifc.emit') return;\n const topic = msg.topic as string | undefined;\n if (!topic?.startsWith('notifications:')) return;\n\n const action = topic.slice(14); // 'notifications:'.length === 14\n const payload = ((msg.payload ?? {}) as Record<string, unknown>);\n\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\n const id = generateId();\n const notification: Notification = {\n id,\n windowId,\n title,\n body,\n read: false,\n createdAt: Math.floor(Date.now() / 1000),\n };\n\n const list = getWindowNotifications(windowId);\n list.push(notification);\n enforceLimit(list);\n notify();\n\n send({ type: 'ifc.event', topic: 'notifications:created', payload: { id } } as NappletMessage);\n break;\n }\n\n case 'dismiss': {\n const id = typeof payload.id === 'string' ? payload.id : '';\n if (!id) return;\n\n const found = findById(id);\n if (found) {\n const [foundWindowId, , index] = found;\n const list = notifications.get(foundWindowId);\n if (list) {\n list.splice(index, 1);\n // Clean up empty lists\n if (list.length === 0) {\n notifications.delete(foundWindowId);\n }\n notify();\n }\n }\n break;\n }\n\n case 'read': {\n const id = typeof payload.id === 'string' ? payload.id : '';\n if (!id) return;\n\n const found = findById(id);\n if (found) {\n const [, notification] = found;\n if (!notification.read) {\n notification.read = true;\n notify();\n }\n }\n break;\n }\n\n case 'list': {\n const windowNotifs = notifications.get(windowId) ?? [];\n send({ type: 'ifc.event', topic: 'notifications:listed', payload: { notifications: windowNotifs } } as NappletMessage);\n break;\n }\n\n default:\n // Unknown notification action — ignore\n break;\n }\n },\n\n onWindowDestroyed(windowId: string): void {\n if (notifications.delete(windowId)) {\n notify();\n }\n },\n };\n}\n","/**\n * identity-service.ts — NIP-5D identity nub 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 * See REQUIREMENTS.md DEPS-03 (Phase 15 changelog).\n *\n * Handles 9 identity.* request types from @napplet/nub-identity. getPublicKey\n * and getRelays return real values sourced from hooks.auth.getSigner(); the\n * remaining 7 (getProfile/getFollows/getList/getZaps/getMutes/getBlocked/\n * getBadges) are stub-level — each returns an empty default payload with the\n * spec-correct envelope shape. Host apps plug real backends via\n * runtime.registerService('identity', realHandler).\n */\n\nimport type { NappletMessage } from '@napplet/core';\nimport type { ServiceHandler, Signer } from '@kehto/runtime';\nimport type {\n IdentityGetPublicKeyResultMessage,\n IdentityGetRelaysResultMessage,\n IdentityGetProfileResultMessage,\n IdentityGetFollowsResultMessage,\n IdentityGetListResultMessage,\n IdentityGetZapsResultMessage,\n IdentityGetMutesResultMessage,\n IdentityGetBlockedResultMessage,\n IdentityGetBadgesResultMessage,\n RelayPermission,\n} from '@napplet/nub-identity';\n\n/** Identity service version — follows semver. */\nconst IDENTITY_SERVICE_VERSION = '1.0.0';\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/**\n * Create an identity service that handles NIP-5D identity.* envelope messages.\n *\n * Supports all 9 identity.* request types from @napplet/nub-identity. The two\n * read-only nostr-info queries (getPublicKey, getRelays) resolve through the\n * caller-supplied signer; the remaining 7 return default/empty payloads with\n * spec-correct 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 NUB 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 const id = (message as NappletMessage & { id?: string }).id ?? '';\n\n function sendError(typeBase: string, error: string): void {\n send({ type: `${typeBase}.error`, id, error } as NappletMessage);\n }\n\n const signer = options.getSigner();\n\n switch (message.type) {\n case 'identity.getPublicKey': {\n // Per NIP-5D spec comment \"Always succeeds\" — return empty pubkey when no signer is\n // configured rather than sending an error. The nub-identity shim's getPublicKey()\n // only handles 'identity.getPublicKey.result'; an error response hangs the Promise\n // indefinitely. Empty pubkey is the correct sentinel for \"no signer connected\".\n if (!signer) {\n const result: IdentityGetPublicKeyResultMessage = {\n type: 'identity.getPublicKey.result',\n id,\n pubkey: '',\n };\n send(result);\n return;\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) => {\n sendError(\n 'identity.getPublicKey',\n (err as Error)?.message ?? 'getPublicKey failed',\n );\n });\n return;\n }\n\n case 'identity.getRelays': {\n if (!signer) {\n sendError('identity.getRelays', 'no signer configured');\n return;\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) => {\n sendError(\n 'identity.getRelays',\n (err as Error)?.message ?? 'getRelays failed',\n );\n });\n return;\n }\n\n case 'identity.getProfile': {\n const result: IdentityGetProfileResultMessage = {\n type: 'identity.getProfile.result',\n id,\n profile: null,\n };\n send(result);\n return;\n }\n\n case 'identity.getFollows': {\n const result: IdentityGetFollowsResultMessage = {\n type: 'identity.getFollows.result',\n id,\n pubkeys: [],\n };\n send(result);\n return;\n }\n\n case 'identity.getList': {\n const result: IdentityGetListResultMessage = {\n type: 'identity.getList.result',\n id,\n entries: [],\n };\n send(result);\n return;\n }\n\n case 'identity.getZaps': {\n const result: IdentityGetZapsResultMessage = {\n type: 'identity.getZaps.result',\n id,\n zaps: [],\n };\n send(result);\n return;\n }\n\n case 'identity.getMutes': {\n const result: IdentityGetMutesResultMessage = {\n type: 'identity.getMutes.result',\n id,\n pubkeys: [],\n };\n send(result);\n return;\n }\n\n case 'identity.getBlocked': {\n const result: IdentityGetBlockedResultMessage = {\n type: 'identity.getBlocked.result',\n id,\n pubkeys: [],\n };\n send(result);\n return;\n }\n\n case 'identity.getBadges': {\n const result: IdentityGetBadgesResultMessage = {\n type: 'identity.getBadges.result',\n id,\n badges: [],\n };\n send(result);\n return;\n }\n\n default:\n sendError(message.type, `Unknown identity method: ${message.type}`);\n }\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 NUB 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\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 if (message.type === 'relay.subscribe') {\n const subId = (message as any).subId as string;\n if (typeof subId !== 'string') return;\n const filters = (message as any).filters as NostrFilter[];\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 relayUrls = 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 = (message as any).subId as string;\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 = (message as any).event as NostrEvent | undefined;\n if (event && typeof event === 'object' && options.isAvailable()) {\n options.publish(event);\n }\n return;\n }\n\n // NUB-08 / SH-C03: relay.publishEncrypted is SHELL-MEDIATED. The shell\n // runtime (@kehto/runtime handleRelayMessage) encrypts the plaintext\n // content via the shell signer, signs the event, and synthesizes a\n // relay.publish envelope that it hands to this service. By the time a\n // publishEncrypted envelope reaches this branch, the content MUST\n // already be ciphertext — the service never decrypts nor re-encrypts.\n //\n // We still accept the envelope as a fallback so tests and alternate\n // host wirings that bypass the runtime shim can delegate to the same\n // publish path. The runtime's canonical path is to NOT hand us\n // publishEncrypted directly — it translates to relay.publish first.\n if (message.type === 'relay.publishEncrypted') {\n const event = (message as any).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","/**\n * cache-service.ts — Local event cache as a ServiceHandler.\n *\n * Wraps an existing cache implementation (query, store, isAvailable)\n * as a ServiceHandler that receives relay NUB envelope messages. Cache\n * subscriptions are one-shot queries — relay.subscribe triggers a query\n * and immediate EOSE, unlike relay pool subscriptions which stay open.\n */\n\nimport type { NostrEvent, NostrFilter, NappletMessage } from '@napplet/core';\nimport type { ServiceHandler } from '@kehto/runtime';\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 * 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 if (message.type === 'relay.subscribe') {\n const subId = (message as any).subId as string;\n if (typeof subId !== 'string') return;\n const filters = (message as any).filters as NostrFilter[];\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 = (message as any).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","/**\n * coordinated-relay.ts — Composite relay + cache ServiceHandler.\n *\n * Combines relay pool and cache into a single service that handles\n * relay.subscribe by querying both sources, deduplicating events by ID,\n * and sending a unified EOSE after both sources complete.\n *\n * This is a convenience helper for shell implementors. Those who need\n * custom coordination can write their own composite service.\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\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 if (message.type === 'relay.subscribe') {\n const subId = (message as any).subId as string;\n if (typeof subId !== 'string') return;\n const filters = (message as any).filters as NostrFilter[];\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 relayUrls = 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 = (message as any).subId as string;\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 = (message as any).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 // Store in cache\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 NUB reference service (stub-level).\n *\n * Handles the 3 napplet -> shell request types from @napplet/nub-keys:\n * - keys.forward -> invokes options.onForward (hotkey passthrough, fire-and-forget)\n * - keys.registerAction -> echoes { actionId, binding: action.defaultKey? } as .result\n * - keys.unregisterAction -> fire-and-forget (no envelope)\n *\n * Stub-level: no real keyboard listener, no binding persistence. Host apps\n * wire a real backend via runtime.registerService('keys', realHandler).\n *\n * Field-name translation: @napplet/nub-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`, `keys.action`) are\n * NOT emitted by this service — they are the shell-side keys forwarder's\n * responsibility (DRIFT-SHELL-06, tracked under Plan 12-11 / future phase).\n */\n\nimport type { NappletMessage } from '@napplet/core';\nimport type { ServiceDescriptor, ServiceHandler } from '@kehto/runtime';\nimport type {\n KeysForwardMessage,\n KeysRegisterActionMessage,\n KeysRegisterActionResultMessage,\n} from '@napplet/nub-keys';\n\n/** Keys service version — follows semver. */\nconst KEYS_SERVICE_VERSION = '1.0.0';\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. 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\n/**\n * Create a keys service handler.\n *\n * The service is stub-level: it dispatches the 3 napplet -> shell keys\n * request types from @napplet/nub-keys, responds with spec-correct\n * envelopes, and defers real keyboard behavior to an optional\n * onForward callback. No binding persistence, no real keyboard listener.\n *\n * @param options - Optional configuration (onForward callback)\n * @returns A ServiceHandler 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 * ```\n */\nexport function createKeysService(options: KeysServiceOptions = {}): ServiceHandler {\n const descriptor: ServiceDescriptor = {\n name: 'keys',\n version: KEYS_SERVICE_VERSION,\n description: 'NIP-5D keys NUB 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 'keys.forward': {\n const m = message as KeysForwardMessage;\n options.onForward?.({\n key: m.key,\n code: m.code,\n ctrlKey: m.ctrl,\n altKey: m.alt,\n shiftKey: m.shift,\n metaKey: m.meta,\n });\n return;\n }\n\n case 'keys.registerAction': {\n const m = message as KeysRegisterActionMessage;\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 — no envelope per @napplet/nub-keys spec\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 /* no per-window state */\n },\n };\n}\n","/**\n * media-service.ts — NIP-5D media NUB reference service (stub-level).\n *\n * Handles 5 napplet -> shell request types from @napplet/nub-media:\n * media.session.create (result), media.session.update, media.session.destroy,\n * media.state, media.capabilities.\n *\n * Stub-level: no real MediaSession API integration. Host apps wire real\n * playback backends via runtime.registerService('media', realHandler).\n *\n * Note: this is SEPARATE from packages/services/src/audio-service.ts, which\n * is the legacy ifc-topic-based audio source registry (audio:* topic events\n * over ifc.emit). media-service is the canonical @napplet/nub-media NIP-5D\n * path and they coexist — audio-service continues to track audio sources for\n * shell UI, while media-service handles the NUB protocol envelope surface.\n *\n * Shell -> Napplet push types (media.command, media.controls) are handled\n * by the shell adapter separately — out of scope for this service.\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 MediaSessionCreateMessage,\n MediaSessionCreateResultMessage,\n} from '@napplet/nub-media';\n\n/** Media service version — follows semver. */\nconst MEDIA_SERVICE_VERSION = '1.0.0';\n\n/**\n * Optional host callbacks for the stub 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/**\n * Create a stub-level media NUB service handler.\n *\n * Implements the 5 napplet->shell media.* request types defined in\n * `@napplet/nub-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 NUB 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\n * @returns A ServiceHandler 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 * ```\n */\nexport function createMediaService(options: MediaServiceOptions = {}): ServiceHandler {\n const descriptor: ServiceDescriptor = {\n name: 'media',\n version: MEDIA_SERVICE_VERSION,\n description: 'NIP-5D media NUB reference handler (stub)',\n };\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 'media.session.create': {\n const m = message as MediaSessionCreateMessage;\n options.onSessionCreate?.(windowId, m.sessionId, m.metadata);\n const result: MediaSessionCreateResultMessage = {\n type: 'media.session.create.result',\n id: m.id,\n sessionId: m.sessionId,\n };\n send(result as NappletMessage);\n return;\n }\n\n case 'media.session.update': {\n const m = message as NappletMessage & { sessionId?: string; metadata?: unknown };\n options.onSessionUpdate?.(windowId, m.sessionId ?? '', m.metadata);\n return; // fire-and-forget\n }\n\n case 'media.session.destroy': {\n const m = message as NappletMessage & { sessionId?: string };\n options.onSessionDestroy?.(windowId, m.sessionId ?? '');\n return; // fire-and-forget\n }\n\n case 'media.state': {\n const m = message as NappletMessage & { sessionId?: string };\n options.onState?.(windowId, m.sessionId ?? '', m);\n return; // fire-and-forget\n }\n\n case 'media.capabilities': {\n const m = message as NappletMessage & { sessionId?: string; actions?: unknown };\n options.onCapabilities?.(windowId, m.sessionId ?? '', m.actions);\n return; // fire-and-forget\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 media method: ${message.type}`,\n } as NappletMessage);\n return;\n }\n }\n },\n\n onWindowDestroyed(_windowId: string): void {\n // Stub service holds no per-window state; real implementations should\n // tear down sessions here.\n },\n };\n}\n","/**\n * notify-service.ts — NIP-5D notify NUB reference service (stub-level).\n *\n * Handles the 5 napplet -> shell request types from `@napplet/nub-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 * ifc-topic-based notification registry (operates on `ifc.emit` topics\n * under `notifications:*`). `notify-service.ts` is the canonical\n * `@napplet/nub-notify` NIP-5D path and lives alongside the legacy module.\n * If the host app registers this service via\n * `runtime.registerService('notify', ...)`, @napplet/nub-notify messages\n * land here; the legacy ifc-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/nub-notify';\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/nub-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 NUB 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 // Fire-and-forget per @napplet/nub-notify. Stub has no backend\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 NUB reference service.\n *\n * Handles the single napplet->shell request type from @napplet/nub-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/nub-theme';\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/nub-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.*` NUB.\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 NUB 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 // Unknown theme.* action — emit a canonical .error envelope so napplets\n // see an explicit rejection rather than a silent drop. theme.changed is\n // shell-initiated (not napplet-sendable) and must not arrive here; if\n // it does, we still emit a spec-correct error so the sender gets a\n // reply envelope.\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"],"mappings":";AAeA,IAAM,wBAAwB;AA4BvB,SAAS,mBAAmB,SAA+C;AAChF,QAAM,UAAU,oBAAI,IAAyB;AAC7C,QAAM,WAAW,SAAS;AAE1B,WAAS,SAAe;AACtB,eAAW,IAAI,IAAI,OAAO,CAAC;AAAA,EAC7B;AAEA,QAAM,aAAgC;AAAA,IACpC,MAAM;AAAA,IACN,SAAS;AAAA,IACT,aAAa;AAAA,EACf;AAEA,SAAO;AAAA,IACL;AAAA,IAEA,cAAc,UAAkB,SAAyB,MAA2C;AAClG,UAAI,QAAQ,SAAS,WAAY;AACjC,YAAM,QAAS,QAAgB;AAC/B,UAAI,CAAC,OAAO,WAAW,QAAQ,EAAG;AAElC,YAAM,SAAS,MAAM,MAAM,CAAC;AAC5B,YAAM,UAAY,QAAgB,WAAW,CAAC;AAE9C,cAAQ,QAAQ;AAAA,QACd,KAAK,YAAY;AACf,gBAAM,eAAe,OAAO,QAAQ,iBAAiB,WAAW,QAAQ,eAAe;AACvF,gBAAM,QAAQ,OAAO,QAAQ,UAAU,WAAW,QAAQ,QAAQ;AAClE,kBAAQ,IAAI,UAAU,EAAE,UAAU,cAAc,OAAO,OAAO,MAAM,CAAC;AACrE,iBAAO;AACP;AAAA,QACF;AAAA,QAEA,KAAK,cAAc;AACjB,cAAI,QAAQ,OAAO,QAAQ,GAAG;AAC5B,mBAAO;AAAA,UACT;AACA;AAAA,QACF;AAAA,QAEA,KAAK,iBAAiB;AACpB,gBAAM,SAAS,QAAQ,IAAI,QAAQ;AACnC,cAAI,CAAC,OAAQ;AACb,cAAI,OAAO,QAAQ,UAAU,UAAU;AACrC,mBAAO,QAAQ,QAAQ;AAAA,UACzB;AACA,iBAAO;AACP;AAAA,QACF;AAAA,QAEA,KAAK,QAAQ;AACX,gBAAM,iBAAiB,OAAO,QAAQ,aAAa,WAC/C,QAAQ,WACR;AACJ,gBAAM,QAAQ,QAAQ,UAAU;AAEhC,gBAAM,SAAS,QAAQ,IAAI,cAAc;AACzC,cAAI,QAAQ;AACV,mBAAO,QAAQ;AACf,mBAAO;AAAA,UACT;AAEA,eAAK,EAAE,MAAM,aAAa,OAAO,uBAAuB,SAAS,EAAE,MAAM,EAAE,CAAmB;AAC9F;AAAA,QACF;AAAA,QAEA;AAEE;AAAA,MACJ;AAAA,IACF;AAAA,IAEA,kBAAkB,UAAwB;AACxC,UAAI,QAAQ,OAAO,QAAQ,GAAG;AAC5B,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF;AACF;;;AC1GA,IAAM,+BAA+B;AAGrC,IAAM,yBAAyB;AAG/B,IAAI,YAAY;AAKhB,SAAS,aAAqB;AAC5B;AACA,SAAO,SAAS,KAAK,IAAI,CAAC,IAAI,SAAS;AACzC;AA4BO,SAAS,0BAA0B,SAAsD;AAC9F,QAAM,gBAAgB,oBAAI,IAA4B;AACtD,QAAM,WAAW,SAAS;AAC1B,QAAM,eAAe,SAAS,gBAAgB;AAK9C,WAAS,sBAAsC;AAC7C,UAAM,MAAsB,CAAC;AAC7B,eAAW,gBAAgB,cAAc,OAAO,GAAG;AACjD,UAAI,KAAK,GAAG,YAAY;AAAA,IAC1B;AACA,WAAO;AAAA,EACT;AAEA,WAAS,SAAe;AACtB,eAAW,oBAAoB,CAAC;AAAA,EAClC;AAKA,WAAS,uBAAuB,UAAkC;AAChE,QAAI,OAAO,cAAc,IAAI,QAAQ;AACrC,QAAI,CAAC,MAAM;AACT,aAAO,CAAC;AACR,oBAAc,IAAI,UAAU,IAAI;AAAA,IAClC;AACA,WAAO;AAAA,EACT;AAKA,WAAS,aAAa,MAA4B;AAChD,WAAO,KAAK,SAAS,cAAc;AACjC,WAAK,MAAM;AAAA,IACb;AAAA,EACF;AAMA,WAAS,SAAS,IAAwD;AACxE,eAAW,CAAC,UAAU,IAAI,KAAK,eAAe;AAC5C,YAAM,QAAQ,KAAK,UAAU,CAAC,MAAM,EAAE,OAAO,EAAE;AAC/C,UAAI,UAAU,IAAI;AAChB,eAAO,CAAC,UAAU,KAAK,KAAK,GAAG,KAAK;AAAA,MACtC;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAEA,QAAM,aAAgC;AAAA,IACpC,MAAM;AAAA,IACN,SAAS;AAAA,IACT,aAAa;AAAA,EACf;AAEA,SAAO;AAAA,IACL;AAAA,IAEA,cAAc,UAAkB,SAAyB,MAA2C;AAClG,YAAM,MAAM;AAIZ,UAAI,QAAQ,KAAK,WAAW,SAAS,GAAG;AACtC,cAAMA,UAAS,QAAQ,KAAK,MAAM,CAAC;AAEnC,gBAAQA,SAAQ;AAAA,UACd,KAAK,UAAU;AACb,kBAAM,QAAQ,OAAO,IAAI,UAAU,WAAW,IAAI,QAAQ;AAC1D,kBAAM,OAAO,OAAO,IAAI,SAAS,WAAW,IAAI,OAAO;AAEvD,kBAAM,KAAK,WAAW;AACtB,kBAAM,eAA6B;AAAA,cACjC;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,cACA,MAAM;AAAA,cACN,WAAW,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAAA,YACzC;AAEA,kBAAM,OAAO,uBAAuB,QAAQ;AAC5C,iBAAK,KAAK,YAAY;AACtB,yBAAa,IAAI;AACjB,mBAAO;AAEP,iBAAK,EAAE,MAAM,kBAAkB,GAAG,CAA8B;AAChE;AAAA,UACF;AAAA,UAEA,KAAK,WAAW;AACd,kBAAM,UAAU,OAAO,IAAI,mBAAmB,WAAW,IAAI,iBAAiB;AAC9E,gBAAI,CAAC,QAAS;AAEd,kBAAM,QAAQ,SAAS,OAAO;AAC9B,gBAAI,OAAO;AACT,oBAAM,CAAC,eAAe,EAAE,KAAK,IAAI;AACjC,oBAAM,OAAO,cAAc,IAAI,aAAa;AAC5C,kBAAI,MAAM;AACR,qBAAK,OAAO,OAAO,CAAC;AACpB,oBAAI,KAAK,WAAW,EAAG,eAAc,OAAO,aAAa;AACzD,uBAAO;AAAA,cACT;AAAA,YACF;AACA;AAAA,UACF;AAAA,UAEA,KAAK,QAAQ;AACX,kBAAM,UAAU,OAAO,IAAI,mBAAmB,WAAW,IAAI,iBAAiB;AAC9E,gBAAI,CAAC,QAAS;AAEd,kBAAM,QAAQ,SAAS,OAAO;AAC9B,gBAAI,OAAO;AACT,oBAAM,CAAC,EAAE,YAAY,IAAI;AACzB,kBAAI,CAAC,aAAa,MAAM;AACtB,6BAAa,OAAO;AACpB,uBAAO;AAAA,cACT;AAAA,YACF;AACA;AAAA,UACF;AAAA,UAEA,KAAK,QAAQ;AACX,kBAAM,eAAe,cAAc,IAAI,QAAQ,KAAK,CAAC;AACrD,iBAAK,EAAE,MAAM,iBAAiB,eAAe,aAAa,CAA8B;AACxF;AAAA,UACF;AAAA,UAEA;AAEE;AAAA,QACJ;AACA;AAAA,MACF;AAIA,UAAI,QAAQ,SAAS,WAAY;AACjC,YAAM,QAAQ,IAAI;AAClB,UAAI,CAAC,OAAO,WAAW,gBAAgB,EAAG;AAE1C,YAAM,SAAS,MAAM,MAAM,EAAE;AAC7B,YAAM,UAAY,IAAI,WAAW,CAAC;AAElC,cAAQ,QAAQ;AAAA,QACd,KAAK,UAAU;AACb,gBAAM,QAAQ,OAAO,QAAQ,UAAU,WAAW,QAAQ,QAAQ;AAClE,gBAAM,OAAO,OAAO,QAAQ,SAAS,WAAW,QAAQ,OAAO;AAE/D,gBAAM,KAAK,WAAW;AACtB,gBAAM,eAA6B;AAAA,YACjC;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA,MAAM;AAAA,YACN,WAAW,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAAA,UACzC;AAEA,gBAAM,OAAO,uBAAuB,QAAQ;AAC5C,eAAK,KAAK,YAAY;AACtB,uBAAa,IAAI;AACjB,iBAAO;AAEP,eAAK,EAAE,MAAM,aAAa,OAAO,yBAAyB,SAAS,EAAE,GAAG,EAAE,CAAmB;AAC7F;AAAA,QACF;AAAA,QAEA,KAAK,WAAW;AACd,gBAAM,KAAK,OAAO,QAAQ,OAAO,WAAW,QAAQ,KAAK;AACzD,cAAI,CAAC,GAAI;AAET,gBAAM,QAAQ,SAAS,EAAE;AACzB,cAAI,OAAO;AACT,kBAAM,CAAC,eAAe,EAAE,KAAK,IAAI;AACjC,kBAAM,OAAO,cAAc,IAAI,aAAa;AAC5C,gBAAI,MAAM;AACR,mBAAK,OAAO,OAAO,CAAC;AAEpB,kBAAI,KAAK,WAAW,GAAG;AACrB,8BAAc,OAAO,aAAa;AAAA,cACpC;AACA,qBAAO;AAAA,YACT;AAAA,UACF;AACA;AAAA,QACF;AAAA,QAEA,KAAK,QAAQ;AACX,gBAAM,KAAK,OAAO,QAAQ,OAAO,WAAW,QAAQ,KAAK;AACzD,cAAI,CAAC,GAAI;AAET,gBAAM,QAAQ,SAAS,EAAE;AACzB,cAAI,OAAO;AACT,kBAAM,CAAC,EAAE,YAAY,IAAI;AACzB,gBAAI,CAAC,aAAa,MAAM;AACtB,2BAAa,OAAO;AACpB,qBAAO;AAAA,YACT;AAAA,UACF;AACA;AAAA,QACF;AAAA,QAEA,KAAK,QAAQ;AACX,gBAAM,eAAe,cAAc,IAAI,QAAQ,KAAK,CAAC;AACrD,eAAK,EAAE,MAAM,aAAa,OAAO,wBAAwB,SAAS,EAAE,eAAe,aAAa,EAAE,CAAmB;AACrH;AAAA,QACF;AAAA,QAEA;AAEE;AAAA,MACJ;AAAA,IACF;AAAA,IAEA,kBAAkB,UAAwB;AACxC,UAAI,cAAc,OAAO,QAAQ,GAAG;AAClC,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF;AACF;;;ACvPA,IAAM,2BAA2B;AA2C1B,SAAS,sBAAsB,SAAiD;AACrF,SAAO;AAAA,IACL,YAAY;AAAA,MACV,MAAM;AAAA,MACN,SAAS;AAAA,MACT,aAAa;AAAA,IACf;AAAA,IAEA,cACE,WACA,SACA,MACM;AACN,YAAM,KAAM,QAA6C,MAAM;AAE/D,eAAS,UAAU,UAAkB,OAAqB;AACxD,aAAK,EAAE,MAAM,GAAG,QAAQ,UAAU,IAAI,MAAM,CAAmB;AAAA,MACjE;AAEA,YAAM,SAAS,QAAQ,UAAU;AAEjC,cAAQ,QAAQ,MAAM;AAAA,QACpB,KAAK,yBAAyB;AAK5B,cAAI,CAAC,QAAQ;AACX,kBAAM,SAA4C;AAAA,cAChD,MAAM;AAAA,cACN;AAAA,cACA,QAAQ;AAAA,YACV;AACA,iBAAK,MAAM;AACX;AAAA,UACF;AACA,kBAAQ,QAAQ,OAAO,eAAe,CAAC,EACpC,KAAK,CAAC,WAAW;AAChB,kBAAM,SAA4C;AAAA,cAChD,MAAM;AAAA,cACN;AAAA,cACA,QAAS,UAAqB;AAAA,YAChC;AACA,iBAAK,MAAM;AAAA,UACb,CAAC,EACA,MAAM,CAAC,QAAiB;AACvB;AAAA,cACE;AAAA,cACC,KAAe,WAAW;AAAA,YAC7B;AAAA,UACF,CAAC;AACH;AAAA,QACF;AAAA,QAEA,KAAK,sBAAsB;AACzB,cAAI,CAAC,QAAQ;AACX,sBAAU,sBAAsB,sBAAsB;AACtD;AAAA,UACF;AACA,kBAAQ,QAAQ,OAAO,YAAY,KAAK,CAAC,CAAC,EACvC,KAAK,CAAC,WAAW;AAChB,kBAAM,SAAyC;AAAA,cAC7C,MAAM;AAAA,cACN;AAAA,cACA;AAAA,YACF;AACA,iBAAK,MAAM;AAAA,UACb,CAAC,EACA,MAAM,CAAC,QAAiB;AACvB;AAAA,cACE;AAAA,cACC,KAAe,WAAW;AAAA,YAC7B;AAAA,UACF,CAAC;AACH;AAAA,QACF;AAAA,QAEA,KAAK,uBAAuB;AAC1B,gBAAM,SAA0C;AAAA,YAC9C,MAAM;AAAA,YACN;AAAA,YACA,SAAS;AAAA,UACX;AACA,eAAK,MAAM;AACX;AAAA,QACF;AAAA,QAEA,KAAK,uBAAuB;AAC1B,gBAAM,SAA0C;AAAA,YAC9C,MAAM;AAAA,YACN;AAAA,YACA,SAAS,CAAC;AAAA,UACZ;AACA,eAAK,MAAM;AACX;AAAA,QACF;AAAA,QAEA,KAAK,oBAAoB;AACvB,gBAAM,SAAuC;AAAA,YAC3C,MAAM;AAAA,YACN;AAAA,YACA,SAAS,CAAC;AAAA,UACZ;AACA,eAAK,MAAM;AACX;AAAA,QACF;AAAA,QAEA,KAAK,oBAAoB;AACvB,gBAAM,SAAuC;AAAA,YAC3C,MAAM;AAAA,YACN;AAAA,YACA,MAAM,CAAC;AAAA,UACT;AACA,eAAK,MAAM;AACX;AAAA,QACF;AAAA,QAEA,KAAK,qBAAqB;AACxB,gBAAM,SAAwC;AAAA,YAC5C,MAAM;AAAA,YACN;AAAA,YACA,SAAS,CAAC;AAAA,UACZ;AACA,eAAK,MAAM;AACX;AAAA,QACF;AAAA,QAEA,KAAK,uBAAuB;AAC1B,gBAAM,SAA0C;AAAA,YAC9C,MAAM;AAAA,YACN;AAAA,YACA,SAAS,CAAC;AAAA,UACZ;AACA,eAAK,MAAM;AACX;AAAA,QACF;AAAA,QAEA,KAAK,sBAAsB;AACzB,gBAAM,SAAyC;AAAA,YAC7C,MAAM;AAAA,YACN;AAAA,YACA,QAAQ,CAAC;AAAA,UACX;AACA,eAAK,MAAM;AACX;AAAA,QACF;AAAA,QAEA;AACE,oBAAU,QAAQ,MAAM,4BAA4B,QAAQ,IAAI,EAAE;AAAA,MACtE;AAAA,IACF;AAAA;AAAA,IAGA,kBAAkB,WAAyB;AAAA,IAE3C;AAAA,EACF;AACF;;;ACpNA,IAAM,mBAAmB;AAmFlB,SAAS,uBAAuB,SAAkD;AACvF,QAAM,UAAU,oBAAI,IAAiC;AAErD,SAAO;AAAA,IACL,YAAY;AAAA,MACV,MAAM;AAAA,MACN,SAAS;AAAA,MACT,aAAa;AAAA,IACf;AAAA,IAEA,cAAc,UAAkB,SAAyB,MAA2C;AAClG,UAAI,QAAQ,SAAS,mBAAmB;AACtC,cAAM,QAAS,QAAgB;AAC/B,YAAI,OAAO,UAAU,SAAU;AAC/B,cAAM,UAAW,QAAgB;AACjC,cAAM,SAAS,GAAG,QAAQ,IAAI,KAAK;AAGnC,cAAM,WAAW,QAAQ,IAAI,MAAM;AACnC,YAAI,UAAU;AACZ,mBAAS,OAAO,YAAY;AAC5B,uBAAa,SAAS,SAAS;AAC/B,kBAAQ,OAAO,MAAM;AAAA,QACvB;AAEA,YAAI,CAAC,QAAQ,YAAY,GAAG;AAC1B,eAAK,EAAE,MAAM,cAAc,MAAM,CAAmB;AACpD;AAAA,QACF;AAEA,cAAM,YAAY,QAAQ,gBAAgB,OAAO;AACjD,YAAI,WAAW;AAEf,cAAM,YAAY,WAAW,MAAM;AACjC,cAAI,CAAC,UAAU;AACb,uBAAW;AACX,iBAAK,EAAE,MAAM,cAAc,MAAM,CAAmB;AAAA,UACtD;AAAA,QACF,GAAG,gBAAgB;AAEnB,cAAM,SAAS,QAAQ,UAAU,SAAS,CAAC,SAAS;AAClD,cAAI,SAAS,QAAQ;AACnB,yBAAa,SAAS;AACtB,gBAAI,CAAC,UAAU;AACb,yBAAW;AACX,mBAAK,EAAE,MAAM,cAAc,MAAM,CAAmB;AAAA,YACtD;AACA;AAAA,UACF;AACA,eAAK,EAAE,MAAM,eAAe,OAAO,OAAO,KAAK,CAAmB;AAAA,QACpE,GAAG,SAAS;AAEZ,gBAAQ,IAAI,QAAQ,EAAE,QAAQ,UAAU,CAAC;AACzC;AAAA,MACF;AAEA,UAAI,QAAQ,SAAS,eAAe;AAClC,cAAM,QAAS,QAAgB;AAC/B,YAAI,OAAO,UAAU,SAAU;AAC/B,cAAM,SAAS,GAAG,QAAQ,IAAI,KAAK;AACnC,cAAM,QAAQ,QAAQ,IAAI,MAAM;AAChC,YAAI,OAAO;AACT,gBAAM,OAAO,YAAY;AACzB,uBAAa,MAAM,SAAS;AAC5B,kBAAQ,OAAO,MAAM;AAAA,QACvB;AACA;AAAA,MACF;AAEA,UAAI,QAAQ,SAAS,iBAAiB;AACpC,cAAM,QAAS,QAAgB;AAC/B,YAAI,SAAS,OAAO,UAAU,YAAY,QAAQ,YAAY,GAAG;AAC/D,kBAAQ,QAAQ,KAAK;AAAA,QACvB;AACA;AAAA,MACF;AAaA,UAAI,QAAQ,SAAS,0BAA0B;AAC7C,cAAM,QAAS,QAAgB;AAC/B,YAAI,SAAS,OAAO,UAAU,YAAY,QAAQ,YAAY,GAAG;AAC/D,kBAAQ,QAAQ,KAAK;AAAA,QACvB;AACA;AAAA,MACF;AAAA,IACF;AAAA,IAEA,kBAAkB,UAAwB;AACxC,YAAM,SAAS,GAAG,QAAQ;AAC1B,iBAAW,CAAC,KAAK,KAAK,KAAK,SAAS;AAClC,YAAI,IAAI,WAAW,MAAM,GAAG;AAC1B,gBAAM,OAAO,YAAY;AACzB,uBAAa,MAAM,SAAS;AAC5B,kBAAQ,OAAO,GAAG;AAAA,QACpB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;AClJO,SAAS,mBAAmB,SAA8C;AAC/E,SAAO;AAAA,IACL,YAAY;AAAA,MACV,MAAM;AAAA,MACN,SAAS;AAAA,MACT,aAAa;AAAA,IACf;AAAA,IAEA,cAAc,WAAmB,SAAyB,MAA2C;AACnG,UAAI,QAAQ,SAAS,mBAAmB;AACtC,cAAM,QAAS,QAAgB;AAC/B,YAAI,OAAO,UAAU,SAAU;AAC/B,cAAM,UAAW,QAAgB;AAEjC,YAAI,CAAC,QAAQ,YAAY,GAAG;AAC1B,eAAK,EAAE,MAAM,cAAc,MAAM,CAAmB;AACpD;AAAA,QACF;AAEA,gBACG,MAAM,OAAO,EACb,KAAK,CAAC,WAAW;AAChB,qBAAW,SAAS,QAAQ;AAC1B,iBAAK,EAAE,MAAM,eAAe,OAAO,MAAM,CAAmB;AAAA,UAC9D;AACA,eAAK,EAAE,MAAM,cAAc,MAAM,CAAmB;AAAA,QACtD,CAAC,EACA,MAAM,MAAM;AAEX,eAAK,EAAE,MAAM,cAAc,MAAM,CAAmB;AAAA,QACtD,CAAC;AACH;AAAA,MACF;AAEA,UAAI,QAAQ,SAAS,iBAAiB;AACpC,cAAM,QAAS,QAAgB;AAC/B,YAAI,SAAS,OAAO,UAAU,YAAY,QAAQ,YAAY,GAAG;AAC/D,cAAI;AACF,oBAAQ,MAAM,KAAK;AAAA,UACrB,QAAQ;AAAA,UAER;AAAA,QACF;AACA;AAAA,MACF;AAAA,IACF;AAAA;AAAA,IAGA,kBAAkB,WAAyB;AAAA,IAE3C;AAAA,EACF;AACF;;;ACtGA,IAAM,0BAA0B;AA0EzB,SAAS,uBAAuB,SAAkD;AACvF,QAAM,YAAY,QAAQ,iBAAiB;AAC3C,QAAM,OAAO,oBAAI,IAAwB;AAEzC,WAAS,cAAc,QAAgB,OAAe,MAA2C;AAC/F,UAAM,MAAM,KAAK,IAAI,MAAM;AAC3B,QAAI,CAAC,OAAO,IAAI,SAAU;AAC1B,QAAI,IAAI,aAAa,IAAI,WAAW;AAClC,UAAI,WAAW;AACf,mBAAa,IAAI,SAAS;AAC1B,WAAK,EAAE,MAAM,cAAc,MAAM,CAAmB;AAAA,IACtD;AAAA,EACF;AAEA,SAAO;AAAA,IACL,YAAY;AAAA,MACV,MAAM;AAAA,MACN,SAAS;AAAA,MACT,aAAa;AAAA,IACf;AAAA,IAEA,cAAc,UAAkB,SAAyB,MAA2C;AAClG,UAAI,QAAQ,SAAS,mBAAmB;AAiCtC,YAASC,WAAT,SAAiB,OAAyB;AACxC,cAAI,QAAQ,QAAQ,IAAI,MAAM,EAAE,EAAG;AACnC,kBAAQ,QAAQ,IAAI,MAAM,EAAE;AAC5B,cAAI,KAAK,IAAI,MAAM,EAAG,MAAK,EAAE,MAAM,eAAe,OAAO,MAAM,CAAmB;AAAA,QACpF;AAJS,sBAAAA;AAhCT,cAAM,QAAS,QAAgB;AAC/B,YAAI,OAAO,UAAU,SAAU;AAC/B,cAAM,UAAW,QAAgB;AACjC,cAAM,SAAS,GAAG,QAAQ,IAAI,KAAK;AAGnC,cAAM,WAAW,KAAK,IAAI,MAAM;AAChC,YAAI,UAAU;AACZ,mBAAS,aAAa,YAAY;AAClC,uBAAa,SAAS,SAAS;AAC/B,eAAK,OAAO,MAAM;AAAA,QACpB;AAEA,cAAM,iBAAiB,QAAQ,MAAM,YAAY;AACjD,cAAM,iBAAiB,QAAQ,UAAU,YAAY;AAGrD,YAAI,CAAC,kBAAkB,CAAC,gBAAgB;AACtC,eAAK,EAAE,MAAM,cAAc,MAAM,CAAmB;AACpD;AAAA,QACF;AAEA,cAAM,UAAsB;AAAA,UAC1B,SAAS,oBAAI,IAAI;AAAA,UACjB,WAAW,CAAC;AAAA;AAAA,UACZ,WAAW,CAAC;AAAA;AAAA,UACZ,UAAU;AAAA,UACV,WAAW;AAAA,UACX,aAAa;AAAA,QACf;AACA,aAAK,IAAI,QAAQ,OAAO;AASxB,YAAI,gBAAgB;AAClB,kBAAQ,MACL,MAAM,OAAO,EACb,KAAK,CAAC,WAAW;AAChB,uBAAW,SAAS,OAAQ,CAAAA,SAAQ,KAAK;AACzC,oBAAQ,YAAY;AACpB,0BAAc,QAAQ,OAAO,IAAI;AAAA,UACnC,CAAC,EACA,MAAM,MAAM;AAEX,oBAAQ,YAAY;AACpB,0BAAc,QAAQ,OAAO,IAAI;AAAA,UACnC,CAAC;AAAA,QACL;AAGA,YAAI,gBAAgB;AAClB,kBAAQ,YAAY,WAAW,MAAM;AACnC,gBAAI,CAAC,QAAQ,UAAU;AACrB,sBAAQ,YAAY;AACpB,4BAAc,QAAQ,OAAO,IAAI;AAAA,YACnC;AAAA,UACF,GAAG,SAAS;AAEZ,gBAAM,YAAY,QAAQ,UAAU,gBAAgB,OAAO;AAC3D,kBAAQ,cAAc,QAAQ,UAAU,UAAU,SAAS,CAAC,SAAS;AACnE,gBAAI,SAAS,QAAQ;AACnB,2BAAa,QAAQ,SAAS;AAC9B,sBAAQ,YAAY;AACpB,4BAAc,QAAQ,OAAO,IAAI;AACjC;AAAA,YACF;AACA,YAAAA,SAAQ,IAAI;AAEZ,gBAAI,gBAAgB;AAClB,kBAAI;AAAE,wBAAQ,MAAM,MAAM,IAAI;AAAA,cAAG,QAAQ;AAAA,cAAoB;AAAA,YAC/D;AAAA,UACF,GAAG,SAAS;AAAA,QACd;AACA;AAAA,MACF;AAEA,UAAI,QAAQ,SAAS,eAAe;AAClC,cAAM,QAAS,QAAgB;AAC/B,YAAI,OAAO,UAAU,SAAU;AAC/B,cAAM,SAAS,GAAG,QAAQ,IAAI,KAAK;AACnC,cAAM,QAAQ,KAAK,IAAI,MAAM;AAC7B,YAAI,OAAO;AACT,gBAAM,aAAa,YAAY;AAC/B,uBAAa,MAAM,SAAS;AAC5B,eAAK,OAAO,MAAM;AAAA,QACpB;AACA;AAAA,MACF;AAEA,UAAI,QAAQ,SAAS,iBAAiB;AACpC,cAAM,QAAS,QAAgB;AAC/B,YAAI,CAAC,SAAS,OAAO,UAAU,SAAU;AAEzC,YAAI,QAAQ,UAAU,YAAY,GAAG;AACnC,kBAAQ,UAAU,QAAQ,KAAK;AAAA,QACjC;AAEA,YAAI,QAAQ,MAAM,YAAY,GAAG;AAC/B,cAAI;AAAE,oBAAQ,MAAM,MAAM,KAAK;AAAA,UAAG,QAAQ;AAAA,UAAoB;AAAA,QAChE;AACA;AAAA,MACF;AAAA,IACF;AAAA,IAEA,kBAAkB,UAAwB;AACxC,YAAM,SAAS,GAAG,QAAQ;AAC1B,iBAAW,CAAC,KAAK,KAAK,KAAK,MAAM;AAC/B,YAAI,IAAI,WAAW,MAAM,GAAG;AAC1B,gBAAM,aAAa,YAAY;AAC/B,uBAAa,MAAM,SAAS;AAC5B,eAAK,OAAO,GAAG;AAAA,QACjB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;AC/MA,IAAM,uBAAuB;AAsDtB,SAAS,kBAAkB,UAA8B,CAAC,GAAmB;AAClF,QAAM,aAAgC;AAAA,IACpC,MAAM;AAAA,IACN,SAAS;AAAA,IACT,aAAa;AAAA,EACf;AAEA,SAAO;AAAA,IACL;AAAA,IAEA,cAAc,WAAmB,SAAyB,MAA2C;AACnG,cAAQ,QAAQ,MAAM;AAAA,QACpB,KAAK,gBAAgB;AACnB,gBAAM,IAAI;AACV,kBAAQ,YAAY;AAAA,YAClB,KAAK,EAAE;AAAA,YACP,MAAM,EAAE;AAAA,YACR,SAAS,EAAE;AAAA,YACX,QAAQ,EAAE;AAAA,YACV,UAAU,EAAE;AAAA,YACZ,SAAS,EAAE;AAAA,UACb,CAAC;AACD;AAAA,QACF;AAAA,QAEA,KAAK,uBAAuB;AAC1B,gBAAM,IAAI;AACV,gBAAM,SAA0C;AAAA,YAC9C,MAAM;AAAA,YACN,IAAI,EAAE;AAAA,YACN,UAAU,EAAE,OAAO;AAAA,YACnB,GAAI,EAAE,OAAO,aAAa,EAAE,SAAS,EAAE,OAAO,WAAW,IAAI,CAAC;AAAA,UAChE;AACA,eAAK,MAAwB;AAC7B;AAAA,QACF;AAAA,QAEA,KAAK,yBAAyB;AAE5B;AAAA,QACF;AAAA,QAEA,SAAS;AACP,gBAAM,KAAM,QAA6C,MAAM;AAC/D,eAAK;AAAA,YACH,MAAM,GAAG,QAAQ,IAAI;AAAA,YACrB;AAAA,YACA,OAAO,wBAAwB,QAAQ,IAAI;AAAA,UAC7C,CAAmB;AACnB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,IAEA,kBAAkB,WAAyB;AAAA,IAE3C;AAAA,EACF;AACF;;;ACjHA,IAAM,wBAAwB;AA2DvB,SAAS,mBAAmB,UAA+B,CAAC,GAAmB;AACpF,QAAM,aAAgC;AAAA,IACpC,MAAM;AAAA,IACN,SAAS;AAAA,IACT,aAAa;AAAA,EACf;AAEA,SAAO;AAAA,IACL;AAAA,IAEA,cACE,UACA,SACA,MACM;AACN,cAAQ,QAAQ,MAAM;AAAA,QACpB,KAAK,wBAAwB;AAC3B,gBAAM,IAAI;AACV,kBAAQ,kBAAkB,UAAU,EAAE,WAAW,EAAE,QAAQ;AAC3D,gBAAM,SAA0C;AAAA,YAC9C,MAAM;AAAA,YACN,IAAI,EAAE;AAAA,YACN,WAAW,EAAE;AAAA,UACf;AACA,eAAK,MAAwB;AAC7B;AAAA,QACF;AAAA,QAEA,KAAK,wBAAwB;AAC3B,gBAAM,IAAI;AACV,kBAAQ,kBAAkB,UAAU,EAAE,aAAa,IAAI,EAAE,QAAQ;AACjE;AAAA,QACF;AAAA,QAEA,KAAK,yBAAyB;AAC5B,gBAAM,IAAI;AACV,kBAAQ,mBAAmB,UAAU,EAAE,aAAa,EAAE;AACtD;AAAA,QACF;AAAA,QAEA,KAAK,eAAe;AAClB,gBAAM,IAAI;AACV,kBAAQ,UAAU,UAAU,EAAE,aAAa,IAAI,CAAC;AAChD;AAAA,QACF;AAAA,QAEA,KAAK,sBAAsB;AACzB,gBAAM,IAAI;AACV,kBAAQ,iBAAiB,UAAU,EAAE,aAAa,IAAI,EAAE,OAAO;AAC/D;AAAA,QACF;AAAA,QAEA,SAAS;AACP,gBAAM,KAAM,QAA6C,MAAM;AAC/D,eAAK;AAAA,YACH,MAAM,GAAG,QAAQ,IAAI;AAAA,YACrB;AAAA,YACA,OAAO,yBAAyB,QAAQ,IAAI;AAAA,UAC9C,CAAmB;AACnB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,IAEA,kBAAkB,WAAyB;AAAA,IAG3C;AAAA,EACF;AACF;;;ACtHA,IAAM,yBAAyB;AAwDxB,SAAS,oBAAoB,UAAgC,CAAC,GAAmB;AACtF,MAAI,UAAU;AACd,QAAM,MAAM,QAAQ,eAAe,MAAc;AAC/C,eAAW;AACX,WAAO,SAAS,OAAO;AAAA,EACzB;AACA,QAAM,eAAe,QAAQ,gBAAgB;AAE7C,QAAM,aAAgC;AAAA,IACpC,MAAM;AAAA,IACN,SAAS;AAAA,IACT,aAAa;AAAA,EACf;AAEA,SAAO;AAAA,IACL;AAAA,IAEA,cAAc,UAAkB,SAAyB,MAA2C;AAClG,cAAQ,QAAQ,MAAM;AAAA,QACpB,KAAK,eAAe;AAClB,gBAAM,IAAI;AACV,kBAAQ,SAAS,UAAU,CAAC;AAC5B,gBAAM,SAAkC;AAAA,YACtC,MAAM;AAAA,YACN,IAAI,EAAE;AAAA,YACN,gBAAgB,IAAI;AAAA,UACtB;AACA,eAAK,MAAwB;AAC7B;AAAA,QACF;AAAA,QAEA,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAGH;AAAA,QAEF,KAAK,6BAA6B;AAChC,gBAAM,IAAI;AACV,gBAAM,SAAwC;AAAA,YAC5C,MAAM;AAAA,YACN,IAAI,EAAE;AAAA,YACN,SAAS;AAAA,UACX;AACA,eAAK,MAAwB;AAC7B;AAAA,QACF;AAAA,QAEA,SAAS;AACP,gBAAM,KAAM,QAA6C,MAAM;AAC/D,eAAK;AAAA,YACH,MAAM,GAAG,QAAQ,IAAI;AAAA,YACrB;AAAA,YACA,OAAO,0BAA0B,QAAQ,IAAI;AAAA,UAC/C,CAAmB;AAAA,QACrB;AAAA,MACF;AAAA,IACF;AAAA,IAEA,kBAAkB,WAAyB;AAAA,IAG3C;AAAA,EACF;AACF;;;ACnHA,IAAM,wBAAwB;AAU9B,IAAM,gBAAuB;AAAA,EAC3B,QAAQ;AAAA,IACN,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,SAAS;AAAA,EACX;AAAA;AAAA;AAGF;AA2EO,SAAS,mBAAmB,UAA+B,CAAC,GAAiB;AAClF,MAAI,eAAsB,QAAQ,gBAAgB;AAElD,QAAM,aAAgC;AAAA,IACpC,MAAM;AAAA,IACN,SAAS;AAAA,IACT,aAAa;AAAA,EACf;AAEA,QAAM,UAA0B;AAAA,IAC9B;AAAA,IAEA,cACE,WACA,SACA,MACM;AACN,YAAM,KAAM,QAA6C,MAAM;AAE/D,UAAI,QAAQ,SAAS,aAAa;AAChC,cAAM,SAAgC;AAAA,UACpC,MAAM;AAAA,UACN;AAAA,UACA,OAAO;AAAA,QACT;AACA,aAAK,MAAwB;AAC7B;AAAA,MACF;AAOA,WAAK;AAAA,QACH,MAAM,GAAG,QAAQ,IAAI;AAAA,QACrB;AAAA,QACA,OAAO,yBAAyB,QAAQ,IAAI;AAAA,MAC9C,CAAmB;AAAA,IACrB;AAAA;AAAA,IAGA,kBAAkB,WAAyB;AAAA,IAE3C;AAAA,EACF;AAEA,WAAS,aAAa,OAAmC;AACvD,mBAAe;AACf,UAAM,WAAgC,EAAE,MAAM,iBAAiB,MAAM;AACrE,YAAQ,cAAc,QAAQ;AAC9B,WAAO;AAAA,EACT;AAEA,WAAS,kBAAyB;AAChC,WAAO;AAAA,EACT;AAEA,SAAO,EAAE,SAAS,cAAc,gBAAgB;AAClD;","names":["action","deliver"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kehto/services",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Reference service implementations for the napplet protocol — audio, notifications, and extensible service handlers",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"sideEffects": false,
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@kehto/runtime": "0.2.0"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"@napplet/core": "^0.2.1",
|
|
27
|
+
"@napplet/nub-identity": "^0.2.1",
|
|
28
|
+
"@napplet/nub-ifc": "^0.2.1",
|
|
29
|
+
"@napplet/nub-keys": "^0.2.1",
|
|
30
|
+
"@napplet/nub-media": "^0.2.1",
|
|
31
|
+
"@napplet/nub-notify": "^0.2.1",
|
|
32
|
+
"@napplet/nub-relay": "^0.2.1",
|
|
33
|
+
"@napplet/nub-storage": "^0.2.1",
|
|
34
|
+
"@napplet/nub-theme": "^0.2.1"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@napplet/core": "^0.2.1",
|
|
38
|
+
"@napplet/nub-identity": "^0.2.1",
|
|
39
|
+
"@napplet/nub-ifc": "^0.2.1",
|
|
40
|
+
"@napplet/nub-keys": "^0.2.1",
|
|
41
|
+
"@napplet/nub-media": "^0.2.1",
|
|
42
|
+
"@napplet/nub-notify": "^0.2.1",
|
|
43
|
+
"@napplet/nub-relay": "^0.2.1",
|
|
44
|
+
"@napplet/nub-storage": "^0.2.1",
|
|
45
|
+
"@napplet/nub-theme": "^0.2.1",
|
|
46
|
+
"tsup": "^8.5.0",
|
|
47
|
+
"typescript": "^5.9.3",
|
|
48
|
+
"vitest": "^4.1.2"
|
|
49
|
+
},
|
|
50
|
+
"license": "MIT",
|
|
51
|
+
"repository": {
|
|
52
|
+
"type": "git",
|
|
53
|
+
"url": "git+https://github.com/kehto/runtime.git",
|
|
54
|
+
"directory": "packages/services"
|
|
55
|
+
},
|
|
56
|
+
"keywords": [
|
|
57
|
+
"nostr",
|
|
58
|
+
"napplet",
|
|
59
|
+
"kehto",
|
|
60
|
+
"services",
|
|
61
|
+
"audio",
|
|
62
|
+
"notifications"
|
|
63
|
+
],
|
|
64
|
+
"scripts": {
|
|
65
|
+
"build": "tsup",
|
|
66
|
+
"type-check": "tsc --noEmit",
|
|
67
|
+
"test:unit": "echo 'no unit tests yet'"
|
|
68
|
+
}
|
|
69
|
+
}
|