@kehto/paja 0.19.0 → 0.20.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 +63 -35
- package/dist/browser-host.js +2281 -1057
- package/dist/browser-host.js.map +1 -1
- package/dist/{chunk-GY5ET3PC.js → chunk-6WEO5D3Z.js} +256 -25
- package/dist/chunk-6WEO5D3Z.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +88 -14
- package/dist/index.js +9 -1
- package/package.json +6 -6
- package/dist/chunk-GY5ET3PC.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config-file.ts","../src/simulation.ts","../src/options.ts","../src/browser-console-panel.ts","../src/host-page.ts","../src/runtime-resolver.ts","../src/browser-resource.ts","../src/parity.ts","../src/readiness.ts","../src/local-target.ts","../src/installed-napplet-catalog.ts","../src/browser-intent-controller.ts","../src/server.ts","../src/target-cors.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nimport type { PajaRawOptions } from './options.js';\nimport type { PajaSimulationRawOptions } from './simulation.js';\nimport { PajaSimulationError } from './simulation.js';\n\n/**\n * Load a JSON Paja config file from disk.\n *\n * @param configPath - Path to a JSON config file.\n * @returns Raw options parsed from the config file.\n */\nexport function loadPajaConfigFile(configPath: string): PajaRawOptions {\n const resolved = resolve(configPath);\n let parsed: unknown;\n try {\n parsed = JSON.parse(readFileSync(resolved, 'utf8'));\n } catch (error) {\n const detail = error instanceof Error ? error.message : String(error);\n throw new PajaSimulationError(`Unable to read --config \"${configPath}\": ${detail}`);\n }\n\n if (!isRecord(parsed)) {\n throw new PajaSimulationError(`Invalid --config \"${configPath}\": expected a JSON object.`);\n }\n\n return parsed as PajaRawOptions;\n}\n\n/**\n * Resolve raw options by merging an optional config file with explicit values.\n *\n * @param raw - CLI/config options, optionally including `configPath`.\n * @returns Raw options with config-file defaults applied.\n */\nexport function resolvePajaRawOptions(raw: PajaRawOptions): PajaRawOptions {\n if (!raw.configPath?.trim()) return raw;\n return mergePajaRawOptions(loadPajaConfigFile(raw.configPath), raw);\n}\n\n/**\n * Merge two raw Paja option objects, with override values taking precedence.\n *\n * @param base - Base options, usually from a config file.\n * @param override - Explicit options, usually from CLI args.\n * @returns Merged raw options.\n */\nexport function mergePajaRawOptions(\n base: PajaRawOptions,\n override: PajaRawOptions,\n): PajaRawOptions {\n return {\n targetUrl: override.targetUrl ?? base.targetUrl,\n command: override.command ?? base.command,\n host: override.host ?? base.host,\n port: override.port ?? base.port,\n readyTimeoutMs: override.readyTimeoutMs ?? base.readyTimeoutMs,\n configPath: override.configPath ?? base.configPath,\n simulation: mergeSimulationOptions(base.simulation, override.simulation),\n };\n}\n\nfunction mergeSimulationOptions(\n base: PajaSimulationRawOptions | undefined,\n override: PajaSimulationRawOptions | undefined,\n): PajaSimulationRawOptions | undefined {\n if (!base) return override;\n if (!override) return base;\n\n return {\n capabilities: {\n ...base.capabilities,\n ...override.capabilities,\n domains: {\n ...base.capabilities?.domains,\n ...override.capabilities?.domains,\n },\n },\n acl: { ...base.acl, ...override.acl },\n firewall: { ...base.firewall, ...override.firewall },\n identity: { ...base.identity, ...override.identity },\n relay: { ...base.relay, ...override.relay },\n storage: { ...base.storage, ...override.storage },\n cache: { ...base.cache, ...override.cache },\n upload: { ...base.upload, ...override.upload },\n media: { ...base.media, ...override.media },\n notifications: { ...base.notifications, ...override.notifications },\n config: {\n ...base.config,\n ...override.config,\n values: { ...base.config?.values, ...override.config?.values },\n },\n theme: {\n ...base.theme,\n ...override.theme,\n values: { ...base.theme?.values, ...override.theme?.values },\n },\n intent: { ...base.intent, ...override.intent },\n cvm: { ...base.cvm, ...override.cvm },\n };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n","/** JSON primitive accepted in Paja simulation config values. */\nexport type JsonPrimitive = string | number | boolean | null;\n/** JSON value accepted in Paja simulation config values. */\nexport type JsonValue = JsonPrimitive | JsonValue[] | { readonly [key: string]: JsonValue };\n/** JSON object used for Paja simulation fixtures and config/theme values. */\nexport type JsonRecord = Record<string, JsonValue>;\n\n/** Capability domain names Paja can advertise or disable. */\nexport type PajaCapabilityDomain =\n | 'relay'\n | 'outbox'\n | 'storage'\n | 'identity'\n | 'keys'\n | 'config'\n | 'resource'\n | 'theme'\n | 'notify'\n | 'media'\n | 'upload'\n | 'intent'\n | 'count'\n | 'link'\n | 'common'\n | 'lists'\n | 'serial'\n | 'ble'\n | 'webrtc'\n | 'dm'\n | 'fs'\n | 'cvm'\n | 'inc';\n\n/** Raw simulation controls accepted from CLI args or JSON config. */\nexport interface PajaSimulationRawOptions {\n /** Per-domain capability overrides. */\n readonly capabilities?: {\n readonly domains?: Partial<Record<PajaCapabilityDomain, boolean>>;\n };\n /** ACL behavior mode for simulated services. */\n readonly acl?: {\n readonly mode?: 'allow' | 'deny';\n };\n readonly firewall?: {\n readonly mode?: 'allow' | 'deny' | 'observe';\n };\n readonly identity?: {\n readonly mode?: 'anonymous' | 'fixed';\n readonly pubkey?: string;\n };\n readonly relay?: {\n readonly mode?: 'live' | 'memory' | 'disabled';\n readonly urls?: readonly string[];\n readonly fixtures?: readonly JsonRecord[];\n };\n readonly storage?: {\n readonly mode?: 'local' | 'memory' | 'disabled';\n };\n readonly cache?: {\n readonly mode?: 'memory' | 'disabled';\n };\n readonly upload?: {\n readonly mode?: 'memory' | 'blossom' | 'disabled';\n readonly rail?: string;\n readonly servers?: readonly string[];\n readonly discoverServers?: boolean;\n readonly maxBytes?: number;\n readonly mimeTypes?: readonly string[];\n };\n readonly media?: {\n readonly enabled?: boolean;\n };\n readonly notifications?: {\n readonly enabled?: boolean;\n readonly grant?: boolean;\n };\n readonly config?: {\n readonly values?: JsonRecord;\n };\n readonly theme?: {\n readonly mode?: 'dark' | 'light';\n readonly values?: JsonRecord;\n };\n readonly intent?: {\n readonly enabled?: boolean;\n };\n readonly cvm?: {\n readonly enabled?: boolean;\n };\n}\n\n/** Fully normalized Paja simulation model served to the browser host. */\nexport interface PajaSimulation {\n /** Capability domains and disabled-domain summary. */\n readonly capabilities: {\n readonly domains: Record<PajaCapabilityDomain, boolean>;\n readonly disabledDomains: readonly PajaCapabilityDomain[];\n };\n readonly acl: {\n readonly mode: 'allow' | 'deny';\n };\n readonly firewall: {\n readonly mode: 'allow' | 'deny' | 'observe';\n };\n readonly identity: {\n readonly mode: 'anonymous' | 'fixed';\n readonly pubkey: string;\n };\n readonly relay: {\n readonly mode: 'live' | 'memory' | 'disabled';\n readonly urls: readonly string[];\n readonly fixtures: readonly JsonRecord[];\n };\n readonly storage: {\n readonly mode: 'local' | 'memory' | 'disabled';\n };\n readonly cache: {\n readonly mode: 'memory' | 'disabled';\n };\n readonly upload: {\n readonly mode: 'memory' | 'blossom' | 'disabled';\n readonly rail?: string;\n readonly servers: readonly string[];\n readonly discoverServers: boolean;\n readonly maxBytes?: number;\n readonly mimeTypes?: readonly string[];\n };\n readonly media: {\n readonly enabled: boolean;\n };\n readonly notifications: {\n readonly enabled: boolean;\n readonly grant: boolean;\n };\n readonly config: {\n readonly values: JsonRecord;\n };\n readonly theme: {\n readonly mode: 'dark' | 'light';\n readonly values: JsonRecord;\n };\n readonly intent: {\n readonly enabled: boolean;\n };\n readonly cvm: {\n readonly enabled: boolean;\n };\n}\n\n/** Thrown when Paja simulation options are internally inconsistent. */\nexport class PajaSimulationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'PajaSimulationError';\n }\n}\n\n/** Capability domains exposed by Paja simulation controls. */\nexport const PAJA_SIMULATION_DOMAINS: readonly PajaCapabilityDomain[] = [\n 'relay',\n 'outbox',\n 'storage',\n 'identity',\n 'keys',\n 'config',\n 'resource',\n 'theme',\n 'notify',\n 'media',\n 'upload',\n 'intent',\n 'count',\n 'link',\n 'common',\n 'lists',\n 'serial',\n 'ble',\n 'webrtc',\n 'dm',\n 'fs',\n 'cvm',\n 'inc',\n];\n\nconst DEFAULT_RELAY_URLS = [\n 'wss://relay.damus.io',\n 'wss://nos.lol',\n 'wss://relay.primal.net',\n 'wss://relay.snort.social',\n] as const;\nconst DEFAULT_CONFIG_VALUES: JsonRecord = {\n runtime: 'kehto paja',\n mode: 'development',\n target: 'single-window',\n};\n\nconst DEFAULT_THEME_VALUES: JsonRecord = {\n title: 'Kehto Paja',\n};\n\n/**\n * Normalize raw simulation options into a complete deterministic model.\n *\n * @param raw - Partial simulation options from CLI args or config.\n * @returns Complete simulation model.\n */\nexport function normalizePajaSimulation(\n raw: PajaSimulationRawOptions | undefined,\n): PajaSimulation {\n const domainOverrides = raw?.capabilities?.domains ?? {};\n const domains = Object.fromEntries(\n PAJA_SIMULATION_DOMAINS.map((domain) => [domain, domainOverrides[domain] ?? true]),\n ) as Record<PajaCapabilityDomain, boolean>;\n\n const relayMode = raw?.relay?.mode ?? (domains.relay ? 'live' : 'disabled');\n if (!domains.relay && relayMode !== 'disabled') {\n throw new PajaSimulationError('Invalid simulation: relay.mode must be \"disabled\" when capabilities.domains.relay is false.');\n }\n if (!domains.outbox && relayMode !== 'disabled') {\n throw new PajaSimulationError('Invalid simulation: relay.mode must be \"disabled\" when capabilities.domains.outbox is false.');\n }\n if (relayMode === 'disabled') {\n domains.relay = false;\n domains.outbox = false;\n domains.dm = false;\n }\n\n const uploadMode = raw?.upload?.mode ?? (domains.upload ? 'memory' : 'disabled');\n if (!domains.upload && uploadMode !== 'disabled') {\n throw new PajaSimulationError('Invalid simulation: upload.mode must be \"disabled\" when capabilities.domains.upload is false.');\n }\n if (uploadMode === 'disabled') domains.upload = false;\n\n const intentEnabled = raw?.intent?.enabled ?? domains.intent;\n if (!domains.intent && intentEnabled) {\n throw new PajaSimulationError('Invalid simulation: intent.enabled must be false when capabilities.domains.intent is false.');\n }\n if (!intentEnabled) domains.intent = false;\n\n if (!domains.relay) {\n domains.count = false;\n domains.dm = false;\n }\n\n const mediaEnabled = raw?.media?.enabled ?? domains.media;\n if (!domains.media && mediaEnabled) {\n throw new PajaSimulationError('Invalid simulation: media.enabled must be false when capabilities.domains.media is false.');\n }\n if (!mediaEnabled) domains.media = false;\n\n const cvmEnabled = raw?.cvm?.enabled ?? domains.cvm;\n if (!domains.cvm && cvmEnabled) {\n throw new PajaSimulationError('Invalid simulation: cvm.enabled must be false when capabilities.domains.cvm is false.');\n }\n if (!cvmEnabled) domains.cvm = false;\n\n const notificationsEnabled = raw?.notifications?.enabled ?? domains.notify;\n if (!domains.notify && notificationsEnabled) {\n throw new PajaSimulationError('Invalid simulation: notifications.enabled must be false when capabilities.domains.notify is false.');\n }\n if (!notificationsEnabled) domains.notify = false;\n\n const storageMode = raw?.storage?.mode ?? (domains.storage ? 'local' : 'disabled');\n if (!domains.storage && storageMode !== 'disabled') {\n throw new PajaSimulationError('Invalid simulation: storage.mode must be \"disabled\" when capabilities.domains.storage is false.');\n }\n if (storageMode !== 'local') domains.storage = false;\n\n const identityMode = raw?.identity?.mode ?? 'anonymous';\n const pubkey = raw?.identity?.pubkey?.trim() ?? '';\n if (identityMode === 'fixed' && !isHexPubkey(pubkey)) {\n throw new PajaSimulationError('Invalid simulation: identity.pubkey must be a 64-character hex string when identity.mode is \"fixed\".');\n }\n\n const relayUrls = raw?.relay?.urls ?? DEFAULT_RELAY_URLS;\n if (relayMode !== 'disabled' && relayUrls.length === 0) {\n throw new PajaSimulationError('Invalid simulation: relay.urls must contain at least one URL when relay.mode is enabled.');\n }\n for (const url of relayUrls) {\n if (typeof url !== 'string' || url.trim().length === 0) {\n throw new PajaSimulationError('Invalid simulation: relay.urls entries must be non-empty strings.');\n }\n if (relayMode === 'live') {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n throw new PajaSimulationError('Invalid simulation: live relay URLs must be absolute ws: or wss: URLs.');\n }\n if ((parsed.protocol !== 'ws:' && parsed.protocol !== 'wss:') || parsed.username || parsed.password) {\n throw new PajaSimulationError('Invalid simulation: live relay URLs must be credential-free ws: or wss: URLs.');\n }\n }\n }\n\n const uploadRail = raw?.upload?.rail?.trim() || 'dev-memory';\n if (uploadMode === 'memory' && uploadRail.length === 0) {\n throw new PajaSimulationError('Invalid simulation: upload.rail must be non-empty when upload.mode is \"memory\".');\n }\n const uploadServers = normalizeUploadServers(raw?.upload?.servers ?? []);\n const discoverServers = raw?.upload?.discoverServers ?? uploadMode === 'blossom';\n const maxBytes = raw?.upload?.maxBytes;\n if (maxBytes !== undefined && (!Number.isSafeInteger(maxBytes) || maxBytes <= 0)) {\n throw new PajaSimulationError('Invalid simulation: upload.maxBytes must be a positive safe integer.');\n }\n const mimeTypes = normalizeMimeTypes(raw?.upload?.mimeTypes);\n\n return {\n capabilities: {\n domains,\n disabledDomains: PAJA_SIMULATION_DOMAINS.filter((domain) => !domains[domain]),\n },\n acl: {\n mode: raw?.acl?.mode ?? 'allow',\n },\n firewall: {\n mode: raw?.firewall?.mode ?? 'observe',\n },\n identity: {\n mode: identityMode,\n pubkey: identityMode === 'fixed' ? pubkey : '',\n },\n relay: {\n mode: relayMode,\n urls: relayUrls.map((url) => url.trim()),\n fixtures: raw?.relay?.fixtures ?? [],\n },\n storage: {\n mode: storageMode,\n },\n cache: {\n mode: raw?.cache?.mode ?? 'memory',\n },\n upload: {\n mode: uploadMode,\n ...(uploadMode === 'memory' ? { rail: uploadRail } : {}),\n servers: uploadServers,\n discoverServers,\n ...(maxBytes !== undefined ? { maxBytes } : {}),\n ...(mimeTypes ? { mimeTypes } : {}),\n },\n media: {\n enabled: mediaEnabled,\n },\n notifications: {\n enabled: notificationsEnabled,\n grant: raw?.notifications?.grant ?? true,\n },\n config: {\n values: { ...DEFAULT_CONFIG_VALUES, ...raw?.config?.values },\n },\n theme: {\n mode: raw?.theme?.mode ?? 'dark',\n values: { ...DEFAULT_THEME_VALUES, ...raw?.theme?.values },\n },\n intent: {\n enabled: intentEnabled,\n },\n cvm: {\n enabled: cvmEnabled,\n },\n };\n}\n\n/**\n * Format a compact, human-readable simulation summary.\n *\n * @param simulation - Normalized simulation model.\n * @returns One-line summary used by CLI and host-page chrome.\n */\nexport function summarizePajaSimulation(simulation: PajaSimulation): string {\n const relay = simulation.relay.mode === 'disabled' ? 'relay:off' : `relay:${simulation.relay.mode}:${simulation.relay.urls.length}`;\n const identity = simulation.identity.mode === 'fixed' ? 'identity:fixed' : 'identity:anon';\n const storage = `storage:${simulation.storage.mode}`;\n const upload = simulation.upload.mode === 'memory'\n ? 'upload:memory:simulator'\n : simulation.upload.mode === 'blossom'\n ? `upload:blossom:${simulation.upload.servers.length}`\n : 'upload:off';\n const theme = `theme:${simulation.theme.mode}`;\n const disabled = simulation.capabilities.disabledDomains.length > 0\n ? `off:${simulation.capabilities.disabledDomains.join(',')}`\n : 'off:none';\n return `${identity} ${relay} ${storage} ${upload} ${theme} ${disabled}`;\n}\n\n/** Normalize an ordered list of shell-owned Blossom server base URLs. */\nexport function normalizeUploadServers(servers: readonly string[]): string[] {\n const normalized: string[] = [];\n for (const value of servers) {\n if (typeof value !== 'string' || value.trim().length === 0) {\n throw new PajaSimulationError('Invalid simulation: upload.servers entries must be non-empty URLs.');\n }\n let url: URL;\n try {\n url = new URL(value.trim());\n } catch {\n throw new PajaSimulationError(`Invalid simulation: upload server \"${value}\" is not a valid URL.`);\n }\n if (url.username || url.password) {\n throw new PajaSimulationError('Invalid simulation: upload server URLs must not contain credentials.');\n }\n if (url.protocol !== 'https:' && !(url.protocol === 'http:' && isLoopbackHost(url.hostname))) {\n throw new PajaSimulationError('Invalid simulation: upload servers require HTTPS, except loopback HTTP for development.');\n }\n if (url.search || url.hash) {\n throw new PajaSimulationError('Invalid simulation: upload server URLs must not contain a query or fragment.');\n }\n const result = url.href.replace(/\\/+$/, '');\n if (!normalized.includes(result)) normalized.push(result);\n }\n return normalized;\n}\n\nfunction isLoopbackHost(hostname: string): boolean {\n return hostname === 'localhost'\n || hostname === '[::1]'\n || /^127(?:\\.\\d{1,3}){3}$/.test(hostname);\n}\n\nfunction normalizeMimeTypes(mimeTypes: readonly string[] | undefined): string[] | undefined {\n if (mimeTypes === undefined) return undefined;\n const normalized: string[] = [];\n for (const value of mimeTypes) {\n if (typeof value !== 'string' || value.trim().length === 0) {\n throw new PajaSimulationError('Invalid simulation: upload.mimeTypes entries must be non-empty strings.');\n }\n const mimeType = value.trim();\n if (!normalized.includes(mimeType)) normalized.push(mimeType);\n }\n return normalized;\n}\n\nfunction isHexPubkey(value: string): boolean {\n return /^[0-9a-fA-F]{64}$/.test(value);\n}\n","import type { NappletCspOptions } from '@kehto/shell';\nimport {\n normalizePajaSimulation,\n type PajaSimulation,\n type PajaSimulationRawOptions,\n} from './simulation.js';\n\n/** Command configuration for a managed Paja target process. */\nexport type PajaCommand =\n /** Spawn an executable with argv preserved exactly after `--`. */\n | { readonly mode: 'argv'; readonly argv: readonly string[] }\n /** Run a shell command string. */\n | { readonly mode: 'shell'; readonly command: string };\n\n/** Raw, unnormalized options accepted from CLI arguments or config files. */\nexport interface PajaRawOptions {\n /** App dev-server URL that Paja should load. */\n readonly targetUrl?: string;\n /** Optional managed command to start before serving Paja. */\n readonly command?: PajaCommand;\n /** Hostname or IP address for the Paja HTTP server. */\n readonly host?: string;\n /** Runtime HTTP port. */\n readonly port?: number | string;\n /** Target readiness timeout in milliseconds. */\n readonly readyTimeoutMs?: number | string;\n /** Optional JSON config file path. */\n readonly configPath?: string;\n /** Simulation controls for injected shell domains. */\n readonly simulation?: PajaSimulationRawOptions;\n}\n\n/** Paja target loading mode. */\nexport type PajaTargetMode = 'iframe-url' | 'runtime-pointer';\n/** Hot-module-reload strategy for the hosted target. */\nexport type PajaHmrStrategy = 'iframe-target-url' | 'none';\n\n/** Runtime-pointer resolution config embedded into the Paja host page. */\nexport interface PajaPointerRuntimeConfig {\n /** Optional naddr/nevent pointer to resolve at boot. */\n readonly value?: string;\n /** Relay hints used to resolve the pointer. */\n readonly relays: readonly string[];\n /** Blossom server hints used for artifact blob fetches. */\n readonly blossomServers: readonly string[];\n /** Maximum pointer-resolution wait in milliseconds. */\n readonly maxWaitMs: number;\n}\n\n/** Normalized Paja runtime options. */\nexport interface PajaOptions {\n /** Absolute target URL for iframe-url mode. */\n readonly targetUrl: string;\n /** Optional managed target command. */\n readonly command?: PajaCommand;\n /** Paja server host. */\n readonly host: string;\n /** Paja server port. */\n readonly port: number;\n /** Target readiness timeout in milliseconds. */\n readonly readyTimeoutMs: number;\n /** Optional config path retained for host diagnostics. */\n readonly configPath?: string;\n /** Normalized simulation model. */\n readonly simulation: PajaSimulation;\n /** Whether Paja owns a managed command or attaches to an external target. */\n readonly mode: 'external-target' | 'managed-command';\n}\n\n/** Serialized host-page config served to the browser runtime. */\nexport interface PajaHostConfig {\n /** Host CSP overrides for verified runtime-pointer documents, validated by shell. */\n readonly csp?: NappletCspOptions;\n /** Config schema version. */\n readonly version: 1;\n /** Window identity used for the development target. */\n readonly window: {\n readonly id: string;\n readonly dTag: string;\n readonly aggregateHash: string;\n };\n readonly target: {\n readonly mode: PajaTargetMode;\n readonly url: string;\n readonly hmrStrategy: PajaHmrStrategy;\n readonly command?: PajaCommand;\n readonly pointer?: PajaPointerRuntimeConfig;\n };\n readonly runtime: {\n readonly host: string;\n readonly port: number;\n readonly readyTimeoutMs: number;\n readonly configPath?: string;\n readonly reloadToken: string;\n readonly createdAt: string;\n };\n readonly chrome: {\n readonly topBar: true;\n readonly bottomBar: true;\n readonly sidePanels: false;\n };\n readonly simulation: PajaSimulation;\n}\n\n/** Thrown when raw Paja options cannot be normalized. */\nexport class PajaOptionsError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'PajaOptionsError';\n }\n}\n\n/** Default loopback host for the Paja HTTP server. */\nexport const DEFAULT_PAJA_HOST = '127.0.0.1';\n/** Default Paja HTTP port. */\nexport const DEFAULT_PAJA_PORT = 5197;\n/** Default readiness timeout for the target URL. */\nexport const DEFAULT_READY_TIMEOUT_MS = 30_000;\n/** Default development window id used in host config. */\nexport const DEFAULT_PAJA_WINDOW_ID = 'kehto-paja-window';\n/** Default development `d` tag used for target identity. */\nexport const DEFAULT_PAJA_DTAG = 'dev-target';\n/** Default development aggregate hash used for target identity. */\nexport const DEFAULT_PAJA_AGGREGATE_HASH = 'paja';\n/** Default pointer resolution wait in runtime-pointer mode. */\nexport const DEFAULT_PAJA_RUNTIME_WAIT_MS = 5_000;\n\n/**\n * Normalize raw CLI/config options into a validated Paja runtime config.\n *\n * @param raw - Raw options from CLI parsing or a config file.\n * @returns Fully normalized runtime options.\n */\nexport function normalizePajaOptions(raw: PajaRawOptions): PajaOptions {\n const targetUrl = normalizeTargetUrl(raw.targetUrl);\n const host = normalizeHost(raw.host);\n const port = normalizePort(raw.port, 'port');\n const readyTimeoutMs = normalizePositiveInteger(\n raw.readyTimeoutMs ?? DEFAULT_READY_TIMEOUT_MS,\n 'ready-timeout',\n );\n const command = normalizeCommand(raw.command);\n const configPath = raw.configPath?.trim();\n const simulation = normalizePajaSimulation(raw.simulation);\n\n return {\n targetUrl,\n command,\n host,\n port,\n readyTimeoutMs,\n configPath: configPath && configPath.length > 0 ? configPath : undefined,\n simulation,\n mode: command ? 'managed-command' : 'external-target',\n };\n}\n\n/**\n * Create browser host-page config for iframe-url mode.\n *\n * @param options - Normalized Paja options.\n * @param now - Clock value used for reload token and timestamps.\n * @returns Serializable host-page config.\n */\nexport function createPajaHostConfig(\n options: PajaOptions,\n now: Date = new Date(),\n): PajaHostConfig {\n const base = createPajaHostConfigBase(options.simulation);\n return {\n ...base,\n target: {\n mode: 'iframe-url',\n url: options.targetUrl,\n hmrStrategy: 'iframe-target-url',\n command: options.command,\n },\n runtime: {\n host: options.host,\n port: options.port,\n readyTimeoutMs: options.readyTimeoutMs,\n configPath: options.configPath,\n reloadToken: createReloadToken(now),\n createdAt: now.toISOString(),\n },\n };\n}\n\n/**\n * Create browser host-page config for runtime-pointer mode.\n *\n * @param options - Pointer, relay, and Blossom hints.\n * @param now - Clock value used for reload token and timestamps.\n * @returns Serializable host-page config.\n */\nexport function createPajaRuntimeHostConfig(\n options: {\n readonly csp?: NappletCspOptions;\n readonly pointer?: string;\n readonly relays?: readonly string[];\n readonly blossomServers?: readonly string[];\n readonly maxWaitMs?: number;\n readonly simulation?: PajaSimulationRawOptions;\n } = {},\n now: Date = new Date(),\n): PajaHostConfig {\n const base = createPajaHostConfigBase(normalizePajaSimulation(options.simulation));\n return {\n ...base,\n ...(options.csp ? { csp: options.csp } : {}),\n target: {\n mode: 'runtime-pointer',\n url: 'about:blank',\n hmrStrategy: 'none',\n pointer: {\n ...(options.pointer ? { value: options.pointer } : {}),\n relays: [...(options.relays ?? [])],\n blossomServers: [...(options.blossomServers ?? [])],\n maxWaitMs: normalizePositiveInteger(\n options.maxWaitMs ?? DEFAULT_PAJA_RUNTIME_WAIT_MS,\n 'runtime-wait',\n ),\n },\n },\n runtime: {\n host: 'static',\n port: 0,\n readyTimeoutMs: 1,\n reloadToken: createReloadToken(now),\n createdAt: now.toISOString(),\n },\n };\n}\n\n/**\n * Format the local Paja runtime URL for display and CLI output.\n *\n * @param options - Host and port pair.\n * @returns HTTP URL for the Paja runtime.\n */\nexport function formatPajaUrl(options: Pick<PajaOptions, 'host' | 'port'>): string {\n return `http://${options.host}:${options.port}/`;\n}\n\nfunction normalizeTargetUrl(value: string | undefined): string {\n const raw = value?.trim();\n if (!raw) {\n throw new PajaOptionsError('Missing --target-url. Provide the napplet app URL that the iframe should load.');\n }\n\n let parsed: URL;\n try {\n parsed = new URL(raw);\n } catch {\n throw new PajaOptionsError(`Invalid --target-url \"${raw}\". Expected an absolute http(s) URL.`);\n }\n\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n throw new PajaOptionsError(`Invalid --target-url \"${raw}\". Only http: and https: URLs are supported.`);\n }\n\n return parsed.href;\n}\n\nfunction normalizeHost(value: string | undefined): string {\n const host = value?.trim() || DEFAULT_PAJA_HOST;\n if (host.length === 0 || host.includes('/')) {\n throw new PajaOptionsError(`Invalid --host \"${value ?? ''}\". Provide a hostname or IP address.`);\n }\n return host;\n}\n\nfunction normalizePort(value: number | string | undefined, label: string): number {\n return normalizeIntegerInRange(value ?? DEFAULT_PAJA_PORT, label, 0, 65_535);\n}\n\nfunction normalizePositiveInteger(value: number | string, label: string, max = Number.MAX_SAFE_INTEGER): number {\n return normalizeIntegerInRange(value, label, 1, max);\n}\n\nfunction createPajaHostConfigBase(simulation: PajaSimulation): Pick<PajaHostConfig, 'version' | 'window' | 'chrome' | 'simulation'> {\n return {\n version: 1,\n window: {\n id: DEFAULT_PAJA_WINDOW_ID,\n dTag: DEFAULT_PAJA_DTAG,\n aggregateHash: DEFAULT_PAJA_AGGREGATE_HASH,\n },\n chrome: {\n topBar: true,\n bottomBar: true,\n sidePanels: false,\n },\n simulation,\n };\n}\n\nfunction normalizeIntegerInRange(value: number | string, label: string, min: number, max: number): number {\n const parsed = typeof value === 'number' ? value : Number(value);\n if (!Number.isInteger(parsed) || parsed < min || parsed > max) {\n throw new PajaOptionsError(`Invalid --${label} \"${String(value)}\". Expected an integer from ${min} to ${max}.`);\n }\n return parsed;\n}\n\nfunction normalizeCommand(command: PajaCommand | undefined): PajaCommand | undefined {\n if (!command) return undefined;\n\n if (command.mode === 'argv') {\n const argv = command.argv.map((part) => part.trim()).filter((part) => part.length > 0);\n if (argv.length === 0) {\n throw new PajaOptionsError('Command mode requires at least one command argument after --.');\n }\n return { mode: 'argv', argv };\n }\n\n const shellCommand = command.command.trim();\n if (shellCommand.length === 0) {\n throw new PajaOptionsError('--command requires a non-empty command string.');\n }\n return { mode: 'shell', command: shellCommand };\n}\n\nfunction createReloadToken(now: Date): string {\n const iso = now.toISOString();\n return `reload-${iso.replaceAll(/[^0-9A-Za-z]/g, '')}`;\n}\n","/** Storage key holding the console visibility for one browser origin. */\nexport const PAJA_CONSOLE_PANEL_STORAGE_KEY = 'kehto:paja:console:v1';\n/** Id of the console column controlled by the toggle. */\nexport const PAJA_CONSOLE_PANEL_ID = 'paja-console';\n/** Id of the host-page console toggle button. */\nexport const PAJA_CONSOLE_TOGGLE_ID = 'paja-console-toggle';\n/** Accessible name while the console is expanded. */\nexport const PAJA_CONSOLE_COLLAPSE_LABEL = 'Collapse the Paja development console';\n/** Accessible name while the console is collapsed. */\nexport const PAJA_CONSOLE_EXPAND_LABEL = 'Expand the Paja development console';\n\n/**\n * Install the host-page console toggle without touching the target iframe.\n * Storage is best-effort; missing or invalid preferences start expanded.\n *\n * @returns Cleanup function that detaches the click listener.\n * @example\n * ```ts\n * const disposeConsolePanel = installPajaConsolePanel();\n * window.addEventListener('pagehide', disposeConsolePanel, { once: true });\n * ```\n */\nexport function installPajaConsolePanel(): () => void {\n const root = document.documentElement;\n const button = document.getElementById(PAJA_CONSOLE_TOGGLE_ID);\n let collapsed = false;\n try {\n collapsed = localStorage.getItem(PAJA_CONSOLE_PANEL_STORAGE_KEY) === 'collapsed';\n } catch {\n // Storage can be unavailable; keep the console usable in-session.\n }\n\n const apply = (): void => {\n root.dataset.pajaConsole = collapsed ? 'collapsed' : 'expanded';\n const label = collapsed ? PAJA_CONSOLE_EXPAND_LABEL : PAJA_CONSOLE_COLLAPSE_LABEL;\n button?.setAttribute('aria-expanded', String(!collapsed));\n button?.setAttribute('aria-label', label);\n button?.setAttribute('title', label);\n };\n const toggle = (): void => {\n collapsed = !collapsed;\n apply();\n try {\n localStorage.setItem(PAJA_CONSOLE_PANEL_STORAGE_KEY, root.dataset.pajaConsole!);\n } catch {\n // Persistence must not prevent collapsing or restoring the console.\n }\n };\n\n apply();\n button?.addEventListener('click', toggle);\n return () => button?.removeEventListener('click', toggle);\n}\n","import {\n PAJA_CONSOLE_COLLAPSE_LABEL,\n PAJA_CONSOLE_PANEL_ID,\n PAJA_CONSOLE_TOGGLE_ID,\n} from './browser-console-panel.js';\nimport type { PajaHostConfig } from './options.js';\nimport { summarizePajaSimulation } from './simulation.js';\n\n/**\n * Render the browser host page for a Paja runtime config.\n *\n * @param config - Serializable host-page config.\n * @returns Complete HTML document served by Paja.\n */\nexport function renderPajaHtml(config: PajaHostConfig): string {\n const configJson = escapeJsonForHtml(JSON.stringify(config));\n const targetLabel = escapeAttribute(getTargetLabel(config));\n\n return `<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <title>@kehto/paja</title>\n <style>\n :root {\n color-scheme: dark;\n --bg: #101211;\n --bar: #181b19;\n --line: #30352f;\n --text: #f4f0df;\n --muted: #a9ad9f;\n --accent: #d8c36a;\n --paja-console-column: minmax(320px, 380px);\n }\n * { box-sizing: border-box; }\n html, body { margin: 0; min-height: 100%; background: var(--bg); color: var(--text); font: 13px/1.4 ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif; }\n body { height: 100vh; display: grid; grid-template-rows: 38px minmax(0, 1fr) 30px; overflow: hidden; }\n .bar { display: flex; align-items: center; gap: 14px; min-width: 0; padding: 0 12px; background: var(--bar); border-color: var(--line); }\n .top { display: grid; grid-template-columns: var(--paja-console-column) minmax(0, 1fr); align-items: stretch; gap: 0; padding: 0; border-bottom: 1px solid var(--line); }\n .top-console { min-width: 0; display: flex; align-items: center; gap: 14px; padding: 0 12px; border-right: 1px solid var(--line); }\n .top-stage { min-width: 0; display: flex; align-items: stretch; gap: 14px; padding: 0 12px 0 10px; overflow: hidden; }\n .bottom { border-top: 1px solid var(--line); color: var(--muted); font-size: 12px; }\n .brand { font-weight: 700; letter-spacing: 0; color: var(--accent); white-space: nowrap; }\n .brand-product { color: var(--text); }\n .target { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); }\n .spacer { flex: 1; min-width: 0; }\n .console-toggle { align-self: center; flex: 0 0 auto; width: 28px; height: 24px; padding: 0; display: inline-grid; place-items: center; color: var(--text); }\n .console-toggle-glyph { font-size: 15px; line-height: 1; }\n .console-toggle-glyph::before { content: '\\\\00ab'; }\n html[data-paja-console=\"collapsed\"] .console-toggle-glyph::before { content: '\\\\00bb'; }\n /* Collapsing is presentation-only: the console column and its top-bar cell\n leave the grid and the stage reclaims the full width, so the target\n iframe keeps its identity, generation, and loaded napplet. */\n html[data-paja-console=\"collapsed\"] .top,\n html[data-paja-console=\"collapsed\"] main { grid-template-columns: minmax(0, 1fr); grid-template-rows: minmax(0, 1fr); }\n html[data-paja-console=\"collapsed\"] .top-console,\n html[data-paja-console=\"collapsed\"] .console { display: none; }\n .tabs { display: flex; align-items: stretch; align-self: flex-end; gap: 4px; min-width: 120px; max-width: min(100%, 760px); overflow-x: auto; scrollbar-width: thin; }\n .tabs:empty { display: none; }\n .tab { min-width: 116px; max-width: 240px; height: 30px; display: grid; grid-template-columns: minmax(0, 1fr) 24px 24px; align-items: center; gap: 2px; border: 1px solid var(--line); border-bottom-color: transparent; background: #151815; color: var(--muted); border-radius: 5px 5px 0 0; padding: 0 2px 0 9px; }\n .tab[data-source=\"local\"] { grid-template-columns: minmax(0, 1fr) 24px; }\n .tab[data-active=\"true\"] { color: var(--text); border-color: var(--accent); border-bottom-color: #151815; background: #20241f; }\n .tab-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; text-align: left; }\n .tab-share, .tab-close { width: 20px; height: 20px; padding: 0; border: 0; background: transparent; color: var(--muted); display: inline-grid; place-items: center; }\n .tab-share:hover, .tab-close:hover { color: var(--text); background: #2d302b; border-color: transparent; }\n button { border: 1px solid var(--line); color: var(--text); background: #20241f; height: 26px; padding: 0 10px; border-radius: 4px; font: inherit; cursor: pointer; }\n button:hover { border-color: var(--accent); }\n label { display: inline-flex; align-items: center; gap: 6px; color: var(--muted); white-space: nowrap; }\n select, input, textarea { border: 1px solid var(--line); color: var(--text); background: #20241f; min-height: 26px; border-radius: 4px; font: inherit; }\n main { min-height: 0; display: grid; grid-template-columns: var(--paja-console-column) minmax(0, 1fr); }\n .console { min-height: 0; overflow: auto; border-right: 1px solid var(--line); background: #121512; padding: 0; display: flex; flex-direction: column; gap: 0; }\n .section { flex-shrink: 0; margin: 0; border-radius: 0; border-bottom: 1px solid var(--line); }\n .section > summary { display: flex; align-items: center; justify-content: space-between; padding: 12px; cursor: pointer; list-style: none; }\n .section > summary::-webkit-details-marker { display: none; }\n .section > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }\n .section-chevron { transition: transform 120ms; }\n .section[open] > summary .section-chevron { transform: rotate(90deg); }\n .section-body { padding: 0 12px 12px; display: grid; gap: 8px; }\n .resource-servers { display: grid; gap: 8px; }\n .resource-servers > label { position: absolute; width: 1px; height: 1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }\n .section-title { color: var(--accent); font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0; }\n .resource-servers { min-width: 0; }\n .resource-servers label { white-space: normal; }\n .resource-servers textarea { width: 100%; min-width: 0; padding: 6px; resize: vertical; }\n .resource-servers small, .resource-servers [role=\"status\"] { color: var(--muted); overflow-wrap: anywhere; }\n .switch-grid, .acl-grid { display: flex; flex-wrap: wrap; gap: 6px; }\n .toggle { height: 24px; padding: 0 8px; color: var(--muted); }\n .toggle[data-enabled=\"true\"] { color: var(--text); border-color: #5f724f; background: #24301f; }\n .toggle[data-enabled=\"false\"] { color: #8d9187; border-color: #453536; background: #241d1d; }\n .signer { color: var(--muted); word-break: break-all; font-size: 12px; }\n .signer-controls { display: grid; grid-template-columns: auto auto minmax(0, 1fr) auto; gap: 6px; }\n .signer-controls button[data-active=\"true\"] { border-color: var(--accent); color: var(--text); background: #2a2a1d; }\n .signer-controls input { min-width: 0; padding: 0 8px; }\n .signer-consent-clear { width: 100%; margin-top: 6px; }\n .pointer-controls { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; gap: 6px; }\n .pointer-status { min-width: 0; color: var(--muted); font-size: 12px; overflow-wrap: anywhere; }\n .log-tools { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 6px; }\n .log-list { min-height: 160px; max-height: 38vh; overflow: auto; border: 1px solid var(--line); border-radius: 4px; background: #0b0d0b; }\n .log-row { display: grid; grid-template-columns: 88px minmax(0, 1fr); gap: 6px; padding: 5px 7px; border-bottom: 1px solid #1b1f1a; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 11px; }\n .log-row[data-error=\"true\"] { background: #1d1414; }\n .log-row:last-child { border-bottom: 0; }\n .log-dir { color: var(--muted); }\n .log-body { min-width: 0; display: grid; gap: 2px; }\n .log-type { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n .log-detail { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: #e6a5a5; }\n .stage { min-width: 0; min-height: 0; position: relative; background: #050705; }\n .empty-stage { position: absolute; inset: 0; display: grid; place-items: center; color: var(--muted); font-size: 12px; }\n .empty-stage[hidden] { display: none; }\n .stage.drop-active { outline: 2px dashed var(--accent); outline-offset: -6px; }\n .tab-panel { position: absolute; inset: 0; min-width: 0; min-height: 0; }\n .tab-panel[hidden] { display: none; }\n iframe { width: 100%; height: 100%; border: 0; background: white; display: block; }\n code { color: var(--text); }\n .dialog-backdrop { position: fixed; inset: 0; z-index: 20; display: grid; place-items: center; padding: 24px; background: rgb(0 0 0 / 0.58); }\n .dialog-backdrop[hidden] { display: none; }\n .dialog { width: min(420px, 100%); border: 1px solid var(--line); border-radius: 6px; background: #181b19; box-shadow: 0 18px 60px rgb(0 0 0 / 0.45); padding: 16px; display: grid; gap: 14px; }\n .dialog-title { font-weight: 700; color: var(--text); }\n .dialog-copy { color: var(--muted); }\n .dialog-details { margin: 0; padding: 10px; border: 1px solid var(--line); border-radius: 4px; background: #0b0d0b; color: var(--text); font: 12px/1.5 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; white-space: pre-wrap; overflow-wrap: anywhere; }\n .dialog-actions { display: flex; justify-content: flex-end; flex-wrap: wrap; gap: 8px; }\n .confirmation-dialog { width: min(520px, calc(100% - 48px)); max-width: none; border: 0; padding: 0; background: transparent; color: var(--text); }\n .confirmation-dialog::backdrop { background: rgb(0 0 0 / 0.68); }\n .signer-consent { margin: 0; padding: 10px; border: 1px solid var(--line); border-radius: 4px; display: grid; gap: 8px; }\n .signer-consent[hidden] { display: none; }\n .signer-consent legend { color: var(--accent); padding: 0 5px; }\n .signer-consent label { display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: start; gap: 8px; }\n .signer-consent input { margin-top: 3px; }\n .signer-consent-trust { padding: 8px; border: 1px solid #9a5d2f; border-radius: 4px; background: #332014; }\n .signer-consent-warning { display: block; margin-top: 4px; color: #ffbd80; font-weight: 700; }\n .config-dialog { width: min(620px, calc(100% - 48px)); max-width: none; max-height: min(760px, calc(100% - 48px)); border: 0; padding: 0; background: transparent; color: var(--text); }\n .config-dialog::backdrop { background: rgb(0 0 0 / 0.68); }\n .config-dialog .dialog { width: 100%; max-height: inherit; grid-template-rows: auto auto minmax(0, 1fr) auto auto; }\n .config-fields { min-height: 0; overflow: auto; display: grid; gap: 12px; }\n .config-field { display: grid; align-items: stretch; gap: 5px; white-space: normal; }\n .config-field-label { color: var(--text); font-weight: 600; }\n .config-field input:not([type=\"checkbox\"]), .config-field select, .config-field textarea { width: 100%; padding: 4px 7px; }\n .config-field textarea { resize: vertical; }\n .config-field small, .config-empty { color: var(--muted); }\n .config-group { margin: 0; padding: 10px; border: 1px solid var(--line); border-radius: 4px; display: grid; gap: 10px; }\n .config-group legend { color: var(--accent); padding: 0 5px; }\n .config-error { min-height: 1.4em; color: #e6a5a5; }\n .paja-notification-center { position: fixed; right: 16px; bottom: 46px; z-index: 10; width: min(360px, calc(100vw - 32px)); display: grid; gap: 8px; pointer-events: none; }\n .paja-notification { display: grid; gap: 8px; padding: 10px; border: 1px solid var(--line); border-left: 3px solid var(--accent); border-radius: 6px; background: #181b19; box-shadow: 0 10px 30px rgb(0 0 0 / 0.45); pointer-events: auto; }\n .paja-notification[data-priority=\"high\"], .paja-notification[data-priority=\"urgent\"] { border-left-color: #e89b5b; }\n .paja-notification-header { display: flex; align-items: center; gap: 8px; }\n .paja-notification-origin { min-width: 0; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); font-size: 11px; }\n .paja-notification-close { height: 22px; padding: 0 6px; color: var(--muted); font-size: 11px; }\n .paja-notification-body { height: auto; min-height: 28px; padding: 0; border: 0; background: transparent; display: grid; gap: 3px; text-align: left; }\n .paja-notification-body:hover { border-color: transparent; }\n .paja-notification-body span { color: var(--muted); }\n .paja-notification-actions { display: flex; justify-content: flex-end; flex-wrap: wrap; gap: 6px; }\n .paja-notification-action { height: 24px; }\n .paja-notification-badges { position: fixed; right: 12px; top: 7px; z-index: 11; display: flex; gap: 6px; pointer-events: none; }\n .paja-notification-badge { max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 2px 7px; border: 1px solid #5f724f; border-radius: 999px; background: #24301f; color: var(--text); font-size: 11px; }\n @media (max-width: 900px) {\n .top { grid-template-columns: minmax(0, 1fr); }\n .top-console { display: none; }\n .top-stage { padding-left: 0; }\n .console-toggle { margin-left: 8px; }\n main { grid-template-columns: 1fr; grid-template-rows: minmax(240px, 40vh) minmax(0, 1fr); }\n .console { border-right: 0; border-bottom: 1px solid var(--line); }\n }\n </style>\n </head>\n <body>\n <header class=\"bar top\">\n <div class=\"top-console\">\n <div class=\"brand\">@kehto/<span class=\"brand-product\">paja</span></div>\n <div class=\"target\" title=\"${targetLabel}\">${targetLabel}</div>\n </div>\n <div class=\"top-stage\">\n <button\n type=\"button\"\n id=\"${PAJA_CONSOLE_TOGGLE_ID}\"\n class=\"console-toggle\"\n aria-expanded=\"true\"\n aria-controls=\"${PAJA_CONSOLE_PANEL_ID}\"\n aria-label=\"${PAJA_CONSOLE_COLLAPSE_LABEL}\"\n title=\"${PAJA_CONSOLE_COLLAPSE_LABEL}\"\n ><span class=\"console-toggle-glyph\" aria-hidden=\"true\"></span></button>\n <div class=\"tabs\" id=\"napplet-tabs\" role=\"tablist\" aria-label=\"Loaded napplets\"></div>\n <div class=\"spacer\"></div>\n <label>theme\n <select id=\"simulation-theme\" aria-label=\"Simulation theme\">\n <option value=\"dark\"${config.simulation.theme.mode === 'dark' ? ' selected' : ''}>dark</option>\n <option value=\"light\"${config.simulation.theme.mode === 'light' ? ' selected' : ''}>light</option>\n </select>\n </label>\n <button type=\"button\" id=\"reload-target\">Reload</button>\n </div>\n </header>\n <main>\n <aside class=\"console\" id=\"${PAJA_CONSOLE_PANEL_ID}\" aria-label=\"Paja development controls\">\n ${renderPointerControls(config)}\n <details class=\"section\" id=\"paja-section-interfaces\" data-paja-section=\"interfaces\" open>\n <summary class=\"section-title\">Interfaces<span class=\"section-chevron\" aria-hidden=\"true\">›</span></summary><div class=\"section-body\">\n <div class=\"switch-grid\" id=\"interface-toggles\"></div>\n </div></details>\n <details class=\"section\" id=\"paja-section-acl\" data-paja-section=\"acl\" open>\n <summary class=\"section-title\">ACL<span class=\"section-chevron\" aria-hidden=\"true\">›</span></summary><div class=\"section-body\">\n <div class=\"acl-grid\" id=\"acl-controls\"></div>\n </div></details>\n <details class=\"section\" id=\"paja-section-signer\" data-paja-section=\"signer\" open>\n <summary class=\"section-title\">Signer<span class=\"section-chevron\" aria-hidden=\"true\">›</span></summary><div class=\"section-body\">\n <div class=\"signer\" id=\"signer-status\">loading</div>\n <div class=\"signer-controls\" id=\"signer-controls\"></div>\n <button type=\"button\" class=\"signer-consent-clear\" id=\"signer-consent-clear\" hidden>Forget remembered approvals</button>\n </div></details>\n <details class=\"section\" id=\"paja-section-resource-servers\" data-paja-section=\"resource-servers\" open>\n <summary class=\"section-title\">Resource servers<span class=\"section-chevron\" aria-hidden=\"true\">›</span></summary><div class=\"section-body\">\n <form class=\"resource-servers\" id=\"paja-resource-servers-form\">\n <label class=\"section-title\" for=\"paja-resource-servers-input\">Resource servers</label>\n <textarea id=\"paja-resource-servers-input\" rows=\"3\" aria-describedby=\"paja-resource-servers-help paja-resource-servers-status\" spellcheck=\"false\"></textarea>\n <small id=\"paja-resource-servers-help\">Extra Blossom lookup servers, one per line. Domains use HTTPS.</small>\n <button type=\"submit\" id=\"paja-resource-servers-save\">Save</button>\n <div id=\"paja-resource-servers-status\" role=\"status\" aria-live=\"polite\"></div>\n </form>\n </div></details>\n <details class=\"section\" id=\"paja-section-messages\" data-paja-section=\"messages\" open>\n <summary class=\"section-title\">Messages<span class=\"section-chevron\" aria-hidden=\"true\">›</span></summary><div class=\"section-body\">\n <div class=\"log-tools\">\n <input id=\"message-filter\" type=\"search\" autocomplete=\"off\" placeholder=\"filter messages\" aria-label=\"Filter message log\">\n <button type=\"button\" id=\"clear-log\">Clear</button>\n </div>\n <div class=\"log-list\" id=\"message-log\" aria-live=\"polite\"></div>\n </div></details>\n </aside>\n ${renderStage(config, targetLabel)}\n </main>\n <div class=\"paja-notification-badges\" id=\"paja-notification-badges\" aria-label=\"Napplet notification badges\"></div>\n <section class=\"paja-notification-center\" id=\"paja-notification-center\" aria-label=\"Napplet notifications\" aria-live=\"polite\"></section>\n ${renderConfirmationDialog()}\n ${renderConfigDialog()}\n ${renderDuplicateDialog()}\n <footer class=\"bar bottom\">\n <span>mode: <code>${escapeHtml(getModeLabel(config))}</code></span>\n <span>hmr: <code>${config.target.hmrStrategy}</code></span>\n <span>runtime: <code>${escapeHtml(config.runtime.host)}:${config.runtime.port}</code></span>\n <span>sim: <code id=\"simulation-status\">${escapeHtml(summarizePajaSimulation(config.simulation))}</code></span>\n <span>state: <code id=\"lifecycle-status\">booting</code></span>\n </footer>\n <script type=\"application/json\" id=\"kehto-paja-config\">${configJson}</script>\n <script type=\"module\" src=\"./__kehto/browser-host.js\"></script>\n </body>\n</html>`;\n}\n\nfunction renderConfirmationDialog(): string {\n return `<dialog class=\"confirmation-dialog\" id=\"paja-confirmation-dialog\" aria-labelledby=\"paja-confirmation-title\" aria-describedby=\"paja-confirmation-summary paja-confirmation-details\">\n <div class=\"dialog\">\n <div class=\"dialog-title\" id=\"paja-confirmation-title\">Confirm Paja request</div>\n <div class=\"dialog-copy\" id=\"paja-confirmation-summary\"></div>\n <pre class=\"dialog-details\" id=\"paja-confirmation-details\"></pre>\n <fieldset class=\"signer-consent\" id=\"paja-signer-consent\" hidden>\n <legend>Remember this signing choice</legend>\n <label>\n <input type=\"radio\" name=\"paja-signer-consent\" id=\"paja-signer-consent-once\" value=\"once\" checked>\n <span>Sign this event only</span>\n </label>\n <label>\n <input type=\"radio\" name=\"paja-signer-consent\" id=\"paja-signer-consent-kind\" value=\"kind\">\n <span>Always sign kind <code id=\"paja-signer-consent-kind-value\"></code> for this napplet identity and target</span>\n </label>\n <label class=\"signer-consent-trust\">\n <input type=\"radio\" name=\"paja-signer-consent\" id=\"paja-signer-consent-napplet\" value=\"napplet\" aria-describedby=\"paja-signer-consent-warning\">\n <span>Trust <strong id=\"paja-signer-consent-napplet-value\"></strong> for every event kind at this target\n <span class=\"signer-consent-warning\" id=\"paja-signer-consent-warning\">Warning: Paja will sign any event this napplet identity requests without asking again. Direct-target trust survives code reloads at the same URL.</span>\n </span>\n </label>\n </fieldset>\n <div class=\"dialog-actions\">\n <button type=\"button\" id=\"paja-confirmation-deny\">Deny</button>\n <button type=\"button\" id=\"paja-confirmation-approve\">Approve</button>\n </div>\n </div>\n </dialog>`;\n}\n\nfunction renderConfigDialog(): string {\n return `<dialog class=\"config-dialog\" id=\"paja-config-dialog\" aria-labelledby=\"paja-config-title\" aria-describedby=\"paja-config-description\">\n <div class=\"dialog\">\n <div class=\"dialog-title\" id=\"paja-config-title\">Napplet settings</div>\n <div class=\"dialog-copy\" id=\"paja-config-description\"></div>\n <div class=\"config-fields\" id=\"paja-config-fields\"></div>\n <div class=\"config-error\" id=\"paja-config-error\" role=\"alert\"></div>\n <div class=\"dialog-actions\">\n <button type=\"button\" id=\"paja-config-cancel\">Cancel</button>\n <button type=\"button\" id=\"paja-config-save\">Save</button>\n </div>\n </div>\n </dialog>`;\n}\n\nfunction renderStage(config: PajaHostConfig, targetLabel: string): string {\n if (config.target.mode === 'runtime-pointer') {\n return `<section class=\"stage\" id=\"napplet-stage\" aria-label=\"Loaded napplet runtimes\">\n <div class=\"empty-stage\" id=\"empty-runtime-stage\">Load a napplet pointer or drop an index.html to start a runtime tab.</div>\n </section>`;\n }\n return `<section class=\"stage\" id=\"napplet-stage\">\n <iframe id=\"napplet-frame\" title=\"Napplet development target\" sandbox=\"allow-scripts\" data-target-url=\"${targetLabel}\"></iframe>\n </section>`;\n}\n\nfunction renderDuplicateDialog(): string {\n return `<div class=\"dialog-backdrop\" id=\"duplicate-pointer-dialog\" hidden>\n <div class=\"dialog\" role=\"dialog\" aria-modal=\"true\" aria-labelledby=\"duplicate-pointer-title\">\n <div class=\"dialog-title\" id=\"duplicate-pointer-title\">this napplet is already running.</div>\n <div class=\"dialog-actions\">\n <button type=\"button\" id=\"duplicate-load-again\">load it again</button>\n <button type=\"button\" id=\"duplicate-open-tab\">open it in tab</button>\n <button type=\"button\" id=\"duplicate-cancel\">cancel</button>\n </div>\n </div>\n </div>`;\n}\n\nfunction renderPointerControls(config: PajaHostConfig): string {\n if (config.target.mode !== 'runtime-pointer') return '';\n const value = escapeAttribute(config.target.pointer?.value ?? '');\n return `<details class=\"section\" id=\"runtime-pointer-section\" data-paja-section=\"pointer\" open>\n <summary class=\"section-title\">Pointer<span class=\"section-chevron\" aria-hidden=\"true\">›</span></summary><div class=\"section-body\">\n <form class=\"pointer-controls\" id=\"runtime-pointer-form\">\n <input id=\"runtime-pointer-input\" type=\"text\" inputmode=\"url\" autocomplete=\"off\" spellcheck=\"false\" placeholder=\"naddr or nevent\" aria-label=\"Runtime napplet pointer\" value=\"${value}\">\n <button type=\"submit\" id=\"runtime-pointer-load\">Load</button>\n <button type=\"button\" id=\"runtime-local-open\" title=\"Open a self-contained single-file index.html\">Open file…</button>\n <input id=\"runtime-local-file\" type=\"file\" accept=\".html,.htm,text/html\" aria-label=\"Local napplet index.html\" hidden>\n </form>\n <div class=\"pointer-status\" id=\"runtime-pointer-status\" aria-live=\"polite\">idle</div>\n </div></details>`;\n}\n\nfunction getModeLabel(config: PajaHostConfig): string {\n if (config.target.mode === 'runtime-pointer') return 'runtime-pointer';\n return config.target.command ? 'managed-command' : 'external-target';\n}\n\nfunction getTargetLabel(config: PajaHostConfig): string {\n if (config.target.mode === 'runtime-pointer') {\n return config.target.pointer?.value ?? 'runtime pointer';\n }\n return config.target.url;\n}\n\nfunction escapeAttribute(value: string): string {\n return escapeHtml(value).replaceAll('\"', '"');\n}\n\nfunction escapeHtml(value: string): string {\n return value\n .replaceAll('&', '&')\n .replaceAll('<', '<')\n .replaceAll('>', '>');\n}\n\nfunction escapeJsonForHtml(value: string): string {\n return value.replaceAll('<', '\\\\u003c');\n}\n","import { injectNappletCsp } from '@kehto/shell';\nimport type { NostrEvent } from 'nostr-tools';\nimport type { Filter } from 'nostr-tools/filter';\nimport { SimplePool } from 'nostr-tools/pool';\nimport { decode } from 'nostr-tools/nip19';\nimport {\n fetchBlob,\n isNappletManifestKind,\n NAPPLET_KIND_NAMED,\n NAPPLET_KINDS,\n openNappletArtifactCache,\n resolveNapplet,\n type ResolvedNapplet,\n} from '@kehto/nip/5d';\n\nimport { normalizePublicBlossomServer } from './browser-resource.js';\n\n/** Named/addressable NIP-5D napplet manifest kind (`35129`). */\nexport const PAJA_NAPPLET_MANIFEST_KIND = NAPPLET_KIND_NAMED;\n/** All NIP-5D napplet manifest kinds accepted by Paja pointers (`5129`, `15129`, `35129`). */\nexport const PAJA_NAPPLET_MANIFEST_KINDS = NAPPLET_KINDS;\n\n/** Decoded naddr/nevent pointer accepted by Paja runtime-pointer mode. */\nexport type PajaDecodedPointer =\n | {\n readonly type: 'naddr';\n readonly value: string;\n readonly identifier: string;\n readonly pubkey: string;\n readonly kind: number;\n readonly relays: readonly string[];\n }\n | {\n readonly type: 'nevent';\n readonly value: string;\n readonly id: string;\n readonly author?: string;\n readonly kind?: number;\n readonly relays: readonly string[];\n };\n\n/** Fully resolved pointer plus verified napplet artifact bytes. */\nexport interface PajaResolvedPointer {\n /** Decoded pointer input. */\n readonly pointer: PajaDecodedPointer;\n /** Manifest event resolved from relays. */\n readonly event: NostrEvent;\n /** Ordered relay URLs queried for the pointer, with embedded hints first. */\n readonly relays: readonly string[];\n /** Blossom server hints used for blob fetches. */\n readonly blossomServers: readonly string[];\n /** Resolved manifest `d` tag. */\n readonly dTag: string;\n /** Verified content identity: current artifact hash or legacy NIP-5A aggregate. */\n readonly aggregateHash: string;\n /** Verified target `/index.html` content. */\n readonly indexHtml: string;\n /** Parsed NIP-5D manifest. */\n readonly manifest: ResolvedNapplet['manifest'];\n}\n\n/** Relay-pool contract for runtime-pointer resolution. */\nexport interface PajaPointerRelayPool {\n /** Query relays synchronously for matching manifest events. */\n querySync(\n relays: string[],\n filter: Filter,\n params?: { readonly maxWait?: number; readonly label?: string; readonly id?: string },\n ): Promise<NostrEvent[]>;\n /** Subscribe until all relays reach EOSE, fail, or the shared deadline expires. */\n subscribeManyEose?(\n relays: string[],\n filter: Filter,\n params: {\n readonly maxWait?: number;\n readonly label?: string;\n readonly id?: string;\n readonly onevent: (event: NostrEvent) => void;\n readonly onclose: (reasons: string[]) => void;\n },\n ): { close(reason?: string): void | Promise<void> };\n close?(relays: string[]): void;\n destroy?(): void;\n}\n\n/** Options for resolving a Paja runtime pointer. */\nexport interface ResolvePajaPointerOptions {\n /** Relay pool override; defaults to a `nostr-tools` `SimplePool`. */\n readonly pool?: PajaPointerRelayPool;\n /** Fetch override used for Blossom blob URLs. */\n readonly fetcher?: (url: string) => Promise<Response>;\n /** Extra relay candidates appended after embedded pointer hints. */\n readonly relays?: readonly string[];\n /** Extra Blossom server hints appended during artifact fetch. */\n readonly blossomServers?: readonly string[];\n /** One overall deadline for relay connection, fanout, and EOSE in milliseconds. */\n readonly maxWaitMs?: number;\n}\n\n/**\n * Decode a NIP-19 naddr/nevent pointer for Paja runtime-pointer mode.\n *\n * @param value - NIP-19 pointer string.\n * @returns Structured pointer metadata.\n */\nexport function decodePajaPointer(value: string): PajaDecodedPointer {\n const trimmed = value.trim();\n const decoded = decode(trimmed);\n if (decoded.type === 'naddr') {\n return {\n type: 'naddr',\n value: trimmed,\n identifier: decoded.data.identifier,\n pubkey: decoded.data.pubkey,\n kind: decoded.data.kind,\n relays: [...(decoded.data.relays ?? [])],\n };\n }\n if (decoded.type === 'nevent') {\n return {\n type: 'nevent',\n value: trimmed,\n id: decoded.data.id,\n ...(decoded.data.author ? { author: decoded.data.author } : {}),\n ...(decoded.data.kind ? { kind: decoded.data.kind } : {}),\n relays: [...(decoded.data.relays ?? [])],\n };\n }\n throw new Error(`Expected naddr or nevent pointer, got ${decoded.type}.`);\n}\n\n/**\n * Resolve a Paja runtime pointer into verified napplet HTML and manifest data.\n *\n * @param value - NIP-19 naddr or nevent pointer.\n * @param options - Relay, fetch, and Blossom overrides.\n * @returns Verified napplet artifact ready for srcdoc injection.\n */\nexport async function resolvePajaPointer(\n value: string,\n options: ResolvePajaPointerOptions = {},\n): Promise<PajaResolvedPointer> {\n const pointer = decodePajaPointer(value);\n const relays = uniqueRelayUrls([...pointer.relays, ...(options.relays ?? [])]);\n if (relays.length === 0) {\n throw new Error('Pointer has no relay hints or enabled configured live relays.');\n }\n\n const pool = options.pool ?? new SimplePool();\n const ownsPool = options.pool === undefined;\n try {\n const event = await resolvePointerEvent(pointer, pool, relays, normalizeMaxWaitMs(options.maxWaitMs));\n const blossomServers = resolvePointerBlossomServers(event, options.blossomServers ?? []);\n const resolved = await resolveNapplet({\n event,\n cache: await openNappletArtifactCache({ requireStorageEstimate: false }),\n fetchBlob: (sha256Hex, servers) =>\n fetchBlob([...servers, ...blossomServers], sha256Hex, async (url) => {\n const response = await (options.fetcher ?? fetch)(url);\n if (!response.ok) throw new Error(`[paja-runtime] blob ${url}: ${response.status}`);\n return new Uint8Array(await response.arrayBuffer());\n }),\n });\n return {\n pointer,\n event,\n relays,\n blossomServers,\n dTag: resolved.dTag,\n aggregateHash: resolved.aggregateHash,\n indexHtml: resolved.indexHtml,\n manifest: resolved.manifest,\n };\n } finally {\n if (ownsPool) {\n pool.close?.(relays);\n pool.destroy?.();\n }\n }\n}\n\nfunction resolvePointerBlossomServers(\n event: NostrEvent,\n configured: readonly string[],\n): string[] {\n const servers: string[] = [];\n for (const value of event.tags.flatMap(\n (tag) => tag[0] === 'server' && tag[1] ? [tag[1]] : [],\n )) {\n const normalized = normalizePublicBlossomServer(value);\n if (normalized && !servers.includes(normalized)) servers.push(normalized);\n }\n for (const value of configured) {\n const server = normalizePublicBlossomServer(value) ?? value;\n if (!servers.includes(server)) servers.push(server);\n }\n return servers;\n}\n\n/**\n * Inject Kehto's Class-1 CSP for verified runtime-pointer srcdoc output.\n * Delegates to the shell policy, which enforces NIP-5D CSP recommendations.\n *\n * @param html - Verified target HTML.\n * @param origins - Origins the resolved target may connect to.\n * @returns HTML with a CSP meta tag inserted.\n */\nexport function injectPajaRuntimeCsp(html: string, origins: readonly string[]): string {\n return injectNappletCsp(html, { connectOrigins: origins });\n}\n\nasync function resolvePointerEvent(\n pointer: PajaDecodedPointer,\n pool: PajaPointerRelayPool,\n relays: readonly string[],\n maxWaitMs: number,\n): Promise<NostrEvent> {\n const filter = pointer.type === 'naddr'\n ? addressableFilter(pointer)\n : eventFilter(pointer);\n const result = await queryPointerRelays(pool, relays, filter, maxWaitMs);\n const event = result.events\n .filter((candidate) => matchesPointer(pointer, candidate))\n .sort((a, b) => b.created_at - a.created_at)[0];\n if (!event) {\n if (result.timedOut) {\n throw new Error(\n `Relay resolution timed out after ${maxWaitMs}ms for ${pointer.type} pointer across ${relays.length} relays before complete EOSE.`,\n );\n }\n const failures = relayFailureReasons(result.reasons);\n if (result.error || failures.length > 0) {\n const detail = result.error instanceof Error\n ? result.error.message\n : result.error === undefined\n ? failures.join('; ')\n : String(result.error);\n throw new Error(`Relay query failed for ${pointer.type} pointer before complete EOSE: ${detail}`);\n }\n throw new Error(\n `All ${relays.length} relays reached EOSE without a matching ${pointer.type} napplet manifest event.`,\n );\n }\n if (!isNappletManifestKind(event.kind)) {\n throw new Error(`Pointer resolved kind ${event.kind}; expected ${formatNappletManifestKinds()}.`);\n }\n return event;\n}\n\ninterface PointerRelayQueryResult {\n readonly events: readonly NostrEvent[];\n readonly reasons: readonly string[];\n readonly timedOut: boolean;\n readonly error?: unknown;\n}\n\nfunction queryPointerRelays(\n pool: PajaPointerRelayPool,\n relays: readonly string[],\n filter: Filter,\n maxWaitMs: number,\n): Promise<PointerRelayQueryResult> {\n if (pool.subscribeManyEose) {\n return queryPointerRelaySubscription(pool, relays, filter, maxWaitMs);\n }\n return queryPointerRelaySync(pool, relays, filter, maxWaitMs);\n}\n\nfunction queryPointerRelaySubscription(\n pool: PajaPointerRelayPool,\n relays: readonly string[],\n filter: Filter,\n maxWaitMs: number,\n): Promise<PointerRelayQueryResult> {\n return new Promise((resolve) => {\n const events: NostrEvent[] = [];\n let settled = false;\n let subscription: ReturnType<NonNullable<PajaPointerRelayPool['subscribeManyEose']>> | undefined;\n const finish = (result: Omit<PointerRelayQueryResult, 'events'>) => {\n if (settled) return;\n settled = true;\n clearTimeout(deadline);\n resolve({ events, ...result });\n };\n const deadline = setTimeout(() => {\n finish({ reasons: [], timedOut: true });\n void subscription?.close('Paja pointer relay deadline exceeded');\n }, maxWaitMs);\n\n try {\n subscription = pool.subscribeManyEose!([...relays], filter, {\n maxWait: maxWaitMs,\n label: 'kehto-paja-runtime',\n onevent(event) {\n events.push(event);\n },\n onclose(reasons) {\n finish({ reasons, timedOut: false });\n },\n });\n } catch (error) {\n finish({ reasons: [], timedOut: false, error });\n }\n });\n}\n\nfunction queryPointerRelaySync(\n pool: PajaPointerRelayPool,\n relays: readonly string[],\n filter: Filter,\n maxWaitMs: number,\n): Promise<PointerRelayQueryResult> {\n return new Promise((resolve) => {\n let settled = false;\n const finish = (result: PointerRelayQueryResult) => {\n if (settled) return;\n settled = true;\n clearTimeout(deadline);\n resolve(result);\n };\n const deadline = setTimeout(() => {\n finish({ events: [], reasons: [], timedOut: true });\n }, maxWaitMs);\n\n void pool.querySync([...relays], filter, {\n maxWait: maxWaitMs,\n label: 'kehto-paja-runtime',\n }).then(\n (events) => finish({ events, reasons: [], timedOut: false }),\n (error: unknown) => finish({ events: [], reasons: [], timedOut: false, error }),\n );\n });\n}\n\nfunction relayFailureReasons(reasons: readonly string[]): string[] {\n return [...new Set(reasons\n .map((reason) => reason.trim())\n .filter((reason) => reason.length > 0 && reason !== 'closed automatically on eose')\n .map((reason) => reason.slice(0, 160)))]\n .slice(0, 3);\n}\n\nfunction normalizeMaxWaitMs(value: number | undefined): number {\n if (value === undefined) return 5_000;\n if (!Number.isFinite(value) || value <= 0) {\n throw new Error('Pointer relay deadline must be a positive number of milliseconds.');\n }\n return Math.floor(value);\n}\n\nfunction addressableFilter(pointer: Extract<PajaDecodedPointer, { type: 'naddr' }>): Filter {\n if (pointer.kind !== NAPPLET_KIND_NAMED) {\n throw new Error(`naddr kind ${pointer.kind} is not a named NIP-5D napplet manifest kind.`);\n }\n return {\n kinds: [NAPPLET_KIND_NAMED],\n authors: [pointer.pubkey],\n '#d': [pointer.identifier],\n limit: 1,\n };\n}\n\nfunction eventFilter(pointer: Extract<PajaDecodedPointer, { type: 'nevent' }>): Filter {\n return {\n ids: [pointer.id],\n ...(pointer.kind ? { kinds: [pointer.kind] } : {}),\n ...(pointer.author ? { authors: [pointer.author] } : {}),\n limit: 1,\n };\n}\n\nfunction matchesPointer(pointer: PajaDecodedPointer, event: NostrEvent): boolean {\n if (pointer.type === 'nevent') {\n if (event.id !== pointer.id) return false;\n if (pointer.author && event.pubkey !== pointer.author) return false;\n if (pointer.kind && event.kind !== pointer.kind) return false;\n return true;\n }\n return event.kind === pointer.kind\n && event.pubkey === pointer.pubkey\n && event.tags.some((tag) => tag[0] === 'd' && tag[1] === pointer.identifier);\n}\n\nfunction uniqueRelayUrls(values: readonly string[]): string[] {\n const out: string[] = [];\n const seen = new Set<string>();\n for (const value of values) {\n const url = normalizeRelayUrl(value);\n if (!url || seen.has(url)) continue;\n seen.add(url);\n out.push(url);\n }\n return out;\n}\n\nfunction normalizeRelayUrl(value: string): string | undefined {\n const raw = value.trim();\n if (!raw) return undefined;\n try {\n const url = new URL(raw);\n if (url.protocol !== 'wss:' && url.protocol !== 'ws:') return undefined;\n url.hash = '';\n url.search = '';\n return url.href.replace(/\\/$/, '');\n } catch {\n return undefined;\n }\n}\n\nfunction formatNappletManifestKinds(): string {\n return PAJA_NAPPLET_MANIFEST_KINDS.join(' / ');\n}\n","import { verifyBlobHash } from '@kehto/nip/5d';\nimport {\n ResourceServiceError,\n type ResourceInfo,\n type ResourceServiceOptions,\n} from '@kehto/services';\n\nimport { normalizeUploadServers } from './simulation.js';\nimport { parsePajaBlossomUri } from './browser-blossom-uri.js';\n\n/** Maximum decoded payload exposed by Paja's local data-resource backend. */\nexport const PAJA_RESOURCE_MAX_BYTES = 10 * 1024 * 1024;\n/** Maximum URLs accepted by one Paja resource bulk request. */\nexport const PAJA_RESOURCE_MAX_URLS = 100;\n/** Maximum combined request-hint and host-default Blossom servers per resource. */\nexport const PAJA_RESOURCE_MAX_SERVERS = 8;\n\nconst UTF8_DECODER = new TextDecoder('utf-8', { fatal: true });\nconst GAME_BOY_NINTENDO_LOGO = [\n 0xce, 0xed, 0x66, 0x66, 0xcc, 0x0d, 0x00, 0x0b,\n 0x03, 0x73, 0x00, 0x83, 0x00, 0x0c, 0x00, 0x0d,\n 0x00, 0x08, 0x11, 0x1f, 0x88, 0x89, 0x00, 0x0e,\n 0xdc, 0xcc, 0x6e, 0xe6, 0xdd, 0xdd, 0xd9, 0x99,\n 0xbb, 0xbb, 0x67, 0x63, 0x6e, 0x0e, 0xec, 0xcc,\n 0xdd, 0xdc, 0x99, 0x9f, 0xbb, 0xb9, 0x33, 0x3e,\n] as const;\n\n/** Host-owned inputs for Paja's NAP-RESOURCE fetch boundary. */\nexport interface PajaResourceFetchOptions {\n /** Return ordered runtime candidates for this source-scoped Blossom URL. */\n readonly getBlossomServers?: (context: {\n readonly url: string;\n readonly windowId?: string;\n }) => readonly string[] | Promise<readonly string[]>;\n /** Fetch implementation used for configured Blossom server requests. */\n readonly fetch?: typeof fetch;\n}\n\n/**\n * Create Paja's developer-oriented NAP-RESOURCE fetch boundary.\n *\n * `data:` bytes are decoded locally. `blossom:<hex>` URLs and the\n * `blossom:sha256:<hex>` compatibility alias are\n * resolved through accepted request hints followed by host-configured Blossom\n * servers, with redirects disabled and SHA-256 verified before delivery.\n * Direct HTTP(S) URLs are resolved from any origin through the browser,\n * subject to its CORS rules. All bytes are capped and classified locally;\n * upstream media types are ignored.\n *\n * @param options - Host-owned Blossom server and transport hooks.\n * @returns A sanitized resource fetch implementation.\n */\nexport function createPajaResourceFetch(\n options: PajaResourceFetchOptions = {},\n): ResourceServiceOptions['fetch'] {\n return async (value, init) => {\n if (init.signal.aborted) throw new DOMException('Resource request cancelled', 'AbortError');\n const url = new URL(value);\n if (init.method && init.method.toUpperCase() !== 'GET') {\n throw new ResourceServiceError('invalid-request', 'NAP-RESOURCE is read-only');\n }\n if (url.protocol === 'blossom:') {\n return fetchBlossomResource(\n value,\n init.signal,\n init.servers ?? [],\n init.windowId,\n options,\n );\n }\n if (url.protocol === 'http:' || url.protocol === 'https:') {\n return fetchNetworkResource(value, init.signal, options.fetch ?? globalThis.fetch);\n }\n if (url.protocol !== 'data:') {\n throw new ResourceServiceError('unsupported-scheme', `Paja does not enable ${url.protocol}`);\n }\n if (value.length > encodedDataUrlLimit(PAJA_RESOURCE_MAX_BYTES)) {\n throw new ResourceServiceError('too-large', `resource exceeds ${PAJA_RESOURCE_MAX_BYTES} bytes`);\n }\n\n const decoded = await fetch(value, { signal: init.signal });\n const bytes = new Uint8Array(await decoded.arrayBuffer());\n if (bytes.byteLength > PAJA_RESOURCE_MAX_BYTES) {\n throw new ResourceServiceError('too-large', `resource exceeds ${PAJA_RESOURCE_MAX_BYTES} bytes`);\n }\n const classification = classifyResourceBytes(bytes);\n if (classification.kind !== 'recognized') {\n throw new ResourceServiceError('decode-failed', 'resource bytes are not an enabled safe media type');\n }\n return new Response(arrayBufferFor(bytes), { headers: { 'content-type': classification.mime } });\n };\n}\n\n/**\n * Return Paja's truthful NAP-RESOURCE policy disclosure.\n *\n * @returns Current resource schemes and enforced limits.\n */\nexport function pajaResourceInfo(): ResourceInfo {\n const schemes = [\n { scheme: 'data', enabled: true },\n { scheme: 'https', enabled: true },\n { scheme: 'http', enabled: true },\n { scheme: 'blossom', enabled: true },\n ];\n return {\n schemes,\n maxBytes: PAJA_RESOURCE_MAX_BYTES,\n maxUrls: PAJA_RESOURCE_MAX_URLS,\n maxServers: PAJA_RESOURCE_MAX_SERVERS,\n };\n}\n\nasync function fetchNetworkResource(\n value: string,\n signal: AbortSignal,\n fetcher: typeof fetch,\n): Promise<Response> {\n let response: Response;\n try {\n response = await fetcher(value, {\n method: 'GET',\n signal,\n credentials: 'omit',\n referrerPolicy: 'no-referrer',\n });\n } catch (error: unknown) {\n if (signal.aborted || isAbortError(error)) throw error;\n throw new ResourceServiceError(\n 'network-error',\n error instanceof Error ? error.message : String(error),\n );\n }\n if (response.status === 404 || response.status === 410) {\n throw new ResourceServiceError('not-found', 'HTTP resource was not found');\n }\n if (!response.ok) {\n throw new ResourceServiceError('network-error', `HTTP resource returned ${response.status}`);\n }\n\n const bytes = await readCappedResponse(response, signal);\n const classification = classifyResourceBytes(bytes);\n if (classification.kind !== 'recognized') {\n throw new ResourceServiceError('decode-failed', 'resource bytes are not an enabled safe media type');\n }\n return new Response(arrayBufferFor(bytes), { headers: { 'content-type': classification.mime } });\n}\n\nasync function fetchBlossomResource(\n value: string,\n signal: AbortSignal,\n requestServers: readonly string[],\n windowId: string | undefined,\n options: PajaResourceFetchOptions,\n): Promise<Response> {\n const parsed = parsePajaBlossomUri(value);\n if (!parsed) {\n throw new ResourceServiceError('invalid-request', 'invalid Blossom URI');\n }\n if (parsed.expectedSize !== undefined && parsed.expectedSize > PAJA_RESOURCE_MAX_BYTES) throw resourceTooLarge();\n const expectedHash = parsed.hash;\n const configuredServers = await options.getBlossomServers?.({ url: value, windowId }) ?? [];\n if (signal.aborted) throw new DOMException('Resource request cancelled', 'AbortError');\n const uriServers = parsed.servers.flatMap((value) => {\n const server = normalizeBlossomUriServer(value);\n return server ? [server] : [];\n });\n const servers = resolveBlossomServers([...requestServers, ...uriServers], configuredServers);\n if (servers.length === 0) {\n throw new ResourceServiceError('blocked-by-policy', 'Paja has no accepted Blossom server');\n }\n\n const fetcher = options.fetch ?? globalThis.fetch;\n let foundHashMismatch = false;\n let foundNotFound = false;\n let foundInconclusive = false;\n let lastNetworkMessage = 'all accepted Blossom servers failed';\n for (const server of servers) {\n if (signal.aborted) throw new DOMException('Resource request cancelled', 'AbortError');\n const resourceUrl = `${server}/${expectedHash}${parsed.extension ? `.${parsed.extension}` : ''}`;\n let response: Response;\n try {\n response = await fetcher(resourceUrl, {\n method: 'GET',\n signal,\n redirect: 'error',\n cache: 'no-store',\n credentials: 'omit',\n referrerPolicy: 'no-referrer',\n });\n } catch (error: unknown) {\n if (signal.aborted || isAbortError(error)) throw error;\n foundInconclusive = true;\n lastNetworkMessage = error instanceof Error ? error.message : String(error);\n continue;\n }\n if (response.status === 404 || response.status === 410) {\n foundNotFound = true;\n continue;\n }\n if (!response.ok) {\n foundInconclusive = true;\n lastNetworkMessage = `Blossom server returned HTTP ${response.status}`;\n continue;\n }\n\n const declared = response.headers.get('content-length');\n if (declared !== null && Number(declared) > PAJA_RESOURCE_MAX_BYTES) throw resourceTooLarge();\n if (parsed.expectedSize !== undefined && declared !== null && /^[0-9]+$/u.test(declared)\n && Number(declared) !== parsed.expectedSize) {\n await response.body?.cancel();\n foundHashMismatch = true;\n continue;\n }\n const bytes = await readCappedResponse(response, signal);\n if ((parsed.expectedSize !== undefined && bytes.byteLength !== parsed.expectedSize)\n || !verifyBlobHash(bytes, expectedHash)) {\n foundHashMismatch = true;\n continue;\n }\n const classification = classifyResourceBytes(bytes);\n if (classification.kind === 'blocked') {\n throw new ResourceServiceError('decode-failed', 'resource bytes are not an enabled safe media type');\n }\n const mime = classification.kind === 'recognized' ? classification.mime : 'application/octet-stream';\n return new Response(arrayBufferFor(bytes), { headers: { 'content-type': mime } });\n }\n\n if (foundHashMismatch) {\n throw new ResourceServiceError('decode-failed', 'Blossom response did not match the requested SHA-256 or size');\n }\n if (foundInconclusive) {\n throw new ResourceServiceError('network-error', lastNetworkMessage);\n }\n if (foundNotFound) {\n throw new ResourceServiceError('not-found', 'Blossom resource was not found');\n }\n throw new ResourceServiceError('network-error', lastNetworkMessage);\n}\n\nfunction resolveBlossomServers(\n requestServers: readonly string[],\n configuredServers: readonly string[],\n): string[] {\n const servers: string[] = [];\n for (const value of requestServers) {\n const server = normalizePublicBlossomServer(value);\n if (server && !servers.includes(server)) servers.push(server);\n if (servers.length === PAJA_RESOURCE_MAX_SERVERS) return servers;\n }\n for (const server of usableBlossomServers(configuredServers)) {\n if (!servers.includes(server)) servers.push(server);\n if (servers.length === PAJA_RESOURCE_MAX_SERVERS) break;\n }\n return servers;\n}\n\n/** Normalize an untrusted Blossom hint to one public HTTPS origin. */\nexport function normalizeBlossomUriServer(value: string): string | null {\n if (/^[a-z0-9.-]+(?::\\d+)?$/i.test(value)) {\n return normalizePublicBlossomServer(`https://${value}`);\n }\n return normalizePublicBlossomServer(value);\n}\n\n/** Normalize an untrusted explicit server to one public HTTPS origin. */\nexport function normalizePublicBlossomServer(value: string): string | null {\n if (typeof value !== 'string' || value.trim().length === 0) return null;\n try {\n const url = new URL(value.trim());\n if (\n url.protocol !== 'https:'\n || url.username\n || url.password\n || url.search\n || url.hash\n || url.pathname !== '/'\n || !isPublicHostname(url.hostname)\n ) return null;\n return url.origin;\n } catch {\n return null;\n }\n}\n\n// Paja is a browser-only developer runtime: reject obvious local/private\n// literals here, while production resolvers must additionally enforce the\n// NAP-RESOURCE DNS-time address check before connecting and after redirects.\nfunction isPublicHostname(value: string): boolean {\n const hostname = value.replace(/^\\[|\\]$/gu, '').toLowerCase();\n if (\n hostname === 'localhost'\n || hostname.endsWith('.localhost')\n || hostname.endsWith('.local')\n || hostname.endsWith('.internal')\n || !hostname.includes('.')\n ) return false;\n if (hostname.includes(':')) {\n return hostname !== '::'\n && hostname !== '::1'\n && !hostname.startsWith('::ffff:')\n && !/^(?:fc|fd|fe[89ab]|ff)/iu.test(hostname)\n && !hostname.startsWith('2001:db8:');\n }\n const octets = hostname.split('.').map(Number);\n if (octets.length !== 4 || octets.some((octet) => !Number.isInteger(octet) || octet < 0 || octet > 255)) return true;\n const [first, second] = octets as [number, number, number, number];\n return first !== 0\n && first !== 10\n && first !== 127\n && first < 224\n && !(first === 100 && second >= 64 && second <= 127)\n && !(first === 169 && second === 254)\n && !(first === 172 && second >= 16 && second <= 31)\n && !(first === 192 && second === 168)\n && !(first === 198 && (second === 18 || second === 19));\n}\n\nfunction usableBlossomServers(values: readonly string[]): string[] {\n const servers: string[] = [];\n for (const value of values) {\n try {\n const [server] = normalizeUploadServers([value]);\n if (server && !servers.includes(server)) servers.push(server);\n } catch {\n // Host config can contain stale or untrusted discovery hints. Ignore them.\n }\n }\n return servers;\n}\n\nasync function readCappedResponse(response: Response, signal: AbortSignal): Promise<Uint8Array> {\n const declaredLength = Number(response.headers.get('content-length'));\n if (Number.isFinite(declaredLength) && declaredLength > PAJA_RESOURCE_MAX_BYTES) {\n throw resourceTooLarge();\n }\n if (!response.body) {\n const bytes = new Uint8Array(await response.arrayBuffer());\n if (bytes.byteLength > PAJA_RESOURCE_MAX_BYTES) throw resourceTooLarge();\n return bytes;\n }\n\n const reader = response.body.getReader();\n const chunks: Uint8Array[] = [];\n let total = 0;\n try {\n while (true) {\n if (signal.aborted) throw new DOMException('Resource request cancelled', 'AbortError');\n const { done, value } = await reader.read();\n if (done) break;\n total += value.byteLength;\n if (total > PAJA_RESOURCE_MAX_BYTES) {\n await reader.cancel();\n throw resourceTooLarge();\n }\n chunks.push(value);\n }\n } finally {\n reader.releaseLock();\n }\n\n const bytes = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n bytes.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return bytes;\n}\n\nfunction resourceTooLarge(): ResourceServiceError {\n return new ResourceServiceError('too-large', `resource exceeds ${PAJA_RESOURCE_MAX_BYTES} bytes`);\n}\n\nfunction isAbortError(error: unknown): boolean {\n return error instanceof Error && (error.name === 'AbortError' || error.name === 'DOMException');\n}\n\nfunction arrayBufferFor(bytes: Uint8Array): ArrayBuffer {\n return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer;\n}\n\nfunction encodedDataUrlLimit(maxBytes: number): number {\n return Math.ceil(maxBytes * 4 / 3) + 1024;\n}\n\nfunction startsWith(bytes: Uint8Array, signature: readonly number[], offset = 0): boolean {\n return signature.every((value, index) => bytes[offset + index] === value);\n}\n\nfunction ascii(bytes: Uint8Array, start: number, end: number): string {\n return String.fromCharCode(...bytes.subarray(start, end));\n}\n\ntype ResourceClassification =\n | { kind: 'recognized'; mime: string }\n | { kind: 'opaque' }\n | { kind: 'blocked' };\n\nfunction classifyResourceBytes(bytes: Uint8Array): ResourceClassification {\n const mime = sniffBinaryMime(bytes);\n if (mime) return { kind: 'recognized', mime };\n return classifyTextBytes(bytes);\n}\n\nfunction sniffBinaryMime(bytes: Uint8Array): string | null {\n if (startsWith(bytes, [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])) return 'image/png';\n if (startsWith(bytes, [0xff, 0xd8, 0xff])) return 'image/jpeg';\n if (ascii(bytes, 0, 6) === 'GIF87a' || ascii(bytes, 0, 6) === 'GIF89a') return 'image/gif';\n if (ascii(bytes, 0, 4) === 'RIFF' && ascii(bytes, 8, 12) === 'WEBP') return 'image/webp';\n if (ascii(bytes, 0, 4) === 'RIFF' && ascii(bytes, 8, 12) === 'WAVE') return 'audio/wav';\n if (ascii(bytes, 0, 4) === 'OggS') return 'audio/ogg';\n if (ascii(bytes, 0, 3) === 'ID3' || startsWith(bytes, [0xff, 0xfb])) return 'audio/mpeg';\n if (startsWith(bytes, [0x1a, 0x45, 0xdf, 0xa3])) return 'video/webm';\n if (ascii(bytes, 4, 8) === 'ftyp') return 'video/mp4';\n if (ascii(bytes, 0, 4) === 'wOFF') return 'font/woff';\n if (ascii(bytes, 0, 4) === 'wOF2') return 'font/woff2';\n if (isGameBoyRom(bytes)) return 'application/vnd.nintendo.gb-rom';\n return null;\n}\n\nfunction isGameBoyRom(bytes: Uint8Array): boolean {\n if (bytes.byteLength < 0x150 || !startsWith(bytes, GAME_BOY_NINTENDO_LOGO, 0x104)) return false;\n let checksum = 0;\n for (let index = 0x134; index <= 0x14c; index += 1) {\n checksum = (checksum - (bytes[index] ?? 0) - 1) & 0xff;\n }\n return checksum === bytes[0x14d];\n}\n\nfunction classifyTextBytes(bytes: Uint8Array): ResourceClassification {\n // Inspect markup before strict decoding: invalid UTF-8 or NUL later in an\n // active document must not turn a blocked prefix into opaque Blossom bytes.\n const normalized = new TextDecoder().decode(bytes).trimStart().toLowerCase();\n if (hasBlockedMarkupPrefix(normalized) || hasBlockedEncodedMarkup(bytes)) return { kind: 'blocked' };\n let text: string;\n try {\n text = UTF8_DECODER.decode(bytes);\n } catch {\n return { kind: 'opaque' };\n }\n if (/\\u0000/u.test(text)) return { kind: 'opaque' };\n if (normalized.startsWith('{') || normalized.startsWith('[')) {\n try {\n JSON.parse(text);\n return { kind: 'recognized', mime: 'application/json' };\n } catch {\n return { kind: 'recognized', mime: 'text/plain' };\n }\n }\n return { kind: 'recognized', mime: 'text/plain' };\n}\n\nfunction hasBlockedMarkupPrefix(text: string): boolean {\n const normalized = text.trimStart().toLowerCase();\n return (\n normalized.startsWith('<svg')\n || normalized.startsWith('<?xml')\n || normalized.startsWith('<!doctype html')\n || normalized.startsWith('<html')\n || normalized.startsWith('<script')\n );\n}\n\nfunction hasBlockedEncodedMarkup(bytes: Uint8Array): boolean {\n // Conservatively reject identifiable UTF-32 documents; no UTF-32 media\n // format is enabled. Check these before the overlapping UTF-16LE BOM.\n if (\n startsWith(bytes, [0xff, 0xfe, 0x00, 0x00])\n || startsWith(bytes, [0x00, 0x00, 0xfe, 0xff])\n || startsWith(bytes, [0x3c, 0x00, 0x00, 0x00])\n || startsWith(bytes, [0x00, 0x00, 0x00, 0x3c])\n ) return true;\n\n let encoding: string;\n if (startsWith(bytes, [0xff, 0xfe]) || startsWith(bytes, [0x3c, 0x00])) {\n encoding = 'utf-16le';\n } else if (startsWith(bytes, [0xfe, 0xff]) || startsWith(bytes, [0x00, 0x3c])) {\n encoding = 'utf-16be';\n } else {\n return false;\n }\n return hasBlockedMarkupPrefix(new TextDecoder(encoding).decode(bytes));\n}\n","import type { ShellCapabilities } from '@kehto/shell';\n\n/** The exact host-visible shell environment shape shared by bootstrap and init. */\nexport type PajaShellEnvironment = Readonly<{\n capabilities: ShellCapabilities;\n services: readonly string[];\n}>;\n\nconst PAJA_LEGACY_COMPATIBILITY_DOMAIN: string = `i${'fc'}`;\n\n/** Domain list mirrored from the upstream web runtime for parity checks. */\nexport const PAJA_UPSTREAM_WEB_DOMAINS = [\n 'ble',\n 'common',\n 'config',\n 'count',\n 'cvm',\n 'dm',\n 'fs',\n 'identity',\n PAJA_LEGACY_COMPATIBILITY_DOMAIN,\n 'inc',\n 'intent',\n 'keys',\n 'link',\n 'lists',\n 'media',\n 'notify',\n 'outbox',\n 'relay',\n 'resource',\n 'serial',\n 'storage',\n 'theme',\n 'upload',\n 'webrtc',\n] as const;\n\n/** Domains required during the initial Paja handshake. */\nexport const PAJA_HANDSHAKE_DOMAINS = ['shell'] as const;\n/** Upstream domains intentionally deferred because no runtime implementation exists. */\nexport const PAJA_DEFERRED_DOMAINS = [] as const;\n\n/** Compatibility aliases for renamed upstream capability domains. */\nexport const PAJA_COMPATIBILITY_ALIASES = {\n [PAJA_LEGACY_COMPATIBILITY_DOMAIN]: 'inc',\n} as const;\n\n/** Domains Paja may advertise when each corresponding host backend is live. */\nexport const PAJA_ADVERTISED_DOMAINS = [\n 'relay',\n 'outbox',\n 'identity',\n 'storage',\n 'inc',\n 'theme',\n 'keys',\n 'link',\n 'common',\n 'lists',\n 'serial',\n 'ble',\n 'webrtc',\n 'media',\n 'notify',\n 'config',\n 'resource',\n 'cvm',\n 'upload',\n 'intent',\n 'count',\n 'dm',\n 'fs',\n] as const;\n\n/** Service names Paja expects the host runtime to provide. */\nexport const PAJA_REQUIRED_SERVICES = [\n 'config',\n 'cvm',\n 'identity',\n 'intent',\n 'keys',\n 'link',\n 'common',\n 'lists',\n 'serial',\n 'ble',\n 'webrtc',\n 'media',\n 'notify',\n 'outbox',\n 'relay',\n 'resource',\n 'theme',\n 'upload',\n 'count',\n 'dm',\n 'fs',\n] as const;\n\n/**\n * Find advertised Paja domains missing from a shell capability snapshot.\n *\n * @param capabilities - Shell capability snapshot to inspect.\n * @returns Advertised domains not present in the snapshot.\n */\nexport function getMissingAdvertisedDomains(capabilities: ShellCapabilities): string[] {\n const advertised = new Set(capabilities.domains);\n return PAJA_ADVERTISED_DOMAINS.filter((domain) => !advertised.has(domain));\n}\n\n/**\n * Find required service names missing from a runtime service list.\n *\n * @param services - Runtime service names to inspect.\n * @returns Required service names that are absent.\n */\nexport function getMissingServices(services: readonly string[]): string[] {\n const wired = new Set(services);\n return PAJA_REQUIRED_SERVICES.filter((service) => !wired.has(service));\n}\n\n/**\n * Compare the observable membership of two independently resolved Paja shell\n * environments. Resolver calls intentionally return different frozen objects.\n */\nexport function hasEqualPajaEnvironmentMembership(\n left: PajaShellEnvironment,\n right: PajaShellEnvironment,\n): boolean {\n return left.capabilities.domains.length === right.capabilities.domains.length\n && left.capabilities.domains.every((domain, index) => domain === right.capabilities.domains[index])\n && left.services.length === right.services.length\n && left.services.every((service, index) => service === right.services[index]);\n}\n","/** Options for polling a target URL until it is ready. */\nexport interface WaitForTargetUrlOptions {\n /** Total wait budget in milliseconds. */\n readonly timeoutMs: number;\n /** Poll interval in milliseconds. Defaults to 250. */\n readonly intervalMs?: number;\n /** Fetch implementation override for tests. */\n readonly fetch?: ReadinessFetch;\n /** Sleep implementation override for tests. */\n readonly sleep?: (ms: number) => Promise<void>;\n /** Clock override for tests. */\n readonly now?: () => number;\n}\n\n/** Fetch-like target readiness probe. */\nexport type ReadinessFetch = (url: string) => Promise<{ readonly status: number }>;\n\n/** Thrown when a target URL does not become ready before the timeout. */\nexport class ReadinessError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ReadinessError';\n }\n}\n\n/**\n * Poll a target URL until it returns a non-5xx response or times out.\n *\n * @param url - Target URL to poll.\n * @param options - Timeout and polling controls.\n */\nexport async function waitForTargetUrl(url: string, options: WaitForTargetUrlOptions): Promise<void> {\n const intervalMs = options.intervalMs ?? 250;\n const fetcher = options.fetch ?? defaultFetch;\n const sleep = options.sleep ?? defaultSleep;\n const now = options.now ?? Date.now;\n const deadline = now() + options.timeoutMs;\n let lastError = 'no response';\n\n while (now() <= deadline) {\n try {\n const response = await fetcher(url);\n if (response.status < 500) {\n return;\n }\n lastError = `status ${response.status}`;\n } catch (error) {\n lastError = error instanceof Error ? error.message : String(error);\n }\n\n await sleep(intervalMs);\n }\n\n throw new ReadinessError(`Timed out waiting ${options.timeoutMs}ms for target URL ${url}: ${lastError}`);\n}\n\nasync function defaultFetch(url: string): Promise<{ readonly status: number }> {\n return fetch(url, { method: 'GET', redirect: 'follow' });\n}\n\nfunction defaultSleep(ms: number): Promise<void> {\n return new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n}\n","import { computeAggregateHash } from '@kehto/nip/5a';\n\nimport type { PajaResolvedPointer } from './runtime-resolver.js';\n\n/**\n * A napplet opened from a local `index.html` file in Paja runtime-pointer mode.\n *\n * Spec-gap decision: NIP-5D's Identity section resolves a signed manifest,\n * fetches the artifact from Blossom, and verifies its hash before it creates\n * the iframe. A local file has no manifest or signature, so it never takes that\n * path. Paja treats local files as an unsigned development target. Identity is\n * still computed from the file's own bytes (never taken from the host), the\n * file loads through the same sandboxed `srcdoc` + CSP + `window.napplet`\n * prelude path as verified pointers, and it never enters the installed\n * napplet catalog. See `docs/policies/NIP-5D-CONFORMANCE.md`.\n */\nexport interface PajaLocalTarget {\n /** Discriminant separating local files from relay/Blossom-resolved pointers. */\n readonly source: 'local';\n /** File name as reported by the browser file picker or drop event. */\n readonly fileName: string;\n /** `<meta name=\"napplet-id\">` value, or `local-<file stem>` when absent. */\n readonly dTag: string;\n /** NIP-5A aggregate hash over the single `/index.html` path entry. */\n readonly aggregateHash: string;\n /** sha256 of the exact file bytes, lowercase hex. */\n readonly sha256: string;\n /** Decoded file content assigned to `srcdoc` after CSP and prelude injection. */\n readonly indexHtml: string;\n /** Always empty: a local file carries no relay hints. */\n readonly relays: readonly string[];\n /** Always empty: a local file carries no Blossom hints. */\n readonly blossomServers: readonly string[];\n /** Relative `src`/`href` references that cannot load from `srcdoc`. */\n readonly relativeAssets: readonly string[];\n}\n\n/** Either a verified pointer target or a local development file target. */\nexport type PajaRuntimeTarget = PajaResolvedPointer | PajaLocalTarget;\n\n/**\n * File input accepted by {@link createPajaLocalTarget}. A browser `File`\n * satisfies the first shape; tests and tools may pass text directly.\n */\nexport type PajaLocalFileInput =\n | { readonly name: string; readonly type?: string; arrayBuffer(): Promise<ArrayBuffer> }\n | { readonly name: string; readonly type?: string; readonly text: string };\n\n/** Hint appended to local-file errors and relative-asset warnings. */\nexport const PAJA_LOCAL_SINGLE_FILE_HINT =\n 'Paja opens self-contained single-file index.html only; relative scripts, styles, and images do not load.';\n\nconst MAX_DTAG_LENGTH = 128;\nconst MAX_REPORTED_RELATIVE_ASSETS = 8;\n\n/**\n * Return whether a runtime target was opened from a local file.\n *\n * @param target - Runtime target, or any object carrying a `source` field.\n * @returns `true` for {@link PajaLocalTarget} values.\n *\n * @example\n * ```ts\n * if (isPajaLocalTarget(tab.resolvedTarget)) console.log(tab.resolvedTarget.fileName);\n * ```\n */\nexport function isPajaLocalTarget(target: object | null | undefined): target is PajaLocalTarget {\n return typeof target === 'object' && target !== null\n && (target as { readonly source?: unknown }).source === 'local';\n}\n\n/**\n * Return whether a file name or MIME type identifies an HTML document.\n *\n * @param file - File name and optional MIME type.\n * @returns `true` for `.html`/`.htm` names or a `text/html` type.\n *\n * @example\n * ```ts\n * isPajaLocalHtmlFile({ name: 'index.html' }); // true\n * ```\n */\nexport function isPajaLocalHtmlFile(file: { readonly name: string; readonly type?: string }): boolean {\n const type = (file.type ?? '').split(';')[0]?.trim().toLowerCase();\n return type === 'text/html' || /\\.html?$/i.test(file.name.trim());\n}\n\n/**\n * Build a local runtime target from an `index.html` file.\n *\n * The identity is computed from the file bytes: `sha256` is the digest of the\n * exact bytes and `aggregateHash` is the NIP-5A aggregate over\n * `[{ path: '/index.html', sha256 }]`, the same derivation Paja's resolver\n * verifies for a published single-file napplet. Editing the file gives it a\n * new identity.\n *\n * @param file - Browser `File` or `{ name, text }`.\n * @returns The local target.\n * @throws Error when the file is not HTML or is empty.\n *\n * @example\n * ```ts\n * const target = await createPajaLocalTarget({ name: 'index.html', text: '<!doctype html>…' });\n * target.dTag; // 'local-index'\n * ```\n */\nexport async function createPajaLocalTarget(file: PajaLocalFileInput): Promise<PajaLocalTarget> {\n const fileName = file.name.trim() || 'index.html';\n if (!isPajaLocalHtmlFile(file)) {\n throw new Error(`\"${fileName}\" is not an HTML file. ${PAJA_LOCAL_SINGLE_FILE_HINT}`);\n }\n const bytes = 'arrayBuffer' in file\n ? new Uint8Array(await file.arrayBuffer())\n : new TextEncoder().encode(file.text);\n const indexHtml = new TextDecoder().decode(bytes);\n if (indexHtml.trim().length === 0) {\n throw new Error(`\"${fileName}\" is empty.`);\n }\n const sha256 = await sha256Hex(bytes);\n return Object.freeze({\n source: 'local',\n fileName,\n dTag: readNappletIdMeta(indexHtml) ?? localDTag(fileName),\n aggregateHash: computeAggregateHash([{ path: '/index.html', sha256 }]),\n sha256,\n indexHtml,\n relays: Object.freeze([]),\n blossomServers: Object.freeze([]),\n relativeAssets: Object.freeze(findRelativeAssetReferences(indexHtml)),\n });\n}\n\n/**\n * Read the NIP-5D publishing metadata `<meta name=\"napplet-id\" content=\"…\">`.\n *\n * @param html - HTML document text.\n * @returns The trimmed id, or `undefined` when absent or blank.\n *\n * @example\n * ```ts\n * readNappletIdMeta('<meta name=\"napplet-id\" content=\"feed\">'); // 'feed'\n * ```\n */\nexport function readNappletIdMeta(html: string): string | undefined {\n for (const match of html.matchAll(/<meta\\b([^>]*)>/gi)) {\n const attributes = parseAttributes(match[1] ?? '');\n if (attributes.get('name')?.toLowerCase() !== 'napplet-id') continue;\n const value = attributes.get('content')?.trim();\n if (value) return value.slice(0, MAX_DTAG_LENGTH);\n }\n return undefined;\n}\n\n/**\n * List relative `src`/`href` references that a `srcdoc` document cannot load.\n *\n * @param html - HTML document text.\n * @returns Up to eight distinct relative references, in document order.\n *\n * @example\n * ```ts\n * findRelativeAssetReferences('<script src=\"./main.js\"></script>'); // ['./main.js']\n * ```\n */\nexport function findRelativeAssetReferences(html: string): string[] {\n const found = new Set<string>();\n for (const match of html.matchAll(/<(?:script|link|img|source|iframe)\\b([^>]*)>/gi)) {\n const attributes = parseAttributes(match[1] ?? '');\n const reference = attributes.get('src') ?? attributes.get('href');\n if (reference && isRelativeReference(reference)) found.add(reference.trim());\n if (found.size >= MAX_REPORTED_RELATIVE_ASSETS) break;\n }\n return [...found];\n}\n\nfunction isRelativeReference(value: string): boolean {\n const trimmed = value.trim();\n if (trimmed.length === 0 || trimmed.startsWith('#')) return false;\n return !/^(?:[a-z][a-z0-9+.-]*:|\\/\\/)/i.test(trimmed);\n}\n\nfunction parseAttributes(source: string): Map<string, string> {\n const attributes = new Map<string, string>();\n const pattern = /([^\\s=/>\"']+)(?:\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\s\"'=<>`]+)))?/g;\n for (const match of source.matchAll(pattern)) {\n const name = match[1]?.toLowerCase();\n if (!name || attributes.has(name)) continue;\n attributes.set(name, match[2] ?? match[3] ?? match[4] ?? '');\n }\n return attributes;\n}\n\nfunction localDTag(fileName: string): string {\n const stem = fileName.replace(/\\.[^.]*$/, '');\n const slug = stem.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');\n return `local-${slug || 'napplet'}`.slice(0, MAX_DTAG_LENGTH);\n}\n\nasync function sha256Hex(bytes: Uint8Array<ArrayBuffer>): Promise<string> {\n const digest = await crypto.subtle.digest('SHA-256', bytes);\n return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, '0')).join('');\n}\n","/**\n * Persistent Paja catalog of resolver-verified napplet installations.\n *\n * The catalog deliberately contains only serializable facts copied from a\n * verified pointer result. Runtime frames, sessions, and controller state are\n * owned elsewhere, so closing a tab cannot alter handler eligibility.\n *\n * @packageDocumentation\n */\n\nimport { manifestToIntentCatalogEntry } from '@kehto/services';\nimport type { IntentCatalogEntry } from '@kehto/services';\nimport type { PajaDecodedPointer, PajaResolvedPointer } from './runtime-resolver.js';\n\n/** Serializable verified pointer and manifest facts for one installed napplet. */\nexport interface InstalledNappletRecord {\n /** Verified manifest d-tag used as the handler identity. */\n readonly dTag: string;\n /** Verified NIP-5A aggregate hash for the installed artifact. */\n readonly aggregateHash: string;\n /** Pointer identity that can later be re-resolved and verified. */\n readonly pointer: PajaDecodedPointer;\n /** Optional manifest title for handler selection UI. */\n readonly title?: string;\n /** Verified NAP domains required by the artifact. */\n readonly requires: readonly string[];\n /** Optional integrations; declarations do not grant capabilities. */\n readonly optional?: readonly string[];\n /** Exact manifest convention contracts used for intent eligibility. */\n readonly archetypes: readonly {\n readonly slug: string;\n readonly convention: string;\n }[];\n}\n\n/** Return whether a live runtime target is exactly the installed verified artifact. */\nexport function matchesInstalledNappletRecord(\n record: Pick<InstalledNappletRecord, 'dTag' | 'aggregateHash'>,\n target: Pick<InstalledNappletRecord, 'dTag' | 'aggregateHash'>,\n): boolean {\n return target.dTag === record.dTag && target.aggregateHash === record.aggregateHash;\n}\n\n/** Listener notified when an installed artifact is inserted or removed. */\nexport type InstalledNappletCatalogListener = (dTag: string) => void;\n\n/**\n * Stores verified Paja installations separately from the browser runtime.\n *\n * @example\n * ```ts\n * const catalog = new InstalledNappletCatalog();\n * catalog.install(resolvedPointer);\n * const candidates = catalog.intentCatalog();\n * ```\n */\nexport class InstalledNappletCatalog {\n private readonly records = new Map<string, InstalledNappletRecord>();\n private readonly listeners = new Set<InstalledNappletCatalogListener>();\n\n /** Insert or replace a record from an already resolver-verified pointer. */\n install(resolved: PajaResolvedPointer): InstalledNappletRecord {\n const record = freezeRecord({\n dTag: resolved.dTag,\n aggregateHash: resolved.aggregateHash,\n pointer: copyPointer(resolved.pointer),\n ...(resolved.manifest.title === undefined ? {} : { title: resolved.manifest.title }),\n requires: [...resolved.manifest.requires],\n optional: [...(resolved.manifest.optional ?? [])],\n archetypes: resolved.manifest.archetypes.map((archetype) => ({\n slug: archetype.slug,\n convention: archetype.convention,\n })),\n });\n // NAP-INTENT handler selection is dTag-based. Nameless root/snapshot\n // artifacts may run, but have no key in this named-handler catalog.\n if (!record.dTag) return record;\n this.records.set(record.dTag, record);\n this.notify(record.dTag);\n return record;\n }\n\n /** Remove an artifact explicitly; frame teardown never calls this method. */\n remove(dTag: string): boolean {\n const removed = this.records.delete(dTag);\n if (removed) this.notify(dTag);\n return removed;\n }\n\n /** Return whether a verified installation exists for a d-tag. */\n has(dTag: string): boolean {\n return this.records.has(dTag);\n }\n\n /** Return immutable serializable installed-artifact facts. */\n installed(): readonly InstalledNappletRecord[] {\n return [...this.records.values()];\n }\n\n /** Return a verified record by d-tag. */\n get(dTag: string): InstalledNappletRecord | undefined {\n return this.records.get(dTag);\n }\n\n /**\n * Atomically confirm that a resolved target still belongs to the exact record\n * selected before an async operation. The record object is the catalog version\n * token, so even a same-identity replacement cannot pass this check.\n */\n validateCurrent(\n selected: InstalledNappletRecord,\n target: Pick<InstalledNappletRecord, 'dTag' | 'aggregateHash'>,\n ): InstalledNappletRecord | null {\n if (this.records.get(selected.dTag) !== selected) return null;\n return matchesInstalledNappletRecord(selected, target) ? selected : null;\n }\n\n /**\n * Return declared handlers whose host environment can receive intent delivery.\n * @param canReceiveIntent - Resolve intent availability for each verified identity;\n * without host policy, return declared candidates for the host to filter.\n * @returns Named intent candidates usable under the supplied host policy.\n */\n intentCatalog(\n canReceiveIntent: (record: InstalledNappletRecord) => boolean = () => true,\n ): IntentCatalogEntry[] {\n return this.installed()\n .filter(canReceiveIntent)\n .map((record) => manifestToIntentCatalogEntry({\n dTag: record.dTag,\n ...(record.title === undefined ? {} : { title: record.title }),\n archetypes: record.archetypes.map((archetype) => ({\n slug: archetype.slug,\n convention: archetype.convention,\n })),\n }));\n }\n\n /** Subscribe to explicit install and artifact-removal changes. */\n onChanged(listener: InstalledNappletCatalogListener): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n private notify(dTag: string): void {\n for (const listener of this.listeners) listener(dTag);\n }\n}\n\nfunction copyPointer(pointer: PajaDecodedPointer): PajaDecodedPointer {\n if (pointer.type === 'naddr') {\n return Object.freeze({\n type: 'naddr' as const,\n value: pointer.value,\n identifier: pointer.identifier,\n pubkey: pointer.pubkey,\n kind: pointer.kind,\n relays: Object.freeze([...pointer.relays]),\n });\n }\n return Object.freeze({\n type: 'nevent' as const,\n value: pointer.value,\n id: pointer.id,\n ...(pointer.author === undefined ? {} : { author: pointer.author }),\n ...(pointer.kind === undefined ? {} : { kind: pointer.kind }),\n relays: Object.freeze([...pointer.relays]),\n });\n}\n\nfunction freezeRecord(record: Omit<InstalledNappletRecord, 'archetypes'> & {\n readonly archetypes: readonly {\n readonly slug: string;\n readonly convention: string;\n }[];\n}): InstalledNappletRecord {\n return Object.freeze({\n ...record,\n requires: Object.freeze([...record.requires]),\n optional: Object.freeze([...(record.optional ?? [])]),\n archetypes: Object.freeze(record.archetypes.map((archetype) =>\n Object.freeze({ ...archetype }))),\n });\n}\n","/**\n * Browser-owned NAP-INTENT target lifecycle policy.\n *\n * Retention is intentionally independent from browser frames. The host supplies\n * generation creation/reuse, source-bound readiness, current-generation checks,\n * and convention delivery. The controller resolves only after the target is\n * ready and the delivery is enqueued.\n *\n * @packageDocumentation\n */\n\nimport type {\n IntentDispatchParams,\n IntentTargetController,\n IntentTargetDispatch,\n} from '@kehto/services';\n\n/** Opaque current target generation controlled by the browser host. */\nexport interface BrowserIntentGeneration {\n /** Host-owned stable identifier for one target generation. */\n readonly id: string;\n}\n\n/** Terminal reasons exposed only to host observability and cleanup policy. */\nexport type BrowserIntentTerminalReason =\n | 'open-failed'\n | 'ready-failed'\n | 'no-current-target'\n | 'send-failed';\n\n/** Browser lifecycle callbacks injected by the Paja host. */\nexport interface BrowserIntentControllerOptions {\n /** Open a compatible target or reuse its current generation. */\n openOrReuse(\n params: IntentDispatchParams,\n attempt: number,\n ): BrowserIntentGeneration | null | Promise<BrowserIntentGeneration | null>;\n /** Await source-bound NAP-SHELL readiness for a selected generation. */\n waitForReady(generation: BrowserIntentGeneration): void | Promise<void>;\n /** Return true only while the generation remains the selected current target. */\n isCurrent(generation: BrowserIntentGeneration): boolean | Promise<boolean>;\n /** Return the runtime-assigned window identifier once the target is ready. */\n getWindowId(generation: BrowserIntentGeneration): string | null;\n /** Send the convention through the ordinary carrier to the ready generation. */\n send(\n generation: BrowserIntentGeneration,\n params: IntentDispatchParams,\n ): void | Promise<void>;\n /** Maximum open/replacement attempts. Finite values clamp to 1–10; defaults to two. */\n maxAttempts?: number;\n /** Observe terminal target policy. */\n onTerminal?(params: IntentDispatchParams, reason: BrowserIntentTerminalReason): void;\n}\n\nconst MAX_INTENT_DELIVERY_ATTEMPTS = 10;\n\n/**\n * Creates/focuses a target and enqueues one convention delivery.\n */\nexport class BrowserIntentController implements IntentTargetController {\n private readonly maxAttempts: number;\n\n constructor(private readonly options: BrowserIntentControllerOptions) {\n this.maxAttempts = normalizeAttempts(options.maxAttempts);\n }\n\n async dispatch(params: IntentDispatchParams): Promise<IntentTargetDispatch> {\n const dispatch = freezeDispatch(params);\n let reason: BrowserIntentTerminalReason = 'no-current-target';\n for (let attempt = 1; attempt <= this.maxAttempts; attempt += 1) {\n let generation: BrowserIntentGeneration | null;\n try {\n generation = await this.options.openOrReuse(dispatch, attempt);\n } catch {\n reason = 'open-failed';\n continue;\n }\n if (!generation) {\n reason = 'open-failed';\n continue;\n }\n try {\n await this.options.waitForReady(generation);\n } catch {\n reason = 'ready-failed';\n continue;\n }\n if (!await this.options.isCurrent(generation)) {\n reason = 'no-current-target';\n continue;\n }\n try {\n await this.options.send(generation, dispatch);\n const windowId = this.options.getWindowId(generation);\n if (!windowId) throw new Error('intent target window is unavailable');\n return { windowId };\n } catch {\n this.options.onTerminal?.(dispatch, 'send-failed');\n throw new Error('intent target send failed');\n }\n }\n this.options.onTerminal?.(dispatch, reason);\n throw new Error(`intent target ${reason}`);\n }\n}\n\nfunction normalizeAttempts(value: number | undefined): number {\n if (value === undefined) return 2;\n if (!Number.isFinite(value)) throw new TypeError('maxAttempts must be finite');\n return Math.min(MAX_INTENT_DELIVERY_ATTEMPTS, Math.max(1, Math.floor(value)));\n}\n\nfunction freezeDispatch(params: IntentDispatchParams): IntentDispatchParams {\n if (!params || typeof params.handler !== 'string' || params.handler.length === 0) {\n throw new TypeError('Intent dispatch requires a handler');\n }\n if (\n typeof params.sender !== 'string'\n || params.sender.length === 0\n || typeof params.archetype !== 'string'\n || params.archetype.length === 0\n || typeof params.action !== 'string'\n || params.action.length === 0\n || typeof params.convention !== 'string'\n || params.convention.length === 0\n ) {\n throw new TypeError('Intent dispatch requires canonical routing fields');\n }\n const payload = freezeValue(params.payload);\n const behavior = params.behavior === undefined\n ? undefined\n : freezeValue({ ...params.behavior }) as NonNullable<IntentDispatchParams['behavior']>;\n return Object.freeze({\n handler: params.handler,\n sender: params.sender,\n archetype: params.archetype,\n action: params.action,\n convention: params.convention,\n ...(params.payload === undefined ? {} : { payload }),\n ...(behavior === undefined ? {} : { behavior }),\n });\n}\n\nfunction freezeValue<T>(value: T, seen = new WeakMap<object, unknown>()): T {\n if (typeof value !== 'object' || value === null) return value;\n const existing = seen.get(value);\n if (existing !== undefined) return existing as T;\n if (Array.isArray(value)) {\n const copy: unknown[] = [];\n seen.set(value, copy);\n for (const item of value) copy.push(freezeValue(item, seen));\n return Object.freeze(copy) as T;\n }\n const copy: Record<string, unknown> = {};\n seen.set(value, copy);\n for (const [key, item] of Object.entries(value)) copy[key] = freezeValue(item, seen);\n return Object.freeze(copy) as T;\n}\n","import { createServer } from 'node:http';\nimport { readFileSync } from 'node:fs';\nimport {\n createPajaHostConfig,\n formatPajaUrl,\n normalizePajaOptions,\n type PajaHostConfig,\n type PajaOptions,\n type PajaRawOptions,\n} from './options.js';\nimport { renderPajaHtml } from './host-page.js';\nimport { resolvePajaRawOptions } from './config-file.js';\nimport { probeTargetCors } from './target-cors.js';\n\n/** Options for starting the Paja local HTTP server. */\nexport interface PajaServerOptions {\n /** Raw options accepted by Paja; config-file references are resolved before serving. */\n readonly options: PajaRawOptions;\n /** Clock override used for deterministic host config in tests. */\n readonly now?: Date;\n}\n\n/** Running Paja server handle. */\nexport interface PajaServer {\n /** Runtime URL where the Paja host page is served. */\n readonly url: string;\n /** Current host-page config. */\n readonly hostConfig: PajaHostConfig;\n /** Update the target iframe URL without restarting the server. */\n updateTargetUrl(targetUrl: string): PajaHostConfig;\n /** Stop the HTTP server. */\n close(): Promise<void>;\n}\n\n/**\n * Start the local Paja development HTTP server.\n *\n * @param input - Raw options and optional clock override.\n * @returns Running server handle.\n */\nexport async function startPajaServer(input: PajaServerOptions): Promise<PajaServer> {\n const rawOptions = resolvePajaRawOptions(input.options);\n const options = normalizePajaOptions(rawOptions);\n const createdAt = input.now ?? new Date();\n let hostConfig = createPajaHostConfig(options, createdAt);\n let html = renderPajaHtml(hostConfig);\n let configJson = JSON.stringify(hostConfig, null, 2);\n let currentOptions = options;\n\n const setHostConfig = (nextOptions: PajaOptions): PajaHostConfig => {\n currentOptions = nextOptions;\n hostConfig = createPajaHostConfig(currentOptions, createdAt);\n html = renderPajaHtml(hostConfig);\n configJson = JSON.stringify(hostConfig, null, 2);\n return hostConfig;\n };\n\n const handleRequest = async (requestUrl: string, response: HttpResponse): Promise<void> => {\n if (requestUrl === '/' || requestUrl.startsWith('/?')) {\n response.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });\n response.end(html);\n return;\n }\n\n if (requestUrl === '/__kehto/config.json') {\n response.writeHead(200, { 'content-type': 'application/json; charset=utf-8' });\n response.end(configJson);\n return;\n }\n\n if (requestUrl === '/__kehto/target.html') {\n const targetHtml = await fetchTargetHtml(hostConfig.target.url);\n response.writeHead(200, {\n 'cache-control': 'no-store',\n 'content-type': 'text/html; charset=utf-8',\n });\n response.end(targetHtml);\n return;\n }\n\n if (requestUrl === '/__kehto/target-cors.json') {\n const diagnostic = await probeTargetCors(hostConfig.target.url);\n response.writeHead(200, {\n 'cache-control': 'no-store',\n 'content-type': 'application/json; charset=utf-8',\n });\n response.end(JSON.stringify(diagnostic));\n return;\n }\n\n if (requestUrl === '/__kehto/browser-host.js') {\n const browserScript = readBrowserHostScript();\n response.writeHead(200, { 'content-type': 'text/javascript; charset=utf-8' });\n response.end(browserScript);\n return;\n }\n\n response.writeHead(404, { 'content-type': 'text/plain; charset=utf-8' });\n response.end('Not found');\n };\n\n const server = createServer((request, response) => {\n void handleRequest(request.url ?? '/', response).catch((error) => {\n const message = error instanceof Error ? error.message : String(error);\n response.writeHead(502, { 'content-type': 'text/plain; charset=utf-8' });\n response.end(message);\n });\n });\n\n await listen(server, options.host, options.port);\n const servedOptions = withBoundPort(options, getBoundPort(server, options.port));\n setHostConfig(servedOptions);\n\n return {\n url: formatPajaUrl(servedOptions),\n get hostConfig() {\n return hostConfig;\n },\n updateTargetUrl(targetUrl) {\n return setHostConfig(withTargetUrl(currentOptions, targetUrl));\n },\n close: () => close(server),\n };\n}\n\nfunction readBrowserHostScript(): string {\n return readFileSync(new URL('./browser-host.js', import.meta.url), 'utf8');\n}\n\nasync function fetchTargetHtml(targetUrl: string): Promise<string> {\n const response = await fetch(targetUrl, {\n headers: {\n accept: 'text/html, application/xhtml+xml;q=0.9, */*;q=0.8',\n },\n });\n if (!response.ok) {\n throw new Error(`Target ${targetUrl} returned ${response.status} ${response.statusText}`);\n }\n return response.text();\n}\n\nfunction listen(server: HttpServer, host: string, port: number): Promise<void> {\n return new Promise((resolve, reject) => {\n server.once('error', reject);\n server.listen(port, host, () => {\n server.off('error', reject);\n resolve();\n });\n });\n}\n\nfunction close(server: HttpServer): Promise<void> {\n return new Promise((resolve, reject) => {\n const forceClose = setTimeout(() => {\n server.closeAllConnections?.();\n }, 500);\n server.close((error?: Error) => {\n clearTimeout(forceClose);\n if (error) {\n reject(error);\n return;\n }\n resolve();\n });\n server.closeIdleConnections?.();\n });\n}\n\ninterface HttpServer {\n listen(port: number, host: string, callback: () => void): void;\n once(event: 'error', callback: (error: Error) => void): void;\n off(event: 'error', callback: (error: Error) => void): void;\n close(callback: (error?: Error) => void): void;\n closeAllConnections?(): void;\n closeIdleConnections?(): void;\n address(): string | { port: number } | null;\n}\n\ninterface HttpResponse {\n writeHead(statusCode: number, headers: Record<string, string>): void;\n end(chunk: string): void;\n}\n\nfunction getBoundPort(server: HttpServer, fallback: number): number {\n const address = server.address();\n return typeof address === 'object' && address !== null ? address.port : fallback;\n}\n\nfunction withBoundPort(options: PajaOptions, port: number): PajaOptions {\n return port === options.port ? options : { ...options, port };\n}\n\nfunction withTargetUrl(options: PajaOptions, targetUrl: string): PajaOptions {\n const url = normalizeServerTargetUrl(targetUrl);\n return url === options.targetUrl ? options : { ...options, targetUrl: url };\n}\n\nfunction normalizeServerTargetUrl(value: string): string {\n const raw = value.trim();\n let parsed: URL;\n try {\n parsed = new URL(raw);\n } catch {\n throw new Error(`Invalid managed target URL \"${raw}\". Expected an absolute http(s) URL.`);\n }\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n throw new Error(`Invalid managed target URL \"${raw}\". Only http: and https: URLs are supported.`);\n }\n return parsed.href;\n}\n","/**\n * Target dev-server CORS diagnostics.\n *\n * Paja loads the napplet target into a `srcdoc` iframe sandboxed without\n * `allow-same-origin`, so the napplet document has an opaque origin and sends\n * `Origin: null` on every subresource fetch. `<script type=\"module\">` is always\n * fetched in CORS mode, so a dev server that does not allow the `null` origin\n * blocks the napplet's entry module and the iframe renders blank.\n *\n * These helpers probe the target with an explicit `Origin: null` request so\n * Paja can report the cause instead of leaving an empty frame behind.\n */\n\n/** Whether the target dev server will serve the sandboxed frame's subresources. */\nexport type PajaTargetCorsStatus = 'allowed' | 'blocked' | 'unreachable';\n\n/** Result of probing a target dev server for opaque-origin CORS support. */\nexport interface PajaTargetCorsDiagnostic {\n /** Classification of the probe result. */\n readonly status: PajaTargetCorsStatus;\n /** Target URL that was probed. */\n readonly targetUrl: string;\n /** `access-control-allow-origin` value the target returned, when any. */\n readonly allowOrigin: string | null;\n /** Human-readable explanation of the classification. */\n readonly detail: string;\n /** Actionable remedy, present only when `status` is not `allowed`. */\n readonly hint: string | null;\n}\n\n/** Remedy shown whenever the target rejects the sandboxed frame's `null` origin. */\nexport const PAJA_TARGET_CORS_HINT =\n \"Paja sandboxes the napplet frame without allow-same-origin, so its assets are requested with `Origin: null`. \"\n + \"Allow that origin in the napplet dev server, e.g. Vite `server: { cors: { origin: '*' } }`.\";\n\n/**\n * Classify an `access-control-allow-origin` response value for an opaque origin.\n *\n * @param targetUrl - Target URL the header came from.\n * @param allowOrigin - Raw `access-control-allow-origin` header value, or `null` when absent.\n * @returns Diagnostic describing whether the sandboxed frame can load assets.\n * @example\n * ```ts\n * classifyTargetCors('http://127.0.0.1:5173/', '*').status; // 'allowed'\n * classifyTargetCors('http://127.0.0.1:5173/', null).status; // 'blocked'\n * ```\n */\nexport function classifyTargetCors(\n targetUrl: string,\n allowOrigin: string | null,\n): PajaTargetCorsDiagnostic {\n const value = allowOrigin?.trim() ?? null;\n if (value === '*' || value === 'null') {\n return {\n status: 'allowed',\n targetUrl,\n allowOrigin: value,\n detail: `Target allows the sandboxed frame's null origin (access-control-allow-origin: ${value}).`,\n hint: null,\n };\n }\n if (value === null || value === '') {\n return {\n status: 'blocked',\n targetUrl,\n allowOrigin: null,\n detail: 'Target sent no access-control-allow-origin for an Origin: null request.',\n hint: PAJA_TARGET_CORS_HINT,\n };\n }\n return {\n status: 'blocked',\n targetUrl,\n allowOrigin: value,\n detail: `Target only allows origin \"${value}\", not the sandboxed frame's null origin.`,\n hint: PAJA_TARGET_CORS_HINT,\n };\n}\n\n/** Minimal fetch surface the probe depends on. */\nexport type PajaTargetCorsFetch = (\n input: string,\n init: { method: string; headers: Record<string, string> },\n) => Promise<{ headers: { get(name: string): string | null } }>;\n\nconst defaultTargetCorsFetch: PajaTargetCorsFetch = (input, init) => fetch(input, init);\n\n/**\n * Probe a target dev server for opaque-origin CORS support.\n *\n * Sends `Origin: null` explicitly — a browser cannot forge that header, but the\n * Paja server can, which is why the probe runs server-side.\n *\n * @param targetUrl - Absolute target URL served by the napplet dev server.\n * @param fetchImpl - Fetch implementation; defaults to the global `fetch`.\n * @returns Diagnostic describing whether the sandboxed frame can load assets.\n * @example\n * ```ts\n * const diagnostic = await probeTargetCors('http://127.0.0.1:5173/');\n * if (diagnostic.status === 'blocked') console.warn(diagnostic.hint);\n * ```\n */\nexport async function probeTargetCors(\n targetUrl: string,\n fetchImpl: PajaTargetCorsFetch = defaultTargetCorsFetch,\n): Promise<PajaTargetCorsDiagnostic> {\n try {\n const response = await fetchImpl(targetUrl, {\n method: 'GET',\n headers: {\n origin: 'null',\n accept: 'text/html, application/xhtml+xml;q=0.9, */*;q=0.8',\n },\n });\n return classifyTargetCors(targetUrl, response.headers.get('access-control-allow-origin'));\n } catch (error) {\n return {\n status: 'unreachable',\n targetUrl,\n allowOrigin: null,\n detail: `Target CORS probe failed: ${error instanceof Error ? error.message : String(error)}`,\n hint: 'Confirm the napplet dev server is running and --target-url points at it.',\n };\n }\n}\n"],"mappings":"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","names":["sha256Hex","resolve","resolve","copy","readFileSync","readFileSync","resolve"]}
|