@yuneta/gobj-js 7.26.27 → 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.
@@ -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.dl_children, walk_type, cb_walking, user_data, user_data2);
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.dl_children, walk_type, cb_walking, user_data, user_data2);
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,18 +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
- function rc_walk_by_list(iter_, walk_type, cb_walking, user_data, user_data2) {
2394
- let iter = [...iter_];
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];
2398
- if (gobj_is_destroying(child)) continue;
2441
+ if (walk_steps_over(child, owner, seen)) continue;
2442
+ seen.add(child);
2399
2443
  let ret = walk_ret(cb_walking(child, user_data, user_data2));
2400
2444
  if (ret < 0) return ret;
2401
2445
  }
2402
2446
  else for (let i = 0; i < iter.length; i++) {
2403
2447
  child = iter[i];
2404
- if (gobj_is_destroying(child)) continue;
2448
+ if (walk_steps_over(child, owner, seen)) continue;
2449
+ seen.add(child);
2405
2450
  let ret = walk_ret(cb_walking(child, user_data, user_data2));
2406
2451
  if (ret < 0) return ret;
2407
2452
  }
@@ -2416,24 +2461,23 @@
2416
2461
  * or
2417
2462
  * WALK_LAST2FIRST
2418
2463
  ***************************************************************/
2419
- function rc_walk_by_level(iter_, walk_type, cb_walking, user_data, user_data2) {
2420
- let iter = [...iter_];
2421
- let ret = rc_walk_by_list(iter, walk_type, cb_walking, user_data, user_data2);
2422
- if (ret < 0) return ret;
2423
- let child;
2424
- if (walk_type & walk_type_t.WALK_LAST2FIRST) for (let i = iter.length; i > 0; i--) {
2425
- child = iter[i - 1];
2426
- if (gobj_is_destroying(child)) continue;
2427
- ret = rc_walk_by_level(child.dl_children, walk_type, cb_walking, user_data, user_data2);
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));
2428
2473
  if (ret < 0) return ret;
2429
2474
  }
2430
- else for (let i = 0; i < iter.length; i++) {
2431
- child = iter[i];
2475
+ for (const child of handed) {
2432
2476
  if (gobj_is_destroying(child)) continue;
2433
- ret = rc_walk_by_level(child.dl_children, walk_type, cb_walking, user_data, user_data2);
2477
+ let ret = rc_walk_by_level(child, seen, walk_type, cb_walking, user_data, user_data2);
2434
2478
  if (ret < 0) return ret;
2435
2479
  }
2436
- return ret;
2480
+ return 0;
2437
2481
  }
2438
2482
  /***************************************************************
2439
2483
  * Walking
@@ -2450,27 +2494,29 @@
2450
2494
  * or
2451
2495
  * WALK_LAST2FIRST
2452
2496
  ***************************************************************/
2453
- function rc_walk_by_tree(iter_, walk_type, cb_walking, user_data, user_data2) {
2454
- if (walk_type & walk_type_t.WALK_BYLEVEL) return rc_walk_by_level(iter_, walk_type, cb_walking, user_data, user_data2);
2455
- let iter = [...iter_];
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];
2456
2500
  let ret;
2457
2501
  let child;
2458
2502
  if (walk_type & walk_type_t.WALK_BOTTOM2TOP) for (let i = iter.length; i > 0; i--) {
2459
2503
  child = iter[i - 1];
2460
- if (gobj_is_destroying(child)) continue;
2461
- ret = rc_walk_by_tree(child.dl_children, walk_type, cb_walking, user_data, user_data2);
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);
2462
2506
  if (ret < 0) return ret;
2463
2507
  if (gobj_is_destroying(child)) continue;
2508
+ seen.add(child);
2464
2509
  ret = walk_ret(cb_walking(child, user_data, user_data2));
2465
2510
  if (ret < 0) return ret;
2466
2511
  }
2467
2512
  else for (let i = 0; i < iter.length; i++) {
2468
2513
  child = iter[i];
2469
- if (gobj_is_destroying(child)) continue;
2514
+ if (walk_steps_over(child, owner, seen)) continue;
2515
+ seen.add(child);
2470
2516
  ret = walk_ret(cb_walking(child, user_data, user_data2));
2471
2517
  if (ret < 0) return ret;
2472
2518
  else if (ret === 0 && !gobj_is_destroying(child)) {
2473
- ret = rc_walk_by_tree(child.dl_children, walk_type, cb_walking, user_data, user_data2);
2519
+ ret = rc_walk_by_tree(child, seen, walk_type, cb_walking, user_data, user_data2);
2474
2520
  if (ret < 0) return ret;
2475
2521
  }
2476
2522
  }
