node-opcua-address-space 2.187.12 → 2.187.16
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/api/helpers/multiform_func.ts +18 -4
- package/api/pseudo_session.ts +1 -1
- package/dist/api/helpers/multiform_func.d.ts +7 -1
- package/dist/api/helpers/multiform_func.d.ts.map +1 -1
- package/dist/api/helpers/multiform_func.js +10 -4
- package/dist/api/helpers/multiform_func.js.map +1 -1
- package/dist/api/pseudo_session.js +1 -1
- package/dist/api/pseudo_session.js.map +1 -1
- package/dist/impl/address_space.d.ts +1 -1
- package/dist/impl/address_space.d.ts.map +1 -1
- package/dist/impl/address_space.js +12 -2
- package/dist/impl/address_space.js.map +1 -1
- package/dist/impl/ua_variable_impl.d.ts +13 -2
- package/dist/impl/ua_variable_impl.d.ts.map +1 -1
- package/dist/impl/ua_variable_impl.js +124 -44
- package/dist/impl/ua_variable_impl.js.map +1 -1
- package/dist/tsconfig_base.tsbuildinfo +1 -1
- package/impl/address_space.ts +13 -2
- package/impl/ua_variable_impl.ts +281 -142
- package/package.json +8 -8
|
@@ -278,7 +278,7 @@ export class UAVariableImpl extends BaseNodeImpl {
|
|
|
278
278
|
// Table 51: no encoding can be applied to a non-Structure value
|
|
279
279
|
return makeNowDataValue({ statusCode: StatusCodes.BadDataEncodingInvalid });
|
|
280
280
|
}
|
|
281
|
-
if (this._timestamped_get_func) {
|
|
281
|
+
if (this._timestamped_get_func && !this._getterIsAsync) {
|
|
282
282
|
if (this._timestamped_get_func.length === 0) {
|
|
283
283
|
const dataValueOrPromise = this._timestamped_get_func();
|
|
284
284
|
if (!Object.hasOwn(dataValueOrPromise.constructor.prototype, "then")) {
|
|
@@ -350,7 +350,7 @@ export class UAVariableImpl extends BaseNodeImpl {
|
|
|
350
350
|
const dataTypeNode = this.addressSpace.findDataType(this.dataType);
|
|
351
351
|
return dataTypeNode._getEnumerationInfo();
|
|
352
352
|
}
|
|
353
|
-
asyncRefresh(oldestDate, callback) {
|
|
353
|
+
asyncRefresh(oldestDate, callback, context) {
|
|
354
354
|
// asyncRefresh is always invoked with a callback at runtime: the public overload without
|
|
355
355
|
// a callback is only reachable through the withCallback() wrapper installed below, which
|
|
356
356
|
// synthesizes one before calling into this implementation.
|
|
@@ -409,7 +409,7 @@ export class UAVariableImpl extends BaseNodeImpl {
|
|
|
409
409
|
this._internal_set_dataValue(refreshed, null);
|
|
410
410
|
}
|
|
411
411
|
cb(err, this.$dataValue);
|
|
412
|
-
});
|
|
412
|
+
}, context);
|
|
413
413
|
answeredAtOnce = false;
|
|
414
414
|
}
|
|
415
415
|
catch (err) {
|
|
@@ -729,6 +729,11 @@ export class UAVariableImpl extends BaseNodeImpl {
|
|
|
729
729
|
assert(write_func);
|
|
730
730
|
write_func.call(this, dataValue, (err, statusCode1) => {
|
|
731
731
|
if (!err) {
|
|
732
|
+
// a write the setter answers Bad did not happen: the stored value stays as it was
|
|
733
|
+
if (statusCode1?.isNotGood()) {
|
|
734
|
+
callback?.(null, statusCode1);
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
732
737
|
dataValue && this.verifyVariantCompatibility(dataValue.value);
|
|
733
738
|
if (indexRange && !indexRange.isEmpty()) {
|
|
734
739
|
if (!indexRange.isValid()) {
|
|
@@ -791,7 +796,7 @@ export class UAVariableImpl extends BaseNodeImpl {
|
|
|
791
796
|
}
|
|
792
797
|
}
|
|
793
798
|
callback?.(err || null, statusCode1);
|
|
794
|
-
});
|
|
799
|
+
}, context, indexRange);
|
|
795
800
|
return undefined;
|
|
796
801
|
}
|
|
797
802
|
writeAttribute(context, writeValueOptions, callback) {
|
|
@@ -1005,6 +1010,7 @@ export class UAVariableImpl extends BaseNodeImpl {
|
|
|
1005
1010
|
this._set_func = null;
|
|
1006
1011
|
this.refreshFunc = undefined;
|
|
1007
1012
|
this._refreshFuncWrapsGetter = false;
|
|
1013
|
+
this._getterIsAsync = false;
|
|
1008
1014
|
this._historyRead = UAVariableImpl.prototype._historyRead;
|
|
1009
1015
|
}
|
|
1010
1016
|
options = options || {};
|
|
@@ -1034,37 +1040,26 @@ export class UAVariableImpl extends BaseNodeImpl {
|
|
|
1034
1040
|
this._readValueAsync(context, (err, dataValue) => (err ? reject(err) : resolve(dataValue)));
|
|
1035
1041
|
});
|
|
1036
1042
|
}
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
return;
|
|
1044
|
-
}
|
|
1045
|
-
if (this.isDisposed()) {
|
|
1046
|
-
callback?.(null, new DataValue({ statusCode: StatusCodes.BadNodeIdUnknown }));
|
|
1047
|
-
return;
|
|
1048
|
-
}
|
|
1049
|
-
this.__waiting_callback = callback;
|
|
1050
|
-
// runs once per sample of every monitored item, so nothing here allocates a closure or
|
|
1051
|
-
// binds a function: the denial (if any) is a StatusCode, and the callback that releases
|
|
1052
|
-
// the waiting readers is made once per variable
|
|
1053
|
-
let denied = null;
|
|
1043
|
+
/**
|
|
1044
|
+
* why this context may not read the Value, or null when it may: the gates of readValue(),
|
|
1045
|
+
* applied to every read of readValueAsync, including one that joins a refresh in flight.
|
|
1046
|
+
* Runs once per sample of every monitored item: it allocates nothing
|
|
1047
|
+
*/
|
|
1048
|
+
_readDenial(context) {
|
|
1054
1049
|
if (context.isAccessRestricted(this)) {
|
|
1055
1050
|
// same gate as readValue(): a variable bound with a simple get()
|
|
1056
1051
|
// also gets a refreshFunc, sending every read down the asyncRefresh
|
|
1057
|
-
// branch
|
|
1052
|
+
// branch of _readValueAsync - which calls the getter directly and never passes
|
|
1058
1053
|
// through readValue(). Without this check here, a node hardened
|
|
1059
1054
|
// with AccessRestrictions(EncryptionRequired) answered its value
|
|
1060
1055
|
// over an unencrypted channel.
|
|
1061
|
-
|
|
1056
|
+
return StatusCodes.BadSecurityModeInsufficient;
|
|
1062
1057
|
}
|
|
1063
1058
|
else if (!this.isReadable(context)) {
|
|
1064
|
-
|
|
1059
|
+
return StatusCodes.BadNotReadable;
|
|
1065
1060
|
}
|
|
1066
1061
|
else if (!this.checkPermissionPrivate(context, PermissionType.Read)) {
|
|
1067
|
-
|
|
1062
|
+
return StatusCodes.BadUserAccessDenied;
|
|
1068
1063
|
}
|
|
1069
1064
|
else if (
|
|
1070
1065
|
// as in readValue(): the Read permission has just passed, only the access level is
|
|
@@ -1072,8 +1067,33 @@ export class UAVariableImpl extends BaseNodeImpl {
|
|
|
1072
1067
|
this.isUserReadable === UAVariableImpl.prototype.isUserReadable
|
|
1073
1068
|
? !this.checkAccessLevelPrivate(context, AccessLevelFlag.CurrentRead)
|
|
1074
1069
|
: !this.isUserReadable(context)) {
|
|
1075
|
-
|
|
1070
|
+
return StatusCodes.BadNotReadable;
|
|
1071
|
+
}
|
|
1072
|
+
return null;
|
|
1073
|
+
}
|
|
1074
|
+
_readValueAsync(context, callback, readAt) {
|
|
1075
|
+
context = context || SessionContext.defaultContext;
|
|
1076
|
+
if (this.__waiting_callback) {
|
|
1077
|
+
// a refresh is in progress: its value answers this read too, once this read's own
|
|
1078
|
+
// access checks have passed (they depend on its context, not on the first reader's)
|
|
1079
|
+
const joinerDenied = this._readDenial(context);
|
|
1080
|
+
if (joinerDenied) {
|
|
1081
|
+
callback(null, makeNowDataValue({ statusCode: joinerDenied }));
|
|
1082
|
+
return;
|
|
1083
|
+
}
|
|
1084
|
+
this.__more_waiting_callbacks ??= [];
|
|
1085
|
+
this.__more_waiting_callbacks.push(callback);
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
if (this.isDisposed()) {
|
|
1089
|
+
callback?.(null, new DataValue({ statusCode: StatusCodes.BadNodeIdUnknown }));
|
|
1090
|
+
return;
|
|
1076
1091
|
}
|
|
1092
|
+
this.__waiting_callback = callback;
|
|
1093
|
+
// runs once per sample of every monitored item, so nothing here allocates a closure or
|
|
1094
|
+
// binds a function: the denial (if any) is a StatusCode, and the callback that releases
|
|
1095
|
+
// the waiting readers is made once per variable
|
|
1096
|
+
const denied = this._readDenial(context);
|
|
1077
1097
|
this.__satisfy_waiting_callbacks ??= (err, dataValue) => {
|
|
1078
1098
|
// now call all pending callbacks, in the order of the reads
|
|
1079
1099
|
const first = this.__waiting_callback;
|
|
@@ -1099,10 +1119,10 @@ export class UAVariableImpl extends BaseNodeImpl {
|
|
|
1099
1119
|
this.__sharedReadClock = readAt !== undefined;
|
|
1100
1120
|
if (this.asyncRefresh === wrappedAsyncRefresh) {
|
|
1101
1121
|
// the thenified wrapper adds nothing when a callback is given
|
|
1102
|
-
asyncRefreshImpl.call(this, clock, satisfy_callbacks);
|
|
1122
|
+
asyncRefreshImpl.call(this, clock, satisfy_callbacks, context);
|
|
1103
1123
|
}
|
|
1104
1124
|
else {
|
|
1105
|
-
this.asyncRefresh(clock, satisfy_callbacks);
|
|
1125
|
+
this.asyncRefresh(clock, satisfy_callbacks, context);
|
|
1106
1126
|
}
|
|
1107
1127
|
// consumed by asyncRefresh on entry; cleared here for an overridden one that never reads it
|
|
1108
1128
|
this.__sharedReadClock = false;
|
|
@@ -1711,7 +1731,17 @@ import { withCallback } from "thenify-ex";
|
|
|
1711
1731
|
// readValueAsync calls the unwrapped asyncRefresh directly: binding the thenified wrapper on
|
|
1712
1732
|
// every sample cost more than the refresh itself
|
|
1713
1733
|
const asyncRefreshImpl = UAVariableImpl.prototype.asyncRefresh;
|
|
1714
|
-
|
|
1734
|
+
// the promise form when no callback is given, as withCallback() does: written out because that
|
|
1735
|
+
// wrapper takes the last argument for the callback, and the context comes after it
|
|
1736
|
+
UAVariableImpl.prototype.asyncRefresh = function (oldestDate, callback, context) {
|
|
1737
|
+
if (typeof callback === "function") {
|
|
1738
|
+
asyncRefreshImpl.call(this, oldestDate, callback, context);
|
|
1739
|
+
return undefined;
|
|
1740
|
+
}
|
|
1741
|
+
return new Promise((resolve, reject) => {
|
|
1742
|
+
asyncRefreshImpl.call(this, oldestDate, (err, dataValue) => (err ? reject(err) : resolve(dataValue)), context);
|
|
1743
|
+
});
|
|
1744
|
+
};
|
|
1715
1745
|
const wrappedAsyncRefresh = UAVariableImpl.prototype.asyncRefresh;
|
|
1716
1746
|
UAVariableImpl.prototype.writeValue = withCallback(UAVariableImpl.prototype.writeValue);
|
|
1717
1747
|
UAVariableImpl.prototype.writeAttribute = withCallback(UAVariableImpl.prototype.writeAttribute);
|
|
@@ -1822,7 +1852,8 @@ function turn_sync_to_async(f, numberOfArgs) {
|
|
|
1822
1852
|
};
|
|
1823
1853
|
}
|
|
1824
1854
|
else {
|
|
1825
|
-
|
|
1855
|
+
// a callback-style function; it may declare the trailing context and indexRange parameters
|
|
1856
|
+
assert(f.length >= numberOfArgs + 1 && f.length <= numberOfArgs + 3);
|
|
1826
1857
|
return f;
|
|
1827
1858
|
}
|
|
1828
1859
|
}
|
|
@@ -1901,14 +1932,16 @@ function _Variable_bind_with_timestamped_get(options) {
|
|
|
1901
1932
|
"\n value_check.constructor.name ", dataValue_verify ? dataValue_verify.constructor.name : "null");
|
|
1902
1933
|
throw new Error(" Bind variable error: " + " the timestamped_get function must return a DataValue");
|
|
1903
1934
|
}
|
|
1935
|
+
this._getterIsAsync = isThenable(dataValue_verify);
|
|
1904
1936
|
_Variable_bind_with_async_refresh.call(this, { refreshFunc: async_refresh_func });
|
|
1905
1937
|
}
|
|
1906
|
-
else if (options.timestamped_get.length === 1) {
|
|
1938
|
+
else if (options.timestamped_get.length === 1 || options.timestamped_get.length === 2) {
|
|
1939
|
+
// callback style: (callback) or (callback, context)
|
|
1907
1940
|
_Variable_bind_with_async_refresh.call(this, { refreshFunc: options.timestamped_get });
|
|
1908
1941
|
}
|
|
1909
1942
|
else {
|
|
1910
|
-
errorLog("timestamped_get has a invalid number of argument , should be 0 or
|
|
1911
|
-
throw new Error("timestamped_get has a invalid number of argument , should be 0 or
|
|
1943
|
+
errorLog("timestamped_get has a invalid number of argument , should be 0, 1 or 2 ");
|
|
1944
|
+
throw new Error("timestamped_get has a invalid number of argument , should be 0, 1 or 2 ");
|
|
1912
1945
|
}
|
|
1913
1946
|
}
|
|
1914
1947
|
// variation 1
|
|
@@ -1957,8 +1990,9 @@ function _Variable_bind_with_simple_set(options) {
|
|
|
1957
1990
|
//
|
|
1958
1991
|
const set_func = turn_sync_to_async(options.set, 1);
|
|
1959
1992
|
this._set_func = set_func;
|
|
1960
|
-
assert(set_func.length
|
|
1961
|
-
|
|
1993
|
+
assert(set_func.length >= 2, " set function must have 2 arguments ( variant, callback)");
|
|
1994
|
+
// the trailing arguments (context, indexRange) are a rest parameter so that the length stays 2
|
|
1995
|
+
this._timestamped_set_func = (timestamped_value, callback, ...extra) => {
|
|
1962
1996
|
assert(timestamped_value instanceof DataValue);
|
|
1963
1997
|
set_func.call(this, timestamped_value.value, (err, statusCode) => {
|
|
1964
1998
|
// c8 ignore next
|
|
@@ -1967,30 +2001,45 @@ function _Variable_bind_with_simple_set(options) {
|
|
|
1967
2001
|
errorLog(chalk.yellow("StatusCode.Good is assumed"));
|
|
1968
2002
|
return callback(err, StatusCodes.Good, timestamped_value);
|
|
1969
2003
|
}
|
|
1970
|
-
if (statusCode?.isNotGood()) {
|
|
1971
|
-
// record the value but still record the statusCode !
|
|
1972
|
-
timestamped_value.statusCode = statusCode;
|
|
1973
|
-
}
|
|
1974
2004
|
callback(err, statusCode ?? StatusCodes.BadInternalError, timestamped_value);
|
|
1975
|
-
});
|
|
2005
|
+
}, ...extra);
|
|
1976
2006
|
};
|
|
1977
2007
|
}
|
|
1978
2008
|
function _Variable_bind_with_timestamped_set(options) {
|
|
1979
|
-
assert(options.timestamped_set.length
|
|
2009
|
+
assert(options.timestamped_set.length >= 1 && options.timestamped_set.length <= 4, "timestamped_set must have 2 parameters timestamped_set: function(dataValue,callback){} or one paramater timestamped_set: function(dataValue): Promise<StatusCode>{}");
|
|
1980
2010
|
assert(!options.set, "should not specify set when timestamped_set_func exists ");
|
|
1981
2011
|
// the callback form of a setter is a stateless wrapper around it (it takes the variable as
|
|
1982
2012
|
// `this`): one per setter function, not one per variable. Every writable variable without a
|
|
1983
2013
|
// setter of its own binds the same default, and a closure per variable was 110 bytes each
|
|
1984
2014
|
let setter = callbackSetters.get(options.timestamped_set);
|
|
1985
2015
|
if (!setter) {
|
|
1986
|
-
setter = convertToCallbackFunction1(options.timestamped_set);
|
|
2016
|
+
setter = convertToCallbackFunction1(options.timestamped_set, 2);
|
|
1987
2017
|
callbackSetters.set(options.timestamped_set, setter);
|
|
1988
2018
|
}
|
|
1989
2019
|
this._timestamped_set_func = setter;
|
|
1990
2020
|
}
|
|
1991
2021
|
/** the callback form of each setter function met so far, see _Variable_bind_with_timestamped_set */
|
|
1992
2022
|
const callbackSetters = new WeakMap();
|
|
2023
|
+
/** an asynchronous setter that is told who writes (`timestamped_set_ctx`) */
|
|
2024
|
+
function _Variable_bind_with_context_setter(setter) {
|
|
2025
|
+
// the context and the index range come after the callback, in a rest parameter: the length stays 2
|
|
2026
|
+
this._timestamped_set_func = function (dataValue, callback, ...extra) {
|
|
2027
|
+
const [context, indexRange] = extra;
|
|
2028
|
+
let promise;
|
|
2029
|
+
try {
|
|
2030
|
+
promise = setter.call(this, dataValue, { context, indexRange });
|
|
2031
|
+
}
|
|
2032
|
+
catch (err) {
|
|
2033
|
+
callback(err);
|
|
2034
|
+
return;
|
|
2035
|
+
}
|
|
2036
|
+
Promise.resolve(promise).then((statusCode) => callback(null, statusCode), (err) => callback(err));
|
|
2037
|
+
};
|
|
2038
|
+
}
|
|
1993
2039
|
function bind_setter(options) {
|
|
2040
|
+
if (options.timestamped_set && options.timestamped_set_ctx) {
|
|
2041
|
+
throw new Error("bind_setter : options should not specify both timestamped_set and timestamped_set_ctx ");
|
|
2042
|
+
}
|
|
1994
2043
|
if (options.set && typeof options.set === "function") {
|
|
1995
2044
|
// variation 1
|
|
1996
2045
|
if (options.timestamped_set === undefined) {
|
|
@@ -2008,8 +2057,13 @@ function bind_setter(options) {
|
|
|
2008
2057
|
timestamped_set: options.timestamped_set
|
|
2009
2058
|
});
|
|
2010
2059
|
}
|
|
2011
|
-
else if (typeof options.
|
|
2012
|
-
//
|
|
2060
|
+
else if (typeof options.timestamped_set_ctx === "function") {
|
|
2061
|
+
// variation 5: the setter is told who writes
|
|
2062
|
+
assert(typeof options.timestamped_get === "function" || typeof options.timestamped_get_ctx === "function", "timestamped_set_ctx must be used with timestamped_get or timestamped_get_ctx");
|
|
2063
|
+
_Variable_bind_with_context_setter.call(this, options.timestamped_set_ctx);
|
|
2064
|
+
}
|
|
2065
|
+
else if (typeof options.timestamped_get === "function" || typeof options.timestamped_get_ctx === "function") {
|
|
2066
|
+
// a timestamped getter is specified but no setter
|
|
2013
2067
|
// => Value is read-only
|
|
2014
2068
|
_Variable_bind_with_timestamped_set.call(this, {
|
|
2015
2069
|
set: undefined,
|
|
@@ -2023,6 +2077,28 @@ function bind_setter(options) {
|
|
|
2023
2077
|
});
|
|
2024
2078
|
}
|
|
2025
2079
|
}
|
|
2080
|
+
/**
|
|
2081
|
+
* variation 5: an asynchronous getter that is told who reads (`timestamped_get_ctx`). It is run by
|
|
2082
|
+
* the refresh only, like any asynchronous getter, with the session context of the read.
|
|
2083
|
+
*/
|
|
2084
|
+
function _Variable_bind_with_context_getter(getter) {
|
|
2085
|
+
_Variable_bind_with_async_refresh.call(this, {
|
|
2086
|
+
refreshFunc: (callback, context) => {
|
|
2087
|
+
let promise;
|
|
2088
|
+
try {
|
|
2089
|
+
promise = getter.call(this, { context });
|
|
2090
|
+
}
|
|
2091
|
+
catch (err) {
|
|
2092
|
+
callback(err);
|
|
2093
|
+
return;
|
|
2094
|
+
}
|
|
2095
|
+
Promise.resolve(promise).then((dataValue) => callback(null, dataValue), (err) => {
|
|
2096
|
+
errorLog("timestamped_get_ctx error: Variable is ", this.nodeId.toString(), this.browseName.toString());
|
|
2097
|
+
callback(err);
|
|
2098
|
+
});
|
|
2099
|
+
}
|
|
2100
|
+
});
|
|
2101
|
+
}
|
|
2026
2102
|
function bind_getter(options) {
|
|
2027
2103
|
if (typeof options.get === "function") {
|
|
2028
2104
|
// variation 1
|
|
@@ -2035,6 +2111,10 @@ function bind_getter(options) {
|
|
|
2035
2111
|
timestamped_get: options.timestamped_get
|
|
2036
2112
|
});
|
|
2037
2113
|
}
|
|
2114
|
+
else if (typeof options.timestamped_get_ctx === "function") {
|
|
2115
|
+
// variation 5
|
|
2116
|
+
_Variable_bind_with_context_getter.call(this, options.timestamped_get_ctx);
|
|
2117
|
+
}
|
|
2038
2118
|
else if (typeof options.refreshFunc === "function") {
|
|
2039
2119
|
// variation 3
|
|
2040
2120
|
if (options.get !== undefined || options.timestamped_get !== undefined) {
|