@condorinst/mobile-bridge 0.22.0 → 0.23.1
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 +133 -34
- package/CHANGELOG.md +187 -0
- package/README.md +30 -481
- package/contract/bridge-contract.json +32 -21
- package/dist/cli.cjs +126 -29
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +126 -29
- package/dist/cli.js.map +1 -1
- package/dist/dev-harness.global.js +402 -55
- package/dist/dev.cjs +403 -55
- package/dist/dev.cjs.map +1 -1
- package/dist/dev.d.cts +245 -9
- package/dist/dev.d.ts +245 -9
- package/dist/dev.js +402 -55
- package/dist/dev.js.map +1 -1
- package/dist/index.cjs +30 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +154 -26
- package/dist/index.d.ts +154 -26
- package/dist/index.js +29 -8
- package/dist/index.js.map +1 -1
- package/docs/agent-guide.md +9 -265
- package/docs/bridge-contract.md +317 -26
- package/docs/client-guide.md +34 -400
- package/docs/device-testing.md +235 -0
- package/docs/pilot-onboarding.md +41 -115
- package/docs/shell-integration.md +292 -0
- package/docs/troubleshooting.md +19 -11
- package/examples/minimal-home/README.md +2 -2
- package/examples/minimal-home/package.json +1 -1
- package/examples/minimal-home/src/main.ts +23 -3
- package/llms-full.txt +769 -865
- package/llms.txt +13 -15
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -5,7 +5,7 @@ import { mkdirSync, writeFileSync } from "fs";
|
|
|
5
5
|
import { dirname as dirname3, resolve as resolve4 } from "path";
|
|
6
6
|
|
|
7
7
|
// package.json
|
|
8
|
-
var version = "0.
|
|
8
|
+
var version = "0.23.1";
|
|
9
9
|
|
|
10
10
|
// src/canvas-admin/command.ts
|
|
11
11
|
import { createHash as createHash2 } from "crypto";
|
|
@@ -164,6 +164,11 @@ var BLOCKED_API_RULES = [
|
|
|
164
164
|
pattern: /window\s*\.\s*open\s*\(/,
|
|
165
165
|
message: "The sandbox has no allow-popups; window.open() returns null."
|
|
166
166
|
},
|
|
167
|
+
{
|
|
168
|
+
rule: "crypto-randomuuid",
|
|
169
|
+
pattern: /crypto\s*\.\s*randomUUID\b/,
|
|
170
|
+
message: "crypto.randomUUID exists only in a secure context, and the app's sandboxed iframe is probably not one: it can be undefined on the phone while it works in the harness. Use createRequestId() from the SDK, or crypto.getRandomValues."
|
|
171
|
+
},
|
|
167
172
|
{
|
|
168
173
|
rule: "top-navigation",
|
|
169
174
|
pattern: /window\s*\.\s*top\b/,
|
|
@@ -303,6 +308,8 @@ function parseArgs(argv) {
|
|
|
303
308
|
scenario: "default",
|
|
304
309
|
port: DEFAULT_DEV_PORT,
|
|
305
310
|
nativeVersion: void 0,
|
|
311
|
+
latencyMs: void 0,
|
|
312
|
+
panel: true,
|
|
306
313
|
help: false
|
|
307
314
|
};
|
|
308
315
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -345,6 +352,14 @@ function parseArgs(argv) {
|
|
|
345
352
|
case "--port":
|
|
346
353
|
args.port = Number.parseInt(argv[++i] ?? "", 10) || DEFAULT_DEV_PORT;
|
|
347
354
|
break;
|
|
355
|
+
case "--latency": {
|
|
356
|
+
const ms = Number.parseInt(argv[++i] ?? "", 10);
|
|
357
|
+
args.latencyMs = Number.isFinite(ms) && ms >= 0 ? ms : void 0;
|
|
358
|
+
break;
|
|
359
|
+
}
|
|
360
|
+
case "--no-panel":
|
|
361
|
+
args.panel = false;
|
|
362
|
+
break;
|
|
348
363
|
default:
|
|
349
364
|
if (token && !token.startsWith("-") && !args.htmlPath) args.htmlPath = token;
|
|
350
365
|
}
|
|
@@ -357,7 +372,8 @@ Usage:
|
|
|
357
372
|
npx condor-mobile-bridge build -i <html> [-o <zip>]
|
|
358
373
|
npx condor-mobile-bridge validate -i <zip|html> [--strict]
|
|
359
374
|
npx condor-mobile-bridge canvas publish|list|revoke ... (see: canvas --help)
|
|
360
|
-
npx condor-mobile-bridge dev -i <html|zip|dir> [--scenario <name>] [--native-version <v|null>]
|
|
375
|
+
npx condor-mobile-bridge dev -i <html|zip|dir> [--scenario <name|file.json>] [--native-version <v|null>]
|
|
376
|
+
[--port <n>] [--latency <ms>] [--no-panel]
|
|
361
377
|
|
|
362
378
|
Commands:
|
|
363
379
|
build Inline a dist/index.html into a self-contained .zip.
|
|
@@ -375,12 +391,15 @@ Options:
|
|
|
375
391
|
-o, --out <path> build only: output .zip (default: ${DEFAULT_OUT}).
|
|
376
392
|
--max-bytes <n> Size limit in bytes (default: ${DEFAULT_MAX_BYTES} = 2 MB).
|
|
377
393
|
--strict validate only: exit 1 on warnings too, not just errors.
|
|
378
|
-
--scenario <n> dev only: device behaviour to simulate (default: default)
|
|
394
|
+
--scenario <n> dev only: device behaviour to simulate (default: default),
|
|
395
|
+
or a .json file that extends one (see docs/device-testing.md).
|
|
379
396
|
--port <n> dev only: HTTP port (default: ${DEFAULT_DEV_PORT}).
|
|
380
397
|
--native-version <v|null>
|
|
381
398
|
dev only: nativeVersion the harness reports (default
|
|
382
|
-
0.
|
|
399
|
+
0.18.0). 'null' reproduces a shell that could not
|
|
383
400
|
reach the native side.
|
|
401
|
+
--latency <ms> dev only: fixed reply latency (e.g. 0 for fast tests).
|
|
402
|
+
--no-panel dev only: hide the floating MOCK panel.
|
|
384
403
|
-h, --help Show this help.`;
|
|
385
404
|
|
|
386
405
|
// src/canvas-admin/client.ts
|
|
@@ -960,7 +979,7 @@ function telemetry(spec, now) {
|
|
|
960
979
|
serialNo: spec.serialNo,
|
|
961
980
|
attemptDate: now - 10 * 6e4,
|
|
962
981
|
message: spec.syncError,
|
|
963
|
-
messageCode: "
|
|
982
|
+
messageCode: "send_data_log_error"
|
|
964
983
|
}
|
|
965
984
|
] : []
|
|
966
985
|
};
|
|
@@ -1044,6 +1063,7 @@ function systemStatus(overrides = {}) {
|
|
|
1044
1063
|
internet: true,
|
|
1045
1064
|
permission: true,
|
|
1046
1065
|
hasDevices: true,
|
|
1066
|
+
hasDevicesKnown: true,
|
|
1047
1067
|
missingOptionalPermissions: [],
|
|
1048
1068
|
missingEssentialPermissions: [],
|
|
1049
1069
|
deviceSyncError: {},
|
|
@@ -1053,28 +1073,27 @@ function systemStatus(overrides = {}) {
|
|
|
1053
1073
|
}
|
|
1054
1074
|
|
|
1055
1075
|
// src/dev/scenarios.ts
|
|
1056
|
-
var HARNESS_NATIVE_VERSION = "0.
|
|
1076
|
+
var HARNESS_NATIVE_VERSION = "0.18.0";
|
|
1057
1077
|
var base = {
|
|
1058
|
-
description: "Two devices, background service already running, realistic latency.",
|
|
1078
|
+
description: "Two devices, background service already running, realistic latency. The first device starts a BLE session 3 s after load; the second stays between sessions.",
|
|
1059
1079
|
nativeVersion: HARNESS_NATIVE_VERSION,
|
|
1060
1080
|
latencyMs: [80, 250],
|
|
1061
1081
|
serviceStartMs: 0,
|
|
1062
|
-
userDeviceSilent: false,
|
|
1063
1082
|
logPeriodS: 60,
|
|
1064
1083
|
systemStatus: systemStatus(),
|
|
1065
1084
|
devices: () => twoDevices()
|
|
1066
1085
|
};
|
|
1067
1086
|
var SCENARIOS = {
|
|
1068
|
-
default: base,
|
|
1087
|
+
default: { ...base, firstSessionAfterMs: 3e3 },
|
|
1069
1088
|
"cold-start": {
|
|
1070
1089
|
...base,
|
|
1071
|
-
description: "App launched from cold: the background service takes 4 s to start, and every ping/userDevice sent before that is lost.
|
|
1090
|
+
description: "App launched from cold: the background service takes 4 s to start, and every ping/userDevice sent before that is lost. From nativeVersion 0.18.0 the app re-sends the lost userDevice itself; below it, a bundle that asks once stays on its loading screen forever.",
|
|
1072
1091
|
serviceStartMs: 4e3
|
|
1073
1092
|
},
|
|
1074
1093
|
"no-devices": {
|
|
1075
1094
|
...base,
|
|
1076
|
-
description: "Account with no linked device: userDevice never answers and systemStatus.hasDevices is false. Your bundle must show an empty state.",
|
|
1077
|
-
|
|
1095
|
+
description: "Account with no linked device: userDevice answers with error NO_DEVICES (below nativeVersion 0.18.0: never answers) and systemStatus.hasDevices is false. Your bundle must show an empty state.",
|
|
1096
|
+
userDeviceError: "NO_DEVICES",
|
|
1078
1097
|
systemStatus: systemStatus({ hasDevices: false })
|
|
1079
1098
|
},
|
|
1080
1099
|
"shared-serial": {
|
|
@@ -1084,9 +1103,8 @@ var SCENARIOS = {
|
|
|
1084
1103
|
},
|
|
1085
1104
|
offline: {
|
|
1086
1105
|
...base,
|
|
1087
|
-
description: "No internet. systemStatus.internet is false and sensorLog fails; userDevice answers from the cache.",
|
|
1088
|
-
|
|
1089
|
-
systemStatus: systemStatus({ internet: false })
|
|
1106
|
+
description: "No internet. systemStatus.internet is false and sensorLog fails; userDevice answers from the cache. The internet comes back when a test calls window.condorDev.setStatus({ internet: true }).",
|
|
1107
|
+
systemStatus: systemStatus({ internet: false, hasDevicesKnown: false })
|
|
1090
1108
|
}
|
|
1091
1109
|
};
|
|
1092
1110
|
|
|
@@ -1105,11 +1123,18 @@ function readClientHtml(input) {
|
|
|
1105
1123
|
return inlineHtml(path);
|
|
1106
1124
|
}
|
|
1107
1125
|
var escapeHtml = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1108
|
-
function harnessPage(scenario, nativeVersion) {
|
|
1126
|
+
function harnessPage(scenario, nativeVersion, extra = {}) {
|
|
1109
1127
|
const options = Object.entries(SCENARIOS).map(
|
|
1110
1128
|
([name, s]) => `<option value="${name}" title="${escapeHtml(s.description)}"${name === scenario ? " selected" : ""}>${name}</option>`
|
|
1111
1129
|
).join("");
|
|
1112
|
-
const description = escapeHtml(
|
|
1130
|
+
const description = escapeHtml(
|
|
1131
|
+
extra.scenarioFile ? `Scenario file ${extra.scenarioFile}` : SCENARIOS[scenario]?.description ?? ""
|
|
1132
|
+
);
|
|
1133
|
+
const defaultsJson = JSON.stringify({
|
|
1134
|
+
latencyMs: extra.latencyMs ?? null,
|
|
1135
|
+
panel: extra.panel !== false,
|
|
1136
|
+
scenarioFile: Boolean(extra.scenarioFile)
|
|
1137
|
+
}).replace(/</g, "\\u003c");
|
|
1113
1138
|
const scenarioJson = JSON.stringify(scenario).replace(/</g, "\\u003c");
|
|
1114
1139
|
const nativeJson = JSON.stringify(
|
|
1115
1140
|
nativeVersion === void 0 ? void 0 : nativeVersion === "null" ? null : nativeVersion
|
|
@@ -1145,6 +1170,11 @@ function harnessPage(scenario, nativeVersion) {
|
|
|
1145
1170
|
<script>
|
|
1146
1171
|
var scenario = ${scenarioJson};
|
|
1147
1172
|
var nativeVersion = ${nativeJson};
|
|
1173
|
+
var defaults = ${defaultsJson};
|
|
1174
|
+
var params = new URLSearchParams(location.search);
|
|
1175
|
+
var latency = params.has('latency') ? Number(params.get('latency')) : defaults.latencyMs;
|
|
1176
|
+
var panel = params.has('panel') ? params.get('panel') !== '0' : defaults.panel;
|
|
1177
|
+
var useFile = defaults.scenarioFile && !params.has('scenario');
|
|
1148
1178
|
var harness = null;
|
|
1149
1179
|
document.getElementById('scenario').addEventListener('change', function (e) {
|
|
1150
1180
|
var params = new URLSearchParams(location.search);
|
|
@@ -1154,14 +1184,55 @@ function harnessPage(scenario, nativeVersion) {
|
|
|
1154
1184
|
document.getElementById('back').addEventListener('click', function () {
|
|
1155
1185
|
if (harness) harness.pressBack();
|
|
1156
1186
|
});
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1187
|
+
// A scenario file extends a named scenario: its fields replace the base's,
|
|
1188
|
+
// systemStatus is merged, and devices is the userDevice payload to answer.
|
|
1189
|
+
function fromFile(file) {
|
|
1190
|
+
var base = CondorDevHarness.SCENARIOS[file.extends || 'default'];
|
|
1191
|
+
if (!base) throw new Error('scenario file: unknown "extends": ' + file.extends);
|
|
1192
|
+
var out = Object.assign({}, base, file);
|
|
1193
|
+
out.systemStatus = Object.assign({}, base.systemStatus, file.systemStatus || {});
|
|
1194
|
+
if (file.devices) {
|
|
1195
|
+
var devices = JSON.stringify(file.devices);
|
|
1196
|
+
out.devices = function () { return JSON.parse(devices); };
|
|
1197
|
+
} else out.devices = base.devices;
|
|
1198
|
+
return out;
|
|
1199
|
+
}
|
|
1200
|
+
var chosen = null;
|
|
1201
|
+
(useFile ? fetch('/__condor/scenario.json').then(function (r) { return r.json(); }).then(fromFile)
|
|
1202
|
+
: Promise.resolve(CondorDevHarness.SCENARIOS[scenario]))
|
|
1203
|
+
.then(function (s) {
|
|
1204
|
+
chosen = s;
|
|
1205
|
+
if (nativeVersion !== undefined) chosen = Object.assign({}, chosen, { nativeVersion: nativeVersion });
|
|
1206
|
+
if (latency !== null && latency >= 0) chosen = Object.assign({}, chosen, { latencyMs: latency });
|
|
1207
|
+
return fetch('/__condor/client.html');
|
|
1208
|
+
})
|
|
1160
1209
|
.then(function (r) { if (!r.ok) throw new Error('could not read the bundle: HTTP ' + r.status); return r.text(); })
|
|
1161
1210
|
.then(function (html) {
|
|
1162
|
-
return CondorDevHarness.createDevHarness({ html: html, scenario: chosen, mount: document.getElementById('device') });
|
|
1211
|
+
return CondorDevHarness.createDevHarness({ html: html, scenario: chosen, mount: document.getElementById('device'), devPanel: panel });
|
|
1212
|
+
})
|
|
1213
|
+
.then(function (h) {
|
|
1214
|
+
harness = h;
|
|
1215
|
+
var confirm = new URLSearchParams(location.search).get('confirm');
|
|
1216
|
+
if (confirm === 'accept' || confirm === 'decline') h.setConfirmations(confirm);
|
|
1217
|
+
// Scripted tests (Puppeteer, Playwright) drive the simulated app here.
|
|
1218
|
+
// See docs/device-testing.md, "Scripted tests".
|
|
1219
|
+
window.condorDev = {
|
|
1220
|
+
scenario: h.scenario,
|
|
1221
|
+
get log() { return h.log.slice(); },
|
|
1222
|
+
// true once the bundle is mounted AND its handshake was answered (or,
|
|
1223
|
+
// with nativeVersion null, left unanswered on purpose).
|
|
1224
|
+
get ready() {
|
|
1225
|
+
return h.log.some(function (e) {
|
|
1226
|
+
return (e.kind === 'reply' && e.key === 'handshake-ack') || /handshake unanswered/.test(e.detail);
|
|
1227
|
+
});
|
|
1228
|
+
},
|
|
1229
|
+
emit: function (key, payload) { h.emit(key, payload); },
|
|
1230
|
+
setStatus: function (patch) { h.setStatus(patch); },
|
|
1231
|
+
setConnected: function (deviceId, connected) { h.setConnected(deviceId, connected); },
|
|
1232
|
+
setConfirmations: function (mode) { h.setConfirmations(mode); },
|
|
1233
|
+
pressBack: function () { h.pressBack(); }
|
|
1234
|
+
};
|
|
1163
1235
|
})
|
|
1164
|
-
.then(function (h) { harness = h; })
|
|
1165
1236
|
.catch(function (err) { document.getElementById('device').textContent = String(err); console.error(err); });
|
|
1166
1237
|
</script>
|
|
1167
1238
|
</body>
|
|
@@ -1172,7 +1243,10 @@ function harnessScriptPath() {
|
|
|
1172
1243
|
return join2(here, "dev-harness.global.js");
|
|
1173
1244
|
}
|
|
1174
1245
|
function startDevServer(opts) {
|
|
1175
|
-
|
|
1246
|
+
const scenarioFile = opts.scenario.endsWith(".json") ? resolve3(opts.scenario) : void 0;
|
|
1247
|
+
if (scenarioFile) {
|
|
1248
|
+
JSON.parse(readFileSync3(scenarioFile, "utf-8"));
|
|
1249
|
+
} else if (!(opts.scenario in SCENARIOS)) {
|
|
1176
1250
|
throw new Error(
|
|
1177
1251
|
`Unknown scenario "${opts.scenario}". Known: ${Object.keys(SCENARIOS).join(", ")}.`
|
|
1178
1252
|
);
|
|
@@ -1189,6 +1263,14 @@ function startDevServer(opts) {
|
|
|
1189
1263
|
res.end(readClientHtml(opts.input));
|
|
1190
1264
|
return;
|
|
1191
1265
|
}
|
|
1266
|
+
if (url.pathname === "/__condor/scenario.json" && scenarioFile) {
|
|
1267
|
+
res.writeHead(200, {
|
|
1268
|
+
"content-type": "application/json; charset=utf-8",
|
|
1269
|
+
"cache-control": "no-store"
|
|
1270
|
+
});
|
|
1271
|
+
res.end(readFileSync3(scenarioFile));
|
|
1272
|
+
return;
|
|
1273
|
+
}
|
|
1192
1274
|
if (url.pathname === "/__condor/harness.js") {
|
|
1193
1275
|
res.writeHead(200, {
|
|
1194
1276
|
"content-type": "text/javascript; charset=utf-8",
|
|
@@ -1198,14 +1280,21 @@ function startDevServer(opts) {
|
|
|
1198
1280
|
return;
|
|
1199
1281
|
}
|
|
1200
1282
|
if (url.pathname === "/") {
|
|
1201
|
-
const
|
|
1283
|
+
const named = url.searchParams.get("scenario");
|
|
1284
|
+
const scenario = named ?? (scenarioFile ? "default" : opts.scenario);
|
|
1202
1285
|
res.writeHead(200, {
|
|
1203
1286
|
"content-type": "text/html; charset=utf-8",
|
|
1204
1287
|
"content-security-policy": CANVAS_CSP,
|
|
1205
1288
|
"cache-control": "no-store"
|
|
1206
1289
|
});
|
|
1207
1290
|
const nativeVersion = url.searchParams.get("nativeVersion") ?? opts.nativeVersion;
|
|
1208
|
-
res.end(
|
|
1291
|
+
res.end(
|
|
1292
|
+
harnessPage(scenario in SCENARIOS ? scenario : "default", nativeVersion, {
|
|
1293
|
+
latencyMs: opts.latencyMs,
|
|
1294
|
+
panel: opts.panel,
|
|
1295
|
+
scenarioFile: named ? void 0 : scenarioFile && opts.scenario
|
|
1296
|
+
})
|
|
1297
|
+
);
|
|
1209
1298
|
return;
|
|
1210
1299
|
}
|
|
1211
1300
|
res.writeHead(404).end();
|
|
@@ -1219,12 +1308,13 @@ function startDevServer(opts) {
|
|
|
1219
1308
|
}
|
|
1220
1309
|
|
|
1221
1310
|
// src/cli.ts
|
|
1222
|
-
function runDev(
|
|
1223
|
-
const
|
|
1311
|
+
function runDev(opts) {
|
|
1312
|
+
const { input, scenario, port, nativeVersion } = opts;
|
|
1313
|
+
const server = startDevServer(opts);
|
|
1224
1314
|
server.on("listening", () => {
|
|
1225
1315
|
const native = nativeVersion === void 0 ? "" : `&nativeVersion=${nativeVersion}`;
|
|
1226
1316
|
console.log(
|
|
1227
|
-
`Condor Canvas dev harness: http://localhost:${port}
|
|
1317
|
+
`Condor Canvas dev harness: http://localhost:${port}/${scenario.endsWith(".json") ? "" : `?scenario=${scenario}`}${native}`
|
|
1228
1318
|
);
|
|
1229
1319
|
console.log(` bundle: ${input} (re-read on every reload)`);
|
|
1230
1320
|
console.log(" sandbox, shell policy and CSP: same as the app");
|
|
@@ -1318,7 +1408,14 @@ function main() {
|
|
|
1318
1408
|
}
|
|
1319
1409
|
if (args.command === "dev") {
|
|
1320
1410
|
try {
|
|
1321
|
-
runDev(
|
|
1411
|
+
runDev({
|
|
1412
|
+
input: args.htmlPath,
|
|
1413
|
+
scenario: args.scenario,
|
|
1414
|
+
port: args.port,
|
|
1415
|
+
nativeVersion: args.nativeVersion,
|
|
1416
|
+
latencyMs: args.latencyMs,
|
|
1417
|
+
panel: args.panel
|
|
1418
|
+
});
|
|
1322
1419
|
} catch (err) {
|
|
1323
1420
|
console.error(`dev: ${err instanceof Error ? err.message : String(err)}`);
|
|
1324
1421
|
process.exit(1);
|