@@ -6103,7 +6149,10 @@
6103
6149
  * for ever; a new corrupt entry is a new WARNING. Its text is never
6104
6150
  * shown (JSON.parse() quotes it in its error, and an entry can hold a
6105
6151
  * token). A store that cannot be read (blocked) is warned once per
6106
- * key, and the default is NOT written there. Outside a browser: the
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
6107
6156
  * default.
6108
6157
  ********************************************/
6109
6158
  function kw_get_local_storage_value(key, default_value, create = false) {
@@ -6117,6 +6166,7 @@
6117
6166
  warn_local_storage_once(key, `${where} cannot be read (storage blocked?): the default is used`);
6118
6167
  return default_value;
6119
6168
  }
6169
+ let went_well = true;
6120
6170
  let value = null;
6121
6171
  if (text !== null) try {
6122
6172
  value = JSON.parse(text);
@@ -6125,17 +6175,25 @@
6125
6175
  try {
6126
6176
  store.removeItem(key);
6127
6177
  log_warning(`${where} does not hold json: removed, the default is used`);
6128
- local_storage_warned.delete(key);
6129
6178
  } catch (e2) {
6179
+ went_well = false;
6130
6180
  warn_local_storage_once(key, `${where} does not hold json and cannot be removed: the default is used`);
6131
6181
  }
6132
- text = void 0;
6133
6182
  }
6134
- if (text !== void 0) local_storage_warned.delete(key);
6135
6183
  if (value === null) {
6136
- if (create) kw_set_local_storage_value(key, default_value);
6137
- return default_value;
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
+ }
6138
6195
  }
6196
+ if (went_well) local_storage_warned.delete(key);
6139
6197
  return value;
6140
6198
  }
6141
6199
  /********************************************
@@ -7375,29 +7433,62 @@
7375
7433
  return -1;
7376
7434
  }
7377
7435
  /************************************************************
7436
+ * A new uuid v4 (RFC 9562).
7378
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.
7379
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;
7380
7467
  function node_uuid() {
7381
- let uuid = "";
7468
+ if (uuid_of_this_page) return uuid_of_this_page;
7382
7469
  const store = local_store();
7383
- if (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();
7384
7484
  try {
7385
- uuid = store.getItem("host_uuid");
7485
+ store.setItem("host_uuid", uuid);
7386
7486
  } catch (e) {
7387
- uuid = null;
7388
- warn_local_storage_once("host_uuid", "node_uuid(): localStorage cannot be read (storage blocked?): a uuid of this page only");
7487
+ warn_local_storage_once("host_uuid", "node_uuid(): localStorage cannot be written (storage full or blocked?): a uuid of this page only");
7389
7488
  }
7390
- if (!uuid && window.crypto?.randomUUID) {
7391
- uuid = window.crypto.randomUUID();
7392
- try {
7393
- store.setItem("host_uuid", uuid);
7394
- } catch (e) {
7395
- warn_local_storage_once("host_uuid", "node_uuid(): localStorage cannot be written (storage full or blocked?): a uuid of this page only");
7396
- }
7397
- }
7398
- } else if (typeof globalThis !== "undefined" && globalThis.crypto?.randomUUID) uuid = globalThis.crypto.randomUUID();
7399
- else uuid = "00000000-0000-0000-0000-000000000000";
7400
- return uuid;
7489
+ }
7490
+ uuid_of_this_page = uuid;
7491
+ return uuid_of_this_page;
7401
7492
  }
7402
7493
  /************************************************************
7403
7494
  * Build a cleaned name
@@ -8682,7 +8773,7 @@
8682
8773
  SDATA(data_type_t.DTP_STRING, "cert_pem", sdata_flag_t.SDF_PERSIST, "", "SSL server certification, PEM str format"),
8683
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."),
8684
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."),
8685
- SDATA(data_type_t.DTP_INTEGER, "timeout_retry", sdata_flag_t.SDF_RD, "5000", "First reconnect delay (ms), at least 1000. It BACKS OFF from here, doubling up to timeout_retry_max, and resets when a session is reached"),
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"),
8686
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"),
8687
8778
  SDATA(data_type_t.DTP_INTEGER, "timeout_idack", sdata_flag_t.SDF_RD, "5000", "timeout waiting idAck"),
8688
8779
  SDATA(data_type_t.DTP_POINTER, "subscriber", 0, 0, "subscriber of output-events. If null then subscriber is the parent"),