@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/AGENTS.md +133 -34
- package/CHANGELOG.md +175 -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 +388 -55
- package/dist/dev.cjs +389 -55
- package/dist/dev.cjs.map +1 -1
- package/dist/dev.d.cts +239 -9
- package/dist/dev.d.ts +239 -9
- package/dist/dev.js +388 -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 +313 -26
- package/docs/client-guide.md +34 -400
- package/docs/device-testing.md +234 -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 +764 -865
- package/llms.txt +13 -15
- package/package.json +1 -1
|
@@ -30,6 +30,7 @@ var CondorDevHarness = (() => {
|
|
|
30
30
|
createDevHarness: () => createDevHarness,
|
|
31
31
|
createSimulatedNative: () => createSimulatedNative,
|
|
32
32
|
formatLocalTimestamp: () => formatLocalTimestamp,
|
|
33
|
+
lightSensorsReading: () => lightSensorsReading,
|
|
33
34
|
parseLocalTimestamp: () => parseLocalTimestamp,
|
|
34
35
|
resolveScenario: () => resolveScenario,
|
|
35
36
|
sensorLogPage: () => sensorLogPage,
|
|
@@ -88,7 +89,7 @@ var CondorDevHarness = (() => {
|
|
|
88
89
|
serialNo: spec.serialNo,
|
|
89
90
|
attemptDate: now - 10 * 6e4,
|
|
90
91
|
message: spec.syncError,
|
|
91
|
-
messageCode: "
|
|
92
|
+
messageCode: "send_data_log_error"
|
|
92
93
|
}
|
|
93
94
|
] : []
|
|
94
95
|
};
|
|
@@ -172,6 +173,7 @@ var CondorDevHarness = (() => {
|
|
|
172
173
|
internet: true,
|
|
173
174
|
permission: true,
|
|
174
175
|
hasDevices: true,
|
|
176
|
+
hasDevicesKnown: true,
|
|
175
177
|
missingOptionalPermissions: [],
|
|
176
178
|
missingEssentialPermissions: [],
|
|
177
179
|
deviceSyncError: {},
|
|
@@ -187,13 +189,15 @@ var CondorDevHarness = (() => {
|
|
|
187
189
|
const photopic = Math.round((daylight * 1800 + noise * 40) * 10) / 10;
|
|
188
190
|
return { photopic, melanopic: Math.round(photopic * 0.62 * 10) / 10 };
|
|
189
191
|
}
|
|
190
|
-
function logAt(epochMs, serialNo, deviceId) {
|
|
191
|
-
const { photopic, melanopic } = lightAt(epochMs, serialNo);
|
|
192
|
+
function logAt(epochMs, serialNo, deviceId, bodyPart, docked) {
|
|
193
|
+
const { photopic, melanopic } = docked ? { photopic: 0, melanopic: 0 } : lightAt(epochMs, serialNo);
|
|
192
194
|
return {
|
|
193
195
|
id: Math.floor(epochMs / 1e3) * 10 + serialNo % 10,
|
|
194
196
|
log_time: formatLocalTimestamp(new Date(epochMs)),
|
|
195
197
|
log_type: 1,
|
|
196
|
-
body_part:
|
|
198
|
+
body_part: bodyPart,
|
|
199
|
+
// The device's raw wear flag; the name misleads: 1 = worn, 0 = on the dock.
|
|
200
|
+
off_wrist: docked ? 0 : 1,
|
|
197
201
|
temperature: 31.5,
|
|
198
202
|
lux_photopic: photopic,
|
|
199
203
|
lux_melanopic: melanopic,
|
|
@@ -201,6 +205,67 @@ var CondorDevHarness = (() => {
|
|
|
201
205
|
act_data_pim: 120
|
|
202
206
|
};
|
|
203
207
|
}
|
|
208
|
+
function spectrum(nm, photopic) {
|
|
209
|
+
const out = {};
|
|
210
|
+
for (const w of nm) {
|
|
211
|
+
const weight = Math.exp(-(((w - 555) / 120) ** 2));
|
|
212
|
+
out[`wavelength${w}`] = Math.round(photopic * weight * 0.01 * 1e4) / 1e4;
|
|
213
|
+
}
|
|
214
|
+
return out;
|
|
215
|
+
}
|
|
216
|
+
var GEN1_NM = [390, 415, 445, 480, 515, 555, 590, 630, 680, 750];
|
|
217
|
+
var GEN2_NM = [395, 426, 457, 488, 520, 551, 582, 613, 644, 675, 707, 738, 769, 800];
|
|
218
|
+
function lightSensorsReading(device, epochMs = Date.now(), withDeviceId = true) {
|
|
219
|
+
const { photopic, melanopic } = lightAt(epochMs, device.serialNo);
|
|
220
|
+
const raw = {
|
|
221
|
+
light_sensor_f1: 0.01,
|
|
222
|
+
light_sensor_f2: 0.02,
|
|
223
|
+
light_sensor_f3: 0.03,
|
|
224
|
+
light_sensor_f4: 0.04,
|
|
225
|
+
light_sensor_f5: 0.05,
|
|
226
|
+
light_sensor_f6: 0.04,
|
|
227
|
+
light_sensor_f7: 0.03,
|
|
228
|
+
light_sensor_f8: 0.02,
|
|
229
|
+
light_sensor_clear: Math.round(photopic * 2e-4 * 1e4) / 1e4,
|
|
230
|
+
light_sensor_nir: 0.01
|
|
231
|
+
};
|
|
232
|
+
const address = {
|
|
233
|
+
serialNo: device.serialNo,
|
|
234
|
+
...withDeviceId ? { deviceId: device.deviceId } : {}
|
|
235
|
+
};
|
|
236
|
+
if (device.modelId === "actlumus_plus") {
|
|
237
|
+
return {
|
|
238
|
+
...address,
|
|
239
|
+
type: "actlumus2",
|
|
240
|
+
data: {
|
|
241
|
+
...raw,
|
|
242
|
+
...spectrum(GEN2_NM, photopic),
|
|
243
|
+
time: epochMs,
|
|
244
|
+
photopic,
|
|
245
|
+
melanopic,
|
|
246
|
+
light_sensor_z: 9e-3,
|
|
247
|
+
light_sensor_y: 0.038,
|
|
248
|
+
light_sensor_x: 0.044,
|
|
249
|
+
light_sensor_fd: 0
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
return {
|
|
254
|
+
...address,
|
|
255
|
+
type: "actlumus",
|
|
256
|
+
data: {
|
|
257
|
+
...raw,
|
|
258
|
+
...spectrum(GEN1_NM, photopic),
|
|
259
|
+
time: epochMs,
|
|
260
|
+
photopic,
|
|
261
|
+
melanopic,
|
|
262
|
+
light_sensor_z: null,
|
|
263
|
+
light_sensor_y: null,
|
|
264
|
+
light_sensor_x: null,
|
|
265
|
+
light_sensor_fd: null
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
}
|
|
204
269
|
var HISTORY_DAYS = 14;
|
|
205
270
|
var encodeCursor = (epochMs) => `mock:${epochMs}`;
|
|
206
271
|
var decodeCursor = (cursor) => Number(cursor.slice("mock:".length));
|
|
@@ -215,7 +280,15 @@ var CondorDevHarness = (() => {
|
|
|
215
280
|
return { requestId: query.requestId, success: false, error: "CONFLICTING_CURSORS" };
|
|
216
281
|
}
|
|
217
282
|
const newest = Math.floor(now / periodMs) * periodMs;
|
|
218
|
-
const
|
|
283
|
+
const history = opts.history ?? {};
|
|
284
|
+
const oldest = newest - (history.days ?? HISTORY_DAYS) * 864e5;
|
|
285
|
+
const dock = new Set(history.dockHours ?? [7]);
|
|
286
|
+
const gapHours = new Set(history.gapHours ?? []);
|
|
287
|
+
const gapDates = new Set(history.gapDates ?? []);
|
|
288
|
+
const present = (t) => {
|
|
289
|
+
const d = new Date(t);
|
|
290
|
+
return !gapHours.has(d.getHours()) && !gapDates.has(formatLocalTimestamp(d).slice(0, 10));
|
|
291
|
+
};
|
|
219
292
|
const rangeStart = query.startTime ? parseLocalTimestamp(query.startTime) : oldest;
|
|
220
293
|
const rangeEnd = query.endTime ? parseLocalTimestamp(query.endTime) - 1 : newest;
|
|
221
294
|
if (Number.isNaN(rangeStart) || Number.isNaN(rangeEnd) || rangeStart > rangeEnd + 1) {
|
|
@@ -223,33 +296,41 @@ var CondorDevHarness = (() => {
|
|
|
223
296
|
}
|
|
224
297
|
const first = Math.max(oldest, Math.ceil(rangeStart / periodMs) * periodMs);
|
|
225
298
|
const last = Math.min(newest, Math.floor(rangeEnd / periodMs) * periodMs);
|
|
226
|
-
|
|
227
|
-
|
|
299
|
+
const walk = (start, step, max) => {
|
|
300
|
+
const out = [];
|
|
301
|
+
for (let t = start; t >= first && t <= last && out.length < max; t += step) {
|
|
302
|
+
if (present(t)) out.push(t);
|
|
303
|
+
}
|
|
304
|
+
return out;
|
|
305
|
+
};
|
|
306
|
+
let slots;
|
|
228
307
|
if (query.endingBefore) {
|
|
229
|
-
|
|
230
|
-
|
|
308
|
+
slots = walk(
|
|
309
|
+
Math.min(last, decodeCursor(query.endingBefore) - periodMs),
|
|
310
|
+
-periodMs,
|
|
311
|
+
limit
|
|
312
|
+
).reverse();
|
|
231
313
|
} else if (query.startingAfter) {
|
|
232
|
-
|
|
233
|
-
to = Math.min(last, from + (limit - 1) * periodMs);
|
|
314
|
+
slots = walk(Math.max(first, decodeCursor(query.startingAfter) + periodMs), periodMs, limit);
|
|
234
315
|
} else if (query.startTime) {
|
|
235
|
-
|
|
236
|
-
to = Math.min(last, from + (limit - 1) * periodMs);
|
|
316
|
+
slots = walk(first, periodMs, limit);
|
|
237
317
|
} else {
|
|
238
|
-
|
|
239
|
-
from = Math.max(first, to - (limit - 1) * periodMs);
|
|
240
|
-
}
|
|
241
|
-
const logs = [];
|
|
242
|
-
for (let t = from; t <= to; t += periodMs) {
|
|
243
|
-
logs.push(logAt(t, query.serialNo, opts.deviceId ?? 0));
|
|
318
|
+
slots = walk(last, -periodMs, limit).reverse();
|
|
244
319
|
}
|
|
320
|
+
const bodyPart = opts.bodyPart ?? "wrist";
|
|
321
|
+
const logs = slots.map(
|
|
322
|
+
(t) => logAt(t, query.serialNo, opts.deviceId ?? 0, bodyPart, dock.has(new Date(t).getHours()))
|
|
323
|
+
);
|
|
324
|
+
const from = slots[0] ?? 0;
|
|
325
|
+
const to = slots[slots.length - 1] ?? 0;
|
|
245
326
|
return {
|
|
246
327
|
requestId: query.requestId,
|
|
247
328
|
success: true,
|
|
248
329
|
logs,
|
|
249
330
|
startingAfter: logs.length ? encodeCursor(to) : null,
|
|
250
331
|
endingBefore: logs.length ? encodeCursor(from) : null,
|
|
251
|
-
hasMore: logs.length > 0 && to
|
|
252
|
-
hasPrevious: logs.length > 0 && from >
|
|
332
|
+
hasMore: logs.length > 0 && walk(to + periodMs, periodMs, 1).length > 0,
|
|
333
|
+
hasPrevious: logs.length > 0 && walk(from - periodMs, -periodMs, 1).length > 0
|
|
253
334
|
};
|
|
254
335
|
}
|
|
255
336
|
|
|
@@ -1718,6 +1799,19 @@ var CondorDevHarness = (() => {
|
|
|
1718
1799
|
}
|
|
1719
1800
|
};
|
|
1720
1801
|
|
|
1802
|
+
// src/version.ts
|
|
1803
|
+
function versionAtLeast(version, min) {
|
|
1804
|
+
if (!version) return false;
|
|
1805
|
+
const a = version.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
1806
|
+
const b = min.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
1807
|
+
for (let i = 0; i < 3; i++) {
|
|
1808
|
+
const x = a[i] ?? 0;
|
|
1809
|
+
const y = b[i] ?? 0;
|
|
1810
|
+
if (x !== y) return x > y;
|
|
1811
|
+
}
|
|
1812
|
+
return true;
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1721
1815
|
// src/dev/policy.ts
|
|
1722
1816
|
var CANVAS_POLICY = {
|
|
1723
1817
|
readOnlyKeys: [
|
|
@@ -1753,28 +1847,27 @@ var CondorDevHarness = (() => {
|
|
|
1753
1847
|
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';";
|
|
1754
1848
|
|
|
1755
1849
|
// src/dev/scenarios.ts
|
|
1756
|
-
var HARNESS_NATIVE_VERSION = "0.
|
|
1850
|
+
var HARNESS_NATIVE_VERSION = "0.18.0";
|
|
1757
1851
|
var base = {
|
|
1758
|
-
description: "Two devices, background service already running, realistic latency.",
|
|
1852
|
+
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.",
|
|
1759
1853
|
nativeVersion: HARNESS_NATIVE_VERSION,
|
|
1760
1854
|
latencyMs: [80, 250],
|
|
1761
1855
|
serviceStartMs: 0,
|
|
1762
|
-
userDeviceSilent: false,
|
|
1763
1856
|
logPeriodS: 60,
|
|
1764
1857
|
systemStatus: systemStatus(),
|
|
1765
1858
|
devices: () => twoDevices()
|
|
1766
1859
|
};
|
|
1767
1860
|
var SCENARIOS = {
|
|
1768
|
-
default: base,
|
|
1861
|
+
default: { ...base, firstSessionAfterMs: 3e3 },
|
|
1769
1862
|
"cold-start": {
|
|
1770
1863
|
...base,
|
|
1771
|
-
description: "App launched from cold: the background service takes 4 s to start, and every ping/userDevice sent before that is lost.
|
|
1864
|
+
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.",
|
|
1772
1865
|
serviceStartMs: 4e3
|
|
1773
1866
|
},
|
|
1774
1867
|
"no-devices": {
|
|
1775
1868
|
...base,
|
|
1776
|
-
description: "Account with no linked device: userDevice never answers and systemStatus.hasDevices is false. Your bundle must show an empty state.",
|
|
1777
|
-
|
|
1869
|
+
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.",
|
|
1870
|
+
userDeviceError: "NO_DEVICES",
|
|
1778
1871
|
systemStatus: systemStatus({ hasDevices: false })
|
|
1779
1872
|
},
|
|
1780
1873
|
"shared-serial": {
|
|
@@ -1784,9 +1877,8 @@ var CondorDevHarness = (() => {
|
|
|
1784
1877
|
},
|
|
1785
1878
|
offline: {
|
|
1786
1879
|
...base,
|
|
1787
|
-
description: "No internet. systemStatus.internet is false and sensorLog fails; userDevice answers from the cache.",
|
|
1788
|
-
|
|
1789
|
-
systemStatus: systemStatus({ internet: false })
|
|
1880
|
+
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 }).",
|
|
1881
|
+
systemStatus: systemStatus({ internet: false, hasDevicesKnown: false })
|
|
1790
1882
|
}
|
|
1791
1883
|
};
|
|
1792
1884
|
function resolveScenario(scenario) {
|
|
@@ -1805,6 +1897,7 @@ var CondorDevHarness = (() => {
|
|
|
1805
1897
|
|
|
1806
1898
|
// src/dev/harness.ts
|
|
1807
1899
|
var DEV_HARNESS_MARKER = "__condorDevHarness__";
|
|
1900
|
+
var STEP_MS = 200;
|
|
1808
1901
|
function createSimulatedNative(adapter, scenario, hooks = {}) {
|
|
1809
1902
|
const now = hooks.now ?? Date.now;
|
|
1810
1903
|
const startedAt = now();
|
|
@@ -1812,6 +1905,25 @@ var CondorDevHarness = (() => {
|
|
|
1812
1905
|
const timers = /* @__PURE__ */ new Set();
|
|
1813
1906
|
let status = { ...scenario.systemStatus };
|
|
1814
1907
|
let canGoBack = false;
|
|
1908
|
+
const alwaysReplies = versionAtLeast(scenario.nativeVersion, "0.18.0");
|
|
1909
|
+
let pendingUserDevice = false;
|
|
1910
|
+
let sensorLogCount = 0;
|
|
1911
|
+
const roster = scenario.devices().devicesConfig;
|
|
1912
|
+
const connected = new Map(roster.map((d) => [d.deviceId, false]));
|
|
1913
|
+
const subscribed = /* @__PURE__ */ new Set();
|
|
1914
|
+
const realTimeOn = /* @__PURE__ */ new Set();
|
|
1915
|
+
const streams = /* @__PURE__ */ new Map();
|
|
1916
|
+
const byId = versionAtLeast(scenario.nativeVersion, "0.17.0");
|
|
1917
|
+
const pendingSync = /* @__PURE__ */ new Set();
|
|
1918
|
+
const pendingErase = /* @__PURE__ */ new Set();
|
|
1919
|
+
const running = /* @__PURE__ */ new Set();
|
|
1920
|
+
const idOf = (device) => byId && roster.filter((d) => d.serialNo === device.serialNo).length > 1 ? { deviceId: device.deviceId } : {};
|
|
1921
|
+
const findDevice = (args) => args.deviceId !== void 0 ? roster.find((d) => d.deviceId === args.deviceId) : roster.find((d) => d.serialNo === args.serialNo);
|
|
1922
|
+
const statusForBundle = () => {
|
|
1923
|
+
if (alwaysReplies) return status;
|
|
1924
|
+
const { hasDevicesKnown: _omitted, ...older } = status;
|
|
1925
|
+
return older;
|
|
1926
|
+
};
|
|
1815
1927
|
const record2 = (entry) => {
|
|
1816
1928
|
const full = { at: now(), ...entry };
|
|
1817
1929
|
log.push(full);
|
|
@@ -1826,12 +1938,34 @@ var CondorDevHarness = (() => {
|
|
|
1826
1938
|
const reply = (key, payload, delay = latency()) => {
|
|
1827
1939
|
const timer = setTimeout(() => {
|
|
1828
1940
|
timers.delete(timer);
|
|
1829
|
-
|
|
1941
|
+
if (typeof payload === "function") payload = payload();
|
|
1942
|
+
record2({ kind: "reply", key, detail: `${key} \u2192 bundle`, payload });
|
|
1830
1943
|
adapter.triggerEmit(key, payload);
|
|
1831
1944
|
}, delay);
|
|
1832
1945
|
timers.add(timer);
|
|
1833
1946
|
};
|
|
1834
1947
|
const serviceUp = () => now() - startedAt >= scenario.serviceStartMs;
|
|
1948
|
+
const answerUserDevice = () => {
|
|
1949
|
+
if (scenario.userDeviceError) {
|
|
1950
|
+
if (!alwaysReplies) {
|
|
1951
|
+
record2({
|
|
1952
|
+
kind: "dropped",
|
|
1953
|
+
command: "userDevice",
|
|
1954
|
+
detail: `userDevice unanswered: the lookup failed (${scenario.userDeviceError}) and this app version replies only on success`
|
|
1955
|
+
});
|
|
1956
|
+
return;
|
|
1957
|
+
}
|
|
1958
|
+
reply("userDevice", {
|
|
1959
|
+
lastUpdate: now(),
|
|
1960
|
+
devicesConfig: [],
|
|
1961
|
+
devicesTelemetry: [],
|
|
1962
|
+
devicesUserConfig: [],
|
|
1963
|
+
error: scenario.userDeviceError
|
|
1964
|
+
});
|
|
1965
|
+
return;
|
|
1966
|
+
}
|
|
1967
|
+
reply("userDevice", scenario.devices());
|
|
1968
|
+
};
|
|
1835
1969
|
const language = () => {
|
|
1836
1970
|
const deviceLocale = typeof navigator !== "undefined" ? navigator.language : "en-US";
|
|
1837
1971
|
const lang = deviceLocale.slice(0, 2);
|
|
@@ -1848,7 +1982,7 @@ var CondorDevHarness = (() => {
|
|
|
1848
1982
|
if (command !== "handshake") {
|
|
1849
1983
|
const shown = JSON.stringify(args);
|
|
1850
1984
|
const detail = shown === "{}" ? command : `${command} ${shown.length > 160 ? `${shown.slice(0, 157)}...` : shown}`;
|
|
1851
|
-
record2({ kind: "sent", command, detail });
|
|
1985
|
+
record2({ kind: "sent", command, detail, args });
|
|
1852
1986
|
}
|
|
1853
1987
|
switch (command) {
|
|
1854
1988
|
case "handshake":
|
|
@@ -1860,8 +1994,8 @@ var CondorDevHarness = (() => {
|
|
|
1860
1994
|
return;
|
|
1861
1995
|
}
|
|
1862
1996
|
reply("handshake-ack", { nativeVersion: scenario.nativeVersion, accepted: true });
|
|
1863
|
-
reply("systemStatus",
|
|
1864
|
-
reply("language", language
|
|
1997
|
+
reply("systemStatus", statusForBundle, latency() + 20);
|
|
1998
|
+
reply("language", language, latency() + 30);
|
|
1865
1999
|
return;
|
|
1866
2000
|
case "ping":
|
|
1867
2001
|
if (!serviceUp()) {
|
|
@@ -1876,30 +2010,33 @@ var CondorDevHarness = (() => {
|
|
|
1876
2010
|
return;
|
|
1877
2011
|
case "userDevice":
|
|
1878
2012
|
if (!serviceUp()) {
|
|
2013
|
+
pendingUserDevice = alwaysReplies;
|
|
1879
2014
|
record2({
|
|
1880
2015
|
kind: "dropped",
|
|
1881
2016
|
command,
|
|
1882
|
-
detail: "userDevice lost: the background service is not running yet. Ask again."
|
|
1883
|
-
});
|
|
1884
|
-
return;
|
|
1885
|
-
}
|
|
1886
|
-
if (scenario.userDeviceSilent) {
|
|
1887
|
-
record2({
|
|
1888
|
-
kind: "dropped",
|
|
1889
|
-
command,
|
|
1890
|
-
detail: "userDevice unanswered: the account has no linked device (the app replies only on success)"
|
|
2017
|
+
detail: alwaysReplies ? "userDevice lost: the background service is not running yet; the app re-sends it when the service is up" : "userDevice lost: the background service is not running yet. Ask again."
|
|
1891
2018
|
});
|
|
1892
2019
|
return;
|
|
1893
2020
|
}
|
|
1894
|
-
|
|
2021
|
+
answerUserDevice();
|
|
1895
2022
|
return;
|
|
1896
2023
|
case "sensorLog": {
|
|
1897
2024
|
const query = args;
|
|
1898
|
-
|
|
2025
|
+
sensorLogCount += 1;
|
|
2026
|
+
if (scenario.sensorLogFailOnRequest === sensorLogCount) {
|
|
1899
2027
|
reply("sensorLog", {
|
|
1900
2028
|
requestId: query.requestId,
|
|
1901
2029
|
success: false,
|
|
1902
|
-
error:
|
|
2030
|
+
error: "Connection failed"
|
|
2031
|
+
});
|
|
2032
|
+
return;
|
|
2033
|
+
}
|
|
2034
|
+
const logError = scenario.sensorLogError ?? (status.internet === false ? "Connection failed" : void 0);
|
|
2035
|
+
if (logError) {
|
|
2036
|
+
reply("sensorLog", {
|
|
2037
|
+
requestId: query.requestId,
|
|
2038
|
+
success: false,
|
|
2039
|
+
error: logError
|
|
1903
2040
|
});
|
|
1904
2041
|
return;
|
|
1905
2042
|
}
|
|
@@ -1918,12 +2055,29 @@ var CondorDevHarness = (() => {
|
|
|
1918
2055
|
sensorLogPage(query, {
|
|
1919
2056
|
now: now(),
|
|
1920
2057
|
periodS: scenario.logPeriodS,
|
|
1921
|
-
deviceId: device?.deviceId
|
|
2058
|
+
deviceId: device?.deviceId,
|
|
2059
|
+
bodyPart: device?.deviceBodyPart ?? "wrist",
|
|
2060
|
+
history: scenario.history
|
|
1922
2061
|
})
|
|
1923
2062
|
);
|
|
1924
2063
|
return;
|
|
1925
2064
|
}
|
|
1926
|
-
case "realTime":
|
|
2065
|
+
case "realTime": {
|
|
2066
|
+
const device = findDevice(args);
|
|
2067
|
+
if (device) {
|
|
2068
|
+
if (args.value === true) realTimeOn.add(device.deviceId);
|
|
2069
|
+
else realTimeOn.delete(device.deviceId);
|
|
2070
|
+
syncStream(device.deviceId);
|
|
2071
|
+
if (args.value !== true && connected.get(device.deviceId) && !running.has(device.deviceId)) {
|
|
2072
|
+
endSession(device.deviceId, latency());
|
|
2073
|
+
}
|
|
2074
|
+
if (args.value === true && !connected.get(device.deviceId)) {
|
|
2075
|
+
record2({
|
|
2076
|
+
kind: "info",
|
|
2077
|
+
detail: `realTime on for device ${device.deviceId}, which is between BLE sessions: readings start when it connects`
|
|
2078
|
+
});
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
1927
2081
|
reply("realTime", {
|
|
1928
2082
|
serialNo: args.serialNo,
|
|
1929
2083
|
...args.deviceId !== void 0 ? { deviceId: args.deviceId } : {},
|
|
@@ -1931,23 +2085,37 @@ var CondorDevHarness = (() => {
|
|
|
1931
2085
|
value: args.value === true
|
|
1932
2086
|
});
|
|
1933
2087
|
return;
|
|
2088
|
+
}
|
|
1934
2089
|
case "syncTime":
|
|
1935
2090
|
case "deleteMemory":
|
|
2091
|
+
{
|
|
2092
|
+
const target = findDevice(args);
|
|
2093
|
+
if (target) (command === "syncTime" ? pendingSync : pendingErase).add(target.deviceId);
|
|
2094
|
+
}
|
|
2095
|
+
record2({
|
|
2096
|
+
kind: "info",
|
|
2097
|
+
detail: command === "deleteMemory" ? "deleteMemory recorded: the app erases the memory in the next sync, after the data is uploaded" : "syncTime recorded: the app sets the clock in the next sync"
|
|
2098
|
+
});
|
|
1936
2099
|
reply(command, {
|
|
1937
2100
|
serialNo: args.serialNo,
|
|
1938
2101
|
...args.deviceId !== void 0 ? { deviceId: args.deviceId } : {},
|
|
1939
2102
|
success: true
|
|
1940
2103
|
});
|
|
1941
2104
|
return;
|
|
1942
|
-
case "deviceConnection":
|
|
1943
|
-
|
|
2105
|
+
case "deviceConnection": {
|
|
2106
|
+
const device = findDevice(args);
|
|
2107
|
+
if (!device) {
|
|
1944
2108
|
reply("deviceConnection", {
|
|
1945
2109
|
serialNo: args.serialNo,
|
|
1946
2110
|
...args.deviceId !== void 0 ? { deviceId: args.deviceId } : {},
|
|
1947
|
-
isConnect:
|
|
2111
|
+
isConnect: false
|
|
1948
2112
|
});
|
|
2113
|
+
return;
|
|
1949
2114
|
}
|
|
2115
|
+
if (args.type === "add") subscribed.add(device.deviceId);
|
|
2116
|
+
else if (args.type === "remove") subscribed.delete(device.deviceId);
|
|
1950
2117
|
return;
|
|
2118
|
+
}
|
|
1951
2119
|
case "language":
|
|
1952
2120
|
reply("language", language());
|
|
1953
2121
|
return;
|
|
@@ -1958,7 +2126,7 @@ var CondorDevHarness = (() => {
|
|
|
1958
2126
|
missingEssentialPermissions: status.missingEssentialPermissions.filter((p) => p !== name),
|
|
1959
2127
|
missingOptionalPermissions: status.missingOptionalPermissions.filter((p) => p !== name)
|
|
1960
2128
|
};
|
|
1961
|
-
reply("systemStatus",
|
|
2129
|
+
reply("systemStatus", statusForBundle, latency() + 400);
|
|
1962
2130
|
return;
|
|
1963
2131
|
}
|
|
1964
2132
|
case "jsError":
|
|
@@ -1982,11 +2150,157 @@ var CondorDevHarness = (() => {
|
|
|
1982
2150
|
return;
|
|
1983
2151
|
}
|
|
1984
2152
|
};
|
|
2153
|
+
function emitConnection(deviceId) {
|
|
2154
|
+
if (!subscribed.has(deviceId)) return;
|
|
2155
|
+
const device = roster.find((d) => d.deviceId === deviceId);
|
|
2156
|
+
if (!device) return;
|
|
2157
|
+
reply("deviceConnection", {
|
|
2158
|
+
serialNo: device.serialNo,
|
|
2159
|
+
...idOf(device),
|
|
2160
|
+
isConnect: connected.get(deviceId) === true
|
|
2161
|
+
});
|
|
2162
|
+
}
|
|
2163
|
+
function syncStream(deviceId) {
|
|
2164
|
+
const want = realTimeOn.has(deviceId) && connected.get(deviceId) === true;
|
|
2165
|
+
const running2 = streams.get(deviceId);
|
|
2166
|
+
if (want && !running2) {
|
|
2167
|
+
const device = roster.find((d) => d.deviceId === deviceId);
|
|
2168
|
+
if (!device) return;
|
|
2169
|
+
streams.set(
|
|
2170
|
+
deviceId,
|
|
2171
|
+
setInterval(() => {
|
|
2172
|
+
adapter.triggerEmit(
|
|
2173
|
+
"deviceLightSensors",
|
|
2174
|
+
lightSensorsReading(device, now(), "deviceId" in idOf(device))
|
|
2175
|
+
);
|
|
2176
|
+
}, 1e3)
|
|
2177
|
+
);
|
|
2178
|
+
} else if (!want && running2) {
|
|
2179
|
+
clearInterval(running2);
|
|
2180
|
+
streams.delete(deviceId);
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
function setConnected(deviceId, value) {
|
|
2184
|
+
if (!connected.has(deviceId)) {
|
|
2185
|
+
record2({ kind: "info", detail: `setConnected: no device ${deviceId} in this scenario` });
|
|
2186
|
+
return;
|
|
2187
|
+
}
|
|
2188
|
+
if (connected.get(deviceId) === value) return;
|
|
2189
|
+
connected.set(deviceId, value);
|
|
2190
|
+
record2({
|
|
2191
|
+
kind: "info",
|
|
2192
|
+
detail: `device ${deviceId}: BLE session ${value ? "started" : "ended"}`
|
|
2193
|
+
});
|
|
2194
|
+
emitConnection(deviceId);
|
|
2195
|
+
syncStream(deviceId);
|
|
2196
|
+
const device = roster.find((d) => d.deviceId === deviceId);
|
|
2197
|
+
if (device) runSession(device, value);
|
|
2198
|
+
}
|
|
2199
|
+
function setSyncError(device, text, delay) {
|
|
2200
|
+
const timer = setTimeout(() => {
|
|
2201
|
+
timers.delete(timer);
|
|
2202
|
+
const byId2 = { ...status.deviceSyncErrorById ?? {} };
|
|
2203
|
+
const bySerial = { ...status.deviceSyncError };
|
|
2204
|
+
const shared = roster.filter((d) => d.serialNo === device.serialNo).length > 1;
|
|
2205
|
+
if (text === null) {
|
|
2206
|
+
if (!(device.deviceId in byId2)) return;
|
|
2207
|
+
delete byId2[device.deviceId];
|
|
2208
|
+
if (!shared) delete bySerial[device.serialNo];
|
|
2209
|
+
} else {
|
|
2210
|
+
byId2[device.deviceId] = text;
|
|
2211
|
+
if (!shared) bySerial[device.serialNo] = text;
|
|
2212
|
+
}
|
|
2213
|
+
status = { ...status, deviceSyncErrorById: byId2, deviceSyncError: bySerial };
|
|
2214
|
+
reply("systemStatus", statusForBundle, 0);
|
|
2215
|
+
}, delay);
|
|
2216
|
+
timers.add(timer);
|
|
2217
|
+
}
|
|
2218
|
+
function runSession(device, start) {
|
|
2219
|
+
const note = (message, messageCode, isError = false) => ({
|
|
2220
|
+
serialNo: device.serialNo,
|
|
2221
|
+
...idOf(device),
|
|
2222
|
+
isConnect: !isError,
|
|
2223
|
+
message,
|
|
2224
|
+
messageCode,
|
|
2225
|
+
isError
|
|
2226
|
+
});
|
|
2227
|
+
if (!start) {
|
|
2228
|
+
reply("deviceNotification", { ...note("Device disconnected", null), isConnect: false });
|
|
2229
|
+
return;
|
|
2230
|
+
}
|
|
2231
|
+
const id = device.deviceId;
|
|
2232
|
+
const steps = [
|
|
2233
|
+
["[Start] Enable BLE periodic", "enable_ble_periodic_notification", "enable_ble_periodic"],
|
|
2234
|
+
["[Not needed] Enable BLE periodic", "enable_ble_periodic_not_needed_notification", ""],
|
|
2235
|
+
["[Start] Set BLE period", "set_ble_period_notification", "set_ble_period"],
|
|
2236
|
+
["[Not needed] Set BLE period", "set_ble_period_not_needed_notification", ""],
|
|
2237
|
+
["[Start] Set sync time", "set_sync_time_notification", "set_sync_time"],
|
|
2238
|
+
...pendingSync.has(id) ? [] : [["[Not needed] Set sync time", "set_sync_time_not_needed_notification", ""]],
|
|
2239
|
+
["[Start] Get data log", "get_data_log_notification", "get_data_log"],
|
|
2240
|
+
["[Start] Send data log", "send_data_log_notification", "send_data_log"],
|
|
2241
|
+
[
|
|
2242
|
+
"[Success] Set last data log sent to server",
|
|
2243
|
+
"set_last_data_log_sent_to_server_notification",
|
|
2244
|
+
""
|
|
2245
|
+
],
|
|
2246
|
+
["[Start] Get system log", "get_system_log_notification", "get_system_log"],
|
|
2247
|
+
["[Start] Send system log", "send_system_log_notification", "send_system_log"],
|
|
2248
|
+
["[Start] Get full charge log", "get_full_charge_log_notification", "get_full_charge_log"],
|
|
2249
|
+
["[Start] Send full charge log", "send_full_charge_log_notification", "send_full_charge_log"],
|
|
2250
|
+
["[Start] Set erase memory", "set_erase_memory_notification", "set_erase_memory"],
|
|
2251
|
+
...pendingErase.has(id) ? [["[Start] Erase memory", "erase_memory_notification", ""]] : [],
|
|
2252
|
+
["[Start] Get device telemetry", "get_device_telemetry_notification", "get_device_telemetry"],
|
|
2253
|
+
["[Start] Save device telemetry", "save_device_telemetry_notification", ""]
|
|
2254
|
+
];
|
|
2255
|
+
const failAt = scenario.sessionFailAt ?? (status.internet === false ? "send_data_log" : void 0);
|
|
2256
|
+
running.add(id);
|
|
2257
|
+
let delay = latency();
|
|
2258
|
+
reply("deviceNotification", note("Device connected", null), delay);
|
|
2259
|
+
for (const [message, code, stage] of steps) {
|
|
2260
|
+
delay += STEP_MS;
|
|
2261
|
+
reply("deviceNotification", note(message, code), delay);
|
|
2262
|
+
if (stage && failAt === stage) {
|
|
2263
|
+
delay += STEP_MS;
|
|
2264
|
+
const what = message.replace(/^\[\w+\] /, "");
|
|
2265
|
+
reply("deviceNotification", note(`[Error] ${what}`, `${stage}_error`, true), delay);
|
|
2266
|
+
setSyncError(device, `Sync failed (${stage}_error)`, delay + 20);
|
|
2267
|
+
endSession(id, delay + STEP_MS);
|
|
2268
|
+
return;
|
|
2269
|
+
}
|
|
2270
|
+
if (stage === "set_sync_time") pendingSync.delete(id);
|
|
2271
|
+
}
|
|
2272
|
+
delay += STEP_MS;
|
|
2273
|
+
reply("deviceNotification", note("[Done] Pipeline", "pipeline_done_notification"), delay);
|
|
2274
|
+
setSyncError(device, null, delay + 20);
|
|
2275
|
+
pendingErase.delete(id);
|
|
2276
|
+
endSession(id, delay + STEP_MS);
|
|
2277
|
+
}
|
|
2278
|
+
function endSession(deviceId, delay) {
|
|
2279
|
+
const timer = setTimeout(() => {
|
|
2280
|
+
timers.delete(timer);
|
|
2281
|
+
running.delete(deviceId);
|
|
2282
|
+
if (!realTimeOn.has(deviceId)) setConnected(deviceId, false);
|
|
2283
|
+
}, delay);
|
|
2284
|
+
timers.add(timer);
|
|
2285
|
+
}
|
|
1985
2286
|
const unsubscribe = adapter.onCallHandler(handle);
|
|
2287
|
+
const first = roster[0];
|
|
2288
|
+
if (first && typeof scenario.firstSessionAfterMs === "number") {
|
|
2289
|
+
const timer = setTimeout(() => {
|
|
2290
|
+
timers.delete(timer);
|
|
2291
|
+
setConnected(first.deviceId, true);
|
|
2292
|
+
}, scenario.firstSessionAfterMs);
|
|
2293
|
+
timers.add(timer);
|
|
2294
|
+
}
|
|
1986
2295
|
if (scenario.serviceStartMs > 0) {
|
|
1987
2296
|
const timer = setTimeout(() => {
|
|
1988
2297
|
timers.delete(timer);
|
|
1989
2298
|
record2({ kind: "info", detail: "background service is up: ping and userDevice now answer" });
|
|
2299
|
+
if (pendingUserDevice) {
|
|
2300
|
+
pendingUserDevice = false;
|
|
2301
|
+
record2({ kind: "info", detail: "the app re-sent the pending userDevice" });
|
|
2302
|
+
answerUserDevice();
|
|
2303
|
+
}
|
|
1990
2304
|
}, scenario.serviceStartMs);
|
|
1991
2305
|
timers.add(timer);
|
|
1992
2306
|
}
|
|
@@ -2002,8 +2316,15 @@ var CondorDevHarness = (() => {
|
|
|
2002
2316
|
}
|
|
2003
2317
|
reply("navigationBack", {}, 0);
|
|
2004
2318
|
},
|
|
2319
|
+
setStatus(patch) {
|
|
2320
|
+
status = { ...status, ...patch };
|
|
2321
|
+
reply("systemStatus", statusForBundle(), 0);
|
|
2322
|
+
},
|
|
2323
|
+
setConnected,
|
|
2005
2324
|
dispose() {
|
|
2006
2325
|
unsubscribe();
|
|
2326
|
+
for (const t of streams.values()) clearInterval(t);
|
|
2327
|
+
streams.clear();
|
|
2007
2328
|
for (const t of timers) clearTimeout(t);
|
|
2008
2329
|
timers.clear();
|
|
2009
2330
|
}
|
|
@@ -2063,15 +2384,18 @@ var CondorDevHarness = (() => {
|
|
|
2063
2384
|
iframe.setAttribute("title", "Condor Canvas bundle");
|
|
2064
2385
|
iframe.setAttribute("style", "border:0;width:100%;height:100%;display:block;background:#fff");
|
|
2065
2386
|
(opts.mount ?? document.body).appendChild(iframe);
|
|
2387
|
+
let confirmations = "ask";
|
|
2066
2388
|
const shell = new BridgeShell({
|
|
2067
2389
|
iframe,
|
|
2068
2390
|
policy: CANVAS_POLICY,
|
|
2069
|
-
onConfirmationRequest: opts.onConfirmationRequest ?? confirmInPage
|
|
2391
|
+
onConfirmationRequest: (req) => confirmations === "ask" ? (opts.onConfirmationRequest ?? confirmInPage)(req) : confirmations === "accept"
|
|
2070
2392
|
});
|
|
2071
2393
|
if (opts.devPanel !== false) {
|
|
2072
2394
|
installDevPanel({ adapter, options: { startCollapsed: true } });
|
|
2073
2395
|
}
|
|
2074
|
-
|
|
2396
|
+
const deviceLike = "<script>try{delete Crypto.prototype.randomUUID}catch(e){}</script>";
|
|
2397
|
+
const html = /<head[^>]*>/i.test(opts.html) ? opts.html.replace(/<head[^>]*>/i, (m) => m + deviceLike) : deviceLike + opts.html;
|
|
2398
|
+
await shell.bootstrapClientIframeWithHtml(html);
|
|
2075
2399
|
try {
|
|
2076
2400
|
const result = await shell.verifySandbox();
|
|
2077
2401
|
if (!result.ok) {
|
|
@@ -2096,6 +2420,15 @@ var CondorDevHarness = (() => {
|
|
|
2096
2420
|
emit(key, payload) {
|
|
2097
2421
|
Bridge.mockEmit(key, payload);
|
|
2098
2422
|
},
|
|
2423
|
+
setStatus(patch) {
|
|
2424
|
+
native.setStatus(patch);
|
|
2425
|
+
},
|
|
2426
|
+
setConnected(deviceId, value) {
|
|
2427
|
+
native.setConnected(deviceId, value);
|
|
2428
|
+
},
|
|
2429
|
+
setConfirmations(mode) {
|
|
2430
|
+
confirmations = mode;
|
|
2431
|
+
},
|
|
2099
2432
|
pressBack() {
|
|
2100
2433
|
native.pressBack();
|
|
2101
2434
|
},
|