@yuneta/gobj-js 7.26.27 → 7.26.29

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;
@@ -2120,7 +2150,8 @@ function gobj_play(gobj) {
2120
2150
  if (!gobj_is_running(gobj)) {
2121
2151
  if (!(gobj.gclass.gclass_flag & gclass_flag_t.gcflag_required_start_to_play)) {
2122
2152
  log_warning(`${gobj_short_name(gobj)}: GObj playing without previous start`);
2123
- gobj_start(gobj);
2153
+ if (start_destroys_it(gobj)) return -1;
2154
+ if (!gobj.running) return -1;
2124
2155
  } else {
2125
2156
  log_error(`${gobj_short_name(gobj)}: Cannot play, start not done`);
2126
2157
  return -1;
@@ -2349,7 +2380,7 @@ function gobj_walk_gobj_children(gobj, walk_type, cb_walking, user_data, user_da
2349
2380
  log_error(`gobj NULL, destroying, or bad type`);
2350
2381
  return 0;
2351
2382
  }
2352
- return rc_walk_by_list(gobj.dl_children, walk_type, cb_walking, user_data, user_data2);
2383
+ return rc_walk_by_list(gobj, /* @__PURE__ */ new Set(), walk_type, cb_walking, user_data, user_data2);
2353
2384
  }
2354
2385
  /***************************************************************************
2355
2386
  *
@@ -2359,7 +2390,7 @@ function gobj_walk_gobj_children_tree(gobj, walk_type, cb_walking, user_data, us
2359
2390
  log_error(`gobj NULL, destroying, or bad type`);
2360
2391
  return 0;
2361
2392
  }
2362
- return rc_walk_by_tree(gobj.dl_children, walk_type, cb_walking, user_data, user_data2);
2393
+ return rc_walk_by_tree(gobj, /* @__PURE__ */ new Set(), walk_type, cb_walking, user_data, user_data2);
2363
2394
  }
2364
2395
  /***************************************************************
2365
2396
  * Walking
@@ -2386,18 +2417,33 @@ function walk_ret(ret) {
2386
2417
  log_error(`walk callback must return a NUMBER (-1 stop, 0 descend, >0 skip children), got ${typeof ret}: taken as 0`);
2387
2418
  return 0;
2388
2419
  }
2389
- function rc_walk_by_list(iter_, walk_type, cb_walking, user_data, user_data2) {
2390
- let iter = [...iter_];
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];
2391
2435
  let child;
2392
2436
  if (walk_type & walk_type_t.WALK_LAST2FIRST) for (let i = iter.length; i > 0; i--) {
2393
2437
  child = iter[i - 1];
2394
- if (gobj_is_destroying(child)) continue;
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];
2400
- if (gobj_is_destroying(child)) continue;
2445
+ if (walk_steps_over(child, owner, seen)) continue;
2446
+ seen.add(child);
2401
2447
  let ret = walk_ret(cb_walking(child, user_data, user_data2));
2402
2448
  if (ret < 0) return ret;
2403
2449
  }
@@ -2412,24 +2458,23 @@ function rc_walk_by_list(iter_, walk_type, cb_walking, user_data, user_data2) {
2412
2458
  * or
2413
2459
  * WALK_LAST2FIRST
2414
2460
  ***************************************************************/
2415
- function rc_walk_by_level(iter_, walk_type, cb_walking, user_data, user_data2) {
2416
- let iter = [...iter_];
2417
- let ret = rc_walk_by_list(iter, walk_type, cb_walking, user_data, user_data2);
2418
- if (ret < 0) return ret;
2419
- let child;
2420
- if (walk_type & walk_type_t.WALK_LAST2FIRST) for (let i = iter.length; i > 0; i--) {
2421
- child = iter[i - 1];
2422
- if (gobj_is_destroying(child)) continue;
2423
- ret = rc_walk_by_level(child.dl_children, walk_type, cb_walking, user_data, user_data2);
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));
2424
2470
  if (ret < 0) return ret;
2425
2471
  }
