@yuneta/gobj-js 7.26.26 → 7.26.28
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/README.md +38 -8
- package/dist/gobj-js.cjs.js +251 -60
- package/dist/gobj-js.cjs.js.map +1 -1
- package/dist/gobj-js.cjs.min.js +802 -692
- package/dist/gobj-js.cjs.min.js.map +1 -1
- package/dist/gobj-js.es.js +251 -61
- package/dist/gobj-js.es.js.map +1 -1
- package/dist/gobj-js.es.min.js +460 -351
- package/dist/gobj-js.es.min.js.map +1 -1
- package/dist/gobj-js.iife.js +251 -60
- package/dist/gobj-js.iife.js.map +1 -1
- package/dist/gobj-js.iife.min.js +795 -685
- package/dist/gobj-js.iife.min.js.map +1 -1
- package/dist/gobj-js.umd.js +251 -60
- package/dist/gobj-js.umd.js.map +1 -1
- package/dist/gobj-js.umd.min.js +795 -685
- package/dist/gobj-js.umd.min.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -308,10 +308,12 @@ string, a `DTP_DICT` or a `DTP_LIST` (7.26.25): a cycle anywhere inside, a
|
|
|
308
308
|
of a class. They were written, and burst (or turned into `"{}"`) when the value
|
|
309
309
|
was serialized, far from the write. And so is a value whose text would say
|
|
310
310
|
another value (7.26.26): an object with a `toJSON()`, `NaN` or `Infinity` inside
|
|
311
|
-
(the text says `null`; jansson holds no such real), a function, `undefined
|
|
312
|
-
symbol inside an array (the text says `null`), and a value
|
|
313
|
-
2048 levels (a scalar is a level too), which C would not read
|
|
314
|
-
bursts `JSON.stringify()` a little further on.
|
|
311
|
+
(the text says `null`; jansson holds no such real), a function, `undefined`, a
|
|
312
|
+
symbol or a hole inside an array (`[1, , 3]`: the text says `null`), and a value
|
|
313
|
+
nested deeper than 2048 levels (a scalar is a level too), which C would not read
|
|
314
|
+
back and which bursts `JSON.stringify()` a little further on. Every version
|
|
315
|
+
before 7.26.26 wrote them, so this is a BREAKING change for a caller that wrote
|
|
316
|
+
one. A getter or a `Proxy` trap that
|
|
315
317
|
throws is a refusal too, never an exception (7.26.26). A key whose value is
|
|
316
318
|
`undefined`, a function or a symbol is dropped, as `JSON.stringify()` drops it:
|
|
317
319
|
an absent key. A plain object of another realm (a `vm` context, an iframe) is a
|
|
@@ -324,6 +326,7 @@ gobj_write_attr(gobj, "settings", new Map([["a", 1]])); // -1, ERROR "a valu
|
|
|
324
326
|
gobj_write_str_attr(gobj, "name", new Map()); // -1: it was the text "{}"
|
|
325
327
|
gobj_write_attr(gobj, "settings", {ratio: NaN}); // -1: it was the text {"ratio":null}
|
|
326
328
|
gobj_write_attr(gobj, "items", [1, undefined]); // -1: it was the text [1,null]
|
|
329
|
+
gobj_write_attr(gobj, "items", [1, , 3]); // -1: a hole, it was the text [1,null,3]
|
|
327
330
|
gobj_write_str_attr(gobj, "name", NaN); // -1: it was the text "null"
|
|
328
331
|
```
|
|
329
332
|
|
|
@@ -457,7 +460,10 @@ gobj_create_volatil(name, gclass_name, attrs, parent)
|
|
|
457
460
|
gobj_create_pure_child(name, gclass_name, attrs, parent)
|
|
458
461
|
|
|
459
462
|
// Start / Stop
|
|
460
|
-
gobj_start(gobj) // calls mt_start
|
|
463
|
+
gobj_start(gobj) // calls mt_start; -1 with an ERROR if a
|
|
464
|
+
// SDF_REQUIRED attr is empty, as in C
|
|
465
|
+
// (BREAKING in 7.26.28: "" string, empty_json()
|
|
466
|
+
// list/dict/json, null pointer)
|
|
461
467
|
gobj_stop(gobj) // calls mt_stop
|
|
462
468
|
gobj_start_children(gobj)
|
|
463
469
|
gobj_stop_children(gobj)
|
|
@@ -596,6 +602,9 @@ gobj_search_path(gobj, path)
|
|
|
596
602
|
|
|
597
603
|
gobj_walk_gobj_children(gobj, walk_type, cb_walking, user_data, user_data2)
|
|
598
604
|
gobj_walk_gobj_children_tree(gobj, walk_type, cb_walking, user_data, user_data2)
|
|
605
|
+
// A callback may destroy or move (gobj_change_parent()) any gobj: a
|
|
606
|
+
// destroyed one is never handed, a moved one is handed where the walk
|
|
607
|
+
// finds it now, and no gobj is handed twice in one walk (7.26.28).
|
|
599
608
|
```
|
|
600
609
|
|
|
601
610
|
### Persistence
|
|
@@ -723,13 +732,30 @@ kw_clone_by_not_keys(gobj, kw, keys, verbose)
|
|
|
723
732
|
|
|
724
733
|
// Local storage helpers (browser localStorage, no "store" parameter)
|
|
725
734
|
kw_get_local_storage_value(key, default_value, create) // create=false
|
|
726
|
-
kw_set_local_storage_value(key, value)
|
|
735
|
+
kw_set_local_storage_value(key, value) // 0, or -1 logged
|
|
727
736
|
kw_remove_local_storage_value(key)
|
|
737
|
+
// BREAKING in 7.26.26: kw_get_local_storage_value() answers default_value
|
|
738
|
+
// where it answered null (an absent entry, a json null) or undefined (no
|
|
739
|
+
// browser), and an entry that is not json is REMOVED, with one WARNING
|
|
740
|
+
// that never shows its text. A store the browser blocks (cookies blocked,
|
|
741
|
+
// a sandboxed iframe: reading window.localStorage throws) answers the
|
|
742
|
+
// default, warned once per key, and a write to it is -1 (7.26.27: before,
|
|
743
|
+
// every helper and node_uuid() threw). A value with no json text is
|
|
744
|
+
// refused by the writer, -1 with an ERROR, as a typed writer refuses it
|
|
745
|
+
// (7.26.27; a function was stored as the text "undefined"):
|
|
746
|
+
// kw_set_local_storage_value("k", () => 1) → -1
|
|
747
|
+
// kw_set_local_storage_value("k", {w: NaN}) → -1 (it was {"w":null})
|
|
748
|
+
// kw_set_local_storage_value("k", [1, , 3]) → -1 (a hole: [1,null,3])
|
|
749
|
+
// A full store (read, not written) with `create`: the default, warned
|
|
750
|
+
// once per key (7.26.28: an ERROR on every read).
|
|
728
751
|
|
|
729
752
|
// Misc utilities
|
|
730
753
|
current_timestamp() // ISO string
|
|
731
754
|
get_now() // Date object
|
|
732
|
-
node_uuid() //
|
|
755
|
+
node_uuid() // uuid of this browser, the SAME for the
|
|
756
|
+
// life of the page (kept in localStorage
|
|
757
|
+
// "host_uuid"; a page uuid if the store
|
|
758
|
+
// cannot keep it; never null: 7.26.28)
|
|
733
759
|
parseBoolean(v) // coerce to boolean
|
|
734
760
|
empty_string(s) // true if null/undefined/""
|
|
735
761
|
str_in_list(list, str, ci) // case-insensitive optional
|
|
@@ -1091,7 +1117,11 @@ count as reached: the backoff goes on from where it was (7.26.25), so a server
|
|
|
1091
1117
|
that breaks every session is not reconnected at the first delay for ever -- unless
|
|
1092
1118
|
the session outlived `timeout_retry_max`: a session that long was served, and its
|
|
1093
1119
|
fault starts the backoff over, as a clean end does (7.26.26). For the old fixed-interval behaviour set `timeout_retry_max` equal to
|
|
1094
|
-
`timeout_retry`.
|
|
1120
|
+
`timeout_retry`. The BASE of the first delay is **at least 1000 ms** (7.26.27;
|
|
1121
|
+
the ±20% jitter puts the first delay itself between 800 and 1200 ms): a lower
|
|
1122
|
+
`timeout_retry` is raised to 1000 with one WARNING, because 0 never doubled
|
|
1123
|
+
away from 0 and a server that closes before the ack was reconnected in a loop
|
|
1124
|
+
with no wait, hundreds of times a second.
|
|
1095
1125
|
|
|
1096
1126
|
**A refused identity card** (`result < 0` in its ack) publishes `EV_ON_ID_NAK`
|
|
1097
1127
|
and closes the channel with 1000; no session was reached, so the close is then
|
package/dist/gobj-js.cjs.js
CHANGED
|
@@ -1949,6 +1949,31 @@ function gobj_destroy_children(gobj) {
|
|
|
1949
1949
|
}
|
|
1950
1950
|
}
|
|
1951
1951
|
/************************************************************
|
|
1952
|
+
* The SDF_REQUIRED attributes with no value, or null when there
|
|
1953
|
+
* is none: what is empty is asked type by type, as in C.
|
|
1954
|
+
************************************************************/
|
|
1955
|
+
function gobj_check_required_attrs(gobj) {
|
|
1956
|
+
const hs = gobj_hsdata(gobj);
|
|
1957
|
+
const missing = [];
|
|
1958
|
+
if (!is_array(gobj.gclass.attrs_table)) return null;
|
|
1959
|
+
for (const it of gobj.gclass.attrs_table) {
|
|
1960
|
+
if (!it.name || !(it.flag & sdata_flag_t.SDF_REQUIRED)) continue;
|
|
1961
|
+
const value = hs[it.name];
|
|
1962
|
+
switch (it.type) {
|
|
1963
|
+
case data_type_t.DTP_STRING:
|
|
1964
|
+
if (!is_string(value) || empty_string(value)) missing.push(it.name);
|
|
1965
|
+
break;
|
|
1966
|
+
case data_type_t.DTP_LIST:
|
|
1967
|
+
case data_type_t.DTP_DICT:
|
|
1968
|
+
case data_type_t.DTP_JSON:
|
|
1969
|
+
if (empty_json(value)) missing.push(it.name);
|
|
1970
|
+
break;
|
|
1971
|
+
case data_type_t.DTP_POINTER: if (!value) missing.push(it.name);
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
return missing.length ? missing : null;
|
|
1975
|
+
}
|
|
1976
|
+
/************************************************************
|
|
1952
1977
|
*
|
|
1953
1978
|
************************************************************/
|
|
1954
1979
|
function gobj_start(gobj) {
|
|
@@ -1964,6 +1989,11 @@ function gobj_start(gobj) {
|
|
|
1964
1989
|
log_error(`${gobj_short_name(gobj)}: GObj DISABLED`);
|
|
1965
1990
|
return -1;
|
|
1966
1991
|
}
|
|
1992
|
+
const required_attrs = gobj_check_required_attrs(gobj);
|
|
1993
|
+
if (required_attrs) {
|
|
1994
|
+
log_error(`${gobj_short_name(gobj)}: Cannot start without all required attributes: ` + JSON.stringify(required_attrs));
|
|
1995
|
+
return -1;
|
|
1996
|
+
}
|
|
1967
1997
|
if (__trace_gobj_start_stop__(gobj)) trace_machine(sprintf("⏺ ⏺ start: %s", gobj_full_name(gobj)));
|
|
1968
1998
|
gobj.running = true;
|
|
1969
1999
|
let ret = 0;
|
|
@@ -2350,7 +2380,7 @@ function gobj_walk_gobj_children(gobj, walk_type, cb_walking, user_data, user_da
|
|
|
2350
2380
|
log_error(`gobj NULL, destroying, or bad type`);
|
|
2351
2381
|
return 0;
|
|
2352
2382
|
}
|
|
2353
|
-
return rc_walk_by_list(gobj
|
|
2383
|
+
return rc_walk_by_list(gobj, /* @__PURE__ */ new Set(), walk_type, cb_walking, user_data, user_data2);
|
|
2354
2384
|
}
|
|
2355
2385
|
/***************************************************************************
|
|
2356
2386
|
*
|
|
@@ -2360,7 +2390,7 @@ function gobj_walk_gobj_children_tree(gobj, walk_type, cb_walking, user_data, us
|
|
|
2360
2390
|
log_error(`gobj NULL, destroying, or bad type`);
|
|
2361
2391
|
return 0;
|
|
2362
2392
|
}
|
|
2363
|
-
return rc_walk_by_tree(gobj
|
|
2393
|
+
return rc_walk_by_tree(gobj, /* @__PURE__ */ new Set(), walk_type, cb_walking, user_data, user_data2);
|
|
2364
2394
|
}
|
|
2365
2395
|
/***************************************************************
|
|
2366
2396
|
* Walking
|
|
@@ -2387,16 +2417,33 @@ function walk_ret(ret) {
|
|
|
2387
2417
|
log_error(`walk callback must return a NUMBER (-1 stop, 0 descend, >0 skip children), got ${typeof ret}: taken as 0`);
|
|
2388
2418
|
return 0;
|
|
2389
2419
|
}
|
|
2390
|
-
|
|
2391
|
-
|
|
2420
|
+
/***************************************************************
|
|
2421
|
+
* Whether a walk steps over this gobj of the list of `owner`.
|
|
2422
|
+
*
|
|
2423
|
+
* Each list is walked over a COPY of it, because a callback may destroy
|
|
2424
|
+
* any gobj of the list, the current one included, or move one with
|
|
2425
|
+
* gobj_change_parent(). One it destroyed is stepped over, as the C walk
|
|
2426
|
+
* does; one it moved is no longer in this list, and is handed where the
|
|
2427
|
+
* walk finds it now (7.26.27 handed it here AND there); and a gobj is
|
|
2428
|
+
* never handed twice in one walk (`seen`), wherever it was moved.
|
|
2429
|
+
***************************************************************/
|
|
2430
|
+
function walk_steps_over(child, owner, seen) {
|
|
2431
|
+
return gobj_is_destroying(child) || child.parent !== owner || seen.has(child);
|
|
2432
|
+
}
|
|
2433
|
+
function rc_walk_by_list(owner, seen, walk_type, cb_walking, user_data, user_data2) {
|
|
2434
|
+
let iter = [...owner.dl_children];
|
|
2392
2435
|
let child;
|
|
2393
2436
|
if (walk_type & walk_type_t.WALK_LAST2FIRST) for (let i = iter.length; i > 0; i--) {
|
|
2394
2437
|
child = iter[i - 1];
|
|
2438
|
+
if (walk_steps_over(child, owner, seen)) continue;
|
|
2439
|
+
seen.add(child);
|
|
2395
2440
|
let ret = walk_ret(cb_walking(child, user_data, user_data2));
|
|
2396
2441
|
if (ret < 0) return ret;
|
|
2397
2442
|
}
|
|
2398
2443
|
else for (let i = 0; i < iter.length; i++) {
|
|
2399
2444
|
child = iter[i];
|
|
2445
|
+
if (walk_steps_over(child, owner, seen)) continue;
|
|
2446
|
+
seen.add(child);
|
|
2400
2447
|
let ret = walk_ret(cb_walking(child, user_data, user_data2));
|
|
2401
2448
|
if (ret < 0) return ret;
|
|
2402
2449
|
}
|
|
@@ -2411,21 +2458,23 @@ function rc_walk_by_list(iter_, walk_type, cb_walking, user_data, user_data2) {
|
|
|
2411
2458
|
* or
|
|
2412
2459
|
* WALK_LAST2FIRST
|
|
2413
2460
|
***************************************************************/
|
|
2414
|
-
function rc_walk_by_level(
|
|
2415
|
-
let
|
|
2416
|
-
if (
|
|
2417
|
-
let
|
|
2418
|
-
|
|
2419
|
-
child
|
|
2420
|
-
|
|
2461
|
+
function rc_walk_by_level(owner, seen, walk_type, cb_walking, user_data, user_data2) {
|
|
2462
|
+
let iter = [...owner.dl_children];
|
|
2463
|
+
if (walk_type & walk_type_t.WALK_LAST2FIRST) iter.reverse();
|
|
2464
|
+
let handed = [];
|
|
2465
|
+
for (const child of iter) {
|
|
2466
|
+
if (walk_steps_over(child, owner, seen)) continue;
|
|
2467
|
+
seen.add(child);
|
|
2468
|
+
handed.push(child);
|
|
2469
|
+
let ret = walk_ret(cb_walking(child, user_data, user_data2));
|
|
2421
2470
|
if (ret < 0) return ret;
|
|
2422
2471
|
}
|
|
2423
|
-
|
|
2424
|
-
child
|
|
2425
|
-
ret = rc_walk_by_level(
|
|
2472
|
+
for (const child of handed) {
|
|
2473
|
+
if (gobj_is_destroying(child)) continue;
|
|
2474
|
+
let ret = rc_walk_by_level(child, seen, walk_type, cb_walking, user_data, user_data2);
|
|
2426
2475
|
if (ret < 0) return ret;
|
|
2427
2476
|
}
|
|
2428
|
-
return
|
|
2477
|
+
return 0;
|
|
2429
2478
|
}
|
|
2430
2479
|
/***************************************************************
|
|
2431
2480
|
* Walking
|
|
@@ -2442,23 +2491,29 @@ function rc_walk_by_level(iter, walk_type, cb_walking, user_data, user_data2) {
|
|
|
2442
2491
|
* or
|
|
2443
2492
|
* WALK_LAST2FIRST
|
|
2444
2493
|
***************************************************************/
|
|
2445
|
-
function rc_walk_by_tree(
|
|
2446
|
-
if (walk_type & walk_type_t.WALK_BYLEVEL) return rc_walk_by_level(
|
|
2494
|
+
function rc_walk_by_tree(owner, seen, walk_type, cb_walking, user_data, user_data2) {
|
|
2495
|
+
if (walk_type & walk_type_t.WALK_BYLEVEL) return rc_walk_by_level(owner, seen, walk_type, cb_walking, user_data, user_data2);
|
|
2496
|
+
let iter = [...owner.dl_children];
|
|
2447
2497
|
let ret;
|
|
2448
2498
|
let child;
|
|
2449
2499
|
if (walk_type & walk_type_t.WALK_BOTTOM2TOP) for (let i = iter.length; i > 0; i--) {
|
|
2450
2500
|
child = iter[i - 1];
|
|
2451
|
-
|
|
2501
|
+
if (walk_steps_over(child, owner, seen)) continue;
|
|
2502
|
+
ret = rc_walk_by_tree(child, seen, walk_type, cb_walking, user_data, user_data2);
|
|
2452
2503
|
if (ret < 0) return ret;
|
|
2504
|
+
if (gobj_is_destroying(child)) continue;
|
|
2505
|
+
seen.add(child);
|
|
2453
2506
|
ret = walk_ret(cb_walking(child, user_data, user_data2));
|
|
2454
2507
|
if (ret < 0) return ret;
|
|
2455
2508
|
}
|
|
2456
2509
|
else for (let i = 0; i < iter.length; i++) {
|
|
2457
2510
|
child = iter[i];
|
|
2511
|
+
if (walk_steps_over(child, owner, seen)) continue;
|
|
2512
|
+
seen.add(child);
|
|
2458
2513
|
ret = walk_ret(cb_walking(child, user_data, user_data2));
|
|
2459
2514
|
if (ret < 0) return ret;
|
|
2460
|
-
else if (ret === 0) {
|
|
2461
|
-
ret = rc_walk_by_tree(
|
|
2515
|
+
else if (ret === 0 && !gobj_is_destroying(child)) {
|
|
2516
|
+
ret = rc_walk_by_tree(child, seen, walk_type, cb_walking, user_data, user_data2);
|
|
2462
2517
|
if (ret < 0) return ret;
|
|
2463
2518
|
}
|
|
2464
2519
|
}
|
|
@@ -6043,44 +6098,99 @@ function kw_clone_by_not_keys(gobj, kw, keys, verbose) {
|
|
|
6043
6098
|
return kw_clone;
|
|
6044
6099
|
}
|
|
6045
6100
|
/********************************************
|
|
6101
|
+
* The browser's localStorage, or null when there is none (outside a
|
|
6102
|
+
* browser).
|
|
6103
|
+
*
|
|
6104
|
+
* With the site's data blocked (Chrome with cookies blocked, a sandboxed
|
|
6105
|
+
* iframe), READING the window.localStorage property already throws a
|
|
6106
|
+
* SecurityError, not only its getItem(): every app reads its locale at
|
|
6107
|
+
* start, so the app did not start. Such a store is answered as one whose
|
|
6108
|
+
* every call throws that error, so each helper fails as it fails for any
|
|
6109
|
+
* store that refuses it: a read gives the default, a write -1.
|
|
6110
|
+
********************************************/
|
|
6111
|
+
function local_store() {
|
|
6112
|
+
if (typeof window === "undefined") return null;
|
|
6113
|
+
let store;
|
|
6114
|
+
try {
|
|
6115
|
+
store = window.localStorage;
|
|
6116
|
+
} catch (e) {
|
|
6117
|
+
const refuse = () => {
|
|
6118
|
+
throw e;
|
|
6119
|
+
};
|
|
6120
|
+
return {
|
|
6121
|
+
getItem: refuse,
|
|
6122
|
+
setItem: refuse,
|
|
6123
|
+
removeItem: refuse
|
|
6124
|
+
};
|
|
6125
|
+
}
|
|
6126
|
+
return store || null;
|
|
6127
|
+
}
|
|
6128
|
+
/********************************************
|
|
6129
|
+
* A failure of the store that would repeat on every read is warned
|
|
6130
|
+
* once per key, until a read of the key goes well again.
|
|
6131
|
+
********************************************/
|
|
6132
|
+
var local_storage_warned = /* @__PURE__ */ new Set();
|
|
6133
|
+
function warn_local_storage_once(key, msg) {
|
|
6134
|
+
if (local_storage_warned.has(key)) return;
|
|
6135
|
+
local_storage_warned.add(key);
|
|
6136
|
+
log_warning(msg);
|
|
6137
|
+
}
|
|
6138
|
+
/********************************************
|
|
6046
6139
|
* Get a local attribute
|
|
6047
6140
|
*
|
|
6048
6141
|
* The json the entry holds, or `default_value` when there is none
|
|
6049
6142
|
* (written there too with `create`). A json null is no value, as
|
|
6050
6143
|
* json_absent() in C: the default. An entry that is not json (a
|
|
6051
|
-
* corrupt or foreign write) is
|
|
6144
|
+
* corrupt or foreign write) is a WARNING and is REMOVED, so the
|
|
6052
6145
|
* next read finds no entry: it used to console.warn on every read,
|
|
6053
|
-
* for ever
|
|
6054
|
-
* error, and an entry can hold a
|
|
6055
|
-
*
|
|
6146
|
+
* for ever; a new corrupt entry is a new WARNING. Its text is never
|
|
6147
|
+
* shown (JSON.parse() quotes it in its error, and an entry can hold a
|
|
6148
|
+
* token). A store that cannot be read (blocked) is warned once per
|
|
6149
|
+
* key, and the default is NOT written there; one that cannot be
|
|
6150
|
+
* written (full) is warned once per key too: with `create` it was an
|
|
6151
|
+
* ERROR on every read, the default tried again each time. A key is
|
|
6152
|
+
* warned again after a read that goes well. Outside a browser: the
|
|
6153
|
+
* default.
|
|
6056
6154
|
********************************************/
|
|
6057
|
-
var local_storage_warned = /* @__PURE__ */ new Set();
|
|
6058
|
-
function warn_local_storage_once(key, what) {
|
|
6059
|
-
if (local_storage_warned.has(key)) return;
|
|
6060
|
-
local_storage_warned.add(key);
|
|
6061
|
-
log_warning(`kw_get_local_storage_value(): localStorage key "${key}" ${what}`);
|
|
6062
|
-
}
|
|
6063
6155
|
function kw_get_local_storage_value(key, default_value, create = false) {
|
|
6064
|
-
|
|
6156
|
+
const store = local_store();
|
|
6157
|
+
if (!store) return default_value;
|
|
6158
|
+
const where = `kw_get_local_storage_value(): localStorage key "${key}"`;
|
|
6065
6159
|
let text;
|
|
6066
6160
|
try {
|
|
6067
|
-
text =
|
|
6161
|
+
text = store.getItem(key);
|
|
6068
6162
|
} catch (e) {
|
|
6069
|
-
warn_local_storage_once(key,
|
|
6163
|
+
warn_local_storage_once(key, `${where} cannot be read (storage blocked?): the default is used`);
|
|
6070
6164
|
return default_value;
|
|
6071
6165
|
}
|
|
6166
|
+
let went_well = true;
|
|
6072
6167
|
let value = null;
|
|
6073
6168
|
if (text !== null) try {
|
|
6074
6169
|
value = JSON.parse(text);
|
|
6075
6170
|
} catch (e) {
|
|
6076
|
-
warn_local_storage_once(key, "does not hold json: removed, the default is used");
|
|
6077
|
-
kw_remove_local_storage_value(key);
|
|
6078
6171
|
value = null;
|
|
6172
|
+
try {
|
|
6173
|
+
store.removeItem(key);
|
|
6174
|
+
log_warning(`${where} does not hold json: removed, the default is used`);
|
|
6175
|
+
} catch (e2) {
|
|
6176
|
+
went_well = false;
|
|
6177
|
+
warn_local_storage_once(key, `${where} does not hold json and cannot be removed: the default is used`);
|
|
6178
|
+
}
|
|
6079
6179
|
}
|
|
6080
6180
|
if (value === null) {
|
|
6081
|
-
|
|
6082
|
-
|
|
6181
|
+
value = default_value;
|
|
6182
|
+
if (create) {
|
|
6183
|
+
const default_text = json_text_of(default_value);
|
|
6184
|
+
if (default_text === void 0) log_error(`${where}: cannot write the default: a value with no json text`);
|
|
6185
|
+
else try {
|
|
6186
|
+
store.setItem(key, default_text);
|
|
6187
|
+
} catch (e) {
|
|
6188
|
+
went_well = false;
|
|
6189
|
+
warn_local_storage_once(key, `${where} cannot be written (storage full or blocked?): the default is used, not kept`);
|
|
6190
|
+
}
|
|
6191
|
+
}
|
|
6083
6192
|
}
|
|
6193
|
+
if (went_well) local_storage_warned.delete(key);
|
|
6084
6194
|
return value;
|
|
6085
6195
|
}
|
|
6086
6196
|
/********************************************
|
|
@@ -6092,15 +6202,26 @@ function kw_get_local_storage_value(key, default_value, create = false) {
|
|
|
6092
6202
|
* reported success on a store that had rejected the write: a full or blocked
|
|
6093
6203
|
* localStorage (quota, private mode) silently discarded the change while the
|
|
6094
6204
|
* UI showed it as saved.
|
|
6205
|
+
*
|
|
6206
|
+
* A value with no json text is refused with an ERROR, as a typed writer
|
|
6207
|
+
* refuses it (json_text_of()): a function was stored as the text
|
|
6208
|
+
* "undefined", and a NaN, a Date or a Map as a text that reads back as
|
|
6209
|
+
* another value. Checked in Node.js too, where there is no store.
|
|
6095
6210
|
********************************************/
|
|
6096
6211
|
function kw_set_local_storage_value(key, value) {
|
|
6097
|
-
if (
|
|
6098
|
-
|
|
6099
|
-
|
|
6212
|
+
if (!key) {
|
|
6213
|
+
log_error(`kw_set_local_storage_value(): invalid key: key=${key}`);
|
|
6214
|
+
return -1;
|
|
6215
|
+
}
|
|
6216
|
+
const text = json_text_of(value);
|
|
6217
|
+
if (text === void 0) {
|
|
6218
|
+
log_error(`kw_set_local_storage_value(): cannot save "${key}": a value with no json text`);
|
|
6100
6219
|
return -1;
|
|
6101
6220
|
}
|
|
6221
|
+
const store = local_store();
|
|
6222
|
+
if (!store) return 0;
|
|
6102
6223
|
try {
|
|
6103
|
-
|
|
6224
|
+
store.setItem(key, text);
|
|
6104
6225
|
return 0;
|
|
6105
6226
|
} catch (e) {
|
|
6106
6227
|
log_error(`kw_set_local_storage_value(): cannot save "${key}" (storage full or blocked?): ${e && e.message ? e.message : e}`);
|
|
@@ -6111,15 +6232,16 @@ function kw_set_local_storage_value(key, value) {
|
|
|
6111
6232
|
* Remove local attribute
|
|
6112
6233
|
********************************************/
|
|
6113
6234
|
function kw_remove_local_storage_value(key) {
|
|
6114
|
-
|
|
6235
|
+
const store = local_store();
|
|
6236
|
+
if (!store) return;
|
|
6115
6237
|
if (!key) {
|
|
6116
|
-
|
|
6238
|
+
log_error(`kw_remove_local_storage_value(): invalid key: key=${key}`);
|
|
6117
6239
|
return;
|
|
6118
6240
|
}
|
|
6119
6241
|
try {
|
|
6120
|
-
|
|
6242
|
+
store.removeItem(key);
|
|
6121
6243
|
} catch (e) {
|
|
6122
|
-
|
|
6244
|
+
log_warning(`kw_remove_local_storage_value(): cannot remove "${key}" (storage blocked?): ${e && e.message ? e.message : e}`);
|
|
6123
6245
|
}
|
|
6124
6246
|
}
|
|
6125
6247
|
/*************************************************************
|
|
@@ -7308,19 +7430,62 @@ function index_in_list(list, elm) {
|
|
|
7308
7430
|
return -1;
|
|
7309
7431
|
}
|
|
7310
7432
|
/************************************************************
|
|
7433
|
+
* A new uuid v4 (RFC 9562).
|
|
7311
7434
|
*
|
|
7435
|
+
* crypto.randomUUID() exists only in a secure context, so a page
|
|
7436
|
+
* served over http without TLS has none; crypto.getRandomValues()
|
|
7437
|
+
* exists there too. With no crypto at all, Math.random(): not fit
|
|
7438
|
+
* for a secret, and a uuid that names a node is not one.
|
|
7312
7439
|
************************************************************/
|
|
7440
|
+
function new_uuid() {
|
|
7441
|
+
const c = typeof window !== "undefined" && window.crypto || globalThis.crypto;
|
|
7442
|
+
if (c && typeof c.randomUUID === "function") return c.randomUUID();
|
|
7443
|
+
const b = /* @__PURE__ */ new Uint8Array(16);
|
|
7444
|
+
if (c && typeof c.getRandomValues === "function") c.getRandomValues(b);
|
|
7445
|
+
else for (let i = 0; i < b.length; i++) b[i] = Math.floor(Math.random() * 256);
|
|
7446
|
+
b[6] = b[6] & 15 | 64;
|
|
7447
|
+
b[8] = b[8] & 63 | 128;
|
|
7448
|
+
const h = Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
|
|
7449
|
+
return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;
|
|
7450
|
+
}
|
|
7451
|
+
/************************************************************
|
|
7452
|
+
* The uuid of this node: ONE for the life of the page.
|
|
7453
|
+
*
|
|
7454
|
+
* The one the store keeps ("host_uuid"), made and written there when
|
|
7455
|
+
* there is none. When the store cannot keep it (blocked, full, no
|
|
7456
|
+
* localStorage, outside a browser) the one this page made, warned
|
|
7457
|
+
* once. It goes in the node_uuid attribute of the yuno, the id of the
|
|
7458
|
+
* identity card and the `host` of every message the client sends, so
|
|
7459
|
+
* the first answer is kept and answered again: 7.26.27 made a new
|
|
7460
|
+
* uuid on every call when the store could not keep it, and answered
|
|
7461
|
+
* null without crypto.randomUUID().
|
|
7462
|
+
************************************************************/
|
|
7463
|
+
var uuid_of_this_page = null;
|
|
7313
7464
|
function node_uuid() {
|
|
7314
|
-
|
|
7315
|
-
|
|
7316
|
-
|
|
7317
|
-
|
|
7318
|
-
|
|
7319
|
-
|
|
7465
|
+
if (uuid_of_this_page) return uuid_of_this_page;
|
|
7466
|
+
const store = local_store();
|
|
7467
|
+
if (!store) {
|
|
7468
|
+
uuid_of_this_page = new_uuid();
|
|
7469
|
+
return uuid_of_this_page;
|
|
7470
|
+
}
|
|
7471
|
+
let uuid = null;
|
|
7472
|
+
try {
|
|
7473
|
+
uuid = store.getItem("host_uuid");
|
|
7474
|
+
} catch (e) {
|
|
7475
|
+
warn_local_storage_once("host_uuid", "node_uuid(): localStorage cannot be read (storage blocked?): a uuid of this page only");
|
|
7476
|
+
uuid_of_this_page = new_uuid();
|
|
7477
|
+
return uuid_of_this_page;
|
|
7478
|
+
}
|
|
7479
|
+
if (!uuid) {
|
|
7480
|
+
uuid = new_uuid();
|
|
7481
|
+
try {
|
|
7482
|
+
store.setItem("host_uuid", uuid);
|
|
7483
|
+
} catch (e) {
|
|
7484
|
+
warn_local_storage_once("host_uuid", "node_uuid(): localStorage cannot be written (storage full or blocked?): a uuid of this page only");
|
|
7320
7485
|
}
|
|
7321
|
-
}
|
|
7322
|
-
|
|
7323
|
-
return
|
|
7486
|
+
}
|
|
7487
|
+
uuid_of_this_page = uuid;
|
|
7488
|
+
return uuid_of_this_page;
|
|
7324
7489
|
}
|
|
7325
7490
|
/************************************************************
|
|
7326
7491
|
* Build a cleaned name
|
|
@@ -8605,7 +8770,7 @@ var attrs_table = [
|
|
|
8605
8770
|
SDATA(data_type_t.DTP_STRING, "cert_pem", sdata_flag_t.SDF_PERSIST, "", "SSL server certification, PEM str format"),
|
|
8606
8771
|
SDATA(data_type_t.DTP_JSON, "extra_info", sdata_flag_t.SDF_RD, "{}", "dict data set by user, added to the identity card msg."),
|
|
8607
8772
|
SDATA(data_type_t.DTP_LIST, "required_services", sdata_flag_t.SDF_RD, "[]", "Services this LINK requires (identity card). Empty = the yuno's `required_services`, as before. A yuno with several links to DIFFERENT backends must set it per link: the yuno-wide list is the union, so every backend is told the service names of all the others."),
|
|
8608
|
-
SDATA(data_type_t.DTP_INTEGER, "timeout_retry", sdata_flag_t.SDF_RD, "5000", "
|
|
8773
|
+
SDATA(data_type_t.DTP_INTEGER, "timeout_retry", sdata_flag_t.SDF_RD, "5000", "Base of the first reconnect delay (ms, ±20% jitter), at least 1000. It BACKS OFF from here, doubling up to timeout_retry_max, and resets when a session is reached"),
|
|
8609
8774
|
SDATA(data_type_t.DTP_INTEGER, "timeout_retry_max", sdata_flag_t.SDF_RD, "60000", "Cap of the reconnect backoff (ms). Set it equal to timeout_retry for the old fixed-interval behaviour"),
|
|
8610
8775
|
SDATA(data_type_t.DTP_INTEGER, "timeout_idack", sdata_flag_t.SDF_RD, "5000", "timeout waiting idAck"),
|
|
8611
8776
|
SDATA(data_type_t.DTP_POINTER, "subscriber", 0, 0, "subscriber of output-events. If null then subscriber is the parent"),
|
|
@@ -8630,7 +8795,8 @@ var PRIVATE_DATA = {
|
|
|
8630
8795
|
inside_on_open: false,
|
|
8631
8796
|
retry_ms: 0,
|
|
8632
8797
|
retry_ms_before_session: 0,
|
|
8633
|
-
session_since: 0
|
|
8798
|
+
session_since: 0,
|
|
8799
|
+
retry_floor_warned: false
|
|
8634
8800
|
};
|
|
8635
8801
|
var __gclass__ = null;
|
|
8636
8802
|
/******************************
|
|
@@ -8902,7 +9068,7 @@ var CLOSE_PEER_FAULT = 4002;
|
|
|
8902
9068
|
function close_for_peer_fault(gobj, reason) {
|
|
8903
9069
|
let priv = gobj.priv;
|
|
8904
9070
|
if (gobj_current_state(gobj) === "ST_SESSION") {
|
|
8905
|
-
if (performance.now() - priv.session_since <
|
|
9071
|
+
if (performance.now() - priv.session_since < retry_bounds(gobj).max) priv.retry_ms = priv.retry_ms_before_session;
|
|
8906
9072
|
priv.retry_ms_before_session = 0;
|
|
8907
9073
|
}
|
|
8908
9074
|
close_websocket(gobj, CLOSE_PEER_FAULT, reason);
|
|
@@ -9174,6 +9340,32 @@ function ac_on_close(gobj, event, kw, src) {
|
|
|
9174
9340
|
return 0;
|
|
9175
9341
|
}
|
|
9176
9342
|
/***************************************************************
|
|
9343
|
+
* The first delay of the backoff and its cap.
|
|
9344
|
+
*
|
|
9345
|
+
* A first delay below RETRY_FLOOR_MS is raised to it, with a WARNING
|
|
9346
|
+
* once: `timeout_retry` 0 never left 0 (0 doubled is 0), so a server
|
|
9347
|
+
* that closed before the ack was reconnected in a loop with no wait,
|
|
9348
|
+
* hundreds of connections a second.
|
|
9349
|
+
***************************************************************/
|
|
9350
|
+
var RETRY_FLOOR_MS = 1e3;
|
|
9351
|
+
function retry_bounds(gobj) {
|
|
9352
|
+
let priv = gobj.priv;
|
|
9353
|
+
let base = gobj_read_integer_attr(gobj, "timeout_retry");
|
|
9354
|
+
let max = gobj_read_integer_attr(gobj, "timeout_retry_max");
|
|
9355
|
+
if (!(base >= RETRY_FLOOR_MS)) {
|
|
9356
|
+
if (!priv.retry_floor_warned) {
|
|
9357
|
+
priv.retry_floor_warned = true;
|
|
9358
|
+
log_warning(`${gobj_short_name(gobj)}: timeout_retry ${base} ms is below ${RETRY_FLOOR_MS} ms: ${RETRY_FLOOR_MS} ms is used`);
|
|
9359
|
+
}
|
|
9360
|
+
base = RETRY_FLOOR_MS;
|
|
9361
|
+
}
|
|
9362
|
+
if (!(max >= base)) max = base;
|
|
9363
|
+
return {
|
|
9364
|
+
base,
|
|
9365
|
+
max
|
|
9366
|
+
};
|
|
9367
|
+
}
|
|
9368
|
+
/***************************************************************
|
|
9177
9369
|
* How long to wait before the next reconnect attempt.
|
|
9178
9370
|
*
|
|
9179
9371
|
* The delay used to be a constant (`timeout_retry`, 5s), for ever. A
|
|
@@ -9194,9 +9386,7 @@ function ac_on_close(gobj, event, kw, src) {
|
|
|
9194
9386
|
***************************************************************/
|
|
9195
9387
|
function next_retry(gobj) {
|
|
9196
9388
|
let priv = gobj.priv;
|
|
9197
|
-
|
|
9198
|
-
let max = gobj_read_integer_attr(gobj, "timeout_retry_max");
|
|
9199
|
-
if (max < base) max = base;
|
|
9389
|
+
const { base, max } = retry_bounds(gobj);
|
|
9200
9390
|
priv.retry_ms = priv.retry_ms ? Math.min(priv.retry_ms * 2, max) : base;
|
|
9201
9391
|
let jitter = priv.retry_ms * .2;
|
|
9202
9392
|
let ms = priv.retry_ms - jitter + Math.random() * 2 * jitter;
|
|
@@ -9694,6 +9884,7 @@ exports.json_object_update = json_object_update;
|
|
|
9694
9884
|
exports.json_object_update_existing = json_object_update_existing;
|
|
9695
9885
|
exports.json_object_update_missing = json_object_update_missing;
|
|
9696
9886
|
exports.json_size = json_size;
|
|
9887
|
+
exports.json_text_of = json_text_of;
|
|
9697
9888
|
exports.jwt2json = jwt2json;
|
|
9698
9889
|
exports.jwtDecode = jwtDecode;
|
|
9699
9890
|
exports.kw_clone_by_keys = kw_clone_by_keys;
|