@mikrojs/native 0.19.0 → 0.20.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/CMakeLists.txt +3 -1
- package/include/mikrojs/mikrojs.h +21 -0
- package/include/mikrojs/ota_policy.h +17 -0
- package/include/mikrojs/platform.h +3 -0
- package/include/mikrojs/private.h +40 -0
- package/include/mikrojs/utils.h +5 -0
- package/package.json +5 -4
- package/prebuilds/darwin-arm64/mikrojs.napi.node +0 -0
- package/prebuilds/linux-arm64/mikrojs.napi.node +0 -0
- package/prebuilds/linux-x64/mikrojs.napi.node +0 -0
- package/runtime/internal.d.ts +5 -5
- package/runtime/ota/ota.ts +2 -10
- package/runtime/ota/types.ts +58 -89
- package/runtime/test/test.ts +68 -31
- package/runtime/watchdog/types.ts +9 -0
- package/runtime/watchdog/watchdog.ts +7 -0
- package/src/eval_bytecode.cpp +1 -0
- package/src/mik_app_config.cpp +38 -0
- package/src/mik_console.cpp +5 -0
- package/src/mik_ota_policy.cpp +34 -0
- package/src/mik_repl.cpp +44 -24
- package/src/mik_watchdog.cpp +170 -0
- package/src/mikrojs.cpp +36 -1
- package/src/utils.cpp +21 -0
package/CMakeLists.txt
CHANGED
|
@@ -61,6 +61,7 @@ set(MIKROJS_CORE_SOURCES
|
|
|
61
61
|
src/mik_ota_js_hooks.cpp
|
|
62
62
|
src/mik_ota_client.cpp
|
|
63
63
|
src/mik_sys_codec.cpp
|
|
64
|
+
src/mik_watchdog.cpp
|
|
64
65
|
)
|
|
65
66
|
|
|
66
67
|
add_library(mikrojs STATIC
|
|
@@ -91,7 +92,7 @@ endif()
|
|
|
91
92
|
include(cmake/mikrojs_bytecode.cmake)
|
|
92
93
|
mikrojs_generate_bytecode(
|
|
93
94
|
RUNTIME_DIR "${CMAKE_CURRENT_SOURCE_DIR}/runtime"
|
|
94
|
-
MODULES abort cbor env result schema fs i2c kv/nvs kv/rtc kv/shared module neopixel observable observable/lazy observable/operators ota ota/client ota/config pin pwm reader sleep spi sntp stdio stream sys test uart udp
|
|
95
|
+
MODULES abort cbor env result schema fs i2c kv/nvs kv/rtc kv/shared module neopixel observable observable/lazy observable/operators ota ota/client ota/config pin pwm reader sleep spi sntp stdio stream sys test uart udp watchdog
|
|
95
96
|
MODULE_PREFIX "mikro"
|
|
96
97
|
SYMBOL_PREFIX "mikro"
|
|
97
98
|
TARGET gen_bytecode
|
|
@@ -222,6 +223,7 @@ if(BUILD_TESTING)
|
|
|
222
223
|
test/ota_wire_fixtures_test.cpp
|
|
223
224
|
test/schema_conformance_test.cpp
|
|
224
225
|
test/sys_codec_test.cpp
|
|
226
|
+
test/watchdog_test.cpp
|
|
225
227
|
)
|
|
226
228
|
|
|
227
229
|
# ── Check-in wire fixtures ───────────────────────────────────────
|
|
@@ -54,8 +54,18 @@ typedef struct MIKConfig {
|
|
|
54
54
|
char log_dir[64];
|
|
55
55
|
uint32_t log_max_size;
|
|
56
56
|
MIKLogFlush log_flush;
|
|
57
|
+
/* Watchdog budgets in ms, 0 = disabled. Mirror of the `watchdog` key in
|
|
58
|
+
* the host-side TS config. blocking: one event-loop turn holding the
|
|
59
|
+
* loop; feed: gap between watchdog.feed() calls; awake: time since boot. */
|
|
60
|
+
int blocking_timeout_ms;
|
|
61
|
+
int feed_timeout_ms;
|
|
62
|
+
int awake_timeout_ms;
|
|
57
63
|
} MIKConfig;
|
|
58
64
|
|
|
65
|
+
/* Shipped blocking default. The ESP32 side static_asserts this against the
|
|
66
|
+
* hardware TWDT timeout so the two cannot drift out of order. */
|
|
67
|
+
#define MIK_WATCHDOG_BLOCKING_DEFAULT_MS 30000
|
|
68
|
+
|
|
59
69
|
void MIK_DefaultConfig(MIKConfig* config);
|
|
60
70
|
int MIK_LoadConfig(const char* base_path, MIKConfig* config);
|
|
61
71
|
void MIK_SetConfig(MIKRuntime* mik_rt, const MIKConfig* config);
|
|
@@ -400,6 +410,17 @@ void MIK_ProtocolClose(void);
|
|
|
400
410
|
* and must detach before freeing. */
|
|
401
411
|
void MIK_ProtocolAttach(MIKRuntime* mik_rt);
|
|
402
412
|
|
|
413
|
+
/* Record what `mik_rt` was handed at boot, reported on MSG_READY. Call once,
|
|
414
|
+
* from the boot path, at the point the app's entry is about to be evaluated:
|
|
415
|
+
* the figures then describe the floor an app starts from rather than whatever
|
|
416
|
+
* happens to be free when a client connects. Later calls are ignored, so a
|
|
417
|
+
* supervisor that swaps runtimes can't overwrite the boot reading with a
|
|
418
|
+
* per-test one. MIK_ProtocolAttach calls this itself, which covers embedders
|
|
419
|
+
* that never call it; a boot path with a test supervisor should call it
|
|
420
|
+
* explicitly, before the supervisor allocates, so both modes report the same
|
|
421
|
+
* figure. */
|
|
422
|
+
void MIK_CaptureBootMemory(MIKRuntime* mik_rt);
|
|
423
|
+
|
|
403
424
|
/* Unbind the current runtime. Call before MIK_FreeRuntime when swapping
|
|
404
425
|
* runtimes mid-session. The session remains open. */
|
|
405
426
|
void MIK_ProtocolDetach(void);
|
|
@@ -51,6 +51,18 @@ public:
|
|
|
51
51
|
|
|
52
52
|
using MIKOtaDownloadFn = std::function<bool(MIKOtaUpdate& update, MIKOtaError* out_err)>;
|
|
53
53
|
|
|
54
|
+
/* Why the last offered build was not taken, in wire-string form, held in the
|
|
55
|
+
* store until a completed check-in delivers it. Persisted rather than held in
|
|
56
|
+
* memory (unlike the built-in client's copy) because an own-transport decline
|
|
57
|
+
* happens after that wake's check-in, so it must survive a deep sleep to make
|
|
58
|
+
* the next report. Field caps match the registry's checkin validation, which
|
|
59
|
+
* rejects a body whose lastDecline oversteps them. */
|
|
60
|
+
struct MIKOtaDeclineRecord {
|
|
61
|
+
char checksum[65] = {};
|
|
62
|
+
char reason[65] = {};
|
|
63
|
+
char detail[257] = {};
|
|
64
|
+
};
|
|
65
|
+
|
|
54
66
|
class MIKOtaStore {
|
|
55
67
|
public:
|
|
56
68
|
explicit MIKOtaStore(const MIKOtaEnv* env) : env_(env) {}
|
|
@@ -75,6 +87,11 @@ public:
|
|
|
75
87
|
bool GetInFlight() const;
|
|
76
88
|
void SetInFlight(bool in_flight);
|
|
77
89
|
|
|
90
|
+
/* False when no record stands. */
|
|
91
|
+
bool GetDecline(MIKOtaDeclineRecord* out) const;
|
|
92
|
+
void SetDecline(const MIKOtaDeclineRecord& record);
|
|
93
|
+
void ClearDecline();
|
|
94
|
+
|
|
78
95
|
private:
|
|
79
96
|
const MIKOtaEnv* env_;
|
|
80
97
|
};
|
|
@@ -19,6 +19,9 @@ typedef struct MIKPlatform {
|
|
|
19
19
|
* deep sleep (hosts), where callers fall back to restart(). */
|
|
20
20
|
void (*deep_sleep_us)(uint64_t us);
|
|
21
21
|
void (*yield)(void);
|
|
22
|
+
/** Feed the hardware task watchdog for the calling task. NULL where the
|
|
23
|
+
* platform has none (hosts) or the watchdog is compiled out. */
|
|
24
|
+
void (*feed_watchdog)(void);
|
|
22
25
|
size_t (*get_free_system_mem)(void);
|
|
23
26
|
size_t (*get_min_free_system_mem)(void); /* All-time low watermark */
|
|
24
27
|
size_t (*get_total_system_mem)(void);
|
|
@@ -80,6 +80,21 @@ struct MIKRuntime {
|
|
|
80
80
|
* the host can still deploy/clean/--recover during the grace window,
|
|
81
81
|
* then platform->restart() fires from MIK_Loop. */
|
|
82
82
|
int64_t restart_at_us;
|
|
83
|
+
/* Watchdog state (mik_watchdog.cpp). All per-runtime, no atomics needed.
|
|
84
|
+
* blocking_start_us == 0 means "stamp on the next interrupt poll"; the
|
|
85
|
+
* handler switches itself off when it fires and back on at mik__blocking_begin. */
|
|
86
|
+
int64_t blocking_start_us;
|
|
87
|
+
bool blocking_armed;
|
|
88
|
+
/* Set when the limit is hit, consumed by the error dump that reports it. */
|
|
89
|
+
bool blocking_tripped;
|
|
90
|
+
/* Feed deadline: last feed()/re-stamp; feed_armed clears when it fires. */
|
|
91
|
+
int64_t feed_last_us;
|
|
92
|
+
bool feed_armed;
|
|
93
|
+
/* Awake budget: deploy-pause time credited against get_boot_us(). */
|
|
94
|
+
int64_t awake_pause_offset_us;
|
|
95
|
+
bool awake_armed;
|
|
96
|
+
/* Boot-clock stamp of the current pause; 0 = not paused. */
|
|
97
|
+
int64_t pause_start_us;
|
|
83
98
|
const char* fs_base_path;
|
|
84
99
|
const char* fs_root; /* Sandbox root for mikrojs/fs operations (separate from module resolution) */
|
|
85
100
|
size_t fs_limit; /* Max bytes for fs_root (0 = unlimited) */
|
|
@@ -296,6 +311,31 @@ JSModuleDef* mik__udp_init(JSContext* ctx);
|
|
|
296
311
|
JSModuleDef* mik__observable_init(JSContext* ctx);
|
|
297
312
|
void mik__observable_dispatch_free(struct MIKRuntime* mik_rt);
|
|
298
313
|
|
|
314
|
+
/* Watchdog (mik_watchdog.cpp). */
|
|
315
|
+
/* Start a fresh blocking budget: top of each MIK_Loop pass, eval entry
|
|
316
|
+
* points, REPL eval, and on return from a deliberately blocking native
|
|
317
|
+
* (lightSleep). One store; the interrupt handler stamps on its next poll. */
|
|
318
|
+
void mik__blocking_begin(struct MIKRuntime* mik_rt);
|
|
319
|
+
/* JS_SetInterruptHandler callback; opaque is the MIKRuntime*. */
|
|
320
|
+
int mik__watchdog_interrupt(JSRuntime* rt, void* opaque);
|
|
321
|
+
/* If the blocking deadline was hit, log the watchdog line and clear the
|
|
322
|
+
* flag. Returns whether it did; callers print the error trace after. */
|
|
323
|
+
bool mik__watchdog_report_blocking(struct MIKRuntime* mik_rt);
|
|
324
|
+
/* Switch feed/awake on from mik_rt->config. Called once the config is set. */
|
|
325
|
+
void mik__watchdog_arm(struct MIKRuntime* mik_rt);
|
|
326
|
+
/* Feed and awake checks. Runs at the top of MIK_Loop after the restart_at_us
|
|
327
|
+
* branch; a feed miss throws an uncatchable error into mik_rt->ctx, on an
|
|
328
|
+
* awake overrun calls MIK_Stop directly. */
|
|
329
|
+
void mik__watchdog_check(struct MIKRuntime* mik_rt);
|
|
330
|
+
/* On return from lightSleep: fresh blocking budget and feed window. */
|
|
331
|
+
void mik__watchdog_wake(struct MIKRuntime* mik_rt);
|
|
332
|
+
/* Deploy/REPL pause bookkeeping: re-stamp the feed clock and credit the
|
|
333
|
+
* paused span to the awake budget. */
|
|
334
|
+
void mik__watchdog_pause_begin(struct MIKRuntime* mik_rt);
|
|
335
|
+
void mik__watchdog_pause_end(struct MIKRuntime* mik_rt);
|
|
336
|
+
/* Registers native:mikro/watchdog (feed) on ctx. */
|
|
337
|
+
void mik__watchdog_init(JSContext* ctx);
|
|
338
|
+
|
|
299
339
|
bool mik__repl_is_evaluating(void);
|
|
300
340
|
|
|
301
341
|
/* REPL protocol mode (mik_repl.cpp) — used by mik_console.cpp, mik_stdio.cpp */
|
package/include/mikrojs/utils.h
CHANGED
|
@@ -68,6 +68,11 @@ void mik_assert(const struct AssertionInfo info);
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
void mik_call_handler(JSContext* ctx, JSValue func, int argc, JSValue* argv);
|
|
71
|
+
/* Print one always-visible error line the way console.error does: an error
|
|
72
|
+
* frame in protocol mode, stderr otherwise. platform->log is filtered per
|
|
73
|
+
* tag on the device and silent by default, so lines that precede a reboot
|
|
74
|
+
* (watchdog lines, panic actions) go through here instead. */
|
|
75
|
+
void mik__print_error_line(const char* fmt, ...) __attribute__((format(printf, 1, 2)));
|
|
71
76
|
void mik_dump_error(JSContext* ctx);
|
|
72
77
|
void mik_dump_error1(JSContext* ctx, JSValue exception_val);
|
|
73
78
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikrojs/native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"description": "Mikro.js C++ runtime library and Node.js native addon",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"esp32",
|
|
@@ -80,21 +80,22 @@
|
|
|
80
80
|
"./runtime/test/types": "./runtime/test/types.ts",
|
|
81
81
|
"./runtime/uart/types": "./runtime/uart/types.ts",
|
|
82
82
|
"./runtime/udp/types": "./runtime/udp/types.ts",
|
|
83
|
+
"./runtime/watchdog/types": "./runtime/watchdog/types.ts",
|
|
83
84
|
"./runtime/wifi/types": "./runtime/wifi/types.ts"
|
|
84
85
|
},
|
|
85
86
|
"dependencies": {
|
|
86
87
|
"cmake-js": "^8.0.0",
|
|
87
88
|
"node-addon-api": "^8.7.0",
|
|
88
89
|
"node-gyp-build": "^4.8.4",
|
|
89
|
-
"@mikrojs/quickjs": "0.
|
|
90
|
+
"@mikrojs/quickjs": "0.20.0"
|
|
90
91
|
},
|
|
91
92
|
"devDependencies": {
|
|
92
93
|
"@swc/core": "^1.15.30",
|
|
93
94
|
"@types/node": "^24.12.2",
|
|
94
95
|
"esbuild": "^0.28.0",
|
|
95
96
|
"terser": "^5.46.2",
|
|
96
|
-
"@mikrojs/registry": "0.
|
|
97
|
-
"@mikrojs/schema": "0.
|
|
97
|
+
"@mikrojs/registry": "0.20.0",
|
|
98
|
+
"@mikrojs/schema": "0.20.0"
|
|
98
99
|
},
|
|
99
100
|
"engines": {
|
|
100
101
|
"node": ">=24.0.0"
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/runtime/internal.d.ts
CHANGED
|
@@ -170,6 +170,10 @@ declare module 'native:mikro/pin' {
|
|
|
170
170
|
export function analogReadMillivolts(pin: number, attenuation: number): Result<number, PinError>
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
+
declare module 'native:mikro/watchdog' {
|
|
174
|
+
export function feed(): void
|
|
175
|
+
}
|
|
176
|
+
|
|
173
177
|
declare module 'native:mikro/sleep' {
|
|
174
178
|
import type {DeepWakeupSources, LightWakeupSources} from '@mikrojs/native/runtime/sleep/types'
|
|
175
179
|
|
|
@@ -580,12 +584,8 @@ declare module 'native:mikro/ota_client' {
|
|
|
580
584
|
|
|
581
585
|
// The mikro/ota policy surface.
|
|
582
586
|
export const reconcile: Ota['reconcile']
|
|
583
|
-
export const running: Ota['running']
|
|
584
|
-
export const parseOffer: Ota['parseOffer']
|
|
585
587
|
export const applyOffer: Ota['applyOffer']
|
|
586
|
-
export const
|
|
587
|
-
export const applyConfig: Ota['applyConfig']
|
|
588
|
-
export const configState: Ota['configState']
|
|
588
|
+
export const decline: Ota['decline']
|
|
589
589
|
export const report: Ota['report']
|
|
590
590
|
export const settle: Ota['settle']
|
|
591
591
|
export const confirm: Ota['confirm']
|
package/runtime/ota/ota.ts
CHANGED
|
@@ -8,18 +8,14 @@
|
|
|
8
8
|
|
|
9
9
|
import {config} from 'mikro/ota/config'
|
|
10
10
|
import {
|
|
11
|
-
applyConfig,
|
|
12
11
|
applyOffer,
|
|
13
12
|
bearer,
|
|
14
|
-
configState,
|
|
15
13
|
confirm,
|
|
16
|
-
|
|
17
|
-
parseOffer,
|
|
14
|
+
decline,
|
|
18
15
|
reconcile,
|
|
19
16
|
registry,
|
|
20
17
|
report,
|
|
21
18
|
revert,
|
|
22
|
-
running,
|
|
23
19
|
settle,
|
|
24
20
|
} from 'native:mikro/ota_client'
|
|
25
21
|
|
|
@@ -27,17 +23,13 @@ import type {Ota} from './types.js'
|
|
|
27
23
|
|
|
28
24
|
const ota: Ota = {
|
|
29
25
|
reconcile,
|
|
30
|
-
running,
|
|
31
|
-
parseOffer,
|
|
32
26
|
applyOffer,
|
|
33
27
|
confirm,
|
|
34
28
|
revert,
|
|
35
29
|
bearer,
|
|
36
30
|
registry,
|
|
37
31
|
config,
|
|
38
|
-
|
|
39
|
-
applyConfig,
|
|
40
|
-
configState,
|
|
32
|
+
decline,
|
|
41
33
|
report,
|
|
42
34
|
settle,
|
|
43
35
|
}
|
package/runtime/ota/types.ts
CHANGED
|
@@ -6,7 +6,8 @@ import type {Result} from '../result/types.js'
|
|
|
6
6
|
/** An update offered by a registry: what to fetch and how to verify it, nothing
|
|
7
7
|
* more. Whether this device should take it is the registry's decision, made
|
|
8
8
|
* against the firmware and bytecode version the device reports at check-in and
|
|
9
|
-
* the whole set of builds it holds.
|
|
9
|
+
* the whole set of builds it holds. `settle()` validates the untrusted
|
|
10
|
+
* response fields into one. */
|
|
10
11
|
export interface Offer {
|
|
11
12
|
/** https URL of the .tgz build */
|
|
12
13
|
url: string
|
|
@@ -117,24 +118,6 @@ export type ApplyOutcome =
|
|
|
117
118
|
* mismatch code, since it cannot tell a bad hash from bad bytes. */
|
|
118
119
|
export type OtaError = OtaWriteError | OtaBeginError | OtaInstallError | OtaDownloadError
|
|
119
120
|
|
|
120
|
-
/** One stored config document: the complete effective config computed and
|
|
121
|
-
* validated by the registry (or by the CLI at cable-seed time), the opaque
|
|
122
|
-
* token that identifies it, and the release version it was computed for.
|
|
123
|
-
* The device stores and returns it without understanding it: validation is
|
|
124
|
-
* the writer's job, and the registry already ships the code. */
|
|
125
|
-
export interface StoredConfig {
|
|
126
|
-
/** Opaque registry-issued token echoed as `configRev` on check-ins. The
|
|
127
|
-
* registry serves its document whenever the echo differs from its own
|
|
128
|
-
* current rev, so a document it does not recognize is replaced. */
|
|
129
|
-
rev?: string
|
|
130
|
-
/** The release version this document was computed for. A document stamped
|
|
131
|
-
* for another version is ignored by `ota.config()`. */
|
|
132
|
-
version: string
|
|
133
|
-
/** The served document: the deviation overlay the read resolves over the
|
|
134
|
-
* build's manifest defaults. */
|
|
135
|
-
doc?: unknown
|
|
136
|
-
}
|
|
137
|
-
|
|
138
121
|
/** The three config slots, mirroring the build's install slots: `current` is
|
|
139
122
|
* what the running build reads, `next` is staged with an offered build,
|
|
140
123
|
* `prev` is the rollback baseline while a trial is unresolved. */
|
|
@@ -166,28 +149,13 @@ export type ConfigWrite =
|
|
|
166
149
|
* included), or it was a clear with nothing to clear. */
|
|
167
150
|
| 'unchanged'
|
|
168
151
|
/** Nothing was written, and the reason is transient: the store could not
|
|
169
|
-
* answer, or the running version could not be read.
|
|
170
|
-
*
|
|
171
|
-
* of the document that did not land. */
|
|
152
|
+
* answer, or the running version could not be read. The next `report()`
|
|
153
|
+
* still echoes the held rev, so the document is served again, rather than
|
|
154
|
+
* the rev of the document that did not land. */
|
|
172
155
|
| 'failed'
|
|
173
|
-
/** Not a usable config document
|
|
174
|
-
* out before the delivery, and log what the wire actually carried. */
|
|
156
|
+
/** Not a usable config document; log what the wire actually carried. */
|
|
175
157
|
| 'invalid'
|
|
176
158
|
|
|
177
|
-
/** What a check-in body owes the registry about config, for a client that
|
|
178
|
-
* builds its own. */
|
|
179
|
-
export interface ConfigState {
|
|
180
|
-
/** The rev to send as `configRev`: the registry serves its document whenever
|
|
181
|
-
* this differs from its own current rev. Absent when the device holds no
|
|
182
|
-
* document. After a rolled-back trial this is the FAILED document's rev,
|
|
183
|
-
* which is what stops the registry serving it again. */
|
|
184
|
-
rev?: string
|
|
185
|
-
/** A document that failed its trial and was rolled back, reported until it
|
|
186
|
-
* is replaced. Send it as `configError`, or an operator has no way to see
|
|
187
|
-
* that the document they published took the device down. */
|
|
188
|
-
error?: ConfigErrorReport
|
|
189
|
-
}
|
|
190
|
-
|
|
191
159
|
/** A config document rolled back after a failed trial; reported on check-ins
|
|
192
160
|
* while it stands. `rev` names the failed document, and the client keeps
|
|
193
161
|
* echoing it as `configRev`, which is what stops the registry re-serving
|
|
@@ -197,6 +165,19 @@ export interface ConfigErrorReport {
|
|
|
197
165
|
message: string
|
|
198
166
|
}
|
|
199
167
|
|
|
168
|
+
/** Why the last offered build was not taken. Without it a registry that waits
|
|
169
|
+
* for `running.checksum` to turn into the offered checksum cannot tell a
|
|
170
|
+
* device still working through a download from one that has permanently
|
|
171
|
+
* stopped trying. */
|
|
172
|
+
export interface DeclineReport {
|
|
173
|
+
/** The declined build, 64 lowercase hex characters. */
|
|
174
|
+
checksum: string
|
|
175
|
+
/** A module outcome ({@link DeclineReason}) or the app's own word for a
|
|
176
|
+
* reason only it can see, at most 64 characters. */
|
|
177
|
+
reason: DeclineReason | (string & {})
|
|
178
|
+
detail?: string
|
|
179
|
+
}
|
|
180
|
+
|
|
200
181
|
/** The check-in body a device owes its registry, as `ota.report()` builds it.
|
|
201
182
|
* Field shapes match the wire, so a client (or the proxy behind it) can
|
|
202
183
|
* forward fields verbatim into `POST /api/v1/checkin`. */
|
|
@@ -217,10 +198,17 @@ export interface CheckinReport {
|
|
|
217
198
|
/** Why a previous install failed. Present after a `reconcile()` that found
|
|
218
199
|
* one, until a `settle()` marks it delivered. */
|
|
219
200
|
lastInstall?: Diagnostic
|
|
220
|
-
/** The held document's rev to echo
|
|
201
|
+
/** The held document's rev to echo: the registry serves its document
|
|
202
|
+
* whenever this differs from its own current rev. After a rolled-back
|
|
203
|
+
* trial this is the FAILED document's rev, which is what stops the
|
|
204
|
+
* registry serving it again. */
|
|
221
205
|
configRev?: string
|
|
222
|
-
/** A rolled-back document to report; see {@link
|
|
206
|
+
/** A rolled-back document to report; see {@link ConfigErrorReport}. */
|
|
223
207
|
configError?: ConfigErrorReport
|
|
208
|
+
/** Why the last offered build was not taken. Present after `applyOffer`
|
|
209
|
+
* declined one or the app recorded its own via `decline()`, until a
|
|
210
|
+
* `settle()` marks it delivered. */
|
|
211
|
+
lastDecline?: DeclineReport
|
|
224
212
|
}
|
|
225
213
|
|
|
226
214
|
/** What `ota.settle()` took from a completed check-in's response. */
|
|
@@ -260,15 +248,14 @@ export type RegisteredConfig = keyof OtaConfig extends never ? unknown : OtaConf
|
|
|
260
248
|
export interface Ota {
|
|
261
249
|
/** Report what happened to a previous update on this boot, and clear the report. */
|
|
262
250
|
reconcile(): InstallOutcome
|
|
263
|
-
/** The build currently executing, read from the live app. */
|
|
264
|
-
running(): RunningBuild
|
|
265
|
-
/** Validate a registry value into an `Offer`, or `undefined` if unusable.
|
|
266
|
-
* `allowInsecure` (dev only) accepts an http build url instead of https. */
|
|
267
|
-
parseOffer(raw: unknown, opts?: {allowInsecure?: boolean}): Offer | undefined
|
|
268
251
|
/** Run the full update policy: skip checks and the retry limit, download
|
|
269
252
|
* via the `download` callback, and verification. Compatibility is not
|
|
270
253
|
* re-checked: the registry selected this build for the reported firmware,
|
|
271
|
-
* and a mismatched archive fails its checksum or fails to load.
|
|
254
|
+
* and a mismatched archive fails its checksum or fails to load.
|
|
255
|
+
*
|
|
256
|
+
* An offer it does not stage is recorded for the registry the way
|
|
257
|
+
* `decline()` records one, except `'current'` and `'trial-pending'`,
|
|
258
|
+
* which are not declines. */
|
|
272
259
|
applyOffer(
|
|
273
260
|
offer: Offer,
|
|
274
261
|
download: DownloadFn,
|
|
@@ -318,50 +305,29 @@ export interface Ota {
|
|
|
318
305
|
*/
|
|
319
306
|
config<T = RegisteredConfig>(): T
|
|
320
307
|
/**
|
|
321
|
-
*
|
|
322
|
-
* `
|
|
323
|
-
*
|
|
324
|
-
*
|
|
325
|
-
*
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
*
|
|
329
|
-
* Whether the document survives CBOR is settled by `applyConfig`, which is
|
|
330
|
-
* where the stored bytes are made.
|
|
331
|
-
*/
|
|
332
|
-
parseConfig(raw: unknown): StoredConfig | undefined
|
|
333
|
-
/**
|
|
334
|
-
* Store a config document, for a client that received one over its own
|
|
335
|
-
* transport. The built-in client covers the ordinary case and does not go
|
|
336
|
-
* through here.
|
|
337
|
-
*
|
|
338
|
-
* The `version` stamp decides where it lands: stamped for the running
|
|
339
|
-
* release it is applied, and the document it replaces is kept as the
|
|
340
|
-
* rollback baseline; stamped for another it is staged for the build it
|
|
341
|
-
* names, to apply when that build installs. The return value says which
|
|
342
|
-
* happened, and says when nothing did.
|
|
308
|
+
* Record why an offered build was not taken, for the next `report()` to
|
|
309
|
+
* carry as `lastDecline` and a later `settle()` to mark delivered — the
|
|
310
|
+
* same lifecycle as `lastInstall`. `applyOffer` records its own outcomes,
|
|
311
|
+
* so this is for a reason only the app can see: its own download budget
|
|
312
|
+
* spent, a build its policy refuses. Without a recorded decline the
|
|
313
|
+
* registry cannot tell a device still working through a download from one
|
|
314
|
+
* that has stopped, and shows the update pending forever.
|
|
343
315
|
*
|
|
344
|
-
*
|
|
345
|
-
*
|
|
346
|
-
*
|
|
347
|
-
*
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
|
|
351
|
-
applyConfig(config: StoredConfig, options?: {trialBoots?: number}): ConfigWrite
|
|
352
|
-
/**
|
|
353
|
-
* What the device owes its registry about config: the rev to echo, and a
|
|
354
|
-
* rolled-back document to report. A client that builds its own check-in body
|
|
355
|
-
* needs both. Without the echo the registry re-serves the same document on
|
|
356
|
-
* every check-in; without the report a document that took the device down is
|
|
357
|
-
* re-served forever and nobody is told.
|
|
316
|
+
* Persisted on the device, so it survives a deep sleep: on a wake-cycle
|
|
317
|
+
* device the decline always lands after that wake's check-in, and the next
|
|
318
|
+
* wake's `report()` is its first chance to travel. Recording again
|
|
319
|
+
* overwrites; only the last decline stands. Throws a `TypeError` on a
|
|
320
|
+
* checksum that is not 64 lowercase hex characters or a `reason` past 64
|
|
321
|
+
* characters, since the registry would reject the whole check-in body;
|
|
322
|
+
* `detail` is free text and is cut to 256 characters instead.
|
|
358
323
|
*/
|
|
359
|
-
|
|
324
|
+
decline(checksum: string, reason: DeclineReason | (string & {}), detail?: string): void
|
|
360
325
|
/**
|
|
361
326
|
* The check-in body the device owes its registry, assembled: identity,
|
|
362
|
-
*
|
|
363
|
-
* report,
|
|
364
|
-
* the fields by hand, and the same facts the
|
|
327
|
+
* the running build, the name pair, free storage, the pending `lastInstall`
|
|
328
|
+
* report, a recorded `lastDecline`, and the config echo. One
|
|
329
|
+
* call instead of gathering the fields by hand, and the same facts the
|
|
330
|
+
* built-in client sends.
|
|
365
331
|
*
|
|
366
332
|
* Call `reconcile()` first, on every boot: it is what surfaces the
|
|
367
333
|
* `lastInstall` report this reads (`settle()` marks it delivered).
|
|
@@ -370,9 +336,12 @@ export interface Ota {
|
|
|
370
336
|
/**
|
|
371
337
|
* Take a COMPLETED check-in's response, whole: confirm the running trial
|
|
372
338
|
* and a read config document's trial (exactly `confirm()`), adopt a
|
|
373
|
-
* delivered name pair, store a delivered config document
|
|
374
|
-
* `
|
|
375
|
-
*
|
|
339
|
+
* delivered name pair, store a delivered config document, and validate the
|
|
340
|
+
* offer fields (`allowInsecure`, dev only, accepts an http build url). A
|
|
341
|
+
* stored document arms a trial of `trialBoots` boots: each boot whose first
|
|
342
|
+
* `config()` read serves it burns one, and the budget spent with no
|
|
343
|
+
* `confirm()` in between restores the previous document, so raise it above
|
|
344
|
+
* the default on a wake-cycle device. An empty or null response is
|
|
376
345
|
* the registry's quiet round: nothing to deliver, and the confirm still
|
|
377
346
|
* happens, which is the point of calling. A response of any other shape
|
|
378
347
|
* never decoded: a captive portal handed back HTML, or a proxy sent an
|