@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
|
@@ -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,26 @@ 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 sessionStart = /* @__PURE__ */ new Map();
|
|
1921
|
+
const idOf = (device) => byId && roster.filter((d) => d.serialNo === device.serialNo).length > 1 ? { deviceId: device.deviceId } : {};
|
|
1922
|
+
const findDevice = (args) => args.deviceId !== void 0 ? roster.find((d) => d.deviceId === args.deviceId) : roster.find((d) => d.serialNo === args.serialNo);
|
|
1923
|
+
const statusForBundle = () => {
|
|
1924
|
+
if (alwaysReplies) return status;
|
|
1925
|
+
const { hasDevicesKnown: _omitted, ...older } = status;
|
|
1926
|
+
return older;
|
|
1927
|
+
};
|
|
1815
1928
|
const record2 = (entry) => {
|
|
1816
1929
|
const full = { at: now(), ...entry };
|
|
1817
1930
|
log.push(full);
|
|
@@ -1826,12 +1939,34 @@ var CondorDevHarness = (() => {
|
|
|
1826
1939
|
const reply = (key, payload, delay = latency()) => {
|
|
1827
1940
|
const timer = setTimeout(() => {
|
|
1828
1941
|
timers.delete(timer);
|
|
1829
|
-
|
|
1942
|
+
if (typeof payload === "function") payload = payload();
|
|
1943
|
+
record2({ kind: "reply", key, detail: `${key} \u2192 bundle`, payload });
|
|
1830
1944
|
adapter.triggerEmit(key, payload);
|
|
1831
1945
|
}, delay);
|
|
1832
1946
|
timers.add(timer);
|
|
1833
1947
|
};
|
|
1834
1948
|
const serviceUp = () => now() - startedAt >= scenario.serviceStartMs;
|
|
1949
|
+
const answerUserDevice = () => {
|
|
1950
|
+
if (scenario.userDeviceError) {
|
|
1951
|
+
if (!alwaysReplies) {
|
|
1952
|
+
record2({
|
|
1953
|
+
kind: "dropped",
|
|
1954
|
+
command: "userDevice",
|
|
1955
|
+
detail: `userDevice unanswered: the lookup failed (${scenario.userDeviceError}) and this app version replies only on success`
|
|
1956
|
+
});
|
|
1957
|
+
return;
|
|
1958
|
+
}
|
|
1959
|
+
reply("userDevice", {
|
|
1960
|
+
lastUpdate: now(),
|
|
1961
|
+
devicesConfig: [],
|
|
1962
|
+
devicesTelemetry: [],
|
|
1963
|
+
devicesUserConfig: [],
|
|
1964
|
+
error: scenario.userDeviceError
|
|
1965
|
+
});
|
|
1966
|
+
return;
|
|
1967
|
+
}
|
|
1968
|
+
reply("userDevice", scenario.devices());
|
|
1969
|
+
};
|
|
1835
1970
|
const language = () => {
|
|
1836
1971
|
const deviceLocale = typeof navigator !== "undefined" ? navigator.language : "en-US";
|
|
1837
1972
|
const lang = deviceLocale.slice(0, 2);
|
|
@@ -1848,7 +1983,7 @@ var CondorDevHarness = (() => {
|
|
|
1848
1983
|
if (command !== "handshake") {
|
|
1849
1984
|
const shown = JSON.stringify(args);
|
|
1850
1985
|
const detail = shown === "{}" ? command : `${command} ${shown.length > 160 ? `${shown.slice(0, 157)}...` : shown}`;
|
|
1851
|
-
record2({ kind: "sent", command, detail });
|
|
1986
|
+
record2({ kind: "sent", command, detail, args });
|
|
1852
1987
|
}
|
|
1853
1988
|
switch (command) {
|
|
1854
1989
|
case "handshake":
|
|
@@ -1860,8 +1995,8 @@ var CondorDevHarness = (() => {
|
|
|
1860
1995
|
return;
|
|
1861
1996
|
}
|
|
1862
1997
|
reply("handshake-ack", { nativeVersion: scenario.nativeVersion, accepted: true });
|
|
1863
|
-
reply("systemStatus",
|
|
1864
|
-
reply("language", language
|
|
1998
|
+
reply("systemStatus", statusForBundle, latency() + 20);
|
|
1999
|
+
reply("language", language, latency() + 30);
|
|
1865
2000
|
return;
|
|
1866
2001
|
case "ping":
|
|
1867
2002
|
if (!serviceUp()) {
|
|
@@ -1876,30 +2011,33 @@ var CondorDevHarness = (() => {
|
|
|
1876
2011
|
return;
|
|
1877
2012
|
case "userDevice":
|
|
1878
2013
|
if (!serviceUp()) {
|
|
2014
|
+
pendingUserDevice = alwaysReplies;
|
|
1879
2015
|
record2({
|
|
1880
2016
|
kind: "dropped",
|
|
1881
2017
|
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)"
|
|
2018
|
+
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
2019
|
});
|
|
1892
2020
|
return;
|
|
1893
2021
|
}
|
|
1894
|
-
|
|
2022
|
+
answerUserDevice();
|
|
1895
2023
|
return;
|
|
1896
2024
|
case "sensorLog": {
|
|
1897
2025
|
const query = args;
|
|
1898
|
-
|
|
2026
|
+
sensorLogCount += 1;
|
|
2027
|
+
if (scenario.sensorLogFailOnRequest === sensorLogCount) {
|
|
1899
2028
|
reply("sensorLog", {
|
|
1900
2029
|
requestId: query.requestId,
|
|
1901
2030
|
success: false,
|
|
1902
|
-
error:
|
|
2031
|
+
error: "Connection failed"
|
|
2032
|
+
});
|
|
2033
|
+
return;
|
|
2034
|
+
}
|
|
2035
|
+
const logError = scenario.sensorLogError ?? (status.internet === false ? "Connection failed" : void 0);
|
|
2036
|
+
if (logError) {
|
|
2037
|
+
reply("sensorLog", {
|
|
2038
|
+
requestId: query.requestId,
|
|
2039
|
+
success: false,
|
|
2040
|
+
error: logError
|
|
1903
2041
|
});
|
|
1904
2042
|
return;
|
|
1905
2043
|
}
|
|
@@ -1918,12 +2056,29 @@ var CondorDevHarness = (() => {
|
|
|
1918
2056
|
sensorLogPage(query, {
|
|
1919
2057
|
now: now(),
|
|
1920
2058
|
periodS: scenario.logPeriodS,
|
|
1921
|
-
deviceId: device?.deviceId
|
|
2059
|
+
deviceId: device?.deviceId,
|
|
2060
|
+
bodyPart: device?.deviceBodyPart ?? "wrist",
|
|
2061
|
+
history: scenario.history
|
|
1922
2062
|
})
|
|
1923
2063
|
);
|
|
1924
2064
|
return;
|
|
1925
2065
|
}
|
|
1926
|
-
case "realTime":
|
|
2066
|
+
case "realTime": {
|
|
2067
|
+
const device = findDevice(args);
|
|
2068
|
+
if (device) {
|
|
2069
|
+
if (args.value === true) realTimeOn.add(device.deviceId);
|
|
2070
|
+
else realTimeOn.delete(device.deviceId);
|
|
2071
|
+
syncStream(device.deviceId);
|
|
2072
|
+
if (args.value !== true && connected.get(device.deviceId) && !running.has(device.deviceId)) {
|
|
2073
|
+
endSession(device.deviceId, latency());
|
|
2074
|
+
}
|
|
2075
|
+
if (args.value === true && !connected.get(device.deviceId)) {
|
|
2076
|
+
record2({
|
|
2077
|
+
kind: "info",
|
|
2078
|
+
detail: `realTime on for device ${device.deviceId}, which is between BLE sessions: readings start when it connects`
|
|
2079
|
+
});
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
1927
2082
|
reply("realTime", {
|
|
1928
2083
|
serialNo: args.serialNo,
|
|
1929
2084
|
...args.deviceId !== void 0 ? { deviceId: args.deviceId } : {},
|
|
@@ -1931,23 +2086,37 @@ var CondorDevHarness = (() => {
|
|
|
1931
2086
|
value: args.value === true
|
|
1932
2087
|
});
|
|
1933
2088
|
return;
|
|
2089
|
+
}
|
|
1934
2090
|
case "syncTime":
|
|
1935
2091
|
case "deleteMemory":
|
|
2092
|
+
{
|
|
2093
|
+
const target = findDevice(args);
|
|
2094
|
+
if (target) (command === "syncTime" ? pendingSync : pendingErase).add(target.deviceId);
|
|
2095
|
+
}
|
|
2096
|
+
record2({
|
|
2097
|
+
kind: "info",
|
|
2098
|
+
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"
|
|
2099
|
+
});
|
|
1936
2100
|
reply(command, {
|
|
1937
2101
|
serialNo: args.serialNo,
|
|
1938
2102
|
...args.deviceId !== void 0 ? { deviceId: args.deviceId } : {},
|
|
1939
2103
|
success: true
|
|
1940
2104
|
});
|
|
1941
2105
|
return;
|
|
1942
|
-
case "deviceConnection":
|
|
1943
|
-
|
|
2106
|
+
case "deviceConnection": {
|
|
2107
|
+
const device = findDevice(args);
|
|
2108
|
+
if (!device) {
|
|
1944
2109
|
reply("deviceConnection", {
|
|
1945
2110
|
serialNo: args.serialNo,
|
|
1946
2111
|
...args.deviceId !== void 0 ? { deviceId: args.deviceId } : {},
|
|
1947
|
-
isConnect:
|
|
2112
|
+
isConnect: false
|
|
1948
2113
|
});
|
|
2114
|
+
return;
|
|
1949
2115
|
}
|
|
2116
|
+
if (args.type === "add") subscribed.add(device.deviceId);
|
|
2117
|
+
else if (args.type === "remove") subscribed.delete(device.deviceId);
|
|
1950
2118
|
return;
|
|
2119
|
+
}
|
|
1951
2120
|
case "language":
|
|
1952
2121
|
reply("language", language());
|
|
1953
2122
|
return;
|
|
@@ -1958,7 +2127,7 @@ var CondorDevHarness = (() => {
|
|
|
1958
2127
|
missingEssentialPermissions: status.missingEssentialPermissions.filter((p) => p !== name),
|
|
1959
2128
|
missingOptionalPermissions: status.missingOptionalPermissions.filter((p) => p !== name)
|
|
1960
2129
|
};
|
|
1961
|
-
reply("systemStatus",
|
|
2130
|
+
reply("systemStatus", statusForBundle, latency() + 400);
|
|
1962
2131
|
return;
|
|
1963
2132
|
}
|
|
1964
2133
|
case "jsError":
|
|
@@ -1982,11 +2151,170 @@ var CondorDevHarness = (() => {
|
|
|
1982
2151
|
return;
|
|
1983
2152
|
}
|
|
1984
2153
|
};
|
|
2154
|
+
function emitConnection(deviceId) {
|
|
2155
|
+
if (!subscribed.has(deviceId)) return;
|
|
2156
|
+
const device = roster.find((d) => d.deviceId === deviceId);
|
|
2157
|
+
if (!device) return;
|
|
2158
|
+
reply("deviceConnection", {
|
|
2159
|
+
serialNo: device.serialNo,
|
|
2160
|
+
...idOf(device),
|
|
2161
|
+
isConnect: connected.get(deviceId) === true
|
|
2162
|
+
});
|
|
2163
|
+
}
|
|
2164
|
+
function syncStream(deviceId) {
|
|
2165
|
+
const want = realTimeOn.has(deviceId) && connected.get(deviceId) === true;
|
|
2166
|
+
const running2 = streams.get(deviceId);
|
|
2167
|
+
if (want && !running2) {
|
|
2168
|
+
const device = roster.find((d) => d.deviceId === deviceId);
|
|
2169
|
+
if (!device) return;
|
|
2170
|
+
streams.set(
|
|
2171
|
+
deviceId,
|
|
2172
|
+
setInterval(() => {
|
|
2173
|
+
adapter.triggerEmit(
|
|
2174
|
+
"deviceLightSensors",
|
|
2175
|
+
lightSensorsReading(device, now(), "deviceId" in idOf(device))
|
|
2176
|
+
);
|
|
2177
|
+
}, 1e3)
|
|
2178
|
+
);
|
|
2179
|
+
} else if (!want && running2) {
|
|
2180
|
+
clearInterval(running2);
|
|
2181
|
+
streams.delete(deviceId);
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
function setConnected(deviceId, value) {
|
|
2185
|
+
if (!connected.has(deviceId)) {
|
|
2186
|
+
record2({ kind: "info", detail: `setConnected: no device ${deviceId} in this scenario` });
|
|
2187
|
+
return;
|
|
2188
|
+
}
|
|
2189
|
+
if (connected.get(deviceId) === value) return;
|
|
2190
|
+
connected.set(deviceId, value);
|
|
2191
|
+
record2({
|
|
2192
|
+
kind: "info",
|
|
2193
|
+
detail: `device ${deviceId}: BLE session ${value ? "started" : "ended"}`
|
|
2194
|
+
});
|
|
2195
|
+
emitConnection(deviceId);
|
|
2196
|
+
syncStream(deviceId);
|
|
2197
|
+
const device = roster.find((d) => d.deviceId === deviceId);
|
|
2198
|
+
if (device) runSession(device, value);
|
|
2199
|
+
}
|
|
2200
|
+
function setSyncError(device, text, delay) {
|
|
2201
|
+
const timer = setTimeout(() => {
|
|
2202
|
+
timers.delete(timer);
|
|
2203
|
+
const byId2 = { ...status.deviceSyncErrorById ?? {} };
|
|
2204
|
+
const bySerial = { ...status.deviceSyncError };
|
|
2205
|
+
const shared = roster.filter((d) => d.serialNo === device.serialNo).length > 1;
|
|
2206
|
+
if (text === null) {
|
|
2207
|
+
if (!(device.deviceId in byId2)) return;
|
|
2208
|
+
delete byId2[device.deviceId];
|
|
2209
|
+
if (!shared) delete bySerial[device.serialNo];
|
|
2210
|
+
} else {
|
|
2211
|
+
byId2[device.deviceId] = text;
|
|
2212
|
+
if (!shared) bySerial[device.serialNo] = text;
|
|
2213
|
+
}
|
|
2214
|
+
status = { ...status, deviceSyncErrorById: byId2, deviceSyncError: bySerial };
|
|
2215
|
+
reply("systemStatus", statusForBundle, 0);
|
|
2216
|
+
}, delay);
|
|
2217
|
+
timers.add(timer);
|
|
2218
|
+
}
|
|
2219
|
+
function runSession(device, start) {
|
|
2220
|
+
const note = (message, messageCode, isError = false) => ({
|
|
2221
|
+
serialNo: device.serialNo,
|
|
2222
|
+
...idOf(device),
|
|
2223
|
+
isConnect: !isError,
|
|
2224
|
+
message,
|
|
2225
|
+
messageCode,
|
|
2226
|
+
isError
|
|
2227
|
+
});
|
|
2228
|
+
if (!start) {
|
|
2229
|
+
reply("deviceNotification", { ...note("Device disconnected", null), isConnect: false });
|
|
2230
|
+
return;
|
|
2231
|
+
}
|
|
2232
|
+
const id = device.deviceId;
|
|
2233
|
+
const steps = [
|
|
2234
|
+
["[Start] Enable BLE periodic", "enable_ble_periodic_notification", "enable_ble_periodic"],
|
|
2235
|
+
["[Not needed] Enable BLE periodic", "enable_ble_periodic_not_needed_notification", ""],
|
|
2236
|
+
["[Start] Set BLE period", "set_ble_period_notification", "set_ble_period"],
|
|
2237
|
+
["[Not needed] Set BLE period", "set_ble_period_not_needed_notification", ""],
|
|
2238
|
+
["[Start] Set sync time", "set_sync_time_notification", "set_sync_time"],
|
|
2239
|
+
...pendingSync.has(id) ? [] : [["[Not needed] Set sync time", "set_sync_time_not_needed_notification", ""]],
|
|
2240
|
+
["[Start] Get data log", "get_data_log_notification", "get_data_log"],
|
|
2241
|
+
["[Start] Send data log", "send_data_log_notification", "send_data_log"],
|
|
2242
|
+
[
|
|
2243
|
+
"[Success] Set last data log sent to server",
|
|
2244
|
+
"set_last_data_log_sent_to_server_notification",
|
|
2245
|
+
""
|
|
2246
|
+
],
|
|
2247
|
+
["[Start] Get system log", "get_system_log_notification", "get_system_log"],
|
|
2248
|
+
["[Start] Send system log", "send_system_log_notification", "send_system_log"],
|
|
2249
|
+
["[Start] Get full charge log", "get_full_charge_log_notification", "get_full_charge_log"],
|
|
2250
|
+
["[Start] Send full charge log", "send_full_charge_log_notification", "send_full_charge_log"],
|
|
2251
|
+
["[Start] Set erase memory", "set_erase_memory_notification", "set_erase_memory"],
|
|
2252
|
+
...pendingErase.has(id) ? [["[Start] Erase memory", "erase_memory_notification", ""]] : [],
|
|
2253
|
+
["[Start] Get device telemetry", "get_device_telemetry_notification", "get_device_telemetry"],
|
|
2254
|
+
["[Start] Save device telemetry", "save_device_telemetry_notification", ""]
|
|
2255
|
+
];
|
|
2256
|
+
const failAt = scenario.sessionFailAt ?? (status.internet === false ? "send_data_log" : void 0);
|
|
2257
|
+
running.add(id);
|
|
2258
|
+
sessionStart.set(id, now());
|
|
2259
|
+
let delay = latency();
|
|
2260
|
+
reply("deviceNotification", note("Device connected", null), delay);
|
|
2261
|
+
for (const [message, code, stage] of steps) {
|
|
2262
|
+
delay += STEP_MS;
|
|
2263
|
+
reply("deviceNotification", note(message, code), delay);
|
|
2264
|
+
if (stage && failAt === stage) {
|
|
2265
|
+
delay += STEP_MS;
|
|
2266
|
+
const what = message.replace(/^\[\w+\] /, "");
|
|
2267
|
+
reply("deviceNotification", note(`[Error] ${what}`, `${stage}_error`, true), delay);
|
|
2268
|
+
setSyncError(device, `Sync failed (${stage}_error)`, delay + 20);
|
|
2269
|
+
endSession(id, delay + STEP_MS);
|
|
2270
|
+
return;
|
|
2271
|
+
}
|
|
2272
|
+
if (stage === "set_sync_time") pendingSync.delete(id);
|
|
2273
|
+
}
|
|
2274
|
+
delay += STEP_MS;
|
|
2275
|
+
reply("deviceNotification", note("[Done] Pipeline", "pipeline_done_notification"), delay);
|
|
2276
|
+
setSyncError(device, null, delay + 20);
|
|
2277
|
+
pendingErase.delete(id);
|
|
2278
|
+
endSession(id, delay + STEP_MS);
|
|
2279
|
+
}
|
|
2280
|
+
function endSession(deviceId, delay) {
|
|
2281
|
+
const timer = setTimeout(() => {
|
|
2282
|
+
timers.delete(timer);
|
|
2283
|
+
running.delete(deviceId);
|
|
2284
|
+
if (!realTimeOn.has(deviceId)) {
|
|
2285
|
+
setConnected(deviceId, false);
|
|
2286
|
+
return;
|
|
2287
|
+
}
|
|
2288
|
+
const startedAt2 = sessionStart.get(deviceId) ?? now();
|
|
2289
|
+
const hold = setTimeout(
|
|
2290
|
+
() => {
|
|
2291
|
+
timers.delete(hold);
|
|
2292
|
+
if (connected.get(deviceId) && !running.has(deviceId)) setConnected(deviceId, false);
|
|
2293
|
+
},
|
|
2294
|
+
Math.max(0, startedAt2 + (scenario.realTimeSessionMs ?? 18e4) - now())
|
|
2295
|
+
);
|
|
2296
|
+
timers.add(hold);
|
|
2297
|
+
}, delay);
|
|
2298
|
+
timers.add(timer);
|
|
2299
|
+
}
|
|
1985
2300
|
const unsubscribe = adapter.onCallHandler(handle);
|
|
2301
|
+
const first = roster[0];
|
|
2302
|
+
if (first && typeof scenario.firstSessionAfterMs === "number") {
|
|
2303
|
+
const timer = setTimeout(() => {
|
|
2304
|
+
timers.delete(timer);
|
|
2305
|
+
setConnected(first.deviceId, true);
|
|
2306
|
+
}, scenario.firstSessionAfterMs);
|
|
2307
|
+
timers.add(timer);
|
|
2308
|
+
}
|
|
1986
2309
|
if (scenario.serviceStartMs > 0) {
|
|
1987
2310
|
const timer = setTimeout(() => {
|
|
1988
2311
|
timers.delete(timer);
|
|
1989
2312
|
record2({ kind: "info", detail: "background service is up: ping and userDevice now answer" });
|
|
2313
|
+
if (pendingUserDevice) {
|
|
2314
|
+
pendingUserDevice = false;
|
|
2315
|
+
record2({ kind: "info", detail: "the app re-sent the pending userDevice" });
|
|
2316
|
+
answerUserDevice();
|
|
2317
|
+
}
|
|
1990
2318
|
}, scenario.serviceStartMs);
|
|
1991
2319
|
timers.add(timer);
|
|
1992
2320
|
}
|
|
@@ -2002,8 +2330,15 @@ var CondorDevHarness = (() => {
|
|
|
2002
2330
|
}
|
|
2003
2331
|
reply("navigationBack", {}, 0);
|
|
2004
2332
|
},
|
|
2333
|
+
setStatus(patch) {
|
|
2334
|
+
status = { ...status, ...patch };
|
|
2335
|
+
reply("systemStatus", statusForBundle(), 0);
|
|
2336
|
+
},
|
|
2337
|
+
setConnected,
|
|
2005
2338
|
dispose() {
|
|
2006
2339
|
unsubscribe();
|
|
2340
|
+
for (const t of streams.values()) clearInterval(t);
|
|
2341
|
+
streams.clear();
|
|
2007
2342
|
for (const t of timers) clearTimeout(t);
|
|
2008
2343
|
timers.clear();
|
|
2009
2344
|
}
|
|
@@ -2063,15 +2398,18 @@ var CondorDevHarness = (() => {
|
|
|
2063
2398
|
iframe.setAttribute("title", "Condor Canvas bundle");
|
|
2064
2399
|
iframe.setAttribute("style", "border:0;width:100%;height:100%;display:block;background:#fff");
|
|
2065
2400
|
(opts.mount ?? document.body).appendChild(iframe);
|
|
2401
|
+
let confirmations = "ask";
|
|
2066
2402
|
const shell = new BridgeShell({
|
|
2067
2403
|
iframe,
|
|
2068
2404
|
policy: CANVAS_POLICY,
|
|
2069
|
-
onConfirmationRequest: opts.onConfirmationRequest ?? confirmInPage
|
|
2405
|
+
onConfirmationRequest: (req) => confirmations === "ask" ? (opts.onConfirmationRequest ?? confirmInPage)(req) : confirmations === "accept"
|
|
2070
2406
|
});
|
|
2071
2407
|
if (opts.devPanel !== false) {
|
|
2072
2408
|
installDevPanel({ adapter, options: { startCollapsed: true } });
|
|
2073
2409
|
}
|
|
2074
|
-
|
|
2410
|
+
const deviceLike = "<script>try{delete Crypto.prototype.randomUUID}catch(e){}</script>";
|
|
2411
|
+
const html = /<head[^>]*>/i.test(opts.html) ? opts.html.replace(/<head[^>]*>/i, (m) => m + deviceLike) : deviceLike + opts.html;
|
|
2412
|
+
await shell.bootstrapClientIframeWithHtml(html);
|
|
2075
2413
|
try {
|
|
2076
2414
|
const result = await shell.verifySandbox();
|
|
2077
2415
|
if (!result.ok) {
|
|
@@ -2096,6 +2434,15 @@ var CondorDevHarness = (() => {
|
|
|
2096
2434
|
emit(key, payload) {
|
|
2097
2435
|
Bridge.mockEmit(key, payload);
|
|
2098
2436
|
},
|
|
2437
|
+
setStatus(patch) {
|
|
2438
|
+
native.setStatus(patch);
|
|
2439
|
+
},
|
|
2440
|
+
setConnected(deviceId, value) {
|
|
2441
|
+
native.setConnected(deviceId, value);
|
|
2442
|
+
},
|
|
2443
|
+
setConfirmations(mode) {
|
|
2444
|
+
confirmations = mode;
|
|
2445
|
+
},
|
|
2099
2446
|
pressBack() {
|
|
2100
2447
|
native.pressBack();
|
|
2101
2448
|
},
|