@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.
@@ -1948,6 +1948,31 @@ function gobj_destroy_children(gobj) {
1948
1948
  }
1949
1949
  }
1950
1950
  /************************************************************
1951
+ * The SDF_REQUIRED attributes with no value, or null when there
1952
+ * is none: what is empty is asked type by type, as in C.
1953
+ ************************************************************/
1954
+ function gobj_check_required_attrs(gobj) {
1955
+ const hs = gobj_hsdata(gobj);
1956
+ const missing = [];
1957
+ if (!is_array(gobj.gclass.attrs_table)) return null;
1958
+ for (const it of gobj.gclass.attrs_table) {
1959
+ if (!it.name || !(it.flag & sdata_flag_t.SDF_REQUIRED)) continue;
1960
+ const value = hs[it.name];
1961
+ switch (it.type) {
1962
+ case data_type_t.DTP_STRING:
1963
+ if (!is_string(value) || empty_string(value)) missing.push(it.name);
1964
+ break;
1965
+ case data_type_t.DTP_LIST:
1966
+ case data_type_t.DTP_DICT:
1967
+ case data_type_t.DTP_JSON:
1968
+ if (empty_json(value)) missing.push(it.name);
1969
+ break;
1970
+ case data_type_t.DTP_POINTER: if (!value) missing.push(it.name);
1971
+ }
1972
+ }
1973
+ return missing.length ? missing : null;
1974
+ }
1975
+ /************************************************************
1951
1976
  *
1952
1977
  ************************************************************/
1953
1978
  function gobj_start(gobj) {
@@ -1963,6 +1988,11 @@ function gobj_start(gobj) {
1963
1988
  log_error(`${gobj_short_name(gobj)}: GObj DISABLED`);
1964
1989
  return -1;
1965
1990
  }
1991
+ const required_attrs = gobj_check_required_attrs(gobj);
1992
+ if (required_attrs) {
1993
+ log_error(`${gobj_short_name(gobj)}: Cannot start without all required attributes: ` + JSON.stringify(required_attrs));
1994
+ return -1;
1995
+ }
1966
1996
  if (__trace_gobj_start_stop__(gobj)) trace_machine(sprintf("⏺ ⏺ start: %s", gobj_full_name(gobj)));
1967
1997
  gobj.running = true;
1968
1998
  let ret = 0;
@@ -2349,7 +2379,7 @@ function gobj_walk_gobj_children(gobj, walk_type, cb_walking, user_data, user_da
2349
2379
  log_error(`gobj NULL, destroying, or bad type`);
2350
2380
  return 0;
2351
2381
  }
2352
- return rc_walk_by_list(gobj.dl_children, walk_type, cb_walking, user_data, user_data2);
2382
+ return rc_walk_by_list(gobj, /* @__PURE__ */ new Set(), walk_type, cb_walking, user_data, user_data2);
2353
2383
  }
2354
2384
  /***************************************************************************
2355
2385
  *
@@ -2359,7 +2389,7 @@ function gobj_walk_gobj_children_tree(gobj, walk_type, cb_walking, user_data, us
2359
2389
  log_error(`gobj NULL, destroying, or bad type`);
2360
2390
  return 0;
2361
2391
  }
2362
- return rc_walk_by_tree(gobj.dl_children, walk_type, cb_walking, user_data, user_data2);
2392
+ return rc_walk_by_tree(gobj, /* @__PURE__ */ new Set(), walk_type, cb_walking, user_data, user_data2);
2363
2393
  }
