@condorinst/mobile-bridge 0.19.2 → 0.21.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 +100 -42
- package/CHANGELOG.md +106 -0
- package/README.md +64 -14
- package/contract/bridge-contract.json +70 -25
- package/dist/cli.cjs +436 -5
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +433 -3
- package/dist/cli.js.map +1 -1
- package/dist/dev-harness.global.js +2110 -0
- package/dist/dev.cjs +1662 -0
- package/dist/dev.cjs.map +1 -0
- package/dist/dev.d.cts +683 -0
- package/dist/dev.d.ts +683 -0
- package/dist/dev.js +1611 -0
- package/dist/dev.js.map +1 -0
- package/dist/index.cjs +96 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +265 -19
- package/dist/index.d.ts +265 -19
- package/dist/index.js +94 -3
- package/dist/index.js.map +1 -1
- package/docs/agent-guide.md +83 -25
- package/docs/bridge-contract.md +154 -25
- package/docs/client-guide.md +39 -28
- package/docs/device-testing.md +84 -0
- package/docs/pilot-onboarding.md +26 -12
- package/docs/troubleshooting.md +83 -18
- package/examples/minimal-home/AGENTS.md +9 -6
- package/examples/minimal-home/README.md +10 -5
- package/examples/minimal-home/build.mjs +2 -1
- package/examples/minimal-home/index.html +2 -0
- package/examples/minimal-home/package.json +3 -2
- package/examples/minimal-home/src/main.ts +151 -73
- package/llms-full.txt +2041 -0
- package/llms.txt +15 -13
- package/package.json +9 -3
package/dist/cli.cjs
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
4
|
// src/cli.ts
|
|
5
|
-
var
|
|
6
|
-
var
|
|
5
|
+
var import_node_fs3 = require("fs");
|
|
6
|
+
var import_node_path3 = require("path");
|
|
7
7
|
|
|
8
8
|
// src/cli-core.ts
|
|
9
9
|
var import_node_crypto = require("crypto");
|
|
@@ -168,8 +168,16 @@ var BLOCKED_API_RULES = [
|
|
|
168
168
|
message: "connect-src 'self' on a null origin blocks every network call. Request data through the bridge instead (e.g. BridgeClient.fetchSensorLog)."
|
|
169
169
|
}
|
|
170
170
|
];
|
|
171
|
+
var DEV_HARNESS_MARKER = "__condorDevHarness__";
|
|
171
172
|
function lintClientHtml(html) {
|
|
172
173
|
const findings = [];
|
|
174
|
+
if (html.includes(DEV_HARNESS_MARKER)) {
|
|
175
|
+
findings.push({
|
|
176
|
+
severity: "error",
|
|
177
|
+
rule: "dev-harness-bundled",
|
|
178
|
+
message: "The bundle contains the development harness (@condorinst/mobile-bridge/dev). It belongs in the page that hosts the bundle during development, never in the bundle: remove the import."
|
|
179
|
+
});
|
|
180
|
+
}
|
|
173
181
|
for (const { rule, pattern, message } of BLOCKED_API_RULES) {
|
|
174
182
|
if (pattern.test(html)) findings.push({ severity: "warning", rule, message });
|
|
175
183
|
}
|
|
@@ -277,6 +285,7 @@ function validateBundle(inputPath, maxBytes = DEFAULT_MAX_BYTES) {
|
|
|
277
285
|
};
|
|
278
286
|
}
|
|
279
287
|
var DEFAULT_OUT = "dist/whitelabel-bundle.zip";
|
|
288
|
+
var DEFAULT_DEV_PORT = 5173;
|
|
280
289
|
function parseArgs(argv) {
|
|
281
290
|
const args = {
|
|
282
291
|
command: "build",
|
|
@@ -284,6 +293,9 @@ function parseArgs(argv) {
|
|
|
284
293
|
outPath: DEFAULT_OUT,
|
|
285
294
|
maxBytes: DEFAULT_MAX_BYTES,
|
|
286
295
|
strict: false,
|
|
296
|
+
scenario: "default",
|
|
297
|
+
port: DEFAULT_DEV_PORT,
|
|
298
|
+
nativeVersion: void 0,
|
|
287
299
|
help: false
|
|
288
300
|
};
|
|
289
301
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -314,6 +326,18 @@ function parseArgs(argv) {
|
|
|
314
326
|
case "validate":
|
|
315
327
|
args.command = "validate";
|
|
316
328
|
break;
|
|
329
|
+
case "dev":
|
|
330
|
+
args.command = "dev";
|
|
331
|
+
break;
|
|
332
|
+
case "--scenario":
|
|
333
|
+
args.scenario = argv[++i] ?? "default";
|
|
334
|
+
break;
|
|
335
|
+
case "--native-version":
|
|
336
|
+
args.nativeVersion = argv[++i] ?? "null";
|
|
337
|
+
break;
|
|
338
|
+
case "--port":
|
|
339
|
+
args.port = Number.parseInt(argv[++i] ?? "", 10) || DEFAULT_DEV_PORT;
|
|
340
|
+
break;
|
|
317
341
|
default:
|
|
318
342
|
if (token && !token.startsWith("-") && !args.htmlPath) args.htmlPath = token;
|
|
319
343
|
}
|
|
@@ -325,25 +349,423 @@ var USAGE = `condor-mobile-bridge \u2014 tooling for Condor Canvas client bundle
|
|
|
325
349
|
Usage:
|
|
326
350
|
npx condor-mobile-bridge build -i <html> [-o <zip>]
|
|
327
351
|
npx condor-mobile-bridge validate -i <zip|html> [--strict]
|
|
352
|
+
npx condor-mobile-bridge dev -i <html|zip|dir> [--scenario <name>] [--native-version <v|null>] [--port <n>]
|
|
328
353
|
|
|
329
354
|
Commands:
|
|
330
355
|
build Inline a dist/index.html into a self-contained .zip.
|
|
331
356
|
validate Check a bundle against every rule the Canvas app enforces, plus
|
|
332
357
|
the APIs the sandbox and the CSP block at runtime.
|
|
358
|
+
dev Serve the bundle in a browser harness that behaves like the app:
|
|
359
|
+
same sandbox, shell and policy, and a simulated native side.
|
|
360
|
+
Re-reads the input on every reload. Scenarios: default,
|
|
361
|
+
cold-start, no-devices, shared-serial, offline.
|
|
333
362
|
|
|
334
363
|
Options:
|
|
335
364
|
-i, --html <path> Input. build: dist/index.html. validate: .zip or .html. Required.
|
|
336
365
|
-o, --out <path> build only: output .zip (default: ${DEFAULT_OUT}).
|
|
337
366
|
--max-bytes <n> Size limit in bytes (default: ${DEFAULT_MAX_BYTES} = 2 MB).
|
|
338
367
|
--strict validate only: exit 1 on warnings too, not just errors.
|
|
368
|
+
--scenario <n> dev only: device behaviour to simulate (default: default).
|
|
369
|
+
--port <n> dev only: HTTP port (default: ${DEFAULT_DEV_PORT}).
|
|
370
|
+
--native-version <v|null>
|
|
371
|
+
dev only: nativeVersion the harness reports (default
|
|
372
|
+
0.17.0). 'null' reproduces a shell that could not
|
|
373
|
+
reach the native side.
|
|
339
374
|
-h, --help Show this help.`;
|
|
340
375
|
|
|
376
|
+
// src/cli-dev.ts
|
|
377
|
+
var import_node_fs2 = require("fs");
|
|
378
|
+
var import_node_http = require("http");
|
|
379
|
+
var import_node_path2 = require("path");
|
|
380
|
+
var import_node_url = require("url");
|
|
381
|
+
var import_fflate2 = require("fflate");
|
|
382
|
+
|
|
383
|
+
// src/keys.ts
|
|
384
|
+
var BRIDGE_KEYS = {
|
|
385
|
+
pingPong: "pingPong",
|
|
386
|
+
userDevice: "userDevice",
|
|
387
|
+
deviceLightSensors: "deviceLightSensors",
|
|
388
|
+
syncTime: "syncTime",
|
|
389
|
+
realTime: "realTime",
|
|
390
|
+
deleteMemory: "deleteMemory",
|
|
391
|
+
deviceNotification: "deviceNotification",
|
|
392
|
+
deviceConnection: "deviceConnection",
|
|
393
|
+
sensorLog: "sensorLog",
|
|
394
|
+
dfu: "dfu",
|
|
395
|
+
systemStatus: "systemStatus",
|
|
396
|
+
/**
|
|
397
|
+
* Native → client: the OS back button was pressed and the client had
|
|
398
|
+
* reported internal history via `reportNavigation(true)`. Go back exactly
|
|
399
|
+
* ONE internal page. Emitted only when the native side decided to delegate
|
|
400
|
+
* the back gesture. See `BridgeClient.onBack`.
|
|
401
|
+
*/
|
|
402
|
+
navigationBack: "navigationBack",
|
|
403
|
+
/**
|
|
404
|
+
* Native → client: the app's language state. Carries the raw device locale
|
|
405
|
+
* AND the language explicitly chosen in the app Settings, so the client can
|
|
406
|
+
* apply its own precedence rule — including rendering a language the native
|
|
407
|
+
* chrome does not support. Emitted on attach, after `handshake-ack`, and on
|
|
408
|
+
* every change in Settings. See `LanguagePayload`.
|
|
409
|
+
*/
|
|
410
|
+
language: "language"
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
// src/dev/policy.ts
|
|
414
|
+
var CANVAS_POLICY = {
|
|
415
|
+
readOnlyKeys: [
|
|
416
|
+
BRIDGE_KEYS.userDevice,
|
|
417
|
+
BRIDGE_KEYS.deviceConnection,
|
|
418
|
+
BRIDGE_KEYS.deviceLightSensors,
|
|
419
|
+
BRIDGE_KEYS.deviceNotification,
|
|
420
|
+
BRIDGE_KEYS.pingPong,
|
|
421
|
+
BRIDGE_KEYS.realTime,
|
|
422
|
+
BRIDGE_KEYS.syncTime,
|
|
423
|
+
BRIDGE_KEYS.deleteMemory,
|
|
424
|
+
BRIDGE_KEYS.sensorLog,
|
|
425
|
+
BRIDGE_KEYS.systemStatus,
|
|
426
|
+
BRIDGE_KEYS.navigationBack,
|
|
427
|
+
BRIDGE_KEYS.language
|
|
428
|
+
],
|
|
429
|
+
allowedCommands: [
|
|
430
|
+
"userDevice",
|
|
431
|
+
"realTime",
|
|
432
|
+
"openSettings",
|
|
433
|
+
"deviceConnection",
|
|
434
|
+
"ping",
|
|
435
|
+
"jsError",
|
|
436
|
+
"sensorLog",
|
|
437
|
+
"setStatusBarColor",
|
|
438
|
+
"requestPermission",
|
|
439
|
+
"navigationState",
|
|
440
|
+
"language"
|
|
441
|
+
],
|
|
442
|
+
requiresConfirmation: ["syncTime", "deleteMemory", "logout"],
|
|
443
|
+
deniedCommands: ["dfu"]
|
|
444
|
+
};
|
|
445
|
+
var CANVAS_CSP = "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; frame-src 'self'; object-src 'none'; base-uri 'none'; form-action 'none';";
|
|
446
|
+
|
|
447
|
+
// src/dev/fixtures.ts
|
|
448
|
+
function config(spec) {
|
|
449
|
+
const item = {
|
|
450
|
+
deviceId: spec.deviceId,
|
|
451
|
+
serialNo: spec.serialNo,
|
|
452
|
+
deviceBodyPart: spec.bodyPart,
|
|
453
|
+
bleConnectionPeriod: 5,
|
|
454
|
+
autosync: true,
|
|
455
|
+
eraseDeviceMemory: false,
|
|
456
|
+
autoEraseDeviceMemory: false,
|
|
457
|
+
autoEraseDeviceMemoryThreshold: 80,
|
|
458
|
+
readSensorsMemory: false,
|
|
459
|
+
startDevice: false,
|
|
460
|
+
readFullMemory: false,
|
|
461
|
+
allowFirmwareUpdate: true
|
|
462
|
+
};
|
|
463
|
+
if (spec.modelId) item.modelId = spec.modelId;
|
|
464
|
+
return item;
|
|
465
|
+
}
|
|
466
|
+
function telemetry(spec, now) {
|
|
467
|
+
return {
|
|
468
|
+
serialNo: spec.serialNo,
|
|
469
|
+
deviceId: spec.deviceId,
|
|
470
|
+
updataDateTime: now - spec.syncedMinutesAgo * 6e4,
|
|
471
|
+
deviceName: spec.name,
|
|
472
|
+
deviceVersion: spec.modelId === "actlumus_plus" ? "3.0.2" : "2.1.0",
|
|
473
|
+
hardwareVersion: spec.modelId === "actlumus_plus" ? "B" : "A",
|
|
474
|
+
deviceDateTime: now - spec.syncedMinutesAgo * 6e4,
|
|
475
|
+
memoryDataUsage: 37.5,
|
|
476
|
+
memorySystemUsage: 4.2,
|
|
477
|
+
batteryLevel: spec.battery,
|
|
478
|
+
batteryVoltage: 3.7 + spec.battery / 1e3,
|
|
479
|
+
isCharging: spec.charging,
|
|
480
|
+
isOnDock: spec.charging,
|
|
481
|
+
syncErrors: spec.syncError ? [
|
|
482
|
+
{
|
|
483
|
+
serialNo: spec.serialNo,
|
|
484
|
+
attemptDate: now - 10 * 6e4,
|
|
485
|
+
message: spec.syncError,
|
|
486
|
+
messageCode: "SYNC_FAILED"
|
|
487
|
+
}
|
|
488
|
+
] : []
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
function userConfig(spec) {
|
|
492
|
+
return {
|
|
493
|
+
serialNo: spec.serialNo,
|
|
494
|
+
deviceId: spec.deviceId,
|
|
495
|
+
syncTime: false,
|
|
496
|
+
realTime: false,
|
|
497
|
+
deleteMemory: false,
|
|
498
|
+
readAllMemory: false,
|
|
499
|
+
readAllSystemLog: false
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
function payloadOf(specs, now = Date.now()) {
|
|
503
|
+
return {
|
|
504
|
+
lastUpdate: now,
|
|
505
|
+
devicesConfig: specs.map(config),
|
|
506
|
+
devicesTelemetry: specs.map((s) => telemetry(s, now)),
|
|
507
|
+
devicesUserConfig: specs.map(userConfig)
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
function twoDevices(now = Date.now()) {
|
|
511
|
+
return payloadOf(
|
|
512
|
+
[
|
|
513
|
+
{
|
|
514
|
+
deviceId: 9001,
|
|
515
|
+
serialNo: 4534,
|
|
516
|
+
modelId: "actlumus",
|
|
517
|
+
bodyPart: "wrist",
|
|
518
|
+
name: "ActLumus 4534",
|
|
519
|
+
battery: 82,
|
|
520
|
+
charging: false,
|
|
521
|
+
syncedMinutesAgo: 12
|
|
522
|
+
},
|
|
523
|
+
{
|
|
524
|
+
deviceId: 9002,
|
|
525
|
+
serialNo: 5120,
|
|
526
|
+
modelId: "actlumus_plus",
|
|
527
|
+
bodyPart: "necklace",
|
|
528
|
+
name: "ActLumus Plus 5120",
|
|
529
|
+
battery: 41,
|
|
530
|
+
charging: true,
|
|
531
|
+
syncedMinutesAgo: 190,
|
|
532
|
+
syncError: "Could not read the device memory."
|
|
533
|
+
}
|
|
534
|
+
],
|
|
535
|
+
now
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
function sharedSerial(now = Date.now()) {
|
|
539
|
+
return payloadOf(
|
|
540
|
+
[
|
|
541
|
+
{
|
|
542
|
+
deviceId: 9101,
|
|
543
|
+
serialNo: 7047,
|
|
544
|
+
bodyPart: "wrist",
|
|
545
|
+
name: "ActLumus 7047",
|
|
546
|
+
battery: 67,
|
|
547
|
+
charging: false,
|
|
548
|
+
syncedMinutesAgo: 30
|
|
549
|
+
},
|
|
550
|
+
{
|
|
551
|
+
deviceId: 9102,
|
|
552
|
+
serialNo: 7047,
|
|
553
|
+
modelId: "actlumus_plus",
|
|
554
|
+
bodyPart: "necklace",
|
|
555
|
+
name: "ActLumus Plus 7047",
|
|
556
|
+
battery: 93,
|
|
557
|
+
charging: false,
|
|
558
|
+
syncedMinutesAgo: 8
|
|
559
|
+
}
|
|
560
|
+
],
|
|
561
|
+
now
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
function systemStatus(overrides = {}) {
|
|
565
|
+
return {
|
|
566
|
+
bluetooth: true,
|
|
567
|
+
internet: true,
|
|
568
|
+
permission: true,
|
|
569
|
+
hasDevices: true,
|
|
570
|
+
missingOptionalPermissions: [],
|
|
571
|
+
missingEssentialPermissions: [],
|
|
572
|
+
deviceSyncError: {},
|
|
573
|
+
deviceSyncErrorById: {},
|
|
574
|
+
...overrides
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// src/dev/scenarios.ts
|
|
579
|
+
var HARNESS_NATIVE_VERSION = "0.17.0";
|
|
580
|
+
var base = {
|
|
581
|
+
description: "Two devices, background service already running, realistic latency.",
|
|
582
|
+
nativeVersion: HARNESS_NATIVE_VERSION,
|
|
583
|
+
latencyMs: [80, 250],
|
|
584
|
+
serviceStartMs: 0,
|
|
585
|
+
userDeviceSilent: false,
|
|
586
|
+
logPeriodS: 60,
|
|
587
|
+
systemStatus: systemStatus(),
|
|
588
|
+
devices: () => twoDevices()
|
|
589
|
+
};
|
|
590
|
+
var SCENARIOS = {
|
|
591
|
+
default: base,
|
|
592
|
+
"cold-start": {
|
|
593
|
+
...base,
|
|
594
|
+
description: "App launched from cold: the background service takes 4 s to start, and every ping/userDevice sent before that is lost. A bundle that asks once stays on its loading screen forever.",
|
|
595
|
+
serviceStartMs: 4e3
|
|
596
|
+
},
|
|
597
|
+
"no-devices": {
|
|
598
|
+
...base,
|
|
599
|
+
description: "Account with no linked device: userDevice never answers and systemStatus.hasDevices is false. Your bundle must show an empty state.",
|
|
600
|
+
userDeviceSilent: true,
|
|
601
|
+
systemStatus: systemStatus({ hasDevices: false })
|
|
602
|
+
},
|
|
603
|
+
"shared-serial": {
|
|
604
|
+
...base,
|
|
605
|
+
description: "An ActLumus and an ActLumus Plus share serial number 7047. Only deviceId tells them apart; sensorLog cannot address one of them.",
|
|
606
|
+
devices: () => sharedSerial()
|
|
607
|
+
},
|
|
608
|
+
offline: {
|
|
609
|
+
...base,
|
|
610
|
+
description: "No internet. systemStatus.internet is false and sensorLog fails; userDevice answers from the cache.",
|
|
611
|
+
sensorLogError: "Connection failed",
|
|
612
|
+
systemStatus: systemStatus({ internet: false })
|
|
613
|
+
}
|
|
614
|
+
};
|
|
615
|
+
|
|
616
|
+
// src/cli-dev.ts
|
|
617
|
+
var import_meta = {};
|
|
618
|
+
function readClientHtml(input) {
|
|
619
|
+
const path = (0, import_node_path2.resolve)(input);
|
|
620
|
+
if ((0, import_node_fs2.existsSync)(path) && (0, import_node_fs2.statSync)(path).isDirectory()) {
|
|
621
|
+
return inlineHtml((0, import_node_path2.join)(path, "index.html"));
|
|
622
|
+
}
|
|
623
|
+
if ((0, import_node_path2.extname)(path).toLowerCase() === ".zip") {
|
|
624
|
+
const files = (0, import_fflate2.unzipSync)(new Uint8Array((0, import_node_fs2.readFileSync)(path)));
|
|
625
|
+
const entry = files["index.html"];
|
|
626
|
+
if (!entry) throw new Error(`${input}: no index.html at the root of the zip.`);
|
|
627
|
+
return (0, import_fflate2.strFromU8)(entry);
|
|
628
|
+
}
|
|
629
|
+
return inlineHtml(path);
|
|
630
|
+
}
|
|
631
|
+
var escapeHtml = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
632
|
+
function harnessPage(scenario, nativeVersion) {
|
|
633
|
+
const options = Object.entries(SCENARIOS).map(
|
|
634
|
+
([name, s]) => `<option value="${name}" title="${escapeHtml(s.description)}"${name === scenario ? " selected" : ""}>${name}</option>`
|
|
635
|
+
).join("");
|
|
636
|
+
const description = escapeHtml(SCENARIOS[scenario]?.description ?? "");
|
|
637
|
+
const scenarioJson = JSON.stringify(scenario).replace(/</g, "\\u003c");
|
|
638
|
+
const nativeJson = JSON.stringify(
|
|
639
|
+
nativeVersion === void 0 ? void 0 : nativeVersion === "null" ? null : nativeVersion
|
|
640
|
+
)?.replace(/</g, "\\u003c") ?? "undefined";
|
|
641
|
+
const nativeLabel = escapeHtml(
|
|
642
|
+
nativeVersion === void 0 ? "" : `nativeVersion ${nativeVersion}`
|
|
643
|
+
);
|
|
644
|
+
return `<!doctype html>
|
|
645
|
+
<html lang="en">
|
|
646
|
+
<head>
|
|
647
|
+
<meta charset="utf-8">
|
|
648
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
649
|
+
<meta http-equiv="Content-Security-Policy" content="${CANVAS_CSP}">
|
|
650
|
+
<title>Condor Canvas \u2014 dev harness</title>
|
|
651
|
+
<style>
|
|
652
|
+
body { margin: 0; font: 13px system-ui, sans-serif; background: #e9ebef; color: #1b1f24; }
|
|
653
|
+
header { display: flex; gap: 12px; align-items: center; padding: 8px 16px; background: #1b1f24; color: #fff; flex-wrap: wrap; }
|
|
654
|
+
header p { margin: 0; opacity: .8; flex: 1 1 320px; }
|
|
655
|
+
main { display: flex; justify-content: center; padding: 16px; }
|
|
656
|
+
#device { width: 390px; height: 780px; max-width: 100%; border-radius: 28px; overflow: hidden; box-shadow: 0 10px 40px rgba(0,0,0,.25); background: #fff; }
|
|
657
|
+
</style>
|
|
658
|
+
</head>
|
|
659
|
+
<body>
|
|
660
|
+
<header>
|
|
661
|
+
<strong>Condor Canvas \xB7 dev harness</strong>
|
|
662
|
+
<label>Scenario <select id="scenario">${options}</select></label>
|
|
663
|
+
<button id="back" type="button" title="The OS back button: reaches the bundle only after it reported canGoBack true">\u25C0 OS back</button>
|
|
664
|
+
<span>${nativeLabel}</span>
|
|
665
|
+
<p>${description}</p>
|
|
666
|
+
</header>
|
|
667
|
+
<main><div id="device"></div></main>
|
|
668
|
+
<script src="/__condor/harness.js"></script>
|
|
669
|
+
<script>
|
|
670
|
+
var scenario = ${scenarioJson};
|
|
671
|
+
var nativeVersion = ${nativeJson};
|
|
672
|
+
var harness = null;
|
|
673
|
+
document.getElementById('scenario').addEventListener('change', function (e) {
|
|
674
|
+
var params = new URLSearchParams(location.search);
|
|
675
|
+
params.set('scenario', e.target.value);
|
|
676
|
+
location.search = params.toString();
|
|
677
|
+
});
|
|
678
|
+
document.getElementById('back').addEventListener('click', function () {
|
|
679
|
+
if (harness) harness.pressBack();
|
|
680
|
+
});
|
|
681
|
+
var chosen = CondorDevHarness.SCENARIOS[scenario];
|
|
682
|
+
if (nativeVersion !== undefined) chosen = Object.assign({}, chosen, { nativeVersion: nativeVersion });
|
|
683
|
+
fetch('/__condor/client.html')
|
|
684
|
+
.then(function (r) { if (!r.ok) throw new Error('could not read the bundle: HTTP ' + r.status); return r.text(); })
|
|
685
|
+
.then(function (html) {
|
|
686
|
+
return CondorDevHarness.createDevHarness({ html: html, scenario: chosen, mount: document.getElementById('device') });
|
|
687
|
+
})
|
|
688
|
+
.then(function (h) { harness = h; })
|
|
689
|
+
.catch(function (err) { document.getElementById('device').textContent = String(err); console.error(err); });
|
|
690
|
+
</script>
|
|
691
|
+
</body>
|
|
692
|
+
</html>`;
|
|
693
|
+
}
|
|
694
|
+
function harnessScriptPath() {
|
|
695
|
+
const here = typeof __dirname !== "undefined" ? __dirname : (0, import_node_path2.dirname)((0, import_node_url.fileURLToPath)(import_meta.url));
|
|
696
|
+
return (0, import_node_path2.join)(here, "dev-harness.global.js");
|
|
697
|
+
}
|
|
698
|
+
function startDevServer(opts) {
|
|
699
|
+
if (!(opts.scenario in SCENARIOS)) {
|
|
700
|
+
throw new Error(
|
|
701
|
+
`Unknown scenario "${opts.scenario}". Known: ${Object.keys(SCENARIOS).join(", ")}.`
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
readClientHtml(opts.input);
|
|
705
|
+
const server = (0, import_node_http.createServer)((req, res) => {
|
|
706
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
707
|
+
try {
|
|
708
|
+
if (url.pathname === "/__condor/client.html") {
|
|
709
|
+
res.writeHead(200, {
|
|
710
|
+
"content-type": "text/html; charset=utf-8",
|
|
711
|
+
"cache-control": "no-store"
|
|
712
|
+
});
|
|
713
|
+
res.end(readClientHtml(opts.input));
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
if (url.pathname === "/__condor/harness.js") {
|
|
717
|
+
res.writeHead(200, {
|
|
718
|
+
"content-type": "text/javascript; charset=utf-8",
|
|
719
|
+
"cache-control": "no-store"
|
|
720
|
+
});
|
|
721
|
+
res.end((0, import_node_fs2.readFileSync)(harnessScriptPath()));
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
if (url.pathname === "/") {
|
|
725
|
+
const scenario = url.searchParams.get("scenario") ?? opts.scenario;
|
|
726
|
+
res.writeHead(200, {
|
|
727
|
+
"content-type": "text/html; charset=utf-8",
|
|
728
|
+
"content-security-policy": CANVAS_CSP,
|
|
729
|
+
"cache-control": "no-store"
|
|
730
|
+
});
|
|
731
|
+
const nativeVersion = url.searchParams.get("nativeVersion") ?? opts.nativeVersion;
|
|
732
|
+
res.end(harnessPage(scenario in SCENARIOS ? scenario : opts.scenario, nativeVersion));
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
res.writeHead(404).end();
|
|
736
|
+
} catch (err) {
|
|
737
|
+
res.writeHead(500, { "content-type": "text/plain; charset=utf-8" });
|
|
738
|
+
res.end(String(err instanceof Error ? err.message : err));
|
|
739
|
+
}
|
|
740
|
+
});
|
|
741
|
+
server.listen(opts.port, "127.0.0.1");
|
|
742
|
+
return server;
|
|
743
|
+
}
|
|
744
|
+
|
|
341
745
|
// src/cli.ts
|
|
746
|
+
function runDev(input, scenario, port, nativeVersion) {
|
|
747
|
+
const server = startDevServer({ input, scenario, port, nativeVersion });
|
|
748
|
+
server.on("listening", () => {
|
|
749
|
+
const native = nativeVersion === void 0 ? "" : `&nativeVersion=${nativeVersion}`;
|
|
750
|
+
console.log(
|
|
751
|
+
`Condor Canvas dev harness: http://localhost:${port}/?scenario=${scenario}${native}`
|
|
752
|
+
);
|
|
753
|
+
console.log(` bundle: ${input} (re-read on every reload)`);
|
|
754
|
+
console.log(" sandbox, shell policy and CSP: same as the app");
|
|
755
|
+
console.log(" scenarios: default, cold-start, no-devices, shared-serial, offline");
|
|
756
|
+
console.log(' OS back button: the "\u25C0 OS back" button at the top');
|
|
757
|
+
console.log("\nA bundle is ready for a device only when it handles every scenario.");
|
|
758
|
+
});
|
|
759
|
+
server.on("error", (err) => {
|
|
760
|
+
console.error(`dev: ${err.message}`);
|
|
761
|
+
process.exit(1);
|
|
762
|
+
});
|
|
763
|
+
}
|
|
342
764
|
function runBuild(htmlPath, outPath, maxBytes) {
|
|
343
765
|
const result = buildBundle(htmlPath, maxBytes);
|
|
344
|
-
const resolvedOut = (0,
|
|
345
|
-
(0,
|
|
346
|
-
(0,
|
|
766
|
+
const resolvedOut = (0, import_node_path3.resolve)(outPath);
|
|
767
|
+
(0, import_node_fs3.mkdirSync)((0, import_node_path3.dirname)(resolvedOut), { recursive: true });
|
|
768
|
+
(0, import_node_fs3.writeFileSync)(resolvedOut, result.zipBytes);
|
|
347
769
|
const kb = (Buffer.byteLength(result.html) / 1024).toFixed(1);
|
|
348
770
|
console.log(`Built ${outPath}`);
|
|
349
771
|
console.log(` entry: index.html (${kb} KB self-contained)`);
|
|
@@ -397,6 +819,15 @@ function main() {
|
|
|
397
819
|
console.log(USAGE);
|
|
398
820
|
process.exit(args.help ? 0 : 1);
|
|
399
821
|
}
|
|
822
|
+
if (args.command === "dev") {
|
|
823
|
+
try {
|
|
824
|
+
runDev(args.htmlPath, args.scenario, args.port, args.nativeVersion);
|
|
825
|
+
} catch (err) {
|
|
826
|
+
console.error(`dev: ${err instanceof Error ? err.message : String(err)}`);
|
|
827
|
+
process.exit(1);
|
|
828
|
+
}
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
400
831
|
if (args.command === "validate") {
|
|
401
832
|
const result = validateBundle(args.htmlPath, args.maxBytes);
|
|
402
833
|
process.exit(reportValidation(args.htmlPath, result, args.strict));
|