@condorinst/mobile-bridge 0.22.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js 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.22.0";
8
+ var version = "0.23.0";
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>] [--port <n>]
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.17.0). 'null' reproduces a shell that could not
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: "SYNC_FAILED"
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.17.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. A bundle that asks once stays on its loading screen forever.",
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
- userDeviceSilent: true,
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
- sensorLogError: "Connection failed",
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
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(SCENARIOS[scenario]?.description ?? "");
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
- var chosen = CondorDevHarness.SCENARIOS[scenario];
1158
- if (nativeVersion !== undefined) chosen = Object.assign({}, chosen, { nativeVersion: nativeVersion });
1159
- fetch('/__condor/client.html')
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
- if (!(opts.scenario in SCENARIOS)) {
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 scenario = url.searchParams.get("scenario") ?? opts.scenario;
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(harnessPage(scenario in SCENARIOS ? scenario : opts.scenario, nativeVersion));
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(input, scenario, port, nativeVersion) {
1223
- const server = startDevServer({ input, scenario, port, nativeVersion });
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}/?scenario=${scenario}${native}`
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(args.htmlPath, args.scenario, args.port, args.nativeVersion);
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);