@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/dist/gobj-js.umd.js
CHANGED
|
@@ -1952,6 +1952,31 @@
|
|
|
1952
1952
|
}
|
|
1953
1953
|
}
|
|
1954
1954
|
/************************************************************
|
|
1955
|
+
* The SDF_REQUIRED attributes with no value, or null when there
|
|
1956
|
+
* is none: what is empty is asked type by type, as in C.
|
|
1957
|
+
************************************************************/
|
|
1958
|
+
function gobj_check_required_attrs(gobj) {
|
|
1959
|
+
const hs = gobj_hsdata(gobj);
|
|
1960
|
+
const missing = [];
|
|
1961
|
+
if (!is_array(gobj.gclass.attrs_table)) return null;
|
|
1962
|
+
for (const it of gobj.gclass.attrs_table) {
|
|
1963
|
+
if (!it.name || !(it.flag & sdata_flag_t.SDF_REQUIRED)) continue;
|
|
1964
|
+
const value = hs[it.name];
|
|
1965
|
+
switch (it.type) {
|
|
1966
|
+
case data_type_t.DTP_STRING:
|
|
1967
|
+
if (!is_string(value) || empty_string(value)) missing.push(it.name);
|
|
1968
|
+
break;
|
|
1969
|
+
case data_type_t.DTP_LIST:
|
|
1970
|
+
case data_type_t.DTP_DICT:
|
|
1971
|
+
case data_type_t.DTP_JSON:
|
|
1972
|
+
if (empty_json(value)) missing.push(it.name);
|
|
1973
|
+
break;
|
|
1974
|
+
case data_type_t.DTP_POINTER: if (!value) missing.push(it.name);
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
return missing.length ? missing : null;
|
|
1978
|
+
}
|
|
1979
|
+
/************************************************************
|
|
1955
1980
|
*
|
|
1956
1981
|
************************************************************/
|
|
1957
1982
|
function gobj_start(gobj) {
|
|
@@ -1967,6 +1992,11 @@
|
|
|
1967
1992
|
log_error(`${gobj_short_name(gobj)}: GObj DISABLED`);
|
|
1968
1993
|
return -1;
|
|
1969
1994
|
}
|
|
1995
|
+
const required_attrs = gobj_check_required_attrs(gobj);
|
|
1996
|
+
if (required_attrs) {
|
|
1997
|
+
log_error(`${gobj_short_name(gobj)}: Cannot start without all required attributes: ` + JSON.stringify(required_attrs));
|
|
1998
|
+
return -1;
|
|
1999
|
+
}
|
|
1970
2000
|
if (__trace_gobj_start_stop__(gobj)) trace_machine(sprintf("⏺ ⏺ start: %s", gobj_full_name(gobj)));
|
|
1971
2001
|
gobj.running = true;
|
|
1972
2002
|
let ret = 0;
|
|
@@ -2353,7 +2383,7 @@
|
|
|
2353
2383
|
log_error(`gobj NULL, destroying, or bad type`);
|
|
2354
2384
|
return 0;
|
|
2355
2385
|
}
|
|
2356
|
-
return rc_walk_by_list(gobj
|
|
2386
|
+
return rc_walk_by_list(gobj, /* @__PURE__ */ new Set(), walk_type, cb_walking, user_data, user_data2);
|
|
2357
2387
|
}
|
|
2358
2388
|
/***************************************************************************
|
|
2359
2389
|
*
|
|
@@ -2363,7 +2393,7 @@
|
|
|
2363
2393
|
log_error(`gobj NULL, destroying, or bad type`);
|
|
2364
2394
|
return 0;
|
|
2365
2395
|
}
|
|
2366
|
-
return rc_walk_by_tree(gobj
|
|
2396
|
+
return rc_walk_by_tree(gobj, /* @__PURE__ */ new Set(), walk_type, cb_walking, user_data, user_data2);
|
|
2367
2397
|
}
|
|
2368
2398
|
/***************************************************************
|
|
2369
2399
|
* Walking
|
|
@@ -2390,16 +2420,33 @@
|
|
|
2390
2420
|
log_error(`walk callback must return a NUMBER (-1 stop, 0 descend, >0 skip children), got ${typeof ret}: taken as 0`);
|
|
2391
2421
|
return 0;
|
|
2392
2422
|
}
|
|
2393
|
-
|
|
2394
|
-
|
|
2423
|
+
/***************************************************************
|
|
2424
|
+
* Whether a walk steps over this gobj of the list of `owner`.
|
|
2425
|
+
*
|
|
2426
|
+
* Each list is walked over a COPY of it, because a callback may destroy
|
|
2427
|
+
* any gobj of the list, the current one included, or move one with
|
|
2428
|
+
* gobj_change_parent(). One it destroyed is stepped over, as the C walk
|
|
2429
|
+
* does; one it moved is no longer in this list, and is handed where the
|
|
2430
|
+
* walk finds it now (7.26.27 handed it here AND there); and a gobj is
|
|
2431
|
+
* never handed twice in one walk (`seen`), wherever it was moved.
|
|
2432
|
+
***************************************************************/
|
|
2433
|
+
function walk_steps_over(child, owner, seen) {
|
|
2434
|
+
return gobj_is_destroying(child) || child.parent !== owner || seen.has(child);
|
|
2435
|
+
}
|
|
2436
|
+
function rc_walk_by_list(owner, seen, walk_type, cb_walking, user_data, user_data2) {
|
|
2437
|
+
let iter = [...owner.dl_children];
|
|
2395
2438
|
let child;
|
|
2396
2439
|
if (walk_type & walk_type_t.WALK_LAST2FIRST) for (let i = iter.length; i > 0; i--) {
|
|
2397
2440
|
child = iter[i - 1];
|
|
2441
|
+
if (walk_steps_over(child, owner, seen)) continue;
|
|
2442
|
+
seen.add(child);
|
|
2398
2443
|
let ret = walk_ret(cb_walking(child, user_data, user_data2));
|
|
2399
2444
|
if (ret < 0) return ret;
|
|
2400
2445
|
}
|
|
2401
2446
|
else for (let i = 0; i < iter.length; i++) {
|
|
2402
2447
|
child = iter[i];
|
|
2448
|
+
if (walk_steps_over(child, owner, seen)) continue;
|
|
2449
|
+
seen.add(child);
|
|
2403
2450
|
let ret = walk_ret(cb_walking(child, user_data, user_data2));
|
|
2404
2451
|
if (ret < 0) return ret;
|
|
2405
2452
|
}
|
|
@@ -2414,21 +2461,23 @@
|
|
|
2414
2461
|
* or
|
|
2415
2462
|
* WALK_LAST2FIRST
|
|
2416
2463
|
***************************************************************/
|
|
2417
|
-
function rc_walk_by_level(
|
|
2418
|
-
let
|
|
2419
|
-
if (
|
|
2420
|
-
let
|
|
2421
|
-
|
|
2422
|
-
child
|
|
2423
|
-
|
|
2464
|
+
function rc_walk_by_level(owner, seen, walk_type, cb_walking, user_data, user_data2) {
|
|
2465
|
+
let iter = [...owner.dl_children];
|
|
2466
|
+
if (walk_type & walk_type_t.WALK_LAST2FIRST) iter.reverse();
|
|
2467
|
+
let handed = [];
|
|
2468
|
+
for (const child of iter) {
|
|
2469
|
+
if (walk_steps_over(child, owner, seen)) continue;
|
|
2470
|
+
seen.add(child);
|
|
2471
|
+
handed.push(child);
|
|
2472
|
+
let ret = walk_ret(cb_walking(child, user_data, user_data2));
|
|
2424
2473
|
if (ret < 0) return ret;
|
|
2425
2474
|
}
|
|
2426
|
-
|
|
2427
|
-
child
|
|
2428
|
-
ret = rc_walk_by_level(
|
|
2475
|
+
for (const child of handed) {
|
|
2476
|
+
if (gobj_is_destroying(child)) continue;
|
|
2477
|
+
let ret = rc_walk_by_level(child, seen, walk_type, cb_walking, user_data, user_data2);
|
|
2429
2478
|
if (ret < 0) return ret;
|
|
2430
2479
|
}
|
|
2431
|
-
return
|
|
2480
|
+
return 0;
|
|
2432
2481
|
}
|
|
2433
2482
|
/***************************************************************
|
|
2434
2483
|
* Walking
|
|
@@ -2445,23 +2494,29 @@
|
|
|
2445
2494
|
* or
|
|
2446
2495
|
* WALK_LAST2FIRST
|
|
2447
2496
|
***************************************************************/
|
|
2448
|
-
function rc_walk_by_tree(
|
|
2449
|
-
if (walk_type & walk_type_t.WALK_BYLEVEL) return rc_walk_by_level(
|
|
2497
|
+
function rc_walk_by_tree(owner, seen, walk_type, cb_walking, user_data, user_data2) {
|
|
2498
|
+
if (walk_type & walk_type_t.WALK_BYLEVEL) return rc_walk_by_level(owner, seen, walk_type, cb_walking, user_data, user_data2);
|
|
2499
|
+
let iter = [...owner.dl_children];
|
|
2450
2500
|
let ret;
|
|
2451
2501
|
let child;
|
|
2452
2502
|
if (walk_type & walk_type_t.WALK_BOTTOM2TOP) for (let i = iter.length; i > 0; i--) {
|
|
2453
2503
|
child = iter[i - 1];
|
|
2454
|
-
|
|
2504
|
+
if (walk_steps_over(child, owner, seen)) continue;
|
|
2505
|
+
ret = rc_walk_by_tree(child, seen, walk_type, cb_walking, user_data, user_data2);
|
|
2455
2506
|
if (ret < 0) return ret;
|
|
2507
|
+
if (gobj_is_destroying(child)) continue;
|
|
2508
|
+
seen.add(child);
|
|
2456
2509
|
ret = walk_ret(cb_walking(child, user_data, user_data2));
|
|
2457
2510
|
if (ret < 0) return ret;
|
|
2458
2511
|
}
|
|
2459
2512
|
else for (let i = 0; i < iter.length; i++) {
|
|
2460
2513
|
child = iter[i];
|
|
2514
|
+
if (walk_steps_over(child, owner, seen)) continue;
|
|
2515
|
+
seen.add(child);
|
|
2461
2516
|
ret = walk_ret(cb_walking(child, user_data, user_data2));
|
|
2462
2517
|
if (ret < 0) return ret;
|
|
2463
|
-
else if (ret === 0) {
|
|
2464
|
-
ret = rc_walk_by_tree(
|
|
2518
|
+
else if (ret === 0 && !gobj_is_destroying(child)) {
|
|
2519
|
+
ret = rc_walk_by_tree(child, seen, walk_type, cb_walking, user_data, user_data2);
|
|
2465
2520
|
if (ret < 0) return ret;
|
|
2466
2521
|
}
|
|
2467
2522
|
}
|
|
@@ -6046,44 +6101,99 @@
|
|
|
6046
6101
|
return kw_clone;
|
|
6047
6102
|
}
|
|
6048
6103
|
/********************************************
|
|
6104
|
+
* The browser's localStorage, or null when there is none (outside a
|
|
6105
|
+
* browser).
|
|
6106
|
+
*
|
|
6107
|
+
* With the site's data blocked (Chrome with cookies blocked, a sandboxed
|
|
6108
|
+
* iframe), READING the window.localStorage property already throws a
|
|
6109
|
+
* SecurityError, not only its getItem(): every app reads its locale at
|
|
6110
|
+
* start, so the app did not start. Such a store is answered as one whose
|
|
6111
|
+
* every call throws that error, so each helper fails as it fails for any
|
|
6112
|
+
* store that refuses it: a read gives the default, a write -1.
|
|
6113
|
+
********************************************/
|
|
6114
|
+
function local_store() {
|
|
6115
|
+
if (typeof window === "undefined") return null;
|
|
6116
|
+
let store;
|
|
6117
|
+
try {
|
|
6118
|
+
store = window.localStorage;
|
|
6119
|
+
} catch (e) {
|
|
6120
|
+
const refuse = () => {
|
|
6121
|
+
throw e;
|
|
6122
|
+
};
|
|
6123
|
+
return {
|
|
6124
|
+
getItem: refuse,
|
|
6125
|
+
setItem: refuse,
|
|
6126
|
+
removeItem: refuse
|
|
6127
|
+
};
|
|
6128
|
+
}
|
|
6129
|
+
return store || null;
|
|
6130
|
+
}
|
|
6131
|
+
/********************************************
|
|
6132
|
+
* A failure of the store that would repeat on every read is warned
|
|
6133
|
+
* once per key, until a read of the key goes well again.
|
|
6134
|
+
********************************************/
|
|
6135
|
+
var local_storage_warned = /* @__PURE__ */ new Set();
|
|
6136
|
+
function warn_local_storage_once(key, msg) {
|
|
6137
|
+
if (local_storage_warned.has(key)) return;
|
|
6138
|
+
local_storage_warned.add(key);
|
|
6139
|
+
log_warning(msg);
|
|
6140
|
+
}
|
|
6141
|
+
/********************************************
|
|
6049
6142
|
* Get a local attribute
|
|
6050
6143
|
*
|
|
6051
6144
|
* The json the entry holds, or `default_value` when there is none
|
|
6052
6145
|
* (written there too with `create`). A json null is no value, as
|
|
6053
6146
|
* json_absent() in C: the default. An entry that is not json (a
|
|
6054
|
-
* corrupt or foreign write) is
|
|
6147
|
+
* corrupt or foreign write) is a WARNING and is REMOVED, so the
|
|
6055
6148
|
* next read finds no entry: it used to console.warn on every read,
|
|
6056
|
-
* for ever
|
|
6057
|
-
* error, and an entry can hold a
|
|
6058
|
-
*
|
|
6149
|
+
* for ever; a new corrupt entry is a new WARNING. Its text is never
|
|
6150
|
+
* shown (JSON.parse() quotes it in its error, and an entry can hold a
|
|
6151
|
+
* token). A store that cannot be read (blocked) is warned once per
|
|
6152
|
+
* key, and the default is NOT written there; one that cannot be
|
|
6153
|
+
* written (full) is warned once per key too: with `create` it was an
|
|
6154
|
+
* ERROR on every read, the default tried again each time. A key is
|
|
6155
|
+
* warned again after a read that goes well. Outside a browser: the
|
|
6156
|
+
* default.
|
|
6059
6157
|
********************************************/
|
|
6060
|
-
var local_storage_warned = /* @__PURE__ */ new Set();
|
|
6061
|
-
function warn_local_storage_once(key, what) {
|
|
6062
|
-
if (local_storage_warned.has(key)) return;
|
|
6063
|
-
local_storage_warned.add(key);
|
|
6064
|
-
log_warning(`kw_get_local_storage_value(): localStorage key "${key}" ${what}`);
|
|
6065
|
-
}
|
|
6066
6158
|
function kw_get_local_storage_value(key, default_value, create = false) {
|
|
6067
|
-
|
|
6159
|
+
const store = local_store();
|
|
6160
|
+
if (!store) return default_value;
|
|
6161
|
+
const where = `kw_get_local_storage_value(): localStorage key "${key}"`;
|
|
6068
6162
|
let text;
|
|
6069
6163
|
try {
|
|
6070
|
-
text =
|
|
6164
|
+
text = store.getItem(key);
|
|
6071
6165
|
} catch (e) {
|
|
6072
|
-
warn_local_storage_once(key,
|
|
6166
|
+
warn_local_storage_once(key, `${where} cannot be read (storage blocked?): the default is used`);
|
|
6073
6167
|
return default_value;
|
|
6074
6168
|
}
|
|
6169
|
+
let went_well = true;
|
|
6075
6170
|
let value = null;
|
|
6076
6171
|
if (text !== null) try {
|
|
6077
6172
|
value = JSON.parse(text);
|
|
6078
6173
|
} catch (e) {
|
|
6079
|
-
warn_local_storage_once(key, "does not hold json: removed, the default is used");
|
|
6080
|
-
kw_remove_local_storage_value(key);
|
|
6081
6174
|
value = null;
|
|
6175
|
+
try {
|
|
6176
|
+
store.removeItem(key);
|
|
6177
|
+
log_warning(`${where} does not hold json: removed, the default is used`);
|
|
6178
|
+
} catch (e2) {
|
|
6179
|
+
went_well = false;
|
|
6180
|
+
warn_local_storage_once(key, `${where} does not hold json and cannot be removed: the default is used`);
|
|
6181
|
+
}
|
|
6082
6182
|
}
|
|
6083
6183
|
if (value === null) {
|
|
6084
|
-
|
|
6085
|
-
|
|
6184
|
+
value = default_value;
|
|
6185
|
+
if (create) {
|
|
6186
|
+
const default_text = json_text_of(default_value);
|
|
6187
|
+
if (default_text === void 0) log_error(`${where}: cannot write the default: a value with no json text`);
|
|
6188
|
+
else try {
|
|
6189
|
+
store.setItem(key, default_text);
|
|
6190
|
+
} catch (e) {
|
|
6191
|
+
went_well = false;
|
|
6192
|
+
warn_local_storage_once(key, `${where} cannot be written (storage full or blocked?): the default is used, not kept`);
|
|
6193
|
+
}
|
|
6194
|
+
}
|
|
6086
6195
|
}
|
|
6196
|
+
if (went_well) local_storage_warned.delete(key);
|
|
6087
6197
|
return value;
|
|
6088
6198
|
}
|
|
6089
6199
|
/********************************************
|
|
@@ -6095,15 +6205,26 @@
|
|
|
6095
6205
|
* reported success on a store that had rejected the write: a full or blocked
|
|
6096
6206
|
* localStorage (quota, private mode) silently discarded the change while the
|
|
6097
6207
|
* UI showed it as saved.
|
|
6208
|
+
*
|
|
6209
|
+
* A value with no json text is refused with an ERROR, as a typed writer
|
|
6210
|
+
* refuses it (json_text_of()): a function was stored as the text
|
|
6211
|
+
* "undefined", and a NaN, a Date or a Map as a text that reads back as
|
|
6212
|
+
* another value. Checked in Node.js too, where there is no store.
|
|
6098
6213
|
********************************************/
|
|
6099
6214
|
function kw_set_local_storage_value(key, value) {
|
|
6100
|
-
if (
|
|
6101
|
-
|
|
6102
|
-
|
|
6215
|
+
if (!key) {
|
|
6216
|
+
log_error(`kw_set_local_storage_value(): invalid key: key=${key}`);
|
|
6217
|
+
return -1;
|
|
6218
|
+
}
|
|
6219
|
+
const text = json_text_of(value);
|
|
6220
|
+
if (text === void 0) {
|
|
6221
|
+
log_error(`kw_set_local_storage_value(): cannot save "${key}": a value with no json text`);
|
|
6103
6222
|
return -1;
|
|
6104
6223
|
}
|
|
6224
|
+
const store = local_store();
|
|
6225
|
+
if (!store) return 0;
|
|
6105
6226
|
try {
|
|
6106
|
-
|
|
6227
|
+
store.setItem(key, text);
|
|
6107
6228
|
return 0;
|
|
6108
6229
|
} catch (e) {
|
|
6109
6230
|
log_error(`kw_set_local_storage_value(): cannot save "${key}" (storage full or blocked?): ${e && e.message ? e.message : e}`);
|
|
@@ -6114,15 +6235,16 @@
|
|
|
6114
6235
|
* Remove local attribute
|
|
6115
6236
|
********************************************/
|
|
6116
6237
|
function kw_remove_local_storage_value(key) {
|
|
6117
|
-
|
|
6238
|
+
const store = local_store();
|
|
6239
|
+
if (!store) return;
|
|
6118
6240
|
if (!key) {
|
|
6119
|
-
|
|
6241
|
+
log_error(`kw_remove_local_storage_value(): invalid key: key=${key}`);
|
|
6120
6242
|
return;
|
|
6121
6243
|
}
|
|
6122
6244
|
try {
|
|
6123
|
-
|
|
6245
|
+
store.removeItem(key);
|
|
6124
6246
|
} catch (e) {
|
|
6125
|
-
|
|
6247
|
+
log_warning(`kw_remove_local_storage_value(): cannot remove "${key}" (storage blocked?): ${e && e.message ? e.message : e}`);
|
|
6126
6248
|
}
|
|
6127
6249
|
}
|
|
6128
6250
|
/*************************************************************
|
|
@@ -7311,19 +7433,62 @@
|
|
|
7311
7433
|
return -1;
|
|
7312
7434
|
}
|
|
7313
7435
|
/************************************************************
|
|
7436
|
+
* A new uuid v4 (RFC 9562).
|
|
7314
7437
|
*
|
|
7438
|
+
* crypto.randomUUID() exists only in a secure context, so a page
|
|
7439
|
+
* served over http without TLS has none; crypto.getRandomValues()
|
|
7440
|
+
* exists there too. With no crypto at all, Math.random(): not fit
|
|
7441
|
+
* for a secret, and a uuid that names a node is not one.
|
|
7315
7442
|
************************************************************/
|
|
7443
|
+
function new_uuid() {
|
|
7444
|
+
const c = typeof window !== "undefined" && window.crypto || globalThis.crypto;
|
|
7445
|
+
if (c && typeof c.randomUUID === "function") return c.randomUUID();
|
|
7446
|
+
const b = /* @__PURE__ */ new Uint8Array(16);
|
|
7447
|
+
if (c && typeof c.getRandomValues === "function") c.getRandomValues(b);
|
|
7448
|
+
else for (let i = 0; i < b.length; i++) b[i] = Math.floor(Math.random() * 256);
|
|
7449
|
+
b[6] = b[6] & 15 | 64;
|
|
7450
|
+
b[8] = b[8] & 63 | 128;
|
|
7451
|
+
const h = Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
|
|
7452
|
+
return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;
|
|
7453
|
+
}
|
|
7454
|
+
/************************************************************
|
|
7455
|
+
* The uuid of this node: ONE for the life of the page.
|
|
7456
|
+
*
|
|
7457
|
+
* The one the store keeps ("host_uuid"), made and written there when
|
|
7458
|
+
* there is none. When the store cannot keep it (blocked, full, no
|
|
7459
|
+
* localStorage, outside a browser) the one this page made, warned
|
|
7460
|
+
* once. It goes in the node_uuid attribute of the yuno, the id of the
|
|
7461
|
+
* identity card and the `host` of every message the client sends, so
|
|
7462
|
+
* the first answer is kept and answered again: 7.26.27 made a new
|
|
7463
|
+
* uuid on every call when the store could not keep it, and answered
|
|
7464
|
+
* null without crypto.randomUUID().
|
|
7465
|
+
************************************************************/
|
|
7466
|
+
var uuid_of_this_page = null;
|
|
7316
7467
|
function node_uuid() {
|
|
7317
|
-
|
|
7318
|
-
|
|
7319
|
-
|
|
7320
|
-
|
|
7321
|
-
|
|
7322
|
-
|
|
7468
|
+
if (uuid_of_this_page) return uuid_of_this_page;
|
|
7469
|
+
const store = local_store();
|
|
7470
|
+
if (!store) {
|
|
7471
|
+
uuid_of_this_page = new_uuid();
|
|
7472
|
+
return uuid_of_this_page;
|
|
7473
|
+
}
|
|
7474
|
+
let uuid = null;
|
|
7475
|
+
try {
|
|
7476
|
+
uuid = store.getItem("host_uuid");
|
|
7477
|
+
} catch (e) {
|
|
7478
|
+
warn_local_storage_once("host_uuid", "node_uuid(): localStorage cannot be read (storage blocked?): a uuid of this page only");
|
|
7479
|
+
uuid_of_this_page = new_uuid();
|
|
7480
|
+
return uuid_of_this_page;
|
|
7481
|
+
}
|
|
7482
|
+
if (!uuid) {
|
|
7483
|
+
uuid = new_uuid();
|
|
7484
|
+
try {
|
|
7485
|
+
store.setItem("host_uuid", uuid);
|
|
7486
|
+
} catch (e) {
|
|
7487
|
+
warn_local_storage_once("host_uuid", "node_uuid(): localStorage cannot be written (storage full or blocked?): a uuid of this page only");
|
|
7323
7488
|
}
|
|
7324
|
-
}
|
|
7325
|
-
|
|
7326
|
-
return
|
|
7489
|
+
}
|
|
7490
|
+
uuid_of_this_page = uuid;
|
|
7491
|
+
return uuid_of_this_page;
|
|
7327
7492
|
}
|
|
7328
7493
|
/************************************************************
|
|
7329
7494
|
* Build a cleaned name
|
|
@@ -8608,7 +8773,7 @@
|
|
|
8608
8773
|
SDATA(data_type_t.DTP_STRING, "cert_pem", sdata_flag_t.SDF_PERSIST, "", "SSL server certification, PEM str format"),
|
|
8609
8774
|
SDATA(data_type_t.DTP_JSON, "extra_info", sdata_flag_t.SDF_RD, "{}", "dict data set by user, added to the identity card msg."),
|
|
8610
8775
|
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."),
|
|
8611
|
-
SDATA(data_type_t.DTP_INTEGER, "timeout_retry", sdata_flag_t.SDF_RD, "5000", "
|
|
8776
|
+
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"),
|
|
8612
8777
|
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"),
|
|
8613
8778
|
SDATA(data_type_t.DTP_INTEGER, "timeout_idack", sdata_flag_t.SDF_RD, "5000", "timeout waiting idAck"),
|
|
8614
8779
|
SDATA(data_type_t.DTP_POINTER, "subscriber", 0, 0, "subscriber of output-events. If null then subscriber is the parent"),
|
|
@@ -8633,7 +8798,8 @@
|
|
|
8633
8798
|
inside_on_open: false,
|
|
8634
8799
|
retry_ms: 0,
|
|
8635
8800
|
retry_ms_before_session: 0,
|
|
8636
|
-
session_since: 0
|
|
8801
|
+
session_since: 0,
|
|
8802
|
+
retry_floor_warned: false
|
|
8637
8803
|
};
|
|
8638
8804
|
var __gclass__ = null;
|
|
8639
8805
|
/******************************
|
|
@@ -8905,7 +9071,7 @@
|
|
|
8905
9071
|
function close_for_peer_fault(gobj, reason) {
|
|
8906
9072
|
let priv = gobj.priv;
|
|
8907
9073
|
if (gobj_current_state(gobj) === "ST_SESSION") {
|
|
8908
|
-
if (performance.now() - priv.session_since <
|
|
9074
|
+
if (performance.now() - priv.session_since < retry_bounds(gobj).max) priv.retry_ms = priv.retry_ms_before_session;
|
|
8909
9075
|
priv.retry_ms_before_session = 0;
|
|
8910
9076
|
}
|
|
8911
9077
|
close_websocket(gobj, CLOSE_PEER_FAULT, reason);
|
|
@@ -9177,6 +9343,32 @@
|
|
|
9177
9343
|
return 0;
|
|
9178
9344
|
}
|
|
9179
9345
|
/***************************************************************
|
|
9346
|
+
* The first delay of the backoff and its cap.
|
|
9347
|
+
*
|
|
9348
|
+
* A first delay below RETRY_FLOOR_MS is raised to it, with a WARNING
|
|
9349
|
+
* once: `timeout_retry` 0 never left 0 (0 doubled is 0), so a server
|
|
9350
|
+
* that closed before the ack was reconnected in a loop with no wait,
|
|
9351
|
+
* hundreds of connections a second.
|
|
9352
|
+
***************************************************************/
|
|
9353
|
+
var RETRY_FLOOR_MS = 1e3;
|
|
9354
|
+
function retry_bounds(gobj) {
|
|
9355
|
+
let priv = gobj.priv;
|
|
9356
|
+
let base = gobj_read_integer_attr(gobj, "timeout_retry");
|
|
9357
|
+
let max = gobj_read_integer_attr(gobj, "timeout_retry_max");
|
|
9358
|
+
if (!(base >= RETRY_FLOOR_MS)) {
|
|
9359
|
+
if (!priv.retry_floor_warned) {
|
|
9360
|
+
priv.retry_floor_warned = true;
|
|
9361
|
+
log_warning(`${gobj_short_name(gobj)}: timeout_retry ${base} ms is below ${RETRY_FLOOR_MS} ms: ${RETRY_FLOOR_MS} ms is used`);
|
|
9362
|
+
}
|
|
9363
|
+
base = RETRY_FLOOR_MS;
|
|
9364
|
+
}
|
|
9365
|
+
if (!(max >= base)) max = base;
|
|
9366
|
+
return {
|
|
9367
|
+
base,
|
|
9368
|
+
max
|
|
9369
|
+
};
|
|
9370
|
+
}
|
|
9371
|
+
/***************************************************************
|
|
9180
9372
|
* How long to wait before the next reconnect attempt.
|
|
9181
9373
|
*
|
|
9182
9374
|
* The delay used to be a constant (`timeout_retry`, 5s), for ever. A
|
|
@@ -9197,9 +9389,7 @@
|
|
|
9197
9389
|
***************************************************************/
|
|
9198
9390
|
function next_retry(gobj) {
|
|
9199
9391
|
let priv = gobj.priv;
|
|
9200
|
-
|
|
9201
|
-
let max = gobj_read_integer_attr(gobj, "timeout_retry_max");
|
|
9202
|
-
if (max < base) max = base;
|
|
9392
|
+
const { base, max } = retry_bounds(gobj);
|
|
9203
9393
|
priv.retry_ms = priv.retry_ms ? Math.min(priv.retry_ms * 2, max) : base;
|
|
9204
9394
|
let jitter = priv.retry_ms * .2;
|
|
9205
9395
|
let ms = priv.retry_ms - jitter + Math.random() * 2 * jitter;
|
|
@@ -9697,6 +9887,7 @@
|
|
|
9697
9887
|
exports.json_object_update_existing = json_object_update_existing;
|
|
9698
9888
|
exports.json_object_update_missing = json_object_update_missing;
|
|
9699
9889
|
exports.json_size = json_size;
|
|
9890
|
+
exports.json_text_of = json_text_of;
|
|
9700
9891
|
exports.jwt2json = jwt2json;
|
|
9701
9892
|
exports.jwtDecode = jwtDecode;
|
|
9702
9893
|
exports.kw_clone_by_keys = kw_clone_by_keys;
|