2364
2394
  /***************************************************************
2365
2395
  * Walking
@@ -2386,16 +2416,33 @@ function walk_ret(ret) {
2386
2416
  log_error(`walk callback must return a NUMBER (-1 stop, 0 descend, >0 skip children), got ${typeof ret}: taken as 0`);
2387
2417
  return 0;
2388
2418
  }
2389
- function rc_walk_by_list(iter_, walk_type, cb_walking, user_data, user_data2) {
2390
- let iter = [...iter_];
2419
+ /***************************************************************
2420
+ * Whether a walk steps over this gobj of the list of `owner`.
2421
+ *
2422
+ * Each list is walked over a COPY of it, because a callback may destroy
2423
+ * any gobj of the list, the current one included, or move one with
2424
+ * gobj_change_parent(). One it destroyed is stepped over, as the C walk
2425
+ * does; one it moved is no longer in this list, and is handed where the
2426
+ * walk finds it now (7.26.27 handed it here AND there); and a gobj is
2427
+ * never handed twice in one walk (`seen`), wherever it was moved.
2428
+ ***************************************************************/
2429
+ function walk_steps_over(child, owner, seen) {
2430
+ return gobj_is_destroying(child) || child.parent !== owner || seen.has(child);
2431
+ }
2432
+ function rc_walk_by_list(owner, seen, walk_type, cb_walking, user_data, user_data2) {
2433
+ let iter = [...owner.dl_children];
2391
2434
  let child;
2392
2435
  if (walk_type & walk_type_t.WALK_LAST2FIRST) for (let i = iter.length; i > 0; i--) {
2393
2436
  child = iter[i - 1];
2437
+ if (walk_steps_over(child, owner, seen)) continue;
2438
+ seen.add(child);
2394
2439
  let ret = walk_ret(cb_walking(child, user_data, user_data2));
2395
2440
  if (ret < 0) return ret;
2396
2441
  }
2397
2442
  else for (let i = 0; i < iter.length; i++) {
2398
2443
  child = iter[i];
2444
+ if (walk_steps_over(child, owner, seen)) continue;
2445
+ seen.add(child);
2399
2446
  let ret = walk_ret(cb_walking(child, user_data, user_data2));
2400
2447
  if (ret < 0) return ret;
2401
2448
  }
@@ -2410,21 +2457,23 @@ function rc_walk_by_list(iter_, walk_type, cb_walking, user_data, user_data2) {
2410
2457
  * or
2411
2458
  * WALK_LAST2FIRST
2412
2459
  ***************************************************************/
2413
- function rc_walk_by_level(iter, walk_type, cb_walking, user_data, user_data2) {
2414
- let ret = rc_walk_by_list(iter, walk_type, cb_walking, user_data, user_data2);
2415
- if (ret < 0) return ret;
2416
- let child;
2417
- if (walk_type & walk_type_t.WALK_LAST2FIRST) for (let i = iter.length; i > 0; i--) {
2418
- child = iter[i - 1];
2419
- ret = rc_walk_by_level([...child.dl_children], walk_type, cb_walking, user_data, user_data2);
2460
+ function rc_walk_by_level(owner, seen, walk_type, cb_walking, user_data, user_data2) {
2461
+ let iter = [...owner.dl_children];
2462
+ if (walk_type & walk_type_t.WALK_LAST2FIRST) iter.reverse();
2463
+ let handed = [];
2464
+ for (const child of iter) {
2465
+ if (walk_steps_over(child, owner, seen)) continue;
2466
+ seen.add(child);
2467
+ handed.push(child);
2468
+ let ret = walk_ret(cb_walking(child, user_data, user_data2));
2420
2469
  if (ret < 0) return ret;
2421
2470
  }
2422
- else for (let i = 0; i < iter.length; i++) {
2423
- child = iter[i];
2424
- ret = rc_walk_by_level([...child.dl_children], walk_type, cb_walking, user_data, user_data2);
2471
+ for (const child of handed) {
2472
+ if (gobj_is_destroying(child)) continue;
2473
+ let ret = rc_walk_by_level(child, seen, walk_type, cb_walking, user_data, user_data2);
2425
2474
  if (ret < 0) return ret;
2426
2475
  }
2427
- return ret;
2476
+ return 0;
2428
2477
  }
2429
2478
  /***************************************************************
2430
2479
  * Walking
@@ -2441,23 +2490,29 @@ function rc_walk_by_level(iter, walk_type, cb_walking, user_data, user_data2) {
2441
2490
  * or
2442
2491
  * WALK_LAST2FIRST
2443
2492
  ***************************************************************/
2444
- function rc_walk_by_tree(iter, walk_type, cb_walking, user_data, user_data2) {
2445
- if (walk_type & walk_type_t.WALK_BYLEVEL) return rc_walk_by_level(iter, walk_type, cb_walking, user_data, user_data2);
2493
+ function rc_walk_by_tree(owner, seen, walk_type, cb_walking, user_data, user_data2) {
2494
+ if (walk_type & walk_type_t.WALK_BYLEVEL) return rc_walk_by_level(owner, seen, walk_type, cb_walking, user_data, user_data2);
2495
+ let iter = [...owner.dl_children];
2446
2496
  let ret;
2447
2497
  let child;
2448
2498
  if (walk_type & walk_type_t.WALK_BOTTOM2TOP) for (let i = iter.length; i > 0; i--) {
2449
2499
  child = iter[i - 1];
2450
- ret = rc_walk_by_tree([...child.dl_children], walk_type, cb_walking, user_data, user_data2);
2500
+ if (walk_steps_over(child, owner, seen)) continue;
2501
+ ret = rc_walk_by_tree(child, seen, walk_type, cb_walking, user_data, user_data2);
2451
2502
  if (ret < 0) return ret;
2503
+ if (gobj_is_destroying(child)) continue;
2504
+ seen.add(child);
2452
2505
  ret = walk_ret(cb_walking(child, user_data, user_data2));
2453
2506
  if (ret < 0) return ret;
2454
2507
  }
2455
2508
  else for (let i = 0; i < iter.length; i++) {
2456
2509
  child = iter[i];
2510
+ if (walk_steps_over(child, owner, seen)) continue;
2511
+ seen.add(child);
2457
2512
  ret = walk_ret(cb_walking(child, user_data, user_data2));
2458
2513
  if (ret < 0) return ret;
2459
- else if (ret === 0) {
2460
- ret = rc_walk_by_tree([...child.dl_children], walk_type, cb_walking, user_data, user_data2);
2514
+ else if (ret === 0 && !gobj_is_destroying(child)) {
2515
+ ret = rc_walk_by_tree(child, seen, walk_type, cb_walking, user_data, user_data2);
2461
2516
  if (ret < 0) return ret;
2462
2517
  }
2463
2518
  }
@@ -6042,44 +6097,99 @@ function kw_clone_by_not_keys(gobj, kw, keys, verbose) {
6042
6097
  return kw_clone;
6043
6098
  }
6044
6099
  /********************************************
6100
+ * The browser's localStorage, or null when there is none (outside a
6101
+ * browser).
6102
+ *
6103
+ * With the site's data blocked (Chrome with cookies blocked, a sandboxed
6104
+ * iframe), READING the window.localStorage property already throws a
6105
+ * SecurityError, not only its getItem(): every app reads its locale at
6106
+ * start, so the app did not start. Such a store is answered as one whose
6107
+ * every call throws that error, so each helper fails as it fails for any
6108
+ * store that refuses it: a read gives the default, a write -1.
6109
+ ********************************************/
6110
+ function local_store() {
6111
+ if (typeof window === "undefined") return null;
6112
+ let store;
6113
+ try {
6114
+ store = window.localStorage;
6115
+ } catch (e) {
6116
+ const refuse = () => {
6117
+ throw e;
6118
+ };
6119
+ return {
6120
+ getItem: refuse,
6121
+ setItem: refuse,
6122
+ removeItem: refuse
6123
+ };
6124
+ }
6125
+ return store || null;
6126
+ }
6127
+ /********************************************
6128
+ * A failure of the store that would repeat on every read is warned
6129
+ * once per key, until a read of the key goes well again.
6130
+ ********************************************/
6131
+ var local_storage_warned = /* @__PURE__ */ new Set();
6132
+ function warn_local_storage_once(key, msg) {
6133
+ if (local_storage_warned.has(key)) return;
6134
+ local_storage_warned.add(key);
6135
+ log_warning(msg);
6136
+ }
6137
+ /********************************************
6045
6138
  * Get a local attribute
6046
6139
  *
6047
6140
  * The json the entry holds, or `default_value` when there is none
6048
6141
  * (written there too with `create`). A json null is no value, as
6049
6142
  * json_absent() in C: the default. An entry that is not json (a
6050
- * corrupt or foreign write) is one WARNING and is REMOVED, so the
6143
+ * corrupt or foreign write) is a WARNING and is REMOVED, so the
6051
6144
  * next read finds no entry: it used to console.warn on every read,
6052
- * for ever. Its text is never shown (JSON.parse() quotes it in its
6053
- * error, and an entry can hold a token). A store that cannot be read
6054
- * is warned once per key. Outside a browser: the default.
6145
+ * for ever; a new corrupt entry is a new WARNING. Its text is never
6146
+ * shown (JSON.parse() quotes it in its error, and an entry can hold a
6147
+ * token). A store that cannot be read (blocked) is warned once per
6148
+ * key, and the default is NOT written there; one that cannot be
6149
+ * written (full) is warned once per key too: with `create` it was an
6150
+ * ERROR on every read, the default tried again each time. A key is
6151
+ * warned again after a read that goes well. Outside a browser: the
6152
+ * default.
6055
6153
  ********************************************/
6056
- var local_storage_warned = /* @__PURE__ */ new Set();
6057
- function warn_local_storage_once(key, what) {
6058
- if (local_storage_warned.has(key)) return;
6059
- local_storage_warned.add(key);
6060
- log_warning(`kw_get_local_storage_value(): localStorage key "${key}" ${what}`);
6061
- }
6062
6154
  function kw_get_local_storage_value(key, default_value, create = false) {
6063
- if (typeof window === "undefined" || !window.localStorage) return default_value;
6155
+ const store = local_store();
6156
+ if (!store) return default_value;
6157
+ const where = `kw_get_local_storage_value(): localStorage key "${key}"`;
6064
6158
  let text;
6065
6159
  try {
6066
- text = window.localStorage.getItem(key);
6160
+ text = store.getItem(key);
6067
6161
  } catch (e) {
6068
- warn_local_storage_once(key, "cannot be read (storage blocked?): the default is used");
6162
+ warn_local_storage_once(key, `${where} cannot be read (storage blocked?): the default is used`);
6069
6163
  return default_value;
6070
6164
  }
6165
+ let went_well = true;
6071
6166
  let value = null;
6072
6167
  if (text !== null) try {
6073
6168
  value = JSON.parse(text);
6074
6169
  } catch (e) {
6075
- warn_local_storage_once(key, "does not hold json: removed, the default is used");
6076
- kw_remove_local_storage_value(key);
6077
6170
  value = null;
6171
+ try {
6172
+ store.removeItem(key);
6173
+ log_warning(`${where} does not hold json: removed, the default is used`);
6174
+ } catch (e2) {
6175
+ went_well = false;
6176
+ warn_local_storage_once(key, `${where} does not hold json and cannot be removed: the default is used`);
6177
+ }
6078
6178
  }
6079
6179
  if (value === null) {
6080
- if (create) kw_set_local_storage_value(key, default_value);
6081
- return default_value;
6180
+ value = default_value;
6181
+ if (create) {
6182
+ const default_text = json_text_of(default_value);
6183
+ if (default_text === void 0) log_error(`${where}: cannot write the default: a value with no json text`);
6184
+ else try {
6185
+ store.setItem(key, default_text);
6186
+ } catch (e) {
6187
+ went_well = false;
6188
+ warn_local_storage_once(key, `${where} cannot be written (storage full or blocked?): the default is used, not kept`);
6189
+ }
6190
+ }
6082
6191
  }
6192
+ if (went_well) local_storage_warned.delete(key);
6083
6193
  return value;
6084
6194
  }
6085
6195
  /********************************************
@@ -6091,15 +6201,26 @@ function kw_get_local_storage_value(key, default_value, create = false) {
6091
6201
  * reported success on a store that had rejected the write: a full or blocked
6092
6202
  * localStorage (quota, private mode) silently discarded the change while the
6093
6203
  * UI showed it as saved.
6204
+ *
6205
+ * A value with no json text is refused with an ERROR, as a typed writer
6206
+ * refuses it (json_text_of()): a function was stored as the text
6207
+ * "undefined", and a NaN, a Date or a Map as a text that reads back as
6208
+ * another value. Checked in Node.js too, where there is no store.
6094
6209
  ********************************************/
6095
6210
  function kw_set_local_storage_value(key, value) {
6096
- if (typeof window === "undefined" || !window.localStorage) return 0;
6097
- if (!key || value === void 0) {
6098
- log_error(`kw_set_local_storage_value(): invalid key or value: key=${key}`);
6211
+ if (!key) {
6212
+ log_error(`kw_set_local_storage_value(): invalid key: key=${key}`);
6213
+ return -1;
6214
+ }
6215
+ const text = json_text_of(value);
6216
+ if (text === void 0) {
6217
+ log_error(`kw_set_local_storage_value(): cannot save "${key}": a value with no json text`);
6099
6218
  return -1;
6100
6219
  }
6220
+ const store = local_store();
6221
+ if (!store) return 0;
6101
6222
  try {
6102
- window.localStorage.setItem(key, JSON.stringify(value));
6223
+ store.setItem(key, text);
6103
6224
  return 0;
6104
6225
  } catch (e) {
6105
6226
  log_error(`kw_set_local_storage_value(): cannot save "${key}" (storage full or blocked?): ${e && e.message ? e.message : e}`);
@@ -6110,15 +6231,16 @@ function kw_set_local_storage_value(key, value) {
6110
6231
  * Remove local attribute
6111
6232
  ********************************************/
6112
6233
  function kw_remove_local_storage_value(key) {
6113
- if (typeof window === "undefined" || !window.localStorage) return;
6234
+ const store = local_store();
6235
+ if (!store) return;
6114
6236
  if (!key) {
6115
- window.console.warn(`Invalid key for localStorage removal: ${key}`);
6237
+ log_error(`kw_remove_local_storage_value(): invalid key: key=${key}`);
6116
6238
  return;
6117
6239
  }
6118
6240
  try {
6119
- window.localStorage.removeItem(key);
6241
+ store.removeItem(key);
6120
6242
  } catch (e) {
6121
- window.console.warn(`Error removing localStorage key "${key}":`, e);
6243
+ log_warning(`kw_remove_local_storage_value(): cannot remove "${key}" (storage blocked?): ${e && e.message ? e.message : e}`);
6122
6244
  }
6123
6245
  }
6124
6246
  /*************************************************************
@@ -7307,19 +7429,62 @@ function index_in_list(list, elm) {
7307
7429
  return -1;
7308
7430
  }
7309
7431
  /************************************************************
7432
+ * A new uuid v4 (RFC 9562).
7310
7433
  *
7434
+ * crypto.randomUUID() exists only in a secure context, so a page
7435
+ * served over http without TLS has none; crypto.getRandomValues()
7436
+ * exists there too. With no crypto at all, Math.random(): not fit
7437
+ * for a secret, and a uuid that names a node is not one.
7311
7438
  ************************************************************/
7439
+ function new_uuid() {
7440
+ const c = typeof window !== "undefined" && window.crypto || globalThis.crypto;
7441
+ if (c && typeof c.randomUUID === "function") return c.randomUUID();
7442
+ const b = /* @__PURE__ */ new Uint8Array(16);
7443
+ if (c && typeof c.getRandomValues === "function") c.getRandomValues(b);
7444
+ else for (let i = 0; i < b.length; i++) b[i] = Math.floor(Math.random() * 256);
7445
+ b[6] = b[6] & 15 | 64;
7446
+ b[8] = b[8] & 63 | 128;
7447
+ const h = Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
7448
+ return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;
7449
+ }
7450
+ /************************************************************
7451
+ * The uuid of this node: ONE for the life of the page.
7452
+ *
7453
+ * The one the store keeps ("host_uuid"), made and written there when
7454
+ * there is none. When the store cannot keep it (blocked, full, no
7455
+ * localStorage, outside a browser) the one this page made, warned
7456
+ * once. It goes in the node_uuid attribute of the yuno, the id of the
7457
+ * identity card and the `host` of every message the client sends, so
7458
+ * the first answer is kept and answered again: 7.26.27 made a new
7459
+ * uuid on every call when the store could not keep it, and answered
7460
+ * null without crypto.randomUUID().
7461
+ ************************************************************/
7462
+ var uuid_of_this_page = null;
7312
7463
  function node_uuid() {
7313
- let uuid = "";
7314
- if (typeof window !== "undefined" && window.localStorage) {
7315
- uuid = window.localStorage.getItem("host_uuid");
7316
- if (!uuid && window.crypto?.randomUUID) {
7317
- uuid = window.crypto.randomUUID();
7318
- window.localStorage.setItem("host_uuid", uuid);
7464
+ if (uuid_of_this_page) return uuid_of_this_page;
7465
+ const store = local_store();
7466
+ if (!store) {
7467
+ uuid_of_this_page = new_uuid();
7468
+ return uuid_of_this_page;
7469
+ }
7470
+ let uuid = null;
7471
+ try {
7472
+ uuid = store.getItem("host_uuid");
7473
+ } catch (e) {
7474
+ warn_local_storage_once("host_uuid", "node_uuid(): localStorage cannot be read (storage blocked?): a uuid of this page only");
7475
+ uuid_of_this_page = new_uuid();
7476
+ return uuid_of_this_page;
7477
+ }
7478
+ if (!uuid) {
7479
+ uuid = new_uuid();
7480
+ try {
7481
+ store.setItem("host_uuid", uuid);
7482
+ } catch (e) {
7483
+ warn_local_storage_once("host_uuid", "node_uuid(): localStorage cannot be written (storage full or blocked?): a uuid of this page only");
7319
7484
  }
7320
- } else if (typeof globalThis !== "undefined" && globalThis.crypto?.randomUUID) uuid = globalThis.crypto.randomUUID();
7321
- else uuid = "00000000-0000-0000-0000-000000000000";
7322
- return uuid;
7485
+ }
7486
+ uuid_of_this_page = uuid;
7487
+ return uuid_of_this_page;
7323
7488
  }
7324
7489
  /************************************************************
7325
7490
  * Build a cleaned name
@@ -8604,7 +8769,7 @@ var attrs_table = [
8604
8769
  SDATA(data_type_t.DTP_STRING, "cert_pem", sdata_flag_t.SDF_PERSIST, "", "SSL server certification, PEM str format"),
8605
8770
  SDATA(data_type_t.DTP_JSON, "extra_info", sdata_flag_t.SDF_RD, "{}", "dict data set by user, added to the identity card msg."),
8606
8771
  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."),
8607
- SDATA(data_type_t.DTP_INTEGER, "timeout_retry", sdata_flag_t.SDF_RD, "5000", "First reconnect delay (ms). It BACKS OFF from here, doubling up to timeout_retry_max, and resets when a session is reached"),
8772
+ 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"),
8608
8773
  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"),
8609
8774
  SDATA(data_type_t.DTP_INTEGER, "timeout_idack", sdata_flag_t.SDF_RD, "5000", "timeout waiting idAck"),
8610
8775
  SDATA(data_type_t.DTP_POINTER, "subscriber", 0, 0, "subscriber of output-events. If null then subscriber is the parent"),
@@ -8629,7 +8794,8 @@ var PRIVATE_DATA = {
8629
8794
  inside_on_open: false,
8630
8795
  retry_ms: 0,
8631
8796
  retry_ms_before_session: 0,
8632
- session_since: 0
8797
+ session_since: 0,
8798
+ retry_floor_warned: false
8633
8799
  };
8634
8800
  var __gclass__ = null;
8635
8801
  /******************************
@@ -8901,7 +9067,7 @@ var CLOSE_PEER_FAULT = 4002;
8901
9067
  function close_for_peer_fault(gobj, reason) {
8902
9068
  let priv = gobj.priv;
8903
9069
  if (gobj_current_state(gobj) === "ST_SESSION") {
8904
- if (performance.now() - priv.session_since < Math.max(gobj_read_integer_attr(gobj, "timeout_retry"), gobj_read_integer_attr(gobj, "timeout_retry_max"))) priv.retry_ms = priv.retry_ms_before_session;
9070
+ if (performance.now() - priv.session_since < retry_bounds(gobj).max) priv.retry_ms = priv.retry_ms_before_session;
8905
9071
  priv.retry_ms_before_session = 0;
8906
9072
  }
8907
9073
  close_websocket(gobj, CLOSE_PEER_FAULT, reason);
@@ -9173,6 +9339,32 @@ function ac_on_close(gobj, event, kw, src) {
9173
9339
  return 0;
9174
9340
  }
9175
9341
  /***************************************************************
9342
+ * The first delay of the backoff and its cap.
9343
+ *
9344
+ * A first delay below RETRY_FLOOR_MS is raised to it, with a WARNING
9345
+ * once: `timeout_retry` 0 never left 0 (0 doubled is 0), so a server
9346
+ * that closed before the ack was reconnected in a loop with no wait,
9347
+ * hundreds of connections a second.
9348
+ ***************************************************************/
9349
+ var RETRY_FLOOR_MS = 1e3;
9350
+ function retry_bounds(gobj) {
9351
+ let priv = gobj.priv;
9352
+ let base = gobj_read_integer_attr(gobj, "timeout_retry");
9353
+ let max = gobj_read_integer_attr(gobj, "timeout_retry_max");
9354
+ if (!(base >= RETRY_FLOOR_MS)) {
9355
+ if (!priv.retry_floor_warned) {
9356
+ priv.retry_floor_warned = true;
9357
+ log_warning(`${gobj_short_name(gobj)}: timeout_retry ${base} ms is below ${RETRY_FLOOR_MS} ms: ${RETRY_FLOOR_MS} ms is used`);
9358
+ }
9359
+ base = RETRY_FLOOR_MS;
9360
+ }
9361
+ if (!(max >= base)) max = base;
9362
+ return {
9363
+ base,
9364
+ max
9365
+ };
9366
+ }
9367
+ /***************************************************************
9176
9368
  * How long to wait before the next reconnect attempt.
9177
9369
  *
9178
9370
  * The delay used to be a constant (`timeout_retry`, 5s), for ever. A
@@ -9193,9 +9385,7 @@ function ac_on_close(gobj, event, kw, src) {
9193
9385
  ***************************************************************/
9194
9386
  function next_retry(gobj) {
9195
9387
  let priv = gobj.priv;
9196
- let base = gobj_read_integer_attr(gobj, "timeout_retry");
9197
- let max = gobj_read_integer_attr(gobj, "timeout_retry_max");
9198
- if (max < base) max = base;
9388
+ const { base, max } = retry_bounds(gobj);
9199
9389
  priv.retry_ms = priv.retry_ms ? Math.min(priv.retry_ms * 2, max) : base;
9200
9390
  let jitter = priv.retry_ms * .2;
9201
9391
  let ms = priv.retry_ms - jitter + Math.random() * 2 * jitter;
@@ -9488,6 +9678,6 @@ function register_c_ievent_cli() {
9488
9678
  return create_gclass(GCLASS_NAME);
9489
9679
  }
9490
9680
  //#endregion
9491
- export { GObj, SDATA, SDATAAUTHZ, SDATACM, SDATACM2, SDATADF, SDATAPM, SDATAPM0, SDATA_END, SDataDesc, YUNETA_VERSION, __yuno__, build_command_response, build_path, build_stats_response, clean_name, clear_timeout, cmp_two_simple_json, command_parameter_from_json, command_parameter_from_text, command_parser, createElement2, createOneHtml, create_json_record, create_template_record, current_timestamp, data_type_t, db_list_persistent_attrs, db_load_persistent_attrs, db_remove_persistent_attrs, db_save_persistent_attrs, debounce, delete_from_list, duplicate_objects, empty_json, empty_string, escapeHtml, event_flag_t, flat2json, flat_apply, flat_diff, flat_key_join, flat_key_split, gclass_add_ev_action, gclass_add_event_type, gclass_add_state, gclass_check_fsm, gclass_create, gclass_event_type, gclass_find_by_name, gclass_flag_t, gclass_unregister, getPositionRelativeToBody, get_function_name, get_now, gobj_bottom_gobj, gobj_change_parent, gobj_change_state, gobj_command, gobj_create, gobj_create2, gobj_create_default_service, gobj_create_pure_child, gobj_create_service, gobj_create_volatil, gobj_create_yuno, gobj_current_state, gobj_default_service, gobj_deliver_posted_events, gobj_destroy, gobj_find_child, gobj_find_event_type, gobj_find_gobj, gobj_find_service, gobj_find_subscribings, gobj_find_subscriptions, gobj_flag_t, gobj_full_name, gobj_gclass_name, gobj_get_gclass_trace_level, gobj_get_gclass_trace_level2, gobj_get_gclass_trace_no_level, gobj_get_global_trace_level, gobj_get_global_trace_no_level, gobj_global_trace_level, gobj_global_trace_no_level, gobj_has_attr, gobj_has_event, gobj_has_output_event, gobj_hsdata, gobj_is_destroying, gobj_is_playing, gobj_is_pure_child, gobj_is_running, gobj_is_service, gobj_is_volatil, gobj_list_persistent_attrs, gobj_list_subscriptions, gobj_load_persistent_attrs, gobj_match_children, gobj_match_children_tree, gobj_match_gobj, gobj_name, gobj_parent, gobj_pause, gobj_play, gobj_post_event, gobj_posted_events_size, gobj_publish_event, gobj_read_attr, gobj_read_attrs, gobj_read_bool_attr, gobj_read_integer_attr, gobj_read_pointer_attr, gobj_read_str_attr, gobj_remove_persistent_attrs, gobj_repr_global_trace_levels, gobj_reset_volatil_attrs, gobj_save_persistent_attrs, gobj_search_path, gobj_send_event, gobj_services, gobj_set_bottom_gobj, gobj_set_deep_trace, gobj_set_gclass_no_trace, gobj_set_gclass_trace, gobj_set_global_no_trace, gobj_set_global_no_trace2, gobj_set_global_trace, gobj_set_global_trace2, gobj_set_gobj_no_trace, gobj_set_gobj_trace, gobj_set_trace_machine_format, gobj_short_name, gobj_start, gobj_start_children, gobj_start_tree, gobj_start_up, gobj_stats, gobj_stop, gobj_stop_children, gobj_stop_tree, gobj_subscribe_event, gobj_trace_level, gobj_trace_machine_format, gobj_trace_no_level, gobj_unsubscribe_event, gobj_unsubscribe_list, gobj_walk_gobj_children, gobj_walk_gobj_children_tree, gobj_write_attr, gobj_write_attrs, gobj_write_bool_attr, gobj_write_integer_attr, gobj_write_str_attr, gobj_yuno, gobj_yuno_id, gobj_yuno_name, gobj_yuno_role, html_markup, id_index_in_obj_list, index_in_list, is_array, is_boolean, is_date, is_function, is_gobj, is_html_markup, is_metadata_key, is_null, is_number, is_object, is_private_key, is_pure_number, is_secret_name, is_string, jdb_delete, jdb_get, jdb_get_by_idx, jdb_get_topic, jdb_init, jdb_update, json2flat, json_array_append, json_array_append_new, json_array_extend, json_array_remove, json_array_size, json_deep_copy, json_is_identical, json_mask_secrets, json_object_del, json_object_get, json_object_set, json_object_set_new, json_object_size, json_object_update, json_object_update_existing, json_object_update_missing, json_size, jwt2json, jwtDecode, kw_clone_by_keys, kw_clone_by_not_keys, kw_collect, kw_delete, kw_find_json_in_list, kw_find_path, kw_flag_t, kw_get_bool, kw_get_dict, kw_get_dict_value, kw_get_int, kw_get_list, kw_get_local_storage_value, kw_get_pointer, kw_get_real, kw_get_str, kw_has_key, kw_match_simple, kw_pop, kw_remove_local_storage_value, kw_select, kw_set_dict_value, kw_set_local_storage_value, kw_set_subdict_value, kwid_collect, kwid_find_one_record, kwid_get_ids, kwid_match_id, kwid_new_dict, kwid_new_list, list2options, load_json_file, log_debug, log_error, log_info, log_warning, mask_secrets_inline, msg_iev_get_msg_type, msg_iev_get_stack, msg_iev_push_stack, msg_iev_read_key, msg_iev_set_msg_type, msg_iev_write_key, node_uuid, parseBoolean, parseSVG, refresh_language, register_c_ievent_cli, register_c_timer, register_c_yuno, safeSrc, sdata_flag_t, send_http_json_post, set_console_log_enabled, set_console_log_filter, set_log_callback, set_remote_log_functions, set_strings_are_text, set_timeout, set_timeout_periodic, sprintf, stats_parser, str_in_list, strcasecmp, strcmp, strings_are_text, strncmp, strs_in_list, strstr, template_get_field_desc, timeTracker, trace_json, trace_json_masked, trace_level_t, trace_msg, treedb_decoder_fkey, treedb_decoder_hook, treedb_encoder_fkey, treedb_get_field_desc, treedb_hook_data_size, vsprintf, walk_type_t };
9681
+ export { GObj, SDATA, SDATAAUTHZ, SDATACM, SDATACM2, SDATADF, SDATAPM, SDATAPM0, SDATA_END, SDataDesc, YUNETA_VERSION, __yuno__, build_command_response, build_path, build_stats_response, clean_name, clear_timeout, cmp_two_simple_json, command_parameter_from_json, command_parameter_from_text, command_parser, createElement2, createOneHtml, create_json_record, create_template_record, current_timestamp, data_type_t, db_list_persistent_attrs, db_load_persistent_attrs, db_remove_persistent_attrs, db_save_persistent_attrs, debounce, delete_from_list, duplicate_objects, empty_json, empty_string, escapeHtml, event_flag_t, flat2json, flat_apply, flat_diff, flat_key_join, flat_key_split, gclass_add_ev_action, gclass_add_event_type, gclass_add_state, gclass_check_fsm, gclass_create, gclass_event_type, gclass_find_by_name, gclass_flag_t, gclass_unregister, getPositionRelativeToBody, get_function_name, get_now, gobj_bottom_gobj, gobj_change_parent, gobj_change_state, gobj_command, gobj_create, gobj_create2, gobj_create_default_service, gobj_create_pure_child, gobj_create_service, gobj_create_volatil, gobj_create_yuno, gobj_current_state, gobj_default_service, gobj_deliver_posted_events, gobj_destroy, gobj_find_child, gobj_find_event_type, gobj_find_gobj, gobj_find_service, gobj_find_subscribings, gobj_find_subscriptions, gobj_flag_t, gobj_full_name, gobj_gclass_name, gobj_get_gclass_trace_level, gobj_get_gclass_trace_level2, gobj_get_gclass_trace_no_level, gobj_get_global_trace_level, gobj_get_global_trace_no_level, gobj_global_trace_level, gobj_global_trace_no_level, gobj_has_attr, gobj_has_event, gobj_has_output_event, gobj_hsdata, gobj_is_destroying, gobj_is_playing, gobj_is_pure_child, gobj_is_running, gobj_is_service, gobj_is_volatil, gobj_list_persistent_attrs, gobj_list_subscriptions, gobj_load_persistent_attrs, gobj_match_children, gobj_match_children_tree, gobj_match_gobj, gobj_name, gobj_parent, gobj_pause, gobj_play, gobj_post_event, gobj_posted_events_size, gobj_publish_event, gobj_read_attr, gobj_read_attrs, gobj_read_bool_attr, gobj_read_integer_attr, gobj_read_pointer_attr, gobj_read_str_attr, gobj_remove_persistent_attrs, gobj_repr_global_trace_levels, gobj_reset_volatil_attrs, gobj_save_persistent_attrs, gobj_search_path, gobj_send_event, gobj_services, gobj_set_bottom_gobj, gobj_set_deep_trace, gobj_set_gclass_no_trace, gobj_set_gclass_trace, gobj_set_global_no_trace, gobj_set_global_no_trace2, gobj_set_global_trace, gobj_set_global_trace2, gobj_set_gobj_no_trace, gobj_set_gobj_trace, gobj_set_trace_machine_format, gobj_short_name, gobj_start, gobj_start_children, gobj_start_tree, gobj_start_up, gobj_stats, gobj_stop, gobj_stop_children, gobj_stop_tree, gobj_subscribe_event, gobj_trace_level, gobj_trace_machine_format, gobj_trace_no_level, gobj_unsubscribe_event, gobj_unsubscribe_list, gobj_walk_gobj_children, gobj_walk_gobj_children_tree, gobj_write_attr, gobj_write_attrs, gobj_write_bool_attr, gobj_write_integer_attr, gobj_write_str_attr, gobj_yuno, gobj_yuno_id, gobj_yuno_name, gobj_yuno_role, html_markup, id_index_in_obj_list, index_in_list, is_array, is_boolean, is_date, is_function, is_gobj, is_html_markup, is_metadata_key, is_null, is_number, is_object, is_private_key, is_pure_number, is_secret_name, is_string, jdb_delete, jdb_get, jdb_get_by_idx, jdb_get_topic, jdb_init, jdb_update, json2flat, json_array_append, json_array_append_new, json_array_extend, json_array_remove, json_array_size, json_deep_copy, json_is_identical, json_mask_secrets, json_object_del, json_object_get, json_object_set, json_object_set_new, json_object_size, json_object_update, json_object_update_existing, json_object_update_missing, json_size, json_text_of, jwt2json, jwtDecode, kw_clone_by_keys, kw_clone_by_not_keys, kw_collect, kw_delete, kw_find_json_in_list, kw_find_path, kw_flag_t, kw_get_bool, kw_get_dict, kw_get_dict_value, kw_get_int, kw_get_list, kw_get_local_storage_value, kw_get_pointer, kw_get_real, kw_get_str, kw_has_key, kw_match_simple, kw_pop, kw_remove_local_storage_value, kw_select, kw_set_dict_value, kw_set_local_storage_value, kw_set_subdict_value, kwid_collect, kwid_find_one_record, kwid_get_ids, kwid_match_id, kwid_new_dict, kwid_new_list, list2options, load_json_file, log_debug, log_error, log_info, log_warning, mask_secrets_inline, msg_iev_get_msg_type, msg_iev_get_stack, msg_iev_push_stack, msg_iev_read_key, msg_iev_set_msg_type, msg_iev_write_key, node_uuid, parseBoolean, parseSVG, refresh_language, register_c_ievent_cli, register_c_timer, register_c_yuno, safeSrc, sdata_flag_t, send_http_json_post, set_console_log_enabled, set_console_log_filter, set_log_callback, set_remote_log_functions, set_strings_are_text, set_timeout, set_timeout_periodic, sprintf, stats_parser, str_in_list, strcasecmp, strcmp, strings_are_text, strncmp, strs_in_list, strstr, template_get_field_desc, timeTracker, trace_json, trace_json_masked, trace_level_t, trace_msg, treedb_decoder_fkey, treedb_decoder_hook, treedb_encoder_fkey, treedb_get_field_desc, treedb_hook_data_size, vsprintf, walk_type_t };
9492
9682
 
9493
9683
  //# sourceMappingURL=gobj-js.es.js.map