import { Capability, ServiceRegistry, RuntimeConfigOverrides, ConsentRequest, Runtime, SessionEntry, PendingUpdate, RuntimeAdapter } from '@kehto/runtime'; export { ALL_CAPABILITIES, AclChecker, Capability, ConsentRequest, EnforceConfig, EnforceResult, IdentityResolver, NapEnforceConfig, NapMessage, NappKeyEntry, PendingUpdate, ServiceDescriptor, ServiceHandler, ServiceRegistry, SessionEntry, createEnforceGate, createNapEnforceGate, formatDenialReason } from '@kehto/runtime'; import { NappletMessage, NostrEvent, NostrFilter } from '@napplet/core'; export { NappletMessage, NostrEvent, NostrFilter, TOPICS, TopicKey, TopicValue } from '@napplet/core'; import { Theme } from '@napplet/nap/theme/types'; import { ResourceBytesItem, ResourceBytesRequest as ResourceBytesRequest$1, ResourceInfo } from '@napplet/nap/resource/types'; export { ResourceInfo } from '@napplet/nap/resource/types'; /** NIP-5D identity metadata associated with a registered iframe window. */ interface OriginIdentity { readonly dTag: string; readonly aggregateHash: string; } /** * Bidirectional registry mapping Window references to windowId strings. * Optionally stores NIP-5D identity metadata (dTag and aggregateHash) per window. * * @example * ```ts * import { originRegistry } from '@kehto/shell'; * * originRegistry.register(iframe.contentWindow, 'napp-1'); * const id = originRegistry.getWindowId(iframe.contentWindow); // 'napp-1' * ``` */ interface OriginRegistry { register(win: Window, windowId: string, identity?: OriginIdentity): void; unregister(windowId: string): void; getWindowId(win: Window): string | undefined; getIframeWindow(windowId: string): Window | null; getAllWindowIds(): string[]; getIdentity(win: Window): OriginIdentity | undefined; setEnvironment(win: Window, environment: ShellEnvironment): void; getEnvironment(win: Window): ShellEnvironment | undefined; getRegistrationId(win: Window): number | undefined; clear(): void; } /** Shell-wide iframe window registry singleton. */ declare const originRegistry: OriginRegistry; /** * ACL entry controlling what a napplet pubkey is permitted to do. * @example * ```ts * const entry: AclEntry = { * pubkey: 'abc123...', capabilities: ['relay:read', 'relay:write'], * blocked: false, stateQuota: 524288, * }; * ``` */ interface AclEntry { pubkey: string; capabilities: Capability[]; blocked: boolean; stateQuota?: number; } /** * Hook for relay pool operations. Host app provides relay connectivity. * @example * ```ts * const relayPoolHooks: RelayPoolHooks = { * getRelayPool: () => myPool, * trackSubscription: (key, cleanup) => subscriptions.set(key, cleanup), * // ... * }; * ``` */ interface RelayPoolHooks { /** Get the relay pool instance — returns null if no pool available. */ getRelayPool(): RelayPoolLike | null; /** Track a subscription for lifecycle management. */ trackSubscription(subKey: string, cleanup: () => void): void; /** Untrack and clean up a subscription. */ untrackSubscription(subKey: string): void; /** Open a scoped relay connection (NIP-29 groups). */ openScopedRelay(windowId: string, relayUrl: string, subId: string, filters: NostrFilter[], sourceWindow: Window): void; /** Close a scoped relay connection. */ closeScopedRelay(windowId: string): void; /** * Publish to a scoped relay. * * Async hosts settle the returned promise after transport acceptance. * Returns false if no active scoped relay or publication fails. */ publishToScopedRelay(windowId: string, event: NostrEvent): boolean | Promise; /** Select relay URLs for a given set of filters. */ selectRelayTier(filters: NostrFilter[]): string[]; } /** Minimal relay pool interface that the shell requires. */ interface RelayPoolLike { subscription(relayUrls: string[], filters: any): { subscribe(observer: (item: unknown) => void): { unsubscribe(): void; }; }; publish(relayUrls: string[], event: any): void | Promise; request(relayUrls: string[], filters: any): { subscribe(observer: { next: (event: unknown) => void; complete: () => void; error: () => void; }): { unsubscribe(): void; }; }; /** Optional exact/approximate count support that does not return event payloads. */ count?(relayUrls: string[], filters: NostrFilter[]): number | Promise; } /** Hook for relay configuration. */ interface RelayConfigHooks { /** Add a relay URL to a named tier. */ addRelay(tier: string, url: string): void; /** Remove a relay URL from a named tier. */ removeRelay(tier: string, url: string): void; /** Get the current relay configuration by tier. */ getRelayConfig(): { discovery: string[]; super: string[]; outbox: string[]; }; /** Get NIP-66 relay suggestions. */ getNip66Suggestions(): unknown; } /** Hook for window management. */ interface WindowManagerHooks { /** Create a new window. Returns the window ID or null on failure. */ createWindow(options: { title: string; class: string; iframeSrc?: string; }): string | null; } /** Hook for auth state and signing. */ interface AuthHooks { /** Get the current user's pubkey, or null if not logged in. */ getUserPubkey(): string | null; /** * Get the NIP-07 compatible signer, or null if unavailable. * * The optional window ID lets a shell bind its own signer policy to the * requesting napplet without imposing that policy in Kehto. * * @param windowId - Originating napplet window, when available */ getSigner(windowId?: string): any | null; } /** Hook for config. */ interface ConfigHooks { /** Get the napp update behavior policy. */ getNappUpdateBehavior(): 'auto-grant' | 'banner' | 'silent-reprompt'; } /** Hook for hotkey dispatch. */ interface HotkeyHooks { /** Execute a forwarded hotkey from a napp. */ executeHotkeyFromForward(event: { key: string; code: string; ctrlKey: boolean; altKey: boolean; shiftKey: boolean; metaKey: boolean; }): void; } /** Hook for worker relay (local cache). */ interface WorkerRelayHooks { /** Get the worker relay instance, or null if unavailable. */ getWorkerRelay(): WorkerRelayLike | null; } /** Minimal worker relay interface. */ interface WorkerRelayLike { event(event: NostrEvent): Promise; query(req: any): Promise; count?(req: any): Promise; } /** Hook for crypto verification. */ interface CryptoHooks { /** Verify a nostr event's signature. */ verifyEvent(event: NostrEvent): Promise; } /** Hook for DM sending (NIP-17 gift-wrap). */ interface DmHooks { /** Send a direct message to a recipient. */ sendDm(recipientPubkey: string, message: string): Promise<{ success: boolean; eventId?: string; error?: string; }>; } /** * Minimal upload backend the shell advertises (NAP-UPLOAD). The host wires the * concrete upload service into the runtime via `registerService('upload', …)`; * this hook is the presence/capability signal `buildShellCapabilities` reads to * decide whether to advertise `upload` to napplets. */ interface UploadBackendLike { /** Storage rails this backend can serve, e.g. `['nip96', 'blossom']`. */ readonly rails: readonly string[]; } /** Hook exposing a shell-mediated upload backend (NAP-UPLOAD). */ interface UploadHooks { /** Get the configured upload backend — returns null when none is available. */ getUploader(): UploadBackendLike | null; } /** * Minimal intent backend the shell advertises (NAP-INTENT). The host wires the * concrete intent service into the runtime via `registerService('intent', …)`; * this hook is the presence/capability signal `buildShellCapabilities` reads to * decide whether to advertise `intent` to napplets. NAP-INTENT is only * meaningful when the host can resolve archetypes to installed napplets and * create/focus their windows. */ interface IntentHooks { /** True when the host has an intent resolver wired and ready to dispatch. */ isAvailable(): boolean; } /** Minimal link backend the shell advertises (NAP-LINK). */ interface LinkHooks { /** True when the host has shell-mediated link opening wired. */ isAvailable(): boolean; } /** Minimal common backend the shell advertises (NAP-COMMON). */ interface CommonHooks { /** True when the host has shell-mediated common social helpers wired. */ isAvailable(): boolean; } /** Minimal lists backend the shell advertises (NAP-LISTS). */ interface ListsHooks { /** True when the host has shell-mediated NIP-51 list mutation helpers wired. */ isAvailable(): boolean; } /** Minimal serial backend the shell advertises (NAP-SERIAL). */ interface SerialHooks { /** True when the host has shell-mediated serial sessions wired. */ isAvailable(): boolean; } /** Minimal BLE backend the shell advertises (NAP-BLE). */ interface BleHooks { /** True when the host has shell-mediated BLE sessions wired. */ isAvailable(): boolean; } /** Minimal WebRTC backend the shell advertises (NAP-WEBRTC). */ interface WebrtcHooks { /** True when the host has shell-mediated WebRTC sessions wired. */ isAvailable(): boolean; } /** * Optional host override for the static shell.init capability handshake. * * Hosts normally advertise the default Kehto NAP surface. Development hosts may * temporarily suppress domains to simulate smaller or policy-constrained shell * environments while still using the production shell-ready path. */ interface CapabilityHooks { /** Bare capability domains to remove from the delivered environment. */ readonly disabledDomains?: readonly string[]; /** * Narrow the live environment for one trusted creation-time identity. * * The shell supplies copied, frozen live domains and named services. Returned * names are intersected with that exact availability set, so this hook cannot * add aliases, disabled entries, or unwired capabilities. * * @param identity - Identity assigned by the host when the iframe was created. * @param available - Immutable live domains and services before host policy. * @returns The requested subset for this identity. */ readonly resolveEnvironment?: (identity: OriginIdentity, available: Readonly<{ domains: readonly string[]; services: readonly string[]; }>) => Readonly<{ domains: readonly string[]; services: readonly string[]; }>; } /** * Event emitted on every ACL enforcement check. * @example * ```ts * hooks.onAclCheck = (event: AclCheckEvent) => { * console.log(`${event.decision}: ${event.capability} for ${event.identity.pubkey}`); * }; * ``` */ interface AclCheckEvent { /** The identity being checked. */ identity: { pubkey: string; dTag: string; hash: string; }; /** The capability being checked (e.g., 'relay:write', 'state:read'). */ capability: string; /** The enforcement decision. */ decision: 'allow' | 'deny'; /** The triggering message, if available. Accepts NIP-01 arrays or NIP-5D NappletMessage envelopes. */ message?: NappletMessage | unknown[]; } /** * Diagnostic payload reported when {@link ShellAdapter.onUnroutedMessage} fires — * i.e. when `ShellBridge.handleMessage` drops an incoming postMessage because it * cannot be routed to a registered napplet window. * * @example * ```ts * hooks.onUnroutedMessage = (info: UnroutedMessageInfo) => { * console.warn(`[shell] dropped ${info.type ?? ''} from ${info.origin}: ${info.reason}`); * }; * ``` */ interface UnroutedMessageInfo { /** * The dropped message's `type` field when `event.data` is an object with a * string `type`; `undefined` for malformed/non-envelope payloads. */ type?: string; /** The `MessageEvent.origin` of the dropped message. */ origin: string; /** * Why the message was dropped: * - `no-source-window` — the `MessageEvent` had no `source` window to identify the sender. * - `unregistered-window` — the source `Window` is not in `originRegistry` (the * sending iframe was never registered, or a `srcdoc` reload swapped its * `contentWindow` to a new object that no longer matches the registry key). */ reason: 'no-source-window' | 'unregistered-window'; } /** * Static capability set sent to napplet iframes through the shell.ready / * shell.init handshake. Optional runtime domain presence is injected before * authored code, while mandatory NAP-SHELL caches this environment for local * capability queries. * * NAP-SHELL needs only a truthful set of bare domain names. The shim answers * `shell.supports(domain)` locally from this snapshot after `shell.init`. */ interface ShellCapabilities { /** Immutable bare domain names the shell offers to this napplet. */ readonly domains: readonly string[]; } /** Immutable NAP-SHELL environment delivered to one trusted iframe session. */ interface ShellEnvironment { readonly capabilities: ShellCapabilities; readonly services: readonly string[]; } /** * All adapters that the shell requires from the host application. * @example * ```ts * const hooks: ShellAdapter = { * relayPool: myRelayPoolHooks, * relayConfig: myRelayConfigHooks, * windowManager: myWindowManagerHooks, * auth: myAuthHooks, * config: myConfigHooks, * hotkeys: myHotkeyHooks, * workerRelay: myWorkerRelayHooks, * crypto: myCryptoHooks, * }; * ``` */ interface ShellAdapter { relayPool: RelayPoolHooks; relayConfig: RelayConfigHooks; windowManager: WindowManagerHooks; auth: AuthHooks; config: ConfigHooks; hotkeys: HotkeyHooks; workerRelay: WorkerRelayHooks; crypto: CryptoHooks; dm?: DmHooks; /** * Optional shell-mediated upload backend (NAP-UPLOAD). When present, the shell * advertises the `upload` domain so napplets can call `window.napplet.upload`. * The host still registers the concrete service via `registerService('upload')`. */ upload?: UploadHooks; /** * Optional archetype intent dispatcher (NAP-INTENT). When present and * `isAvailable()` is true, the shell advertises the `intent` domain so * napplets can call `window.napplet.intent`. The host still registers the * concrete service via `registerService('intent')`. */ intent?: IntentHooks; /** * Optional shell-mediated link backend (NAP-LINK). When present and * `isAvailable()` is true, the shell advertises `link`. The host still * registers the concrete service via `registerService('link')`. */ link?: LinkHooks; /** * Optional shell-mediated common social backend (NAP-COMMON). When present * and `isAvailable()` is true, the shell advertises `common`. The host still * registers the concrete service via `registerService('common')`. */ common?: CommonHooks; /** * Optional shell-mediated NIP-51 list backend (NAP-LISTS). When present and * `isAvailable()` is true, the shell advertises `lists`. The host still * registers the concrete service via `registerService('lists')`. */ lists?: ListsHooks; /** * Optional shell-mediated serial backend (NAP-SERIAL). When present and * `isAvailable()` is true, the shell advertises `serial`. The host still * registers the concrete service via `registerService('serial')`. */ serial?: SerialHooks; /** * Optional shell-mediated BLE backend (NAP-BLE). When present and * `isAvailable()` is true, the shell advertises `ble`. The host still * registers the concrete service via `registerService('ble')`. */ ble?: BleHooks; /** * Optional shell-mediated WebRTC backend (NAP-WEBRTC). When present and * `isAvailable()` is true, the shell advertises `webrtc`. The host still * registers the concrete service via `registerService('webrtc')`. */ webrtc?: WebrtcHooks; /** * Optional capability advertisement override. Omitted by production hosts. */ capabilities?: CapabilityHooks; /** Called on every ACL enforcement check. Both allows and denials are reported. */ onAclCheck?: (event: AclCheckEvent) => void; /** * Called when `ShellBridge.handleMessage` drops an incoming postMessage because * it cannot be routed to a registered napplet window (no source window, or the * source `Window` is not in `originRegistry`). Observe-only — the message is * still dropped; this hook exists so otherwise-silent drops are diagnosable * (the FEED-02 / hyprgate#21 class of "a napplet's messages vanish" bug). */ onUnroutedMessage?: (info: UnroutedMessageInfo) => void; /** Called when aggregate hash verification fails (computed != declared). */ onHashMismatch?: (dTag: string, claimed: string, computed: string) => void; /** * Called at iframe creation for NIP-5D napplets. * Returns identity metadata for originRegistry.register(). Returning null * means "not NIP-5D / skip registration". */ onNip5dIframeCreate?: (windowId: string) => { dTag: string; aggregateHash: string; } | null; /** * Optional service extensions. Each key is a service name (e.g., 'audio', * 'notifications'). Napplets discover available services via kind 29010 * service discovery events. * * @example * ```ts * const hooks: ShellAdapter = { * // ... required adapters ... * services: { * audio: myAudioServiceHandler, * notifications: myNotificationServiceHandler, * }, * }; * ``` */ services?: ServiceRegistry; /** * Optional runtime behavior overrides — demo/debug use only. * Called lazily on each relevant operation (replay check, buffer push), * so changes take effect immediately without runtime recreation. */ getConfigOverrides?(): RuntimeConfigOverrides; } /** * Shell-side message bridge that handles NIP-5D communication with napplet iframes. * * The bridge acts as a browser adapter: it receives raw MessageEvents from * window.addEventListener('message', ...), extracts the source Window, resolves * it to a windowId via originRegistry, and delegates NIP-5D envelope messages * to the runtime engine. The shell.ready/shell.init capability handshake is * handled locally within the bridge and never forwarded to the runtime. * * @example * ```ts * import { createShellBridge } from '@kehto/shell'; * * const bridge = createShellBridge(hooks); * window.addEventListener('message', bridge.handleMessage); * ``` */ interface ShellBridge { /** * Handle an incoming postMessage from a napplet iframe. * * Only NIP-5D envelope objects (plain objects with a `.type` string) are * accepted. NIP-01 arrays and all other message shapes are silently dropped * (clean break — no legacy array fallback). * * shell.ready messages are handled locally: the bridge responds with shell.init * containing the capability set and registered service list. All other envelopes * are delegated to the runtime's NAP domain dispatch. * * @param event - The raw MessageEvent from window.addEventListener('message', ...) * @example * ```ts * window.addEventListener('message', bridge.handleMessage); * ``` */ handleMessage(event: MessageEvent): void; /** * Inject a shell-originated event into subscription delivery. Under NIP-5D, * shell-originated events are forwarded to napplets as inc.event envelope * messages. The runtime's injectEvent() handles the per-session routing. * * v1.10 hard-removed the v1.8 soft-rename compatibility branch for the * old `auth:identity-changed` topic. Use the canonical `identity:changed` * topic for identity-change pushes. * * @param topic - The event topic tag value. Forwarded exactly once. * @param payload - The event content * @example * ```ts * bridge.injectEvent('identity:changed', { pubkey: userPubkey }); * ``` */ injectEvent(topic: string, payload: unknown): void; /** * Destroy the bridge instance, cleaning up all internal state. * Persists manifest cache and clears all subscriptions, buffers, and registries. * Call when the shell is shutting down or the bridge is no longer needed. * * @example * ```ts * bridge.destroy(); * ``` */ destroy(): void; /** * Register a handler for consent requests on destructive signing kinds. * Called when a napplet requests signing for kinds 0, 3, 5, or 10002. * * @param handler - Callback receiving the consent request with a resolve function * @example * ```ts * bridge.registerConsentHandler((request) => { * const allowed = confirm(`Allow signing kind ${request.event.kind}?`); * request.resolve(allowed); * }); * ``` */ registerConsentHandler(handler: (request: ConsentRequest) => void): void; /** * Publish a theme update to each eligible napplet. * * Posts a `theme.changed` envelope (shell → napplet push) to every * live authenticated session whose frozen environment grants `theme` and * whose current ACL grants `theme:read`. Stale or ineligible sessions are * skipped before origin-registry delivery; recipient code is not trusted * to enforce host authorization. * * @param theme - The new theme payload to broadcast. * @example * ```ts * bridge.publishTheme({ * colors: { background: '#0a0a0a', text: '#e0e0e0', primary: '#7aa2f7' }, * title: 'Dark', * }); * ``` */ publishTheme(theme: Theme): void; /** * Publish the current shell-user identity to every loaded napplet. * * Posts an `identity.changed` envelope (shell → napplet push) with the * current user pubkey. An empty pubkey means no signer/user identity is * currently connected. This is distinct from NIP-5D napplet session identity, * which remains source-bound at iframe creation. * * @param pubkey - Current user's hex pubkey, or empty string when signed out. */ publishIdentityChanged(pubkey: string): void; /** * Access the underlying runtime instance for advanced use cases. * Provides direct access to the runtime's sessionRegistry, aclState, * and manifestCache. */ readonly runtime: Runtime; } declare function createShellBridge(hooks: ShellAdapter): ShellBridge; /** * The injected implementation must remain one self-contained function: the * renderer serializes it ahead of verified artifact scripts without changing * the signed artifact bytes. Splitting it into imported helpers would leave * unresolved closures in the generated `srcdoc`. * * aislop-ignore-file complexity/file-too-large complexity/function-too-long */ /** * Options for rendering the host-owned NIP-5D `window.napplet` namespace prelude. * * @example * ```ts * const options: NappletNamespacePreludeOptions = { * domains: ['shell', 'relay', 'identity'], * }; * ``` */ interface NappletNamespacePreludeOptions { /** * Optional bare NAP domain names the shell exposes to this napplet. * Mandatory `shell` is injected even when omitted here. */ domains: readonly string[]; } /** * Render the host-owned NIP-5D bootstrap that exposes available NAP domains * under `window.napplet` before napplet artifact code runs. * * @param options - Domain availability to inject. * @returns An inline script tag suitable for `srcdoc` prelude insertion. * @example * ```ts * const prelude = renderNappletNamespacePrelude({ * domains: ['shell', 'relay', 'identity'], * }); * ``` */ declare function renderNappletNamespacePrelude(options: NappletNamespacePreludeOptions): string; /** * Insert the NIP-5D namespace prelude into HTML before authored scripts. * * @param html - Verified napplet artifact HTML. * @param options - Domain availability to inject. * @returns HTML with the prelude inside `` when possible. * @example * ```ts * const srcdoc = injectNappletNamespacePrelude(verifiedHtml, { * domains: ['shell', 'relay', 'identity'], * }); * iframe.srcdoc = srcdoc; * ``` */ declare function injectNappletNamespacePrelude(html: string, options: NappletNamespacePreludeOptions): string; /** * Manifest cache — persists verified NIP-5A aggregate hashes per napplet identity. */ /** * A cached manifest entry for a verified napplet build. * @example * ```ts * const entry: ManifestCacheEntry = { * pubkey: 'abc123...', dTag: '3chat', * aggregateHash: 'deadbeef', verifiedAt: Date.now(), * }; * ``` */ interface ManifestCacheEntry { pubkey: string; dTag: string; aggregateHash: string; verifiedAt: number; } /** * Cache for verified napplet manifest entries. Persists to localStorage. * Used to detect napplet updates (aggregateHash changes) across sessions. * * @example * ```ts * import { manifestCache } from '@kehto/shell'; * * manifestCache.set({ pubkey: 'abc...', dTag: 'chat', aggregateHash: 'dead', verifiedAt: Date.now() }); * const entry = manifestCache.get('abc...', 'chat'); * ``` */ interface ManifestCache { get(pubkey: string, dTag: string): ManifestCacheEntry | undefined; set(entry: ManifestCacheEntry): void; has(pubkey: string, dTag: string, hash: string): boolean; remove(pubkey: string, dTag: string): void; load(): void; persist(): void; clear(): void; } /** Shell-wide manifest verification cache singleton. */ declare const manifestCache: ManifestCache; /** * ACL store — manages capability grants, revocations, and blocks for napp identities. * Persists to localStorage and uses a permissive default policy (all capabilities granted). * * @example * ```ts * import { aclStore } from '@kehto/shell'; * * aclStore.grant(pubkey, dTag, hash, 'relay:read'); * const allowed = aclStore.check(pubkey, dTag, hash, 'relay:read'); // true * ``` */ interface AclStore { check(pubkey: string, dTag: string, aggregateHash: string, capability: Capability): boolean; grant(pubkey: string, dTag: string, aggregateHash: string, capability: Capability): void; revoke(pubkey: string, dTag: string, aggregateHash: string, capability: Capability): void; block(pubkey: string, dTag: string, aggregateHash: string): void; unblock(pubkey: string, dTag: string, aggregateHash: string): void; isBlocked(pubkey: string, dTag: string, aggregateHash: string): boolean; getEntry(pubkey: string, dTag: string, aggregateHash: string): AclEntry | undefined; getAllEntries(): AclEntry[]; persist(): void; load(): void; getStateQuota(pubkey: string, dTag: string, aggregateHash: string): number; clear(): void; } /** Shell ACL store singleton. */ declare const aclStore: AclStore; /** * audio-manager.ts — Shell-side registry of active audio sources. * * Tracks which windows are producing audio. UI components read the registry * reactively via the version counter and CustomEvent pattern. */ /** * An active audio source registered by a napplet. * @example * ```ts * const source: AudioSource = { * windowId: 'win-1', nappletClass: 'music-player', * title: 'Now Playing', muted: false, * }; * ``` */ interface AudioSource { windowId: string; nappletClass: string; title: string; muted: boolean; } /** * Registry of active audio sources across all napplet windows. * Emits 'napplet:audio-changed' CustomEvents when the registry changes. * * @example * ```ts * import { audioManager } from '@kehto/shell'; * * audioManager.register('win-1', 'music', 'My Song'); * audioManager.mute('win-1', true); * ``` */ interface AudioManager { register(windowId: string, nappletClass: string, title: string): void; unregister(windowId: string): void; updateState(windowId: string, update: { title?: string; }): void; mute(windowId: string, muted: boolean): void; has(windowId: string): boolean; get(windowId: string): AudioSource | undefined; getSources(): Map; readonly version: number; readonly count: number; clear(): void; } /** Shell-wide audio source registry singleton. */ declare const audioManager: AudioManager; /** * SessionRegistry — windowId to verified napplet pubkey bidirectional mapping. * * After a successful AUTH handshake, the ShellBridge registers the napplet's * verified pubkey here. Both mappings are kept in sync. */ /** * Bidirectional registry mapping windowIds to verified napplet pubkeys. * Maintained by ShellBridge after successful AUTH handshakes. * * @example * ```ts * import { sessionRegistry } from '@kehto/shell'; * * const pubkey = sessionRegistry.getPubkey('win-1'); * const entry = pubkey ? sessionRegistry.getEntry(pubkey) : undefined; * ``` */ interface SessionRegistry { register(windowId: string, entry: SessionEntry): void; unregister(windowId: string): void; getPubkey(windowId: string): string | undefined; getEntry(pubkey: string): SessionEntry | undefined; getWindowId(pubkey: string): string | undefined; isRegistered(windowId: string): boolean; getAllEntries(): SessionEntry[]; setPendingUpdate(windowId: string, update: PendingUpdate): void; getPendingUpdate(windowId: string): PendingUpdate | undefined; clearPendingUpdate(windowId: string): void; clear(): void; } /** Shell-wide verified napplet session registry singleton. */ declare const sessionRegistry: SessionRegistry; /** @deprecated Use sessionRegistry. Will be removed in v0.9.0. */ declare const nappKeyRegistry: SessionRegistry; /** * hooks-adapter.ts — Converts ShellAdapter (browser-facing) to RuntimeAdapter (environment-agnostic). * * The adapter bridges the gap between the shell's browser-oriented ShellAdapter interfaces * (Window references, localStorage, postMessage) and the runtime's abstract RuntimeAdapter * (windowId strings, persistence interfaces, sendToNapplet callbacks). */ /** * Browser-specific singletons that the adapter bridges to the runtime. * These use browser APIs (Window, localStorage, postMessage, CustomEvent) * that the runtime cannot access directly. */ interface BrowserDeps { originRegistry: typeof originRegistry; manifestCache: typeof manifestCache; aclStore: typeof aclStore; audioManager: typeof audioManager; nappKeyRegistry: typeof sessionRegistry; } /** * Convert ShellAdapter (browser-facing) into RuntimeAdapter (environment-agnostic). * * The adapter is the single translation layer between browser APIs and the * runtime's abstract interfaces. It: * - Converts Window references to windowId strings via originRegistry * - Wraps localStorage-backed singletons into persistence interfaces * - Translates relay pool API shapes (Observable → callback) * * @param shellHooks - The browser-oriented ShellAdapter provided by the host app * @param deps - Browser-specific singletons (originRegistry, aclStore, etc.) * @returns RuntimeAdapter suitable for createRuntime() * * @example * ```ts * const runtimeHooks = adaptHooks(shellHooks, { * originRegistry, manifestCache, aclStore, audioManager, nappKeyRegistry, * }); * const runtime = createRuntime(runtimeHooks); * ``` */ declare function adaptHooks(shellHooks: ShellAdapter, deps: BrowserDeps): RuntimeAdapter; /** * Build the shell's immutable live capability snapshot from adapter wiring. * * @param hooks - The ShellAdapter provided by the host app. * @returns Domain-only ShellCapabilities for the current live wiring. * @example * ```ts * const caps = buildShellCapabilities(hooks); * // caps.domains => ['relay', 'identity', 'storage', 'inc', 'theme', 'keys', 'media', 'notify'] * ``` */ declare function buildShellCapabilities(hooks: ShellAdapter): ShellCapabilities; /** * Resolve a trusted creation identity's immutable NAP-SHELL environment. * * This host-adapter API is intentionally not part of `window.napplet`: it * bounds host policy to current runtime wiring before `shell.init` crosses into * an untrusted iframe. * * @param hooks - Shell host wiring and optional per-identity grant policy. * @param identity - The source's creation-time identity. * @returns A fresh immutable environment whose entries are exact live subsets. */ declare function resolveShellEnvironment(hooks: ShellAdapter, identity: OriginIdentity): ShellEnvironment; /** * identity-proxy.ts — Shell-side per-domain proxy for identity.* envelopes. * * Establishes the canonical proxy shape for @kehto/shell (Plan 12-11): each * per-domain proxy exposes a `dispatch` method that delegates napplet→shell * requests to the runtime. * * By default, `createShellBridge()` does NOT compose this proxy into its * dispatch path — the runtime already owns identity.* dispatch per Plan * 12-03 (see @kehto/services identity-service). This module exists as an * optional composition point for host apps that want to intercept or * augment identity dispatch (e.g. custom logging, sandboxed rewrites, test * doubles). * * Identity changes are deliberately not emitted through this proxy. Hosts * must use `ShellBridge.publishIdentityChanged()`, which enforces the live * session, granted-domain, and current recipient-capability checks. */ /** * Minimal origin-registry contract used by per-domain proxies. * * Accepts the `@kehto/shell` singleton `originRegistry` as well as any test * double with a matching `getIframeWindow` method. */ interface ProxyOriginRegistry { /** Resolve a registered napplet windowId to its iframe Window, or null. */ getIframeWindow(windowId: string): Window | null; } /** * Dependencies for `createIdentityProxy`. * * @example * ```ts * const proxy = createIdentityProxy({ * runtime: shellBridge.runtime, * originRegistry, * }); * ``` */ interface IdentityProxyDeps { /** The runtime engine that owns identity.* dispatch (Plan 12-03). */ runtime: Runtime; /** Origin registry for resolving windowId → iframe Window. */ originRegistry: ProxyOriginRegistry; } /** * Per-domain proxy for `identity.*` envelopes. * * `dispatch` routes napplet→shell requests into the runtime. The deprecated * `emit` member remains only as a fail-closed compatibility trap. */ interface IdentityProxy { /** * Route a napplet-originated identity.* envelope into the runtime. * * Delegation only — the runtime already owns identity.* dispatch after * Plan 12-03. Override by wrapping or replacing this method. * * @param windowId - The source napplet's windowId * @param envelope - The NIP-5D NappletMessage envelope */ dispatch(windowId: string, envelope: NappletMessage): void; /** * Direct identity delivery is prohibited. * * @deprecated Use `ShellBridge.publishIdentityChanged()` so delivery is * filtered by live session, granted domain, and current ACL. */ emit(windowId: string, envelope: NappletMessage): void; } /** * Factory for the canonical identity-domain proxy. * * @param deps - Runtime + origin registry * @returns An {@link IdentityProxy} ready to route identity.* envelopes * @example * ```ts * import { createIdentityProxy, originRegistry, createShellBridge } from '@kehto/shell'; * * const bridge = createShellBridge(hooks); * const identityProxy = createIdentityProxy({ * runtime: bridge.runtime, * originRegistry, * }); * * // Optional composition: intercept napplet->shell identity requests * const originalDispatch = identityProxy.dispatch; * identityProxy.dispatch = (windowId, envelope) => { * console.log('identity dispatch', windowId, envelope.type); * originalDispatch(windowId, envelope); * }; * ``` */ declare function createIdentityProxy(deps: IdentityProxyDeps): IdentityProxy; /** * theme-proxy.ts — Shell-side per-domain proxy for theme.* envelopes. * * Establishes the shell-side shape that Phase 13 composes into. Phase 13 is * expected to add `theme-service.ts` (runtime) + the shell-side `theme.set` * API that emits `theme.changed` push envelopes to registered napplets; * this proxy is the canonical seam those pieces plug into. * * `dispatch(windowId, envelope)` routes napplet→shell `theme.get` into the * runtime. Theme changes must use `ShellBridge.publishTheme()` so the host * projection enforces live-session, granted-domain, and current-ACL checks. * * By default `createShellBridge()` does NOT compose this proxy into its * dispatch path — the runtime owns theme.* dispatch. This module is an * optional composition point for host apps or Phase 13 wiring. */ /** * Dependencies for `createThemeProxy`. * * @example * ```ts * const proxy = createThemeProxy({ * runtime: shellBridge.runtime, * originRegistry, * }); * ``` */ interface ThemeProxyDeps { /** The runtime engine that will own theme.* dispatch (Phase 13). */ runtime: Runtime; /** Origin registry for resolving windowId → iframe Window. */ originRegistry: ProxyOriginRegistry; } /** * Per-domain proxy for `theme.*` envelopes. * * `dispatch` routes napplet→shell requests into the runtime. The deprecated * `emit` member remains only as a fail-closed compatibility trap. */ interface ThemeProxy { /** * Route a napplet-originated theme.* envelope (e.g. `theme.get`) into * the runtime. * * @param windowId - The source napplet's windowId * @param envelope - The NIP-5D NappletMessage envelope */ dispatch(windowId: string, envelope: NappletMessage): void; /** * Direct theme delivery is prohibited. * * @deprecated Use `ShellBridge.publishTheme()` so delivery is filtered by * live session, granted domain, and current ACL. */ emit(windowId: string, envelope: NappletMessage): void; } /** * Factory for the canonical theme-domain proxy. * * @param deps - Runtime + origin registry * @returns A {@link ThemeProxy} ready to route theme.* envelopes * @example * ```ts * import { createThemeProxy, originRegistry } from '@kehto/shell'; * * const themeProxy = createThemeProxy({ * runtime, * originRegistry, * }); * themeProxy.dispatch(windowId, { type: 'theme.get', id: requestId }); * ``` */ declare function createThemeProxy(deps: ThemeProxyDeps): ThemeProxy; /** * keys-proxy.ts — Shell-side per-domain proxy for keys.* envelopes. * * The runtime already dispatches napplet-originated `keys.*` (forward, * registerAction, unregisterAction) to the keys service. This proxy is the * shell-side composition point for host apps that want to observe requests or * inject shell-originated keys envelopes such as `keys.action` and * `keys.bindings`. * * Shape mirrors identity-proxy (Plan 12-11): * * - `dispatch(windowId, envelope)` routes napplet→shell keys requests * into the runtime. * - `emit(windowId, envelope)` posts shell→napplet pushes (`keys.action`, * `keys.bindings`, `keys.registerAction.result`) through the origin * registry. * * By default `createShellBridge()` does NOT compose this proxy into its * dispatch path — the runtime owns keys.* dispatch. This module is an * optional composition point for host apps (e.g. global hotkey UIs). */ /** * Dependencies for `createKeysProxy`. * * @example * ```ts * const proxy = createKeysProxy({ * runtime: shellBridge.runtime, * originRegistry, * }); * ``` */ interface KeysProxyDeps { /** The runtime engine that owns keys.* dispatch (Plan 12-05). */ runtime: Runtime; /** Origin registry for resolving windowId → iframe Window. */ originRegistry: ProxyOriginRegistry; } /** * Per-domain proxy for `keys.*` envelopes. * * Shape: `dispatch` routes napplet→shell requests into the runtime; `emit` * pushes shell→napplet envelopes through the iframe's Window. */ interface KeysProxy { /** * Route a napplet-originated keys.* envelope (e.g. `keys.forward`, * `keys.registerAction`) into the runtime. * * @param windowId - The source napplet's windowId * @param envelope - The NIP-5D NappletMessage envelope */ dispatch(windowId: string, envelope: NappletMessage): void; /** * Push a shell-initiated keys-domain envelope (e.g. `keys.action`, * `keys.bindings`) into a napplet iframe. * * No-op when the originRegistry cannot resolve the windowId (unknown or * unregistered napplet). Never throws. * * @param windowId - The target napplet's windowId * @param envelope - The NIP-5D NappletMessage envelope to deliver */ emit(windowId: string, envelope: NappletMessage): void; } /** * Factory for the canonical keys-domain proxy. * * @param deps - Runtime + origin registry * @returns A {@link KeysProxy} ready to route keys.* envelopes * @example * ```ts * import { createKeysProxy, originRegistry, createShellBridge } from '@kehto/shell'; * * const bridge = createShellBridge(hooks); * const keysProxy = createKeysProxy({ * runtime: bridge.runtime, * originRegistry, * }); * * // Host-app-initiated action trigger: * keysProxy.emit('win-editor', { type: 'keys.action', actionId: 'editor.save' }); * ``` */ declare function createKeysProxy(deps: KeysProxyDeps): KeysProxy; /** * media-proxy.ts — Shell-side per-domain proxy for media.* envelopes. * * Establishes the shell-side composition seam for `@napplet/nap/media` * session-control envelopes. Shape mirrors identity-proxy (Plan 12-11): * * - `dispatch(windowId, envelope)` routes napplet→shell media requests * (`media.session.create`, `media.session.update`, `media.session.destroy`, * `media.state`, `media.capabilities`) into the runtime (Plan 12-06). * - `emit(windowId, envelope)` posts shell→napplet pushes (`media.command`, * `media.controls`, `media.session.create.result`) through the origin * registry. * * By default `createShellBridge()` does NOT compose this proxy into its * dispatch path — the runtime owns media.* dispatch. This module is an * optional composition point for host apps (e.g. shell-rendered playback * UIs that want to send `media.command` pushes). */ /** * Dependencies for `createMediaProxy`. * * @example * ```ts * const proxy = createMediaProxy({ * runtime: shellBridge.runtime, * originRegistry, * }); * ``` */ interface MediaProxyDeps { /** The runtime engine that owns media.* dispatch (Plan 12-06). */ runtime: Runtime; /** Origin registry for resolving windowId → iframe Window. */ originRegistry: ProxyOriginRegistry; } /** * Per-domain proxy for `media.*` envelopes. * * Shape: `dispatch` routes napplet→shell requests into the runtime; `emit` * pushes shell→napplet envelopes through the iframe's Window. */ interface MediaProxy { /** * Route a napplet-originated media.* envelope into the runtime. * * @param windowId - The source napplet's windowId * @param envelope - The NIP-5D NappletMessage envelope */ dispatch(windowId: string, envelope: NappletMessage): void; /** * Push a shell-initiated media-domain envelope (e.g. `media.command`, * `media.controls`) into a napplet iframe. * * No-op when the originRegistry cannot resolve the windowId (unknown or * unregistered napplet). Never throws. * * @param windowId - The target napplet's windowId * @param envelope - The NIP-5D NappletMessage envelope to deliver */ emit(windowId: string, envelope: NappletMessage): void; } /** * Factory for the canonical media-domain proxy. * * @param deps - Runtime + origin registry * @returns A {@link MediaProxy} ready to route media.* envelopes * @example * ```ts * import { createMediaProxy, originRegistry, createShellBridge } from '@kehto/shell'; * * const bridge = createShellBridge(hooks); * const mediaProxy = createMediaProxy({ * runtime: bridge.runtime, * originRegistry, * }); * * // Shell-UI-initiated media command: * mediaProxy.emit('win-player', { * type: 'media.command', * sessionId: 's1', * action: 'seek', * value: 120, * }); * ``` */ declare function createMediaProxy(deps: MediaProxyDeps): MediaProxy; /** * notify-proxy.ts — Shell-side per-domain proxy for notify.* envelopes. * * Establishes the shell-side composition seam for `@napplet/nap/notify` * notification envelopes. Shape mirrors identity-proxy (Plan 12-11): * * - `dispatch(windowId, envelope)` routes napplet→shell notify requests * (`notify.send`, `notify.dismiss`, `notify.badge`, * `notify.channel.register`, `notify.permission.request`) into the * runtime (Plan 12-07). * - `emit(windowId, envelope)` posts shell→napplet pushes * (`notify.send.result`, `notify.permission.result`, `notify.action`, * `notify.clicked`, `notify.dismissed`, `notify.controls`) through the * origin registry. * * By default `createShellBridge()` does NOT compose this proxy into its * dispatch path — the runtime owns notify.* dispatch. This module is an * optional composition point for host apps (e.g. custom notification UIs * that need to emit `notify.clicked` / `notify.action` pushes). */ /** * Dependencies for `createNotifyProxy`. * * @example * ```ts * const proxy = createNotifyProxy({ * runtime: shellBridge.runtime, * originRegistry, * }); * ``` */ interface NotifyProxyDeps { /** The runtime engine that owns notify.* dispatch (Plan 12-07). */ runtime: Runtime; /** Origin registry for resolving windowId → iframe Window. */ originRegistry: ProxyOriginRegistry; } /** * Per-domain proxy for `notify.*` envelopes. * * Shape: `dispatch` routes napplet→shell requests into the runtime; `emit` * pushes shell→napplet envelopes through the iframe's Window. */ interface NotifyProxy { /** * Route a napplet-originated notify.* envelope into the runtime. * * @param windowId - The source napplet's windowId * @param envelope - The NIP-5D NappletMessage envelope */ dispatch(windowId: string, envelope: NappletMessage): void; /** * Push a shell-initiated notify-domain envelope (e.g. `notify.action`, * `notify.clicked`) into a napplet iframe. * * No-op when the originRegistry cannot resolve the windowId (unknown or * unregistered napplet). Never throws. * * @param windowId - The target napplet's windowId * @param envelope - The NIP-5D NappletMessage envelope to deliver */ emit(windowId: string, envelope: NappletMessage): void; } /** * Factory for the canonical notify-domain proxy. * * @param deps - Runtime + origin registry * @returns A {@link NotifyProxy} ready to route notify.* envelopes * @example * ```ts * import { createNotifyProxy, originRegistry, createShellBridge } from '@kehto/shell'; * * const bridge = createShellBridge(hooks); * const notifyProxy = createNotifyProxy({ * runtime: bridge.runtime, * originRegistry, * }); * * // Shell-UI notifies napplet that user clicked its toast: * notifyProxy.emit('win-chat', { * type: 'notify.clicked', * notificationId: 'shell-42', * }); * ``` */ declare function createNotifyProxy(deps: NotifyProxyDeps): NotifyProxy; /** * @file internal-resource.ts * * Kehto-internal shell-side resource wire types. Per PROJECT.md Decision #31, * this is NOT a staging-ground duplicate of upstream `@napplet/nap/resource`. * Canonical resource info, per-resource request, and result-item shapes are * imported from the published package. The remaining local envelopes retain * Kehto's legacy compatibility fields (`requestId`, `bodyBase64`, status, and * headers) alongside the current `id`, `blob`, and `mime` fields. * * `resource-service.ts` now emits both legacy single-fetch compatibility * fields and the current upstream-compatible fields. * * Resource protocol: * Inbound: resource.info, resource.bytes, resource.bytesMany, resource.cancel * Outbound: resource.info.result, resource.info.error, * resource.bytes.result, resource.bytes.error, * resource.bytesMany.result, resource.bytesMany.error */ /** * Unique id for correlating a `resource.bytes` request to its later result / * error / cancel envelope. */ type ResourceRequestId = string; /** Inbound: napplet asks for advisory resource policy and scheme support. */ interface ResourceInfoRequest { type: 'resource.info'; id: ResourceRequestId; } /** * Inbound: napplet requests bytes from an origin. Shell consults * getConnectGrants(dTag, aggregateHash) before proxying; ungranted origins * receive a `denied` error (RESOURCE-01 H-03 prevention). */ interface ResourceBytesRequest { type: 'resource.bytes'; /** Current NAP-RESOURCE correlation ID. */ id?: ResourceRequestId; /** Legacy Kehto correlation ID. */ requestId?: ResourceRequestId; url: string; /** Advisory Blossom server locations. Ignored for other schemes. */ servers?: readonly string[]; /** Optional subset of fetch init (method, headers). Body bytes are shell-proxy-internal. */ init?: { method?: string; headers?: Readonly>; }; } /** * Inbound: napplet cancels a previously-issued bytes request. Shell correlates * to the in-flight request by requestId and emits a `resource.bytes.error` * with `code: 'canceled'`. */ interface ResourceCancelRequest { type: 'resource.cancel'; /** Current NAP-RESOURCE correlation ID. */ id?: ResourceRequestId; /** Legacy Kehto correlation ID. */ requestId?: ResourceRequestId; } /** * Inbound: napplet requests many resource URLs in one envelope. */ interface ResourceBytesManyRequest { type: 'resource.bytesMany'; id: ResourceRequestId; requests: readonly ResourceBytesRequest$1[]; } /** * Outbound: successful fetch result. */ interface ResourceBytesResult { type: 'resource.bytes.result'; /** Current NAP-RESOURCE correlation ID. */ id: ResourceRequestId; /** Legacy Kehto correlation ID. */ requestId: ResourceRequestId; /** Current NAP-RESOURCE Blob payload. */ blob?: Blob; /** Current NAP-RESOURCE runtime-classified MIME. */ mime?: string; status: number; headers: Readonly>; /** Raw response bytes, base64-encoded for the postMessage wire. */ bodyBase64: string; } /** * Canonical typed-error codes for RESOURCE-03 cancel correlation + H-03 * ungranted-origin refusal. */ type ResourceErrorCode = 'denied' | 'canceled' | 'network-error' | 'invalid-url'; /** * Outbound: error result — used for both grant-refusal (RESOURCE-01) and * cancel-correlation (RESOURCE-03) cases. */ interface ResourceBytesError { type: 'resource.bytes.error'; /** Current NAP-RESOURCE correlation ID. */ id: ResourceRequestId; /** Legacy Kehto correlation ID. */ requestId: ResourceRequestId; /** Current NAP-RESOURCE error field. */ error?: string; code: ResourceErrorCode; message: string; } /** * Current NAP-RESOURCE bulk result item. */ type ResourceBytesManyItem = ResourceBytesItem; /** * Outbound: ordered bulk fetch result. */ interface ResourceBytesManyResult { type: 'resource.bytesMany.result'; id: ResourceRequestId; items: readonly ResourceBytesManyItem[]; } /** * Outbound: top-level bulk request failure. */ interface ResourceBytesManyError { type: 'resource.bytesMany.error'; id: ResourceRequestId; error: string; message?: string; } /** Outbound: advisory resource policy and scheme support. */ interface ResourceInfoResult { type: 'resource.info.result'; id: ResourceRequestId; info: ResourceInfo; } /** Outbound: resource info could not be resolved. */ interface ResourceInfoError { type: 'resource.info.error'; id: ResourceRequestId; error: string; message?: string; } /** * Union of all inbound resource wire messages (napplet -> shell). */ type ResourceInbound = ResourceInfoRequest | ResourceBytesRequest | ResourceBytesManyRequest | ResourceCancelRequest; /** * Union of all outbound resource wire messages (shell -> napplet). */ type ResourceOutbound = ResourceInfoResult | ResourceInfoError | ResourceBytesResult | ResourceBytesError | ResourceBytesManyResult | ResourceBytesManyError; export { type AclCheckEvent, type AclEntry, type AclStore, type AudioManager, type AudioSource, type AuthHooks, type BleHooks, type BrowserDeps, type CapabilityHooks, type CommonHooks, type ConfigHooks, type CryptoHooks, type DmHooks, type HotkeyHooks, type IdentityProxy, type IdentityProxyDeps, type IntentHooks, type KeysProxy, type KeysProxyDeps, type LinkHooks, type ListsHooks, type ManifestCache, type ManifestCacheEntry, type MediaProxy, type MediaProxyDeps, type NappletNamespacePreludeOptions, type NotifyProxy, type NotifyProxyDeps, type OriginIdentity, type OriginRegistry, type ProxyOriginRegistry, type RelayConfigHooks, type RelayPoolHooks, type RelayPoolLike, type ResourceBytesError, type ResourceBytesManyError, type ResourceBytesManyItem, type ResourceBytesManyRequest, type ResourceBytesManyResult, type ResourceBytesRequest, type ResourceBytesResult, type ResourceCancelRequest, type ResourceErrorCode, type ResourceInbound, type ResourceInfoError, type ResourceInfoRequest, type ResourceInfoResult, type ResourceOutbound, type ResourceRequestId, type SerialHooks, type SessionRegistry, type ShellAdapter, type ShellBridge, type ShellCapabilities, type ShellEnvironment, type ThemeProxy, type ThemeProxyDeps, type UnroutedMessageInfo, type UploadBackendLike, type UploadHooks, type WebrtcHooks, type WindowManagerHooks, type WorkerRelayHooks, type WorkerRelayLike, adaptHooks, audioManager, buildShellCapabilities, createIdentityProxy, createKeysProxy, createMediaProxy, createNotifyProxy, createShellBridge, createThemeProxy, injectNappletNamespacePrelude, manifestCache, nappKeyRegistry, originRegistry, renderNappletNamespacePrelude, resolveShellEnvironment, sessionRegistry };