2426
- else for (let i = 0; i < iter.length; i++) {
2427
- child = iter[i];
2472
+ for (const child of handed) {
2428
2473
  if (gobj_is_destroying(child)) continue;
2429
- ret = rc_walk_by_level(child.dl_children, walk_type, cb_walking, user_data, user_data2);
2474
+ let ret = rc_walk_by_level(child, seen, walk_type, cb_walking, user_data, user_data2);
2430
2475
  if (ret < 0) return ret;
2431
2476
  }
2432
- return ret;
2477
+ return 0;
2433
2478
  }
2434
2479
  /***************************************************************
2435
2480
  * Walking
@@ -2446,27 +2491,29 @@ function rc_walk_by_level(iter_, walk_type, cb_walking, user_data, user_data2) {
2446
2491
  * or
2447
2492
  * WALK_LAST2FIRST
2448
2493
  ***************************************************************/
2449
- function rc_walk_by_tree(iter_, walk_type, cb_walking, user_data, user_data2) {
2450
- if (walk_type & walk_type_t.WALK_BYLEVEL) return rc_walk_by_level(iter_, walk_type, cb_walking, user_data, user_data2);
2451
- let iter = [...iter_];
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];
2452
2497
  let ret;
2453
2498
  let child;
2454
2499
  if (walk_type & walk_type_t.WALK_BOTTOM2TOP) for (let i = iter.length; i > 0; i--) {
2455
2500
  child = iter[i - 1];
2456
- if (gobj_is_destroying(child)) continue;
2457
- ret = rc_walk_by_tree(child.dl_children, walk_type, cb_walking, user_data, user_data2);
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);
2458
2503
  if (ret < 0) return ret;
2459
2504
  if (gobj_is_destroying(child)) continue;
2505
+ seen.add(child);
2460
2506
  ret = walk_ret(cb_walking(child, user_data, user_data2));
2461
2507
  if (ret < 0) return ret;
2462
2508
  }
2463
2509
  else for (let i = 0; i < iter.length; i++) {
2464
2510
  child = iter[i];
2465
- if (gobj_is_destroying(child)) continue;
2511
+ if (walk_steps_over(child, owner, seen)) continue;
2512
+ seen.add(child);
2466
2513
  ret = walk_ret(cb_walking(child, user_data, user_data2));
2467
2514
  if (ret < 0) return ret;
2468
2515
  else if (ret === 0 && !gobj_is_destroying(child)) {
2469
- ret = rc_walk_by_tree(child.dl_children, walk_type, cb_walking, user_data, user_data2);
2516
+ ret = rc_walk_by_tree(child, seen, walk_type, cb_walking, user_data, user_data2);
2470
2517
  if (ret < 0) return ret;
2471
2518
  }
2472
2519
  }
