@yuneta/gobj-js 7.26.25 → 7.26.27

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.
@@ -980,11 +980,14 @@ var gobjJs = (function(exports) {
980
980
  if (DTP_IS_NUMBER(type)) return typeof value === "number" || typeof value === "boolean";
981
981
  return true;
982
982
  }
983
+ function is_int64_number(value) {
984
+ return value >= -0x8000000000000000 && value < 0x8000000000000000;
985
+ }
983
986
  function shape_of_value(value) {
984
987
  if (is_array(value)) return "an array";
985
988
  if (is_object(value)) return "an object";
986
989
  if (is_string(value)) return REFUSED_TEXT;
987
- if (typeof value === "number") return Number.isInteger(value) && Math.abs(value) <= 0x8000000000000000 ? "an integer" : "a real";
990
+ if (typeof value === "number") return Number.isInteger(value) && is_int64_number(value) ? "an integer" : "a real";
988
991
  if (typeof value === "boolean") return "a boolean";
989
992
  return "null";
990
993
  }
@@ -1005,7 +1008,7 @@ var gobjJs = (function(exports) {
1005
1008
  value: value ? 1 : 0,
1006
1009
  refused: null
1007
1010
  };
1008
- if (!(value >= -0x8000000000000000 && value <= 0x8000000000000000)) return {
1011
+ if (!is_int64_number(value)) return {
1009
1012
  value: void 0,
1010
1013
  refused: "a number out of its range"
1011
1014
  };
@@ -1070,8 +1073,13 @@ var gobjJs = (function(exports) {
1070
1073
  value: n !== 0n,
1071
1074
  refused: null
1072
1075
  };
1076
+ const v = Number(n);
1077
+ if (!is_int64_number(v)) return {
1078
+ value: void 0,
1079
+ refused: "a number out of its range"
1080
+ };
1073
1081
  return {
1074
- value: Number(n),
1082
+ value: v,
1075
1083
  refused: null
1076
1084
  };
1077
1085
  }
@@ -1130,6 +1138,16 @@ var gobjJs = (function(exports) {
1130
1138
  * undefined, or an empty text given to a number).
1131
1139
  ***************************************************************************/
1132
1140
  function command_parameter_from_json(type, value) {
1141
+ try {
1142
+ return parameter_from_json(type, value);
1143
+ } catch (e) {
1144
+ return {
1145
+ value: void 0,
1146
+ refused: NO_JSON_TEXT
1147
+ };
1148
+ }
1149
+ }
1150
+ function parameter_from_json(type, value) {
1133
1151
  if (value === void 0 || value === null) {
1134
1152
  if (DTP_IS_JSON(type) && value === null) return {
1135
1153
  value: null,
@@ -1188,31 +1206,63 @@ var gobjJs = (function(exports) {
1188
1206
  data_type_t.DTP_LIST,
1189
1207
  data_type_t.DTP_DICT
1190
1208
  ];
1191
- var LEAVE_NODE = Symbol("leave");
1192
- function is_json_value(value) {
1193
- const on_path = /* @__PURE__ */ new Set();
1194
- const stack = [value];
1195
- while (stack.length) {
1196
- const v = stack.pop();
1197
- if (v === LEAVE_NODE) {
1198
- on_path.delete(stack.pop());
1199
- continue;
1209
+ function is_plain_object$1(v) {
1210
+ const proto = Object.getPrototypeOf(v);
1211
+ if (proto === Object.prototype || proto === null) return true;
1212
+ if (Object.getPrototypeOf(proto) !== null || !Object.prototype.hasOwnProperty.call(proto, "constructor")) return false;
1213
+ const ctor = proto.constructor;
1214
+ return typeof ctor === "function" && ctor.name === "Object";
1215
+ }
1216
+ function json_scalar_or_container(v) {
1217
+ switch (typeof v) {
1218
+ case "string":
1219
+ case "boolean": return true;
1220
+ case "number": return Number.isFinite(v);
1221
+ case "object": return v === null ? true : null;
1222
+ default: return false;
1223
+ }
1224
+ }
1225
+ function is_json_node(value) {
1226
+ const top = json_scalar_or_container(value);
1227
+ if (top !== null) return top;
1228
+ const values = [value];
1229
+ const levels = [1];
1230
+ while (values.length) {
1231
+ const v = values.pop();
1232
+ const level = levels.pop() + 1;
1233
+ let children;
1234
+ if (Array.isArray(v)) {
1235
+ if (typeof v.toJSON === "function") return false;
1236
+ children = v;
1237
+ } else {
1238
+ if (!is_plain_object$1(v) || typeof v.toJSON === "function") return false;
1239
+ children = [];
1240
+ for (const key of Object.keys(v)) {
1241
+ const x = v[key];
1242
+ if (x === void 0 || typeof x === "function" || typeof x === "symbol") continue;
1243
+ children.push(x);
1244
+ }
1200
1245
  }
1201
- if (typeof v === "bigint") return false;
1202
- if (v === null || typeof v !== "object") continue;
1203
- if (on_path.has(v)) return false;
1204
- const array = Array.isArray(v);
1205
- if (!array) {
1206
- const proto = Object.getPrototypeOf(v);
1207
- if (proto !== Object.prototype && proto !== null) return false;
1246
+ if (children.length > 0 && level > JSON_MAX_DEPTH) return false;
1247
+ for (let i = 0; i < children.length; i++) {
1248
+ const x = children[i];
1249
+ const r = json_scalar_or_container(x);
1250
+ if (r === false) return false;
1251
+ if (r === null) {
1252
+ values.push(x);
1253
+ levels.push(level);
1254
+ }
1208
1255
  }
1209
- on_path.add(v);
1210
- stack.push(v, LEAVE_NODE);
1211
- if (array) for (let i = 0; i < v.length; i++) stack.push(v[i]);
1212
- else for (const key of Object.keys(v)) stack.push(v[key]);
1213
1256
  }
1214
1257
  return true;
1215
1258
  }
1259
+ function is_json_value(value) {
1260
+ try {
1261
+ return is_json_node(value);
1262
+ } catch (e) {
1263
+ return false;
1264
+ }
1265
+ }
1216
1266
  var NO_JSON_TEXT = "a value with no json text";
1217
1267
  function json_text_of(value) {
1218
1268
  if (!is_json_value(value)) return;
@@ -2343,11 +2393,13 @@ var gobjJs = (function(exports) {
2343
2393
  let child;
2344
2394
  if (walk_type & walk_type_t.WALK_LAST2FIRST) for (let i = iter.length; i > 0; i--) {
2345
2395
  child = iter[i - 1];
2396
+ if (gobj_is_destroying(child)) continue;
2346
2397
  let ret = walk_ret(cb_walking(child, user_data, user_data2));
2347
2398
  if (ret < 0) return ret;
2348
2399
  }
2349
2400
  else for (let i = 0; i < iter.length; i++) {
2350
2401
  child = iter[i];
2402
+ if (gobj_is_destroying(child)) continue;
2351
2403
  let ret = walk_ret(cb_walking(child, user_data, user_data2));
2352
2404
  if (ret < 0) return ret;
2353
2405
  }
@@ -2362,18 +2414,21 @@ var gobjJs = (function(exports) {
2362
2414
  * or
2363
2415
  * WALK_LAST2FIRST
2364
2416
  ***************************************************************/
2365
- function rc_walk_by_level(iter, walk_type, cb_walking, user_data, user_data2) {
2417
+ function rc_walk_by_level(iter_, walk_type, cb_walking, user_data, user_data2) {
2418
+ let iter = [...iter_];
2366
2419
  let ret = rc_walk_by_list(iter, walk_type, cb_walking, user_data, user_data2);
2367
2420
  if (ret < 0) return ret;
2368
2421
  let child;
2369
2422
  if (walk_type & walk_type_t.WALK_LAST2FIRST) for (let i = iter.length; i > 0; i--) {
2370
2423
  child = iter[i - 1];
2371
- ret = rc_walk_by_level([...child.dl_children], walk_type, cb_walking, user_data, user_data2);
2424
+ if (gobj_is_destroying(child)) continue;
2425
+ ret = rc_walk_by_level(child.dl_children, walk_type, cb_walking, user_data, user_data2);
2372
2426
  if (ret < 0) return ret;
2373
2427
  }
2374
2428
  else for (let i = 0; i < iter.length; i++) {
2375
2429
  child = iter[i];
2376
- ret = rc_walk_by_level([...child.dl_children], walk_type, cb_walking, user_data, user_data2);
2430
+ if (gobj_is_destroying(child)) continue;
2431
+ ret = rc_walk_by_level(child.dl_children, walk_type, cb_walking, user_data, user_data2);
2377
2432
  if (ret < 0) return ret;
2378
2433
  }
2379
2434
  return ret;
@@ -2393,23 +2448,27 @@ var gobjJs = (function(exports) {
2393
2448
  * or
2394
2449
  * WALK_LAST2FIRST
2395
2450
  ***************************************************************/
2396
- function rc_walk_by_tree(iter, walk_type, cb_walking, user_data, user_data2) {
2397
- if (walk_type & walk_type_t.WALK_BYLEVEL) return rc_walk_by_level(iter, walk_type, cb_walking, user_data, user_data2);
2451
+ function rc_walk_by_tree(iter_, walk_type, cb_walking, user_data, user_data2) {
2452
+ if (walk_type & walk_type_t.WALK_BYLEVEL) return rc_walk_by_level(iter_, walk_type, cb_walking, user_data, user_data2);
2453
+ let iter = [...iter_];
2398
2454
  let ret;
2399
2455
  let child;
2400
2456
  if (walk_type & walk_type_t.WALK_BOTTOM2TOP) for (let i = iter.length; i > 0; i--) {
2401
2457
  child = iter[i - 1];
2402
- ret = rc_walk_by_tree([...child.dl_children], walk_type, cb_walking, user_data, user_data2);
2458
+ if (gobj_is_destroying(child)) continue;
2459
+ ret = rc_walk_by_tree(child.dl_children, walk_type, cb_walking, user_data, user_data2);
2403
2460
  if (ret < 0) return ret;
2461
+ if (gobj_is_destroying(child)) continue;
2404
2462
  ret = walk_ret(cb_walking(child, user_data, user_data2));
2405
2463
  if (ret < 0) return ret;
2406
2464
  }
2407
2465
  else for (let i = 0; i < iter.length; i++) {
2408
2466
  child = iter[i];
2467
+ if (gobj_is_destroying(child)) continue;
2409
2468
  ret = walk_ret(cb_walking(child, user_data, user_data2));
2410
2469
  if (ret < 0) return ret;
2411
- else if (ret === 0) {
2412
- ret = rc_walk_by_tree([...child.dl_children], walk_type, cb_walking, user_data, user_data2);
2470
+ else if (ret === 0 && !gobj_is_destroying(child)) {
2471
+ ret = rc_walk_by_tree(child.dl_children, walk_type, cb_walking, user_data, user_data2);
2413
2472
  if (ret < 0) return ret;
2414
2473
  }
2415
2474
  }
@@ -4726,6 +4785,7 @@ var gobjJs = (function(exports) {
4726
4785
  mark_labels(str, marks);
4727
4786
  }
4728
4787
  var MASK_MAX_DECODINGS = 2;
4788
+ var VIEW_CHUNK = 8192;
4729
4789
  function encoded_ascii_at(str, p) {
4730
4790
  if (p + 3 > str.length || str.charCodeAt(p) !== 37) return -1;
4731
4791
  const hi = hex_digit_value(str.charCodeAt(p + 1));
@@ -4736,28 +4796,30 @@ var gobjJs = (function(exports) {
4736
4796
  }
4737
4797
  function mark_decoded_view(str, marks, decodings) {
4738
4798
  if (decodings >= MASK_MAX_DECODINGS) return;
4739
- const pieces = [];
4740
- let last = 0;
4741
- let p = str.indexOf("%");
4742
- while (p >= 0) {
4743
- const c = encoded_ascii_at(str, p);
4799
+ let first = str.indexOf("%");
4800
+ while (first >= 0 && encoded_ascii_at(str, first) < 0) first = str.indexOf("%", first + 1);
4801
+ if (first < 0) return;
4802
+ const codes = new Uint16Array(str.length - first);
4803
+ let n = 0;
4804
+ for (let p = first; p < str.length; p++) {
4805
+ const code = str.charCodeAt(p);
4806
+ const c = code === 37 ? encoded_ascii_at(str, p) : -1;
4744
4807
  if (c >= 0) {
4745
- pieces.push(str.substring(last, p), String.fromCharCode(c));
4746
- last = p + 3;
4747
- p = str.indexOf("%", last);
4748
- } else p = str.indexOf("%", p + 1);
4749
- }
4750
- if (pieces.length === 0) return;
4751
- pieces.push(str.substring(last));
4752
- const view = pieces.join("");
4808
+ codes[n++] = c;
4809
+ p += 2;
4810
+ } else codes[n++] = code;
4811
+ }
4812
+ const chunks = [str.substring(0, first)];
4813
+ for (let k = 0; k < n; k += VIEW_CHUNK) chunks.push(String.fromCharCode.apply(null, codes.subarray(k, Math.min(n, k + VIEW_CHUNK))));
4814
+ const view = chunks.join("");
4753
4815
  const view_marks = new Uint8Array(view.length);
4754
4816
  mark_inline_rules_of_now(view, view_marks);
4755
4817
  mark_decoded_view(view, view_marks, decodings + 1);
4756
4818
  let q = 0;
4757
4819
  for (let i = 0; i < view.length; i++) {
4758
- const n = encoded_ascii_at(str, q) >= 0 ? 3 : 1;
4759
- if (view_marks[i]) mark_range(marks, q, q + n);
4760
- q += n;
4820
+ const n_from = encoded_ascii_at(str, q) >= 0 ? 3 : 1;
4821
+ if (view_marks[i]) mark_range(marks, q, q + n_from);
4822
+ q += n_from;
4761
4823
  }
4762
4824
  }
4763
4825
  function mark_inline_secrets(str, marks) {
@@ -5991,21 +6053,88 @@ var gobjJs = (function(exports) {
5991
6053
  return kw_clone;
5992
6054
  }
5993
6055
  /********************************************
6056
+ * The browser's localStorage, or null when there is none (outside a
6057
+ * browser).
6058
+ *
6059
+ * With the site's data blocked (Chrome with cookies blocked, a sandboxed
6060
+ * iframe), READING the window.localStorage property already throws a
6061
+ * SecurityError, not only its getItem(): every app reads its locale at
6062
+ * start, so the app did not start. Such a store is answered as one whose
6063
+ * every call throws that error, so each helper fails as it fails for any
6064
+ * store that refuses it: a read gives the default, a write -1.
6065
+ ********************************************/
6066
+ function local_store() {
6067
+ if (typeof window === "undefined") return null;
6068
+ let store;
6069
+ try {
6070
+ store = window.localStorage;
6071
+ } catch (e) {
6072
+ const refuse = () => {
6073
+ throw e;
6074
+ };
6075
+ return {
6076
+ getItem: refuse,
6077
+ setItem: refuse,
6078
+ removeItem: refuse
6079
+ };
6080
+ }
6081
+ return store || null;
6082
+ }
6083
+ /********************************************
6084
+ * A failure of the store that would repeat on every read is warned
6085
+ * once per key, until a read of the key goes well again.
6086
+ ********************************************/
6087
+ var local_storage_warned = /* @__PURE__ */ new Set();
6088
+ function warn_local_storage_once(key, msg) {
6089
+ if (local_storage_warned.has(key)) return;
6090
+ local_storage_warned.add(key);
6091
+ log_warning(msg);
6092
+ }
6093
+ /********************************************
5994
6094
  * Get a local attribute
6095
+ *
6096
+ * The json the entry holds, or `default_value` when there is none
6097
+ * (written there too with `create`). A json null is no value, as
6098
+ * json_absent() in C: the default. An entry that is not json (a
6099
+ * corrupt or foreign write) is a WARNING and is REMOVED, so the
6100
+ * next read finds no entry: it used to console.warn on every read,
6101
+ * for ever; a new corrupt entry is a new WARNING. Its text is never
6102
+ * shown (JSON.parse() quotes it in its error, and an entry can hold a
6103
+ * token). A store that cannot be read (blocked) is warned once per
6104
+ * key, and the default is NOT written there. Outside a browser: the
6105
+ * default.
5995
6106
  ********************************************/
5996
6107
  function kw_get_local_storage_value(key, default_value, create = false) {
5997
- if (typeof window === "undefined" || !window.localStorage) return;
6108
+ const store = local_store();
6109
+ if (!store) return default_value;
6110
+ const where = `kw_get_local_storage_value(): localStorage key "${key}"`;
6111
+ let text;
5998
6112
  try {
5999
- let value = window.localStorage.getItem(key);
6000
- if (value === null) {
6001
- if (create) kw_set_local_storage_value(key, default_value);
6002
- return default_value;
6003
- }
6004
- return JSON.parse(value);
6113
+ text = store.getItem(key);
6005
6114
  } catch (e) {
6006
- window.console.warn(`Error reading localStorage key "${key}":`, e);
6115
+ warn_local_storage_once(key, `${where} cannot be read (storage blocked?): the default is used`);
6007
6116
  return default_value;
6008
6117
  }
6118
+ let value = null;
6119
+ if (text !== null) try {
6120
+ value = JSON.parse(text);
6121
+ } catch (e) {
6122
+ value = null;
6123
+ try {
6124
+ store.removeItem(key);
6125
+ log_warning(`${where} does not hold json: removed, the default is used`);
6126
+ local_storage_warned.delete(key);
6127
+ } catch (e2) {
6128
+ warn_local_storage_once(key, `${where} does not hold json and cannot be removed: the default is used`);
6129
+ }
6130
+ text = void 0;
6131
+ }
6132
+ if (text !== void 0) local_storage_warned.delete(key);
6133
+ if (value === null) {
6134
+ if (create) kw_set_local_storage_value(key, default_value);
6135
+ return default_value;
6136
+ }
6137
+ return value;
6009
6138
  }
6010
6139
  /********************************************
6011
6140
  * Save a local attribute
@@ -6016,15 +6145,26 @@ var gobjJs = (function(exports) {
6016
6145
  * reported success on a store that had rejected the write: a full or blocked
6017
6146
  * localStorage (quota, private mode) silently discarded the change while the
6018
6147
  * UI showed it as saved.
6148
+ *
6149
+ * A value with no json text is refused with an ERROR, as a typed writer
6150
+ * refuses it (json_text_of()): a function was stored as the text
6151
+ * "undefined", and a NaN, a Date or a Map as a text that reads back as
6152
+ * another value. Checked in Node.js too, where there is no store.
6019
6153
  ********************************************/
6020
6154
  function kw_set_local_storage_value(key, value) {
6021
- if (typeof window === "undefined" || !window.localStorage) return 0;
6022
- if (!key || value === void 0) {
6023
- log_error(`kw_set_local_storage_value(): invalid key or value: key=${key}`);
6155
+ if (!key) {
6156
+ log_error(`kw_set_local_storage_value(): invalid key: key=${key}`);
6024
6157
  return -1;
6025
6158
  }
6159
+ const text = json_text_of(value);
6160
+ if (text === void 0) {
6161
+ log_error(`kw_set_local_storage_value(): cannot save "${key}": a value with no json text`);
6162
+ return -1;
6163
+ }
6164
+ const store = local_store();
6165
+ if (!store) return 0;
6026
6166
  try {
6027
- window.localStorage.setItem(key, JSON.stringify(value));
6167
+ store.setItem(key, text);
6028
6168
  return 0;
6029
6169
  } catch (e) {
6030
6170
  log_error(`kw_set_local_storage_value(): cannot save "${key}" (storage full or blocked?): ${e && e.message ? e.message : e}`);
@@ -6035,15 +6175,16 @@ var gobjJs = (function(exports) {
6035
6175
  * Remove local attribute
6036
6176
  ********************************************/
6037
6177
  function kw_remove_local_storage_value(key) {
6038
- if (typeof window === "undefined" || !window.localStorage) return;
6178
+ const store = local_store();
6179
+ if (!store) return;
6039
6180
  if (!key) {
6040
- window.console.warn(`Invalid key for localStorage removal: ${key}`);
6181
+ log_error(`kw_remove_local_storage_value(): invalid key: key=${key}`);
6041
6182
  return;
6042
6183
  }
6043
6184
  try {
6044
- window.localStorage.removeItem(key);
6185
+ store.removeItem(key);
6045
6186
  } catch (e) {
6046
- window.console.warn(`Error removing localStorage key "${key}":`, e);
6187
+ log_warning(`kw_remove_local_storage_value(): cannot remove "${key}" (storage blocked?): ${e && e.message ? e.message : e}`);
6047
6188
  }
6048
6189
  }
6049
6190
  /*************************************************************
@@ -7236,11 +7377,21 @@ var gobjJs = (function(exports) {
7236
7377
  ************************************************************/
7237
7378
  function node_uuid() {
7238
7379
  let uuid = "";
7239
- if (typeof window !== "undefined" && window.localStorage) {
7240
- uuid = window.localStorage.getItem("host_uuid");
7380
+ const store = local_store();
7381
+ if (store) {
7382
+ try {
7383
+ uuid = store.getItem("host_uuid");
7384
+ } catch (e) {
7385
+ uuid = null;
7386
+ warn_local_storage_once("host_uuid", "node_uuid(): localStorage cannot be read (storage blocked?): a uuid of this page only");
7387
+ }
7241
7388
  if (!uuid && window.crypto?.randomUUID) {
7242
7389
  uuid = window.crypto.randomUUID();
7243
- window.localStorage.setItem("host_uuid", uuid);
7390
+ try {
7391
+ store.setItem("host_uuid", uuid);
7392
+ } catch (e) {
7393
+ warn_local_storage_once("host_uuid", "node_uuid(): localStorage cannot be written (storage full or blocked?): a uuid of this page only");
7394
+ }
7244
7395
  }
7245
7396
  } else if (typeof globalThis !== "undefined" && globalThis.crypto?.randomUUID) uuid = globalThis.crypto.randomUUID();
7246
7397
  else uuid = "00000000-0000-0000-0000-000000000000";
@@ -8529,7 +8680,7 @@ var gobjJs = (function(exports) {
8529
8680
  SDATA(data_type_t.DTP_STRING, "cert_pem", sdata_flag_t.SDF_PERSIST, "", "SSL server certification, PEM str format"),
8530
8681
  SDATA(data_type_t.DTP_JSON, "extra_info", sdata_flag_t.SDF_RD, "{}", "dict data set by user, added to the identity card msg."),
8531
8682
  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."),
8532
- 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"),
8683
+ 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"),
8533
8684
  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"),
8534
8685
  SDATA(data_type_t.DTP_INTEGER, "timeout_idack", sdata_flag_t.SDF_RD, "5000", "timeout waiting idAck"),
8535
8686
  SDATA(data_type_t.DTP_POINTER, "subscriber", 0, 0, "subscriber of output-events. If null then subscriber is the parent"),
@@ -8553,7 +8704,9 @@ var gobjJs = (function(exports) {
8553
8704
  websocket: null,
8554
8705
  inside_on_open: false,
8555
8706
  retry_ms: 0,
8556
- retry_ms_before_session: 0
8707
+ retry_ms_before_session: 0,
8708
+ session_since: 0,
8709
+ retry_floor_warned: false
8557
8710
  };
8558
8711
  var __gclass__ = null;
8559
8712
  /******************************
@@ -8812,15 +8965,22 @@ var gobjJs = (function(exports) {
8812
8965
  * protocol error, throws there), so it is 4002 -- and the server
8813
8966
  * echoes it, so EV_ON_CLOSE carries it.
8814
8967
  *
8815
- * And a session the server broke was not a session reached: the
8816
- * backoff the ack reset is put back, or a server that breaks every
8968
+ * And a session the server broke at once was not a session reached:
8969
+ * the backoff the ack reset is put back, or a server that breaks every
8817
8970
  * session is reconnected at the first delay for ever, with an
8818
- * EV_ON_OPEN and an EV_ON_CLOSE in the app per round.
8971
+ * EV_ON_OPEN and an EV_ON_CLOSE in the app per round. A session that
8972
+ * outlived the longest delay of the backoff (`timeout_retry_max`) was
8973
+ * served: its fault starts the backoff over, as a clean end does, or
8974
+ * one foreign message hours later is waited for with a delay escalated
8975
+ * by failures long gone.
8819
8976
  ***************************************************************/
8820
8977
  var CLOSE_PEER_FAULT = 4002;
8821
8978
  function close_for_peer_fault(gobj, reason) {
8822
8979
  let priv = gobj.priv;
8823
- if (gobj_current_state(gobj) === "ST_SESSION") priv.retry_ms = priv.retry_ms_before_session;
8980
+ if (gobj_current_state(gobj) === "ST_SESSION") {
8981
+ if (performance.now() - priv.session_since < retry_bounds(gobj).max) priv.retry_ms = priv.retry_ms_before_session;
8982
+ priv.retry_ms_before_session = 0;
8983
+ }
8824
8984
  close_websocket(gobj, CLOSE_PEER_FAULT, reason);
8825
8985
  }
8826
8986
  /***************************************************************
@@ -9090,6 +9250,32 @@ var gobjJs = (function(exports) {
9090
9250
  return 0;
9091
9251
  }
9092
9252
  /***************************************************************
9253
+ * The first delay of the backoff and its cap.
9254
+ *
9255
+ * A first delay below RETRY_FLOOR_MS is raised to it, with a WARNING
9256
+ * once: `timeout_retry` 0 never left 0 (0 doubled is 0), so a server
9257
+ * that closed before the ack was reconnected in a loop with no wait,
9258
+ * hundreds of connections a second.
9259
+ ***************************************************************/
9260
+ var RETRY_FLOOR_MS = 1e3;
9261
+ function retry_bounds(gobj) {
9262
+ let priv = gobj.priv;
9263
+ let base = gobj_read_integer_attr(gobj, "timeout_retry");
9264
+ let max = gobj_read_integer_attr(gobj, "timeout_retry_max");
9265
+ if (!(base >= RETRY_FLOOR_MS)) {
9266
+ if (!priv.retry_floor_warned) {
9267
+ priv.retry_floor_warned = true;
9268
+ log_warning(`${gobj_short_name(gobj)}: timeout_retry ${base} ms is below ${RETRY_FLOOR_MS} ms: ${RETRY_FLOOR_MS} ms is used`);
9269
+ }
9270
+ base = RETRY_FLOOR_MS;
9271
+ }
9272
+ if (!(max >= base)) max = base;
9273
+ return {
9274
+ base,
9275
+ max
9276
+ };
9277
+ }
9278
+ /***************************************************************
9093
9279
  * How long to wait before the next reconnect attempt.
9094
9280
  *
9095
9281
  * The delay used to be a constant (`timeout_retry`, 5s), for ever. A
@@ -9110,9 +9296,7 @@ var gobjJs = (function(exports) {
9110
9296
  ***************************************************************/
9111
9297
  function next_retry(gobj) {
9112
9298
  let priv = gobj.priv;
9113
- let base = gobj_read_integer_attr(gobj, "timeout_retry");
9114
- let max = gobj_read_integer_attr(gobj, "timeout_retry_max");
9115
- if (max < base) max = base;
9299
+ const { base, max } = retry_bounds(gobj);
9116
9300
  priv.retry_ms = priv.retry_ms ? Math.min(priv.retry_ms * 2, max) : base;
9117
9301
  let jitter = priv.retry_ms * .2;
9118
9302
  let ms = priv.retry_ms - jitter + Math.random() * 2 * jitter;
@@ -9158,6 +9342,7 @@ var gobjJs = (function(exports) {
9158
9342
  priv.inside_on_open = true;
9159
9343
  priv.retry_ms_before_session = priv.retry_ms;
9160
9344
  priv.retry_ms = 0;
9345
+ priv.session_since = performance.now();
9161
9346
  if (!priv.inform_on_close) {
9162
9347
  priv.inform_on_close = true;
9163
9348
  gobj_publish_event(gobj, "EV_ON_OPEN", {
@@ -9255,16 +9440,30 @@ var gobjJs = (function(exports) {
9255
9440
  return 0;
9256
9441
  }
9257
9442
  /***************************************************************************
9443
+ * The server asking this client for stats or a command. The C client
9444
+ * serves them (gobj_stats() / gobj_command() of the service named, and
9445
+ * an answer); a browser client does not run what its server asks, so it
9446
+ * is refused: a WARNING naming what was asked, masked (a command line
9447
+ * can carry a secret), and nothing else -- the protocol allows the ask,
9448
+ * so the session goes on. Up to 7.26.25 it was dropped without a trace.
9449
+ ***************************************************************************/
9450
+ function refuse_asked_by_server(gobj, event, kw, key) {
9451
+ const asked = kw && typeof kw[key] === "string" ? kw[key] : "";
9452
+ const service = kw && typeof kw.service === "string" ? kw.service : "";
9453
+ log_warning(`${gobj_short_name(gobj)}: ${event} not served by this client, DROPPED: ${key.replace(/_/g, "")} '${peer_text_shown(asked, PEER_NAME_SHOWN)}', service '${peer_text_shown(service, PEER_NAME_SHOWN)}'`);
9454
+ return -1;
9455
+ }
9456
+ /***************************************************************************
9258
9457
  * remote asking for stats
9259
9458
  ***************************************************************************/
9260
9459
  function ac_mt_stats(gobj, event, kw, src) {
9261
- return 0;
9460
+ return refuse_asked_by_server(gobj, event, kw, "__stats__");
9262
9461
  }
9263
9462
  /***************************************************************************
9264
9463
  * remote asking for command
9265
9464
  ***************************************************************************/
9266
9465
  function ac_mt_command(gobj, event, kw, src) {
9267
- return 0;
9466
+ return refuse_asked_by_server(gobj, event, kw, "__command__");
9268
9467
  }
9269
9468
  /***************************
9270
9469
  * FSM
@@ -9595,6 +9794,7 @@ var gobjJs = (function(exports) {
9595
9794
  exports.json_object_update_existing = json_object_update_existing;
9596
9795
  exports.json_object_update_missing = json_object_update_missing;
9597
9796
  exports.json_size = json_size;
9797
+ exports.json_text_of = json_text_of;
9598
9798
  exports.jwt2json = jwt2json;
9599
9799
  exports.jwtDecode = jwtDecode;
9600
9800
  exports.kw_clone_by_keys = kw_clone_by_keys;