@monimesl/dot-simulator 0.1.0-alpha.2 → 0.1.0-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/compatibility.ts","../../src/host-calls.ts","../../src/permissions.ts","../../src/host.ts","../../src/versions.ts","../../src/paths.ts","../../src/page.ts","../../src/server.ts"],"sourcesContent":["import { isReactCompatible, isVersionAtLeast, majorVersion } from '@monimesl/dot-core';\n\nimport type { HostManifest } from './manifest';\n\nexport type IncompatibilityReason = 'runtimeTooOld' | 'dashTooOld' | 'reactUnavailable';\n\n// The two versions a host knows about itself.\nexport interface HostVersions {\n // The Dash version this host is, checked against the dot's `dash.minVersion`.\n readonly dashVersion: string;\n // The oldest @monimesl/dot-runtime this host runs. Dots built with an older one must be rebuilt.\n readonly minRuntimeVersion: string;\n // The React versions the host ships. Left out by a host that serves the React the dot was built\n // with, as the simulator does.\n readonly reactVersions?: readonly string[] | undefined;\n}\n\n// Why a host refuses to open a dot: it was built with a runtime older than the host runs, it needs\n// a newer Dash than the host is, or it imports a React the host doesn't ship.\nexport class IncompatibleDotError extends Error {\n override readonly name = 'IncompatibleDotError';\n readonly reason: IncompatibilityReason;\n\n constructor(reason: IncompatibilityReason, message: string) {\n super(message);\n this.reason = reason;\n }\n}\n\n// Throws an IncompatibleDotError when a host with these versions cannot run the dot.\nexport function checkCompatibility(\n manifest: HostManifest,\n { dashVersion, minRuntimeVersion, reactVersions }: HostVersions,\n): void {\n if (!isVersionAtLeast(manifest.runtime, minRuntimeVersion)) {\n throw new IncompatibleDotError(\n 'runtimeTooOld',\n `This dot was built with Dot runtime ${manifest.runtime}, but this host runs ${minRuntimeVersion} or newer. ` +\n 'Rebuild it with a newer @monimesl/dot-runtime.',\n );\n }\n const minDashVersion = manifest.dash?.minVersion;\n if (minDashVersion !== undefined && !isVersionAtLeast(dashVersion, minDashVersion)) {\n throw new IncompatibleDotError(\n 'dashTooOld',\n `This dot needs Dash ${minDashVersion} or newer, and this is Dash ${dashVersion}.`,\n );\n }\n if (\n reactVersions !== undefined &&\n !reactVersions.some((version) => isReactCompatible(version, manifest.react))\n ) {\n throw new IncompatibleDotError(\n 'reactUnavailable',\n `This dot needs React ${manifest.react} or a newer ${majorVersion(manifest.react)}.x, which this host doesn't ship.`,\n );\n }\n}\n","import type {\n DotPermission,\n HostErrorCode,\n HostResult,\n LogicLayerMessages,\n} from '@monimesl/dot-core';\nimport { HostError } from '@monimesl/dot-core';\n\nimport type { PermissionGate } from './permissions';\n\nexport interface HostCall {\n readonly method: string;\n readonly params: unknown;\n // Aborted when the call ends early: cancelled, out of time, or dismissed with its page.\n readonly signal: AbortSignal;\n}\n\n// Runs one host method. Throw a `HostError` to fail with its code; anything else fails as `failed`.\nexport type HostHandler = (call: HostCall) => unknown;\n\n// A method that needs a permission: it runs only if the dot declared it and the person allowed it.\nexport interface PermissionedHandler {\n readonly permission: DotPermission;\n readonly handle: HostHandler;\n}\n\nexport type HostHandlers = Readonly<Record<string, HostHandler | PermissionedHandler>>;\n\nconst UI_METHOD_PREFIX = 'dash.ui.';\n\ninterface RunningCall {\n readonly method: string;\n readonly controller: AbortController;\n timer?: ReturnType<typeof setTimeout>;\n}\n\n// Runs the service's host calls with `handlers`, and answers each call exactly once.\nexport class HostCallRunner {\n readonly #handlers: HostHandlers;\n readonly #permissions: PermissionGate;\n readonly #answer: (result: HostResult) => void;\n readonly #running = new Map<number, RunningCall>();\n\n constructor(\n handlers: HostHandlers,\n permissions: PermissionGate,\n answer: (result: HostResult) => void,\n ) {\n this.#handlers = handlers;\n this.#permissions = permissions;\n this.#answer = answer;\n }\n\n run({ id, method, params, timeout }: LogicLayerMessages['hostCall']): void {\n const entry = this.#handlers[method];\n if (!entry) {\n this.#answer({ id, error: { code: 'unsupported', message: `This host has no \"${method}\"` } });\n return;\n }\n const call: RunningCall = { method, controller: new AbortController() };\n if (timeout !== undefined) {\n call.timer = setTimeout(() => {\n this.#fail(id, new HostError('timeout', `\"${method}\" took longer than ${timeout}ms`));\n }, timeout);\n }\n this.#running.set(id, call);\n void this.#invoke(id, entry, { method, params, signal: call.controller.signal });\n }\n\n // The service stopped waiting, so the call ends without an answer.\n cancel(id: number): void {\n this.#end(id)?.controller.abort();\n }\n\n // Dialogs belong to the page on top: when it changes, the ones still open are dismissed.\n dismissUI(): void {\n for (const [id, call] of this.#running) {\n if (call.method.startsWith(UI_METHOD_PREFIX)) {\n this.#fail(id, new HostError('cancelled', 'The page it was shown on is no longer on top'));\n }\n }\n }\n\n async #invoke(\n id: number,\n entry: HostHandler | PermissionedHandler,\n call: HostCall,\n ): Promise<void> {\n try {\n if (typeof entry === 'function') {\n this.#succeed(id, await entry(call));\n return;\n }\n await this.#permissions.check(call.method, entry.permission, call.signal);\n this.#succeed(id, await entry.handle(call));\n } catch (error) {\n this.#fail(id, error);\n }\n }\n\n #succeed(id: number, value: unknown): void {\n if (this.#end(id)) {\n this.#answer({ id, value });\n }\n }\n\n #fail(id: number, error: unknown): void {\n const call = this.#end(id);\n if (!call) {\n return;\n }\n call.controller.abort();\n this.#answer({ id, error: describeError(error) });\n }\n\n #end(id: number): RunningCall | undefined {\n const call = this.#running.get(id);\n if (call) {\n this.#running.delete(id);\n clearTimeout(call.timer);\n }\n return call;\n }\n}\n\nfunction describeError(error: unknown): { code: HostErrorCode; message: string } {\n if (error instanceof HostError) {\n return { code: error.code, message: error.message };\n }\n return { code: 'failed', message: error instanceof Error ? error.message : String(error) };\n}\n","import type { DotPermission } from '@monimesl/dot-core';\nimport { HostError } from '@monimesl/dot-core';\n\n// A dot asking for a permission it declared, the first time a call needs it.\nexport interface PermissionRequest {\n readonly dotName: string;\n readonly permission: DotPermission;\n // Why the dot needs it, from its dot.json.\n readonly reason: string;\n}\n\n// Each permission a dot declared in its dot.json, with the reason Dash shows the person.\nexport type DeclaredPermissions = Readonly<Partial<Record<string, string>>>;\n\n// Asks the person, and resolves with whether they allowed it. `signal` aborts when the call that\n// needs the permission ends first; the host then takes its prompt down and rejects.\nexport type AskPermission = (request: PermissionRequest, signal: AbortSignal) => Promise<boolean>;\n\n// Lets a call that needs a permission through only if the dot declared it and the person allowed\n// it. The person is asked once per run; the answer, yes or no, is kept.\nexport class PermissionGate {\n readonly #dotName: string;\n readonly #declared: DeclaredPermissions;\n readonly #ask: AskPermission;\n readonly #answers = new Map<DotPermission, boolean>();\n\n constructor(dotName: string, declared: DeclaredPermissions, ask: AskPermission) {\n this.#dotName = dotName;\n this.#declared = declared;\n this.#ask = ask;\n }\n\n // Rejects with a `denied` HostError when the call may not run.\n async check(method: string, permission: DotPermission, signal: AbortSignal): Promise<void> {\n const reason = this.#declared[permission];\n if (reason === undefined) {\n throw new HostError(\n 'denied',\n `\"${method}\" needs the \"${permission}\" permission. Add it to dot.json's permissions, with the reason Dash shows the person.`,\n );\n }\n let allowed = this.#answers.get(permission);\n if (allowed === undefined) {\n allowed = await this.#ask({ dotName: this.#dotName, permission, reason }, signal);\n this.#answers.set(permission, allowed);\n }\n if (!allowed) {\n throw new HostError('denied', `The person did not allow \"${permission}\"`);\n }\n }\n}\n","import type {\n Envelope,\n HostLayerMessages,\n HostResult,\n LogicLayerMessages,\n Navigation,\n SystemContext,\n Transport,\n} from '@monimesl/dot-core';\nimport { NavigationStack } from '@monimesl/dot-core';\n\nimport type { HostVersions } from './compatibility';\nimport { checkCompatibility } from './compatibility';\nimport type { HostHandlers } from './host-calls';\nimport { HostCallRunner } from './host-calls';\nimport type { HostManifest } from './manifest';\nimport type { AskPermission } from './permissions';\nimport { PermissionGate } from './permissions';\n\nexport type Direction = 'toService' | 'toWebview' | 'toHost' | 'fromHost';\n\nexport interface HostOptions extends HostVersions {\n ui: Transport;\n logic: Transport;\n manifest: HostManifest;\n systemContext: SystemContext;\n // The methods dots may call, such as `dash.ui.alert`; any other is answered `unsupported`.\n handlers?: HostHandlers;\n // Asks the person for a permission the dot declared, the first time a method needs it.\n askPermission: AskPermission;\n observe?: (direction: Direction, message: Envelope) => void;\n}\n\nexport interface Host {\n stop(): void;\n // false on the first page, where a device closes the dot instead.\n back(): boolean;\n setSystemContext(context: SystemContext): void;\n}\n\n// The host contract in JavaScript; native hosts implement the same in Swift or Kotlin.\n// Throws an IncompatibleDotError, before either layer hears anything, if it cannot run the dot.\nexport function startHost({\n logic,\n ui,\n manifest,\n dashVersion,\n minRuntimeVersion,\n systemContext,\n handlers = {},\n askPermission,\n observe,\n}: HostOptions): Host {\n checkCompatibility(manifest, { dashVersion, minRuntimeVersion });\n const stack = new NavigationStack();\n let context = systemContext;\n const permissions = new PermissionGate(manifest.name, manifest.permissions ?? {}, askPermission);\n const calls = new HostCallRunner(handlers, permissions, (result) => {\n const message: Envelope<'hostResult', HostResult> = { type: 'hostResult', payload: result };\n observe?.('fromHost', message);\n logic.post(message);\n });\n const broadcast = (message: Envelope) => {\n observe?.('fromHost', message);\n ui.post(message);\n logic.post(message);\n };\n const syncStack = () => {\n const message: Envelope<'syncStack', HostLayerMessages['syncStack']> = {\n type: 'syncStack',\n payload: { pages: stack.pages, limit: stack.limit },\n };\n broadcast(message);\n };\n const syncSystemContext = () => {\n const message: Envelope<'syncSystemContext', HostLayerMessages['syncSystemContext']> = {\n type: 'syncSystemContext',\n payload: context,\n };\n broadcast(message);\n };\n const navigate = (navigation: Navigation): boolean => {\n if (navigation.action !== 'back' && !manifest.pages.includes(navigation.page)) {\n return false;\n }\n const previousTop = stack.top;\n if (!stack.apply(navigation)) {\n return false;\n }\n syncStack();\n if (stack.top !== previousTop) {\n calls.dismissUI();\n }\n return true;\n };\n\n // Only the page on top may navigate; a request from a page below it is dropped.\n const navigateFromPage = ({ from, ...navigation }: LogicLayerMessages['navigate']) => {\n if (from === stack.top?.instanceId) {\n navigate(navigation);\n }\n };\n // Service messages meant for the host itself; every other one is relayed to the webview.\n const hostMessageHandlers = new Map<string, (payload: unknown) => void>([\n ['navigate', (payload) => navigateFromPage(payload as LogicLayerMessages['navigate'])],\n ['hostCall', (payload) => calls.run(payload as LogicLayerMessages['hostCall'])],\n ['hostCancel', (payload) => calls.cancel((payload as LogicLayerMessages['hostCancel']).id)],\n ]);\n\n const stopService = logic.listen((raw) => {\n const message = raw as Envelope;\n const handleHostMessage = hostMessageHandlers.get(message.type);\n if (handleHostMessage) {\n observe?.('toHost', message);\n handleHostMessage(message.payload);\n return;\n }\n observe?.('toWebview', message);\n ui.post(message);\n });\n const stopWebview = ui.listen((raw) => {\n const message = raw as Envelope;\n observe?.('toService', message);\n logic.post(message);\n if (message.type === 'connect') {\n syncSystemContext();\n navigate({ action: 'reset', page: manifest.entry, options: {} });\n }\n });\n return {\n back: () => navigate({ action: 'back', delta: 1 }),\n setSystemContext: (next) => {\n context = next;\n syncSystemContext();\n },\n stop: () => {\n stopService();\n stopWebview();\n },\n };\n}\n","import type { HostVersions } from './compatibility';\n\n// The Dash version the simulator acts as, for dots that set `dash.minVersion`.\nexport const SIMULATED_DASH_VERSION = '1.0.0';\n\n// The oldest @monimesl/dot-runtime whose bundles the simulator's bridge runs. Raise it when the\n// messages between the layers and the host change in a way older runtimes don't understand.\nexport const MIN_RUNTIME_VERSION = '0.1.0-alpha.1';\n\nexport const SIMULATOR_VERSIONS: HostVersions = {\n dashVersion: SIMULATED_DASH_VERSION,\n minRuntimeVersion: MIN_RUNTIME_VERSION,\n};\n","export const CLIENT_PATH = '/__dot/';\nexport const EVENTS_PATH = `${CLIENT_PATH}events`;\n\nexport const DOT_PATH = '/dot/';\nexport const WEBVIEW_URL = `${DOT_PATH}ui/index.html`;\nexport const SERVICE_URL = `${DOT_PATH}logic/dot.js`;\nexport const MANIFEST_URL = `${DOT_PATH}manifest.json`;\n","import { CLIENT_PATH } from './paths';\n\nconst escapeHtml = (text: string) => text.replace(/[&<>\"]/g, (char) => `&#${char.charCodeAt(0)};`);\n\nexport function simulatorPage(appName: string): string {\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.0\" />\n <title>${escapeHtml(appName)} · Dot simulator</title>\n <style>${STYLES}</style>\n </head>\n <body>\n <header>\n <strong>${escapeHtml(appName)}</strong>\n <span class=\"subtle\">Dot simulator</span>\n <select id=\"scheme\" aria-label=\"Device appearance\">\n <option value=\"light\">Light</option>\n <option value=\"dark\">Dark</option>\n </select>\n <button id=\"back\" type=\"button\" title=\"The system back button\">◀ Back</button>\n <button id=\"reload\" type=\"button\">Reload</button>\n </header>\n <main>\n <section class=\"stage\">\n <pre id=\"error\" hidden></pre>\n <div class=\"phone\" data-scheme=\"light\">\n <div class=\"status-bar\">\n <span class=\"clock\">9:41</span>\n <span class=\"island\"></span>\n <span class=\"indicators\">${STATUS_ICONS}</span>\n </div>\n <iframe id=\"screen\" title=\"${escapeHtml(appName)}\"></iframe>\n <span class=\"home-indicator\"></span>\n </div>\n </section>\n <aside>\n <div class=\"panel-head\">\n <span>Messages</span>\n <button id=\"clear\" type=\"button\">Clear</button>\n </div>\n <ol id=\"log\"></ol>\n </aside>\n </main>\n <script src=\"${CLIENT_PATH}simulator.js\"></script>\n </body>\n</html>\n`;\n}\n\nconst TORCH_ICON = `<svg class=\"torch\" width=\"10\" height=\"14\" viewBox=\"0 0 10 14\" aria-label=\"Torch on\"><path d=\"M0 0h10v3L7.5 6v7a1 1 0 0 1-1 1h-3a1 1 0 0 1-1-1V6L0 3Z\" fill=\"#ffd60a\"/></svg>`;\n\nconst STATUS_ICONS = `${TORCH_ICON}<svg width=\"18\" height=\"12\" viewBox=\"0 0 18 12\" fill=\"currentColor\" aria-hidden=\"true\"><rect x=\"0\" y=\"8\" width=\"3\" height=\"4\" rx=\"1\"/><rect x=\"5\" y=\"5.5\" width=\"3\" height=\"6.5\" rx=\"1\"/><rect x=\"10\" y=\"3\" width=\"3\" height=\"9\" rx=\"1\"/><rect x=\"15\" y=\"0\" width=\"3\" height=\"12\" rx=\"1\"/></svg><svg width=\"16\" height=\"12\" viewBox=\"0 0 16 12\" fill=\"currentColor\" aria-hidden=\"true\"><path d=\"M8 2.2c2.3 0 4.4.9 6 2.4l1.2-1.3A10.4 10.4 0 0 0 8 .4 10.4 10.4 0 0 0 .8 3.3L2 4.6a8.6 8.6 0 0 1 6-2.4Zm0 3.6c1.3 0 2.5.5 3.5 1.3l1.2-1.3A6.9 6.9 0 0 0 8 4a6.9 6.9 0 0 0-4.7 1.8l1.2 1.3A5.1 5.1 0 0 1 8 5.8Zm0 3.6c-.5 0-1 .2-1.3.5L8 11.4l1.3-1.5c-.3-.3-.8-.5-1.3-.5Z\"/></svg><svg width=\"27\" height=\"13\" viewBox=\"0 0 27 13\" aria-hidden=\"true\"><rect x=\"0.5\" y=\"0.5\" width=\"23\" height=\"12\" rx=\"3.5\" fill=\"none\" stroke=\"currentColor\" opacity=\"0.4\"/><rect x=\"2\" y=\"2\" width=\"20\" height=\"9\" rx=\"2\" fill=\"currentColor\"/><path d=\"M25 4.5v4c.8-.3 1.3-1.1 1.3-2s-.5-1.7-1.3-2Z\" fill=\"currentColor\" opacity=\"0.4\"/></svg>`;\n\nconst STYLES = `\n :root {\n color-scheme: light dark;\n --bg: #eef0f3; --panel: #ffffff; --border: #d6d9de; --text: #1f2328; --subtle: #6b7280;\n --to-service: #2563eb; --to-webview: #059669; --danger: #dc2626;\n font: 13px/1.4 system-ui, sans-serif;\n }\n @media (prefers-color-scheme: dark) {\n :root {\n --bg: #0d1117; --panel: #161b22; --border: #30363d; --text: #e6edf3; --subtle: #8b949e;\n --to-service: #58a6ff; --to-webview: #3fb950; --danger: #f85149;\n }\n }\n * { box-sizing: border-box; }\n body { margin: 0; height: 100vh; display: flex; flex-direction: column; background: var(--bg); color: var(--text); }\n header { display: flex; align-items: center; gap: 12px; padding: 8px 16px; background: var(--panel); border-bottom: 1px solid var(--border); }\n #scheme { margin-left: auto; }\n button, select { font: inherit; color: inherit; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; padding: 4px 10px; cursor: pointer; }\n .subtle { color: var(--subtle); }\n main { flex: 1; display: flex; min-height: 0; }\n .stage { flex: 1; overflow: auto; display: flex; flex-direction: column; align-items: center; gap: 12px; padding: 24px 16px; }\n .phone { flex: none; position: relative; display: flex; flex-direction: column; width: 393px; height: 852px; box-sizing: content-box; border: 12px solid #0b0b0c; border-radius: 67px; overflow: hidden; background: var(--screen); box-shadow: 0 0 0 2px #3a3a3c, 0 18px 50px rgb(0 0 0 / 0.3); --screen: #f2f2f2; --screen-text: #000; }\n .phone[data-scheme=\"dark\"] { --screen: #121212; --screen-text: #fff; --dialog: #2c2c2e; --dialog-line: #48484a; }\n .phone { --dialog: #f7f7f7; --dialog-line: #d1d1d6; }\n .torch { display: none; }\n .phone[data-torch=\"on\"] .torch { display: block; }\n .dialog-layer { position: absolute; inset: 0; z-index: 1; display: flex; align-items: center; justify-content: center; background: rgb(0 0 0 / 0.3); }\n .dialog { width: 270px; border-radius: 14px; overflow: hidden; background: var(--dialog); color: var(--screen-text); text-align: center; font: 13px/1.35 system-ui, sans-serif; }\n .dialog-body { display: flex; flex-direction: column; gap: 4px; padding: 18px 16px 16px; }\n .dialog-title { font-size: 17px; font-weight: 600; }\n .dialog-actions { display: flex; border-top: 1px solid var(--dialog-line); }\n .dialog-actions.stacked { flex-direction: column; }\n .dialog-actions button { flex: 1; margin: 0; padding: 11px; border: 0; border-radius: 0; background: transparent; color: #0a84ff; font-size: 17px; }\n .dialog-actions button + button { border-left: 1px solid var(--dialog-line); }\n .dialog-actions.stacked button + button { border-left: 0; border-top: 1px solid var(--dialog-line); }\n .dialog-actions .cancel { font-weight: 600; }\n .dialog-actions .destructive { color: #ff3b30; }\n .status-bar { flex: none; position: relative; height: 54px; display: flex; align-items: center; justify-content: space-between; padding: 0 30px 0 50px; color: var(--screen-text); font: 600 17px/1 system-ui, sans-serif; }\n .island { position: absolute; top: 11px; left: 50%; width: 125px; height: 37px; margin-left: -62.5px; border-radius: 19px; background: #000; }\n .indicators { display: flex; align-items: center; gap: 6px; }\n #screen { flex: 1; display: block; width: 100%; border: 0; background: var(--screen); }\n .home-indicator { position: absolute; bottom: 8px; left: 50%; width: 134px; height: 5px; margin-left: -67px; border-radius: 3px; background: var(--screen-text); pointer-events: none; }\n #error { max-width: 640px; width: 100%; margin: 0; padding: 12px; white-space: pre-wrap; overflow-wrap: anywhere; color: #fff; background: var(--danger); border-radius: 8px; }\n aside { width: 380px; display: flex; flex-direction: column; background: var(--panel); border-left: 1px solid var(--border); }\n .panel-head { display: flex; align-items: center; justify-content: space-between; padding: 8px 12px; border-bottom: 1px solid var(--border); font-weight: 600; }\n #log { flex: 1; overflow: auto; margin: 0; padding: 0; list-style: none; font: 12px/1.4 ui-monospace, monospace; }\n #log li { border-bottom: 1px solid var(--border); padding: 4px 12px; }\n #log summary { display: flex; gap: 8px; cursor: pointer; }\n #log pre { margin: 4px 0; white-space: pre-wrap; word-break: break-all; color: var(--subtle); }\n #log .time { color: var(--subtle); }\n #log .toService .arrow { color: var(--to-service); }\n #log .toWebview .arrow { color: var(--to-webview); }\n #log .toHost .arrow, #log .fromHost .arrow { color: var(--subtle); }\n #log .type { font-weight: 600; }\n #log .note { padding: 2px 12px; text-align: center; color: var(--subtle); background: var(--bg); }\n #log .failed .type { color: var(--danger); }\n @media (max-width: 900px) { main { flex-direction: column; } aside { width: auto; height: 40vh; border-left: 0; border-top: 1px solid var(--border); } }\n`;\n","import { readdir, readFile, stat } from 'node:fs/promises';\nimport type { Server, ServerResponse } from 'node:http';\nimport { createServer } from 'node:http';\nimport type { AddressInfo } from 'node:net';\nimport { createRequire } from 'node:module';\nimport { dirname, extname, join, resolve, sep } from 'node:path';\n\nimport { REACT_HOST_PATH, newestReactOfMajor } from '@monimesl/dot-core';\n\nimport { simulatorPage } from './page';\nimport { DOT_PATH, CLIENT_PATH, EVENTS_PATH, WEBVIEW_URL } from './paths';\n\nexport interface SimulatorOptions {\n dotDir: string;\n // The React files hosts serve, a folder per version, from the dot's renderer. Without it, a dot\n // cannot load React.\n reactHostDir?: string | undefined;\n name?: string;\n port?: number | undefined;\n host?: string;\n clientDir?: string;\n}\n\nexport interface Simulator {\n readonly url: string;\n reload(): void;\n error(message: string): void;\n close(): Promise<void>;\n}\n\nexport const DEFAULT_PORT = 3680;\n\nconst PORT_ATTEMPTS = 5;\n\nconst MIME_TYPES: Record<string, string> = {\n '.html': 'text/html; charset=utf-8',\n '.js': 'text/javascript; charset=utf-8',\n '.map': 'application/json; charset=utf-8',\n '.json': 'application/json; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.svg': 'image/svg+xml',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.gif': 'image/gif',\n '.webp': 'image/webp',\n '.woff2': 'font/woff2',\n};\n\nexport async function startSimulator({\n dotDir,\n reactHostDir,\n name = 'Dot',\n port = DEFAULT_PORT,\n host = 'localhost',\n clientDir = defaultClientDir(),\n}: SimulatorOptions): Promise<Simulator> {\n const clients = new Set<ServerResponse>();\n let buildError: string | null = null;\n const broadcast = (event: string, data = '') => {\n for (const client of clients) {\n client.write(serverEvent(event, data));\n }\n };\n\n const server = createServer((request, response) => {\n const { pathname } = new URL(request.url ?? '/', 'http://localhost');\n if (pathname === '/') {\n send(response, 200, simulatorPage(name), '.html');\n return;\n }\n if (pathname === '/favicon.ico') {\n send(response, 204, '');\n return;\n }\n if (pathname === EVENTS_PATH) {\n response.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-store',\n Connection: 'keep-alive',\n });\n response.write(': connected\\n\\n');\n if (buildError !== null) {\n response.write(serverEvent('build-error', buildError));\n }\n clients.add(response);\n request.on('close', () => clients.delete(response));\n return;\n }\n // As a native host serves the React it ships.\n if (pathname.startsWith(REACT_HOST_PATH) && reactHostDir !== undefined) {\n void serveReact(response, reactHostDir, pathname.slice(REACT_HOST_PATH.length));\n return;\n }\n if (pathname.startsWith(CLIENT_PATH)) {\n void serveFile(response, clientDir, pathname.slice(CLIENT_PATH.length));\n return;\n }\n if (pathname.startsWith(DOT_PATH)) {\n // As a native host injects its bridge into the WebView before the dot runs.\n const injectBridge = pathname === WEBVIEW_URL || `${pathname}index.html` === WEBVIEW_URL;\n void serveFile(response, dotDir, pathname.slice(DOT_PATH.length), injectBridge);\n return;\n }\n send(response, 404, 'Not found');\n });\n\n await listen(server, port, host);\n\n return {\n url: `http://${host}:${(server.address() as AddressInfo).port}/`,\n reload: () => {\n buildError = null;\n broadcast('reload');\n },\n error: (message) => {\n buildError = message;\n broadcast('build-error', message);\n },\n close: () =>\n new Promise((resolvePromise) => {\n for (const client of clients) {\n client.end();\n }\n server.close(() => resolvePromise());\n }),\n };\n}\n\nfunction serverEvent(event: string, data: string): string {\n const lines = data.split('\\n').map((line) => `data: ${line}`);\n return `event: ${event}\\n${lines.join('\\n')}\\n\\n`;\n}\n\nasync function listen(server: Server, port: number, host: string): Promise<void> {\n for (let attempt = 0; attempt < PORT_ATTEMPTS; attempt++) {\n const taken = await new Promise<boolean>((resolvePromise, reject) => {\n const onError = (error: NodeJS.ErrnoException) => {\n if (error.code === 'EADDRINUSE') {\n resolvePromise(true);\n } else {\n reject(error);\n }\n };\n server.once('error', onError);\n server.listen(port + attempt, host, () => {\n server.off('error', onError);\n resolvePromise(false);\n });\n });\n if (!taken) {\n return;\n }\n }\n const last = port + PORT_ATTEMPTS - 1;\n throw new Error(`Ports ${port} to ${last} are all in use. Pass another with --port.`);\n}\n\n// `<major>/<file>`, from the newest version of that major in `reactHostDir`.\nasync function serveReact(\n response: ServerResponse,\n reactHostDir: string,\n path: string,\n): Promise<void> {\n const [major = '', ...file] = path.split('/');\n const versions = await readdir(reactHostDir).catch(() => []);\n const version = newestReactOfMajor(versions, Number(major));\n if (version === undefined) {\n send(response, 404, 'Not found');\n return;\n }\n await serveFile(response, join(reactHostDir, version), file.join('/'));\n}\n\nasync function serveFile(\n response: ServerResponse,\n root: string,\n path: string,\n injectBridge = false,\n): Promise<void> {\n const base = resolve(root);\n let file = resolve(base, decodeURIComponent(path));\n if (file !== base && !file.startsWith(base + sep)) {\n send(response, 404, 'Not found');\n return;\n }\n try {\n if ((await stat(file)).isDirectory()) {\n file = join(file, 'index.html');\n }\n const body = await readFile(file);\n if (!injectBridge) {\n send(response, 200, body, extname(file));\n return;\n }\n const bridge = `<script src=\"${CLIENT_PATH}webview-bridge.js\"></script>`;\n send(response, 200, body.toString('utf8').replace('<head>', `<head>\\n ${bridge}`), '.html');\n } catch {\n send(response, 404, 'Not found');\n }\n}\n\nfunction send(\n response: ServerResponse,\n status: number,\n body: string | Buffer,\n extension = '.txt',\n): void {\n response.writeHead(status, {\n 'Content-Type': MIME_TYPES[extension] ?? 'text/plain; charset=utf-8',\n 'Cache-Control': 'no-store',\n });\n response.end(body);\n}\n\nfunction defaultClientDir(): string {\n const manifest = createRequire(import.meta.url).resolve('@monimesl/dot-simulator/package.json');\n return join(dirname(manifest), 'dist/client');\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["save","send","open"],"sources":["../../src/compatibility.ts","../../src/host-calls.ts","../../src/permissions.ts","../../src/host.ts","../../src/versions.ts","../../src/paths.ts","../../src/page.ts","../../src/bytes.ts","../../src/content-types.ts","../../src/network.ts","../../src/files.ts","../../src/temporary-files.ts","../../src/server.ts"],"sourcesContent":["import { isReactCompatible, isVersionAtLeast, majorVersion } from '@monimesl/dot-core';\n\nimport type { HostManifest } from './manifest';\n\nexport type IncompatibilityReason = 'runtimeTooOld' | 'dashTooOld' | 'reactUnavailable';\n\n// The two versions a host knows about itself.\nexport interface HostVersions {\n // The Dash version this host is, checked against the dot's `dash.minVersion`.\n readonly dashVersion: string;\n // The oldest @monimesl/dot-runtime this host runs. Dots built with an older one must be rebuilt.\n readonly minRuntimeVersion: string;\n // The React versions the host ships. Left out by a host that serves the React the dot was built\n // with, as the simulator does.\n readonly reactVersions?: readonly string[] | undefined;\n}\n\n// Why a host refuses to open a dot: it was built with a runtime older than the host runs, it needs\n// a newer Dash than the host is, or it imports a React the host doesn't ship.\nexport class IncompatibleDotError extends Error {\n override readonly name = 'IncompatibleDotError';\n readonly reason: IncompatibilityReason;\n\n constructor(reason: IncompatibilityReason, message: string) {\n super(message);\n this.reason = reason;\n }\n}\n\n// Throws an IncompatibleDotError when a host with these versions cannot run the dot.\nexport function checkCompatibility(\n manifest: HostManifest,\n { dashVersion, minRuntimeVersion, reactVersions }: HostVersions,\n): void {\n if (!isVersionAtLeast(manifest.runtime, minRuntimeVersion)) {\n throw new IncompatibleDotError(\n 'runtimeTooOld',\n `This dot was built with Dot runtime ${manifest.runtime}, but this host runs ${minRuntimeVersion} or newer. ` +\n 'Rebuild it with a newer @monimesl/dot-runtime.',\n );\n }\n const minDashVersion = manifest.dash?.minVersion;\n if (minDashVersion !== undefined && !isVersionAtLeast(dashVersion, minDashVersion)) {\n throw new IncompatibleDotError(\n 'dashTooOld',\n `This dot needs Dash ${minDashVersion} or newer, and this is Dash ${dashVersion}.`,\n );\n }\n if (\n reactVersions !== undefined &&\n !reactVersions.some((version) => isReactCompatible(version, manifest.react))\n ) {\n throw new IncompatibleDotError(\n 'reactUnavailable',\n `This dot needs React ${manifest.react} or a newer ${majorVersion(manifest.react)}.x, which this host doesn't ship.`,\n );\n }\n}\n","import type {\n DotPermission,\n HostErrorCode,\n HostResult,\n LogicLayerMessages,\n} from '@monimesl/dot-core';\nimport { HostError } from '@monimesl/dot-core';\n\nimport type { PermissionGate } from './permissions';\n\nexport interface HostCall {\n readonly method: string;\n readonly params: unknown;\n // Aborted when the call ends early: cancelled, out of time, or dismissed with its page.\n readonly signal: AbortSignal;\n}\n\n// Runs one host method. Throw a `HostError` to fail with its code; anything else fails as `failed`.\nexport type HostHandler = (call: HostCall) => unknown;\n\n// A method that needs a permission: it runs only if the dot declared it and the person allowed it.\nexport interface PermissionedHandler {\n readonly permission: DotPermission;\n readonly handle: HostHandler;\n}\n\nexport type HostHandlers = Readonly<Record<string, HostHandler | PermissionedHandler>>;\n\nconst UI_METHOD_PREFIX = 'dash.ui.';\n\ninterface RunningCall {\n readonly method: string;\n readonly controller: AbortController;\n timer?: ReturnType<typeof setTimeout>;\n}\n\n// Runs the service's host calls with `handlers`, and answers each call exactly once.\nexport class HostCallRunner {\n readonly #handlers: HostHandlers;\n readonly #permissions: PermissionGate;\n readonly #answer: (result: HostResult) => void;\n readonly #running = new Map<number, RunningCall>();\n\n constructor(\n handlers: HostHandlers,\n permissions: PermissionGate,\n answer: (result: HostResult) => void,\n ) {\n this.#handlers = handlers;\n this.#permissions = permissions;\n this.#answer = answer;\n }\n\n run({ id, method, params, timeout }: LogicLayerMessages['hostCall']): void {\n const entry = this.#handlers[method];\n if (!entry) {\n this.#answer({ id, error: { code: 'unsupported', message: `This host has no \"${method}\"` } });\n return;\n }\n const call: RunningCall = { method, controller: new AbortController() };\n if (timeout !== undefined) {\n call.timer = setTimeout(() => {\n this.#fail(id, new HostError('timeout', `\"${method}\" took longer than ${timeout}ms`));\n }, timeout);\n }\n this.#running.set(id, call);\n void this.#invoke(id, entry, { method, params, signal: call.controller.signal });\n }\n\n // The service stopped waiting, so the call ends without an answer.\n cancel(id: number): void {\n this.#end(id)?.controller.abort();\n }\n\n // Dialogs belong to the page on top: when it changes, the ones still open are dismissed.\n dismissUI(): void {\n for (const [id, call] of this.#running) {\n if (call.method.startsWith(UI_METHOD_PREFIX)) {\n this.#fail(id, new HostError('cancelled', 'The page it was shown on is no longer on top'));\n }\n }\n }\n\n async #invoke(\n id: number,\n entry: HostHandler | PermissionedHandler,\n call: HostCall,\n ): Promise<void> {\n try {\n if (typeof entry === 'function') {\n this.#succeed(id, await entry(call));\n return;\n }\n await this.#permissions.check(call.method, entry.permission, call.signal);\n this.#succeed(id, await entry.handle(call));\n } catch (error) {\n this.#fail(id, error);\n }\n }\n\n #succeed(id: number, value: unknown): void {\n if (this.#end(id)) {\n this.#answer({ id, value });\n }\n }\n\n #fail(id: number, error: unknown): void {\n const call = this.#end(id);\n if (!call) {\n return;\n }\n call.controller.abort();\n this.#answer({ id, error: describeError(error) });\n }\n\n #end(id: number): RunningCall | undefined {\n const call = this.#running.get(id);\n if (call) {\n this.#running.delete(id);\n clearTimeout(call.timer);\n }\n return call;\n }\n}\n\nfunction describeError(error: unknown): { code: HostErrorCode; message: string } {\n if (error instanceof HostError) {\n return { code: error.code, message: error.message };\n }\n return { code: 'failed', message: error instanceof Error ? error.message : String(error) };\n}\n","import type { DotPermission } from '@monimesl/dot-core';\nimport { HostError } from '@monimesl/dot-core';\n\n// A dot asking for a permission it declared, the first time a call needs it.\nexport interface PermissionRequest {\n readonly dotName: string;\n readonly permission: DotPermission;\n // Why the dot needs it, from its dot.json.\n readonly reason: string;\n}\n\n// Each permission a dot declared in its dot.json, with the reason Dash shows the person.\nexport type DeclaredPermissions = Readonly<Partial<Record<string, string>>>;\n\n// Asks the person, and resolves with whether they allowed it. `signal` aborts when the call that\n// needs the permission ends first; the host then takes its prompt down and rejects.\nexport type AskPermission = (request: PermissionRequest, signal: AbortSignal) => Promise<boolean>;\n\n// Lets a call that needs a permission through only if the dot declared it and the person allowed\n// it. The person is asked once per run; the answer, yes or no, is kept.\nexport class PermissionGate {\n readonly #dotName: string;\n readonly #declared: DeclaredPermissions;\n readonly #ask: AskPermission;\n readonly #answers = new Map<DotPermission, boolean>();\n\n constructor(dotName: string, declared: DeclaredPermissions, ask: AskPermission) {\n this.#dotName = dotName;\n this.#declared = declared;\n this.#ask = ask;\n }\n\n // Rejects with a `denied` HostError when the call may not run.\n async check(method: string, permission: DotPermission, signal: AbortSignal): Promise<void> {\n const reason = this.#declared[permission];\n if (reason === undefined) {\n throw new HostError(\n 'denied',\n `\"${method}\" needs the \"${permission}\" permission. Add it to dot.json's permissions, with the reason Dash shows the person.`,\n );\n }\n let allowed = this.#answers.get(permission);\n if (allowed === undefined) {\n allowed = await this.#ask({ dotName: this.#dotName, permission, reason }, signal);\n this.#answers.set(permission, allowed);\n }\n if (!allowed) {\n throw new HostError('denied', `The person did not allow \"${permission}\"`);\n }\n }\n}\n","import type {\n Envelope,\n HostLayerMessages,\n HostResult,\n LogicLayerMessages,\n Navigation,\n SystemContext,\n Transport,\n} from '@monimesl/dot-core';\nimport { NavigationStack } from '@monimesl/dot-core';\n\nimport type { HostVersions } from './compatibility';\nimport { checkCompatibility } from './compatibility';\nimport type { HostHandlers } from './host-calls';\nimport { HostCallRunner } from './host-calls';\nimport type { HostManifest } from './manifest';\nimport type { AskPermission } from './permissions';\nimport { PermissionGate } from './permissions';\n\nexport type Direction = 'toService' | 'toWebview' | 'toHost' | 'fromHost';\n\nexport interface HostOptions extends HostVersions {\n ui: Transport;\n logic: Transport;\n manifest: HostManifest;\n systemContext: SystemContext;\n // The methods dots may call, such as `dash.ui.alert`; any other is answered `unsupported`.\n handlers?: HostHandlers;\n // Asks the person for a permission the dot declared, the first time a method needs it.\n askPermission: AskPermission;\n observe?: (direction: Direction, message: Envelope) => void;\n}\n\nexport interface Host {\n stop(): void;\n // false on the first page, where a device closes the dot instead.\n back(): boolean;\n setSystemContext(context: SystemContext): void;\n}\n\n// The host contract in JavaScript; native hosts implement the same in Swift or Kotlin.\n// Throws an IncompatibleDotError, before either layer hears anything, if it cannot run the dot.\nexport function startHost({\n logic,\n ui,\n manifest,\n dashVersion,\n minRuntimeVersion,\n systemContext,\n handlers = {},\n askPermission,\n observe,\n}: HostOptions): Host {\n checkCompatibility(manifest, { dashVersion, minRuntimeVersion });\n const stack = new NavigationStack();\n let context = systemContext;\n const permissions = new PermissionGate(manifest.name, manifest.permissions ?? {}, askPermission);\n const calls = new HostCallRunner(handlers, permissions, (result) => {\n const message: Envelope<'hostResult', HostResult> = { type: 'hostResult', payload: result };\n observe?.('fromHost', message);\n logic.post(message);\n });\n const broadcast = (message: Envelope) => {\n observe?.('fromHost', message);\n ui.post(message);\n logic.post(message);\n };\n const syncStack = () => {\n const message: Envelope<'syncStack', HostLayerMessages['syncStack']> = {\n type: 'syncStack',\n payload: { pages: stack.pages, limit: stack.limit },\n };\n broadcast(message);\n };\n const syncSystemContext = () => {\n const message: Envelope<'syncSystemContext', HostLayerMessages['syncSystemContext']> = {\n type: 'syncSystemContext',\n payload: context,\n };\n broadcast(message);\n };\n const navigate = (navigation: Navigation): boolean => {\n if (navigation.action !== 'back' && !manifest.pages.includes(navigation.page)) {\n return false;\n }\n const previousTop = stack.top;\n if (!stack.apply(navigation)) {\n return false;\n }\n syncStack();\n if (stack.top !== previousTop) {\n calls.dismissUI();\n }\n return true;\n };\n\n // Only the page on top may navigate; a request from a page below it is dropped.\n const navigateFromPage = ({ from, ...navigation }: LogicLayerMessages['navigate']) => {\n if (from === stack.top?.instanceId) {\n navigate(navigation);\n }\n };\n // Service messages meant for the host itself; every other one is relayed to the webview.\n const hostMessageHandlers = new Map<string, (payload: unknown) => void>([\n ['navigate', (payload) => navigateFromPage(payload as LogicLayerMessages['navigate'])],\n ['hostCall', (payload) => calls.run(payload as LogicLayerMessages['hostCall'])],\n ['hostCancel', (payload) => calls.cancel((payload as LogicLayerMessages['hostCancel']).id)],\n ]);\n\n const stopService = logic.listen((raw) => {\n const message = raw as Envelope;\n const handleHostMessage = hostMessageHandlers.get(message.type);\n if (handleHostMessage) {\n observe?.('toHost', message);\n handleHostMessage(message.payload);\n return;\n }\n observe?.('toWebview', message);\n ui.post(message);\n });\n const stopWebview = ui.listen((raw) => {\n const message = raw as Envelope;\n observe?.('toService', message);\n logic.post(message);\n if (message.type === 'connect') {\n syncSystemContext();\n navigate({ action: 'reset', page: manifest.entry, options: {} });\n }\n });\n return {\n back: () => navigate({ action: 'back', delta: 1 }),\n setSystemContext: (next) => {\n context = next;\n syncSystemContext();\n },\n stop: () => {\n stopService();\n stopWebview();\n },\n };\n}\n","import type { HostVersions } from './compatibility';\n\n// The Dash version the simulator acts as, for dots that set `dash.minVersion`.\nexport const SIMULATED_DASH_VERSION = '1.0.0';\n\n// The oldest @monimesl/dot-runtime whose bundles the simulator's bridge runs. Raise it when the\n// messages between the layers and the host change in a way older runtimes don't understand.\nexport const MIN_RUNTIME_VERSION = '0.1.0-alpha.1';\n\nexport const SIMULATOR_VERSIONS: HostVersions = {\n dashVersion: SIMULATED_DASH_VERSION,\n minRuntimeVersion: MIN_RUNTIME_VERSION,\n};\n","export const CLIENT_PATH = '/__dot/';\nexport const EVENTS_PATH = `${CLIENT_PATH}events`;\nexport const HTTP_PATH = `${CLIENT_PATH}network/http/`;\nexport const FILE_PATH = `${CLIENT_PATH}file/`;\n\nexport const DOT_PATH = '/dot/';\nexport const WEBVIEW_URL = `${DOT_PATH}ui/index.html`;\nexport const SERVICE_URL = `${DOT_PATH}logic/dot.js`;\nexport const MANIFEST_URL = `${DOT_PATH}manifest.json`;\n","import { CLIENT_PATH } from './paths';\n\nconst escapeHtml = (text: string) => text.replace(/[&<>\"]/g, (char) => `&#${char.charCodeAt(0)};`);\n\nexport function simulatorPage(appName: string): string {\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.0\" />\n <title>${escapeHtml(appName)} · Dot simulator</title>\n <style>${STYLES}</style>\n </head>\n <body>\n <header>\n <strong>${escapeHtml(appName)}</strong>\n <span class=\"subtle\">Dot simulator</span>\n <select id=\"scheme\" aria-label=\"Device appearance\">\n <option value=\"light\">Light</option>\n <option value=\"dark\">Dark</option>\n </select>\n <button id=\"back\" type=\"button\" title=\"The system back button\">◀ Back</button>\n <button id=\"reload\" type=\"button\">Reload</button>\n </header>\n <main>\n <section class=\"stage\">\n <pre id=\"error\" hidden></pre>\n <div class=\"phone\" data-scheme=\"light\">\n <div class=\"status-bar\">\n <span class=\"clock\">9:41</span>\n <span class=\"island\"></span>\n <span class=\"indicators\">${STATUS_ICONS}</span>\n </div>\n <iframe id=\"screen\" title=\"${escapeHtml(appName)}\"></iframe>\n <span class=\"home-indicator\"></span>\n </div>\n </section>\n <aside>\n <div class=\"panel-head\">\n <span>Messages</span>\n <button id=\"clear\" type=\"button\">Clear</button>\n </div>\n <ol id=\"log\"></ol>\n </aside>\n </main>\n <script src=\"${CLIENT_PATH}simulator.js\"></script>\n </body>\n</html>\n`;\n}\n\nconst TORCH_ICON = `<svg class=\"torch\" width=\"10\" height=\"14\" viewBox=\"0 0 10 14\" aria-label=\"Torch on\"><path d=\"M0 0h10v3L7.5 6v7a1 1 0 0 1-1 1h-3a1 1 0 0 1-1-1V6L0 3Z\" fill=\"#ffd60a\"/></svg>`;\n\nconst STATUS_ICONS = `${TORCH_ICON}<svg width=\"18\" height=\"12\" viewBox=\"0 0 18 12\" fill=\"currentColor\" aria-hidden=\"true\"><rect x=\"0\" y=\"8\" width=\"3\" height=\"4\" rx=\"1\"/><rect x=\"5\" y=\"5.5\" width=\"3\" height=\"6.5\" rx=\"1\"/><rect x=\"10\" y=\"3\" width=\"3\" height=\"9\" rx=\"1\"/><rect x=\"15\" y=\"0\" width=\"3\" height=\"12\" rx=\"1\"/></svg><svg width=\"16\" height=\"12\" viewBox=\"0 0 16 12\" fill=\"currentColor\" aria-hidden=\"true\"><path d=\"M8 2.2c2.3 0 4.4.9 6 2.4l1.2-1.3A10.4 10.4 0 0 0 8 .4 10.4 10.4 0 0 0 .8 3.3L2 4.6a8.6 8.6 0 0 1 6-2.4Zm0 3.6c1.3 0 2.5.5 3.5 1.3l1.2-1.3A6.9 6.9 0 0 0 8 4a6.9 6.9 0 0 0-4.7 1.8l1.2 1.3A5.1 5.1 0 0 1 8 5.8Zm0 3.6c-.5 0-1 .2-1.3.5L8 11.4l1.3-1.5c-.3-.3-.8-.5-1.3-.5Z\"/></svg><svg width=\"27\" height=\"13\" viewBox=\"0 0 27 13\" aria-hidden=\"true\"><rect x=\"0.5\" y=\"0.5\" width=\"23\" height=\"12\" rx=\"3.5\" fill=\"none\" stroke=\"currentColor\" opacity=\"0.4\"/><rect x=\"2\" y=\"2\" width=\"20\" height=\"9\" rx=\"2\" fill=\"currentColor\"/><path d=\"M25 4.5v4c.8-.3 1.3-1.1 1.3-2s-.5-1.7-1.3-2Z\" fill=\"currentColor\" opacity=\"0.4\"/></svg>`;\n\nconst STYLES = `\n :root {\n color-scheme: light dark;\n --bg: #eef0f3; --panel: #ffffff; --border: #d6d9de; --text: #1f2328; --subtle: #6b7280;\n --to-service: #2563eb; --to-webview: #059669; --danger: #dc2626;\n font: 13px/1.4 system-ui, sans-serif;\n }\n @media (prefers-color-scheme: dark) {\n :root {\n --bg: #0d1117; --panel: #161b22; --border: #30363d; --text: #e6edf3; --subtle: #8b949e;\n --to-service: #58a6ff; --to-webview: #3fb950; --danger: #f85149;\n }\n }\n * { box-sizing: border-box; }\n body { margin: 0; height: 100vh; display: flex; flex-direction: column; background: var(--bg); color: var(--text); }\n header { display: flex; align-items: center; gap: 12px; padding: 8px 16px; background: var(--panel); border-bottom: 1px solid var(--border); }\n #scheme { margin-left: auto; }\n button, select { font: inherit; color: inherit; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; padding: 4px 10px; cursor: pointer; }\n .subtle { color: var(--subtle); }\n main { flex: 1; display: flex; min-height: 0; }\n .stage { flex: 1; overflow: auto; display: flex; flex-direction: column; align-items: center; gap: 12px; padding: 24px 16px; }\n .phone { flex: none; position: relative; display: flex; flex-direction: column; width: 393px; height: 852px; box-sizing: content-box; border: 12px solid #0b0b0c; border-radius: 67px; overflow: hidden; background: var(--screen); box-shadow: 0 0 0 2px #3a3a3c, 0 18px 50px rgb(0 0 0 / 0.3); --screen: #f2f2f2; --screen-text: #000; }\n .phone[data-scheme=\"dark\"] { --screen: #121212; --screen-text: #fff; --dialog: #2c2c2e; --dialog-line: #48484a; }\n .phone { --dialog: #f7f7f7; --dialog-line: #d1d1d6; }\n .torch { display: none; }\n .phone[data-torch=\"on\"] .torch { display: block; }\n .dialog-layer { position: absolute; inset: 0; z-index: 1; display: flex; align-items: center; justify-content: center; background: rgb(0 0 0 / 0.3); }\n .dialog { width: 270px; border-radius: 14px; overflow: hidden; background: var(--dialog); color: var(--screen-text); text-align: center; font: 13px/1.35 system-ui, sans-serif; }\n .dialog-body { display: flex; flex-direction: column; gap: 4px; padding: 18px 16px 16px; }\n .dialog-title { font-size: 17px; font-weight: 600; }\n .dialog-actions { display: flex; border-top: 1px solid var(--dialog-line); }\n .dialog-actions.stacked { flex-direction: column; }\n .dialog-actions button { flex: 1; margin: 0; padding: 11px; border: 0; border-radius: 0; background: transparent; color: #0a84ff; font-size: 17px; }\n .dialog-actions button + button { border-left: 1px solid var(--dialog-line); }\n .dialog-actions.stacked button + button { border-left: 0; border-top: 1px solid var(--dialog-line); }\n .dialog-actions .cancel { font-weight: 600; }\n .dialog-actions .destructive { color: #ff3b30; }\n .status-bar { flex: none; position: relative; height: 54px; display: flex; align-items: center; justify-content: space-between; padding: 0 30px 0 50px; color: var(--screen-text); font: 600 17px/1 system-ui, sans-serif; }\n .island { position: absolute; top: 11px; left: 50%; width: 125px; height: 37px; margin-left: -62.5px; border-radius: 19px; background: #000; }\n .indicators { display: flex; align-items: center; gap: 6px; }\n #screen { flex: 1; display: block; width: 100%; border: 0; background: var(--screen); }\n .home-indicator { position: absolute; bottom: 8px; left: 50%; width: 134px; height: 5px; margin-left: -67px; border-radius: 3px; background: var(--screen-text); pointer-events: none; }\n #error { max-width: 640px; width: 100%; margin: 0; padding: 12px; white-space: pre-wrap; overflow-wrap: anywhere; color: #fff; background: var(--danger); border-radius: 8px; }\n aside { width: 380px; display: flex; flex-direction: column; background: var(--panel); border-left: 1px solid var(--border); }\n .panel-head { display: flex; align-items: center; justify-content: space-between; padding: 8px 12px; border-bottom: 1px solid var(--border); font-weight: 600; }\n #log { flex: 1; overflow: auto; margin: 0; padding: 0; list-style: none; font: 12px/1.4 ui-monospace, monospace; }\n #log li { border-bottom: 1px solid var(--border); padding: 4px 12px; }\n #log summary { display: flex; gap: 8px; cursor: pointer; }\n #log pre { margin: 4px 0; white-space: pre-wrap; word-break: break-all; color: var(--subtle); }\n #log .time { color: var(--subtle); }\n #log .toService .arrow { color: var(--to-service); }\n #log .toWebview .arrow { color: var(--to-webview); }\n #log .toHost .arrow, #log .fromHost .arrow { color: var(--subtle); }\n #log .type { font-weight: 600; }\n #log .note { padding: 2px 12px; text-align: center; color: var(--subtle); background: var(--bg); }\n #log .failed .type { color: var(--danger); }\n @media (max-width: 900px) { main { flex-direction: column; } aside { width: auto; height: 40vh; border-left: 0; border-top: 1px solid var(--border); } }\n`;\n","export const MEGABYTE = 1024 * 1024;\n\n// Rounded up to a tenth, such as \"0.3\".\nexport function megabytes(bytes: number): string {\n return String(Math.ceil((bytes * 10) / MEGABYTE) / 10);\n}\n","import { extname } from 'node:path';\n\nconst CONTENT_TYPES: Readonly<Record<string, string>> = {\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.gif': 'image/gif',\n '.webp': 'image/webp',\n '.svg': 'image/svg+xml',\n '.pdf': 'application/pdf',\n '.json': 'application/json',\n '.txt': 'text/plain',\n '.csv': 'text/csv',\n '.html': 'text/html',\n '.zip': 'application/zip',\n};\n\nexport function contentTypeFor(name: string): string {\n return CONTENT_TYPES[extname(name).toLowerCase()] ?? 'application/octet-stream';\n}\n","import { open, readFile, rm } from 'node:fs/promises';\nimport { STATUS_CODES } from 'node:http';\nimport { TextDecoder } from 'node:util';\n\nimport type { DashMethods, DotFile, HttpDownloadResponse, HttpResponse } from '@monimesl/dot-dash';\nimport { HostError, allowsHost } from '@monimesl/dot-core';\n\nimport { MEGABYTE, megabytes } from './bytes';\nimport { contentTypeFor } from './content-types';\nimport type { TemporaryFiles } from './temporary-files';\n\n// Sends a dot's requests from the dev server, with the checks DashNetwork makes in the Kotlin core.\n\ntype HttpRequestParams = DashMethods['dash.network.http.request']['params'];\ntype HttpDownloadParams = DashMethods['dash.network.http.download']['params'];\ntype HttpUploadParams = DashMethods['dash.network.http.upload']['params'];\n\nexport const MAX_RESPONSE_BYTES = 5 * MEGABYTE;\n\nexport const MAX_DOWNLOAD_BYTES = 100 * MEGABYTE;\n\nexport const MAX_REDIRECTS = 10;\n\nconst METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'];\nconst UPLOAD_METHODS = ['POST', 'PUT', 'PATCH'];\nconst BODILESS_METHODS = ['GET', 'HEAD'];\nconst REDIRECT_STATUSES = [301, 302, 303, 307, 308];\nconst HOST_HEADERS = [\n 'content-type',\n 'content-length',\n 'transfer-encoding',\n 'host',\n 'connection',\n 'upgrade',\n];\nconst CONTENT_TYPE = /^[\\w.+-]+\\/[\\w.+*-]+\\s*(;.*)?$/;\n\n// A form is made again for each redirect that keeps it.\ntype Body =\n | { kind: 'text'; text: string; contentType: string }\n | { kind: 'form'; form: () => Promise<FormData> };\n\ninterface OutgoingRequest {\n url: URL;\n method: string;\n headers: Headers;\n body: Body | undefined;\n}\n\nexport async function sendHttpRequest(\n request: HttpRequestParams,\n hosts: readonly string[],\n signal: AbortSignal,\n): Promise<HttpResponse> {\n const outgoing = textRequest(request);\n return exchange(outgoing, hosts, signal, async (response) =>\n result(response, await readBody(response)),\n );\n}\n\nexport async function downloadHttpFile(\n request: HttpDownloadParams,\n hosts: readonly string[],\n files: TemporaryFiles,\n signal: AbortSignal,\n): Promise<HttpDownloadResponse> {\n const outgoing = textRequest(request);\n return exchange(outgoing, hosts, signal, async (response) => {\n if (!response.ok) {\n return { ...result(response, await readBody(response)), ok: false };\n }\n const file = await save(response, request.name, files);\n const { status, statusText, headers } = result(response, '');\n return { status, statusText, headers, ok: true, file };\n });\n}\n\nexport async function uploadHttpFiles(\n request: HttpUploadParams,\n hosts: readonly string[],\n files: TemporaryFiles,\n signal: AbortSignal,\n): Promise<HttpResponse> {\n if (request.files.length === 0) {\n throw invalid('An upload needs at least one file; send text with http.request');\n }\n const parts = request.files.map((upload) => {\n if (!upload.field.trim()) {\n throw invalid('Each file needs a field name');\n }\n const file = files.requireFile(upload.file);\n return { field: upload.field, file, name: upload.name ? safeFileName(upload.name) : file.name };\n });\n const makeForm = async () => {\n const form = new FormData();\n for (const [name, value] of Object.entries(request.fields ?? {})) {\n form.append(name, value);\n }\n for (const part of parts) {\n const contents = await readFile(part.file.path);\n form.append(part.field, new File([contents], part.name, { type: part.file.contentType }));\n }\n return form;\n };\n const outgoing: OutgoingRequest = {\n method: parseMethod(request.method ?? 'POST', UPLOAD_METHODS),\n headers: checkedHeaders(request.headers ?? {}),\n url: parseUrl(request.url),\n body: { kind: 'form', form: makeForm },\n };\n return exchange(outgoing, hosts, signal, async (response) =>\n result(response, await readBody(response)),\n );\n}\n\nasync function exchange<T>(\n request: OutgoingRequest,\n hosts: readonly string[],\n signal: AbortSignal,\n read: (response: Response) => Promise<T>,\n): Promise<T> {\n let outgoing = request;\n for (let redirects = 0; redirects <= MAX_REDIRECTS; redirects++) {\n if (!allowsHost(hosts, outgoing.url.hostname)) {\n throw new HostError(\n 'denied',\n `\"${outgoing.url.hostname}\" is not in dot.json's network.hosts. List every host the dot sends requests to there.`,\n );\n }\n const response = await send(outgoing, signal);\n const location = response.headers.get('location');\n if (!REDIRECT_STATUSES.includes(response.status) || location === null) {\n return read(response);\n }\n await response.body?.cancel();\n outgoing = redirected(outgoing, location, response.status);\n }\n throw new HostError(\n 'failed',\n `\"${request.url.href}\" redirected more than ${MAX_REDIRECTS} times`,\n );\n}\n\nfunction textRequest(request: HttpRequestParams): OutgoingRequest {\n const method = parseMethod(request.method ?? 'GET', METHODS);\n const { contentType = 'text/plain; charset=utf-8', others } = takeContentType(\n request.headers ?? {},\n );\n if (!CONTENT_TYPE.test(contentType)) {\n throw invalid(`\"${contentType}\" is not a Content-Type`);\n }\n const headers = checkedHeaders(others);\n let body: Body | undefined;\n if (request.body !== undefined) {\n if (BODILESS_METHODS.includes(method)) {\n throw invalid(`A ${method} request can't have a body`);\n }\n body = { kind: 'text', text: request.body, contentType };\n }\n return { url: parseUrl(request.url), method, headers, body };\n}\n\nfunction checkedHeaders(values: Readonly<Record<string, string>>): Headers {\n for (const name of Object.keys(values)) {\n if (HOST_HEADERS.includes(name.toLowerCase())) {\n throw invalid(`\"${name}\" is set by the host`);\n }\n }\n return new Headers(values);\n}\n\nfunction takeContentType(values: Readonly<Record<string, string>>): {\n contentType: string | undefined;\n others: Record<string, string>;\n} {\n let contentType: string | undefined;\n const others: Record<string, string> = {};\n for (const [name, value] of Object.entries(values)) {\n if (name.toLowerCase() === 'content-type') {\n contentType = value;\n } else {\n others[name] = value;\n }\n }\n return { contentType, others };\n}\n\nasync function send(request: OutgoingRequest, signal: AbortSignal): Promise<Response> {\n const headers = new Headers(request.headers);\n let body: string | FormData | null = null;\n if (request.body?.kind === 'text') {\n body = request.body.text;\n headers.set('content-type', request.body.contentType);\n } else if (request.body?.kind === 'form') {\n body = await request.body.form();\n }\n try {\n // Node's fetch keeps no cookies or cached responses.\n return await fetch(request.url, {\n method: request.method,\n headers,\n body,\n redirect: 'manual',\n signal,\n });\n } catch (error) {\n if (signal.aborted) {\n throw error;\n }\n const cause = (error as { cause?: { message?: string } }).cause?.message;\n throw new HostError(\n 'failed',\n `Could not reach ${request.url.hostname}: ${cause ?? (error as Error).message}`,\n );\n }\n}\n\n// As browsers do: a 303, or a 301 or 302 after a POST, becomes a GET without the body, and\n// credentials don't follow a redirect to another origin.\nfunction redirected(request: OutgoingRequest, location: string, status: number): OutgoingRequest {\n const url = URL.parse(location, request.url.href);\n if (!url || (url.protocol !== 'http:' && url.protocol !== 'https:')) {\n throw new HostError(\n 'failed',\n `${request.url.hostname} redirected to \"${location}\", which is not an http(s) URL`,\n );\n }\n const becomesGet =\n (status === 303 && request.method !== 'HEAD') ||\n ((status === 301 || status === 302) && request.method === 'POST');\n const headers = new Headers(request.headers);\n if (url.origin !== request.url.origin) {\n headers.delete('authorization');\n }\n return {\n url,\n method: becomesGet ? 'GET' : request.method,\n headers,\n body: becomesGet ? undefined : request.body,\n };\n}\n\nfunction result(response: Response, body: string): HttpResponse {\n return {\n status: response.status,\n // HTTP/2 responses carry no reason phrase, so the standard one stands in.\n statusText: response.statusText || (STATUS_CODES[response.status] ?? ''),\n ok: response.ok,\n headers: Object.fromEntries(response.headers),\n body,\n };\n}\n\nasync function readBody(response: Response): Promise<string> {\n const chunks: Uint8Array[] = [];\n await readChunks(response, MAX_RESPONSE_BYTES, (chunk) => {\n chunks.push(chunk);\n return Promise.resolve();\n });\n const charset = /charset=([^;\\s]+)/i.exec(response.headers.get('content-type') ?? '')?.[1];\n return decoderFor(charset).decode(Buffer.concat(chunks));\n}\n\nasync function save(\n response: Response,\n requestedName: string | undefined,\n files: TemporaryFiles,\n): Promise<DotFile> {\n const { id, path } = await files.newPath();\n const handle = await open(path, 'w');\n try {\n await readChunks(response, MAX_DOWNLOAD_BYTES, async (chunk) => {\n await handle.write(chunk);\n });\n } catch (error) {\n await handle.close();\n await rm(path, { force: true });\n throw error;\n }\n await handle.close();\n const name = requestedName ? safeFileName(requestedName) : fileName(response);\n return files.add(id, path, name, contentType(response, name));\n}\n\nasync function readChunks(\n response: Response,\n limit: number,\n take: (chunk: Uint8Array) => Promise<void>,\n): Promise<void> {\n if (Number(response.headers.get('content-length') ?? 0) > limit) {\n await response.body?.cancel();\n throw tooLarge(limit);\n }\n if (!response.body) {\n return;\n }\n const reader = response.body.getReader();\n let length = 0;\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n return;\n }\n length += value.length;\n if (length > limit) {\n await reader.cancel();\n throw tooLarge(limit);\n }\n await take(value);\n }\n}\n\nfunction fileName(response: Response): string {\n const disposition = response.headers.get('content-disposition') ?? '';\n const encoded = /filename\\*\\s*=\\s*[^']*''([^;]+)/i.exec(disposition)?.[1];\n const plain = /filename\\s*=\\s*\"?([^\";]+)\"?/i.exec(disposition)?.[1];\n const segment = new URL(response.url).pathname.split('/').at(-1);\n const candidates = [encoded && safeDecode(encoded), plain, segment && safeDecode(segment)];\n return candidates.map((name) => safeFileName(name ?? '')).find(Boolean) ?? 'download';\n}\n\nfunction safeFileName(name: string): string {\n return (name.split(/[/\\\\]/).at(-1) ?? '').trim().slice(0, 255);\n}\n\nfunction safeDecode(text: string): string {\n try {\n return decodeURIComponent(text);\n } catch {\n return text;\n }\n}\n\nfunction contentType(response: Response, name: string): string {\n const declared = response.headers.get('content-type')?.split(';')[0]?.trim();\n if (declared && declared !== 'application/octet-stream') {\n return declared;\n }\n return contentTypeFor(name);\n}\n\nfunction decoderFor(charset: string | undefined): TextDecoder {\n try {\n return new TextDecoder(charset ?? 'utf-8');\n } catch {\n return new TextDecoder('utf-8');\n }\n}\n\nfunction parseUrl(text: string): URL {\n const url = /^https?:\\/\\//i.test(text) ? URL.parse(text) : null;\n if (!url?.hostname) {\n throw invalid(`\"${text}\" is not an http:// or https:// URL`);\n }\n return url;\n}\n\nfunction parseMethod(name: string, methods: readonly string[]): string {\n const method = name.toUpperCase();\n if (!methods.includes(method)) {\n throw invalid(`\"${name}\" is not an HTTP method here: use ${methods.join(', ')}`);\n }\n return method;\n}\n\nfunction invalid(message: string): HostError {\n return new HostError('invalid', message);\n}\n\nfunction tooLarge(limit: number): HostError {\n return new HostError('failed', `The response is larger than ${megabytes(limit)} MB`);\n}\n","import {\n appendFile,\n cp,\n lstat,\n mkdir,\n readdir,\n readFile,\n realpath,\n rename,\n rm,\n stat,\n writeFile,\n} from 'node:fs/promises';\nimport { basename, dirname, join } from 'node:path';\n\nimport type { Stats } from 'node:fs';\n\nimport type { DashMethods, DotFile, FileEntry, StorageUsage } from '@monimesl/dot-dash';\nimport { HostError, dotFilePathProblem } from '@monimesl/dot-core';\n\nimport { MEGABYTE, megabytes } from './bytes';\nimport { contentTypeFor } from './content-types';\nimport type { TemporaryFiles } from './temporary-files';\n\n// A dot's own files, with the checks DotStorage and DashFiles make in the Kotlin core.\n\nexport const DEFAULT_QUOTA = 100 * MEGABYTE;\n\nexport const MAX_READ_BYTES = 5 * MEGABYTE;\n\ntype Params<Method extends keyof DashMethods> = DashMethods[Method]['params'];\n\nexport class DotStorage {\n readonly #root: string;\n readonly quota: number;\n\n constructor(root: string, quota = DEFAULT_QUOTA) {\n this.#root = root;\n this.quota = quota;\n }\n\n async resolve(path: string, { allowRoot = false } = {}): Promise<string> {\n const problem = dotFilePathProblem(path, { allowRoot });\n if (problem) {\n throw new HostError('invalid', problem);\n }\n const resolved = path === '' ? this.#root : join(this.#root, path);\n await this.#checkInside(resolved);\n return resolved;\n }\n\n async usage(): Promise<number> {\n return sizeOf(this.#root);\n }\n\n async clear(): Promise<void> {\n await rm(this.#root, { recursive: true, force: true });\n }\n\n async ensureRoot(): Promise<void> {\n await mkdir(this.#root, { recursive: true });\n }\n\n // The nearest part of the path that exists must be inside the storage, however links resolve.\n async #checkInside(path: string): Promise<void> {\n const realRoot = await realpath(this.#root).catch(() => null);\n if (realRoot === null) {\n return;\n }\n let existing = path;\n while (!(await exists(existing))) {\n existing = dirname(existing);\n }\n const real = await realpath(existing);\n if (real !== realRoot && !real.startsWith(`${realRoot}/`)) {\n throw new HostError('denied', \"That path leads outside the dot's storage\");\n }\n }\n}\n\nexport async function runFileCall(\n operation: string,\n params: unknown,\n storage: DotStorage,\n temporaryFiles: TemporaryFiles,\n): Promise<unknown> {\n switch (operation) {\n case 'read':\n return read(params as Params<'dash.file.read'>, storage);\n case 'write':\n return write(params as Params<'dash.file.write'>, storage);\n case 'stat': {\n const { path } = params as Params<'dash.file.stat'>;\n return entry(path, await storage.resolve(path, { allowRoot: true }));\n }\n case 'list':\n return list(params as Params<'dash.file.list'>, storage);\n case 'createFolder':\n return createFolder((params as Params<'dash.file.createFolder'>).path, storage);\n case 'move':\n return transfer(params as Params<'dash.file.move'>, storage, false);\n case 'copy':\n return transfer(params as Params<'dash.file.copy'>, storage, true);\n case 'remove':\n return remove(params as Params<'dash.file.remove'>, storage);\n case 'save':\n return save(params as Params<'dash.file.save'>, storage, temporaryFiles);\n case 'open':\n return open((params as Params<'dash.file.open'>).path, storage, temporaryFiles);\n case 'usage':\n return { used: await storage.usage(), quota: storage.quota } satisfies StorageUsage;\n default:\n throw new HostError('unsupported', `This host has no \"dash.file.${operation}\"`);\n }\n}\n\nasync function read({ path, as }: Params<'dash.file.read'>, storage: DotStorage): Promise<string> {\n const encoding = parseEncoding(as);\n const file = await storage.resolve(path);\n const info = await statOrNull(file);\n if (!info) {\n throw failed(`There is no file at \"${path}\"`);\n }\n if (info.isDirectory()) {\n throw failed(`\"${path}\" is a folder`);\n }\n if (info.size > MAX_READ_BYTES) {\n throw failed(\n `\"${path}\" is larger than ${megabytes(MAX_READ_BYTES)} MB, too large to read at once`,\n );\n }\n const bytes = await readFile(file);\n return encoding === 'base64' ? bytes.toString('base64') : bytes.toString('utf8');\n}\n\nasync function write(\n { path, data, as, append = false }: Params<'dash.file.write'>,\n storage: DotStorage,\n): Promise<FileEntry> {\n const encoding = parseEncoding(as);\n const file = await storage.resolve(path);\n const existing = await statOrNull(file);\n if (existing?.isDirectory()) {\n throw failed(`\"${path}\" is a folder`);\n }\n if (encoding === 'base64' && !/^[A-Za-z0-9+/]*={0,2}$/.test(data)) {\n throw new HostError('invalid', \"The data isn't base64\");\n }\n const bytes = Buffer.from(data, encoding === 'base64' ? 'base64' : 'utf8');\n const replacedBytes = append ? 0 : (existing?.size ?? 0);\n await checkQuota(storage, bytes.length - replacedBytes);\n await createParent(storage, file, path);\n if (append) {\n await appendFile(file, bytes);\n } else {\n await writeFile(file, bytes);\n }\n return written(path, file);\n}\n\nasync function list(\n { path, recursive = false }: Params<'dash.file.list'>,\n storage: DotStorage,\n): Promise<FileEntry[]> {\n const folder = await storage.resolve(path, { allowRoot: true });\n const info = await statOrNull(folder);\n if (!info) {\n // The storage itself exists once the dot first writes.\n if (path === '') {\n return [];\n }\n throw failed(`There is no folder at \"${path}\"`);\n }\n if (!info.isDirectory()) {\n throw failed(`\"${path}\" is a file`);\n }\n const entries: FileEntry[] = [];\n await collect(path, folder, recursive, entries);\n // Paths are unique, so no two compare equal.\n return entries.sort((first, second) => (first.path < second.path ? -1 : 1));\n}\n\nasync function createFolder(path: string, storage: DotStorage): Promise<FileEntry> {\n const folder = await storage.resolve(path);\n if ((await statOrNull(folder))?.isFile()) {\n throw failed(`\"${path}\" is a file`);\n }\n await createParent(storage, folder, path);\n await mkdir(folder, { recursive: true });\n return written(path, folder);\n}\n\nasync function transfer(\n { from, to, overwrite = false }: Params<'dash.file.move'>,\n storage: DotStorage,\n keepSource: boolean,\n): Promise<FileEntry> {\n const source = await storage.resolve(from);\n const target = await storage.resolve(to);\n if (!(await exists(source))) {\n throw failed(`There is nothing at \"${from}\"`);\n }\n if (to === from || to.startsWith(`${from}/`)) {\n throw new HostError('invalid', `\"${to}\" is inside \"${from}\"`);\n }\n // A move adds nothing: the files only change folders.\n const addedBytes = keepSource ? await sizeOf(source) : 0;\n await makeRoom(storage, target, to, overwrite, addedBytes);\n if (keepSource) {\n await cp(source, target, { recursive: true });\n } else {\n await rename(source, target);\n }\n return written(to, target);\n}\n\nasync function remove(\n { path, recursive = false }: Params<'dash.file.remove'>,\n storage: DotStorage,\n): Promise<void> {\n const target = await storage.resolve(path);\n const info = await statOrNull(target);\n if (!info) {\n return;\n }\n if (info.isDirectory() && !recursive && (await readdir(target)).length > 0) {\n throw failed(\n `\"${path}\" is a folder with files in it: pass { recursive: true } to remove them too`,\n );\n }\n await rm(target, { recursive: true, force: true });\n}\n\nasync function save(\n { file: id, path, overwrite = false }: Params<'dash.file.save'>,\n storage: DotStorage,\n temporaryFiles: TemporaryFiles,\n): Promise<FileEntry> {\n const file = temporaryFiles.requireFile(id);\n const target = await storage.resolve(path);\n await makeRoom(storage, target, path, overwrite, file.size);\n await cp(file.path, target);\n return written(path, target);\n}\n\nasync function open(\n path: string,\n storage: DotStorage,\n temporaryFiles: TemporaryFiles,\n): Promise<DotFile> {\n const file = await storage.resolve(path);\n const info = await statOrNull(file);\n if (!info?.isFile()) {\n throw failed(`There is no file at \"${path}\"`);\n }\n const name = basename(path);\n return temporaryFiles.reference(file, name, contentTypeFor(name));\n}\n\nasync function collect(\n folderPath: string,\n folder: string,\n recursive: boolean,\n entries: FileEntry[],\n): Promise<void> {\n for (const name of await readdir(folder)) {\n const path = folderPath === '' ? name : `${folderPath}/${name}`;\n const child = join(folder, name);\n const found = await entry(path, child);\n if (!found) {\n continue;\n }\n entries.push(found);\n if (recursive && found.kind === 'folder') {\n await collect(path, child, true, entries);\n }\n }\n}\n\nasync function entry(path: string, file: string): Promise<FileEntry | null> {\n const info = await statOrNull(file);\n if (!info) {\n return null;\n }\n const name = path === '' ? '' : basename(path);\n const modifiedAt = Math.round(info.mtimeMs);\n if (info.isDirectory()) {\n return { name, path, kind: 'folder', size: 0, modifiedAt };\n }\n return {\n name,\n path,\n kind: 'file',\n size: info.size,\n contentType: contentTypeFor(name),\n modifiedAt,\n };\n}\n\nasync function written(path: string, file: string): Promise<FileEntry> {\n const found = await entry(path, file);\n if (!found) {\n throw failed(`\"${path}\" was removed while it was written`);\n }\n return found;\n}\n\nasync function makeRoom(\n storage: DotStorage,\n target: string,\n path: string,\n overwrite: boolean,\n addedBytes: number,\n): Promise<void> {\n if ((await exists(target)) && !overwrite) {\n throw failed(`\"${path}\" already exists: pass { overwrite: true } to replace it`);\n }\n await checkQuota(storage, addedBytes - (await sizeOf(target)));\n await rm(target, { recursive: true, force: true });\n await createParent(storage, target, path);\n}\n\nasync function createParent(storage: DotStorage, file: string, path: string): Promise<void> {\n await storage.ensureRoot();\n const parent = dirname(file);\n if ((await statOrNull(parent))?.isFile()) {\n throw failed(`A folder in \"${path}\" is a file`);\n }\n await mkdir(parent, { recursive: true });\n}\n\nasync function checkQuota(storage: DotStorage, addedBytes: number): Promise<void> {\n if (addedBytes <= 0) {\n return;\n }\n const used = await storage.usage();\n if (used + addedBytes > storage.quota) {\n throw failed(\n `The dot's storage is full: it uses ${megabytes(used)} of ${megabytes(storage.quota)} MB, ` +\n `and this needs ${megabytes(addedBytes)} MB more`,\n );\n }\n}\n\nasync function sizeOf(path: string): Promise<number> {\n const info = await lstat(path).catch(() => null);\n if (!info) {\n return 0;\n }\n if (!info.isDirectory()) {\n return info.size;\n }\n let total = 0;\n for (const name of await readdir(path)) {\n total += await sizeOf(join(path, name));\n }\n return total;\n}\n\nasync function exists(path: string): Promise<boolean> {\n return (await lstat(path).catch(() => null)) !== null;\n}\n\nasync function statOrNull(path: string): Promise<Stats | null> {\n return stat(path).catch(() => null);\n}\n\nfunction parseEncoding(name: string | undefined): 'text' | 'base64' {\n if (name === undefined || name === 'text' || name === 'base64') {\n return name ?? 'text';\n }\n throw new HostError('invalid', `\"${String(name)}\" is not an encoding: use \"text\" or \"base64\"`);\n}\n\nfunction failed(message: string): HostError {\n return new HostError('failed', message);\n}\n","import { join } from 'node:path';\nimport { randomBytes } from 'node:crypto';\nimport { mkdir, rm, stat } from 'node:fs/promises';\n\nimport { HostError } from '@monimesl/dot-core';\nimport type { DotFile } from '@monimesl/dot-dash';\n\nexport const TEMPORARY_FILES_PATH = '/__dot/files/';\n\nexport interface TemporaryFile extends DotFile {\n readonly path: string;\n}\n\n// The files dash calls make for the dot, such as downloads, until it reloads.\nexport class TemporaryFiles {\n readonly #directory: string;\n readonly #files = new Map<string, TemporaryFile>();\n\n constructor(directory: string) {\n this.#directory = directory;\n }\n\n file(id: string): TemporaryFile | undefined {\n return this.#files.get(id);\n }\n\n requireFile(id: string): TemporaryFile {\n const file = this.#files.get(id);\n if (!file) {\n throw new HostError('invalid', `\"${id}\" is not a file this dot has`);\n }\n return file;\n }\n\n async clear(): Promise<void> {\n this.#files.clear();\n await rm(this.#directory, { recursive: true, force: true });\n }\n\n async newPath(): Promise<{ id: string; path: string }> {\n await mkdir(this.#directory, { recursive: true });\n const id = newId();\n return { id, path: join(this.#directory, id) };\n }\n\n // Serves a file kept elsewhere without copying it; clearing leaves the file.\n async reference(path: string, name: string, contentType: string): Promise<DotFile> {\n return this.add(newId(), path, name, contentType);\n }\n\n async add(id: string, path: string, name: string, contentType: string): Promise<DotFile> {\n const { size } = await stat(path);\n const url = `${TEMPORARY_FILES_PATH}${id}`;\n this.#files.set(id, { id, name, size, contentType, url, path });\n return { id, name, size, contentType, url };\n }\n}\n\nfunction newId(): string {\n return `f_${randomBytes(12).toString('base64url')}`;\n}\n","import { mkdtemp, readdir, readFile, stat } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport type { IncomingMessage, Server, ServerResponse } from 'node:http';\nimport { createServer } from 'node:http';\nimport type { AddressInfo } from 'node:net';\nimport { createRequire } from 'node:module';\nimport { dirname, extname, join, resolve, sep } from 'node:path';\n\nimport { HostError, REACT_HOST_PATH, newestReactOfMajor } from '@monimesl/dot-core';\nimport type { DashMethods } from '@monimesl/dot-dash';\n\nimport { simulatorPage } from './page';\nimport type { HostManifest } from './manifest';\nimport { downloadHttpFile, sendHttpRequest, uploadHttpFiles } from './network';\nimport { DotStorage, runFileCall } from './files';\nimport { DOT_PATH, CLIENT_PATH, EVENTS_PATH, FILE_PATH, HTTP_PATH, WEBVIEW_URL } from './paths';\nimport { TEMPORARY_FILES_PATH, TemporaryFiles } from './temporary-files';\n\ntype HttpRequestParams = DashMethods['dash.network.http.request']['params'];\ntype HttpDownloadParams = DashMethods['dash.network.http.download']['params'];\ntype HttpUploadParams = DashMethods['dash.network.http.upload']['params'];\n\nexport interface SimulatorOptions {\n dotDir: string;\n // The React files hosts serve, a folder per version, from the dot's renderer. Without it, a dot\n // cannot load React.\n reactHostDir?: string | undefined;\n name?: string;\n port?: number | undefined;\n host?: string;\n clientDir?: string;\n // A temporary folder by default.\n storageDir?: string | undefined;\n}\n\nexport interface Simulator {\n readonly url: string;\n reload(): void;\n error(message: string): void;\n close(): Promise<void>;\n}\n\nexport const DEFAULT_PORT = 3680;\n\nconst PORT_ATTEMPTS = 5;\n\nconst MIME_TYPES: Record<string, string> = {\n '.html': 'text/html; charset=utf-8',\n '.js': 'text/javascript; charset=utf-8',\n '.map': 'application/json; charset=utf-8',\n '.json': 'application/json; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.svg': 'image/svg+xml',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.gif': 'image/gif',\n '.webp': 'image/webp',\n '.woff2': 'font/woff2',\n};\n\nexport async function startSimulator({\n dotDir,\n reactHostDir,\n name = 'Dot',\n port = DEFAULT_PORT,\n host = 'localhost',\n clientDir = defaultClientDir(),\n storageDir,\n}: SimulatorOptions): Promise<Simulator> {\n const clients = new Set<ServerResponse>();\n const files = new TemporaryFiles(await mkdtemp(join(tmpdir(), 'dot-simulator-files-')));\n const storage = new DotStorage(\n storageDir ?? (await mkdtemp(join(tmpdir(), 'dot-simulator-storage-'))),\n );\n let buildError: string | null = null;\n const broadcast = (event: string, data = '') => {\n for (const client of clients) {\n client.write(serverEvent(event, data));\n }\n };\n\n const server = createServer((request, response) => {\n const { pathname } = new URL(request.url ?? '/', 'http://localhost');\n if (pathname === '/') {\n send(response, 200, simulatorPage(name), '.html');\n return;\n }\n if (pathname === '/favicon.ico') {\n send(response, 204, '');\n return;\n }\n if (pathname === EVENTS_PATH) {\n response.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-store',\n Connection: 'keep-alive',\n });\n response.write(': connected\\n\\n');\n if (buildError !== null) {\n response.write(serverEvent('build-error', buildError));\n }\n clients.add(response);\n request.on('close', () => clients.delete(response));\n return;\n }\n if (pathname.startsWith(HTTP_PATH) && request.method === 'POST') {\n void relayHttpCall(request, response, pathname.slice(HTTP_PATH.length), dotDir, files);\n return;\n }\n if (pathname.startsWith(FILE_PATH) && request.method === 'POST') {\n const operation = pathname.slice(FILE_PATH.length);\n void answer(request, response, (params) => runFileCall(operation, params, storage, files));\n return;\n }\n if (pathname.startsWith(TEMPORARY_FILES_PATH)) {\n void serveTemporaryFile(response, files, pathname.slice(TEMPORARY_FILES_PATH.length));\n return;\n }\n // As a native host serves the React it ships.\n if (pathname.startsWith(REACT_HOST_PATH) && reactHostDir !== undefined) {\n void serveReact(response, reactHostDir, pathname.slice(REACT_HOST_PATH.length));\n return;\n }\n if (pathname.startsWith(CLIENT_PATH)) {\n void serveFile(response, clientDir, pathname.slice(CLIENT_PATH.length));\n return;\n }\n if (pathname.startsWith(DOT_PATH)) {\n // As a native host injects its bridge into the WebView before the dot runs.\n const injectBridge = pathname === WEBVIEW_URL || `${pathname}index.html` === WEBVIEW_URL;\n void serveFile(response, dotDir, pathname.slice(DOT_PATH.length), injectBridge);\n return;\n }\n send(response, 404, 'Not found');\n });\n\n await listen(server, port, host);\n\n return {\n url: `http://${host}:${(server.address() as AddressInfo).port}/`,\n reload: () => {\n buildError = null;\n // The dot starts again, so what its last run made goes.\n void files.clear();\n broadcast('reload');\n },\n error: (message) => {\n buildError = message;\n broadcast('build-error', message);\n },\n close: () =>\n new Promise((resolvePromise) => {\n for (const client of clients) {\n client.end();\n }\n server.close(() => void files.clear().then(() => resolvePromise()));\n }),\n };\n}\n\nfunction relayHttpCall(\n request: IncomingMessage,\n response: ServerResponse,\n call: string,\n dotDir: string,\n files: TemporaryFiles,\n): Promise<void> {\n return answer(request, response, async (params, signal) => {\n const hosts = await networkHosts(dotDir);\n if (call === 'download') {\n return downloadHttpFile(params as HttpDownloadParams, hosts, files, signal);\n }\n if (call === 'upload') {\n return uploadHttpFiles(params as HttpUploadParams, hosts, files, signal);\n }\n return sendHttpRequest(params as HttpRequestParams, hosts, signal);\n });\n}\n\n// The page closing the connection means the dot cancelled the call.\nasync function answer(\n request: IncomingMessage,\n response: ServerResponse,\n run: (params: unknown, signal: AbortSignal) => Promise<unknown>,\n): Promise<void> {\n const controller = new AbortController();\n response.on('close', () => controller.abort());\n let reply: unknown;\n try {\n const chunks: Buffer[] = [];\n for await (const chunk of request) {\n chunks.push(chunk as Buffer);\n }\n const params: unknown = JSON.parse(Buffer.concat(chunks).toString('utf8'));\n reply = { result: (await run(params, controller.signal)) ?? null };\n } catch (error) {\n if (controller.signal.aborted) {\n return;\n }\n const code = error instanceof HostError ? error.code : 'failed';\n reply = { error: { code, message: (error as Error).message } };\n }\n send(response, 200, JSON.stringify(reply), '.json');\n}\n\nasync function serveTemporaryFile(\n response: ServerResponse,\n files: TemporaryFiles,\n id: string,\n): Promise<void> {\n const file = files.file(id);\n if (!file) {\n send(response, 404, 'Not found');\n return;\n }\n response.writeHead(200, { 'Content-Type': file.contentType, 'Cache-Control': 'no-store' });\n response.end(await readFile(file.path));\n}\n\n// Read for each request, so a rebuild with new hosts applies at once.\nasync function networkHosts(dotDir: string): Promise<readonly string[]> {\n const manifest = JSON.parse(\n await readFile(join(dotDir, 'manifest.json'), 'utf8'),\n ) as HostManifest;\n return manifest.network?.hosts ?? [];\n}\n\nfunction serverEvent(event: string, data: string): string {\n const lines = data.split('\\n').map((line) => `data: ${line}`);\n return `event: ${event}\\n${lines.join('\\n')}\\n\\n`;\n}\n\nasync function listen(server: Server, port: number, host: string): Promise<void> {\n for (let attempt = 0; attempt < PORT_ATTEMPTS; attempt++) {\n const taken = await new Promise<boolean>((resolvePromise, reject) => {\n const onError = (error: NodeJS.ErrnoException) => {\n if (error.code === 'EADDRINUSE') {\n resolvePromise(true);\n } else {\n reject(error);\n }\n };\n server.once('error', onError);\n server.listen(port + attempt, host, () => {\n server.off('error', onError);\n resolvePromise(false);\n });\n });\n if (!taken) {\n return;\n }\n }\n const last = port + PORT_ATTEMPTS - 1;\n throw new Error(`Ports ${port} to ${last} are all in use. Pass another with --port.`);\n}\n\n// `<major>/<file>`, from the newest version of that major in `reactHostDir`.\nasync function serveReact(\n response: ServerResponse,\n reactHostDir: string,\n path: string,\n): Promise<void> {\n const [major = '', ...file] = path.split('/');\n const versions = await readdir(reactHostDir).catch(() => []);\n const version = newestReactOfMajor(versions, Number(major));\n if (version === undefined) {\n send(response, 404, 'Not found');\n return;\n }\n await serveFile(response, join(reactHostDir, version), file.join('/'));\n}\n\nasync function serveFile(\n response: ServerResponse,\n root: string,\n path: string,\n injectBridge = false,\n): Promise<void> {\n const base = resolve(root);\n let file = resolve(base, decodeURIComponent(path));\n if (file !== base && !file.startsWith(base + sep)) {\n send(response, 404, 'Not found');\n return;\n }\n try {\n if ((await stat(file)).isDirectory()) {\n file = join(file, 'index.html');\n }\n const body = await readFile(file);\n if (!injectBridge) {\n send(response, 200, body, extname(file));\n return;\n }\n const bridge = `<script src=\"${CLIENT_PATH}webview-bridge.js\"></script>`;\n send(response, 200, body.toString('utf8').replace('<head>', `<head>\\n ${bridge}`), '.html');\n } catch {\n send(response, 404, 'Not found');\n }\n}\n\nfunction send(\n response: ServerResponse,\n status: number,\n body: string | Buffer,\n extension = '.txt',\n): void {\n response.writeHead(status, {\n 'Content-Type': MIME_TYPES[extension] ?? 'text/plain; charset=utf-8',\n 'Cache-Control': 'no-store',\n });\n response.end(body);\n}\n\nfunction defaultClientDir(): string {\n const manifest = createRequire(import.meta.url).resolve('@monimesl/dot-simulator/package.json');\n return join(dirname(manifest), 'dist/client');\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monimesl/dot-simulator",
3
- "version": "0.1.0-alpha.2",
3
+ "version": "0.1.0-alpha.3",
4
4
  "description": "Browser host for Dot mini apps: runs the service in a Worker and the webview in a device frame",
5
5
  "type": "module",
6
6
  "repository": {
@@ -24,7 +24,7 @@
24
24
  "main": "./dist/node/index.js",
25
25
  "types": "./dist/node/index.d.ts",
26
26
  "dependencies": {
27
- "@monimesl/dot-core": "0.1.0-alpha.2"
27
+ "@monimesl/dot-core": "0.1.0-alpha.3"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@types/node": "^22.20.4",
@@ -33,7 +33,7 @@
33
33
  "tsdown": "^0.23.0",
34
34
  "typescript": "^5.9.3",
35
35
  "vitest": "^5.0.1",
36
- "@monimesl/dot-dash": "0.1.0-alpha.2"
36
+ "@monimesl/dot-dash": "0.1.0-alpha.3"
37
37
  },
38
38
  "publishConfig": {
39
39
  "access": "public"