@condorinst/mobile-bridge 0.18.0 → 0.19.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/AGENTS.md +120 -0
- package/CHANGELOG.md +57 -0
- package/README.md +66 -11
- package/contract/bridge-contract.json +198 -0
- package/dist/cli.cjs +230 -17
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +231 -18
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +64 -64
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +442 -396
- package/dist/index.d.ts +442 -396
- package/dist/index.js +64 -64
- package/dist/index.js.map +1 -1
- package/docs/agent-guide.md +209 -0
- package/docs/bridge-contract.md +249 -0
- package/docs/client-guide.md +387 -0
- package/docs/limits.md +111 -0
- package/docs/pilot-onboarding.md +103 -0
- package/docs/troubleshooting.md +150 -0
- package/examples/minimal-home/AGENTS.md +28 -0
- package/examples/minimal-home/README.md +42 -0
- package/examples/minimal-home/build.mjs +31 -0
- package/examples/minimal-home/index.html +77 -0
- package/examples/minimal-home/package.json +23 -0
- package/examples/minimal-home/src/main.ts +161 -0
- package/examples/minimal-home/tsconfig.json +14 -0
- package/llms.txt +44 -0
- package/package.json +15 -2
package/dist/cli.js
CHANGED
|
@@ -8,7 +8,7 @@ import { dirname as dirname2, resolve as resolve2 } from "path";
|
|
|
8
8
|
import { createHash } from "crypto";
|
|
9
9
|
import { readFileSync } from "fs";
|
|
10
10
|
import { dirname, extname, resolve } from "path";
|
|
11
|
-
import { strToU8, zipSync } from "fflate";
|
|
11
|
+
import { strToU8, unzipSync, zipSync } from "fflate";
|
|
12
12
|
import { parse } from "node-html-parser";
|
|
13
13
|
var DEFAULT_MAX_BYTES = 2 * 1024 * 1024;
|
|
14
14
|
var FIXED_MTIME = /* @__PURE__ */ new Date("2000-01-01T00:00:00Z");
|
|
@@ -120,12 +120,169 @@ function buildBundle(htmlPath, maxBytes = DEFAULT_MAX_BYTES) {
|
|
|
120
120
|
const hash = `sha256-${createHash("sha256").update(zipBytes).digest("hex")}`;
|
|
121
121
|
return { html, zipBytes, size, hash, overLimit: size > maxBytes };
|
|
122
122
|
}
|
|
123
|
+
var BLOCKED_API_RULES = [
|
|
124
|
+
{
|
|
125
|
+
rule: "web-storage",
|
|
126
|
+
pattern: /\b(?:localStorage|sessionStorage)\b/,
|
|
127
|
+
message: "Web Storage throws SecurityError: the client iframe has a null origin. Keep state in memory \u2014 it is lost when the WebView reloads."
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
rule: "indexed-db",
|
|
131
|
+
pattern: /\bindexedDB\b/,
|
|
132
|
+
message: "IndexedDB is unavailable on a null origin. Keep state in memory."
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
rule: "cookies",
|
|
136
|
+
pattern: /document\s*\.\s*cookie/,
|
|
137
|
+
message: "Cookies cannot be written from a null origin; the assignment is a no-op."
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
rule: "dynamic-eval",
|
|
141
|
+
pattern: /\beval\s*\(|new\s+Function\s*\(/,
|
|
142
|
+
message: "The shell CSP has no 'unsafe-eval'; eval() and new Function() are blocked. Build without eval-based sourcemaps."
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
rule: "service-worker",
|
|
146
|
+
pattern: /serviceWorker/,
|
|
147
|
+
message: "Service workers cannot be registered from a null origin."
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
rule: "send-beacon",
|
|
151
|
+
pattern: /sendBeacon/,
|
|
152
|
+
message: "navigator.sendBeacon is neutralized by the shell before your bundle runs (pen test PT-10). The call returns without sending."
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
rule: "popups",
|
|
156
|
+
pattern: /window\s*\.\s*open\s*\(/,
|
|
157
|
+
message: "The sandbox has no allow-popups; window.open() returns null."
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
rule: "top-navigation",
|
|
161
|
+
pattern: /window\s*\.\s*top\b/,
|
|
162
|
+
message: "The sandbox has no allow-top-navigation; reading or navigating window.top throws SecurityError."
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
rule: "external-network",
|
|
166
|
+
pattern: /\bfetch\s*\(|\bXMLHttpRequest\b|\bWebSocket\s*\(/,
|
|
167
|
+
message: "connect-src 'self' on a null origin blocks every network call. Request data through the bridge instead (e.g. BridgeClient.fetchSensorLog)."
|
|
168
|
+
}
|
|
169
|
+
];
|
|
170
|
+
function lintClientHtml(html) {
|
|
171
|
+
const findings = [];
|
|
172
|
+
for (const { rule, pattern, message } of BLOCKED_API_RULES) {
|
|
173
|
+
if (pattern.test(html)) findings.push({ severity: "warning", rule, message });
|
|
174
|
+
}
|
|
175
|
+
return findings;
|
|
176
|
+
}
|
|
177
|
+
function checkSelfContained(html) {
|
|
178
|
+
const findings = [];
|
|
179
|
+
const root = parse(html, { comment: true, voidTag: { closingSlash: true } });
|
|
180
|
+
for (const el of root.querySelectorAll("script[src]")) {
|
|
181
|
+
const src = el.getAttribute("src") ?? "";
|
|
182
|
+
if (/^data:/i.test(src)) continue;
|
|
183
|
+
findings.push({
|
|
184
|
+
severity: "error",
|
|
185
|
+
rule: "not-self-contained",
|
|
186
|
+
message: `<script src="${src}"> \u2014 the app rejects bundles with external scripts. Inline it (npx condor-mobile-bridge build does this for you).`
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
for (const el of root.querySelectorAll('link[rel="stylesheet"]')) {
|
|
190
|
+
const href = el.getAttribute("href") ?? "";
|
|
191
|
+
if (/^data:/i.test(href)) continue;
|
|
192
|
+
findings.push({
|
|
193
|
+
severity: "error",
|
|
194
|
+
rule: "not-self-contained",
|
|
195
|
+
message: `<link rel="stylesheet" href="${href}"> \u2014 the app rejects bundles with external stylesheets. Inline it as <style>.`
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
for (const el of root.querySelectorAll("img[src]")) {
|
|
199
|
+
const src = el.getAttribute("src") ?? "";
|
|
200
|
+
if (/^data:/i.test(src) || isExternalOrInline(src)) continue;
|
|
201
|
+
findings.push({
|
|
202
|
+
severity: "warning",
|
|
203
|
+
rule: "relative-asset",
|
|
204
|
+
message: `<img src="${src}"> is relative and will not resolve inside srcdoc. Embed it as a data: URI.`
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
return findings;
|
|
208
|
+
}
|
|
209
|
+
function errorsFirst(findings) {
|
|
210
|
+
return [...findings].sort(
|
|
211
|
+
(a, b) => a.severity === b.severity ? 0 : a.severity === "error" ? -1 : 1
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
function validateZipBytes(zipBytes, maxBytes = DEFAULT_MAX_BYTES) {
|
|
215
|
+
const findings = [];
|
|
216
|
+
const size = zipBytes.length;
|
|
217
|
+
const hash = `sha256-${createHash("sha256").update(zipBytes).digest("hex")}`;
|
|
218
|
+
if (size > maxBytes) {
|
|
219
|
+
findings.push({
|
|
220
|
+
severity: "error",
|
|
221
|
+
rule: "too-large",
|
|
222
|
+
message: `The .zip is ${size} bytes, over the ${maxBytes}-byte limit. The app rejects it and falls back to the default screen.`
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
let entries;
|
|
226
|
+
try {
|
|
227
|
+
entries = unzipSync(zipBytes);
|
|
228
|
+
} catch (err) {
|
|
229
|
+
findings.push({
|
|
230
|
+
severity: "error",
|
|
231
|
+
rule: "unreadable-zip",
|
|
232
|
+
message: `Could not read the .zip: ${err instanceof Error ? err.message : String(err)}`
|
|
233
|
+
});
|
|
234
|
+
return { ok: false, entry: null, size, hash, findings: errorsFirst(findings) };
|
|
235
|
+
}
|
|
236
|
+
const names = Object.keys(entries).filter((n) => !n.endsWith("/"));
|
|
237
|
+
let entry = null;
|
|
238
|
+
if (names.includes("index.html")) {
|
|
239
|
+
entry = "index.html";
|
|
240
|
+
} else {
|
|
241
|
+
const firstHtml = names.find((n) => n.toLowerCase().endsWith(".html"));
|
|
242
|
+
if (firstHtml) {
|
|
243
|
+
entry = firstHtml;
|
|
244
|
+
findings.push({
|
|
245
|
+
severity: "warning",
|
|
246
|
+
rule: "entry-fallback",
|
|
247
|
+
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.`
|
|
248
|
+
});
|
|
249
|
+
} else {
|
|
250
|
+
findings.push({
|
|
251
|
+
severity: "error",
|
|
252
|
+
rule: "entry-missing",
|
|
253
|
+
message: "The .zip carries no .html document. The app cannot render this bundle."
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (entry) {
|
|
258
|
+
const html = Buffer.from(entries[entry]).toString("utf-8");
|
|
259
|
+
findings.push(...checkSelfContained(html), ...lintClientHtml(html));
|
|
260
|
+
}
|
|
261
|
+
const ok = !findings.some((f) => f.severity === "error");
|
|
262
|
+
return { ok, entry, size, hash, findings: errorsFirst(findings) };
|
|
263
|
+
}
|
|
264
|
+
function validateBundle(inputPath, maxBytes = DEFAULT_MAX_BYTES) {
|
|
265
|
+
if (extname(inputPath).toLowerCase() === ".zip") {
|
|
266
|
+
return validateZipBytes(readFileSync(inputPath), maxBytes);
|
|
267
|
+
}
|
|
268
|
+
const html = readFileSync(inputPath, "utf-8");
|
|
269
|
+
const findings = [...checkSelfContained(html), ...lintClientHtml(html)];
|
|
270
|
+
return {
|
|
271
|
+
ok: !findings.some((f) => f.severity === "error"),
|
|
272
|
+
entry: inputPath,
|
|
273
|
+
size: null,
|
|
274
|
+
hash: null,
|
|
275
|
+
findings: errorsFirst(findings)
|
|
276
|
+
};
|
|
277
|
+
}
|
|
123
278
|
var DEFAULT_OUT = "dist/whitelabel-bundle.zip";
|
|
124
279
|
function parseArgs(argv) {
|
|
125
280
|
const args = {
|
|
281
|
+
command: "build",
|
|
126
282
|
htmlPath: "",
|
|
127
283
|
outPath: DEFAULT_OUT,
|
|
128
284
|
maxBytes: DEFAULT_MAX_BYTES,
|
|
285
|
+
strict: false,
|
|
129
286
|
help: false
|
|
130
287
|
};
|
|
131
288
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -137,6 +294,7 @@ function parseArgs(argv) {
|
|
|
137
294
|
break;
|
|
138
295
|
case "--html":
|
|
139
296
|
case "-i":
|
|
297
|
+
case "--input":
|
|
140
298
|
args.htmlPath = argv[++i] ?? "";
|
|
141
299
|
break;
|
|
142
300
|
case "--out":
|
|
@@ -146,7 +304,14 @@ function parseArgs(argv) {
|
|
|
146
304
|
case "--max-bytes":
|
|
147
305
|
args.maxBytes = Number.parseInt(argv[++i] ?? "", 10) || DEFAULT_MAX_BYTES;
|
|
148
306
|
break;
|
|
307
|
+
case "--strict":
|
|
308
|
+
args.strict = true;
|
|
309
|
+
break;
|
|
149
310
|
case "build":
|
|
311
|
+
args.command = "build";
|
|
312
|
+
break;
|
|
313
|
+
case "validate":
|
|
314
|
+
args.command = "validate";
|
|
150
315
|
break;
|
|
151
316
|
default:
|
|
152
317
|
if (token && !token.startsWith("-") && !args.htmlPath) args.htmlPath = token;
|
|
@@ -154,40 +319,88 @@ function parseArgs(argv) {
|
|
|
154
319
|
}
|
|
155
320
|
return args;
|
|
156
321
|
}
|
|
157
|
-
var USAGE = `condor-mobile-bridge
|
|
322
|
+
var USAGE = `condor-mobile-bridge \u2014 tooling for Condor Canvas client bundles.
|
|
158
323
|
|
|
159
324
|
Usage:
|
|
160
|
-
npx condor-mobile-bridge build
|
|
325
|
+
npx condor-mobile-bridge build -i <html> [-o <zip>]
|
|
326
|
+
npx condor-mobile-bridge validate -i <zip|html> [--strict]
|
|
327
|
+
|
|
328
|
+
Commands:
|
|
329
|
+
build Inline a dist/index.html into a self-contained .zip.
|
|
330
|
+
validate Check a bundle against every rule the Canvas app enforces, plus
|
|
331
|
+
the APIs the sandbox and the CSP block at runtime.
|
|
161
332
|
|
|
162
333
|
Options:
|
|
163
|
-
-i, --html <path> Input
|
|
164
|
-
-o, --out <path>
|
|
165
|
-
--max-bytes <n>
|
|
334
|
+
-i, --html <path> Input. build: dist/index.html. validate: .zip or .html. Required.
|
|
335
|
+
-o, --out <path> build only: output .zip (default: ${DEFAULT_OUT}).
|
|
336
|
+
--max-bytes <n> Size limit in bytes (default: ${DEFAULT_MAX_BYTES} = 2 MB).
|
|
337
|
+
--strict validate only: exit 1 on warnings too, not just errors.
|
|
166
338
|
-h, --help Show this help.`;
|
|
167
339
|
|
|
168
340
|
// src/cli.ts
|
|
169
|
-
function
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
}
|
|
175
|
-
const result = buildBundle(args.htmlPath, args.maxBytes);
|
|
176
|
-
const outPath = resolve2(args.outPath);
|
|
177
|
-
mkdirSync(dirname2(outPath), { recursive: true });
|
|
178
|
-
writeFileSync(outPath, result.zipBytes);
|
|
341
|
+
function runBuild(htmlPath, outPath, maxBytes) {
|
|
342
|
+
const result = buildBundle(htmlPath, maxBytes);
|
|
343
|
+
const resolvedOut = resolve2(outPath);
|
|
344
|
+
mkdirSync(dirname2(resolvedOut), { recursive: true });
|
|
345
|
+
writeFileSync(resolvedOut, result.zipBytes);
|
|
179
346
|
const kb = (Buffer.byteLength(result.html) / 1024).toFixed(1);
|
|
180
|
-
console.log(`Built ${
|
|
347
|
+
console.log(`Built ${outPath}`);
|
|
181
348
|
console.log(` entry: index.html (${kb} KB self-contained)`);
|
|
182
349
|
console.log(` bundleSize (zip): ${result.size} bytes`);
|
|
183
350
|
console.log(` bundleHash (zip): ${result.hash}`);
|
|
184
351
|
if (result.overLimit) {
|
|
185
|
-
const limitMb = (
|
|
352
|
+
const limitMb = (maxBytes / (1024 * 1024)).toFixed(1);
|
|
186
353
|
console.warn(
|
|
187
354
|
`
|
|
188
355
|
\u26A0\uFE0F WARNING: the .zip (${result.size} bytes) exceeds the ${limitMb} MB limit \u2014 the app will REJECT this bundle (falls back to the default screen). Reduce inline assets/media.`
|
|
189
356
|
);
|
|
190
357
|
}
|
|
358
|
+
console.log("\nNext: npx condor-mobile-bridge validate -i " + outPath + " --strict");
|
|
359
|
+
}
|
|
360
|
+
function reportValidation(input, result, strict) {
|
|
361
|
+
console.log(`Validating ${input}`);
|
|
362
|
+
if (result.entry) console.log(` entry: ${result.entry}`);
|
|
363
|
+
if (result.size !== null) console.log(` bundleSize (zip): ${result.size} bytes`);
|
|
364
|
+
if (result.hash !== null) console.log(` bundleHash (zip): ${result.hash}`);
|
|
365
|
+
const errors = result.findings.filter((f) => f.severity === "error");
|
|
366
|
+
const warnings = result.findings.filter((f) => f.severity === "warning");
|
|
367
|
+
if (result.findings.length === 0) {
|
|
368
|
+
console.log("\n\u2705 No findings. This bundle conforms to the Canvas contract.");
|
|
369
|
+
return 0;
|
|
370
|
+
}
|
|
371
|
+
console.log("");
|
|
372
|
+
for (const f of result.findings) {
|
|
373
|
+
const badge = f.severity === "error" ? "\u274C ERROR " : "\u26A0\uFE0F WARNING";
|
|
374
|
+
console.log(`${badge} [${f.rule}] ${f.message}`);
|
|
375
|
+
}
|
|
376
|
+
console.log(
|
|
377
|
+
`
|
|
378
|
+
${errors.length} error(s), ${warnings.length} warning(s).` + (result.size === null ? "\nNote: validating an HTML file skips the size and hash gates. Validate the .zip before delivery." : "")
|
|
379
|
+
);
|
|
380
|
+
if (errors.length > 0) {
|
|
381
|
+
console.log("Errors are gates the Canvas app itself applies \u2014 it would reject this bundle.");
|
|
382
|
+
return 1;
|
|
383
|
+
}
|
|
384
|
+
if (warnings.length > 0 && strict) {
|
|
385
|
+
console.log("Warnings are failures under --strict.");
|
|
386
|
+
return 1;
|
|
387
|
+
}
|
|
388
|
+
console.log(
|
|
389
|
+
"Warnings are not app-level rejections, but they fail silently on a real device. Re-run with --strict to gate on them."
|
|
390
|
+
);
|
|
391
|
+
return 0;
|
|
392
|
+
}
|
|
393
|
+
function main() {
|
|
394
|
+
const args = parseArgs(process.argv.slice(2));
|
|
395
|
+
if (args.help || !args.htmlPath) {
|
|
396
|
+
console.log(USAGE);
|
|
397
|
+
process.exit(args.help ? 0 : 1);
|
|
398
|
+
}
|
|
399
|
+
if (args.command === "validate") {
|
|
400
|
+
const result = validateBundle(args.htmlPath, args.maxBytes);
|
|
401
|
+
process.exit(reportValidation(args.htmlPath, result, args.strict));
|
|
402
|
+
}
|
|
403
|
+
runBuild(args.htmlPath, args.outPath, args.maxBytes);
|
|
191
404
|
}
|
|
192
405
|
main();
|
|
193
406
|
//# sourceMappingURL=cli.js.map
|
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 // `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"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/cli-core.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 { buildBundle, parseArgs, USAGE, type ValidationResult, validateBundle } from './cli-core';\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\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 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 * 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/** Runs the blocked-API lint over a client document. */\nexport function lintClientHtml(html: string): ValidationFinding[] {\n const findings: ValidationFinding[] = [];\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';\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 help: boolean;\n}\n\nconst DEFAULT_OUT = 'dist/whitelabel-bundle.zip';\n\n/**\n * Minimal argv parse (no deps): `--html/-i`, `--out/-o`, `--max-bytes`,\n * `--strict`, `--help/-h`, plus the `build` and `validate` 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 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 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\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\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 -h, --help Show this help.`;\n"],"mappings":"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","names":["dirname","resolve","resolve","dirname"]}
|