@condorinst/mobile-bridge 0.22.0 → 0.23.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/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts","../package.json","../src/canvas-admin/command.ts","../src/cli-core.ts","../src/canvas-admin/client.ts","../src/canvas-admin/tty.ts","../src/cli-dev.ts","../src/keys.ts","../src/dev/policy.ts","../src/dev/fixtures.ts","../src/dev/scenarios.ts"],"sourcesContent":["/**\n * cli.ts — `bin` entry for `condor-mobile-bridge`.\n *\n * Thin: parse argv, dispatch to the subcommand, print a report and pick an\n * exit code. Every testable decision lives in `cli-core.ts`.\n */\nimport { mkdirSync, writeFileSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport { version as packageVersion } from '../package.json';\nimport { CANVAS_USAGE, parseCanvasArgs, runCanvas } from './canvas-admin/command';\nimport { closeTerminalIo, terminalIo } from './canvas-admin/tty';\nimport { buildBundle, parseArgs, USAGE, type ValidationResult, validateBundle } from './cli-core';\nimport { startDevServer } from './cli-dev';\n\nfunction runDev(input: string, scenario: string, port: number, nativeVersion?: string): void {\n const server = startDevServer({ input, scenario, port, nativeVersion });\n server.on('listening', () => {\n const native = nativeVersion === undefined ? '' : `&nativeVersion=${nativeVersion}`;\n console.log(\n `Condor Canvas dev harness: http://localhost:${port}/?scenario=${scenario}${native}`,\n );\n console.log(` bundle: ${input} (re-read on every reload)`);\n console.log(' sandbox, shell policy and CSP: same as the app');\n console.log(' scenarios: default, cold-start, no-devices, shared-serial, offline');\n console.log(' OS back button: the \"◀ OS back\" button at the top');\n console.log('\\nA bundle is ready for a device only when it handles every scenario.');\n });\n server.on('error', (err) => {\n console.error(`dev: ${err.message}`);\n process.exit(1);\n });\n}\n\nfunction runBuild(htmlPath: string, outPath: string, maxBytes: number): void {\n const result = buildBundle(htmlPath, maxBytes);\n\n const resolvedOut = resolve(outPath);\n mkdirSync(dirname(resolvedOut), { recursive: true });\n writeFileSync(resolvedOut, result.zipBytes);\n\n const kb = (Buffer.byteLength(result.html) / 1024).toFixed(1);\n console.log(`Built ${outPath}`);\n console.log(` entry: index.html (${kb} KB self-contained)`);\n console.log(` bundleSize (zip): ${result.size} bytes`);\n console.log(` bundleHash (zip): ${result.hash}`);\n\n if (result.overLimit) {\n const limitMb = (maxBytes / (1024 * 1024)).toFixed(1);\n console.warn(\n `\\n⚠️ WARNING: the .zip (${result.size} bytes) exceeds the ${limitMb} MB limit — ` +\n 'the app will REJECT this bundle (falls back to the default screen). Reduce inline assets/media.',\n );\n }\n\n console.log('\\nNext: npx condor-mobile-bridge validate -i ' + outPath + ' --strict');\n}\n\nfunction reportValidation(input: string, result: ValidationResult, strict: boolean): number {\n console.log(`Validating ${input}`);\n if (result.entry) console.log(` entry: ${result.entry}`);\n if (result.size !== null) console.log(` bundleSize (zip): ${result.size} bytes`);\n if (result.hash !== null) console.log(` bundleHash (zip): ${result.hash}`);\n\n const errors = result.findings.filter((f) => f.severity === 'error');\n const warnings = result.findings.filter((f) => f.severity === 'warning');\n\n if (result.findings.length === 0) {\n console.log('\\n✅ No findings. This bundle conforms to the Canvas contract.');\n return 0;\n }\n\n console.log('');\n for (const f of result.findings) {\n const badge = f.severity === 'error' ? '❌ ERROR ' : '⚠️ WARNING';\n console.log(`${badge} [${f.rule}] ${f.message}`);\n }\n\n console.log(\n `\\n${errors.length} error(s), ${warnings.length} warning(s).` +\n (result.size === null\n ? '\\nNote: validating an HTML file skips the size and hash gates. Validate the .zip before delivery.'\n : ''),\n );\n\n if (errors.length > 0) {\n console.log('Errors are gates the Canvas app itself applies — it would reject this bundle.');\n return 1;\n }\n if (warnings.length > 0 && strict) {\n console.log('Warnings are failures under --strict.');\n return 1;\n }\n console.log(\n 'Warnings are not app-level rejections, but they fail silently on a real device. Re-run with --strict to gate on them.',\n );\n return 0;\n}\n\nasync function runCanvasCommand(argv: string[]): Promise<void> {\n if (argv.length === 0 || argv.includes('--help') || argv.includes('-h')) {\n console.log(CANVAS_USAGE);\n process.exit(argv.length === 0 ? 1 : 0);\n }\n const parsed = parseCanvasArgs(argv, process.env);\n if ('error' in parsed) {\n console.error(parsed.error);\n console.error('Run `condor-mobile-bridge canvas --help`.');\n process.exit(1);\n }\n const code = await runCanvas(parsed, terminalIo(), {\n userAgent: `condor-mobile-bridge-cli/${packageVersion}`,\n });\n closeTerminalIo();\n process.exit(code);\n}\n\nfunction main(): void {\n if (process.argv[2] === 'canvas') {\n void runCanvasCommand(process.argv.slice(3));\n return;\n }\n const args = parseArgs(process.argv.slice(2));\n\n if (args.help || !args.htmlPath) {\n console.log(USAGE);\n process.exit(args.help ? 0 : 1);\n }\n\n if (args.command === 'dev') {\n try {\n runDev(args.htmlPath, args.scenario, args.port, args.nativeVersion);\n } catch (err) {\n console.error(`dev: ${err instanceof Error ? err.message : String(err)}`);\n process.exit(1);\n }\n return;\n }\n\n if (args.command === 'validate') {\n const result = validateBundle(args.htmlPath, args.maxBytes);\n process.exit(reportValidation(args.htmlPath, result, args.strict));\n }\n\n runBuild(args.htmlPath, args.outPath, args.maxBytes);\n}\n\nmain();\n","{\n \"name\": \"@condorinst/mobile-bridge\",\n \"version\": \"0.22.0\",\n \"description\": \"TypeScript SDK for the JS↔Native bridge of the Condor white-label mobile app.\",\n \"license\": \"SEE LICENSE IN LICENSE\",\n \"private\": false,\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"type\": \"module\",\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"./dev\": {\n \"types\": \"./dist/dev.d.ts\",\n \"import\": \"./dist/dev.js\",\n \"require\": \"./dist/dev.cjs\"\n },\n \"./contract\": \"./contract/bridge-contract.json\",\n \"./package.json\": \"./package.json\"\n },\n \"bin\": {\n \"condor-mobile-bridge\": \"dist/cli.js\"\n },\n \"files\": [\n \"dist\",\n \"docs\",\n \"contract\",\n \"examples/minimal-home/src\",\n \"examples/minimal-home/index.html\",\n \"examples/minimal-home/build.mjs\",\n \"examples/minimal-home/package.json\",\n \"examples/minimal-home/tsconfig.json\",\n \"examples/minimal-home/README.md\",\n \"examples/minimal-home/AGENTS.md\",\n \"AGENTS.md\",\n \"llms.txt\",\n \"llms-full.txt\",\n \"README.md\",\n \"CHANGELOG.md\",\n \"LICENSE\"\n ],\n \"scripts\": {\n \"build\": \"tsup && node scripts/gen-llms-full.mjs\",\n \"typecheck\": \"tsc --noEmit && tsc --noEmit -p examples/minimal-home/tsconfig.check.json && tsc --noEmit -p examples/client_demo\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"lint\": \"biome check .\",\n \"format\": \"biome format --write .\",\n \"prepare\": \"npm run build\",\n \"prepublishOnly\": \"npm run build && npm run test\"\n },\n \"engines\": {\n \"node\": \">=18\"\n },\n \"devDependencies\": {\n \"@biomejs/biome\": \"^2.0.0\",\n \"@types/node\": \"^22.0.0\",\n \"jsdom\": \"^26.0.0\",\n \"tsup\": \"^8.3.0\",\n \"typescript\": \"^5.6.0\",\n \"vitest\": \"^4.1.8\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://bitbucket.org/condorinstprogdev/mobile-bridge.git\"\n },\n \"homepage\": \"https://bitbucket.org/condorinstprogdev/mobile-bridge\",\n \"author\": \"Condor Instruments\",\n \"bugs\": {\n \"url\": \"https://bitbucket.org/condorinstprogdev/mobile-bridge/issues\"\n },\n \"dependencies\": {\n \"fflate\": \"^0.8.3\",\n \"node-html-parser\": \"^8.0.2\",\n \"valibot\": \"^1.4.1\"\n }\n}\n","/**\n * `condor-mobile-bridge canvas publish | list | revoke` — the flow around\n * {@link CanvasAdminClient}: local validation, prompts, confirmation and\n * reporting. Terminal I/O is injected ({@link CanvasIo}) so the flow is testable.\n *\n * Safety rules, on purpose:\n * - the password is only ever read from an interactive prompt without echo —\n * never from a flag, argv or the environment — and is never stored;\n * - the session is kept in memory for one command;\n * - publishing shows what goes where and asks before sending, because it goes\n * live at once for every user of the company.\n */\n\nimport { createHash } from 'node:crypto';\nimport { existsSync, readFileSync, statSync } from 'node:fs';\nimport { extname, join, resolve } from 'node:path';\nimport { buildBundle, type ValidationFinding, validateZipBytes } from '../cli-core';\nimport {\n CANVAS_ADMIN_API,\n CanvasAdminClient,\n type CanvasAdminEnv,\n CanvasAdminError,\n type CanvasBundleRecord,\n} from './client';\n\nexport interface CanvasIo {\n /** `false` when stdin is not a terminal: nothing can be asked. */\n readonly interactive: boolean;\n ask(question: string): Promise<string>;\n /** Reads without echoing what is typed. */\n askSecret(question: string): Promise<string>;\n print(line: string): void;\n printError(line: string): void;\n}\n\nexport type CanvasAction = 'publish' | 'list' | 'revoke';\n\nexport interface CanvasArgs {\n action: CanvasAction;\n env: CanvasAdminEnv;\n baseUrl?: string;\n companyId?: number;\n userId?: number;\n input?: string;\n version?: string;\n minAppVersion?: string;\n bundleId?: number;\n fallbackVersion?: string;\n yes: boolean;\n json: boolean;\n}\n\nexport const CANVAS_USAGE = `condor-mobile-bridge canvas — publish a bundle to the Condor Canvas app.\n\nUsage:\n npx condor-mobile-bridge canvas publish <dist/index.html|dir|bundle.zip> --version <v> [options]\n npx condor-mobile-bridge canvas list [options]\n npx condor-mobile-bridge canvas revoke <bundleId> [--fallback-version <v>] [options]\n\nPublishing goes LIVE AT ONCE for every user of the company — there is no draft.\nUse a test company, and see docs/device-testing.md.\n\nOptions:\n --company <id> Company id, as in the Condor web login (or CONDOR_CANVAS_COMPANY).\n --user <id> Your user id, as in the Condor web login (or CONDOR_CANVAS_USER).\n Both are asked when not given.\n --version <v> publish: bundle version, unique per company.\n --min-app-version <v> publish: minimum app build that may load it.\n --fallback-version <v> revoke: VERSION to serve instead (the version column of\n canvas list, e.g. 0.0.8 — not the id).\n --env prod|dev API environment (default: prod). dev is for Condor staff.\n --base-url <url> Override the API URL.\n --yes Do not ask for confirmation.\n --json Print results as JSON.\n\nYour password (and the two-factor code, when your company uses one) is asked\ninteractively, without echo, and never stored. There is no non-interactive login.`;\n\nconst intOrUndefined = (value: string | undefined): number | undefined => {\n if (value === undefined) return undefined;\n const n = Number(value);\n return Number.isInteger(n) && n > 0 ? n : Number.NaN;\n};\n\n/**\n * Parses the argv that follows `canvas`. The company and user ids — not\n * secrets — may also come from `CONDOR_CANVAS_COMPANY` / `CONDOR_CANVAS_USER`;\n * flags win. The password never comes from anywhere but the prompt.\n */\nexport function parseCanvasArgs(\n argv: string[],\n env: Record<string, string | undefined> = {},\n): CanvasArgs | { error: string } {\n const [action, ...rest] = argv;\n if (action !== 'publish' && action !== 'list' && action !== 'revoke') {\n return { error: `Unknown canvas command \"${action ?? ''}\". Use publish, list or revoke.` };\n }\n const args: CanvasArgs = {\n action,\n env: 'prod',\n yes: false,\n json: false,\n companyId: intOrUndefined(env.CONDOR_CANVAS_COMPANY),\n userId: intOrUndefined(env.CONDOR_CANVAS_USER),\n };\n const positional: string[] = [];\n for (let i = 0; i < rest.length; i++) {\n const token = rest[i] ?? '';\n const next = (): string | undefined => rest[++i];\n switch (token) {\n case '--env': {\n const env = next();\n if (env !== 'prod' && env !== 'dev') return { error: '--env must be prod or dev.' };\n args.env = env;\n break;\n }\n case '--base-url':\n args.baseUrl = next();\n break;\n case '--company':\n args.companyId = intOrUndefined(next());\n break;\n case '--user':\n args.userId = intOrUndefined(next());\n break;\n case '--version':\n args.version = next();\n break;\n case '--min-app-version':\n args.minAppVersion = next();\n break;\n case '--fallback-version':\n args.fallbackVersion = next();\n break;\n case '--yes':\n args.yes = true;\n break;\n case '--json':\n args.json = true;\n break;\n case '--password':\n return {\n error: 'There is no --password flag: the password is only read from the prompt.',\n };\n default:\n if (token.startsWith('-')) return { error: `Unknown option ${token}.` };\n positional.push(token);\n }\n }\n if (Number.isNaN(args.companyId) || Number.isNaN(args.userId)) {\n return {\n error:\n '--company and --user (or CONDOR_CANVAS_COMPANY / CONDOR_CANVAS_USER) take a positive integer id.',\n };\n }\n if (action === 'publish') {\n args.input = positional[0];\n if (!args.input)\n return { error: 'canvas publish needs the bundle: dist/index.html, a dir or a .zip.' };\n if (!args.version) return { error: 'canvas publish needs --version.' };\n }\n if (action === 'revoke') {\n args.bundleId = intOrUndefined(positional[0]);\n if (!args.bundleId || Number.isNaN(args.bundleId)) {\n return { error: 'canvas revoke needs the bundle id (see canvas list).' };\n }\n }\n return args;\n}\n\n/** Reads the input as the zip the app will download, building it when needed. */\nfunction loadZip(input: string): { zipBytes: Uint8Array; findings: ValidationFinding[] } {\n const path = resolve(input);\n if (!existsSync(path)) throw new Error(`${input}: not found.`);\n let zipBytes: Uint8Array;\n if (extname(path).toLowerCase() === '.zip') {\n zipBytes = new Uint8Array(readFileSync(path));\n } else {\n const html = statSync(path).isDirectory() ? join(path, 'index.html') : path;\n zipBytes = buildBundle(html).zipBytes;\n }\n return { zipBytes, findings: validateZipBytes(zipBytes).findings };\n}\n\nasync function askId(io: CanvasIo, label: string, value: number | undefined): Promise<number> {\n if (value !== undefined) return value;\n const answer = (await io.ask(`${label}: `)).trim();\n const n = Number(answer);\n if (!Number.isInteger(n) || n <= 0) throw new Error(`${label} must be a positive integer.`);\n return n;\n}\n\nasync function confirm(io: CanvasIo, args: CanvasArgs, question: string): Promise<boolean> {\n if (args.yes) return true;\n const answer = (await io.ask(`${question} [y/N] `)).trim().toLowerCase();\n return answer === 'y' || answer === 'yes';\n}\n\nasync function logIn(\n io: CanvasIo,\n client: CanvasAdminClient,\n companyId: number,\n userId: number,\n): Promise<void> {\n io.print('');\n io.print('Log in with your Condor web account (typing is hidden; press Enter when done).');\n let password = '';\n for (let attempt = 0; attempt < 3 && !password.trim(); attempt++) {\n if (attempt > 0) io.printError('The password cannot be empty.');\n password = await io.askSecret('Password: ');\n }\n // The server drops empty fields and answers \"Invalid data\": stop here instead.\n if (!password.trim()) throw new Error('No password given. Nothing was sent.');\n const { needsCode } = await client.login({ companyId, userId, password });\n if (!needsCode) return;\n io.print(\n 'Your company uses two-factor login: a code was sent to your e-mail. Type it below (hidden).',\n );\n for (let attempt = 1; attempt <= 2; attempt++) {\n const code = (await io.askSecret('Code (or a recovery code): ')).trim();\n try {\n await client.verifyCode(code);\n return;\n } catch (err) {\n if (attempt === 2 || !(err instanceof CanvasAdminError) || err.status !== 400) throw err;\n io.printError('That code was not accepted. Try once more.');\n }\n }\n}\n\nfunction describeError(err: unknown): string {\n if (!(err instanceof CanvasAdminError)) return err instanceof Error ? err.message : String(err);\n if (err.status === 400 && /fallback/i.test(err.reason ?? '')) {\n return `${err.message}\\n--fallback-version takes the VERSION column of \\`canvas list\\` (e.g. 0.0.8), not the id.`;\n }\n switch (err.status) {\n case 401:\n return `${err.message}\\nCheck the company id, user id and password.`;\n case 403:\n return `${err.message}\\nYour user cannot do this for that company: publishing needs a manager of the company.`;\n case 409:\n return `${err.message}\\nThis version was already published for the company. Use a new --version.`;\n case 413:\n return `${err.message}\\nThe .zip is over 2 MB.`;\n default:\n return err.message;\n }\n}\n\nfunction table(rows: CanvasBundleRecord[]): string[] {\n if (rows.length === 0) return ['No bundle published for this company.'];\n return [\n 'id version current revoked published_at min_app',\n ...rows.map(\n (r) =>\n `${String(r.id).padEnd(8)}${r.version.padEnd(15)}${(r.is_current ? 'yes' : '').padEnd(9)}${(\n r.revoked ? 'yes' : ''\n ).padEnd(9)}${(r.published_at ?? '').padEnd(28)}${r.min_app_version ?? ''}`,\n ),\n ];\n}\n\nexport interface CanvasDeps {\n userAgent: string;\n fetchImpl?: typeof fetch;\n}\n\n/** Runs one `canvas` command. Returns the process exit code. */\nexport async function runCanvas(args: CanvasArgs, io: CanvasIo, deps: CanvasDeps): Promise<number> {\n const baseUrl = args.baseUrl ?? CANVAS_ADMIN_API[args.env];\n const envLabel = args.baseUrl ? `custom (${baseUrl})` : `${args.env.toUpperCase()} (${baseUrl})`;\n try {\n let zip: { zipBytes: Uint8Array; findings: ValidationFinding[] } | undefined;\n if (args.action === 'publish') {\n zip = loadZip(args.input ?? '');\n const errors = zip.findings.filter((f) => f.severity === 'error');\n for (const f of zip.findings) {\n (f.severity === 'error' ? io.printError : io.print)(\n `${f.severity.toUpperCase()} [${f.rule}] ${f.message}`,\n );\n }\n if (errors.length > 0) {\n io.printError('The app would reject this bundle. Nothing was sent.');\n return 1;\n }\n }\n\n if (!io.interactive) {\n io.printError(\n 'canvas needs an interactive terminal: the password is only read from a prompt.',\n );\n return 1;\n }\n const companyId = await askId(io, 'Company id', args.companyId);\n const userId = await askId(io, 'User id', args.userId);\n\n if (args.action === 'publish' && zip) {\n const hash = `sha256-${createHash('sha256').update(zip.zipBytes).digest('hex')}`;\n io.print('');\n io.print(`Environment: ${envLabel}`);\n io.print(`Company: ${companyId}`);\n io.print(\n `Version: ${args.version}${args.minAppVersion ? ` (min app ${args.minAppVersion})` : ''}`,\n );\n io.print(`Bundle: ${zip.zipBytes.length} bytes, ${hash}`);\n io.print('It goes LIVE AT ONCE for every user of this company.');\n if (!(await confirm(io, args, 'Publish?'))) {\n io.print('Cancelled. Nothing was sent.');\n return 1;\n }\n }\n if (args.action === 'revoke') {\n io.print('');\n io.print(`Environment: ${envLabel}`);\n io.print(`Revoke bundle ${args.bundleId} of company ${companyId}.`);\n io.print(\n args.fallbackVersion\n ? `The app switches to version ${args.fallbackVersion} (a version, not an id).`\n : 'Without --fallback-version, revoking the current bundle leaves the company with none: the app shows its default screen.',\n );\n if (!(await confirm(io, args, 'Revoke?'))) {\n io.print('Cancelled. Nothing was sent.');\n return 1;\n }\n }\n\n const client = new CanvasAdminClient({\n baseUrl,\n userAgent: deps.userAgent,\n fetchImpl: deps.fetchImpl,\n });\n await logIn(io, client, companyId, userId);\n\n if (args.action === 'publish' && zip) {\n const record = await client.publish({\n zipBytes: zip.zipBytes,\n version: args.version ?? '',\n companyId,\n minAppVersion: args.minAppVersion,\n });\n if (args.json) io.print(JSON.stringify(record));\n else {\n io.print(\n `Published version ${record.version} (id ${record.id}) — current: ${record.is_current}.`,\n );\n io.print(`bundleHash: ${record.bundle_hash}`);\n io.print('Apps of this company load it the next time they come to the foreground.');\n }\n return 0;\n }\n if (args.action === 'list') {\n const rows = await client.list(companyId);\n if (args.json) io.print(JSON.stringify(rows));\n else for (const line of table(rows)) io.print(line);\n return 0;\n }\n const result = await client.revoke(args.bundleId ?? 0, args.fallbackVersion);\n io.print(args.json ? JSON.stringify(result) : `Revoked. ${JSON.stringify(result)}`);\n return 0;\n } catch (err) {\n io.printError(describeError(err));\n return 1;\n }\n}\n","/**\n * cli-core.ts — the pure, testable core of the `condor-mobile-bridge` CLI.\n *\n * `build` takes a client's `dist/index.html`, inlines JavaScript into\n * `<script>`, CSS into `<style>` (resolving `url()` into `data:` URIs),\n * converts local media into `data:` URIs, and packs the result into a `.zip`\n * holding a single `index.html` at the root. It does not touch the output\n * filesystem — `cli.ts` writes the `.zip`.\n *\n * `validate` checks a bundle against the gates the app applies on download,\n * plus a lint for the APIs the sandbox and the CSP block at runtime.\n *\n * Why self-contained: the app renders through `iframe.srcdoc` with a `null`\n * origin and no base URL, which can only resolve inline content and `data:`\n * URIs.\n *\n * **Known limitation.** The post-build inliner assumes a single-chunk `dist`:\n * one JS file, one CSS file, assets. A build with code splitting, or ES module\n * imports between chunks, breaks under `srcdoc` — the client must build\n * single-file (Vite: `vite-plugin-singlefile`; CRA: no splitting).\n */\nimport { createHash } from 'node:crypto';\nimport { readFileSync } from 'node:fs';\nimport { dirname, extname, resolve } from 'node:path';\nimport { strToU8, unzipSync, zipSync } from 'fflate';\nimport { type HTMLElement, parse } from 'node-html-parser';\n\n/** Maximum `.zip` size. Mirrors `WhitelabelBundleLimits.maxZipBytes` in the app. */\nexport const DEFAULT_MAX_BYTES = 2 * 1024 * 1024;\n\n/** Fixed mtime (>= 1980, the ZIP format floor) so the SHA-256 is reproducible. */\nconst FIXED_MTIME = new Date('2000-01-01T00:00:00Z');\n\n/** Extension → MIME map, for building `data:` URIs of embedded media. */\nexport const MIME_TYPES: Record<string, string> = {\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.gif': 'image/gif',\n '.webp': 'image/webp',\n '.avif': 'image/avif',\n '.svg': 'image/svg+xml',\n '.ico': 'image/x-icon',\n '.bmp': 'image/bmp',\n '.woff': 'font/woff',\n '.woff2': 'font/woff2',\n '.ttf': 'font/ttf',\n '.otf': 'font/otf',\n '.eot': 'application/vnd.ms-fontobject',\n '.mp3': 'audio/mpeg',\n '.mp4': 'video/mp4',\n '.webm': 'video/webm',\n '.json': 'application/json',\n};\n\n/** Refs that must NOT be inlined: external absolute, protocol-relative, already-inline `data:`. */\nfunction isExternalOrInline(url: string): boolean {\n return /^(https?:)?\\/\\//i.test(url) || /^data:/i.test(url) || /^(mailto:|tel:|#)/i.test(url);\n}\n\n/** Strips query and hash from a local ref (`main.js?v=abc#x` → `main.js`). */\nfunction stripQueryHash(url: string): string {\n return url.split(/[?#]/)[0] ?? url;\n}\n\n/**\n * Resolves a local ref to an absolute filesystem path.\n *\n * `htmlRoot` is the site root (the directory of `index.html`); `resourceDir` is\n * the directory of the resource holding the ref — for `url()` inside CSS, that\n * is the directory of the `.css`. A ref starting with `/` is relative to the\n * site root.\n */\nfunction resolveLocal(htmlRoot: string, resourceDir: string, url: string): string {\n const clean = stripQueryHash(url);\n return clean.startsWith('/') ? resolve(htmlRoot, `.${clean}`) : resolve(resourceDir, clean);\n}\n\n/** Reads a local file and returns a base64 `data:` URI. */\nexport function toDataUri(filePath: string): string {\n const bytes = readFileSync(filePath);\n const mime = MIME_TYPES[extname(filePath).toLowerCase()] ?? 'application/octet-stream';\n return `data:${mime};base64,${bytes.toString('base64')}`;\n}\n\n/**\n * Resolves `url(...)` inside a CSS block, replacing local refs with `data:`\n * URIs. `cssDir` is the directory of the CSS file, or of the HTML for an inline\n * `<style>`.\n */\nexport function inlineCssUrls(css: string, htmlRoot: string, cssDir: string): string {\n return css.replace(/url\\(\\s*(['\"]?)([^'\")]+)\\1\\s*\\)/gi, (match, _quote, rawUrl: string) => {\n if (isExternalOrInline(rawUrl)) return match;\n const filePath = resolveLocal(htmlRoot, cssDir, rawUrl);\n return `url(${toDataUri(filePath)})`;\n });\n}\n\n/** Inlines `<script src=\"local\">` into `<script>…</script>`, escaping `</script>`. */\nfunction inlineScripts(root: HTMLElement, htmlRoot: string): void {\n for (const el of root.querySelectorAll('script[src]')) {\n const src = el.getAttribute('src');\n if (!src || isExternalOrInline(src)) continue;\n const js = readFileSync(resolveLocal(htmlRoot, htmlRoot, src), 'utf-8').replace(\n /<\\/script>/gi,\n '<\\\\/script>',\n );\n el.removeAttribute('src');\n // `textContent`, never `set_content`: the latter PARSES what it receives\n // as HTML. Every bundle carries markup inside JS strings, and the parser\n // rewrites it — `<stop … />` becomes `<stop …></stop>`, a comparison like\n // `a<T` is read as an opening tag, and the matching close is emitted at the\n // end of the script. The JS comes out syntactically broken, the zip is\n // produced without error, and the app aborts the Home with \"native bridges\n // reachable from the iframe\" — because the client never got far enough to\n // answer the verify-sandbox probe.\n el.textContent = js;\n }\n}\n\n/** Inlines `<link rel=\"stylesheet\" href=\"local\">` into `<style>…</style>`, resolving `url()`. */\nfunction inlineStylesheets(root: HTMLElement, htmlRoot: string): void {\n for (const el of root.querySelectorAll('link[rel=\"stylesheet\"]')) {\n const href = el.getAttribute('href');\n if (!href || isExternalOrInline(href)) continue;\n const cssPath = resolveLocal(htmlRoot, htmlRoot, href);\n const css = inlineCssUrls(readFileSync(cssPath, 'utf-8'), htmlRoot, dirname(cssPath));\n el.replaceWith(...parse(`<style>${css}</style>`).childNodes);\n }\n}\n\n/** Resolves `url()` in `<style>` blocks already present in the HTML. */\nfunction inlineExistingStyleBlocks(root: HTMLElement, htmlRoot: string): void {\n for (const el of root.querySelectorAll('style')) {\n const css = el.innerHTML;\n if (!css.includes('url(')) continue;\n el.set_content(inlineCssUrls(css, htmlRoot, htmlRoot));\n }\n}\n\n/** Converts local media referenced by attribute (`src`/`href`) into `data:` URIs. */\nfunction inlineMediaAttributes(root: HTMLElement, htmlRoot: string): void {\n const targets: Array<[selector: string, attr: string]> = [\n ['img[src]', 'src'],\n ['source[src]', 'src'],\n ['audio[src]', 'src'],\n ['video[src]', 'src'],\n ['link[rel=\"icon\"]', 'href'],\n ['link[rel=\"shortcut icon\"]', 'href'],\n ['link[rel=\"apple-touch-icon\"]', 'href'],\n ['link[rel=\"preload\"]', 'href'],\n ];\n for (const [selector, attr] of targets) {\n for (const el of root.querySelectorAll(selector)) {\n const ref = el.getAttribute(attr);\n if (!ref || isExternalOrInline(ref)) continue;\n el.setAttribute(attr, toDataUri(resolveLocal(htmlRoot, htmlRoot, ref)));\n }\n }\n}\n\n/**\n * Reads the input HTML and returns the consolidated, self-contained document:\n * JS and CSS inline, media as `data:` URIs. Writes nothing to disk.\n */\nexport function inlineHtml(htmlPath: string): string {\n const htmlRoot = dirname(resolve(htmlPath));\n const root = parse(readFileSync(htmlPath, 'utf-8'), {\n comment: true,\n voidTag: { closingSlash: true },\n });\n inlineScripts(root, htmlRoot);\n inlineStylesheets(root, htmlRoot);\n inlineExistingStyleBlocks(root, htmlRoot);\n inlineMediaAttributes(root, htmlRoot);\n return root.toString();\n}\n\nexport interface BundleResult {\n /** The consolidated, self-contained HTML. */\n html: string;\n /** Bytes of the `.zip`, whose single entry is `index.html` at the root. */\n zipBytes: Uint8Array;\n /** Size of the `.zip` in bytes. */\n size: number;\n /** SHA-256 of the `.zip` bytes, as `sha256-<hex>`. Matches what the app computes. */\n hash: string;\n /** `true` when `size` exceeds `maxBytes`. */\n overLimit: boolean;\n}\n\n/**\n * Builds the bundle from the input `index.html`: inlines, zips it with\n * `index.html` at the root and a fixed mtime for a deterministic hash, and\n * computes size and SHA-256. Pure — writing the `.zip` is the caller's job.\n */\nexport function buildBundle(htmlPath: string, maxBytes: number = DEFAULT_MAX_BYTES): BundleResult {\n const html = inlineHtml(htmlPath);\n const zipBytes = zipSync({ 'index.html': [strToU8(html), { mtime: FIXED_MTIME }] }, { level: 6 });\n const size = zipBytes.length;\n const hash = `sha256-${createHash('sha256').update(zipBytes).digest('hex')}`;\n return { html, zipBytes, size, hash, overLimit: size > maxBytes };\n}\n\n// ---------------------------------------------------------------------------\n// validate — conformance checks for a client bundle\n// ---------------------------------------------------------------------------\n\n/**\n * Severity of a validation finding.\n *\n * `error` mirrors a guard the Canvas app itself applies when it downloads the\n * bundle: the app REJECTS the artifact and keeps the previous cache or falls\n * back to the default screen. `warning` marks something the app accepts but\n * the runtime blocks — the CSP or the `sandbox` attribute makes the call fail\n * on the device, where it is much harder to diagnose.\n */\nexport type ValidationSeverity = 'error' | 'warning';\n\n/** A single conformance finding. `rule` is a stable machine-readable id. */\nexport interface ValidationFinding {\n severity: ValidationSeverity;\n rule: string;\n message: string;\n}\n\n/** Outcome of validating a bundle (`.zip`) or a pre-build `index.html`. */\nexport interface ValidationResult {\n /** `false` when at least one `error` finding was produced. */\n ok: boolean;\n /** Entry document the app would render, or `null` when none was found. */\n entry: string | null;\n /** Size of the `.zip` in bytes; `null` when validating a bare HTML file. */\n size: number | null;\n /** `sha256-<hex>` of the `.zip`; `null` when validating a bare HTML file. */\n hash: string | null;\n findings: ValidationFinding[];\n}\n\n/**\n * APIs that are unreachable inside the Canvas client iframe.\n *\n * The client runs in `<iframe sandbox=\"allow-scripts\">` rendered from\n * `srcdoc`, so its origin is `null`, and it inherits the shell CSP\n * (`script-src 'self' 'unsafe-inline'`, `connect-src 'self'`, no\n * `'unsafe-eval'`). Every entry here fails silently or throws at runtime on a\n * real device — the bundle still installs, which is exactly what makes these\n * expensive to find later.\n *\n * Detection is textual and therefore best-effort: minified code that renames\n * `window.open` to `t.open` is invisible to it. Absence of findings is not a\n * proof of conformance.\n */\nexport const BLOCKED_API_RULES: ReadonlyArray<{\n rule: string;\n pattern: RegExp;\n message: string;\n}> = [\n {\n rule: 'web-storage',\n pattern: /\\b(?:localStorage|sessionStorage)\\b/,\n message:\n 'Web Storage throws SecurityError: the client iframe has a null origin. Keep state in memory — it is lost when the WebView reloads.',\n },\n {\n rule: 'indexed-db',\n pattern: /\\bindexedDB\\b/,\n message: 'IndexedDB is unavailable on a null origin. Keep state in memory.',\n },\n {\n rule: 'cookies',\n pattern: /document\\s*\\.\\s*cookie/,\n message: 'Cookies cannot be written from a null origin; the assignment is a no-op.',\n },\n {\n rule: 'dynamic-eval',\n pattern: /\\beval\\s*\\(|new\\s+Function\\s*\\(/,\n message:\n \"The shell CSP has no 'unsafe-eval'; eval() and new Function() are blocked. Build without eval-based sourcemaps.\",\n },\n {\n rule: 'service-worker',\n pattern: /serviceWorker/,\n message: 'Service workers cannot be registered from a null origin.',\n },\n {\n rule: 'send-beacon',\n pattern: /sendBeacon/,\n message:\n 'navigator.sendBeacon is neutralized by the shell before your bundle runs (pen test PT-10). The call returns without sending.',\n },\n {\n rule: 'popups',\n pattern: /window\\s*\\.\\s*open\\s*\\(/,\n message: 'The sandbox has no allow-popups; window.open() returns null.',\n },\n {\n rule: 'top-navigation',\n pattern: /window\\s*\\.\\s*top\\b/,\n message:\n 'The sandbox has no allow-top-navigation; reading or navigating window.top throws SecurityError.',\n },\n {\n rule: 'external-network',\n pattern: /\\bfetch\\s*\\(|\\bXMLHttpRequest\\b|\\bWebSocket\\s*\\(/,\n message:\n \"connect-src 'self' on a null origin blocks every network call. Request data through the bridge instead (e.g. BridgeClient.fetchSensorLog).\",\n },\n];\n\n/**\n * Marker the dev harness (`@condorinst/mobile-bridge/dev`) leaves in any code\n * that bundles it. Kept in sync with `DEV_HARNESS_MARKER` in `src/dev/harness.ts`.\n */\nexport const DEV_HARNESS_MARKER = '__condorDevHarness__';\n\n/** Runs the blocked-API lint over a client document. */\nexport function lintClientHtml(html: string): ValidationFinding[] {\n const findings: ValidationFinding[] = [];\n if (html.includes(DEV_HARNESS_MARKER)) {\n findings.push({\n severity: 'error',\n rule: 'dev-harness-bundled',\n message:\n 'The bundle contains the development harness (@condorinst/mobile-bridge/dev). It belongs in the page that hosts the bundle during development, never in the bundle: remove the import.',\n });\n }\n for (const { rule, pattern, message } of BLOCKED_API_RULES) {\n if (pattern.test(html)) findings.push({ severity: 'warning', rule, message });\n }\n return findings;\n}\n\n/**\n * Checks that a document carries no reference the `srcdoc` render cannot\n * resolve. Mirrors `_isSelfContained` in the app's\n * `whitelabel_bundle_service.dart`: a `<script src>` or a stylesheet `<link>`\n * pointing anywhere but a `data:` URI makes the app reject the bundle.\n * Relative images are NOT rejected by the app (cosmetic degradation only), so\n * they surface here as warnings.\n */\nexport function checkSelfContained(html: string): ValidationFinding[] {\n const findings: ValidationFinding[] = [];\n const root = parse(html, { comment: true, voidTag: { closingSlash: true } });\n\n for (const el of root.querySelectorAll('script[src]')) {\n const src = el.getAttribute('src') ?? '';\n if (/^data:/i.test(src)) continue;\n findings.push({\n severity: 'error',\n rule: 'not-self-contained',\n message: `<script src=\"${src}\"> — the app rejects bundles with external scripts. Inline it (npx condor-mobile-bridge build does this for you).`,\n });\n }\n\n for (const el of root.querySelectorAll('link[rel=\"stylesheet\"]')) {\n const href = el.getAttribute('href') ?? '';\n if (/^data:/i.test(href)) continue;\n findings.push({\n severity: 'error',\n rule: 'not-self-contained',\n message: `<link rel=\"stylesheet\" href=\"${href}\"> — the app rejects bundles with external stylesheets. Inline it as <style>.`,\n });\n }\n\n for (const el of root.querySelectorAll('img[src]')) {\n const src = el.getAttribute('src') ?? '';\n if (/^data:/i.test(src) || isExternalOrInline(src)) continue;\n findings.push({\n severity: 'warning',\n rule: 'relative-asset',\n message: `<img src=\"${src}\"> is relative and will not resolve inside srcdoc. Embed it as a data: URI.`,\n });\n }\n\n return findings;\n}\n\n/** Sorts findings so every error is reported before any warning. */\nfunction errorsFirst(findings: ValidationFinding[]): ValidationFinding[] {\n return [...findings].sort((a, b) =>\n a.severity === b.severity ? 0 : a.severity === 'error' ? -1 : 1,\n );\n}\n\n/**\n * Validates a built `.zip` — the artifact actually delivered to Condor.\n * Applies the same three hard gates the app applies on download: a single\n * `index.html` at the root, self-contained, and within the size limit.\n */\nexport function validateZipBytes(\n zipBytes: Uint8Array,\n maxBytes: number = DEFAULT_MAX_BYTES,\n): ValidationResult {\n const findings: ValidationFinding[] = [];\n const size = zipBytes.length;\n const hash = `sha256-${createHash('sha256').update(zipBytes).digest('hex')}`;\n\n if (size > maxBytes) {\n findings.push({\n severity: 'error',\n rule: 'too-large',\n message: `The .zip is ${size} bytes, over the ${maxBytes}-byte limit. The app rejects it and falls back to the default screen.`,\n });\n }\n\n let entries: Record<string, Uint8Array>;\n try {\n entries = unzipSync(zipBytes);\n } catch (err) {\n findings.push({\n severity: 'error',\n rule: 'unreadable-zip',\n message: `Could not read the .zip: ${err instanceof Error ? err.message : String(err)}`,\n });\n return { ok: false, entry: null, size, hash, findings: errorsFirst(findings) };\n }\n\n const names = Object.keys(entries).filter((n) => !n.endsWith('/'));\n let entry: string | null = null;\n\n if (names.includes('index.html')) {\n entry = 'index.html';\n } else {\n const firstHtml = names.find((n) => n.toLowerCase().endsWith('.html'));\n if (firstHtml) {\n entry = firstHtml;\n findings.push({\n severity: 'warning',\n rule: 'entry-fallback',\n message: `No index.html at the root; the app would fall back to \"${firstHtml}\". Name the entry index.html and put it at the root.`,\n });\n } else {\n findings.push({\n severity: 'error',\n rule: 'entry-missing',\n message: 'The .zip carries no .html document. The app cannot render this bundle.',\n });\n }\n }\n\n if (entry) {\n const html = Buffer.from(entries[entry] as Uint8Array).toString('utf-8');\n findings.push(...checkSelfContained(html), ...lintClientHtml(html));\n }\n\n const ok = !findings.some((f) => f.severity === 'error');\n return { ok, entry, size, hash, findings: errorsFirst(findings) };\n}\n\n/**\n * Validates an input path. A `.zip` is checked as the delivered artifact; any\n * other path is treated as a pre-build `index.html` and gets the document\n * checks only — there is no size or hash to report yet.\n */\nexport function validateBundle(\n inputPath: string,\n maxBytes: number = DEFAULT_MAX_BYTES,\n): ValidationResult {\n if (extname(inputPath).toLowerCase() === '.zip') {\n return validateZipBytes(readFileSync(inputPath), maxBytes);\n }\n const html = readFileSync(inputPath, 'utf-8');\n const findings = [...checkSelfContained(html), ...lintClientHtml(html)];\n return {\n ok: !findings.some((f) => f.severity === 'error'),\n entry: inputPath,\n size: null,\n hash: null,\n findings: errorsFirst(findings),\n };\n}\n\nexport type CliCommand = 'build' | 'validate' | 'dev';\n\nexport interface CliArgs {\n /** Subcommand. Defaults to `build` so pre-0.19 invocations keep working. */\n command: CliCommand;\n htmlPath: string;\n outPath: string;\n maxBytes: number;\n /** `validate` only: treat warnings as failures (exit code 1). */\n strict: boolean;\n /** `dev` only: harness scenario. */\n scenario: string;\n /** `dev` only: HTTP port. */\n port: number;\n /** `dev` only: overrides the scenario's nativeVersion (`'null'` for null). */\n nativeVersion: string | undefined;\n help: boolean;\n}\n\nconst DEFAULT_OUT = 'dist/whitelabel-bundle.zip';\nexport const DEFAULT_DEV_PORT = 5173;\n\n/**\n * Minimal argv parse (no deps): `--html/-i`, `--out/-o`, `--max-bytes`,\n * `--strict`, `--scenario`, `--port`, `--help/-h`, plus the `build`,\n * `validate` and `dev` subcommands.\n */\nexport function parseArgs(argv: string[]): CliArgs {\n const args: CliArgs = {\n command: 'build',\n htmlPath: '',\n outPath: DEFAULT_OUT,\n maxBytes: DEFAULT_MAX_BYTES,\n strict: false,\n scenario: 'default',\n port: DEFAULT_DEV_PORT,\n nativeVersion: undefined,\n help: false,\n };\n for (let i = 0; i < argv.length; i++) {\n const token = argv[i];\n switch (token) {\n case '--help':\n case '-h':\n args.help = true;\n break;\n case '--html':\n case '-i':\n case '--input':\n args.htmlPath = argv[++i] ?? '';\n break;\n case '--out':\n case '-o':\n args.outPath = argv[++i] ?? DEFAULT_OUT;\n break;\n case '--max-bytes':\n args.maxBytes = Number.parseInt(argv[++i] ?? '', 10) || DEFAULT_MAX_BYTES;\n break;\n case '--strict':\n args.strict = true;\n break;\n case 'build':\n args.command = 'build';\n break;\n case 'validate':\n args.command = 'validate';\n break;\n case 'dev':\n args.command = 'dev';\n break;\n case '--scenario':\n args.scenario = argv[++i] ?? 'default';\n break;\n case '--native-version':\n args.nativeVersion = argv[++i] ?? 'null';\n break;\n case '--port':\n args.port = Number.parseInt(argv[++i] ?? '', 10) || DEFAULT_DEV_PORT;\n break;\n default:\n // First positional becomes the input (ergonomics: `build dist/index.html`).\n if (token && !token.startsWith('-') && !args.htmlPath) args.htmlPath = token;\n }\n }\n return args;\n}\n\nexport const USAGE = `condor-mobile-bridge — tooling for Condor Canvas client bundles.\n\nUsage:\n npx condor-mobile-bridge build -i <html> [-o <zip>]\n npx condor-mobile-bridge validate -i <zip|html> [--strict]\n npx condor-mobile-bridge canvas publish|list|revoke ... (see: canvas --help)\n npx condor-mobile-bridge dev -i <html|zip|dir> [--scenario <name>] [--native-version <v|null>] [--port <n>]\n\nCommands:\n build Inline a dist/index.html into a self-contained .zip.\n validate Check a bundle against every rule the Canvas app enforces, plus\n the APIs the sandbox and the CSP block at runtime.\n canvas Publish, list and revoke the bundle of your company in the\n Condor Canvas app (interactive login). See: canvas --help.\n dev Serve the bundle in a browser harness that behaves like the app:\n same sandbox, shell and policy, and a simulated native side.\n Re-reads the input on every reload. Scenarios: default,\n cold-start, no-devices, shared-serial, offline.\n\nOptions:\n -i, --html <path> Input. build: dist/index.html. validate: .zip or .html. Required.\n -o, --out <path> build only: output .zip (default: ${DEFAULT_OUT}).\n --max-bytes <n> Size limit in bytes (default: ${DEFAULT_MAX_BYTES} = 2 MB).\n --strict validate only: exit 1 on warnings too, not just errors.\n --scenario <n> dev only: device behaviour to simulate (default: default).\n --port <n> dev only: HTTP port (default: ${DEFAULT_DEV_PORT}).\n --native-version <v|null>\n dev only: nativeVersion the harness reports (default\n 0.17.0). 'null' reproduces a shell that could not\n reach the native side.\n -h, --help Show this help.`;\n","/**\n * Client for the Condor admin API that publishes Canvas bundles — the same\n * endpoints the Condor web admin calls, authenticated the same way (login +\n * optional two-factor code, session in an HTTP-only cookie).\n *\n * The session lives only in this object: it is never written to disk, and each\n * CLI command logs in again. Credentials are passed in by the caller and never\n * logged.\n */\n\n/** API base URLs, as the Condor web apps use them. */\nexport const CANVAS_ADMIN_API = {\n prod: 'https://api.sleepdiary.condorapps.net',\n dev: 'https://dev.sleepdiary.condorapps.net',\n} as const;\n\nexport type CanvasAdminEnv = keyof typeof CANVAS_ADMIN_API;\n\n/** A published bundle, as the API describes it. */\nexport interface CanvasBundleRecord {\n id: number;\n company_id: number;\n company_name?: string;\n version: string;\n bundle_hash: string;\n bundle_size: number | null;\n min_app_version: string | null;\n published_at?: string;\n is_current: boolean;\n revoked: boolean;\n fallback_version?: string | null;\n object_key?: string;\n}\n\n/** An HTTP failure with the server's own reason, when it gave one. */\nexport class CanvasAdminError extends Error {\n constructor(\n message: string,\n readonly status: number,\n readonly reason: string | null,\n ) {\n super(message);\n this.name = 'CanvasAdminError';\n }\n}\n\ntype FetchLike = typeof fetch;\n\nexport interface CanvasAdminClientOptions {\n baseUrl: string;\n userAgent: string;\n fetchImpl?: FetchLike;\n timeoutMs?: number;\n}\n\n/** Extracts `token=<value>` from the response's Set-Cookie headers. */\nfunction tokenFrom(headers: Headers): string | null {\n const values =\n typeof headers.getSetCookie === 'function'\n ? headers.getSetCookie()\n : [headers.get('set-cookie') ?? ''];\n for (const value of values) {\n const match = /(?:^|[,;\\s])token=([^;,\\s]*)/.exec(value);\n if (match?.[1]) return match[1];\n }\n return null;\n}\n\n/**\n * The server answers errors in two shapes: `{status: \"NOK\", error}` from the\n * bundle controllers, and `{message}` / `{description}` from the global\n * authentication handler.\n */\nasync function reasonFrom(response: Response): Promise<string | null> {\n try {\n const body = (await response.json()) as Record<string, unknown>;\n for (const key of ['error', 'message', 'description', 'code']) {\n const value = body[key];\n if (typeof value === 'string' && value) return value;\n }\n } catch {\n // Not JSON: no reason to report.\n }\n return null;\n}\n\nexport class CanvasAdminClient {\n private readonly baseUrl: string;\n private readonly userAgent: string;\n private readonly fetchImpl: FetchLike;\n private readonly timeoutMs: number;\n private token: string | null = null;\n\n constructor(opts: CanvasAdminClientOptions) {\n this.baseUrl = opts.baseUrl.replace(/\\/+$/, '');\n this.userAgent = opts.userAgent;\n this.fetchImpl = opts.fetchImpl ?? fetch;\n this.timeoutMs = opts.timeoutMs ?? 60_000;\n }\n\n private async request(path: string, init: RequestInit = {}): Promise<Response> {\n const headers = new Headers(init.headers);\n headers.set('user-agent', this.userAgent);\n headers.set('accept', 'application/json');\n if (this.token) headers.set('cookie', `token=${this.token}`);\n try {\n return await this.fetchImpl(`${this.baseUrl}${path}`, {\n ...init,\n headers,\n redirect: 'manual',\n signal: AbortSignal.timeout(this.timeoutMs),\n });\n } catch (err) {\n throw new CanvasAdminError(\n `Could not reach ${this.baseUrl}: ${err instanceof Error ? err.message : String(err)}`,\n 0,\n null,\n );\n }\n }\n\n private async fail(response: Response, what: string): Promise<never> {\n const reason = await reasonFrom(response);\n throw new CanvasAdminError(\n `${what} failed (HTTP ${response.status})${reason ? `: ${reason}` : ''}`,\n response.status,\n reason,\n );\n }\n\n /**\n * Logs in. Resolves `{ needsCode: true }` when the company requires a\n * two-factor code (sent by e-mail): call {@link verifyCode} next.\n */\n async login(creds: {\n companyId: number;\n userId: number;\n password: string;\n }): Promise<{ needsCode: boolean }> {\n const response = await this.request('/auth/login', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n company_id: creds.companyId,\n user_id: creds.userId,\n password: creds.password,\n }),\n });\n if (!response.ok) await this.fail(response, 'Login');\n const token = tokenFrom(response.headers);\n if (!token) throw new CanvasAdminError('Login answered without a session cookie', 200, null);\n this.token = token;\n const body = (await response.json().catch(() => ({}))) as { completed_auth_flow?: boolean };\n return { needsCode: body.completed_auth_flow === false };\n }\n\n /** Sends the two-factor (or recovery) code; the server swaps the session cookie. */\n async verifyCode(code: string): Promise<void> {\n const response = await this.request('/auth/verify_2fa', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ otp_code: code }),\n });\n if (!response.ok) await this.fail(response, 'Two-factor verification');\n const token = tokenFrom(response.headers);\n if (token) this.token = token;\n }\n\n /**\n * Publishes a bundle. **It goes live at once** for every user of the\n * company: there is no draft. A version can be published only once per\n * company (HTTP 409).\n */\n async publish(input: {\n zipBytes: Uint8Array;\n version: string;\n companyId?: number;\n minAppVersion?: string;\n }): Promise<CanvasBundleRecord> {\n const form = new FormData();\n form.set(\n 'bundle',\n new Blob([input.zipBytes as BlobPart], { type: 'application/zip' }),\n 'bundle.zip',\n );\n form.set('version', input.version);\n if (input.companyId !== undefined) form.set('company_id', String(input.companyId));\n if (input.minAppVersion) form.set('min_app_version', input.minAppVersion);\n const response = await this.request('/v2/condor_canvas/bundles', {\n method: 'POST',\n body: form,\n });\n if (!response.ok) await this.fail(response, 'Publish');\n const body = (await response.json()) as { data: CanvasBundleRecord };\n return body.data;\n }\n\n /** Lists the bundles of a company (a manager always sees their own). */\n async list(companyId?: number): Promise<CanvasBundleRecord[]> {\n const query = companyId === undefined ? '' : `?company_id=${companyId}`;\n const response = await this.request(`/v2/condor_canvas/bundles${query}`);\n if (!response.ok) await this.fail(response, 'List');\n const body = (await response.json()) as { data: CanvasBundleRecord[] };\n return body.data;\n }\n\n /**\n * Revokes a bundle. Without `fallbackVersion`, revoking the current bundle\n * leaves the company with none: the app shows its default screen.\n */\n async revoke(bundleId: number, fallbackVersion?: string): Promise<Record<string, unknown>> {\n const response = await this.request(`/v2/condor_canvas/bundles/${bundleId}/revoke`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(fallbackVersion ? { fallbackVersion } : {}),\n });\n if (!response.ok) await this.fail(response, 'Revoke');\n const body = (await response.json()) as { data: Record<string, unknown> };\n return body.data;\n }\n}\n","/**\n * Terminal implementation of {@link CanvasIo}: questions on stderr, answers\n * from stdin, secrets without echo.\n *\n * One readline interface serves the whole command. Opening a new one per\n * question lost input in the field: after a `y` + Enter at the confirmation,\n * the next interface (the password prompt) received an empty line at once, and\n * the server answered the empty password with HTTP 400 \"Invalid data\".\n */\nimport { createInterface, type Interface } from 'node:readline';\nimport type { CanvasIo } from './command';\n\nlet rl: Interface | null = null;\n/** While set, only this exact prompt text is echoed — never what is typed. */\nlet mutedPrompt: string | null = null;\n\nfunction reader(): Interface {\n if (rl) return rl;\n rl = createInterface({ input: process.stdin, output: process.stderr, terminal: true });\n const out = rl as unknown as { _writeToOutput: (s: string) => void };\n const write = out._writeToOutput.bind(rl);\n // While a secret is being typed, let the prompt through and echo nothing else.\n out._writeToOutput = (s: string) => {\n if (mutedPrompt === null || s.includes(mutedPrompt)) write(s);\n };\n return rl;\n}\n\nfunction question(prompt: string, secret: boolean): Promise<string> {\n const r = reader();\n return new Promise((resolve) => {\n mutedPrompt = secret ? prompt : null;\n r.question(prompt, (answer) => {\n if (secret) {\n mutedPrompt = null;\n process.stderr.write('\\n');\n }\n resolve(answer);\n });\n });\n}\n\nexport function terminalIo(): CanvasIo {\n return {\n interactive: Boolean(process.stdin.isTTY),\n ask: (q) => question(q, false),\n askSecret: (q) => question(q, true),\n print: (line) => console.log(line),\n printError: (line) => console.error(line),\n };\n}\n\n/** Releases stdin so the process can exit. */\nexport function closeTerminalIo(): void {\n rl?.close();\n rl = null;\n}\n","/**\n * cli-dev.ts — the `dev` subcommand: a local HTTP server hosting the bundle in\n * the development harness (`src/dev/harness.ts`).\n *\n * The page it serves is the **shell** frame, with the app's CSP; the bundle\n * runs inside it in `<iframe sandbox=\"allow-scripts\">`, so the network, storage\n * and API restrictions of a device apply in the browser too. The input is\n * re-read on every request: rebuild, reload, done.\n */\nimport { existsSync, readFileSync, statSync } from 'node:fs';\nimport { createServer, type Server } from 'node:http';\nimport { dirname, extname, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { strFromU8, unzipSync } from 'fflate';\nimport { inlineHtml } from './cli-core';\nimport { CANVAS_CSP } from './dev/policy';\nimport { SCENARIOS } from './dev/scenarios';\n\n/**\n * Reads the bundle's `index.html` from a file, a `dist/` directory or a `.zip`.\n * An HTML file is inlined exactly as `build` does (scripts, styles, media), so\n * a `dist/` with `index.html` + `main.js` runs as it will in the `.zip`.\n */\nexport function readClientHtml(input: string): string {\n const path = resolve(input);\n if (existsSync(path) && statSync(path).isDirectory()) {\n return inlineHtml(join(path, 'index.html'));\n }\n if (extname(path).toLowerCase() === '.zip') {\n const files = unzipSync(new Uint8Array(readFileSync(path)));\n const entry = files['index.html'];\n if (!entry) throw new Error(`${input}: no index.html at the root of the zip.`);\n return strFromU8(entry);\n }\n return inlineHtml(path);\n}\n\nconst escapeHtml = (s: string): string =>\n s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;');\n\n/** The harness page. Exported for tests. */\n/**\n * The harness page. `nativeVersion` overrides the scenario's (`'null'` → null).\n * Exported for tests.\n */\nexport function harnessPage(scenario: string, nativeVersion?: string): string {\n const options = Object.entries(SCENARIOS)\n .map(\n ([name, s]) =>\n `<option value=\"${name}\" title=\"${escapeHtml(s.description)}\"${\n name === scenario ? ' selected' : ''\n }>${name}</option>`,\n )\n .join('');\n const description = escapeHtml(SCENARIOS[scenario as keyof typeof SCENARIOS]?.description ?? '');\n // JSON in an inline script: escape every `<` so no `</script>` or `<!--` can close it.\n const scenarioJson = JSON.stringify(scenario).replace(/</g, '\\\\u003c');\n const nativeJson =\n JSON.stringify(\n nativeVersion === undefined ? undefined : nativeVersion === 'null' ? null : nativeVersion,\n )?.replace(/</g, '\\\\u003c') ?? 'undefined';\n const nativeLabel = escapeHtml(\n nativeVersion === undefined ? '' : `nativeVersion ${nativeVersion}`,\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<meta http-equiv=\"Content-Security-Policy\" content=\"${CANVAS_CSP}\">\n<title>Condor Canvas — dev harness</title>\n<style>\n body { margin: 0; font: 13px system-ui, sans-serif; background: #e9ebef; color: #1b1f24; }\n header { display: flex; gap: 12px; align-items: center; padding: 8px 16px; background: #1b1f24; color: #fff; flex-wrap: wrap; }\n header p { margin: 0; opacity: .8; flex: 1 1 320px; }\n main { display: flex; justify-content: center; padding: 16px; }\n #device { width: 390px; height: 780px; max-width: 100%; border-radius: 28px; overflow: hidden; box-shadow: 0 10px 40px rgba(0,0,0,.25); background: #fff; }\n</style>\n</head>\n<body>\n<header>\n <strong>Condor Canvas · dev harness</strong>\n <label>Scenario <select id=\"scenario\">${options}</select></label>\n <button id=\"back\" type=\"button\" title=\"The OS back button: reaches the bundle only after it reported canGoBack true\">◀ OS back</button>\n <span>${nativeLabel}</span>\n <p>${description}</p>\n</header>\n<main><div id=\"device\"></div></main>\n<script src=\"/__condor/harness.js\"></script>\n<script>\n var scenario = ${scenarioJson};\n var nativeVersion = ${nativeJson};\n var harness = null;\n document.getElementById('scenario').addEventListener('change', function (e) {\n var params = new URLSearchParams(location.search);\n params.set('scenario', e.target.value);\n location.search = params.toString();\n });\n document.getElementById('back').addEventListener('click', function () {\n if (harness) harness.pressBack();\n });\n var chosen = CondorDevHarness.SCENARIOS[scenario];\n if (nativeVersion !== undefined) chosen = Object.assign({}, chosen, { nativeVersion: nativeVersion });\n fetch('/__condor/client.html')\n .then(function (r) { if (!r.ok) throw new Error('could not read the bundle: HTTP ' + r.status); return r.text(); })\n .then(function (html) {\n return CondorDevHarness.createDevHarness({ html: html, scenario: chosen, mount: document.getElementById('device') });\n })\n .then(function (h) { harness = h; })\n .catch(function (err) { document.getElementById('device').textContent = String(err); console.error(err); });\n</script>\n</body>\n</html>`;\n}\n\n/** Path of the IIFE harness build, next to the compiled CLI in `dist/`. */\nfunction harnessScriptPath(): string {\n const here =\n typeof __dirname !== 'undefined' ? __dirname : dirname(fileURLToPath(import.meta.url));\n return join(here, 'dev-harness.global.js');\n}\n\nexport function startDevServer(opts: {\n input: string;\n scenario: string;\n port: number;\n nativeVersion?: string;\n}): Server {\n if (!(opts.scenario in SCENARIOS)) {\n throw new Error(\n `Unknown scenario \"${opts.scenario}\". Known: ${Object.keys(SCENARIOS).join(', ')}.`,\n );\n }\n // Fail before listening if the input cannot be read.\n readClientHtml(opts.input);\n\n const server = createServer((req, res) => {\n const url = new URL(req.url ?? '/', 'http://localhost');\n try {\n if (url.pathname === '/__condor/client.html') {\n res.writeHead(200, {\n 'content-type': 'text/html; charset=utf-8',\n 'cache-control': 'no-store',\n });\n res.end(readClientHtml(opts.input));\n return;\n }\n if (url.pathname === '/__condor/harness.js') {\n res.writeHead(200, {\n 'content-type': 'text/javascript; charset=utf-8',\n 'cache-control': 'no-store',\n });\n res.end(readFileSync(harnessScriptPath()));\n return;\n }\n if (url.pathname === '/') {\n const scenario = url.searchParams.get('scenario') ?? opts.scenario;\n res.writeHead(200, {\n 'content-type': 'text/html; charset=utf-8',\n 'content-security-policy': CANVAS_CSP,\n 'cache-control': 'no-store',\n });\n const nativeVersion = url.searchParams.get('nativeVersion') ?? opts.nativeVersion;\n res.end(harnessPage(scenario in SCENARIOS ? scenario : opts.scenario, nativeVersion));\n return;\n }\n res.writeHead(404).end();\n } catch (err) {\n res.writeHead(500, { 'content-type': 'text/plain; charset=utf-8' });\n res.end(String(err instanceof Error ? err.message : err));\n }\n });\n server.listen(opts.port, '127.0.0.1');\n return server;\n}\n","/**\n * Catalogue of the JS↔Native bridge keys.\n *\n * Mirrors the JS-facing side of `ForegroundComCoreKeys` in `condor_core`.\n * The strings here are what JavaScript receives through\n * `window.CondorBridge.emit(key, payload)`.\n *\n * Thirteen keys exist. A sandboxed client can subscribe to twelve of them:\n * `dfu` is denied by the shell policy and skipped natively. See\n * `docs/bridge-contract.md` for what each one carries and when it is emitted.\n */\nexport const BRIDGE_KEYS = {\n pingPong: 'pingPong',\n userDevice: 'userDevice',\n deviceLightSensors: 'deviceLightSensors',\n syncTime: 'syncTime',\n realTime: 'realTime',\n deleteMemory: 'deleteMemory',\n deviceNotification: 'deviceNotification',\n deviceConnection: 'deviceConnection',\n sensorLog: 'sensorLog',\n dfu: 'dfu',\n systemStatus: 'systemStatus',\n /**\n * Native → client: the OS back button was pressed and the client had\n * reported internal history via `reportNavigation(true)`. Go back exactly\n * ONE internal page. Emitted only when the native side decided to delegate\n * the back gesture. See `BridgeClient.onBack`.\n */\n navigationBack: 'navigationBack',\n /**\n * Native → client: the app's language state. Carries the raw device locale\n * AND the language explicitly chosen in the app Settings, so the client can\n * apply its own precedence rule — including rendering a language the native\n * chrome does not support. Emitted on attach, after `handshake-ack`, and on\n * every change in Settings. See `LanguagePayload`.\n */\n language: 'language',\n} as const;\n\nexport type BridgeKey = (typeof BRIDGE_KEYS)[keyof typeof BRIDGE_KEYS];\n","/**\n * The shell policy of the Condor Canvas app, reproduced for the development\n * harness so a command the app refuses is refused in the browser too.\n *\n * Mirrors `apps/canvas/web-mock-src/src/shell.ts` (see\n * `contract/bridge-contract.json` → `verifiedAgainst.sources.shellPolicy`).\n * `tests/dev-harness.test.ts` derives the same lists from the contract and\n * fails if they drift.\n */\n\nimport { BRIDGE_KEYS } from '../keys';\nimport type { ShellPolicy } from '../shell-messages';\n\nexport const CANVAS_POLICY: ShellPolicy = {\n readOnlyKeys: [\n BRIDGE_KEYS.userDevice,\n BRIDGE_KEYS.deviceConnection,\n BRIDGE_KEYS.deviceLightSensors,\n BRIDGE_KEYS.deviceNotification,\n BRIDGE_KEYS.pingPong,\n BRIDGE_KEYS.realTime,\n BRIDGE_KEYS.syncTime,\n BRIDGE_KEYS.deleteMemory,\n BRIDGE_KEYS.sensorLog,\n BRIDGE_KEYS.systemStatus,\n BRIDGE_KEYS.navigationBack,\n BRIDGE_KEYS.language,\n ],\n allowedCommands: [\n 'userDevice',\n 'realTime',\n 'openSettings',\n 'deviceConnection',\n 'ping',\n 'jsError',\n 'sensorLog',\n 'setStatusBarColor',\n 'requestPermission',\n 'navigationState',\n 'language',\n ],\n requiresConfirmation: ['syncTime', 'deleteMemory', 'logout'],\n deniedCommands: ['dfu'],\n};\n\n/** CSP of the app shell, inherited by the client's `srcdoc` iframe. */\nexport const CANVAS_CSP =\n \"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; \" +\n \"img-src 'self' data:; font-src 'self' data:; connect-src 'self'; frame-src 'self'; \" +\n \"object-src 'none'; base-uri 'none'; form-action 'none';\";\n","/**\n * Realistic fixture data for the development harness.\n *\n * Shapes follow the typed payloads in `../payloads`, which mirror the Dart\n * `toMap()` of `condor_core`. Timestamps that the device produces (`log_time`)\n * are **device-local** wall-clock strings, built with local `Date` getters —\n * never `toISOString()`.\n */\n\nimport type {\n DeviceConfigItem,\n DeviceTelemetryItem,\n DeviceUserConfigItem,\n SensorLogItem,\n SensorLogPayload,\n SensorLogQueryPayload,\n SystemStatusPayload,\n UserDevicePayload,\n} from '../payloads';\n\n/** Default log period of an ActLumus, in seconds. Programmable per device. */\nexport const DEFAULT_LOG_PERIOD_S = 60;\n\nconst pad = (n: number): string => String(n).padStart(2, '0');\n\n/** `YYYY-MM-DD HH:MM:SS` in local time — the format of `log_time`. */\nexport function formatLocalTimestamp(date: Date): string {\n return (\n `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ` +\n `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`\n );\n}\n\n/** Parses a local `YYYY-MM-DD HH:MM:SS` back to epoch ms. */\nexport function parseLocalTimestamp(value: string): number {\n const m = /^(\\d{4})-(\\d{2})-(\\d{2}) (\\d{2}):(\\d{2}):(\\d{2})$/.exec(value);\n if (!m) return Number.NaN;\n const [, y, mo, d, h, mi, s] = m.map(Number) as number[];\n return new Date(y ?? 0, (mo ?? 1) - 1, d ?? 1, h ?? 0, mi ?? 0, s ?? 0).getTime();\n}\n\ninterface DeviceSpec {\n deviceId: number;\n serialNo: number;\n modelId?: DeviceConfigItem['modelId'];\n bodyPart: DeviceConfigItem['deviceBodyPart'];\n name: string;\n battery: number;\n charging: boolean;\n syncedMinutesAgo: number;\n syncError?: string;\n}\n\nfunction config(spec: DeviceSpec): DeviceConfigItem {\n const item: DeviceConfigItem = {\n deviceId: spec.deviceId,\n serialNo: spec.serialNo,\n deviceBodyPart: spec.bodyPart,\n bleConnectionPeriod: 5,\n autosync: true,\n eraseDeviceMemory: false,\n autoEraseDeviceMemory: false,\n autoEraseDeviceMemoryThreshold: 80,\n readSensorsMemory: false,\n startDevice: false,\n readFullMemory: false,\n allowFirmwareUpdate: true,\n };\n if (spec.modelId) item.modelId = spec.modelId;\n return item;\n}\n\nfunction telemetry(spec: DeviceSpec, now: number): DeviceTelemetryItem {\n return {\n serialNo: spec.serialNo,\n deviceId: spec.deviceId,\n updataDateTime: now - spec.syncedMinutesAgo * 60_000,\n deviceName: spec.name,\n deviceVersion: spec.modelId === 'actlumus_plus' ? '3.0.2' : '2.1.0',\n hardwareVersion: spec.modelId === 'actlumus_plus' ? 'B' : 'A',\n deviceDateTime: now - spec.syncedMinutesAgo * 60_000,\n memoryDataUsage: 37.5,\n memorySystemUsage: 4.2,\n batteryLevel: spec.battery,\n batteryVoltage: 3.7 + spec.battery / 1000,\n isCharging: spec.charging,\n isOnDock: spec.charging,\n syncErrors: spec.syncError\n ? [\n {\n serialNo: spec.serialNo,\n attemptDate: now - 10 * 60_000,\n message: spec.syncError,\n messageCode: 'SYNC_FAILED',\n },\n ]\n : [],\n };\n}\n\nfunction userConfig(spec: DeviceSpec): DeviceUserConfigItem {\n return {\n serialNo: spec.serialNo,\n deviceId: spec.deviceId,\n syncTime: false,\n realTime: false,\n deleteMemory: false,\n readAllMemory: false,\n readAllSystemLog: false,\n };\n}\n\nfunction payloadOf(specs: DeviceSpec[], now = Date.now()): UserDevicePayload {\n return {\n lastUpdate: now,\n devicesConfig: specs.map(config),\n devicesTelemetry: specs.map((s) => telemetry(s, now)),\n devicesUserConfig: specs.map(userConfig),\n };\n}\n\n/** Two devices with distinct serial numbers: the common case. */\nexport function twoDevices(now = Date.now()): UserDevicePayload {\n return payloadOf(\n [\n {\n deviceId: 9001,\n serialNo: 4534,\n modelId: 'actlumus',\n bodyPart: 'wrist',\n name: 'ActLumus 4534',\n battery: 82,\n charging: false,\n syncedMinutesAgo: 12,\n },\n {\n deviceId: 9002,\n serialNo: 5120,\n modelId: 'actlumus_plus',\n bodyPart: 'necklace',\n name: 'ActLumus Plus 5120',\n battery: 41,\n charging: true,\n syncedMinutesAgo: 190,\n syncError: 'Could not read the device memory.',\n },\n ],\n now,\n );\n}\n\n/**\n * An ActLumus and an ActLumus Plus sharing serial number 7047 — real data\n * looked like this for the first pilot account. Only `deviceId` tells them\n * apart.\n */\nexport function sharedSerial(now = Date.now()): UserDevicePayload {\n return payloadOf(\n [\n {\n deviceId: 9101,\n serialNo: 7047,\n bodyPart: 'wrist',\n name: 'ActLumus 7047',\n battery: 67,\n charging: false,\n syncedMinutesAgo: 30,\n },\n {\n deviceId: 9102,\n serialNo: 7047,\n modelId: 'actlumus_plus',\n bodyPart: 'necklace',\n name: 'ActLumus Plus 7047',\n battery: 93,\n charging: false,\n syncedMinutesAgo: 8,\n },\n ],\n now,\n );\n}\n\nexport function systemStatus(overrides: Partial<SystemStatusPayload> = {}): SystemStatusPayload {\n return {\n bluetooth: true,\n internet: true,\n permission: true,\n hasDevices: true,\n missingOptionalPermissions: [],\n missingEssentialPermissions: [],\n deviceSyncError: {},\n deviceSyncErrorById: {},\n ...overrides,\n };\n}\n\n/** Light follows a daylight curve so charts look like real data. */\nfunction lightAt(epochMs: number, serialNo: number): { photopic: number; melanopic: number } {\n const d = new Date(epochMs);\n const hour = d.getHours() + d.getMinutes() / 60;\n const daylight = Math.max(0, Math.sin(((hour - 6) / 12) * Math.PI));\n const noise = ((epochMs / 60_000 + serialNo) % 7) / 7;\n const photopic = Math.round((daylight * 1800 + noise * 40) * 10) / 10;\n return { photopic, melanopic: Math.round(photopic * 0.62 * 10) / 10 };\n}\n\nfunction logAt(epochMs: number, serialNo: number, deviceId: number): SensorLogItem {\n const { photopic, melanopic } = lightAt(epochMs, serialNo);\n return {\n id: Math.floor(epochMs / 1000) * 10 + (serialNo % 10),\n log_time: formatLocalTimestamp(new Date(epochMs)),\n log_type: 1,\n body_part: 'wrist',\n temperature: 31.5,\n lux_photopic: photopic,\n lux_melanopic: melanopic,\n device_id: deviceId,\n act_data_pim: 120,\n };\n}\n\nconst HISTORY_DAYS = 14;\nconst encodeCursor = (epochMs: number): string => `mock:${epochMs}`;\nconst decodeCursor = (cursor: string): number => Number(cursor.slice('mock:'.length));\n\n/**\n * Answers a `sensorLog` query with the backend's paging semantics:\n * - no range: the most recent `limit` logs; older pages through `endingBefore`;\n * - with `startTime`: the oldest logs of the range first; newer pages through\n * `startingAfter` (with only `endTime`: the most recent before it);\n * - `logs` always chronological ascending; `hasMore` / `hasPrevious` computed\n * within the range.\n */\nexport function sensorLogPage(\n query: SensorLogQueryPayload,\n opts: { now?: number; periodS?: number; deviceId?: number } = {},\n): SensorLogPayload {\n const now = opts.now ?? Date.now();\n const periodMs = (opts.periodS ?? DEFAULT_LOG_PERIOD_S) * 1000;\n const limit = query.limit ?? 10;\n if (limit < 1 || limit > 1000) {\n return { requestId: query.requestId, success: false, error: 'INVALID_LIMIT' };\n }\n if (query.startingAfter && query.endingBefore) {\n return { requestId: query.requestId, success: false, error: 'CONFLICTING_CURSORS' };\n }\n\n const newest = Math.floor(now / periodMs) * periodMs;\n const oldest = newest - HISTORY_DAYS * 86_400_000;\n const rangeStart = query.startTime ? parseLocalTimestamp(query.startTime) : oldest;\n const rangeEnd = query.endTime ? parseLocalTimestamp(query.endTime) - 1 : newest;\n if (Number.isNaN(rangeStart) || Number.isNaN(rangeEnd) || rangeStart > rangeEnd + 1) {\n return { requestId: query.requestId, success: false, error: 'INVALID_TIME_RANGE' };\n }\n const first = Math.max(oldest, Math.ceil(rangeStart / periodMs) * periodMs);\n const last = Math.min(newest, Math.floor(rangeEnd / periodMs) * periodMs);\n\n let from: number;\n let to: number;\n if (query.endingBefore) {\n to = Math.min(last, decodeCursor(query.endingBefore) - periodMs);\n from = Math.max(first, to - (limit - 1) * periodMs);\n } else if (query.startingAfter) {\n from = Math.max(first, decodeCursor(query.startingAfter) + periodMs);\n to = Math.min(last, from + (limit - 1) * periodMs);\n } else if (query.startTime) {\n from = first;\n to = Math.min(last, from + (limit - 1) * periodMs);\n } else {\n to = last;\n from = Math.max(first, to - (limit - 1) * periodMs);\n }\n\n const logs: SensorLogItem[] = [];\n for (let t = from; t <= to; t += periodMs) {\n logs.push(logAt(t, query.serialNo, opts.deviceId ?? 0));\n }\n return {\n requestId: query.requestId,\n success: true,\n logs,\n startingAfter: logs.length ? encodeCursor(to) : null,\n endingBefore: logs.length ? encodeCursor(from) : null,\n hasMore: logs.length > 0 && to < last,\n hasPrevious: logs.length > 0 && from > first,\n };\n}\n","/**\n * Scenarios for the development harness — the device behaviours that a plain\n * \"answer everything at once\" mock hides.\n *\n * Each one reproduces something the Condor Canvas app really does, so a bundle\n * that survives every scenario in the browser survives them on a device.\n */\n\nimport type { SystemStatusPayload, UserDevicePayload } from '../payloads';\nimport { sharedSerial, systemStatus, twoDevices } from './fixtures';\n\n/** Native schema version the harness reports by default. */\nexport const HARNESS_NATIVE_VERSION = '0.17.0';\n\nexport interface Scenario {\n /** Shown in the harness toolbar. */\n description: string;\n /**\n * Relayed to the client by the handshake. `null` reproduces a shell that\n * could not reach the native side: the client receives `nativeVersion: null`.\n */\n nativeVersion: string | null;\n /** Delay of every native reply, in ms. A pair draws uniformly from the range. */\n latencyMs: number | [min: number, max: number];\n /**\n * Time until the app's background service is up, in ms after the page loads.\n * Until then `ping` and `userDevice` are **dropped without a reply** — on\n * the device they travel to that service, and a message sent before it runs\n * is lost.\n */\n serviceStartMs: number;\n /**\n * `userDevice` never answers. Today the app replies only when the device\n * list loads, so an account with no linked device gets silence.\n */\n userDeviceSilent: boolean;\n /** When set, every `sensorLog` reply fails with this `error`. */\n sensorLogError?: string;\n /** Log period of the devices, in seconds (programmable; default 60). */\n logPeriodS: number;\n systemStatus: SystemStatusPayload;\n devices: () => UserDevicePayload;\n}\n\nexport type ScenarioName = 'default' | 'cold-start' | 'no-devices' | 'shared-serial' | 'offline';\n\nconst base: Scenario = {\n description: 'Two devices, background service already running, realistic latency.',\n nativeVersion: HARNESS_NATIVE_VERSION,\n latencyMs: [80, 250],\n serviceStartMs: 0,\n userDeviceSilent: false,\n logPeriodS: 60,\n systemStatus: systemStatus(),\n devices: () => twoDevices(),\n};\n\nexport const SCENARIOS: Record<ScenarioName, Scenario> = {\n default: base,\n 'cold-start': {\n ...base,\n description:\n 'App launched from cold: the background service takes 4 s to start, and ' +\n 'every ping/userDevice sent before that is lost. A bundle that asks once ' +\n 'stays on its loading screen forever.',\n serviceStartMs: 4000,\n },\n 'no-devices': {\n ...base,\n description:\n 'Account with no linked device: userDevice never answers and ' +\n 'systemStatus.hasDevices is false. Your bundle must show an empty state.',\n userDeviceSilent: true,\n systemStatus: systemStatus({ hasDevices: false }),\n },\n 'shared-serial': {\n ...base,\n description:\n 'An ActLumus and an ActLumus Plus share serial number 7047. Only deviceId ' +\n 'tells them apart; sensorLog cannot address one of them.',\n devices: () => sharedSerial(),\n },\n offline: {\n ...base,\n description:\n 'No internet. systemStatus.internet is false and sensorLog fails; ' +\n 'userDevice answers from the cache.',\n sensorLogError: 'Connection failed',\n systemStatus: systemStatus({ internet: false }),\n },\n};\n\nexport function resolveScenario(scenario: ScenarioName | Scenario | undefined): Scenario {\n if (scenario === undefined) return SCENARIOS.default;\n if (typeof scenario === 'string') {\n const found = SCENARIOS[scenario];\n if (!found) {\n throw new Error(\n `condor-mobile-bridge dev: unknown scenario \"${scenario}\". ` +\n `Known: ${Object.keys(SCENARIOS).join(', ')}.`,\n );\n }\n return found;\n }\n return scenario;\n}\n"],"mappings":"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","names":["dirname","resolve","createHash","readFileSync","extname","resolve","env","resolve","extname","readFileSync","question","createHash","resolve","existsSync","readFileSync","statSync","dirname","extname","join","resolve","unzipSync","resolve","existsSync","statSync","join","extname","unzipSync","readFileSync","dirname","resolve","dirname"]}
1
+ {"version":3,"sources":["../src/cli.ts","../package.json","../src/canvas-admin/command.ts","../src/cli-core.ts","../src/canvas-admin/client.ts","../src/canvas-admin/tty.ts","../src/cli-dev.ts","../src/keys.ts","../src/dev/policy.ts","../src/dev/fixtures.ts","../src/dev/scenarios.ts"],"sourcesContent":["/**\n * cli.ts — `bin` entry for `condor-mobile-bridge`.\n *\n * Thin: parse argv, dispatch to the subcommand, print a report and pick an\n * exit code. Every testable decision lives in `cli-core.ts`.\n */\nimport { mkdirSync, writeFileSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport { version as packageVersion } from '../package.json';\nimport { CANVAS_USAGE, parseCanvasArgs, runCanvas } from './canvas-admin/command';\nimport { closeTerminalIo, terminalIo } from './canvas-admin/tty';\nimport { buildBundle, parseArgs, USAGE, type ValidationResult, validateBundle } from './cli-core';\nimport { startDevServer } from './cli-dev';\n\nfunction runDev(opts: {\n input: string;\n scenario: string;\n port: number;\n nativeVersion?: string;\n latencyMs?: number;\n panel: boolean;\n}): void {\n const { input, scenario, port, nativeVersion } = opts;\n const server = startDevServer(opts);\n server.on('listening', () => {\n const native = nativeVersion === undefined ? '' : `&nativeVersion=${nativeVersion}`;\n console.log(\n `Condor Canvas dev harness: http://localhost:${port}/${\n scenario.endsWith('.json') ? '' : `?scenario=${scenario}`\n }${native}`,\n );\n console.log(` bundle: ${input} (re-read on every reload)`);\n console.log(' sandbox, shell policy and CSP: same as the app');\n console.log(' scenarios: default, cold-start, no-devices, shared-serial, offline');\n console.log(' OS back button: the \"◀ OS back\" button at the top');\n console.log('\\nA bundle is ready for a device only when it handles every scenario.');\n });\n server.on('error', (err) => {\n console.error(`dev: ${err.message}`);\n process.exit(1);\n });\n}\n\nfunction runBuild(htmlPath: string, outPath: string, maxBytes: number): void {\n const result = buildBundle(htmlPath, maxBytes);\n\n const resolvedOut = resolve(outPath);\n mkdirSync(dirname(resolvedOut), { recursive: true });\n writeFileSync(resolvedOut, result.zipBytes);\n\n const kb = (Buffer.byteLength(result.html) / 1024).toFixed(1);\n console.log(`Built ${outPath}`);\n console.log(` entry: index.html (${kb} KB self-contained)`);\n console.log(` bundleSize (zip): ${result.size} bytes`);\n console.log(` bundleHash (zip): ${result.hash}`);\n\n if (result.overLimit) {\n const limitMb = (maxBytes / (1024 * 1024)).toFixed(1);\n console.warn(\n `\\n⚠️ WARNING: the .zip (${result.size} bytes) exceeds the ${limitMb} MB limit — ` +\n 'the app will REJECT this bundle (falls back to the default screen). Reduce inline assets/media.',\n );\n }\n\n console.log('\\nNext: npx condor-mobile-bridge validate -i ' + outPath + ' --strict');\n}\n\nfunction reportValidation(input: string, result: ValidationResult, strict: boolean): number {\n console.log(`Validating ${input}`);\n if (result.entry) console.log(` entry: ${result.entry}`);\n if (result.size !== null) console.log(` bundleSize (zip): ${result.size} bytes`);\n if (result.hash !== null) console.log(` bundleHash (zip): ${result.hash}`);\n\n const errors = result.findings.filter((f) => f.severity === 'error');\n const warnings = result.findings.filter((f) => f.severity === 'warning');\n\n if (result.findings.length === 0) {\n console.log('\\n✅ No findings. This bundle conforms to the Canvas contract.');\n return 0;\n }\n\n console.log('');\n for (const f of result.findings) {\n const badge = f.severity === 'error' ? '❌ ERROR ' : '⚠️ WARNING';\n console.log(`${badge} [${f.rule}] ${f.message}`);\n }\n\n console.log(\n `\\n${errors.length} error(s), ${warnings.length} warning(s).` +\n (result.size === null\n ? '\\nNote: validating an HTML file skips the size and hash gates. Validate the .zip before delivery.'\n : ''),\n );\n\n if (errors.length > 0) {\n console.log('Errors are gates the Canvas app itself applies — it would reject this bundle.');\n return 1;\n }\n if (warnings.length > 0 && strict) {\n console.log('Warnings are failures under --strict.');\n return 1;\n }\n console.log(\n 'Warnings are not app-level rejections, but they fail silently on a real device. Re-run with --strict to gate on them.',\n );\n return 0;\n}\n\nasync function runCanvasCommand(argv: string[]): Promise<void> {\n if (argv.length === 0 || argv.includes('--help') || argv.includes('-h')) {\n console.log(CANVAS_USAGE);\n process.exit(argv.length === 0 ? 1 : 0);\n }\n const parsed = parseCanvasArgs(argv, process.env);\n if ('error' in parsed) {\n console.error(parsed.error);\n console.error('Run `condor-mobile-bridge canvas --help`.');\n process.exit(1);\n }\n const code = await runCanvas(parsed, terminalIo(), {\n userAgent: `condor-mobile-bridge-cli/${packageVersion}`,\n });\n closeTerminalIo();\n process.exit(code);\n}\n\nfunction main(): void {\n if (process.argv[2] === 'canvas') {\n void runCanvasCommand(process.argv.slice(3));\n return;\n }\n const args = parseArgs(process.argv.slice(2));\n\n if (args.help || !args.htmlPath) {\n console.log(USAGE);\n process.exit(args.help ? 0 : 1);\n }\n\n if (args.command === 'dev') {\n try {\n runDev({\n input: args.htmlPath,\n scenario: args.scenario,\n port: args.port,\n nativeVersion: args.nativeVersion,\n latencyMs: args.latencyMs,\n panel: args.panel,\n });\n } catch (err) {\n console.error(`dev: ${err instanceof Error ? err.message : String(err)}`);\n process.exit(1);\n }\n return;\n }\n\n if (args.command === 'validate') {\n const result = validateBundle(args.htmlPath, args.maxBytes);\n process.exit(reportValidation(args.htmlPath, result, args.strict));\n }\n\n runBuild(args.htmlPath, args.outPath, args.maxBytes);\n}\n\nmain();\n","{\n \"name\": \"@condorinst/mobile-bridge\",\n \"version\": \"0.23.0\",\n \"description\": \"TypeScript SDK for the JS↔Native bridge of the Condor white-label mobile app.\",\n \"license\": \"SEE LICENSE IN LICENSE\",\n \"private\": false,\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"type\": \"module\",\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"./dev\": {\n \"types\": \"./dist/dev.d.ts\",\n \"import\": \"./dist/dev.js\",\n \"require\": \"./dist/dev.cjs\"\n },\n \"./contract\": \"./contract/bridge-contract.json\",\n \"./package.json\": \"./package.json\"\n },\n \"bin\": {\n \"condor-mobile-bridge\": \"dist/cli.js\"\n },\n \"files\": [\n \"dist\",\n \"docs\",\n \"contract\",\n \"examples/minimal-home/src\",\n \"examples/minimal-home/index.html\",\n \"examples/minimal-home/build.mjs\",\n \"examples/minimal-home/package.json\",\n \"examples/minimal-home/tsconfig.json\",\n \"examples/minimal-home/README.md\",\n \"examples/minimal-home/AGENTS.md\",\n \"AGENTS.md\",\n \"llms.txt\",\n \"llms-full.txt\",\n \"README.md\",\n \"CHANGELOG.md\",\n \"LICENSE\"\n ],\n \"scripts\": {\n \"build\": \"tsup && node scripts/gen-llms-full.mjs\",\n \"typecheck\": \"tsc --noEmit && tsc --noEmit -p examples/minimal-home/tsconfig.check.json && tsc --noEmit -p examples/client_demo\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"lint\": \"biome check .\",\n \"format\": \"biome format --write .\",\n \"prepare\": \"npm run build\",\n \"prepublishOnly\": \"npm run build && npm run test\"\n },\n \"engines\": {\n \"node\": \">=18\"\n },\n \"devDependencies\": {\n \"@biomejs/biome\": \"^2.0.0\",\n \"@types/node\": \"^22.0.0\",\n \"jsdom\": \"^26.0.0\",\n \"tsup\": \"^8.3.0\",\n \"typescript\": \"^5.6.0\",\n \"vitest\": \"^4.1.8\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://bitbucket.org/condorinstprogdev/mobile-bridge.git\"\n },\n \"homepage\": \"https://bitbucket.org/condorinstprogdev/mobile-bridge\",\n \"author\": \"Condor Instruments\",\n \"bugs\": {\n \"url\": \"https://bitbucket.org/condorinstprogdev/mobile-bridge/issues\"\n },\n \"dependencies\": {\n \"fflate\": \"^0.8.3\",\n \"node-html-parser\": \"^8.0.2\",\n \"valibot\": \"^1.4.1\"\n }\n}\n","/**\n * `condor-mobile-bridge canvas publish | list | revoke` — the flow around\n * {@link CanvasAdminClient}: local validation, prompts, confirmation and\n * reporting. Terminal I/O is injected ({@link CanvasIo}) so the flow is testable.\n *\n * Safety rules, on purpose:\n * - the password is only ever read from an interactive prompt without echo —\n * never from a flag, argv or the environment — and is never stored;\n * - the session is kept in memory for one command;\n * - publishing shows what goes where and asks before sending, because it goes\n * live at once for every user of the company.\n */\n\nimport { createHash } from 'node:crypto';\nimport { existsSync, readFileSync, statSync } from 'node:fs';\nimport { extname, join, resolve } from 'node:path';\nimport { buildBundle, type ValidationFinding, validateZipBytes } from '../cli-core';\nimport {\n CANVAS_ADMIN_API,\n CanvasAdminClient,\n type CanvasAdminEnv,\n CanvasAdminError,\n type CanvasBundleRecord,\n} from './client';\n\nexport interface CanvasIo {\n /** `false` when stdin is not a terminal: nothing can be asked. */\n readonly interactive: boolean;\n ask(question: string): Promise<string>;\n /** Reads without echoing what is typed. */\n askSecret(question: string): Promise<string>;\n print(line: string): void;\n printError(line: string): void;\n}\n\nexport type CanvasAction = 'publish' | 'list' | 'revoke';\n\nexport interface CanvasArgs {\n action: CanvasAction;\n env: CanvasAdminEnv;\n baseUrl?: string;\n companyId?: number;\n userId?: number;\n input?: string;\n version?: string;\n minAppVersion?: string;\n bundleId?: number;\n fallbackVersion?: string;\n yes: boolean;\n json: boolean;\n}\n\nexport const CANVAS_USAGE = `condor-mobile-bridge canvas — publish a bundle to the Condor Canvas app.\n\nUsage:\n npx condor-mobile-bridge canvas publish <dist/index.html|dir|bundle.zip> --version <v> [options]\n npx condor-mobile-bridge canvas list [options]\n npx condor-mobile-bridge canvas revoke <bundleId> [--fallback-version <v>] [options]\n\nPublishing goes LIVE AT ONCE for every user of the company — there is no draft.\nUse a test company, and see docs/device-testing.md.\n\nOptions:\n --company <id> Company id, as in the Condor web login (or CONDOR_CANVAS_COMPANY).\n --user <id> Your user id, as in the Condor web login (or CONDOR_CANVAS_USER).\n Both are asked when not given.\n --version <v> publish: bundle version, unique per company.\n --min-app-version <v> publish: minimum app build that may load it.\n --fallback-version <v> revoke: VERSION to serve instead (the version column of\n canvas list, e.g. 0.0.8 — not the id).\n --env prod|dev API environment (default: prod). dev is for Condor staff.\n --base-url <url> Override the API URL.\n --yes Do not ask for confirmation.\n --json Print results as JSON.\n\nYour password (and the two-factor code, when your company uses one) is asked\ninteractively, without echo, and never stored. There is no non-interactive login.`;\n\nconst intOrUndefined = (value: string | undefined): number | undefined => {\n if (value === undefined) return undefined;\n const n = Number(value);\n return Number.isInteger(n) && n > 0 ? n : Number.NaN;\n};\n\n/**\n * Parses the argv that follows `canvas`. The company and user ids — not\n * secrets — may also come from `CONDOR_CANVAS_COMPANY` / `CONDOR_CANVAS_USER`;\n * flags win. The password never comes from anywhere but the prompt.\n */\nexport function parseCanvasArgs(\n argv: string[],\n env: Record<string, string | undefined> = {},\n): CanvasArgs | { error: string } {\n const [action, ...rest] = argv;\n if (action !== 'publish' && action !== 'list' && action !== 'revoke') {\n return { error: `Unknown canvas command \"${action ?? ''}\". Use publish, list or revoke.` };\n }\n const args: CanvasArgs = {\n action,\n env: 'prod',\n yes: false,\n json: false,\n companyId: intOrUndefined(env.CONDOR_CANVAS_COMPANY),\n userId: intOrUndefined(env.CONDOR_CANVAS_USER),\n };\n const positional: string[] = [];\n for (let i = 0; i < rest.length; i++) {\n const token = rest[i] ?? '';\n const next = (): string | undefined => rest[++i];\n switch (token) {\n case '--env': {\n const env = next();\n if (env !== 'prod' && env !== 'dev') return { error: '--env must be prod or dev.' };\n args.env = env;\n break;\n }\n case '--base-url':\n args.baseUrl = next();\n break;\n case '--company':\n args.companyId = intOrUndefined(next());\n break;\n case '--user':\n args.userId = intOrUndefined(next());\n break;\n case '--version':\n args.version = next();\n break;\n case '--min-app-version':\n args.minAppVersion = next();\n break;\n case '--fallback-version':\n args.fallbackVersion = next();\n break;\n case '--yes':\n args.yes = true;\n break;\n case '--json':\n args.json = true;\n break;\n case '--password':\n return {\n error: 'There is no --password flag: the password is only read from the prompt.',\n };\n default:\n if (token.startsWith('-')) return { error: `Unknown option ${token}.` };\n positional.push(token);\n }\n }\n if (Number.isNaN(args.companyId) || Number.isNaN(args.userId)) {\n return {\n error:\n '--company and --user (or CONDOR_CANVAS_COMPANY / CONDOR_CANVAS_USER) take a positive integer id.',\n };\n }\n if (action === 'publish') {\n args.input = positional[0];\n if (!args.input)\n return { error: 'canvas publish needs the bundle: dist/index.html, a dir or a .zip.' };\n if (!args.version) return { error: 'canvas publish needs --version.' };\n }\n if (action === 'revoke') {\n args.bundleId = intOrUndefined(positional[0]);\n if (!args.bundleId || Number.isNaN(args.bundleId)) {\n return { error: 'canvas revoke needs the bundle id (see canvas list).' };\n }\n }\n return args;\n}\n\n/** Reads the input as the zip the app will download, building it when needed. */\nfunction loadZip(input: string): { zipBytes: Uint8Array; findings: ValidationFinding[] } {\n const path = resolve(input);\n if (!existsSync(path)) throw new Error(`${input}: not found.`);\n let zipBytes: Uint8Array;\n if (extname(path).toLowerCase() === '.zip') {\n zipBytes = new Uint8Array(readFileSync(path));\n } else {\n const html = statSync(path).isDirectory() ? join(path, 'index.html') : path;\n zipBytes = buildBundle(html).zipBytes;\n }\n return { zipBytes, findings: validateZipBytes(zipBytes).findings };\n}\n\nasync function askId(io: CanvasIo, label: string, value: number | undefined): Promise<number> {\n if (value !== undefined) return value;\n const answer = (await io.ask(`${label}: `)).trim();\n const n = Number(answer);\n if (!Number.isInteger(n) || n <= 0) throw new Error(`${label} must be a positive integer.`);\n return n;\n}\n\nasync function confirm(io: CanvasIo, args: CanvasArgs, question: string): Promise<boolean> {\n if (args.yes) return true;\n const answer = (await io.ask(`${question} [y/N] `)).trim().toLowerCase();\n return answer === 'y' || answer === 'yes';\n}\n\nasync function logIn(\n io: CanvasIo,\n client: CanvasAdminClient,\n companyId: number,\n userId: number,\n): Promise<void> {\n io.print('');\n io.print('Log in with your Condor web account (typing is hidden; press Enter when done).');\n let password = '';\n for (let attempt = 0; attempt < 3 && !password.trim(); attempt++) {\n if (attempt > 0) io.printError('The password cannot be empty.');\n password = await io.askSecret('Password: ');\n }\n // The server drops empty fields and answers \"Invalid data\": stop here instead.\n if (!password.trim()) throw new Error('No password given. Nothing was sent.');\n const { needsCode } = await client.login({ companyId, userId, password });\n if (!needsCode) return;\n io.print(\n 'Your company uses two-factor login: a code was sent to your e-mail. Type it below (hidden).',\n );\n for (let attempt = 1; attempt <= 2; attempt++) {\n const code = (await io.askSecret('Code (or a recovery code): ')).trim();\n try {\n await client.verifyCode(code);\n return;\n } catch (err) {\n if (attempt === 2 || !(err instanceof CanvasAdminError) || err.status !== 400) throw err;\n io.printError('That code was not accepted. Try once more.');\n }\n }\n}\n\nfunction describeError(err: unknown): string {\n if (!(err instanceof CanvasAdminError)) return err instanceof Error ? err.message : String(err);\n if (err.status === 400 && /fallback/i.test(err.reason ?? '')) {\n return `${err.message}\\n--fallback-version takes the VERSION column of \\`canvas list\\` (e.g. 0.0.8), not the id.`;\n }\n switch (err.status) {\n case 401:\n return `${err.message}\\nCheck the company id, user id and password.`;\n case 403:\n return `${err.message}\\nYour user cannot do this for that company: publishing needs a manager of the company.`;\n case 409:\n return `${err.message}\\nThis version was already published for the company. Use a new --version.`;\n case 413:\n return `${err.message}\\nThe .zip is over 2 MB.`;\n default:\n return err.message;\n }\n}\n\nfunction table(rows: CanvasBundleRecord[]): string[] {\n if (rows.length === 0) return ['No bundle published for this company.'];\n return [\n 'id version current revoked published_at min_app',\n ...rows.map(\n (r) =>\n `${String(r.id).padEnd(8)}${r.version.padEnd(15)}${(r.is_current ? 'yes' : '').padEnd(9)}${(\n r.revoked ? 'yes' : ''\n ).padEnd(9)}${(r.published_at ?? '').padEnd(28)}${r.min_app_version ?? ''}`,\n ),\n ];\n}\n\nexport interface CanvasDeps {\n userAgent: string;\n fetchImpl?: typeof fetch;\n}\n\n/** Runs one `canvas` command. Returns the process exit code. */\nexport async function runCanvas(args: CanvasArgs, io: CanvasIo, deps: CanvasDeps): Promise<number> {\n const baseUrl = args.baseUrl ?? CANVAS_ADMIN_API[args.env];\n const envLabel = args.baseUrl ? `custom (${baseUrl})` : `${args.env.toUpperCase()} (${baseUrl})`;\n try {\n let zip: { zipBytes: Uint8Array; findings: ValidationFinding[] } | undefined;\n if (args.action === 'publish') {\n zip = loadZip(args.input ?? '');\n const errors = zip.findings.filter((f) => f.severity === 'error');\n for (const f of zip.findings) {\n (f.severity === 'error' ? io.printError : io.print)(\n `${f.severity.toUpperCase()} [${f.rule}] ${f.message}`,\n );\n }\n if (errors.length > 0) {\n io.printError('The app would reject this bundle. Nothing was sent.');\n return 1;\n }\n }\n\n if (!io.interactive) {\n io.printError(\n 'canvas needs an interactive terminal: the password is only read from a prompt.',\n );\n return 1;\n }\n const companyId = await askId(io, 'Company id', args.companyId);\n const userId = await askId(io, 'User id', args.userId);\n\n if (args.action === 'publish' && zip) {\n const hash = `sha256-${createHash('sha256').update(zip.zipBytes).digest('hex')}`;\n io.print('');\n io.print(`Environment: ${envLabel}`);\n io.print(`Company: ${companyId}`);\n io.print(\n `Version: ${args.version}${args.minAppVersion ? ` (min app ${args.minAppVersion})` : ''}`,\n );\n io.print(`Bundle: ${zip.zipBytes.length} bytes, ${hash}`);\n io.print('It goes LIVE AT ONCE for every user of this company.');\n if (!(await confirm(io, args, 'Publish?'))) {\n io.print('Cancelled. Nothing was sent.');\n return 1;\n }\n }\n if (args.action === 'revoke') {\n io.print('');\n io.print(`Environment: ${envLabel}`);\n io.print(`Revoke bundle ${args.bundleId} of company ${companyId}.`);\n io.print(\n args.fallbackVersion\n ? `The app switches to version ${args.fallbackVersion} (a version, not an id).`\n : 'Without --fallback-version, revoking the current bundle leaves the company with none: the app shows its default screen.',\n );\n if (!(await confirm(io, args, 'Revoke?'))) {\n io.print('Cancelled. Nothing was sent.');\n return 1;\n }\n }\n\n const client = new CanvasAdminClient({\n baseUrl,\n userAgent: deps.userAgent,\n fetchImpl: deps.fetchImpl,\n });\n await logIn(io, client, companyId, userId);\n\n if (args.action === 'publish' && zip) {\n const record = await client.publish({\n zipBytes: zip.zipBytes,\n version: args.version ?? '',\n companyId,\n minAppVersion: args.minAppVersion,\n });\n if (args.json) io.print(JSON.stringify(record));\n else {\n io.print(\n `Published version ${record.version} (id ${record.id}) — current: ${record.is_current}.`,\n );\n io.print(`bundleHash: ${record.bundle_hash}`);\n io.print('Apps of this company load it the next time they come to the foreground.');\n }\n return 0;\n }\n if (args.action === 'list') {\n const rows = await client.list(companyId);\n if (args.json) io.print(JSON.stringify(rows));\n else for (const line of table(rows)) io.print(line);\n return 0;\n }\n const result = await client.revoke(args.bundleId ?? 0, args.fallbackVersion);\n io.print(args.json ? JSON.stringify(result) : `Revoked. ${JSON.stringify(result)}`);\n return 0;\n } catch (err) {\n io.printError(describeError(err));\n return 1;\n }\n}\n","/**\n * cli-core.ts — the pure, testable core of the `condor-mobile-bridge` CLI.\n *\n * `build` takes a client's `dist/index.html`, inlines JavaScript into\n * `<script>`, CSS into `<style>` (resolving `url()` into `data:` URIs),\n * converts local media into `data:` URIs, and packs the result into a `.zip`\n * holding a single `index.html` at the root. It does not touch the output\n * filesystem — `cli.ts` writes the `.zip`.\n *\n * `validate` checks a bundle against the gates the app applies on download,\n * plus a lint for the APIs the sandbox and the CSP block at runtime.\n *\n * Why self-contained: the app renders through `iframe.srcdoc` with a `null`\n * origin and no base URL, which can only resolve inline content and `data:`\n * URIs.\n *\n * **Known limitation.** The post-build inliner assumes a single-chunk `dist`:\n * one JS file, one CSS file, assets. A build with code splitting, or ES module\n * imports between chunks, breaks under `srcdoc` — the client must build\n * single-file (Vite: `vite-plugin-singlefile`; CRA: no splitting).\n */\nimport { createHash } from 'node:crypto';\nimport { readFileSync } from 'node:fs';\nimport { dirname, extname, resolve } from 'node:path';\nimport { strToU8, unzipSync, zipSync } from 'fflate';\nimport { type HTMLElement, parse } from 'node-html-parser';\n\n/** Maximum `.zip` size. Mirrors `WhitelabelBundleLimits.maxZipBytes` in the app. */\nexport const DEFAULT_MAX_BYTES = 2 * 1024 * 1024;\n\n/** Fixed mtime (>= 1980, the ZIP format floor) so the SHA-256 is reproducible. */\nconst FIXED_MTIME = new Date('2000-01-01T00:00:00Z');\n\n/** Extension → MIME map, for building `data:` URIs of embedded media. */\nexport const MIME_TYPES: Record<string, string> = {\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.gif': 'image/gif',\n '.webp': 'image/webp',\n '.avif': 'image/avif',\n '.svg': 'image/svg+xml',\n '.ico': 'image/x-icon',\n '.bmp': 'image/bmp',\n '.woff': 'font/woff',\n '.woff2': 'font/woff2',\n '.ttf': 'font/ttf',\n '.otf': 'font/otf',\n '.eot': 'application/vnd.ms-fontobject',\n '.mp3': 'audio/mpeg',\n '.mp4': 'video/mp4',\n '.webm': 'video/webm',\n '.json': 'application/json',\n};\n\n/** Refs that must NOT be inlined: external absolute, protocol-relative, already-inline `data:`. */\nfunction isExternalOrInline(url: string): boolean {\n return /^(https?:)?\\/\\//i.test(url) || /^data:/i.test(url) || /^(mailto:|tel:|#)/i.test(url);\n}\n\n/** Strips query and hash from a local ref (`main.js?v=abc#x` → `main.js`). */\nfunction stripQueryHash(url: string): string {\n return url.split(/[?#]/)[0] ?? url;\n}\n\n/**\n * Resolves a local ref to an absolute filesystem path.\n *\n * `htmlRoot` is the site root (the directory of `index.html`); `resourceDir` is\n * the directory of the resource holding the ref — for `url()` inside CSS, that\n * is the directory of the `.css`. A ref starting with `/` is relative to the\n * site root.\n */\nfunction resolveLocal(htmlRoot: string, resourceDir: string, url: string): string {\n const clean = stripQueryHash(url);\n return clean.startsWith('/') ? resolve(htmlRoot, `.${clean}`) : resolve(resourceDir, clean);\n}\n\n/** Reads a local file and returns a base64 `data:` URI. */\nexport function toDataUri(filePath: string): string {\n const bytes = readFileSync(filePath);\n const mime = MIME_TYPES[extname(filePath).toLowerCase()] ?? 'application/octet-stream';\n return `data:${mime};base64,${bytes.toString('base64')}`;\n}\n\n/**\n * Resolves `url(...)` inside a CSS block, replacing local refs with `data:`\n * URIs. `cssDir` is the directory of the CSS file, or of the HTML for an inline\n * `<style>`.\n */\nexport function inlineCssUrls(css: string, htmlRoot: string, cssDir: string): string {\n return css.replace(/url\\(\\s*(['\"]?)([^'\")]+)\\1\\s*\\)/gi, (match, _quote, rawUrl: string) => {\n if (isExternalOrInline(rawUrl)) return match;\n const filePath = resolveLocal(htmlRoot, cssDir, rawUrl);\n return `url(${toDataUri(filePath)})`;\n });\n}\n\n/** Inlines `<script src=\"local\">` into `<script>…</script>`, escaping `</script>`. */\nfunction inlineScripts(root: HTMLElement, htmlRoot: string): void {\n for (const el of root.querySelectorAll('script[src]')) {\n const src = el.getAttribute('src');\n if (!src || isExternalOrInline(src)) continue;\n const js = readFileSync(resolveLocal(htmlRoot, htmlRoot, src), 'utf-8').replace(\n /<\\/script>/gi,\n '<\\\\/script>',\n );\n el.removeAttribute('src');\n // `textContent`, never `set_content`: the latter PARSES what it receives\n // as HTML. Every bundle carries markup inside JS strings, and the parser\n // rewrites it — `<stop … />` becomes `<stop …></stop>`, a comparison like\n // `a<T` is read as an opening tag, and the matching close is emitted at the\n // end of the script. The JS comes out syntactically broken, the zip is\n // produced without error, and the app aborts the Home with \"native bridges\n // reachable from the iframe\" — because the client never got far enough to\n // answer the verify-sandbox probe.\n el.textContent = js;\n }\n}\n\n/** Inlines `<link rel=\"stylesheet\" href=\"local\">` into `<style>…</style>`, resolving `url()`. */\nfunction inlineStylesheets(root: HTMLElement, htmlRoot: string): void {\n for (const el of root.querySelectorAll('link[rel=\"stylesheet\"]')) {\n const href = el.getAttribute('href');\n if (!href || isExternalOrInline(href)) continue;\n const cssPath = resolveLocal(htmlRoot, htmlRoot, href);\n const css = inlineCssUrls(readFileSync(cssPath, 'utf-8'), htmlRoot, dirname(cssPath));\n el.replaceWith(...parse(`<style>${css}</style>`).childNodes);\n }\n}\n\n/** Resolves `url()` in `<style>` blocks already present in the HTML. */\nfunction inlineExistingStyleBlocks(root: HTMLElement, htmlRoot: string): void {\n for (const el of root.querySelectorAll('style')) {\n const css = el.innerHTML;\n if (!css.includes('url(')) continue;\n el.set_content(inlineCssUrls(css, htmlRoot, htmlRoot));\n }\n}\n\n/** Converts local media referenced by attribute (`src`/`href`) into `data:` URIs. */\nfunction inlineMediaAttributes(root: HTMLElement, htmlRoot: string): void {\n const targets: Array<[selector: string, attr: string]> = [\n ['img[src]', 'src'],\n ['source[src]', 'src'],\n ['audio[src]', 'src'],\n ['video[src]', 'src'],\n ['link[rel=\"icon\"]', 'href'],\n ['link[rel=\"shortcut icon\"]', 'href'],\n ['link[rel=\"apple-touch-icon\"]', 'href'],\n ['link[rel=\"preload\"]', 'href'],\n ];\n for (const [selector, attr] of targets) {\n for (const el of root.querySelectorAll(selector)) {\n const ref = el.getAttribute(attr);\n if (!ref || isExternalOrInline(ref)) continue;\n el.setAttribute(attr, toDataUri(resolveLocal(htmlRoot, htmlRoot, ref)));\n }\n }\n}\n\n/**\n * Reads the input HTML and returns the consolidated, self-contained document:\n * JS and CSS inline, media as `data:` URIs. Writes nothing to disk.\n */\nexport function inlineHtml(htmlPath: string): string {\n const htmlRoot = dirname(resolve(htmlPath));\n const root = parse(readFileSync(htmlPath, 'utf-8'), {\n comment: true,\n voidTag: { closingSlash: true },\n });\n inlineScripts(root, htmlRoot);\n inlineStylesheets(root, htmlRoot);\n inlineExistingStyleBlocks(root, htmlRoot);\n inlineMediaAttributes(root, htmlRoot);\n return root.toString();\n}\n\nexport interface BundleResult {\n /** The consolidated, self-contained HTML. */\n html: string;\n /** Bytes of the `.zip`, whose single entry is `index.html` at the root. */\n zipBytes: Uint8Array;\n /** Size of the `.zip` in bytes. */\n size: number;\n /** SHA-256 of the `.zip` bytes, as `sha256-<hex>`. Matches what the app computes. */\n hash: string;\n /** `true` when `size` exceeds `maxBytes`. */\n overLimit: boolean;\n}\n\n/**\n * Builds the bundle from the input `index.html`: inlines, zips it with\n * `index.html` at the root and a fixed mtime for a deterministic hash, and\n * computes size and SHA-256. Pure — writing the `.zip` is the caller's job.\n */\nexport function buildBundle(htmlPath: string, maxBytes: number = DEFAULT_MAX_BYTES): BundleResult {\n const html = inlineHtml(htmlPath);\n const zipBytes = zipSync({ 'index.html': [strToU8(html), { mtime: FIXED_MTIME }] }, { level: 6 });\n const size = zipBytes.length;\n const hash = `sha256-${createHash('sha256').update(zipBytes).digest('hex')}`;\n return { html, zipBytes, size, hash, overLimit: size > maxBytes };\n}\n\n// ---------------------------------------------------------------------------\n// validate — conformance checks for a client bundle\n// ---------------------------------------------------------------------------\n\n/**\n * Severity of a validation finding.\n *\n * `error` mirrors a guard the Canvas app itself applies when it downloads the\n * bundle: the app REJECTS the artifact and keeps the previous cache or falls\n * back to the default screen. `warning` marks something the app accepts but\n * the runtime blocks — the CSP or the `sandbox` attribute makes the call fail\n * on the device, where it is much harder to diagnose.\n */\nexport type ValidationSeverity = 'error' | 'warning';\n\n/** A single conformance finding. `rule` is a stable machine-readable id. */\nexport interface ValidationFinding {\n severity: ValidationSeverity;\n rule: string;\n message: string;\n}\n\n/** Outcome of validating a bundle (`.zip`) or a pre-build `index.html`. */\nexport interface ValidationResult {\n /** `false` when at least one `error` finding was produced. */\n ok: boolean;\n /** Entry document the app would render, or `null` when none was found. */\n entry: string | null;\n /** Size of the `.zip` in bytes; `null` when validating a bare HTML file. */\n size: number | null;\n /** `sha256-<hex>` of the `.zip`; `null` when validating a bare HTML file. */\n hash: string | null;\n findings: ValidationFinding[];\n}\n\n/**\n * APIs that are unreachable inside the Canvas client iframe.\n *\n * The client runs in `<iframe sandbox=\"allow-scripts\">` rendered from\n * `srcdoc`, so its origin is `null`, and it inherits the shell CSP\n * (`script-src 'self' 'unsafe-inline'`, `connect-src 'self'`, no\n * `'unsafe-eval'`). Every entry here fails silently or throws at runtime on a\n * real device — the bundle still installs, which is exactly what makes these\n * expensive to find later.\n *\n * Detection is textual and therefore best-effort: minified code that renames\n * `window.open` to `t.open` is invisible to it. Absence of findings is not a\n * proof of conformance.\n */\nexport const BLOCKED_API_RULES: ReadonlyArray<{\n rule: string;\n pattern: RegExp;\n message: string;\n}> = [\n {\n rule: 'web-storage',\n pattern: /\\b(?:localStorage|sessionStorage)\\b/,\n message:\n 'Web Storage throws SecurityError: the client iframe has a null origin. Keep state in memory — it is lost when the WebView reloads.',\n },\n {\n rule: 'indexed-db',\n pattern: /\\bindexedDB\\b/,\n message: 'IndexedDB is unavailable on a null origin. Keep state in memory.',\n },\n {\n rule: 'cookies',\n pattern: /document\\s*\\.\\s*cookie/,\n message: 'Cookies cannot be written from a null origin; the assignment is a no-op.',\n },\n {\n rule: 'dynamic-eval',\n pattern: /\\beval\\s*\\(|new\\s+Function\\s*\\(/,\n message:\n \"The shell CSP has no 'unsafe-eval'; eval() and new Function() are blocked. Build without eval-based sourcemaps.\",\n },\n {\n rule: 'service-worker',\n pattern: /serviceWorker/,\n message: 'Service workers cannot be registered from a null origin.',\n },\n {\n rule: 'send-beacon',\n pattern: /sendBeacon/,\n message:\n 'navigator.sendBeacon is neutralized by the shell before your bundle runs (pen test PT-10). The call returns without sending.',\n },\n {\n rule: 'popups',\n pattern: /window\\s*\\.\\s*open\\s*\\(/,\n message: 'The sandbox has no allow-popups; window.open() returns null.',\n },\n {\n rule: 'crypto-randomuuid',\n pattern: /crypto\\s*\\.\\s*randomUUID\\b/,\n message:\n \"crypto.randomUUID exists only in a secure context, and the app's sandboxed iframe is probably not one: it can be undefined on the phone while it works in the harness. Use createRequestId() from the SDK, or crypto.getRandomValues.\",\n },\n {\n rule: 'top-navigation',\n pattern: /window\\s*\\.\\s*top\\b/,\n message:\n 'The sandbox has no allow-top-navigation; reading or navigating window.top throws SecurityError.',\n },\n {\n rule: 'external-network',\n pattern: /\\bfetch\\s*\\(|\\bXMLHttpRequest\\b|\\bWebSocket\\s*\\(/,\n message:\n \"connect-src 'self' on a null origin blocks every network call. Request data through the bridge instead (e.g. BridgeClient.fetchSensorLog).\",\n },\n];\n\n/**\n * Marker the dev harness (`@condorinst/mobile-bridge/dev`) leaves in any code\n * that bundles it. Kept in sync with `DEV_HARNESS_MARKER` in `src/dev/harness.ts`.\n */\nexport const DEV_HARNESS_MARKER = '__condorDevHarness__';\n\n/** Runs the blocked-API lint over a client document. */\nexport function lintClientHtml(html: string): ValidationFinding[] {\n const findings: ValidationFinding[] = [];\n if (html.includes(DEV_HARNESS_MARKER)) {\n findings.push({\n severity: 'error',\n rule: 'dev-harness-bundled',\n message:\n 'The bundle contains the development harness (@condorinst/mobile-bridge/dev). It belongs in the page that hosts the bundle during development, never in the bundle: remove the import.',\n });\n }\n for (const { rule, pattern, message } of BLOCKED_API_RULES) {\n if (pattern.test(html)) findings.push({ severity: 'warning', rule, message });\n }\n return findings;\n}\n\n/**\n * Checks that a document carries no reference the `srcdoc` render cannot\n * resolve. Mirrors `_isSelfContained` in the app's\n * `whitelabel_bundle_service.dart`: a `<script src>` or a stylesheet `<link>`\n * pointing anywhere but a `data:` URI makes the app reject the bundle.\n * Relative images are NOT rejected by the app (cosmetic degradation only), so\n * they surface here as warnings.\n */\nexport function checkSelfContained(html: string): ValidationFinding[] {\n const findings: ValidationFinding[] = [];\n const root = parse(html, { comment: true, voidTag: { closingSlash: true } });\n\n for (const el of root.querySelectorAll('script[src]')) {\n const src = el.getAttribute('src') ?? '';\n if (/^data:/i.test(src)) continue;\n findings.push({\n severity: 'error',\n rule: 'not-self-contained',\n message: `<script src=\"${src}\"> — the app rejects bundles with external scripts. Inline it (npx condor-mobile-bridge build does this for you).`,\n });\n }\n\n for (const el of root.querySelectorAll('link[rel=\"stylesheet\"]')) {\n const href = el.getAttribute('href') ?? '';\n if (/^data:/i.test(href)) continue;\n findings.push({\n severity: 'error',\n rule: 'not-self-contained',\n message: `<link rel=\"stylesheet\" href=\"${href}\"> — the app rejects bundles with external stylesheets. Inline it as <style>.`,\n });\n }\n\n for (const el of root.querySelectorAll('img[src]')) {\n const src = el.getAttribute('src') ?? '';\n if (/^data:/i.test(src) || isExternalOrInline(src)) continue;\n findings.push({\n severity: 'warning',\n rule: 'relative-asset',\n message: `<img src=\"${src}\"> is relative and will not resolve inside srcdoc. Embed it as a data: URI.`,\n });\n }\n\n return findings;\n}\n\n/** Sorts findings so every error is reported before any warning. */\nfunction errorsFirst(findings: ValidationFinding[]): ValidationFinding[] {\n return [...findings].sort((a, b) =>\n a.severity === b.severity ? 0 : a.severity === 'error' ? -1 : 1,\n );\n}\n\n/**\n * Validates a built `.zip` — the artifact actually delivered to Condor.\n * Applies the same three hard gates the app applies on download: a single\n * `index.html` at the root, self-contained, and within the size limit.\n */\nexport function validateZipBytes(\n zipBytes: Uint8Array,\n maxBytes: number = DEFAULT_MAX_BYTES,\n): ValidationResult {\n const findings: ValidationFinding[] = [];\n const size = zipBytes.length;\n const hash = `sha256-${createHash('sha256').update(zipBytes).digest('hex')}`;\n\n if (size > maxBytes) {\n findings.push({\n severity: 'error',\n rule: 'too-large',\n message: `The .zip is ${size} bytes, over the ${maxBytes}-byte limit. The app rejects it and falls back to the default screen.`,\n });\n }\n\n let entries: Record<string, Uint8Array>;\n try {\n entries = unzipSync(zipBytes);\n } catch (err) {\n findings.push({\n severity: 'error',\n rule: 'unreadable-zip',\n message: `Could not read the .zip: ${err instanceof Error ? err.message : String(err)}`,\n });\n return { ok: false, entry: null, size, hash, findings: errorsFirst(findings) };\n }\n\n const names = Object.keys(entries).filter((n) => !n.endsWith('/'));\n let entry: string | null = null;\n\n if (names.includes('index.html')) {\n entry = 'index.html';\n } else {\n const firstHtml = names.find((n) => n.toLowerCase().endsWith('.html'));\n if (firstHtml) {\n entry = firstHtml;\n findings.push({\n severity: 'warning',\n rule: 'entry-fallback',\n message: `No index.html at the root; the app would fall back to \"${firstHtml}\". Name the entry index.html and put it at the root.`,\n });\n } else {\n findings.push({\n severity: 'error',\n rule: 'entry-missing',\n message: 'The .zip carries no .html document. The app cannot render this bundle.',\n });\n }\n }\n\n if (entry) {\n const html = Buffer.from(entries[entry] as Uint8Array).toString('utf-8');\n findings.push(...checkSelfContained(html), ...lintClientHtml(html));\n }\n\n const ok = !findings.some((f) => f.severity === 'error');\n return { ok, entry, size, hash, findings: errorsFirst(findings) };\n}\n\n/**\n * Validates an input path. A `.zip` is checked as the delivered artifact; any\n * other path is treated as a pre-build `index.html` and gets the document\n * checks only — there is no size or hash to report yet.\n */\nexport function validateBundle(\n inputPath: string,\n maxBytes: number = DEFAULT_MAX_BYTES,\n): ValidationResult {\n if (extname(inputPath).toLowerCase() === '.zip') {\n return validateZipBytes(readFileSync(inputPath), maxBytes);\n }\n const html = readFileSync(inputPath, 'utf-8');\n const findings = [...checkSelfContained(html), ...lintClientHtml(html)];\n return {\n ok: !findings.some((f) => f.severity === 'error'),\n entry: inputPath,\n size: null,\n hash: null,\n findings: errorsFirst(findings),\n };\n}\n\nexport type CliCommand = 'build' | 'validate' | 'dev';\n\nexport interface CliArgs {\n /** Subcommand. Defaults to `build` so pre-0.19 invocations keep working. */\n command: CliCommand;\n htmlPath: string;\n outPath: string;\n maxBytes: number;\n /** `validate` only: treat warnings as failures (exit code 1). */\n strict: boolean;\n /** `dev` only: harness scenario. */\n scenario: string;\n /** `dev` only: HTTP port. */\n port: number;\n /** `dev` only: overrides the scenario's nativeVersion (`'null'` for null). */\n nativeVersion: string | undefined;\n /** `dev` only: fixed reply latency in ms, instead of the scenario's. */\n latencyMs: number | undefined;\n /** `dev` only: show the floating MOCK panel (`--no-panel` hides it). */\n panel: boolean;\n help: boolean;\n}\n\nconst DEFAULT_OUT = 'dist/whitelabel-bundle.zip';\nexport const DEFAULT_DEV_PORT = 5173;\n\n/**\n * Minimal argv parse (no deps): `--html/-i`, `--out/-o`, `--max-bytes`,\n * `--strict`, `--scenario`, `--port`, `--help/-h`, plus the `build`,\n * `validate` and `dev` subcommands.\n */\nexport function parseArgs(argv: string[]): CliArgs {\n const args: CliArgs = {\n command: 'build',\n htmlPath: '',\n outPath: DEFAULT_OUT,\n maxBytes: DEFAULT_MAX_BYTES,\n strict: false,\n scenario: 'default',\n port: DEFAULT_DEV_PORT,\n nativeVersion: undefined,\n latencyMs: undefined,\n panel: true,\n help: false,\n };\n for (let i = 0; i < argv.length; i++) {\n const token = argv[i];\n switch (token) {\n case '--help':\n case '-h':\n args.help = true;\n break;\n case '--html':\n case '-i':\n case '--input':\n args.htmlPath = argv[++i] ?? '';\n break;\n case '--out':\n case '-o':\n args.outPath = argv[++i] ?? DEFAULT_OUT;\n break;\n case '--max-bytes':\n args.maxBytes = Number.parseInt(argv[++i] ?? '', 10) || DEFAULT_MAX_BYTES;\n break;\n case '--strict':\n args.strict = true;\n break;\n case 'build':\n args.command = 'build';\n break;\n case 'validate':\n args.command = 'validate';\n break;\n case 'dev':\n args.command = 'dev';\n break;\n case '--scenario':\n args.scenario = argv[++i] ?? 'default';\n break;\n case '--native-version':\n args.nativeVersion = argv[++i] ?? 'null';\n break;\n case '--port':\n args.port = Number.parseInt(argv[++i] ?? '', 10) || DEFAULT_DEV_PORT;\n break;\n case '--latency': {\n const ms = Number.parseInt(argv[++i] ?? '', 10);\n args.latencyMs = Number.isFinite(ms) && ms >= 0 ? ms : undefined;\n break;\n }\n case '--no-panel':\n args.panel = false;\n break;\n default:\n // First positional becomes the input (ergonomics: `build dist/index.html`).\n if (token && !token.startsWith('-') && !args.htmlPath) args.htmlPath = token;\n }\n }\n return args;\n}\n\nexport const USAGE = `condor-mobile-bridge — tooling for Condor Canvas client bundles.\n\nUsage:\n npx condor-mobile-bridge build -i <html> [-o <zip>]\n npx condor-mobile-bridge validate -i <zip|html> [--strict]\n npx condor-mobile-bridge canvas publish|list|revoke ... (see: canvas --help)\n npx condor-mobile-bridge dev -i <html|zip|dir> [--scenario <name|file.json>] [--native-version <v|null>]\n [--port <n>] [--latency <ms>] [--no-panel]\n\nCommands:\n build Inline a dist/index.html into a self-contained .zip.\n validate Check a bundle against every rule the Canvas app enforces, plus\n the APIs the sandbox and the CSP block at runtime.\n canvas Publish, list and revoke the bundle of your company in the\n Condor Canvas app (interactive login). See: canvas --help.\n dev Serve the bundle in a browser harness that behaves like the app:\n same sandbox, shell and policy, and a simulated native side.\n Re-reads the input on every reload. Scenarios: default,\n cold-start, no-devices, shared-serial, offline.\n\nOptions:\n -i, --html <path> Input. build: dist/index.html. validate: .zip or .html. Required.\n -o, --out <path> build only: output .zip (default: ${DEFAULT_OUT}).\n --max-bytes <n> Size limit in bytes (default: ${DEFAULT_MAX_BYTES} = 2 MB).\n --strict validate only: exit 1 on warnings too, not just errors.\n --scenario <n> dev only: device behaviour to simulate (default: default),\n or a .json file that extends one (see docs/device-testing.md).\n --port <n> dev only: HTTP port (default: ${DEFAULT_DEV_PORT}).\n --native-version <v|null>\n dev only: nativeVersion the harness reports (default\n 0.18.0). 'null' reproduces a shell that could not\n reach the native side.\n --latency <ms> dev only: fixed reply latency (e.g. 0 for fast tests).\n --no-panel dev only: hide the floating MOCK panel.\n -h, --help Show this help.`;\n","/**\n * Client for the Condor admin API that publishes Canvas bundles — the same\n * endpoints the Condor web admin calls, authenticated the same way (login +\n * optional two-factor code, session in an HTTP-only cookie).\n *\n * The session lives only in this object: it is never written to disk, and each\n * CLI command logs in again. Credentials are passed in by the caller and never\n * logged.\n */\n\n/** API base URLs, as the Condor web apps use them. */\nexport const CANVAS_ADMIN_API = {\n prod: 'https://api.sleepdiary.condorapps.net',\n dev: 'https://dev.sleepdiary.condorapps.net',\n} as const;\n\nexport type CanvasAdminEnv = keyof typeof CANVAS_ADMIN_API;\n\n/** A published bundle, as the API describes it. */\nexport interface CanvasBundleRecord {\n id: number;\n company_id: number;\n company_name?: string;\n version: string;\n bundle_hash: string;\n bundle_size: number | null;\n min_app_version: string | null;\n published_at?: string;\n is_current: boolean;\n revoked: boolean;\n fallback_version?: string | null;\n object_key?: string;\n}\n\n/** An HTTP failure with the server's own reason, when it gave one. */\nexport class CanvasAdminError extends Error {\n constructor(\n message: string,\n readonly status: number,\n readonly reason: string | null,\n ) {\n super(message);\n this.name = 'CanvasAdminError';\n }\n}\n\ntype FetchLike = typeof fetch;\n\nexport interface CanvasAdminClientOptions {\n baseUrl: string;\n userAgent: string;\n fetchImpl?: FetchLike;\n timeoutMs?: number;\n}\n\n/** Extracts `token=<value>` from the response's Set-Cookie headers. */\nfunction tokenFrom(headers: Headers): string | null {\n const values =\n typeof headers.getSetCookie === 'function'\n ? headers.getSetCookie()\n : [headers.get('set-cookie') ?? ''];\n for (const value of values) {\n const match = /(?:^|[,;\\s])token=([^;,\\s]*)/.exec(value);\n if (match?.[1]) return match[1];\n }\n return null;\n}\n\n/**\n * The server answers errors in two shapes: `{status: \"NOK\", error}` from the\n * bundle controllers, and `{message}` / `{description}` from the global\n * authentication handler.\n */\nasync function reasonFrom(response: Response): Promise<string | null> {\n try {\n const body = (await response.json()) as Record<string, unknown>;\n for (const key of ['error', 'message', 'description', 'code']) {\n const value = body[key];\n if (typeof value === 'string' && value) return value;\n }\n } catch {\n // Not JSON: no reason to report.\n }\n return null;\n}\n\nexport class CanvasAdminClient {\n private readonly baseUrl: string;\n private readonly userAgent: string;\n private readonly fetchImpl: FetchLike;\n private readonly timeoutMs: number;\n private token: string | null = null;\n\n constructor(opts: CanvasAdminClientOptions) {\n this.baseUrl = opts.baseUrl.replace(/\\/+$/, '');\n this.userAgent = opts.userAgent;\n this.fetchImpl = opts.fetchImpl ?? fetch;\n this.timeoutMs = opts.timeoutMs ?? 60_000;\n }\n\n private async request(path: string, init: RequestInit = {}): Promise<Response> {\n const headers = new Headers(init.headers);\n headers.set('user-agent', this.userAgent);\n headers.set('accept', 'application/json');\n if (this.token) headers.set('cookie', `token=${this.token}`);\n try {\n return await this.fetchImpl(`${this.baseUrl}${path}`, {\n ...init,\n headers,\n redirect: 'manual',\n signal: AbortSignal.timeout(this.timeoutMs),\n });\n } catch (err) {\n throw new CanvasAdminError(\n `Could not reach ${this.baseUrl}: ${err instanceof Error ? err.message : String(err)}`,\n 0,\n null,\n );\n }\n }\n\n private async fail(response: Response, what: string): Promise<never> {\n const reason = await reasonFrom(response);\n throw new CanvasAdminError(\n `${what} failed (HTTP ${response.status})${reason ? `: ${reason}` : ''}`,\n response.status,\n reason,\n );\n }\n\n /**\n * Logs in. Resolves `{ needsCode: true }` when the company requires a\n * two-factor code (sent by e-mail): call {@link verifyCode} next.\n */\n async login(creds: {\n companyId: number;\n userId: number;\n password: string;\n }): Promise<{ needsCode: boolean }> {\n const response = await this.request('/auth/login', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n company_id: creds.companyId,\n user_id: creds.userId,\n password: creds.password,\n }),\n });\n if (!response.ok) await this.fail(response, 'Login');\n const token = tokenFrom(response.headers);\n if (!token) throw new CanvasAdminError('Login answered without a session cookie', 200, null);\n this.token = token;\n const body = (await response.json().catch(() => ({}))) as { completed_auth_flow?: boolean };\n return { needsCode: body.completed_auth_flow === false };\n }\n\n /** Sends the two-factor (or recovery) code; the server swaps the session cookie. */\n async verifyCode(code: string): Promise<void> {\n const response = await this.request('/auth/verify_2fa', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ otp_code: code }),\n });\n if (!response.ok) await this.fail(response, 'Two-factor verification');\n const token = tokenFrom(response.headers);\n if (token) this.token = token;\n }\n\n /**\n * Publishes a bundle. **It goes live at once** for every user of the\n * company: there is no draft. A version can be published only once per\n * company (HTTP 409).\n */\n async publish(input: {\n zipBytes: Uint8Array;\n version: string;\n companyId?: number;\n minAppVersion?: string;\n }): Promise<CanvasBundleRecord> {\n const form = new FormData();\n form.set(\n 'bundle',\n new Blob([input.zipBytes as BlobPart], { type: 'application/zip' }),\n 'bundle.zip',\n );\n form.set('version', input.version);\n if (input.companyId !== undefined) form.set('company_id', String(input.companyId));\n if (input.minAppVersion) form.set('min_app_version', input.minAppVersion);\n const response = await this.request('/v2/condor_canvas/bundles', {\n method: 'POST',\n body: form,\n });\n if (!response.ok) await this.fail(response, 'Publish');\n const body = (await response.json()) as { data: CanvasBundleRecord };\n return body.data;\n }\n\n /** Lists the bundles of a company (a manager always sees their own). */\n async list(companyId?: number): Promise<CanvasBundleRecord[]> {\n const query = companyId === undefined ? '' : `?company_id=${companyId}`;\n const response = await this.request(`/v2/condor_canvas/bundles${query}`);\n if (!response.ok) await this.fail(response, 'List');\n const body = (await response.json()) as { data: CanvasBundleRecord[] };\n return body.data;\n }\n\n /**\n * Revokes a bundle. Without `fallbackVersion`, revoking the current bundle\n * leaves the company with none: the app shows its default screen.\n */\n async revoke(bundleId: number, fallbackVersion?: string): Promise<Record<string, unknown>> {\n const response = await this.request(`/v2/condor_canvas/bundles/${bundleId}/revoke`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(fallbackVersion ? { fallbackVersion } : {}),\n });\n if (!response.ok) await this.fail(response, 'Revoke');\n const body = (await response.json()) as { data: Record<string, unknown> };\n return body.data;\n }\n}\n","/**\n * Terminal implementation of {@link CanvasIo}: questions on stderr, answers\n * from stdin, secrets without echo.\n *\n * One readline interface serves the whole command. Opening a new one per\n * question lost input in the field: after a `y` + Enter at the confirmation,\n * the next interface (the password prompt) received an empty line at once, and\n * the server answered the empty password with HTTP 400 \"Invalid data\".\n */\nimport { createInterface, type Interface } from 'node:readline';\nimport type { CanvasIo } from './command';\n\nlet rl: Interface | null = null;\n/** While set, only this exact prompt text is echoed — never what is typed. */\nlet mutedPrompt: string | null = null;\n\nfunction reader(): Interface {\n if (rl) return rl;\n rl = createInterface({ input: process.stdin, output: process.stderr, terminal: true });\n const out = rl as unknown as { _writeToOutput: (s: string) => void };\n const write = out._writeToOutput.bind(rl);\n // While a secret is being typed, let the prompt through and echo nothing else.\n out._writeToOutput = (s: string) => {\n if (mutedPrompt === null || s.includes(mutedPrompt)) write(s);\n };\n return rl;\n}\n\nfunction question(prompt: string, secret: boolean): Promise<string> {\n const r = reader();\n return new Promise((resolve) => {\n mutedPrompt = secret ? prompt : null;\n r.question(prompt, (answer) => {\n if (secret) {\n mutedPrompt = null;\n process.stderr.write('\\n');\n }\n resolve(answer);\n });\n });\n}\n\nexport function terminalIo(): CanvasIo {\n return {\n interactive: Boolean(process.stdin.isTTY),\n ask: (q) => question(q, false),\n askSecret: (q) => question(q, true),\n print: (line) => console.log(line),\n printError: (line) => console.error(line),\n };\n}\n\n/** Releases stdin so the process can exit. */\nexport function closeTerminalIo(): void {\n rl?.close();\n rl = null;\n}\n","/**\n * cli-dev.ts — the `dev` subcommand: a local HTTP server hosting the bundle in\n * the development harness (`src/dev/harness.ts`).\n *\n * The page it serves is the **shell** frame, with the app's CSP; the bundle\n * runs inside it in `<iframe sandbox=\"allow-scripts\">`, so the network, storage\n * and API restrictions of a device apply in the browser too. The input is\n * re-read on every request: rebuild, reload, done.\n */\nimport { existsSync, readFileSync, statSync } from 'node:fs';\nimport { createServer, type Server } from 'node:http';\nimport { dirname, extname, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { strFromU8, unzipSync } from 'fflate';\nimport { inlineHtml } from './cli-core';\nimport { CANVAS_CSP } from './dev/policy';\nimport { SCENARIOS } from './dev/scenarios';\n\n/**\n * Reads the bundle's `index.html` from a file, a `dist/` directory or a `.zip`.\n * An HTML file is inlined exactly as `build` does (scripts, styles, media), so\n * a `dist/` with `index.html` + `main.js` runs as it will in the `.zip`.\n */\nexport function readClientHtml(input: string): string {\n const path = resolve(input);\n if (existsSync(path) && statSync(path).isDirectory()) {\n return inlineHtml(join(path, 'index.html'));\n }\n if (extname(path).toLowerCase() === '.zip') {\n const files = unzipSync(new Uint8Array(readFileSync(path)));\n const entry = files['index.html'];\n if (!entry) throw new Error(`${input}: no index.html at the root of the zip.`);\n return strFromU8(entry);\n }\n return inlineHtml(path);\n}\n\nconst escapeHtml = (s: string): string =>\n s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;');\n\n/** The harness page. Exported for tests. */\n/**\n * The harness page. `nativeVersion` overrides the scenario's (`'null'` → null).\n * Exported for tests.\n */\nexport function harnessPage(\n scenario: string,\n nativeVersion?: string,\n extra: { latencyMs?: number; panel?: boolean; scenarioFile?: string } = {},\n): string {\n const options = Object.entries(SCENARIOS)\n .map(\n ([name, s]) =>\n `<option value=\"${name}\" title=\"${escapeHtml(s.description)}\"${\n name === scenario ? ' selected' : ''\n }>${name}</option>`,\n )\n .join('');\n const description = escapeHtml(\n extra.scenarioFile\n ? `Scenario file ${extra.scenarioFile}`\n : (SCENARIOS[scenario as keyof typeof SCENARIOS]?.description ?? ''),\n );\n const defaultsJson = JSON.stringify({\n latencyMs: extra.latencyMs ?? null,\n panel: extra.panel !== false,\n scenarioFile: Boolean(extra.scenarioFile),\n }).replace(/</g, '\\\\u003c');\n // JSON in an inline script: escape every `<` so no `</script>` or `<!--` can close it.\n const scenarioJson = JSON.stringify(scenario).replace(/</g, '\\\\u003c');\n const nativeJson =\n JSON.stringify(\n nativeVersion === undefined ? undefined : nativeVersion === 'null' ? null : nativeVersion,\n )?.replace(/</g, '\\\\u003c') ?? 'undefined';\n const nativeLabel = escapeHtml(\n nativeVersion === undefined ? '' : `nativeVersion ${nativeVersion}`,\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<meta http-equiv=\"Content-Security-Policy\" content=\"${CANVAS_CSP}\">\n<title>Condor Canvas — dev harness</title>\n<style>\n body { margin: 0; font: 13px system-ui, sans-serif; background: #e9ebef; color: #1b1f24; }\n header { display: flex; gap: 12px; align-items: center; padding: 8px 16px; background: #1b1f24; color: #fff; flex-wrap: wrap; }\n header p { margin: 0; opacity: .8; flex: 1 1 320px; }\n main { display: flex; justify-content: center; padding: 16px; }\n #device { width: 390px; height: 780px; max-width: 100%; border-radius: 28px; overflow: hidden; box-shadow: 0 10px 40px rgba(0,0,0,.25); background: #fff; }\n</style>\n</head>\n<body>\n<header>\n <strong>Condor Canvas · dev harness</strong>\n <label>Scenario <select id=\"scenario\">${options}</select></label>\n <button id=\"back\" type=\"button\" title=\"The OS back button: reaches the bundle only after it reported canGoBack true\">◀ OS back</button>\n <span>${nativeLabel}</span>\n <p>${description}</p>\n</header>\n<main><div id=\"device\"></div></main>\n<script src=\"/__condor/harness.js\"></script>\n<script>\n var scenario = ${scenarioJson};\n var nativeVersion = ${nativeJson};\n var defaults = ${defaultsJson};\n var params = new URLSearchParams(location.search);\n var latency = params.has('latency') ? Number(params.get('latency')) : defaults.latencyMs;\n var panel = params.has('panel') ? params.get('panel') !== '0' : defaults.panel;\n var useFile = defaults.scenarioFile && !params.has('scenario');\n var harness = null;\n document.getElementById('scenario').addEventListener('change', function (e) {\n var params = new URLSearchParams(location.search);\n params.set('scenario', e.target.value);\n location.search = params.toString();\n });\n document.getElementById('back').addEventListener('click', function () {\n if (harness) harness.pressBack();\n });\n // A scenario file extends a named scenario: its fields replace the base's,\n // systemStatus is merged, and devices is the userDevice payload to answer.\n function fromFile(file) {\n var base = CondorDevHarness.SCENARIOS[file.extends || 'default'];\n if (!base) throw new Error('scenario file: unknown \"extends\": ' + file.extends);\n var out = Object.assign({}, base, file);\n out.systemStatus = Object.assign({}, base.systemStatus, file.systemStatus || {});\n if (file.devices) {\n var devices = JSON.stringify(file.devices);\n out.devices = function () { return JSON.parse(devices); };\n } else out.devices = base.devices;\n return out;\n }\n var chosen = null;\n (useFile ? fetch('/__condor/scenario.json').then(function (r) { return r.json(); }).then(fromFile)\n : Promise.resolve(CondorDevHarness.SCENARIOS[scenario]))\n .then(function (s) {\n chosen = s;\n if (nativeVersion !== undefined) chosen = Object.assign({}, chosen, { nativeVersion: nativeVersion });\n if (latency !== null && latency >= 0) chosen = Object.assign({}, chosen, { latencyMs: latency });\n return fetch('/__condor/client.html');\n })\n .then(function (r) { if (!r.ok) throw new Error('could not read the bundle: HTTP ' + r.status); return r.text(); })\n .then(function (html) {\n return CondorDevHarness.createDevHarness({ html: html, scenario: chosen, mount: document.getElementById('device'), devPanel: panel });\n })\n .then(function (h) {\n harness = h;\n var confirm = new URLSearchParams(location.search).get('confirm');\n if (confirm === 'accept' || confirm === 'decline') h.setConfirmations(confirm);\n // Scripted tests (Puppeteer, Playwright) drive the simulated app here.\n // See docs/device-testing.md, \"Scripted tests\".\n window.condorDev = {\n scenario: h.scenario,\n get log() { return h.log.slice(); },\n // true once the bundle is mounted AND its handshake was answered (or,\n // with nativeVersion null, left unanswered on purpose).\n get ready() {\n return h.log.some(function (e) {\n return (e.kind === 'reply' && e.key === 'handshake-ack') || /handshake unanswered/.test(e.detail);\n });\n },\n emit: function (key, payload) { h.emit(key, payload); },\n setStatus: function (patch) { h.setStatus(patch); },\n setConnected: function (deviceId, connected) { h.setConnected(deviceId, connected); },\n setConfirmations: function (mode) { h.setConfirmations(mode); },\n pressBack: function () { h.pressBack(); }\n };\n })\n .catch(function (err) { document.getElementById('device').textContent = String(err); console.error(err); });\n</script>\n</body>\n</html>`;\n}\n\n/** Path of the IIFE harness build, next to the compiled CLI in `dist/`. */\nfunction harnessScriptPath(): string {\n const here =\n typeof __dirname !== 'undefined' ? __dirname : dirname(fileURLToPath(import.meta.url));\n return join(here, 'dev-harness.global.js');\n}\n\nexport function startDevServer(opts: {\n input: string;\n scenario: string;\n port: number;\n nativeVersion?: string;\n latencyMs?: number;\n panel?: boolean;\n}): Server {\n const scenarioFile = opts.scenario.endsWith('.json') ? resolve(opts.scenario) : undefined;\n if (scenarioFile) {\n // Fail before listening if the file is missing or not JSON.\n JSON.parse(readFileSync(scenarioFile, 'utf-8'));\n } else if (!(opts.scenario in SCENARIOS)) {\n throw new Error(\n `Unknown scenario \"${opts.scenario}\". Known: ${Object.keys(SCENARIOS).join(', ')}.`,\n );\n }\n // Fail before listening if the input cannot be read.\n readClientHtml(opts.input);\n\n const server = createServer((req, res) => {\n const url = new URL(req.url ?? '/', 'http://localhost');\n try {\n if (url.pathname === '/__condor/client.html') {\n res.writeHead(200, {\n 'content-type': 'text/html; charset=utf-8',\n 'cache-control': 'no-store',\n });\n res.end(readClientHtml(opts.input));\n return;\n }\n if (url.pathname === '/__condor/scenario.json' && scenarioFile) {\n res.writeHead(200, {\n 'content-type': 'application/json; charset=utf-8',\n 'cache-control': 'no-store',\n });\n res.end(readFileSync(scenarioFile));\n return;\n }\n if (url.pathname === '/__condor/harness.js') {\n res.writeHead(200, {\n 'content-type': 'text/javascript; charset=utf-8',\n 'cache-control': 'no-store',\n });\n res.end(readFileSync(harnessScriptPath()));\n return;\n }\n if (url.pathname === '/') {\n const named = url.searchParams.get('scenario');\n const scenario = named ?? (scenarioFile ? 'default' : opts.scenario);\n res.writeHead(200, {\n 'content-type': 'text/html; charset=utf-8',\n 'content-security-policy': CANVAS_CSP,\n 'cache-control': 'no-store',\n });\n const nativeVersion = url.searchParams.get('nativeVersion') ?? opts.nativeVersion;\n res.end(\n harnessPage(scenario in SCENARIOS ? scenario : 'default', nativeVersion, {\n latencyMs: opts.latencyMs,\n panel: opts.panel,\n scenarioFile: named ? undefined : scenarioFile && opts.scenario,\n }),\n );\n return;\n }\n res.writeHead(404).end();\n } catch (err) {\n res.writeHead(500, { 'content-type': 'text/plain; charset=utf-8' });\n res.end(String(err instanceof Error ? err.message : err));\n }\n });\n server.listen(opts.port, '127.0.0.1');\n return server;\n}\n","/**\n * Catalogue of the JS↔Native bridge keys.\n *\n * Mirrors the JS-facing side of `ForegroundComCoreKeys` in `condor_core`.\n * The strings here are what JavaScript receives through\n * `window.CondorBridge.emit(key, payload)`.\n *\n * Thirteen keys exist. A sandboxed client can subscribe to twelve of them:\n * `dfu` is denied by the shell policy and skipped natively. See\n * `docs/bridge-contract.md` for what each one carries and when it is emitted.\n */\nexport const BRIDGE_KEYS = {\n pingPong: 'pingPong',\n userDevice: 'userDevice',\n deviceLightSensors: 'deviceLightSensors',\n syncTime: 'syncTime',\n realTime: 'realTime',\n deleteMemory: 'deleteMemory',\n deviceNotification: 'deviceNotification',\n deviceConnection: 'deviceConnection',\n sensorLog: 'sensorLog',\n dfu: 'dfu',\n systemStatus: 'systemStatus',\n /**\n * Native → client: the OS back button was pressed and the client had\n * reported internal history via `reportNavigation(true)`. Go back exactly\n * ONE internal page. Emitted only when the native side decided to delegate\n * the back gesture. See `BridgeClient.onBack`.\n */\n navigationBack: 'navigationBack',\n /**\n * Native → client: the app's language state. Carries the raw device locale\n * AND the language explicitly chosen in the app Settings, so the client can\n * apply its own precedence rule — including rendering a language the native\n * chrome does not support. Emitted on attach, after `handshake-ack`, and on\n * every change in Settings. See `LanguagePayload`.\n */\n language: 'language',\n} as const;\n\nexport type BridgeKey = (typeof BRIDGE_KEYS)[keyof typeof BRIDGE_KEYS];\n","/**\n * The shell policy of the Condor Canvas app, reproduced for the development\n * harness so a command the app refuses is refused in the browser too.\n *\n * Mirrors `apps/canvas/web-mock-src/src/shell.ts` (see\n * `contract/bridge-contract.json` → `verifiedAgainst.sources.shellPolicy`).\n * `tests/dev-harness.test.ts` derives the same lists from the contract and\n * fails if they drift.\n */\n\nimport { BRIDGE_KEYS } from '../keys';\nimport type { ShellPolicy } from '../shell-messages';\n\nexport const CANVAS_POLICY: ShellPolicy = {\n readOnlyKeys: [\n BRIDGE_KEYS.userDevice,\n BRIDGE_KEYS.deviceConnection,\n BRIDGE_KEYS.deviceLightSensors,\n BRIDGE_KEYS.deviceNotification,\n BRIDGE_KEYS.pingPong,\n BRIDGE_KEYS.realTime,\n BRIDGE_KEYS.syncTime,\n BRIDGE_KEYS.deleteMemory,\n BRIDGE_KEYS.sensorLog,\n BRIDGE_KEYS.systemStatus,\n BRIDGE_KEYS.navigationBack,\n BRIDGE_KEYS.language,\n ],\n allowedCommands: [\n 'userDevice',\n 'realTime',\n 'openSettings',\n 'deviceConnection',\n 'ping',\n 'jsError',\n 'sensorLog',\n 'setStatusBarColor',\n 'requestPermission',\n 'navigationState',\n 'language',\n ],\n requiresConfirmation: ['syncTime', 'deleteMemory', 'logout'],\n deniedCommands: ['dfu'],\n};\n\n/** CSP of the app shell, inherited by the client's `srcdoc` iframe. */\nexport const CANVAS_CSP =\n \"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; \" +\n \"img-src 'self' data:; font-src 'self' data:; connect-src 'self'; frame-src 'self'; \" +\n \"object-src 'none'; base-uri 'none'; form-action 'none';\";\n","/**\n * Realistic fixture data for the development harness.\n *\n * Shapes follow the typed payloads in `../payloads`, which mirror the Dart\n * `toMap()` of `condor_core`. Timestamps that the device produces (`log_time`)\n * are **device-local** wall-clock strings, built with local `Date` getters —\n * never `toISOString()`.\n */\n\nimport type {\n DeviceConfigItem,\n DeviceLightSensorsPayload,\n DeviceTelemetryItem,\n DeviceUserConfigItem,\n SensorLogItem,\n SensorLogPayload,\n SensorLogQueryPayload,\n SystemStatusPayload,\n UserDevicePayload,\n} from '../payloads';\n\n/** Default log period of an ActLumus, in seconds. Programmable per device. */\nexport const DEFAULT_LOG_PERIOD_S = 60;\n\nconst pad = (n: number): string => String(n).padStart(2, '0');\n\n/** `YYYY-MM-DD HH:MM:SS` in local time — the format of `log_time`. */\nexport function formatLocalTimestamp(date: Date): string {\n return (\n `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ` +\n `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`\n );\n}\n\n/** Parses a local `YYYY-MM-DD HH:MM:SS` back to epoch ms. */\nexport function parseLocalTimestamp(value: string): number {\n const m = /^(\\d{4})-(\\d{2})-(\\d{2}) (\\d{2}):(\\d{2}):(\\d{2})$/.exec(value);\n if (!m) return Number.NaN;\n const [, y, mo, d, h, mi, s] = m.map(Number) as number[];\n return new Date(y ?? 0, (mo ?? 1) - 1, d ?? 1, h ?? 0, mi ?? 0, s ?? 0).getTime();\n}\n\ninterface DeviceSpec {\n deviceId: number;\n serialNo: number;\n modelId?: DeviceConfigItem['modelId'];\n bodyPart: DeviceConfigItem['deviceBodyPart'];\n name: string;\n battery: number;\n charging: boolean;\n syncedMinutesAgo: number;\n syncError?: string;\n}\n\nfunction config(spec: DeviceSpec): DeviceConfigItem {\n const item: DeviceConfigItem = {\n deviceId: spec.deviceId,\n serialNo: spec.serialNo,\n deviceBodyPart: spec.bodyPart,\n bleConnectionPeriod: 5,\n autosync: true,\n eraseDeviceMemory: false,\n autoEraseDeviceMemory: false,\n autoEraseDeviceMemoryThreshold: 80,\n readSensorsMemory: false,\n startDevice: false,\n readFullMemory: false,\n allowFirmwareUpdate: true,\n };\n if (spec.modelId) item.modelId = spec.modelId;\n return item;\n}\n\nfunction telemetry(spec: DeviceSpec, now: number): DeviceTelemetryItem {\n return {\n serialNo: spec.serialNo,\n deviceId: spec.deviceId,\n updataDateTime: now - spec.syncedMinutesAgo * 60_000,\n deviceName: spec.name,\n deviceVersion: spec.modelId === 'actlumus_plus' ? '3.0.2' : '2.1.0',\n hardwareVersion: spec.modelId === 'actlumus_plus' ? 'B' : 'A',\n deviceDateTime: now - spec.syncedMinutesAgo * 60_000,\n memoryDataUsage: 37.5,\n memorySystemUsage: 4.2,\n batteryLevel: spec.battery,\n batteryVoltage: 3.7 + spec.battery / 1000,\n isCharging: spec.charging,\n isOnDock: spec.charging,\n syncErrors: spec.syncError\n ? [\n {\n serialNo: spec.serialNo,\n attemptDate: now - 10 * 60_000,\n message: spec.syncError,\n messageCode: 'send_data_log_error',\n },\n ]\n : [],\n };\n}\n\nfunction userConfig(spec: DeviceSpec): DeviceUserConfigItem {\n return {\n serialNo: spec.serialNo,\n deviceId: spec.deviceId,\n syncTime: false,\n realTime: false,\n deleteMemory: false,\n readAllMemory: false,\n readAllSystemLog: false,\n };\n}\n\nfunction payloadOf(specs: DeviceSpec[], now = Date.now()): UserDevicePayload {\n return {\n lastUpdate: now,\n devicesConfig: specs.map(config),\n devicesTelemetry: specs.map((s) => telemetry(s, now)),\n devicesUserConfig: specs.map(userConfig),\n };\n}\n\n/** Two devices with distinct serial numbers: the common case. */\nexport function twoDevices(now = Date.now()): UserDevicePayload {\n return payloadOf(\n [\n {\n deviceId: 9001,\n serialNo: 4534,\n modelId: 'actlumus',\n bodyPart: 'wrist',\n name: 'ActLumus 4534',\n battery: 82,\n charging: false,\n syncedMinutesAgo: 12,\n },\n {\n deviceId: 9002,\n serialNo: 5120,\n modelId: 'actlumus_plus',\n bodyPart: 'necklace',\n name: 'ActLumus Plus 5120',\n battery: 41,\n charging: true,\n syncedMinutesAgo: 190,\n syncError: 'Could not read the device memory.',\n },\n ],\n now,\n );\n}\n\n/**\n * An ActLumus and an ActLumus Plus sharing serial number 7047 — real data\n * looked like this for the first pilot account. Only `deviceId` tells them\n * apart.\n */\nexport function sharedSerial(now = Date.now()): UserDevicePayload {\n return payloadOf(\n [\n {\n deviceId: 9101,\n serialNo: 7047,\n bodyPart: 'wrist',\n name: 'ActLumus 7047',\n battery: 67,\n charging: false,\n syncedMinutesAgo: 30,\n },\n {\n deviceId: 9102,\n serialNo: 7047,\n modelId: 'actlumus_plus',\n bodyPart: 'necklace',\n name: 'ActLumus Plus 7047',\n battery: 93,\n charging: false,\n syncedMinutesAgo: 8,\n },\n ],\n now,\n );\n}\n\nexport function systemStatus(overrides: Partial<SystemStatusPayload> = {}): SystemStatusPayload {\n return {\n bluetooth: true,\n internet: true,\n permission: true,\n hasDevices: true,\n hasDevicesKnown: true,\n missingOptionalPermissions: [],\n missingEssentialPermissions: [],\n deviceSyncError: {},\n deviceSyncErrorById: {},\n ...overrides,\n };\n}\n\n/** Light follows a daylight curve so charts look like real data. */\nfunction lightAt(epochMs: number, serialNo: number): { photopic: number; melanopic: number } {\n const d = new Date(epochMs);\n const hour = d.getHours() + d.getMinutes() / 60;\n const daylight = Math.max(0, Math.sin(((hour - 6) / 12) * Math.PI));\n const noise = ((epochMs / 60_000 + serialNo) % 7) / 7;\n const photopic = Math.round((daylight * 1800 + noise * 40) * 10) / 10;\n return { photopic, melanopic: Math.round(photopic * 0.62 * 10) / 10 };\n}\n\nfunction logAt(\n epochMs: number,\n serialNo: number,\n deviceId: number,\n bodyPart: SensorLogItem['body_part'],\n docked: boolean,\n): SensorLogItem {\n const { photopic, melanopic } = docked\n ? { photopic: 0, melanopic: 0 }\n : lightAt(epochMs, serialNo);\n return {\n id: Math.floor(epochMs / 1000) * 10 + (serialNo % 10),\n log_time: formatLocalTimestamp(new Date(epochMs)),\n log_type: 1,\n body_part: bodyPart,\n // The device's raw wear flag; the name misleads: 1 = worn, 0 = on the dock.\n off_wrist: docked ? 0 : 1,\n temperature: 31.5,\n lux_photopic: photopic,\n lux_melanopic: melanopic,\n device_id: deviceId,\n act_data_pim: 120,\n };\n}\n\n/** Spreads a scalar over a fake spectrum peaking near 555 nm. */\nfunction spectrum(nm: readonly number[], photopic: number): Record<string, number> {\n const out: Record<string, number> = {};\n for (const w of nm) {\n const weight = Math.exp(-(((w - 555) / 120) ** 2));\n out[`wavelength${w}`] = Math.round(photopic * weight * 0.01 * 1e4) / 1e4;\n }\n return out;\n}\n\nconst GEN1_NM = [390, 415, 445, 480, 515, 555, 590, 630, 680, 750] as const;\nconst GEN2_NM = [395, 426, 457, 488, 520, 551, 582, 613, 644, 675, 707, 738, 769, 800] as const;\n\n/**\n * One `deviceLightSensors` reading for a device, shaped by its generation\n * (`modelId` `'actlumus_plus'` → `type: 'actlumus2'`). The harness emits one\n * per second while `realTime` is on for that device. Values are plausible,\n * not measured.\n */\nexport function lightSensorsReading(\n device: Pick<DeviceConfigItem, 'serialNo' | 'deviceId' | 'modelId'>,\n epochMs = Date.now(),\n withDeviceId = true,\n): DeviceLightSensorsPayload {\n const { photopic, melanopic } = lightAt(epochMs, device.serialNo);\n const raw = {\n light_sensor_f1: 0.01,\n light_sensor_f2: 0.02,\n light_sensor_f3: 0.03,\n light_sensor_f4: 0.04,\n light_sensor_f5: 0.05,\n light_sensor_f6: 0.04,\n light_sensor_f7: 0.03,\n light_sensor_f8: 0.02,\n light_sensor_clear: Math.round(photopic * 0.0002 * 1e4) / 1e4,\n light_sensor_nir: 0.01,\n };\n const address = {\n serialNo: device.serialNo,\n ...(withDeviceId ? { deviceId: device.deviceId } : {}),\n };\n if (device.modelId === 'actlumus_plus') {\n return {\n ...address,\n type: 'actlumus2',\n data: {\n ...raw,\n ...(spectrum(GEN2_NM, photopic) as Record<`wavelength${(typeof GEN2_NM)[number]}`, number>),\n time: epochMs,\n photopic,\n melanopic,\n light_sensor_z: 0.009,\n light_sensor_y: 0.038,\n light_sensor_x: 0.044,\n light_sensor_fd: 0,\n },\n };\n }\n return {\n ...address,\n type: 'actlumus',\n data: {\n ...raw,\n ...(spectrum(GEN1_NM, photopic) as Record<`wavelength${(typeof GEN1_NM)[number]}`, number>),\n time: epochMs,\n photopic,\n melanopic,\n light_sensor_z: null,\n light_sensor_y: null,\n light_sensor_x: null,\n light_sensor_fd: null,\n },\n };\n}\n\n/**\n * What the simulated history contains. The default: 14 days up to now, one log\n * per `logPeriodS`, worn all day except the dock hour 07:00–08:00 (local), no\n * gaps.\n */\nexport interface HistoryOptions {\n /** Days of history before now. Default 14. */\n days?: number;\n /** Local hours (0–23) the device sits on its dock: `off_wrist` 0, no light. Default `[7]`. */\n dockHours?: number[];\n /** Local hours (0–23) with no log at all, every day (device off, or memory full). */\n gapHours?: number[];\n /** Local dates (`YYYY-MM-DD`) with no log at all. */\n gapDates?: string[];\n}\n\nconst HISTORY_DAYS = 14;\nconst encodeCursor = (epochMs: number): string => `mock:${epochMs}`;\nconst decodeCursor = (cursor: string): number => Number(cursor.slice('mock:'.length));\n\n/**\n * Answers a `sensorLog` query with the backend's paging semantics:\n * - no range: the most recent `limit` logs; older pages through `endingBefore`;\n * - with `startTime`: the oldest logs of the range first; newer pages through\n * `startingAfter` (with only `endTime`: the most recent before it);\n * - `logs` always chronological ascending; `hasMore` / `hasPrevious` computed\n * within the range.\n */\nexport function sensorLogPage(\n query: SensorLogQueryPayload,\n opts: {\n now?: number;\n periodS?: number;\n deviceId?: number;\n /** `body_part` of every log: the device's `deviceBodyPart`. Default `'wrist'`. */\n bodyPart?: SensorLogItem['body_part'];\n history?: HistoryOptions;\n } = {},\n): SensorLogPayload {\n const now = opts.now ?? Date.now();\n const periodMs = (opts.periodS ?? DEFAULT_LOG_PERIOD_S) * 1000;\n const limit = query.limit ?? 10;\n if (limit < 1 || limit > 1000) {\n return { requestId: query.requestId, success: false, error: 'INVALID_LIMIT' };\n }\n if (query.startingAfter && query.endingBefore) {\n return { requestId: query.requestId, success: false, error: 'CONFLICTING_CURSORS' };\n }\n\n const newest = Math.floor(now / periodMs) * periodMs;\n const history = opts.history ?? {};\n const oldest = newest - (history.days ?? HISTORY_DAYS) * 86_400_000;\n const dock = new Set(history.dockHours ?? [7]);\n const gapHours = new Set(history.gapHours ?? []);\n const gapDates = new Set(history.gapDates ?? []);\n const present = (t: number): boolean => {\n const d = new Date(t);\n return !gapHours.has(d.getHours()) && !gapDates.has(formatLocalTimestamp(d).slice(0, 10));\n };\n const rangeStart = query.startTime ? parseLocalTimestamp(query.startTime) : oldest;\n const rangeEnd = query.endTime ? parseLocalTimestamp(query.endTime) - 1 : newest;\n if (Number.isNaN(rangeStart) || Number.isNaN(rangeEnd) || rangeStart > rangeEnd + 1) {\n return { requestId: query.requestId, success: false, error: 'INVALID_TIME_RANGE' };\n }\n const first = Math.max(oldest, Math.ceil(rangeStart / periodMs) * periodMs);\n const last = Math.min(newest, Math.floor(rangeEnd / periodMs) * periodMs);\n\n // Walk the slots of the range, skipping gaps, so a page always holds up to\n // `limit` logs and the cursors behave like the backend's.\n const walk = (start: number, step: number, max: number): number[] => {\n const out: number[] = [];\n for (let t = start; t >= first && t <= last && out.length < max; t += step) {\n if (present(t)) out.push(t);\n }\n return out;\n };\n let slots: number[];\n if (query.endingBefore) {\n slots = walk(\n Math.min(last, decodeCursor(query.endingBefore) - periodMs),\n -periodMs,\n limit,\n ).reverse();\n } else if (query.startingAfter) {\n slots = walk(Math.max(first, decodeCursor(query.startingAfter) + periodMs), periodMs, limit);\n } else if (query.startTime) {\n slots = walk(first, periodMs, limit);\n } else {\n slots = walk(last, -periodMs, limit).reverse();\n }\n const bodyPart = opts.bodyPart ?? 'wrist';\n const logs: SensorLogItem[] = slots.map((t) =>\n logAt(t, query.serialNo, opts.deviceId ?? 0, bodyPart, dock.has(new Date(t).getHours())),\n );\n const from = slots[0] ?? 0;\n const to = slots[slots.length - 1] ?? 0;\n return {\n requestId: query.requestId,\n success: true,\n logs,\n startingAfter: logs.length ? encodeCursor(to) : null,\n endingBefore: logs.length ? encodeCursor(from) : null,\n hasMore: logs.length > 0 && walk(to + periodMs, periodMs, 1).length > 0,\n hasPrevious: logs.length > 0 && walk(from - periodMs, -periodMs, 1).length > 0,\n };\n}\n","/**\n * Scenarios for the development harness — the device behaviours that a plain\n * \"answer everything at once\" mock hides.\n *\n * Each one reproduces something the Condor Canvas app really does, so a bundle\n * that survives every scenario in the browser survives them on a device.\n */\n\nimport type { SystemStatusPayload, UserDevicePayload } from '../payloads';\nimport type { HistoryOptions } from './fixtures';\nimport { sharedSerial, systemStatus, twoDevices } from './fixtures';\n\n/** Native schema version the harness reports by default. */\nexport const HARNESS_NATIVE_VERSION = '0.18.0';\n\nexport interface Scenario {\n /** Shown in the harness toolbar. */\n description: string;\n /**\n * Relayed to the client by the handshake. `null` reproduces a shell that\n * could not reach the native side: the client receives `nativeVersion: null`.\n */\n nativeVersion: string | null;\n /** Delay of every native reply, in ms. A pair draws uniformly from the range. */\n latencyMs: number | [min: number, max: number];\n /**\n * Time until the app's background service is up, in ms after the page loads.\n * Until then `ping` and `userDevice` are **dropped without a reply** — on\n * the device they travel to that service, and a message sent before it runs\n * is lost.\n */\n serviceStartMs: number;\n /**\n * The device lookup fails with this code (`NO_DEVICES`, `NETWORK`, …). From\n * nativeVersion 0.18.0 the app answers `userDevice` with empty arrays and\n * `error`; below it, the bundle gets no reply at all.\n */\n userDeviceError?: string;\n /** When set, every `sensorLog` reply fails with this `error`. */\n sensorLogError?: string;\n /** The Nth `sensorLog` request (1-based) fails once with `Connection failed`. */\n sensorLogFailOnRequest?: number;\n /** What the simulated history contains — see `HistoryOptions`. */\n history?: HistoryOptions;\n /**\n * After this many ms the first device starts a BLE session (a\n * `deviceConnection` with `isConnect: true`). Only `default` sets it; absent,\n * no device connects by itself (a scenario file can pass `null`).\n */\n firstSessionAfterMs?: number | null;\n /**\n * The pipeline stage at which every simulated BLE session fails, e.g.\n * `'get_data_log'`: `deviceNotification` then ends with `<stage>_error`\n * instead of `pipeline_done_notification`. Without internet the sessions\n * fail at `'send_data_log'` anyway.\n */\n sessionFailAt?: string;\n /** Log period of the devices, in seconds (programmable; default 60). */\n logPeriodS: number;\n systemStatus: SystemStatusPayload;\n devices: () => UserDevicePayload;\n}\n\nexport type ScenarioName = 'default' | 'cold-start' | 'no-devices' | 'shared-serial' | 'offline';\n\nconst base: Scenario = {\n description:\n 'Two devices, background service already running, realistic latency. The first ' +\n 'device starts a BLE session 3 s after load; the second stays between sessions.',\n nativeVersion: HARNESS_NATIVE_VERSION,\n latencyMs: [80, 250],\n serviceStartMs: 0,\n logPeriodS: 60,\n systemStatus: systemStatus(),\n devices: () => twoDevices(),\n};\n\nexport const SCENARIOS: Record<ScenarioName, Scenario> = {\n default: { ...base, firstSessionAfterMs: 3000 },\n 'cold-start': {\n ...base,\n description:\n 'App launched from cold: the background service takes 4 s to start, and ' +\n 'every ping/userDevice sent before that is lost. From nativeVersion 0.18.0 ' +\n 'the app re-sends the lost userDevice itself; below it, a bundle that asks ' +\n 'once stays on its loading screen forever.',\n serviceStartMs: 4000,\n },\n 'no-devices': {\n ...base,\n description:\n 'Account with no linked device: userDevice answers with error NO_DEVICES ' +\n '(below nativeVersion 0.18.0: never answers) and systemStatus.hasDevices ' +\n 'is false. Your bundle must show an empty state.',\n userDeviceError: 'NO_DEVICES',\n systemStatus: systemStatus({ hasDevices: false }),\n },\n 'shared-serial': {\n ...base,\n description:\n 'An ActLumus and an ActLumus Plus share serial number 7047. Only deviceId ' +\n 'tells them apart; sensorLog cannot address one of them.',\n devices: () => sharedSerial(),\n },\n offline: {\n ...base,\n description:\n 'No internet. systemStatus.internet is false and sensorLog fails; ' +\n 'userDevice answers from the cache. The internet comes back when a test calls ' +\n 'window.condorDev.setStatus({ internet: true }).',\n systemStatus: systemStatus({ internet: false, hasDevicesKnown: false }),\n },\n};\n\nexport function resolveScenario(scenario: ScenarioName | Scenario | undefined): Scenario {\n if (scenario === undefined) return SCENARIOS.default;\n if (typeof scenario === 'string') {\n const found = SCENARIOS[scenario];\n if (!found) {\n throw new Error(\n `condor-mobile-bridge dev: unknown scenario \"${scenario}\". ` +\n `Known: ${Object.keys(SCENARIOS).join(', ')}.`,\n );\n }\n return found;\n }\n return scenario;\n}\n"],"mappings":";;;AAMA,SAAS,WAAW,qBAAqB;AACzC,SAAS,WAAAA,UAAS,WAAAC,gBAAe;;;ACL/B,cAAW;;;ACWb,SAAS,cAAAC,mBAAkB;AAC3B,SAAS,YAAY,gBAAAC,eAAc,gBAAgB;AACnD,SAAS,WAAAC,UAAS,MAAM,WAAAC,gBAAe;;;ACMvC,SAAS,kBAAkB;AAC3B,SAAS,oBAAoB;AAC7B,SAAS,SAAS,SAAS,eAAe;AAC1C,SAAS,SAAS,WAAW,eAAe;AAC5C,SAA2B,aAAa;AAGjC,IAAM,oBAAoB,IAAI,OAAO;AAG5C,IAAM,cAAc,oBAAI,KAAK,sBAAsB;AAG5C,IAAM,aAAqC;AAAA,EAChD,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,SAAS;AACX;AAGA,SAAS,mBAAmB,KAAsB;AAChD,SAAO,mBAAmB,KAAK,GAAG,KAAK,UAAU,KAAK,GAAG,KAAK,qBAAqB,KAAK,GAAG;AAC7F;AAGA,SAAS,eAAe,KAAqB;AAC3C,SAAO,IAAI,MAAM,MAAM,EAAE,CAAC,KAAK;AACjC;AAUA,SAAS,aAAa,UAAkB,aAAqB,KAAqB;AAChF,QAAM,QAAQ,eAAe,GAAG;AAChC,SAAO,MAAM,WAAW,GAAG,IAAI,QAAQ,UAAU,IAAI,KAAK,EAAE,IAAI,QAAQ,aAAa,KAAK;AAC5F;AAGO,SAAS,UAAU,UAA0B;AAClD,QAAM,QAAQ,aAAa,QAAQ;AACnC,QAAM,OAAO,WAAW,QAAQ,QAAQ,EAAE,YAAY,CAAC,KAAK;AAC5D,SAAO,QAAQ,IAAI,WAAW,MAAM,SAAS,QAAQ,CAAC;AACxD;AAOO,SAAS,cAAc,KAAa,UAAkB,QAAwB;AACnF,SAAO,IAAI,QAAQ,qCAAqC,CAAC,OAAO,QAAQ,WAAmB;AACzF,QAAI,mBAAmB,MAAM,EAAG,QAAO;AACvC,UAAM,WAAW,aAAa,UAAU,QAAQ,MAAM;AACtD,WAAO,OAAO,UAAU,QAAQ,CAAC;AAAA,EACnC,CAAC;AACH;AAGA,SAAS,cAAc,MAAmB,UAAwB;AAChE,aAAW,MAAM,KAAK,iBAAiB,aAAa,GAAG;AACrD,UAAM,MAAM,GAAG,aAAa,KAAK;AACjC,QAAI,CAAC,OAAO,mBAAmB,GAAG,EAAG;AACrC,UAAM,KAAK,aAAa,aAAa,UAAU,UAAU,GAAG,GAAG,OAAO,EAAE;AAAA,MACtE;AAAA,MACA;AAAA,IACF;AACA,OAAG,gBAAgB,KAAK;AASxB,OAAG,cAAc;AAAA,EACnB;AACF;AAGA,SAAS,kBAAkB,MAAmB,UAAwB;AACpE,aAAW,MAAM,KAAK,iBAAiB,wBAAwB,GAAG;AAChE,UAAM,OAAO,GAAG,aAAa,MAAM;AACnC,QAAI,CAAC,QAAQ,mBAAmB,IAAI,EAAG;AACvC,UAAM,UAAU,aAAa,UAAU,UAAU,IAAI;AACrD,UAAM,MAAM,cAAc,aAAa,SAAS,OAAO,GAAG,UAAU,QAAQ,OAAO,CAAC;AACpF,OAAG,YAAY,GAAG,MAAM,UAAU,GAAG,UAAU,EAAE,UAAU;AAAA,EAC7D;AACF;AAGA,SAAS,0BAA0B,MAAmB,UAAwB;AAC5E,aAAW,MAAM,KAAK,iBAAiB,OAAO,GAAG;AAC/C,UAAM,MAAM,GAAG;AACf,QAAI,CAAC,IAAI,SAAS,MAAM,EAAG;AAC3B,OAAG,YAAY,cAAc,KAAK,UAAU,QAAQ,CAAC;AAAA,EACvD;AACF;AAGA,SAAS,sBAAsB,MAAmB,UAAwB;AACxE,QAAM,UAAmD;AAAA,IACvD,CAAC,YAAY,KAAK;AAAA,IAClB,CAAC,eAAe,KAAK;AAAA,IACrB,CAAC,cAAc,KAAK;AAAA,IACpB,CAAC,cAAc,KAAK;AAAA,IACpB,CAAC,oBAAoB,MAAM;AAAA,IAC3B,CAAC,6BAA6B,MAAM;AAAA,IACpC,CAAC,gCAAgC,MAAM;AAAA,IACvC,CAAC,uBAAuB,MAAM;AAAA,EAChC;AACA,aAAW,CAAC,UAAU,IAAI,KAAK,SAAS;AACtC,eAAW,MAAM,KAAK,iBAAiB,QAAQ,GAAG;AAChD,YAAM,MAAM,GAAG,aAAa,IAAI;AAChC,UAAI,CAAC,OAAO,mBAAmB,GAAG,EAAG;AACrC,SAAG,aAAa,MAAM,UAAU,aAAa,UAAU,UAAU,GAAG,CAAC,CAAC;AAAA,IACxE;AAAA,EACF;AACF;AAMO,SAAS,WAAW,UAA0B;AACnD,QAAM,WAAW,QAAQ,QAAQ,QAAQ,CAAC;AAC1C,QAAM,OAAO,MAAM,aAAa,UAAU,OAAO,GAAG;AAAA,IAClD,SAAS;AAAA,IACT,SAAS,EAAE,cAAc,KAAK;AAAA,EAChC,CAAC;AACD,gBAAc,MAAM,QAAQ;AAC5B,oBAAkB,MAAM,QAAQ;AAChC,4BAA0B,MAAM,QAAQ;AACxC,wBAAsB,MAAM,QAAQ;AACpC,SAAO,KAAK,SAAS;AACvB;AAoBO,SAAS,YAAY,UAAkB,WAAmB,mBAAiC;AAChG,QAAM,OAAO,WAAW,QAAQ;AAChC,QAAM,WAAW,QAAQ,EAAE,cAAc,CAAC,QAAQ,IAAI,GAAG,EAAE,OAAO,YAAY,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC;AAChG,QAAM,OAAO,SAAS;AACtB,QAAM,OAAO,UAAU,WAAW,QAAQ,EAAE,OAAO,QAAQ,EAAE,OAAO,KAAK,CAAC;AAC1E,SAAO,EAAE,MAAM,UAAU,MAAM,MAAM,WAAW,OAAO,SAAS;AAClE;AAmDO,IAAM,oBAIR;AAAA,EACH;AAAA,IACE,MAAM;AAAA,IACN,SAAS;AAAA,IACT,SACE;AAAA,EACJ;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,SAAS;AAAA,IACT,SAAS;AAAA,EACX;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,SAAS;AAAA,IACT,SAAS;AAAA,EACX;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,SAAS;AAAA,IACT,SACE;AAAA,EACJ;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,SAAS;AAAA,IACT,SAAS;AAAA,EACX;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,SAAS;AAAA,IACT,SACE;AAAA,EACJ;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,SAAS;AAAA,IACT,SAAS;AAAA,EACX;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,SAAS;AAAA,IACT,SACE;AAAA,EACJ;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,SAAS;AAAA,IACT,SACE;AAAA,EACJ;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,SAAS;AAAA,IACT,SACE;AAAA,EACJ;AACF;AAMO,IAAM,qBAAqB;AAG3B,SAAS,eAAe,MAAmC;AAChE,QAAM,WAAgC,CAAC;AACvC,MAAI,KAAK,SAAS,kBAAkB,GAAG;AACrC,aAAS,KAAK;AAAA,MACZ,UAAU;AAAA,MACV,MAAM;AAAA,MACN,SACE;AAAA,IACJ,CAAC;AAAA,EACH;AACA,aAAW,EAAE,MAAM,SAAS,QAAQ,KAAK,mBAAmB;AAC1D,QAAI,QAAQ,KAAK,IAAI,EAAG,UAAS,KAAK,EAAE,UAAU,WAAW,MAAM,QAAQ,CAAC;AAAA,EAC9E;AACA,SAAO;AACT;AAUO,SAAS,mBAAmB,MAAmC;AACpE,QAAM,WAAgC,CAAC;AACvC,QAAM,OAAO,MAAM,MAAM,EAAE,SAAS,MAAM,SAAS,EAAE,cAAc,KAAK,EAAE,CAAC;AAE3E,aAAW,MAAM,KAAK,iBAAiB,aAAa,GAAG;AACrD,UAAM,MAAM,GAAG,aAAa,KAAK,KAAK;AACtC,QAAI,UAAU,KAAK,GAAG,EAAG;AACzB,aAAS,KAAK;AAAA,MACZ,UAAU;AAAA,MACV,MAAM;AAAA,MACN,SAAS,gBAAgB,GAAG;AAAA,IAC9B,CAAC;AAAA,EACH;AAEA,aAAW,MAAM,KAAK,iBAAiB,wBAAwB,GAAG;AAChE,UAAM,OAAO,GAAG,aAAa,MAAM,KAAK;AACxC,QAAI,UAAU,KAAK,IAAI,EAAG;AAC1B,aAAS,KAAK;AAAA,MACZ,UAAU;AAAA,MACV,MAAM;AAAA,MACN,SAAS,gCAAgC,IAAI;AAAA,IAC/C,CAAC;AAAA,EACH;AAEA,aAAW,MAAM,KAAK,iBAAiB,UAAU,GAAG;AAClD,UAAM,MAAM,GAAG,aAAa,KAAK,KAAK;AACtC,QAAI,UAAU,KAAK,GAAG,KAAK,mBAAmB,GAAG,EAAG;AACpD,aAAS,KAAK;AAAA,MACZ,UAAU;AAAA,MACV,MAAM;AAAA,MACN,SAAS,aAAa,GAAG;AAAA,IAC3B,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAGA,SAAS,YAAY,UAAoD;AACvE,SAAO,CAAC,GAAG,QAAQ,EAAE;AAAA,IAAK,CAAC,GAAG,MAC5B,EAAE,aAAa,EAAE,WAAW,IAAI,EAAE,aAAa,UAAU,KAAK;AAAA,EAChE;AACF;AAOO,SAAS,iBACd,UACA,WAAmB,mBACD;AAClB,QAAM,WAAgC,CAAC;AACvC,QAAM,OAAO,SAAS;AACtB,QAAM,OAAO,UAAU,WAAW,QAAQ,EAAE,OAAO,QAAQ,EAAE,OAAO,KAAK,CAAC;AAE1E,MAAI,OAAO,UAAU;AACnB,aAAS,KAAK;AAAA,MACZ,UAAU;AAAA,MACV,MAAM;AAAA,MACN,SAAS,eAAe,IAAI,oBAAoB,QAAQ;AAAA,IAC1D,CAAC;AAAA,EACH;AAEA,MAAI;AACJ,MAAI;AACF,cAAU,UAAU,QAAQ;AAAA,EAC9B,SAAS,KAAK;AACZ,aAAS,KAAK;AAAA,MACZ,UAAU;AAAA,MACV,MAAM;AAAA,MACN,SAAS,4BAA4B,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,IACvF,CAAC;AACD,WAAO,EAAE,IAAI,OAAO,OAAO,MAAM,MAAM,MAAM,UAAU,YAAY,QAAQ,EAAE;AAAA,EAC/E;AAEA,QAAM,QAAQ,OAAO,KAAK,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,SAAS,GAAG,CAAC;AACjE,MAAI,QAAuB;AAE3B,MAAI,MAAM,SAAS,YAAY,GAAG;AAChC,YAAQ;AAAA,EACV,OAAO;AACL,UAAM,YAAY,MAAM,KAAK,CAAC,MAAM,EAAE,YAAY,EAAE,SAAS,OAAO,CAAC;AACrE,QAAI,WAAW;AACb,cAAQ;AACR,eAAS,KAAK;AAAA,QACZ,UAAU;AAAA,QACV,MAAM;AAAA,QACN,SAAS,0DAA0D,SAAS;AAAA,MAC9E,CAAC;AAAA,IACH,OAAO;AACL,eAAS,KAAK;AAAA,QACZ,UAAU;AAAA,QACV,MAAM;AAAA,QACN,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AAAA,EACF;AAEA,MAAI,OAAO;AACT,UAAM,OAAO,OAAO,KAAK,QAAQ,KAAK,CAAe,EAAE,SAAS,OAAO;AACvE,aAAS,KAAK,GAAG,mBAAmB,IAAI,GAAG,GAAG,eAAe,IAAI,CAAC;AAAA,EACpE;AAEA,QAAM,KAAK,CAAC,SAAS,KAAK,CAAC,MAAM,EAAE,aAAa,OAAO;AACvD,SAAO,EAAE,IAAI,OAAO,MAAM,MAAM,UAAU,YAAY,QAAQ,EAAE;AAClE;AAOO,SAAS,eACd,WACA,WAAmB,mBACD;AAClB,MAAI,QAAQ,SAAS,EAAE,YAAY,MAAM,QAAQ;AAC/C,WAAO,iBAAiB,aAAa,SAAS,GAAG,QAAQ;AAAA,EAC3D;AACA,QAAM,OAAO,aAAa,WAAW,OAAO;AAC5C,QAAM,WAAW,CAAC,GAAG,mBAAmB,IAAI,GAAG,GAAG,eAAe,IAAI,CAAC;AACtE,SAAO;AAAA,IACL,IAAI,CAAC,SAAS,KAAK,CAAC,MAAM,EAAE,aAAa,OAAO;AAAA,IAChD,OAAO;AAAA,IACP,MAAM;AAAA,IACN,MAAM;AAAA,IACN,UAAU,YAAY,QAAQ;AAAA,EAChC;AACF;AAyBA,IAAM,cAAc;AACb,IAAM,mBAAmB;AAOzB,SAAS,UAAU,MAAyB;AACjD,QAAM,OAAgB;AAAA,IACpB,SAAS;AAAA,IACT,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,MAAM;AAAA,IACN,eAAe;AAAA,IACf,WAAW;AAAA,IACX,OAAO;AAAA,IACP,MAAM;AAAA,EACR;AACA,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,QAAQ,KAAK,CAAC;AACpB,YAAQ,OAAO;AAAA,MACb,KAAK;AAAA,MACL,KAAK;AACH,aAAK,OAAO;AACZ;AAAA,MACF,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AACH,aAAK,WAAW,KAAK,EAAE,CAAC,KAAK;AAC7B;AAAA,MACF,KAAK;AAAA,MACL,KAAK;AACH,aAAK,UAAU,KAAK,EAAE,CAAC,KAAK;AAC5B;AAAA,MACF,KAAK;AACH,aAAK,WAAW,OAAO,SAAS,KAAK,EAAE,CAAC,KAAK,IAAI,EAAE,KAAK;AACxD;AAAA,MACF,KAAK;AACH,aAAK,SAAS;AACd;AAAA,MACF,KAAK;AACH,aAAK,UAAU;AACf;AAAA,MACF,KAAK;AACH,aAAK,UAAU;AACf;AAAA,MACF,KAAK;AACH,aAAK,UAAU;AACf;AAAA,MACF,KAAK;AACH,aAAK,WAAW,KAAK,EAAE,CAAC,KAAK;AAC7B;AAAA,MACF,KAAK;AACH,aAAK,gBAAgB,KAAK,EAAE,CAAC,KAAK;AAClC;AAAA,MACF,KAAK;AACH,aAAK,OAAO,OAAO,SAAS,KAAK,EAAE,CAAC,KAAK,IAAI,EAAE,KAAK;AACpD;AAAA,MACF,KAAK,aAAa;AAChB,cAAM,KAAK,OAAO,SAAS,KAAK,EAAE,CAAC,KAAK,IAAI,EAAE;AAC9C,aAAK,YAAY,OAAO,SAAS,EAAE,KAAK,MAAM,IAAI,KAAK;AACvD;AAAA,MACF;AAAA,MACA,KAAK;AACH,aAAK,QAAQ;AACb;AAAA,MACF;AAEE,YAAI,SAAS,CAAC,MAAM,WAAW,GAAG,KAAK,CAAC,KAAK,SAAU,MAAK,WAAW;AAAA,IAC3E;AAAA,EACF;AACA,SAAO;AACT;AAEO,IAAM,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,2DAsBsC,WAAW;AAAA,uDACf,iBAAiB;AAAA;AAAA;AAAA;AAAA,uDAIjB,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACplBhE,IAAM,mBAAmB;AAAA,EAC9B,MAAM;AAAA,EACN,KAAK;AACP;AAqBO,IAAM,mBAAN,cAA+B,MAAM;AAAA,EAC1C,YACE,SACS,QACA,QACT;AACA,UAAM,OAAO;AAHJ;AACA;AAGT,SAAK,OAAO;AAAA,EACd;AACF;AAYA,SAAS,UAAU,SAAiC;AAClD,QAAM,SACJ,OAAO,QAAQ,iBAAiB,aAC5B,QAAQ,aAAa,IACrB,CAAC,QAAQ,IAAI,YAAY,KAAK,EAAE;AACtC,aAAW,SAAS,QAAQ;AAC1B,UAAM,QAAQ,+BAA+B,KAAK,KAAK;AACvD,QAAI,QAAQ,CAAC,EAAG,QAAO,MAAM,CAAC;AAAA,EAChC;AACA,SAAO;AACT;AAOA,eAAe,WAAW,UAA4C;AACpE,MAAI;AACF,UAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,eAAW,OAAO,CAAC,SAAS,WAAW,eAAe,MAAM,GAAG;AAC7D,YAAM,QAAQ,KAAK,GAAG;AACtB,UAAI,OAAO,UAAU,YAAY,MAAO,QAAO;AAAA,IACjD;AAAA,EACF,QAAQ;AAAA,EAER;AACA,SAAO;AACT;AAEO,IAAM,oBAAN,MAAwB;AAAA,EAO7B,YAAY,MAAgC;AAF5C,SAAQ,QAAuB;AAG7B,SAAK,UAAU,KAAK,QAAQ,QAAQ,QAAQ,EAAE;AAC9C,SAAK,YAAY,KAAK;AACtB,SAAK,YAAY,KAAK,aAAa;AACnC,SAAK,YAAY,KAAK,aAAa;AAAA,EACrC;AAAA,EAEA,MAAc,QAAQ,MAAc,OAAoB,CAAC,GAAsB;AAC7E,UAAM,UAAU,IAAI,QAAQ,KAAK,OAAO;AACxC,YAAQ,IAAI,cAAc,KAAK,SAAS;AACxC,YAAQ,IAAI,UAAU,kBAAkB;AACxC,QAAI,KAAK,MAAO,SAAQ,IAAI,UAAU,SAAS,KAAK,KAAK,EAAE;AAC3D,QAAI;AACF,aAAO,MAAM,KAAK,UAAU,GAAG,KAAK,OAAO,GAAG,IAAI,IAAI;AAAA,QACpD,GAAG;AAAA,QACH;AAAA,QACA,UAAU;AAAA,QACV,QAAQ,YAAY,QAAQ,KAAK,SAAS;AAAA,MAC5C,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,YAAM,IAAI;AAAA,QACR,mBAAmB,KAAK,OAAO,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,QACpF;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAc,KAAK,UAAoB,MAA8B;AACnE,UAAM,SAAS,MAAM,WAAW,QAAQ;AACxC,UAAM,IAAI;AAAA,MACR,GAAG,IAAI,iBAAiB,SAAS,MAAM,IAAI,SAAS,KAAK,MAAM,KAAK,EAAE;AAAA,MACtE,SAAS;AAAA,MACT;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,MAAM,OAIwB;AAClC,UAAM,WAAW,MAAM,KAAK,QAAQ,eAAe;AAAA,MACjD,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU;AAAA,QACnB,YAAY,MAAM;AAAA,QAClB,SAAS,MAAM;AAAA,QACf,UAAU,MAAM;AAAA,MAClB,CAAC;AAAA,IACH,CAAC;AACD,QAAI,CAAC,SAAS,GAAI,OAAM,KAAK,KAAK,UAAU,OAAO;AACnD,UAAM,QAAQ,UAAU,SAAS,OAAO;AACxC,QAAI,CAAC,MAAO,OAAM,IAAI,iBAAiB,2CAA2C,KAAK,IAAI;AAC3F,SAAK,QAAQ;AACb,UAAM,OAAQ,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AACpD,WAAO,EAAE,WAAW,KAAK,wBAAwB,MAAM;AAAA,EACzD;AAAA;AAAA,EAGA,MAAM,WAAW,MAA6B;AAC5C,UAAM,WAAW,MAAM,KAAK,QAAQ,oBAAoB;AAAA,MACtD,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,EAAE,UAAU,KAAK,CAAC;AAAA,IACzC,CAAC;AACD,QAAI,CAAC,SAAS,GAAI,OAAM,KAAK,KAAK,UAAU,yBAAyB;AACrE,UAAM,QAAQ,UAAU,SAAS,OAAO;AACxC,QAAI,MAAO,MAAK,QAAQ;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,QAAQ,OAKkB;AAC9B,UAAM,OAAO,IAAI,SAAS;AAC1B,SAAK;AAAA,MACH;AAAA,MACA,IAAI,KAAK,CAAC,MAAM,QAAoB,GAAG,EAAE,MAAM,kBAAkB,CAAC;AAAA,MAClE;AAAA,IACF;AACA,SAAK,IAAI,WAAW,MAAM,OAAO;AACjC,QAAI,MAAM,cAAc,OAAW,MAAK,IAAI,cAAc,OAAO,MAAM,SAAS,CAAC;AACjF,QAAI,MAAM,cAAe,MAAK,IAAI,mBAAmB,MAAM,aAAa;AACxE,UAAM,WAAW,MAAM,KAAK,QAAQ,6BAA6B;AAAA,MAC/D,QAAQ;AAAA,MACR,MAAM;AAAA,IACR,CAAC;AACD,QAAI,CAAC,SAAS,GAAI,OAAM,KAAK,KAAK,UAAU,SAAS;AACrD,UAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,MAAM,KAAK,WAAmD;AAC5D,UAAM,QAAQ,cAAc,SAAY,KAAK,eAAe,SAAS;AACrE,UAAM,WAAW,MAAM,KAAK,QAAQ,4BAA4B,KAAK,EAAE;AACvE,QAAI,CAAC,SAAS,GAAI,OAAM,KAAK,KAAK,UAAU,MAAM;AAClD,UAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,OAAO,UAAkB,iBAA4D;AACzF,UAAM,WAAW,MAAM,KAAK,QAAQ,6BAA6B,QAAQ,WAAW;AAAA,MAClF,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,kBAAkB,EAAE,gBAAgB,IAAI,CAAC,CAAC;AAAA,IACjE,CAAC;AACD,QAAI,CAAC,SAAS,GAAI,OAAM,KAAK,KAAK,UAAU,QAAQ;AACpD,UAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,WAAO,KAAK;AAAA,EACd;AACF;;;AFxKO,IAAM,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA0B5B,IAAM,iBAAiB,CAAC,UAAkD;AACxE,MAAI,UAAU,OAAW,QAAO;AAChC,QAAM,IAAI,OAAO,KAAK;AACtB,SAAO,OAAO,UAAU,CAAC,KAAK,IAAI,IAAI,IAAI,OAAO;AACnD;AAOO,SAAS,gBACd,MACA,MAA0C,CAAC,GACX;AAChC,QAAM,CAAC,QAAQ,GAAG,IAAI,IAAI;AAC1B,MAAI,WAAW,aAAa,WAAW,UAAU,WAAW,UAAU;AACpE,WAAO,EAAE,OAAO,2BAA2B,UAAU,EAAE,kCAAkC;AAAA,EAC3F;AACA,QAAM,OAAmB;AAAA,IACvB;AAAA,IACA,KAAK;AAAA,IACL,KAAK;AAAA,IACL,MAAM;AAAA,IACN,WAAW,eAAe,IAAI,qBAAqB;AAAA,IACnD,QAAQ,eAAe,IAAI,kBAAkB;AAAA,EAC/C;AACA,QAAM,aAAuB,CAAC;AAC9B,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,QAAQ,KAAK,CAAC,KAAK;AACzB,UAAM,OAAO,MAA0B,KAAK,EAAE,CAAC;AAC/C,YAAQ,OAAO;AAAA,MACb,KAAK,SAAS;AACZ,cAAMC,OAAM,KAAK;AACjB,YAAIA,SAAQ,UAAUA,SAAQ,MAAO,QAAO,EAAE,OAAO,6BAA6B;AAClF,aAAK,MAAMA;AACX;AAAA,MACF;AAAA,MACA,KAAK;AACH,aAAK,UAAU,KAAK;AACpB;AAAA,MACF,KAAK;AACH,aAAK,YAAY,eAAe,KAAK,CAAC;AACtC;AAAA,MACF,KAAK;AACH,aAAK,SAAS,eAAe,KAAK,CAAC;AACnC;AAAA,MACF,KAAK;AACH,aAAK,UAAU,KAAK;AACpB;AAAA,MACF,KAAK;AACH,aAAK,gBAAgB,KAAK;AAC1B;AAAA,MACF,KAAK;AACH,aAAK,kBAAkB,KAAK;AAC5B;AAAA,MACF,KAAK;AACH,aAAK,MAAM;AACX;AAAA,MACF,KAAK;AACH,aAAK,OAAO;AACZ;AAAA,MACF,KAAK;AACH,eAAO;AAAA,UACL,OAAO;AAAA,QACT;AAAA,MACF;AACE,YAAI,MAAM,WAAW,GAAG,EAAG,QAAO,EAAE,OAAO,kBAAkB,KAAK,IAAI;AACtE,mBAAW,KAAK,KAAK;AAAA,IACzB;AAAA,EACF;AACA,MAAI,OAAO,MAAM,KAAK,SAAS,KAAK,OAAO,MAAM,KAAK,MAAM,GAAG;AAC7D,WAAO;AAAA,MACL,OACE;AAAA,IACJ;AAAA,EACF;AACA,MAAI,WAAW,WAAW;AACxB,SAAK,QAAQ,WAAW,CAAC;AACzB,QAAI,CAAC,KAAK;AACR,aAAO,EAAE,OAAO,qEAAqE;AACvF,QAAI,CAAC,KAAK,QAAS,QAAO,EAAE,OAAO,kCAAkC;AAAA,EACvE;AACA,MAAI,WAAW,UAAU;AACvB,SAAK,WAAW,eAAe,WAAW,CAAC,CAAC;AAC5C,QAAI,CAAC,KAAK,YAAY,OAAO,MAAM,KAAK,QAAQ,GAAG;AACjD,aAAO,EAAE,OAAO,uDAAuD;AAAA,IACzE;AAAA,EACF;AACA,SAAO;AACT;AAGA,SAAS,QAAQ,OAAwE;AACvF,QAAM,OAAOC,SAAQ,KAAK;AAC1B,MAAI,CAAC,WAAW,IAAI,EAAG,OAAM,IAAI,MAAM,GAAG,KAAK,cAAc;AAC7D,MAAI;AACJ,MAAIC,SAAQ,IAAI,EAAE,YAAY,MAAM,QAAQ;AAC1C,eAAW,IAAI,WAAWC,cAAa,IAAI,CAAC;AAAA,EAC9C,OAAO;AACL,UAAM,OAAO,SAAS,IAAI,EAAE,YAAY,IAAI,KAAK,MAAM,YAAY,IAAI;AACvE,eAAW,YAAY,IAAI,EAAE;AAAA,EAC/B;AACA,SAAO,EAAE,UAAU,UAAU,iBAAiB,QAAQ,EAAE,SAAS;AACnE;AAEA,eAAe,MAAM,IAAc,OAAe,OAA4C;AAC5F,MAAI,UAAU,OAAW,QAAO;AAChC,QAAM,UAAU,MAAM,GAAG,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK;AACjD,QAAM,IAAI,OAAO,MAAM;AACvB,MAAI,CAAC,OAAO,UAAU,CAAC,KAAK,KAAK,EAAG,OAAM,IAAI,MAAM,GAAG,KAAK,8BAA8B;AAC1F,SAAO;AACT;AAEA,eAAe,QAAQ,IAAc,MAAkBC,WAAoC;AACzF,MAAI,KAAK,IAAK,QAAO;AACrB,QAAM,UAAU,MAAM,GAAG,IAAI,GAAGA,SAAQ,SAAS,GAAG,KAAK,EAAE,YAAY;AACvE,SAAO,WAAW,OAAO,WAAW;AACtC;AAEA,eAAe,MACb,IACA,QACA,WACA,QACe;AACf,KAAG,MAAM,EAAE;AACX,KAAG,MAAM,gFAAgF;AACzF,MAAI,WAAW;AACf,WAAS,UAAU,GAAG,UAAU,KAAK,CAAC,SAAS,KAAK,GAAG,WAAW;AAChE,QAAI,UAAU,EAAG,IAAG,WAAW,+BAA+B;AAC9D,eAAW,MAAM,GAAG,UAAU,YAAY;AAAA,EAC5C;AAEA,MAAI,CAAC,SAAS,KAAK,EAAG,OAAM,IAAI,MAAM,sCAAsC;AAC5E,QAAM,EAAE,UAAU,IAAI,MAAM,OAAO,MAAM,EAAE,WAAW,QAAQ,SAAS,CAAC;AACxE,MAAI,CAAC,UAAW;AAChB,KAAG;AAAA,IACD;AAAA,EACF;AACA,WAAS,UAAU,GAAG,WAAW,GAAG,WAAW;AAC7C,UAAM,QAAQ,MAAM,GAAG,UAAU,6BAA6B,GAAG,KAAK;AACtE,QAAI;AACF,YAAM,OAAO,WAAW,IAAI;AAC5B;AAAA,IACF,SAAS,KAAK;AACZ,UAAI,YAAY,KAAK,EAAE,eAAe,qBAAqB,IAAI,WAAW,IAAK,OAAM;AACrF,SAAG,WAAW,4CAA4C;AAAA,IAC5D;AAAA,EACF;AACF;AAEA,SAAS,cAAc,KAAsB;AAC3C,MAAI,EAAE,eAAe,kBAAmB,QAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC9F,MAAI,IAAI,WAAW,OAAO,YAAY,KAAK,IAAI,UAAU,EAAE,GAAG;AAC5D,WAAO,GAAG,IAAI,OAAO;AAAA;AAAA,EACvB;AACA,UAAQ,IAAI,QAAQ;AAAA,IAClB,KAAK;AACH,aAAO,GAAG,IAAI,OAAO;AAAA;AAAA,IACvB,KAAK;AACH,aAAO,GAAG,IAAI,OAAO;AAAA;AAAA,IACvB,KAAK;AACH,aAAO,GAAG,IAAI,OAAO;AAAA;AAAA,IACvB,KAAK;AACH,aAAO,GAAG,IAAI,OAAO;AAAA;AAAA,IACvB;AACE,aAAO,IAAI;AAAA,EACf;AACF;AAEA,SAAS,MAAM,MAAsC;AACnD,MAAI,KAAK,WAAW,EAAG,QAAO,CAAC,uCAAuC;AACtE,SAAO;AAAA,IACL;AAAA,IACA,GAAG,KAAK;AAAA,MACN,CAAC,MACC,GAAG,OAAO,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC,GAAG,EAAE,QAAQ,OAAO,EAAE,CAAC,IAAI,EAAE,aAAa,QAAQ,IAAI,OAAO,CAAC,CAAC,IACtF,EAAE,UAAU,QAAQ,IACpB,OAAO,CAAC,CAAC,IAAI,EAAE,gBAAgB,IAAI,OAAO,EAAE,CAAC,GAAG,EAAE,mBAAmB,EAAE;AAAA,IAC7E;AAAA,EACF;AACF;AAQA,eAAsB,UAAU,MAAkB,IAAc,MAAmC;AACjG,QAAM,UAAU,KAAK,WAAW,iBAAiB,KAAK,GAAG;AACzD,QAAM,WAAW,KAAK,UAAU,WAAW,OAAO,MAAM,GAAG,KAAK,IAAI,YAAY,CAAC,KAAK,OAAO;AAC7F,MAAI;AACF,QAAI;AACJ,QAAI,KAAK,WAAW,WAAW;AAC7B,YAAM,QAAQ,KAAK,SAAS,EAAE;AAC9B,YAAM,SAAS,IAAI,SAAS,OAAO,CAAC,MAAM,EAAE,aAAa,OAAO;AAChE,iBAAW,KAAK,IAAI,UAAU;AAC5B,SAAC,EAAE,aAAa,UAAU,GAAG,aAAa,GAAG;AAAA,UAC3C,GAAG,EAAE,SAAS,YAAY,CAAC,KAAK,EAAE,IAAI,KAAK,EAAE,OAAO;AAAA,QACtD;AAAA,MACF;AACA,UAAI,OAAO,SAAS,GAAG;AACrB,WAAG,WAAW,qDAAqD;AACnE,eAAO;AAAA,MACT;AAAA,IACF;AAEA,QAAI,CAAC,GAAG,aAAa;AACnB,SAAG;AAAA,QACD;AAAA,MACF;AACA,aAAO;AAAA,IACT;AACA,UAAM,YAAY,MAAM,MAAM,IAAI,cAAc,KAAK,SAAS;AAC9D,UAAM,SAAS,MAAM,MAAM,IAAI,WAAW,KAAK,MAAM;AAErD,QAAI,KAAK,WAAW,aAAa,KAAK;AACpC,YAAM,OAAO,UAAUC,YAAW,QAAQ,EAAE,OAAO,IAAI,QAAQ,EAAE,OAAO,KAAK,CAAC;AAC9E,SAAG,MAAM,EAAE;AACX,SAAG,MAAM,iBAAiB,QAAQ,EAAE;AACpC,SAAG,MAAM,iBAAiB,SAAS,EAAE;AACrC,SAAG;AAAA,QACD,iBAAiB,KAAK,OAAO,GAAG,KAAK,gBAAgB,aAAa,KAAK,aAAa,MAAM,EAAE;AAAA,MAC9F;AACA,SAAG,MAAM,iBAAiB,IAAI,SAAS,MAAM,WAAW,IAAI,EAAE;AAC9D,SAAG,MAAM,sDAAsD;AAC/D,UAAI,CAAE,MAAM,QAAQ,IAAI,MAAM,UAAU,GAAI;AAC1C,WAAG,MAAM,8BAA8B;AACvC,eAAO;AAAA,MACT;AAAA,IACF;AACA,QAAI,KAAK,WAAW,UAAU;AAC5B,SAAG,MAAM,EAAE;AACX,SAAG,MAAM,iBAAiB,QAAQ,EAAE;AACpC,SAAG,MAAM,iBAAiB,KAAK,QAAQ,eAAe,SAAS,GAAG;AAClE,SAAG;AAAA,QACD,KAAK,kBACD,+BAA+B,KAAK,eAAe,6BACnD;AAAA,MACN;AACA,UAAI,CAAE,MAAM,QAAQ,IAAI,MAAM,SAAS,GAAI;AACzC,WAAG,MAAM,8BAA8B;AACvC,eAAO;AAAA,MACT;AAAA,IACF;AAEA,UAAM,SAAS,IAAI,kBAAkB;AAAA,MACnC;AAAA,MACA,WAAW,KAAK;AAAA,MAChB,WAAW,KAAK;AAAA,IAClB,CAAC;AACD,UAAM,MAAM,IAAI,QAAQ,WAAW,MAAM;AAEzC,QAAI,KAAK,WAAW,aAAa,KAAK;AACpC,YAAM,SAAS,MAAM,OAAO,QAAQ;AAAA,QAClC,UAAU,IAAI;AAAA,QACd,SAAS,KAAK,WAAW;AAAA,QACzB;AAAA,QACA,eAAe,KAAK;AAAA,MACtB,CAAC;AACD,UAAI,KAAK,KAAM,IAAG,MAAM,KAAK,UAAU,MAAM,CAAC;AAAA,WACzC;AACH,WAAG;AAAA,UACD,qBAAqB,OAAO,OAAO,QAAQ,OAAO,EAAE,qBAAgB,OAAO,UAAU;AAAA,QACvF;AACA,WAAG,MAAM,eAAe,OAAO,WAAW,EAAE;AAC5C,WAAG,MAAM,yEAAyE;AAAA,MACpF;AACA,aAAO;AAAA,IACT;AACA,QAAI,KAAK,WAAW,QAAQ;AAC1B,YAAM,OAAO,MAAM,OAAO,KAAK,SAAS;AACxC,UAAI,KAAK,KAAM,IAAG,MAAM,KAAK,UAAU,IAAI,CAAC;AAAA,UACvC,YAAW,QAAQ,MAAM,IAAI,EAAG,IAAG,MAAM,IAAI;AAClD,aAAO;AAAA,IACT;AACA,UAAM,SAAS,MAAM,OAAO,OAAO,KAAK,YAAY,GAAG,KAAK,eAAe;AAC3E,OAAG,MAAM,KAAK,OAAO,KAAK,UAAU,MAAM,IAAI,YAAY,KAAK,UAAU,MAAM,CAAC,EAAE;AAClF,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,OAAG,WAAW,cAAc,GAAG,CAAC;AAChC,WAAO;AAAA,EACT;AACF;;;AGlWA,SAAS,uBAAuC;AAGhD,IAAI,KAAuB;AAE3B,IAAI,cAA6B;AAEjC,SAAS,SAAoB;AAC3B,MAAI,GAAI,QAAO;AACf,OAAK,gBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,QAAQ,UAAU,KAAK,CAAC;AACrF,QAAM,MAAM;AACZ,QAAM,QAAQ,IAAI,eAAe,KAAK,EAAE;AAExC,MAAI,iBAAiB,CAAC,MAAc;AAClC,QAAI,gBAAgB,QAAQ,EAAE,SAAS,WAAW,EAAG,OAAM,CAAC;AAAA,EAC9D;AACA,SAAO;AACT;AAEA,SAAS,SAAS,QAAgB,QAAkC;AAClE,QAAM,IAAI,OAAO;AACjB,SAAO,IAAI,QAAQ,CAACC,aAAY;AAC9B,kBAAc,SAAS,SAAS;AAChC,MAAE,SAAS,QAAQ,CAAC,WAAW;AAC7B,UAAI,QAAQ;AACV,sBAAc;AACd,gBAAQ,OAAO,MAAM,IAAI;AAAA,MAC3B;AACA,MAAAA,SAAQ,MAAM;AAAA,IAChB,CAAC;AAAA,EACH,CAAC;AACH;AAEO,SAAS,aAAuB;AACrC,SAAO;AAAA,IACL,aAAa,QAAQ,QAAQ,MAAM,KAAK;AAAA,IACxC,KAAK,CAAC,MAAM,SAAS,GAAG,KAAK;AAAA,IAC7B,WAAW,CAAC,MAAM,SAAS,GAAG,IAAI;AAAA,IAClC,OAAO,CAAC,SAAS,QAAQ,IAAI,IAAI;AAAA,IACjC,YAAY,CAAC,SAAS,QAAQ,MAAM,IAAI;AAAA,EAC1C;AACF;AAGO,SAAS,kBAAwB;AACtC,MAAI,MAAM;AACV,OAAK;AACP;;;AC/CA,SAAS,cAAAC,aAAY,gBAAAC,eAAc,YAAAC,iBAAgB;AACnD,SAAS,oBAAiC;AAC1C,SAAS,WAAAC,UAAS,WAAAC,UAAS,QAAAC,OAAM,WAAAC,gBAAe;AAChD,SAAS,qBAAqB;AAC9B,SAAS,WAAW,aAAAC,kBAAiB;;;ACF9B,IAAM,cAAc;AAAA,EACzB,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,oBAAoB;AAAA,EACpB,UAAU;AAAA,EACV,UAAU;AAAA,EACV,cAAc;AAAA,EACd,oBAAoB;AAAA,EACpB,kBAAkB;AAAA,EAClB,WAAW;AAAA,EACX,KAAK;AAAA,EACL,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOd,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQhB,UAAU;AACZ;;;ACzBO,IAAM,gBAA6B;AAAA,EACxC,cAAc;AAAA,IACZ,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,YAAY;AAAA,EACd;AAAA,EACA,iBAAiB;AAAA,IACf;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,sBAAsB,CAAC,YAAY,gBAAgB,QAAQ;AAAA,EAC3D,gBAAgB,CAAC,KAAK;AACxB;AAGO,IAAM,aACX;;;ACOF,SAAS,OAAO,MAAoC;AAClD,QAAM,OAAyB;AAAA,IAC7B,UAAU,KAAK;AAAA,IACf,UAAU,KAAK;AAAA,IACf,gBAAgB,KAAK;AAAA,IACrB,qBAAqB;AAAA,IACrB,UAAU;AAAA,IACV,mBAAmB;AAAA,IACnB,uBAAuB;AAAA,IACvB,gCAAgC;AAAA,IAChC,mBAAmB;AAAA,IACnB,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,qBAAqB;AAAA,EACvB;AACA,MAAI,KAAK,QAAS,MAAK,UAAU,KAAK;AACtC,SAAO;AACT;AAEA,SAAS,UAAU,MAAkB,KAAkC;AACrE,SAAO;AAAA,IACL,UAAU,KAAK;AAAA,IACf,UAAU,KAAK;AAAA,IACf,gBAAgB,MAAM,KAAK,mBAAmB;AAAA,IAC9C,YAAY,KAAK;AAAA,IACjB,eAAe,KAAK,YAAY,kBAAkB,UAAU;AAAA,IAC5D,iBAAiB,KAAK,YAAY,kBAAkB,MAAM;AAAA,IAC1D,gBAAgB,MAAM,KAAK,mBAAmB;AAAA,IAC9C,iBAAiB;AAAA,IACjB,mBAAmB;AAAA,IACnB,cAAc,KAAK;AAAA,IACnB,gBAAgB,MAAM,KAAK,UAAU;AAAA,IACrC,YAAY,KAAK;AAAA,IACjB,UAAU,KAAK;AAAA,IACf,YAAY,KAAK,YACb;AAAA,MACE;AAAA,QACE,UAAU,KAAK;AAAA,QACf,aAAa,MAAM,KAAK;AAAA,QACxB,SAAS,KAAK;AAAA,QACd,aAAa;AAAA,MACf;AAAA,IACF,IACA,CAAC;AAAA,EACP;AACF;AAEA,SAAS,WAAW,MAAwC;AAC1D,SAAO;AAAA,IACL,UAAU,KAAK;AAAA,IACf,UAAU,KAAK;AAAA,IACf,UAAU;AAAA,IACV,UAAU;AAAA,IACV,cAAc;AAAA,IACd,eAAe;AAAA,IACf,kBAAkB;AAAA,EACpB;AACF;AAEA,SAAS,UAAU,OAAqB,MAAM,KAAK,IAAI,GAAsB;AAC3E,SAAO;AAAA,IACL,YAAY;AAAA,IACZ,eAAe,MAAM,IAAI,MAAM;AAAA,IAC/B,kBAAkB,MAAM,IAAI,CAAC,MAAM,UAAU,GAAG,GAAG,CAAC;AAAA,IACpD,mBAAmB,MAAM,IAAI,UAAU;AAAA,EACzC;AACF;AAGO,SAAS,WAAW,MAAM,KAAK,IAAI,GAAsB;AAC9D,SAAO;AAAA,IACL;AAAA,MACE;AAAA,QACE,UAAU;AAAA,QACV,UAAU;AAAA,QACV,SAAS;AAAA,QACT,UAAU;AAAA,QACV,MAAM;AAAA,QACN,SAAS;AAAA,QACT,UAAU;AAAA,QACV,kBAAkB;AAAA,MACpB;AAAA,MACA;AAAA,QACE,UAAU;AAAA,QACV,UAAU;AAAA,QACV,SAAS;AAAA,QACT,UAAU;AAAA,QACV,MAAM;AAAA,QACN,SAAS;AAAA,QACT,UAAU;AAAA,QACV,kBAAkB;AAAA,QAClB,WAAW;AAAA,MACb;AAAA,IACF;AAAA,IACA;AAAA,EACF;AACF;AAOO,SAAS,aAAa,MAAM,KAAK,IAAI,GAAsB;AAChE,SAAO;AAAA,IACL;AAAA,MACE;AAAA,QACE,UAAU;AAAA,QACV,UAAU;AAAA,QACV,UAAU;AAAA,QACV,MAAM;AAAA,QACN,SAAS;AAAA,QACT,UAAU;AAAA,QACV,kBAAkB;AAAA,MACpB;AAAA,MACA;AAAA,QACE,UAAU;AAAA,QACV,UAAU;AAAA,QACV,SAAS;AAAA,QACT,UAAU;AAAA,QACV,MAAM;AAAA,QACN,SAAS;AAAA,QACT,UAAU;AAAA,QACV,kBAAkB;AAAA,MACpB;AAAA,IACF;AAAA,IACA;AAAA,EACF;AACF;AAEO,SAAS,aAAa,YAA0C,CAAC,GAAwB;AAC9F,SAAO;AAAA,IACL,WAAW;AAAA,IACX,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,iBAAiB;AAAA,IACjB,4BAA4B,CAAC;AAAA,IAC7B,6BAA6B,CAAC;AAAA,IAC9B,iBAAiB,CAAC;AAAA,IAClB,qBAAqB,CAAC;AAAA,IACtB,GAAG;AAAA,EACL;AACF;;;ACxLO,IAAM,yBAAyB;AAoDtC,IAAM,OAAiB;AAAA,EACrB,aACE;AAAA,EAEF,eAAe;AAAA,EACf,WAAW,CAAC,IAAI,GAAG;AAAA,EACnB,gBAAgB;AAAA,EAChB,YAAY;AAAA,EACZ,cAAc,aAAa;AAAA,EAC3B,SAAS,MAAM,WAAW;AAC5B;AAEO,IAAM,YAA4C;AAAA,EACvD,SAAS,EAAE,GAAG,MAAM,qBAAqB,IAAK;AAAA,EAC9C,cAAc;AAAA,IACZ,GAAG;AAAA,IACH,aACE;AAAA,IAIF,gBAAgB;AAAA,EAClB;AAAA,EACA,cAAc;AAAA,IACZ,GAAG;AAAA,IACH,aACE;AAAA,IAGF,iBAAiB;AAAA,IACjB,cAAc,aAAa,EAAE,YAAY,MAAM,CAAC;AAAA,EAClD;AAAA,EACA,iBAAiB;AAAA,IACf,GAAG;AAAA,IACH,aACE;AAAA,IAEF,SAAS,MAAM,aAAa;AAAA,EAC9B;AAAA,EACA,SAAS;AAAA,IACP,GAAG;AAAA,IACH,aACE;AAAA,IAGF,cAAc,aAAa,EAAE,UAAU,OAAO,iBAAiB,MAAM,CAAC;AAAA,EACxE;AACF;;;AJzFO,SAAS,eAAe,OAAuB;AACpD,QAAM,OAAOC,SAAQ,KAAK;AAC1B,MAAIC,YAAW,IAAI,KAAKC,UAAS,IAAI,EAAE,YAAY,GAAG;AACpD,WAAO,WAAWC,MAAK,MAAM,YAAY,CAAC;AAAA,EAC5C;AACA,MAAIC,SAAQ,IAAI,EAAE,YAAY,MAAM,QAAQ;AAC1C,UAAM,QAAQC,WAAU,IAAI,WAAWC,cAAa,IAAI,CAAC,CAAC;AAC1D,UAAM,QAAQ,MAAM,YAAY;AAChC,QAAI,CAAC,MAAO,OAAM,IAAI,MAAM,GAAG,KAAK,yCAAyC;AAC7E,WAAO,UAAU,KAAK;AAAA,EACxB;AACA,SAAO,WAAW,IAAI;AACxB;AAEA,IAAM,aAAa,CAAC,MAClB,EAAE,QAAQ,MAAM,OAAO,EAAE,QAAQ,MAAM,MAAM,EAAE,QAAQ,MAAM,MAAM,EAAE,QAAQ,MAAM,QAAQ;AAOtF,SAAS,YACd,UACA,eACA,QAAwE,CAAC,GACjE;AACR,QAAM,UAAU,OAAO,QAAQ,SAAS,EACrC;AAAA,IACC,CAAC,CAAC,MAAM,CAAC,MACP,kBAAkB,IAAI,YAAY,WAAW,EAAE,WAAW,CAAC,IACzD,SAAS,WAAW,cAAc,EACpC,IAAI,IAAI;AAAA,EACZ,EACC,KAAK,EAAE;AACV,QAAM,cAAc;AAAA,IAClB,MAAM,eACF,iBAAiB,MAAM,YAAY,KAClC,UAAU,QAAkC,GAAG,eAAe;AAAA,EACrE;AACA,QAAM,eAAe,KAAK,UAAU;AAAA,IAClC,WAAW,MAAM,aAAa;AAAA,IAC9B,OAAO,MAAM,UAAU;AAAA,IACvB,cAAc,QAAQ,MAAM,YAAY;AAAA,EAC1C,CAAC,EAAE,QAAQ,MAAM,SAAS;AAE1B,QAAM,eAAe,KAAK,UAAU,QAAQ,EAAE,QAAQ,MAAM,SAAS;AACrE,QAAM,aACJ,KAAK;AAAA,IACH,kBAAkB,SAAY,SAAY,kBAAkB,SAAS,OAAO;AAAA,EAC9E,GAAG,QAAQ,MAAM,SAAS,KAAK;AACjC,QAAM,cAAc;AAAA,IAClB,kBAAkB,SAAY,KAAK,iBAAiB,aAAa;AAAA,EACnE;AACA,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA,sDAK6C,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,0CAatB,OAAO;AAAA;AAAA,UAEvC,WAAW;AAAA,OACd,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA,mBAKC,YAAY;AAAA,wBACP,UAAU;AAAA,mBACf,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmE/B;AAGA,SAAS,oBAA4B;AACnC,QAAM,OACJ,OAAO,cAAc,cAAc,YAAYC,SAAQ,cAAc,YAAY,GAAG,CAAC;AACvF,SAAOJ,MAAK,MAAM,uBAAuB;AAC3C;AAEO,SAAS,eAAe,MAOpB;AACT,QAAM,eAAe,KAAK,SAAS,SAAS,OAAO,IAAIH,SAAQ,KAAK,QAAQ,IAAI;AAChF,MAAI,cAAc;AAEhB,SAAK,MAAMM,cAAa,cAAc,OAAO,CAAC;AAAA,EAChD,WAAW,EAAE,KAAK,YAAY,YAAY;AACxC,UAAM,IAAI;AAAA,MACR,qBAAqB,KAAK,QAAQ,aAAa,OAAO,KAAK,SAAS,EAAE,KAAK,IAAI,CAAC;AAAA,IAClF;AAAA,EACF;AAEA,iBAAe,KAAK,KAAK;AAEzB,QAAM,SAAS,aAAa,CAAC,KAAK,QAAQ;AACxC,UAAM,MAAM,IAAI,IAAI,IAAI,OAAO,KAAK,kBAAkB;AACtD,QAAI;AACF,UAAI,IAAI,aAAa,yBAAyB;AAC5C,YAAI,UAAU,KAAK;AAAA,UACjB,gBAAgB;AAAA,UAChB,iBAAiB;AAAA,QACnB,CAAC;AACD,YAAI,IAAI,eAAe,KAAK,KAAK,CAAC;AAClC;AAAA,MACF;AACA,UAAI,IAAI,aAAa,6BAA6B,cAAc;AAC9D,YAAI,UAAU,KAAK;AAAA,UACjB,gBAAgB;AAAA,UAChB,iBAAiB;AAAA,QACnB,CAAC;AACD,YAAI,IAAIA,cAAa,YAAY,CAAC;AAClC;AAAA,MACF;AACA,UAAI,IAAI,aAAa,wBAAwB;AAC3C,YAAI,UAAU,KAAK;AAAA,UACjB,gBAAgB;AAAA,UAChB,iBAAiB;AAAA,QACnB,CAAC;AACD,YAAI,IAAIA,cAAa,kBAAkB,CAAC,CAAC;AACzC;AAAA,MACF;AACA,UAAI,IAAI,aAAa,KAAK;AACxB,cAAM,QAAQ,IAAI,aAAa,IAAI,UAAU;AAC7C,cAAM,WAAW,UAAU,eAAe,YAAY,KAAK;AAC3D,YAAI,UAAU,KAAK;AAAA,UACjB,gBAAgB;AAAA,UAChB,2BAA2B;AAAA,UAC3B,iBAAiB;AAAA,QACnB,CAAC;AACD,cAAM,gBAAgB,IAAI,aAAa,IAAI,eAAe,KAAK,KAAK;AACpE,YAAI;AAAA,UACF,YAAY,YAAY,YAAY,WAAW,WAAW,eAAe;AAAA,YACvE,WAAW,KAAK;AAAA,YAChB,OAAO,KAAK;AAAA,YACZ,cAAc,QAAQ,SAAY,gBAAgB,KAAK;AAAA,UACzD,CAAC;AAAA,QACH;AACA;AAAA,MACF;AACA,UAAI,UAAU,GAAG,EAAE,IAAI;AAAA,IACzB,SAAS,KAAK;AACZ,UAAI,UAAU,KAAK,EAAE,gBAAgB,4BAA4B,CAAC;AAClE,UAAI,IAAI,OAAO,eAAe,QAAQ,IAAI,UAAU,GAAG,CAAC;AAAA,IAC1D;AAAA,EACF,CAAC;AACD,SAAO,OAAO,KAAK,MAAM,WAAW;AACpC,SAAO;AACT;;;ANhPA,SAAS,OAAO,MAOP;AACP,QAAM,EAAE,OAAO,UAAU,MAAM,cAAc,IAAI;AACjD,QAAM,SAAS,eAAe,IAAI;AAClC,SAAO,GAAG,aAAa,MAAM;AAC3B,UAAM,SAAS,kBAAkB,SAAY,KAAK,kBAAkB,aAAa;AACjF,YAAQ;AAAA,MACN,+CAA+C,IAAI,IACjD,SAAS,SAAS,OAAO,IAAI,KAAK,aAAa,QAAQ,EACzD,GAAG,MAAM;AAAA,IACX;AACA,YAAQ,IAAI,eAAe,KAAK,4BAA4B;AAC5D,YAAQ,IAAI,kDAAkD;AAC9D,YAAQ,IAAI,sEAAsE;AAClF,YAAQ,IAAI,0DAAqD;AACjE,YAAQ,IAAI,uEAAuE;AAAA,EACrF,CAAC;AACD,SAAO,GAAG,SAAS,CAAC,QAAQ;AAC1B,YAAQ,MAAM,QAAQ,IAAI,OAAO,EAAE;AACnC,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC;AACH;AAEA,SAAS,SAAS,UAAkB,SAAiB,UAAwB;AAC3E,QAAM,SAAS,YAAY,UAAU,QAAQ;AAE7C,QAAM,cAAcE,SAAQ,OAAO;AACnC,YAAUC,SAAQ,WAAW,GAAG,EAAE,WAAW,KAAK,CAAC;AACnD,gBAAc,aAAa,OAAO,QAAQ;AAE1C,QAAM,MAAM,OAAO,WAAW,OAAO,IAAI,IAAI,MAAM,QAAQ,CAAC;AAC5D,UAAQ,IAAI,SAAS,OAAO,EAAE;AAC9B,UAAQ,IAAI,wBAAwB,EAAE,qBAAqB;AAC3D,UAAQ,IAAI,uBAAuB,OAAO,IAAI,QAAQ;AACtD,UAAQ,IAAI,uBAAuB,OAAO,IAAI,EAAE;AAEhD,MAAI,OAAO,WAAW;AACpB,UAAM,WAAW,YAAY,OAAO,OAAO,QAAQ,CAAC;AACpD,YAAQ;AAAA,MACN;AAAA,mCAA4B,OAAO,IAAI,uBAAuB,OAAO;AAAA,IAEvE;AAAA,EACF;AAEA,UAAQ,IAAI,kDAAkD,UAAU,WAAW;AACrF;AAEA,SAAS,iBAAiB,OAAe,QAA0B,QAAyB;AAC1F,UAAQ,IAAI,cAAc,KAAK,EAAE;AACjC,MAAI,OAAO,MAAO,SAAQ,IAAI,YAAY,OAAO,KAAK,EAAE;AACxD,MAAI,OAAO,SAAS,KAAM,SAAQ,IAAI,uBAAuB,OAAO,IAAI,QAAQ;AAChF,MAAI,OAAO,SAAS,KAAM,SAAQ,IAAI,uBAAuB,OAAO,IAAI,EAAE;AAE1E,QAAM,SAAS,OAAO,SAAS,OAAO,CAAC,MAAM,EAAE,aAAa,OAAO;AACnE,QAAM,WAAW,OAAO,SAAS,OAAO,CAAC,MAAM,EAAE,aAAa,SAAS;AAEvE,MAAI,OAAO,SAAS,WAAW,GAAG;AAChC,YAAQ,IAAI,oEAA+D;AAC3E,WAAO;AAAA,EACT;AAEA,UAAQ,IAAI,EAAE;AACd,aAAW,KAAK,OAAO,UAAU;AAC/B,UAAM,QAAQ,EAAE,aAAa,UAAU,mBAAc;AACrD,YAAQ,IAAI,GAAG,KAAK,KAAK,EAAE,IAAI,KAAK,EAAE,OAAO,EAAE;AAAA,EACjD;AAEA,UAAQ;AAAA,IACN;AAAA,EAAK,OAAO,MAAM,cAAc,SAAS,MAAM,kBAC5C,OAAO,SAAS,OACb,sGACA;AAAA,EACR;AAEA,MAAI,OAAO,SAAS,GAAG;AACrB,YAAQ,IAAI,oFAA+E;AAC3F,WAAO;AAAA,EACT;AACA,MAAI,SAAS,SAAS,KAAK,QAAQ;AACjC,YAAQ,IAAI,uCAAuC;AACnD,WAAO;AAAA,EACT;AACA,UAAQ;AAAA,IACN;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAe,iBAAiB,MAA+B;AAC7D,MAAI,KAAK,WAAW,KAAK,KAAK,SAAS,QAAQ,KAAK,KAAK,SAAS,IAAI,GAAG;AACvE,YAAQ,IAAI,YAAY;AACxB,YAAQ,KAAK,KAAK,WAAW,IAAI,IAAI,CAAC;AAAA,EACxC;AACA,QAAM,SAAS,gBAAgB,MAAM,QAAQ,GAAG;AAChD,MAAI,WAAW,QAAQ;AACrB,YAAQ,MAAM,OAAO,KAAK;AAC1B,YAAQ,MAAM,2CAA2C;AACzD,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,QAAM,OAAO,MAAM,UAAU,QAAQ,WAAW,GAAG;AAAA,IACjD,WAAW,4BAA4B,OAAc;AAAA,EACvD,CAAC;AACD,kBAAgB;AAChB,UAAQ,KAAK,IAAI;AACnB;AAEA,SAAS,OAAa;AACpB,MAAI,QAAQ,KAAK,CAAC,MAAM,UAAU;AAChC,SAAK,iBAAiB,QAAQ,KAAK,MAAM,CAAC,CAAC;AAC3C;AAAA,EACF;AACA,QAAM,OAAO,UAAU,QAAQ,KAAK,MAAM,CAAC,CAAC;AAE5C,MAAI,KAAK,QAAQ,CAAC,KAAK,UAAU;AAC/B,YAAQ,IAAI,KAAK;AACjB,YAAQ,KAAK,KAAK,OAAO,IAAI,CAAC;AAAA,EAChC;AAEA,MAAI,KAAK,YAAY,OAAO;AAC1B,QAAI;AACF,aAAO;AAAA,QACL,OAAO,KAAK;AAAA,QACZ,UAAU,KAAK;AAAA,QACf,MAAM,KAAK;AAAA,QACX,eAAe,KAAK;AAAA,QACpB,WAAW,KAAK;AAAA,QAChB,OAAO,KAAK;AAAA,MACd,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,cAAQ,MAAM,QAAQ,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AACxE,cAAQ,KAAK,CAAC;AAAA,IAChB;AACA;AAAA,EACF;AAEA,MAAI,KAAK,YAAY,YAAY;AAC/B,UAAM,SAAS,eAAe,KAAK,UAAU,KAAK,QAAQ;AAC1D,YAAQ,KAAK,iBAAiB,KAAK,UAAU,QAAQ,KAAK,MAAM,CAAC;AAAA,EACnE;AAEA,WAAS,KAAK,UAAU,KAAK,SAAS,KAAK,QAAQ;AACrD;AAEA,KAAK;","names":["dirname","resolve","createHash","readFileSync","extname","resolve","env","resolve","extname","readFileSync","question","createHash","resolve","existsSync","readFileSync","statSync","dirname","extname","join","resolve","unzipSync","resolve","existsSync","statSync","join","extname","unzipSync","readFileSync","dirname","resolve","dirname"]}