@condorinst/mobile-bridge 0.13.2 → 0.15.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/CHANGELOG.md +102 -0
- package/README.md +2 -0
- package/dist/cli.cjs +1 -1
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +34 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +117 -4
- package/dist/index.d.ts +117 -4
- package/dist/index.js +34 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,108 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.15.0] — 2026-08-31
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`SensorLogQueryPayload` — the request side of `sensorLog` is now typed.**
|
|
15
|
+
The command already accepted these arguments (`send` is permissive by
|
|
16
|
+
design), but nothing described them, so every client rediscovered the shape
|
|
17
|
+
by reading the native handler. The type also carries the two parameters the
|
|
18
|
+
backend added in the v2 range contract: `startTime` (inclusive) and
|
|
19
|
+
`endTime` (exclusive), both `YYYY-MM-DD HH:MM:SS`.
|
|
20
|
+
|
|
21
|
+
⚠️ Those timestamps are **local device time, not UTC** — `log_time` is a
|
|
22
|
+
passthrough of the device RTC. Build the range with the local `Date` getters
|
|
23
|
+
and never with `toISOString()`: converting shifts every series by the
|
|
24
|
+
timezone offset, with plausible values and no visible error.
|
|
25
|
+
|
|
26
|
+
- **`BridgeClient.fetchSensorLog(query, { timeoutMs })`** — resolves with the
|
|
27
|
+
page whose `requestId` matches, so clients stop hand-rolling the
|
|
28
|
+
`requestId → resolve` map (the demo had two copies of it). It resolves on
|
|
29
|
+
`success: false` too — that is an answer, not a transport failure — and only
|
|
30
|
+
rejects on timeout (default 10 s, sized for the ~2.7 s worst-case page).
|
|
31
|
+
|
|
32
|
+
Paging over a period means chaining calls on the returned `startingAfter`
|
|
33
|
+
until `hasMore` is `false`; the cursors are dependent, so pages cannot be
|
|
34
|
+
fetched in parallel.
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
|
|
38
|
+
- **`condor-mobile-bridge build` corrupted bundles whose JS contains markup.**
|
|
39
|
+
The inliner used `set_content()`, which **parses** what it receives as HTML.
|
|
40
|
+
Every client bundle carries markup inside template literals, and the parser
|
|
41
|
+
rewrote it: `<stop … />` became `<stop …></stop>`, a minified comparison like
|
|
42
|
+
`a<T` was read as an opening tag, and its closing tag was emitted at the end
|
|
43
|
+
of the script. The JS came out syntactically broken.
|
|
44
|
+
|
|
45
|
+
Nothing failed loudly. The zip was produced, the hash printed, the upload
|
|
46
|
+
succeeded — and the app then aborted the Home with *"Security failure: native
|
|
47
|
+
bridges reachable from the iframe"*, because the broken client never got to
|
|
48
|
+
answer the shell's `verify-sandbox`. A message pointing at security, for what
|
|
49
|
+
was a corrupted bundle.
|
|
50
|
+
|
|
51
|
+
Whether a given bundle broke depended on how the minifier happened to name
|
|
52
|
+
its variables, so the same source could build fine one day and not the next.
|
|
53
|
+
The script is now inserted as raw text (`textContent`), byte for byte.
|
|
54
|
+
|
|
55
|
+
### Changed
|
|
56
|
+
|
|
57
|
+
- `SensorLogPayload` docs corrected: the timestamps were described as "UTC
|
|
58
|
+
naive", which is wrong and was the most expensive kind of wrong — a silent
|
|
59
|
+
3-hour shift. Also documents that with a range, `hasMore`/`hasPrevious` are
|
|
60
|
+
computed **within the period**, and that `error` is a diagnostic code, never
|
|
61
|
+
user-facing text.
|
|
62
|
+
|
|
63
|
+
## [0.14.0] — 2026-08-27
|
|
64
|
+
|
|
65
|
+
### Added
|
|
66
|
+
|
|
67
|
+
- **`language` observable key — the client can finally translate.** Until now
|
|
68
|
+
the language never crossed the native→web boundary: the app could be running
|
|
69
|
+
in Portuguese while the client bundle rendered English, inside the same
|
|
70
|
+
screen. The new key carries the app's language state, emitted on attach,
|
|
71
|
+
again right after `handshake-ack` (so subscribing late still gets a value),
|
|
72
|
+
and on every language change in the app Settings — a page already loaded
|
|
73
|
+
re-renders without a restart.
|
|
74
|
+
|
|
75
|
+
The payload carries **two independent sources plus the app's own
|
|
76
|
+
resolution**, and the distinction is the point of the channel:
|
|
77
|
+
|
|
78
|
+
- `deviceLocale` — the raw device locale as a BCP-47 tag (`"pt-BR"`,
|
|
79
|
+
`"es-419"`), region included, never passed through the app's
|
|
80
|
+
supported-language fallback.
|
|
81
|
+
- `appLanguage` — the language explicitly chosen in the app Settings, or
|
|
82
|
+
`null` when the user never chose one. Never a fallback in disguise, so
|
|
83
|
+
`null` is a real signal you can branch on.
|
|
84
|
+
- `resolvedLanguage` — what the native chrome is rendering, for clients that
|
|
85
|
+
would rather stay visually consistent with the frame around them.
|
|
86
|
+
|
|
87
|
+
The app ships four languages (`en`, `pt`, `de`, `fr`) and collapses anything
|
|
88
|
+
else to English; a client bundle has no such limit. Sending only the resolved
|
|
89
|
+
language would mean a device set to Spanish reaching the client as `en` —
|
|
90
|
+
rendering English while holding a Spanish bundle. Both raw values are sent so
|
|
91
|
+
the precedence rule stays the client's own decision.
|
|
92
|
+
|
|
93
|
+
It is **push + pull**. The app pushes on attach, after the handshake ack, and
|
|
94
|
+
on every Settings change — but the first push happens while the WebView is
|
|
95
|
+
still being created, before the client bundle exists. Send the `language`
|
|
96
|
+
command once after your handshake to pull the current state; without it a
|
|
97
|
+
first launch would sit on the client's default until the user changed the
|
|
98
|
+
language by hand.
|
|
99
|
+
|
|
100
|
+
There is no version negotiation: a native host older than `0.14.0` never
|
|
101
|
+
emits this key, so the event simply never arrives — keep a default language
|
|
102
|
+
for that case and read `nativeVersion` from the handshake ack if you need to
|
|
103
|
+
branch on it. `client_demo` subscribes to the key and drives its date
|
|
104
|
+
formatting from it, and its mock previews the interesting case (device in
|
|
105
|
+
Spanish, no explicit choice).
|
|
106
|
+
|
|
107
|
+
### Documentation
|
|
108
|
+
|
|
109
|
+
- The README key catalog was missing the `systemStatus` row; both it and
|
|
110
|
+
`language` are now listed.
|
|
111
|
+
|
|
10
112
|
## [0.13.2] — 2026-08-26
|
|
11
113
|
|
|
12
114
|
### Added
|
package/README.md
CHANGED
|
@@ -219,7 +219,9 @@ are what the JS receives via `window.CondorBridge.emit(key, payload)`.
|
|
|
219
219
|
| `deviceConnection` | `'deviceConnection'` | `DeviceConnectionPayload = { serialNo, isConnect }` | BG → JS (status); JS → BG (`{type, serialNo}`, permissive) |
|
|
220
220
|
| `sensorLog` | `'sensorLog'` | `SensorLogPayload` | BG → JS |
|
|
221
221
|
| `dfu` | `'dfu'` | `Record<string, unknown>` (fallback) | BG → JS |
|
|
222
|
+
| `systemStatus` | `'systemStatus'` | `SystemStatusPayload = { bluetooth, internet, permission, hasDevices, missingOptionalPermissions[], missingEssentialPermissions[], deviceSyncError }` | native → JS (on change + snapshot after handshake) |
|
|
222
223
|
| `navigationBack` | `'navigationBack'` | `NavigationBackPayload = {}` (OS back pressed) | BG → JS (via `BridgeClient.onBack`) |
|
|
224
|
+
| `language` | `'language'` | `LanguagePayload = { deviceLocale, appLanguage, resolvedLanguage }` | native → JS (on change + snapshot after handshake) |
|
|
223
225
|
|
|
224
226
|
**Automatic inference on `Bridge.on`:**
|
|
225
227
|
|
package/dist/cli.cjs
CHANGED
package/dist/cli.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/cli-core.ts"],"sourcesContent":["/**\n * cli.ts — Entry `bin` do `condor-mobile-bridge build`.\n *\n * Fino: parse de argv → `buildBundle` → escreve o `.zip` (mkdir recursivo) →\n * imprime tamanho + SHA-256 (formato que espelha `web-mock-src/build-demo.mjs`)\n * e avisa se passar do limite. Toda a lógica testável vive em `cli-core.ts`.\n */\nimport { mkdirSync, writeFileSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport { buildBundle, parseArgs, USAGE } from './cli-core';\n\nfunction main(): void {\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 const result = buildBundle(args.htmlPath, args.maxBytes);\n\n const outPath = resolve(args.outPath);\n mkdirSync(dirname(outPath), { recursive: true });\n writeFileSync(outPath, result.zipBytes);\n\n const kb = (Buffer.byteLength(result.html) / 1024).toFixed(1);\n console.log(`Built ${args.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 = (args.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\nmain();\n","/**\n * cli-core.ts — Núcleo (puro/testável) do CLI `condor-mobile-bridge build`.\n *\n * Materializa a Frente 1 da Opção A do bundle white-label: pega um\n * `dist/index.html` (build React/Vite do cliente), inlina JS em `<script>`,\n * CSS em `<style>` (resolvendo `url()` em `data:` URI), converte mídias\n * locais em `data:` URI e empacota num `.zip` com um único `index.html` na\n * raiz. Não toca o filesystem de saída — quem escreve o `.zip` é `cli.ts`.\n *\n * Modelo de render do app: `iframe.srcdoc` (origem `null`, sem base URL) só\n * resolve conteúdo inline + `data:` URI — por isso o bundle precisa ser\n * self-contained. Ver `condor_wiki/decisions/2026-06-22_whitelabel-bundle-format.md`.\n *\n * LIMITAÇÃO conhecida: o inliner pós-build assume `dist` single-chunk\n * (um JS + um CSS + assets). Builds com code-split / ES modules que fazem\n * `import` entre chunks quebram em `srcdoc` — o cliente deve buildar\n * single-file (Vite: `vite-plugin-singlefile`; CRA: sem split). Ver README.\n */\nimport { createHash } from 'node:crypto';\nimport { readFileSync } from 'node:fs';\nimport { dirname, extname, resolve } from 'node:path';\nimport { strToU8, zipSync } from 'fflate';\nimport { type HTMLElement, parse } from 'node-html-parser';\n\n/** Default do tamanho máximo do `.zip` (espelha `WhitelabelBundleLimits.maxZipBytes` no app). */\nexport const DEFAULT_MAX_BYTES = 2 * 1024 * 1024;\n\n/** Mtime fixo (>=1980, limite do formato ZIP) → SHA-256 reprodutível entre builds. */\nconst FIXED_MTIME = new Date('2000-01-01T00:00:00Z');\n\n/** Mapa de extensão → MIME para montar `data:` URIs de mídias embutidas. */\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 que NÃO devem ser inlinadas: absolutas externas, protocol-relative, data: já-inline. */\nfunction isExternalOrInline(url: string): boolean {\n return /^(https?:)?\\/\\//i.test(url) || /^data:/i.test(url) || /^(mailto:|tel:|#)/i.test(url);\n}\n\n/** Remove query/hash de uma ref local (`main.js?v=abc#x` → `main.js`). */\nfunction stripQueryHash(url: string): string {\n return url.split(/[?#]/)[0] ?? url;\n}\n\n/**\n * Resolve uma ref local para caminho absoluto no filesystem.\n * `htmlRoot` é a raiz do site (dir do `index.html`); `resourceDir` é o dir do\n * recurso que contém a ref (p/ `url()` no CSS, é o dir do `.css`). Refs que\n * começam com `/` são relativas à raiz do site.\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/** Lê um arquivo local e devolve uma `data:` URI base64. */\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 * Resolve `url(...)` dentro de um bloco CSS, trocando refs locais por `data:`\n * URIs. `cssDir` é o dir do arquivo CSS (ou do HTML, p/ `<style>` inline).\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/** Inlina `<script src=\"local\">` → `<script>…</script>` (escapando `</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 el.set_content(js);\n }\n}\n\n/** Inlina `<link rel=\"stylesheet\" href=\"local\">` → `<style>…</style>` (com `url()` resolvido). */\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/** Resolve `url()` em blocos `<style>` já existentes no 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/** Converte mídias locais referenciadas por atributo (`src`/`href`) em `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 * Lê o HTML de entrada e devolve o HTML consolidado self-contained\n * (JS/CSS inline, mídias em `data:` URI). Não escreve nada em disco.\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 /** HTML consolidado self-contained. */\n html: string;\n /** Bytes do `.zip` (entry único `index.html` na raiz). */\n zipBytes: Uint8Array;\n /** Tamanho do `.zip` em bytes. */\n size: number;\n /** SHA-256 dos bytes do `.zip`, no formato `sha256-<hex>`. */\n hash: string;\n /** `true` se `size` excede `maxBytes`. */\n overLimit: boolean;\n}\n\n/**\n * Constrói o bundle a partir do `index.html` de entrada: inlina, zipa\n * (entry `index.html` na raiz, mtime fixo p/ hash determinístico) e calcula\n * tamanho + SHA-256. Puro — não escreve o `.zip` (responsabilidade do caller).\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\nexport interface CliArgs {\n htmlPath: string;\n outPath: string;\n maxBytes: number;\n help: boolean;\n}\n\nconst DEFAULT_OUT = 'dist/whitelabel-bundle.zip';\n\n/** Parse mínimo de argv (sem deps): `--html/-i`, `--out/-o`, `--max-bytes`, `--help/-h`. */\nexport function parseArgs(argv: string[]): CliArgs {\n const args: CliArgs = {\n htmlPath: '',\n outPath: DEFAULT_OUT,\n maxBytes: DEFAULT_MAX_BYTES,\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 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 'build':\n // Subcomando (`condor-mobile-bridge build …`) — ignorado aqui.\n break;\n default:\n // Primeiro posicional vira o html de entrada (ergonomia: `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 build — builds the self-contained white-label .zip.\n\nUsage:\n npx condor-mobile-bridge build -i <html> [-o <zip>]\n\nOptions:\n -i, --html <path> Input HTML (e.g. dist/index.html). Required.\n -o, --out <path> Output .zip (default: ${DEFAULT_OUT}).\n --max-bytes <n> Warning threshold in bytes (default: ${DEFAULT_MAX_BYTES} = 2 MB).\n -h, --help Show this help.`;\n"],"mappings":";;;;AAOA,IAAAA,kBAAyC;AACzC,IAAAC,oBAAiC;;;ACUjC,yBAA2B;AAC3B,qBAA6B;AAC7B,uBAA0C;AAC1C,oBAAiC;AACjC,8BAAwC;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;AAQA,SAAS,aAAa,UAAkB,aAAqB,KAAqB;AAChF,QAAM,QAAQ,eAAe,GAAG;AAChC,SAAO,MAAM,WAAW,GAAG,QAAI,0BAAQ,UAAU,IAAI,KAAK,EAAE,QAAI,0BAAQ,aAAa,KAAK;AAC5F;AAGO,SAAS,UAAU,UAA0B;AAClD,QAAM,YAAQ,6BAAa,QAAQ;AACnC,QAAM,OAAO,eAAW,0BAAQ,QAAQ,EAAE,YAAY,CAAC,KAAK;AAC5D,SAAO,QAAQ,IAAI,WAAW,MAAM,SAAS,QAAQ,CAAC;AACxD;AAMO,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,SAAK,6BAAa,aAAa,UAAU,UAAU,GAAG,GAAG,OAAO,EAAE;AAAA,MACtE;AAAA,MACA;AAAA,IACF;AACA,OAAG,gBAAgB,KAAK;AACxB,OAAG,YAAY,EAAE;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,kBAAc,6BAAa,SAAS,OAAO,GAAG,cAAU,0BAAQ,OAAO,CAAC;AACpF,OAAG,YAAY,OAAG,+BAAM,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,eAAW,8BAAQ,0BAAQ,QAAQ,CAAC;AAC1C,QAAM,WAAO,mCAAM,6BAAa,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,eAAW,uBAAQ,EAAE,cAAc,KAAC,uBAAQ,IAAI,GAAG,EAAE,OAAO,YAAY,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC;AAChG,QAAM,OAAO,SAAS;AACtB,QAAM,OAAO,cAAU,+BAAW,QAAQ,EAAE,OAAO,QAAQ,EAAE,OAAO,KAAK,CAAC;AAC1E,SAAO,EAAE,MAAM,UAAU,MAAM,MAAM,WAAW,OAAO,SAAS;AAClE;AASA,IAAM,cAAc;AAGb,SAAS,UAAU,MAAyB;AACjD,QAAM,OAAgB;AAAA,IACpB,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,IACV,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;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;AAEH;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,+CAO0B,WAAW;AAAA,8DACI,iBAAiB;AAAA;;;ADzO/E,SAAS,OAAa;AACpB,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,QAAM,SAAS,YAAY,KAAK,UAAU,KAAK,QAAQ;AAEvD,QAAM,cAAU,2BAAQ,KAAK,OAAO;AACpC,qCAAU,2BAAQ,OAAO,GAAG,EAAE,WAAW,KAAK,CAAC;AAC/C,qCAAc,SAAS,OAAO,QAAQ;AAEtC,QAAM,MAAM,OAAO,WAAW,OAAO,IAAI,IAAI,MAAM,QAAQ,CAAC;AAC5D,UAAQ,IAAI,SAAS,KAAK,OAAO,EAAE;AACnC,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,KAAK,YAAY,OAAO,OAAO,QAAQ,CAAC;AACzD,YAAQ;AAAA,MACN;AAAA,mCAA4B,OAAO,IAAI,uBAAuB,OAAO;AAAA,IAEvE;AAAA,EACF;AACF;AAEA,KAAK;","names":["import_node_fs","import_node_path"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/cli-core.ts"],"sourcesContent":["/**\n * cli.ts — Entry `bin` do `condor-mobile-bridge build`.\n *\n * Fino: parse de argv → `buildBundle` → escreve o `.zip` (mkdir recursivo) →\n * imprime tamanho + SHA-256 (formato que espelha `web-mock-src/build-demo.mjs`)\n * e avisa se passar do limite. Toda a lógica testável vive em `cli-core.ts`.\n */\nimport { mkdirSync, writeFileSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport { buildBundle, parseArgs, USAGE } from './cli-core';\n\nfunction main(): void {\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 const result = buildBundle(args.htmlPath, args.maxBytes);\n\n const outPath = resolve(args.outPath);\n mkdirSync(dirname(outPath), { recursive: true });\n writeFileSync(outPath, result.zipBytes);\n\n const kb = (Buffer.byteLength(result.html) / 1024).toFixed(1);\n console.log(`Built ${args.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 = (args.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\nmain();\n","/**\n * cli-core.ts — Núcleo (puro/testável) do CLI `condor-mobile-bridge build`.\n *\n * Materializa a Frente 1 da Opção A do bundle white-label: pega um\n * `dist/index.html` (build React/Vite do cliente), inlina JS em `<script>`,\n * CSS em `<style>` (resolvendo `url()` em `data:` URI), converte mídias\n * locais em `data:` URI e empacota num `.zip` com um único `index.html` na\n * raiz. Não toca o filesystem de saída — quem escreve o `.zip` é `cli.ts`.\n *\n * Modelo de render do app: `iframe.srcdoc` (origem `null`, sem base URL) só\n * resolve conteúdo inline + `data:` URI — por isso o bundle precisa ser\n * self-contained. Ver `condor_wiki/decisions/2026-06-22_whitelabel-bundle-format.md`.\n *\n * LIMITAÇÃO conhecida: o inliner pós-build assume `dist` single-chunk\n * (um JS + um CSS + assets). Builds com code-split / ES modules que fazem\n * `import` entre chunks quebram em `srcdoc` — o cliente deve buildar\n * single-file (Vite: `vite-plugin-singlefile`; CRA: sem split). Ver README.\n */\nimport { createHash } from 'node:crypto';\nimport { readFileSync } from 'node:fs';\nimport { dirname, extname, resolve } from 'node:path';\nimport { strToU8, zipSync } from 'fflate';\nimport { type HTMLElement, parse } from 'node-html-parser';\n\n/** Default do tamanho máximo do `.zip` (espelha `WhitelabelBundleLimits.maxZipBytes` no app). */\nexport const DEFAULT_MAX_BYTES = 2 * 1024 * 1024;\n\n/** Mtime fixo (>=1980, limite do formato ZIP) → SHA-256 reprodutível entre builds. */\nconst FIXED_MTIME = new Date('2000-01-01T00:00:00Z');\n\n/** Mapa de extensão → MIME para montar `data:` URIs de mídias embutidas. */\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 que NÃO devem ser inlinadas: absolutas externas, protocol-relative, data: já-inline. */\nfunction isExternalOrInline(url: string): boolean {\n return /^(https?:)?\\/\\//i.test(url) || /^data:/i.test(url) || /^(mailto:|tel:|#)/i.test(url);\n}\n\n/** Remove query/hash de uma ref local (`main.js?v=abc#x` → `main.js`). */\nfunction stripQueryHash(url: string): string {\n return url.split(/[?#]/)[0] ?? url;\n}\n\n/**\n * Resolve uma ref local para caminho absoluto no filesystem.\n * `htmlRoot` é a raiz do site (dir do `index.html`); `resourceDir` é o dir do\n * recurso que contém a ref (p/ `url()` no CSS, é o dir do `.css`). Refs que\n * começam com `/` são relativas à raiz do site.\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/** Lê um arquivo local e devolve uma `data:` URI base64. */\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 * Resolve `url(...)` dentro de um bloco CSS, trocando refs locais por `data:`\n * URIs. `cssDir` é o dir do arquivo CSS (ou do HTML, p/ `<style>` inline).\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/** Inlina `<script src=\"local\">` → `<script>…</script>` (escapando `</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`, nunca `set_content`: este último **parseia** o que recebe\n // como HTML. Todo bundle carrega markup dentro de strings JS, e o parser o\n // reescreve — `<stop … />` vira `<stop …></stop>`, uma comparação `a<T` é\n // lida como abertura de tag e o fechamento correspondente é cuspido no fim\n // do script. O JS sai sintaticamente quebrado, o zip é gerado sem erro, e\n // o app aborta a Home com \"native bridges reachable from the iframe\" —\n // porque o cliente nunca chegou a responder o verify-sandbox.\n el.textContent = js;\n }\n}\n\n/** Inlina `<link rel=\"stylesheet\" href=\"local\">` → `<style>…</style>` (com `url()` resolvido). */\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/** Resolve `url()` em blocos `<style>` já existentes no 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/** Converte mídias locais referenciadas por atributo (`src`/`href`) em `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 * Lê o HTML de entrada e devolve o HTML consolidado self-contained\n * (JS/CSS inline, mídias em `data:` URI). Não escreve nada em disco.\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 /** HTML consolidado self-contained. */\n html: string;\n /** Bytes do `.zip` (entry único `index.html` na raiz). */\n zipBytes: Uint8Array;\n /** Tamanho do `.zip` em bytes. */\n size: number;\n /** SHA-256 dos bytes do `.zip`, no formato `sha256-<hex>`. */\n hash: string;\n /** `true` se `size` excede `maxBytes`. */\n overLimit: boolean;\n}\n\n/**\n * Constrói o bundle a partir do `index.html` de entrada: inlina, zipa\n * (entry `index.html` na raiz, mtime fixo p/ hash determinístico) e calcula\n * tamanho + SHA-256. Puro — não escreve o `.zip` (responsabilidade do caller).\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\nexport interface CliArgs {\n htmlPath: string;\n outPath: string;\n maxBytes: number;\n help: boolean;\n}\n\nconst DEFAULT_OUT = 'dist/whitelabel-bundle.zip';\n\n/** Parse mínimo de argv (sem deps): `--html/-i`, `--out/-o`, `--max-bytes`, `--help/-h`. */\nexport function parseArgs(argv: string[]): CliArgs {\n const args: CliArgs = {\n htmlPath: '',\n outPath: DEFAULT_OUT,\n maxBytes: DEFAULT_MAX_BYTES,\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 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 'build':\n // Subcomando (`condor-mobile-bridge build …`) — ignorado aqui.\n break;\n default:\n // Primeiro posicional vira o html de entrada (ergonomia: `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 build — builds the self-contained white-label .zip.\n\nUsage:\n npx condor-mobile-bridge build -i <html> [-o <zip>]\n\nOptions:\n -i, --html <path> Input HTML (e.g. dist/index.html). Required.\n -o, --out <path> Output .zip (default: ${DEFAULT_OUT}).\n --max-bytes <n> Warning threshold in bytes (default: ${DEFAULT_MAX_BYTES} = 2 MB).\n -h, --help Show this help.`;\n"],"mappings":"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","names":["import_node_fs","import_node_path"]}
|
package/dist/cli.js
CHANGED
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/cli-core.ts"],"sourcesContent":["/**\n * cli.ts — Entry `bin` do `condor-mobile-bridge build`.\n *\n * Fino: parse de argv → `buildBundle` → escreve o `.zip` (mkdir recursivo) →\n * imprime tamanho + SHA-256 (formato que espelha `web-mock-src/build-demo.mjs`)\n * e avisa se passar do limite. Toda a lógica testável vive em `cli-core.ts`.\n */\nimport { mkdirSync, writeFileSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport { buildBundle, parseArgs, USAGE } from './cli-core';\n\nfunction main(): void {\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 const result = buildBundle(args.htmlPath, args.maxBytes);\n\n const outPath = resolve(args.outPath);\n mkdirSync(dirname(outPath), { recursive: true });\n writeFileSync(outPath, result.zipBytes);\n\n const kb = (Buffer.byteLength(result.html) / 1024).toFixed(1);\n console.log(`Built ${args.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 = (args.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\nmain();\n","/**\n * cli-core.ts — Núcleo (puro/testável) do CLI `condor-mobile-bridge build`.\n *\n * Materializa a Frente 1 da Opção A do bundle white-label: pega um\n * `dist/index.html` (build React/Vite do cliente), inlina JS em `<script>`,\n * CSS em `<style>` (resolvendo `url()` em `data:` URI), converte mídias\n * locais em `data:` URI e empacota num `.zip` com um único `index.html` na\n * raiz. Não toca o filesystem de saída — quem escreve o `.zip` é `cli.ts`.\n *\n * Modelo de render do app: `iframe.srcdoc` (origem `null`, sem base URL) só\n * resolve conteúdo inline + `data:` URI — por isso o bundle precisa ser\n * self-contained. Ver `condor_wiki/decisions/2026-06-22_whitelabel-bundle-format.md`.\n *\n * LIMITAÇÃO conhecida: o inliner pós-build assume `dist` single-chunk\n * (um JS + um CSS + assets). Builds com code-split / ES modules que fazem\n * `import` entre chunks quebram em `srcdoc` — o cliente deve buildar\n * single-file (Vite: `vite-plugin-singlefile`; CRA: sem split). Ver README.\n */\nimport { createHash } from 'node:crypto';\nimport { readFileSync } from 'node:fs';\nimport { dirname, extname, resolve } from 'node:path';\nimport { strToU8, zipSync } from 'fflate';\nimport { type HTMLElement, parse } from 'node-html-parser';\n\n/** Default do tamanho máximo do `.zip` (espelha `WhitelabelBundleLimits.maxZipBytes` no app). */\nexport const DEFAULT_MAX_BYTES = 2 * 1024 * 1024;\n\n/** Mtime fixo (>=1980, limite do formato ZIP) → SHA-256 reprodutível entre builds. */\nconst FIXED_MTIME = new Date('2000-01-01T00:00:00Z');\n\n/** Mapa de extensão → MIME para montar `data:` URIs de mídias embutidas. */\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 que NÃO devem ser inlinadas: absolutas externas, protocol-relative, data: já-inline. */\nfunction isExternalOrInline(url: string): boolean {\n return /^(https?:)?\\/\\//i.test(url) || /^data:/i.test(url) || /^(mailto:|tel:|#)/i.test(url);\n}\n\n/** Remove query/hash de uma ref local (`main.js?v=abc#x` → `main.js`). */\nfunction stripQueryHash(url: string): string {\n return url.split(/[?#]/)[0] ?? url;\n}\n\n/**\n * Resolve uma ref local para caminho absoluto no filesystem.\n * `htmlRoot` é a raiz do site (dir do `index.html`); `resourceDir` é o dir do\n * recurso que contém a ref (p/ `url()` no CSS, é o dir do `.css`). Refs que\n * começam com `/` são relativas à raiz do site.\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/** Lê um arquivo local e devolve uma `data:` URI base64. */\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 * Resolve `url(...)` dentro de um bloco CSS, trocando refs locais por `data:`\n * URIs. `cssDir` é o dir do arquivo CSS (ou do HTML, p/ `<style>` inline).\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/** Inlina `<script src=\"local\">` → `<script>…</script>` (escapando `</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 el.set_content(js);\n }\n}\n\n/** Inlina `<link rel=\"stylesheet\" href=\"local\">` → `<style>…</style>` (com `url()` resolvido). */\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/** Resolve `url()` em blocos `<style>` já existentes no 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/** Converte mídias locais referenciadas por atributo (`src`/`href`) em `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 * Lê o HTML de entrada e devolve o HTML consolidado self-contained\n * (JS/CSS inline, mídias em `data:` URI). Não escreve nada em disco.\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 /** HTML consolidado self-contained. */\n html: string;\n /** Bytes do `.zip` (entry único `index.html` na raiz). */\n zipBytes: Uint8Array;\n /** Tamanho do `.zip` em bytes. */\n size: number;\n /** SHA-256 dos bytes do `.zip`, no formato `sha256-<hex>`. */\n hash: string;\n /** `true` se `size` excede `maxBytes`. */\n overLimit: boolean;\n}\n\n/**\n * Constrói o bundle a partir do `index.html` de entrada: inlina, zipa\n * (entry `index.html` na raiz, mtime fixo p/ hash determinístico) e calcula\n * tamanho + SHA-256. Puro — não escreve o `.zip` (responsabilidade do caller).\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\nexport interface CliArgs {\n htmlPath: string;\n outPath: string;\n maxBytes: number;\n help: boolean;\n}\n\nconst DEFAULT_OUT = 'dist/whitelabel-bundle.zip';\n\n/** Parse mínimo de argv (sem deps): `--html/-i`, `--out/-o`, `--max-bytes`, `--help/-h`. */\nexport function parseArgs(argv: string[]): CliArgs {\n const args: CliArgs = {\n htmlPath: '',\n outPath: DEFAULT_OUT,\n maxBytes: DEFAULT_MAX_BYTES,\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 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 'build':\n // Subcomando (`condor-mobile-bridge build …`) — ignorado aqui.\n break;\n default:\n // Primeiro posicional vira o html de entrada (ergonomia: `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 build — builds the self-contained white-label .zip.\n\nUsage:\n npx condor-mobile-bridge build -i <html> [-o <zip>]\n\nOptions:\n -i, --html <path> Input HTML (e.g. dist/index.html). Required.\n -o, --out <path> Output .zip (default: ${DEFAULT_OUT}).\n --max-bytes <n> Warning threshold in bytes (default: ${DEFAULT_MAX_BYTES} = 2 MB).\n -h, --help Show this help.`;\n"],"mappings":"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","names":["dirname","resolve","resolve","dirname"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/cli-core.ts"],"sourcesContent":["/**\n * cli.ts — Entry `bin` do `condor-mobile-bridge build`.\n *\n * Fino: parse de argv → `buildBundle` → escreve o `.zip` (mkdir recursivo) →\n * imprime tamanho + SHA-256 (formato que espelha `web-mock-src/build-demo.mjs`)\n * e avisa se passar do limite. Toda a lógica testável vive em `cli-core.ts`.\n */\nimport { mkdirSync, writeFileSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport { buildBundle, parseArgs, USAGE } from './cli-core';\n\nfunction main(): void {\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 const result = buildBundle(args.htmlPath, args.maxBytes);\n\n const outPath = resolve(args.outPath);\n mkdirSync(dirname(outPath), { recursive: true });\n writeFileSync(outPath, result.zipBytes);\n\n const kb = (Buffer.byteLength(result.html) / 1024).toFixed(1);\n console.log(`Built ${args.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 = (args.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\nmain();\n","/**\n * cli-core.ts — Núcleo (puro/testável) do CLI `condor-mobile-bridge build`.\n *\n * Materializa a Frente 1 da Opção A do bundle white-label: pega um\n * `dist/index.html` (build React/Vite do cliente), inlina JS em `<script>`,\n * CSS em `<style>` (resolvendo `url()` em `data:` URI), converte mídias\n * locais em `data:` URI e empacota num `.zip` com um único `index.html` na\n * raiz. Não toca o filesystem de saída — quem escreve o `.zip` é `cli.ts`.\n *\n * Modelo de render do app: `iframe.srcdoc` (origem `null`, sem base URL) só\n * resolve conteúdo inline + `data:` URI — por isso o bundle precisa ser\n * self-contained. Ver `condor_wiki/decisions/2026-06-22_whitelabel-bundle-format.md`.\n *\n * LIMITAÇÃO conhecida: o inliner pós-build assume `dist` single-chunk\n * (um JS + um CSS + assets). Builds com code-split / ES modules que fazem\n * `import` entre chunks quebram em `srcdoc` — o cliente deve buildar\n * single-file (Vite: `vite-plugin-singlefile`; CRA: sem split). Ver README.\n */\nimport { createHash } from 'node:crypto';\nimport { readFileSync } from 'node:fs';\nimport { dirname, extname, resolve } from 'node:path';\nimport { strToU8, zipSync } from 'fflate';\nimport { type HTMLElement, parse } from 'node-html-parser';\n\n/** Default do tamanho máximo do `.zip` (espelha `WhitelabelBundleLimits.maxZipBytes` no app). */\nexport const DEFAULT_MAX_BYTES = 2 * 1024 * 1024;\n\n/** Mtime fixo (>=1980, limite do formato ZIP) → SHA-256 reprodutível entre builds. */\nconst FIXED_MTIME = new Date('2000-01-01T00:00:00Z');\n\n/** Mapa de extensão → MIME para montar `data:` URIs de mídias embutidas. */\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 que NÃO devem ser inlinadas: absolutas externas, protocol-relative, data: já-inline. */\nfunction isExternalOrInline(url: string): boolean {\n return /^(https?:)?\\/\\//i.test(url) || /^data:/i.test(url) || /^(mailto:|tel:|#)/i.test(url);\n}\n\n/** Remove query/hash de uma ref local (`main.js?v=abc#x` → `main.js`). */\nfunction stripQueryHash(url: string): string {\n return url.split(/[?#]/)[0] ?? url;\n}\n\n/**\n * Resolve uma ref local para caminho absoluto no filesystem.\n * `htmlRoot` é a raiz do site (dir do `index.html`); `resourceDir` é o dir do\n * recurso que contém a ref (p/ `url()` no CSS, é o dir do `.css`). Refs que\n * começam com `/` são relativas à raiz do site.\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/** Lê um arquivo local e devolve uma `data:` URI base64. */\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 * Resolve `url(...)` dentro de um bloco CSS, trocando refs locais por `data:`\n * URIs. `cssDir` é o dir do arquivo CSS (ou do HTML, p/ `<style>` inline).\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/** Inlina `<script src=\"local\">` → `<script>…</script>` (escapando `</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`, nunca `set_content`: este último **parseia** o que recebe\n // como HTML. Todo bundle carrega markup dentro de strings JS, e o parser o\n // reescreve — `<stop … />` vira `<stop …></stop>`, uma comparação `a<T` é\n // lida como abertura de tag e o fechamento correspondente é cuspido no fim\n // do script. O JS sai sintaticamente quebrado, o zip é gerado sem erro, e\n // o app aborta a Home com \"native bridges reachable from the iframe\" —\n // porque o cliente nunca chegou a responder o verify-sandbox.\n el.textContent = js;\n }\n}\n\n/** Inlina `<link rel=\"stylesheet\" href=\"local\">` → `<style>…</style>` (com `url()` resolvido). */\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/** Resolve `url()` em blocos `<style>` já existentes no 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/** Converte mídias locais referenciadas por atributo (`src`/`href`) em `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 * Lê o HTML de entrada e devolve o HTML consolidado self-contained\n * (JS/CSS inline, mídias em `data:` URI). Não escreve nada em disco.\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 /** HTML consolidado self-contained. */\n html: string;\n /** Bytes do `.zip` (entry único `index.html` na raiz). */\n zipBytes: Uint8Array;\n /** Tamanho do `.zip` em bytes. */\n size: number;\n /** SHA-256 dos bytes do `.zip`, no formato `sha256-<hex>`. */\n hash: string;\n /** `true` se `size` excede `maxBytes`. */\n overLimit: boolean;\n}\n\n/**\n * Constrói o bundle a partir do `index.html` de entrada: inlina, zipa\n * (entry `index.html` na raiz, mtime fixo p/ hash determinístico) e calcula\n * tamanho + SHA-256. Puro — não escreve o `.zip` (responsabilidade do caller).\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\nexport interface CliArgs {\n htmlPath: string;\n outPath: string;\n maxBytes: number;\n help: boolean;\n}\n\nconst DEFAULT_OUT = 'dist/whitelabel-bundle.zip';\n\n/** Parse mínimo de argv (sem deps): `--html/-i`, `--out/-o`, `--max-bytes`, `--help/-h`. */\nexport function parseArgs(argv: string[]): CliArgs {\n const args: CliArgs = {\n htmlPath: '',\n outPath: DEFAULT_OUT,\n maxBytes: DEFAULT_MAX_BYTES,\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 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 'build':\n // Subcomando (`condor-mobile-bridge build …`) — ignorado aqui.\n break;\n default:\n // Primeiro posicional vira o html de entrada (ergonomia: `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 build — builds the self-contained white-label .zip.\n\nUsage:\n npx condor-mobile-bridge build -i <html> [-o <zip>]\n\nOptions:\n -i, --html <path> Input HTML (e.g. dist/index.html). Required.\n -o, --out <path> Output .zip (default: ${DEFAULT_OUT}).\n --max-bytes <n> Warning threshold in bytes (default: ${DEFAULT_MAX_BYTES} = 2 MB).\n -h, --help Show this help.`;\n"],"mappings":"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","names":["dirname","resolve","resolve","dirname"]}
|
package/dist/index.cjs
CHANGED
|
@@ -56,7 +56,15 @@ var BRIDGE_KEYS = {
|
|
|
56
56
|
* UMA página interna. Emitido só quando o nativo decidiu delegar o back
|
|
57
57
|
* ao cliente. Vide `BridgeClient.onBack`.
|
|
58
58
|
*/
|
|
59
|
-
navigationBack: "navigationBack"
|
|
59
|
+
navigationBack: "navigationBack",
|
|
60
|
+
/**
|
|
61
|
+
* Native → client: the app's language state. Carries the raw device locale
|
|
62
|
+
* AND the language explicitly chosen in the app Settings, so the client can
|
|
63
|
+
* apply its own precedence rule — including rendering a language the native
|
|
64
|
+
* chrome does not support. Emitted on attach, after `handshake-ack`, and on
|
|
65
|
+
* every change in Settings. Vide `LanguagePayload`.
|
|
66
|
+
*/
|
|
67
|
+
language: "language"
|
|
60
68
|
};
|
|
61
69
|
|
|
62
70
|
// src/internal/dev-panel.ts
|
|
@@ -623,6 +631,7 @@ var Bridge = {
|
|
|
623
631
|
// src/bridge-client.ts
|
|
624
632
|
var PROTOCOL_VERSION2 = "0.5.0";
|
|
625
633
|
var HANDSHAKE_TIMEOUT_MS = 5e3;
|
|
634
|
+
var SENSOR_LOG_TIMEOUT_MS = 1e4;
|
|
626
635
|
var subscribers2 = /* @__PURE__ */ new Map();
|
|
627
636
|
var pendingHandshake2 = null;
|
|
628
637
|
var messageListenerInstalled = false;
|
|
@@ -709,6 +718,30 @@ var BridgeClient = {
|
|
|
709
718
|
openSettings() {
|
|
710
719
|
this.send("openSettings");
|
|
711
720
|
},
|
|
721
|
+
fetchSensorLog(query, opts) {
|
|
722
|
+
const timeoutMs = opts?.timeoutMs ?? SENSOR_LOG_TIMEOUT_MS;
|
|
723
|
+
return new Promise((resolve, reject) => {
|
|
724
|
+
let settled = false;
|
|
725
|
+
const unsubscribe = this.on(BRIDGE_KEYS.sensorLog, (payload) => {
|
|
726
|
+
if (settled || payload.requestId !== query.requestId) return;
|
|
727
|
+
settled = true;
|
|
728
|
+
clearTimeout(timer);
|
|
729
|
+
unsubscribe();
|
|
730
|
+
resolve(payload);
|
|
731
|
+
});
|
|
732
|
+
const timer = setTimeout(() => {
|
|
733
|
+
if (settled) return;
|
|
734
|
+
settled = true;
|
|
735
|
+
unsubscribe();
|
|
736
|
+
reject(
|
|
737
|
+
new Error(
|
|
738
|
+
`BridgeClient: sensorLog timeout after ${timeoutMs}ms (requestId=${query.requestId})`
|
|
739
|
+
)
|
|
740
|
+
);
|
|
741
|
+
}, timeoutMs);
|
|
742
|
+
this.send("sensorLog", { ...query });
|
|
743
|
+
});
|
|
744
|
+
},
|
|
712
745
|
requestPermission(permission) {
|
|
713
746
|
this.send("requestPermission", { permission });
|
|
714
747
|
},
|