@@ -2929,12 +2976,20 @@ function gobj_current_state(gobj) {
2929
2976
  function gobj_change_parent(gobj, parent) {
2930
2977
  if (gobj_is_destroying(gobj)) {
2931
2978
  log_error("gobj NULL or DESTROYED");
2932
- return null;
2979
+ return -1;
2933
2980
  }
2934
2981
  if (!(parent instanceof GObj)) {
2935
2982
  log_error(`${gobj_short_name(gobj)}: gobj_change_parent() BAD TYPE of parent`);
2936
2983
  return -1;
2937
2984
  }
2985
+ if (gobj_is_destroying(parent)) {
2986
+ log_error(`${gobj_short_name(gobj)}: gobj_change_parent() parent NULL or DESTROYED`);
2987
+ return -1;
2988
+ }
2989
+ for (let p = parent; p; p = p.parent) if (p === gobj) {
2990
+ log_error(`${gobj_short_name(gobj)}: gobj_change_parent() a gobj cannot be its own ancestor: ${gobj_short_name(parent)}`);
2991
+ return -1;
2992
+ }
2938
2993
  if (gobj.parent && gobj.parent.gclass.gmt.mt_child_removed) gobj.parent.gclass.gmt.mt_child_removed(gobj.parent, gobj);
2939
2994
  if (gobj.parent) {
2940
2995
  _remove_child(gobj.parent, gobj);
@@ -6099,7 +6154,10 @@ function warn_local_storage_once(key, msg) {
6099
6154
  * for ever; a new corrupt entry is a new WARNING. Its text is never
6100
6155
  * shown (JSON.parse() quotes it in its error, and an entry can hold a
6101
6156
  * token). A store that cannot be read (blocked) is warned once per
6102
- * key, and the default is NOT written there. Outside a browser: the
6157
+ * key, and the default is NOT written there; one that cannot be
6158
+ * written (full) is warned once per key too: with `create` it was an
6159
+ * ERROR on every read, the default tried again each time. A key is
6160
+ * warned again after a read that goes well. Outside a browser: the
6103
6161
  * default.
6104
6162
  ********************************************/
6105
6163
  function kw_get_local_storage_value(key, default_value, create = false) {
@@ -6113,6 +6171,7 @@ function kw_get_local_storage_value(key, default_value, create = false) {
6113
6171
  warn_local_storage_once(key, `${where} cannot be read (storage blocked?): the default is used`);
6114
6172
  return default_value;
6115
6173
  }
6174
+ let went_well = true;
6116
6175
  let value = null;
6117
6176
  if (text !== null) try {
6118
6177
  value = JSON.parse(text);
@@ -6121,17 +6180,25 @@ function kw_get_local_storage_value(key, default_value, create = false) {
6121
6180
  try {
6122
6181
  store.removeItem(key);
6123
6182
  log_warning(`${where} does not hold json: removed, the default is used`);
6124
- local_storage_warned.delete(key);
6125
6183
  } catch (e2) {
6184
+ went_well = false;
6126
6185
  warn_local_storage_once(key, `${where} does not hold json and cannot be removed: the default is used`);
6127
6186
  }
6128
- text = void 0;
6129
6187
  }
6130
- if (text !== void 0) local_storage_warned.delete(key);
6131
6188
  if (value === null) {
6132
- if (create) kw_set_local_storage_value(key, default_value);
6133
- return default_value;
6189
+ value = default_value;
6190
+ if (create) {
6191
+ const default_text = json_text_of(default_value);
6192
+ if (default_text === void 0) log_error(`${where}: cannot write the default: a value with no json text`);
6193
+ else try {
6194
+ store.setItem(key, default_text);
6195
+ } catch (e) {
6196
+ went_well = false;
6197
+ warn_local_storage_once(key, `${where} cannot be written (storage full or blocked?): the default is used, not kept`);
6198
+ }
6199
+ }
6134
6200
  }
6201
+ if (went_well) local_storage_warned.delete(key);
6135
6202
  return value;
6136
6203
  }
6137
6204
  /********************************************
@@ -7371,29 +7438,68 @@ function index_in_list(list, elm) {
7371
7438
  return -1;
7372
7439
  }
7373
7440
  /************************************************************
7441
+ * A new uuid v4 (RFC 9562).
7442
+ *
7443
+ * crypto.randomUUID() exists only in a secure context, so a page
7444
+ * served over http without TLS has none; crypto.getRandomValues()
7445
+ * exists there too. With no crypto at all, Math.random(): not fit
7446
+ * for a secret, and a uuid that names a node is not one.
7447
+ ************************************************************/
7448
+ function new_uuid() {
7449
+ const c = typeof window !== "undefined" && window.crypto || globalThis.crypto;
7450
+ if (c && typeof c.randomUUID === "function") return c.randomUUID();
7451
+ const b = /* @__PURE__ */ new Uint8Array(16);
7452
+ if (c && typeof c.getRandomValues === "function") c.getRandomValues(b);
7453
+ else for (let i = 0; i < b.length; i++) b[i] = Math.floor(Math.random() * 256);
7454
+ b[6] = b[6] & 15 | 64;
7455
+ b[8] = b[8] & 63 | 128;
7456
+ const h = Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
7457
+ return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;
7458
+ }
7459
+ /************************************************************
7460
+ * The uuid of this node: ONE for the life of the page.
7374
7461
  *
7462
+ * The one the store keeps ("host_uuid"), made and written there when
7463
+ * there is none. When the store cannot keep it (blocked, full, no
7464
+ * localStorage, outside a browser) the one this page made, warned
7465
+ * once. It goes in the node_uuid attribute of the yuno, the id of the
7466
+ * identity card and the `host` of every message the client sends, so
7467
+ * the first answer is kept and answered again: 7.26.27 made a new
7468
+ * uuid on every call when the store could not keep it, and answered
7469
+ * null without crypto.randomUUID(). A stored value that is not a
7470
+ * uuid (a foreign or corrupt write) is replaced, with a WARNING.
7375
7471
  ************************************************************/
7472
+ var uuid_of_this_page = null;
7473
+ var UUID_SHAPE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
7376
7474
  function node_uuid() {
7377
- let uuid = "";
7475
+ if (uuid_of_this_page) return uuid_of_this_page;
7378
7476
  const store = local_store();
7379
- if (store) {
7477
+ if (!store) {
7478
+ uuid_of_this_page = new_uuid();
7479
+ return uuid_of_this_page;
7480
+ }
7481
+ let uuid = null;
7482
+ try {
7483
+ uuid = store.getItem("host_uuid");
7484
+ } catch (e) {
7485
+ warn_local_storage_once("host_uuid", "node_uuid(): localStorage cannot be read (storage blocked?): a uuid of this page only");
7486
+ uuid_of_this_page = new_uuid();
7487
+ return uuid_of_this_page;
7488
+ }
7489
+ if (uuid && !UUID_SHAPE.test(uuid)) {
7490
+ log_warning("node_uuid(): the stored host_uuid is not a uuid: a new one replaces it");
7491
+ uuid = null;
7492
+ }
7493
+ if (!uuid) {
7494
+ uuid = new_uuid();
7380
7495
  try {
7381
- uuid = store.getItem("host_uuid");
7496
+ store.setItem("host_uuid", uuid);
7382
7497
  } catch (e) {
7383
- uuid = null;
7384
- warn_local_storage_once("host_uuid", "node_uuid(): localStorage cannot be read (storage blocked?): a uuid of this page only");
7385
- }
7386
- if (!uuid && window.crypto?.randomUUID) {
7387
- uuid = window.crypto.randomUUID();
7388
- try {
7389
- store.setItem("host_uuid", uuid);
7390
- } catch (e) {
7391
- warn_local_storage_once("host_uuid", "node_uuid(): localStorage cannot be written (storage full or blocked?): a uuid of this page only");
7392
- }
7498
+ warn_local_storage_once("host_uuid", "node_uuid(): localStorage cannot be written (storage full or blocked?): a uuid of this page only");
7393
7499
  }
7394
- } else if (typeof globalThis !== "undefined" && globalThis.crypto?.randomUUID) uuid = globalThis.crypto.randomUUID();
7395
- else uuid = "00000000-0000-0000-0000-000000000000";
7396
- return uuid;
7500
+ }
7501
+ uuid_of_this_page = uuid;
7502
+ return uuid_of_this_page;
7397
7503
  }
7398
7504
  /************************************************************
7399
7505
  * Build a cleaned name
@@ -8678,7 +8784,7 @@ var attrs_table = [
8678
8784
  SDATA(data_type_t.DTP_STRING, "cert_pem", sdata_flag_t.SDF_PERSIST, "", "SSL server certification, PEM str format"),
8679
8785
  SDATA(data_type_t.DTP_JSON, "extra_info", sdata_flag_t.SDF_RD, "{}", "dict data set by user, added to the identity card msg."),
8680
8786
  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."),
8681
- 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"),
8787
+ 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"),
8682
8788
  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"),
8683
8789
  SDATA(data_type_t.DTP_INTEGER, "timeout_idack", sdata_flag_t.SDF_RD, "5000", "timeout waiting idAck"),
8684
8790
  SDATA(data_type_t.DTP_POINTER, "subscriber", 0, 0, "subscriber of output-events. If null then subscriber is the parent"),