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