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
package/impl/address_space.ts
CHANGED
|
@@ -942,7 +942,7 @@ export class AddressSpaceImpl implements AddressSpacePrivate {
|
|
|
942
942
|
*
|
|
943
943
|
*
|
|
944
944
|
*/
|
|
945
|
-
public browsePath(browsePath: BrowsePath): BrowsePathResult {
|
|
945
|
+
public browsePath(browsePath: BrowsePath, context?: ISessionContext): BrowsePathResult {
|
|
946
946
|
assert(browsePath instanceof BrowsePath);
|
|
947
947
|
|
|
948
948
|
const startingNode = this.findNode(browsePath.startingNode);
|
|
@@ -989,10 +989,21 @@ export class AddressSpaceImpl implements AddressSpacePrivate {
|
|
|
989
989
|
|
|
990
990
|
const res: BrowsePathTargetOptions[] = [];
|
|
991
991
|
|
|
992
|
+
// a node the context cannot browse is invisible: neither followed nor returned
|
|
993
|
+
const isBrowseDenied = (nodeId: NodeId): boolean => {
|
|
994
|
+
if (!context) {
|
|
995
|
+
return false;
|
|
996
|
+
}
|
|
997
|
+
const node = this.findNode(nodeId);
|
|
998
|
+
return !!node && context.isBrowseAccessRestricted(node);
|
|
999
|
+
};
|
|
1000
|
+
|
|
992
1001
|
const explore_element = (curNodeObject: BaseNode, elements: RelativePathElement[], index: number) => {
|
|
993
1002
|
const element = elements[index];
|
|
994
1003
|
const is_last = index + 1 === elements.length;
|
|
995
|
-
const nodeIds = curNodeObject
|
|
1004
|
+
const nodeIds = curNodeObject
|
|
1005
|
+
.browseNodeByTargetName(element, is_last)
|
|
1006
|
+
.filter((nodeId: NodeId) => !isBrowseDenied(nodeId));
|
|
996
1007
|
const targets = nodeIds.map((nodeId: NodeId) => {
|
|
997
1008
|
return {
|
|
998
1009
|
remainingPathIndex: elements.length - index,
|
package/impl/ua_variable_impl.ts
CHANGED
|
@@ -27,7 +27,9 @@ import {
|
|
|
27
27
|
makeDefaultCloneExtraInfo,
|
|
28
28
|
type SetFunc,
|
|
29
29
|
type VariableDataValueGetterSync,
|
|
30
|
+
type VariableDataValueGetterWithContext,
|
|
30
31
|
type VariableDataValueSetterWithCallback,
|
|
32
|
+
type VariableDataValueSetterWithContext,
|
|
31
33
|
type VariableSetter
|
|
32
34
|
} from "node-opcua-address-space-base";
|
|
33
35
|
import { assert } from "node-opcua-assert";
|
|
@@ -248,13 +250,18 @@ export class UAVariableImpl<T extends UAVariableEvents & ListenerSignature<T> =
|
|
|
248
250
|
declare public _timestamped_set_func?: VariableDataValueSetterWithCallback | null;
|
|
249
251
|
declare public _get_func?: GetFunc | null;
|
|
250
252
|
declare public _set_func?: ((value: Variant, callback: (err: Error | null, statusCode?: StatusCode) => void) => void) | null;
|
|
251
|
-
declare public refreshFunc?: (callback: CallbackT<DataValue
|
|
253
|
+
declare public refreshFunc?: (callback: CallbackT<DataValue>, context?: ISessionContext) => void;
|
|
252
254
|
/**
|
|
253
255
|
* true when refreshFunc only wraps the synchronous getter of a Variable bound with a plain
|
|
254
256
|
* `get`. readValue evaluates that getter itself: refreshing such a Variable before reading
|
|
255
257
|
* it, as the Read service does for the others, evaluates the getter twice for nothing.
|
|
256
258
|
*/
|
|
257
259
|
declare public _refreshFuncWrapsGetter?: boolean;
|
|
260
|
+
/**
|
|
261
|
+
* true when the timestamped getter answered a Promise when it was bound: it is run by asyncRefresh
|
|
262
|
+
* only. readValue cannot use a Promise: it serves the value the last refresh left.
|
|
263
|
+
*/
|
|
264
|
+
declare public _getterIsAsync?: boolean;
|
|
258
265
|
// the read that started the refresh in progress, and the reads that came while it was (an array only then):
|
|
259
266
|
// a sampled variable is read once per sample, and one at a time
|
|
260
267
|
declare private __waiting_callback?: CallbackT<DataValue>;
|
|
@@ -463,7 +470,7 @@ export class UAVariableImpl<T extends UAVariableEvents & ListenerSignature<T> =
|
|
|
463
470
|
return makeNowDataValue({ statusCode: StatusCodes.BadDataEncodingInvalid });
|
|
464
471
|
}
|
|
465
472
|
|
|
466
|
-
if (this._timestamped_get_func) {
|
|
473
|
+
if (this._timestamped_get_func && !this._getterIsAsync) {
|
|
467
474
|
if (this._timestamped_get_func.length === 0) {
|
|
468
475
|
const dataValueOrPromise = (this._timestamped_get_func as VariableDataValueGetterSync)();
|
|
469
476
|
if (!Object.hasOwn(dataValueOrPromise.constructor.prototype, "then")) {
|
|
@@ -547,9 +554,13 @@ export class UAVariableImpl<T extends UAVariableEvents & ListenerSignature<T> =
|
|
|
547
554
|
return dataTypeNode._getEnumerationInfo();
|
|
548
555
|
}
|
|
549
556
|
|
|
550
|
-
public asyncRefresh(oldestDate: PreciseClock, callback: CallbackT<DataValue
|
|
557
|
+
public asyncRefresh(oldestDate: PreciseClock, callback: CallbackT<DataValue>, context?: ISessionContext): void;
|
|
551
558
|
public asyncRefresh(oldestDate: PreciseClock): Promise<DataValue>;
|
|
552
|
-
public asyncRefresh(
|
|
559
|
+
public asyncRefresh(
|
|
560
|
+
oldestDate: PreciseClock,
|
|
561
|
+
callback?: CallbackT<DataValue>,
|
|
562
|
+
context?: ISessionContext
|
|
563
|
+
): Promise<DataValue> | undefined {
|
|
553
564
|
// asyncRefresh is always invoked with a callback at runtime: the public overload without
|
|
554
565
|
// a callback is only reachable through the withCallback() wrapper installed below, which
|
|
555
566
|
// synthesizes one before calling into this implementation.
|
|
@@ -588,33 +599,37 @@ export class UAVariableImpl<T extends UAVariableEvents & ListenerSignature<T> =
|
|
|
588
599
|
const requestedAtPicoseconds = oldestDate.picoseconds;
|
|
589
600
|
let answeredAtOnce = true;
|
|
590
601
|
try {
|
|
591
|
-
this.refreshFunc.call(
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
if (
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
602
|
+
this.refreshFunc.call(
|
|
603
|
+
this,
|
|
604
|
+
(err: Error | null, dataValue?: DataValueLike) => {
|
|
605
|
+
// c8 ignore next
|
|
606
|
+
if (err || !dataValue) {
|
|
607
|
+
errorLog(
|
|
608
|
+
"-------------- refresh call failed",
|
|
609
|
+
this.browseName.toString(),
|
|
610
|
+
this.nodeId.toString(),
|
|
611
|
+
err?.message
|
|
612
|
+
);
|
|
613
|
+
dataValue = { statusCode: StatusCodes.BadNoDataAvailable };
|
|
614
|
+
}
|
|
615
|
+
if (dataValue && dataValue !== this.$dataValue) {
|
|
616
|
+
const refreshed = coerceDataValue(dataValue);
|
|
617
|
+
if (!refreshed.serverTimestamp) {
|
|
618
|
+
if (answeredAtOnce && sharedClock) {
|
|
619
|
+
refreshed.serverTimestamp = requestedAt;
|
|
620
|
+
refreshed.serverPicoseconds = requestedAtPicoseconds;
|
|
621
|
+
} else {
|
|
622
|
+
const now = getCurrentClock();
|
|
623
|
+
refreshed.serverTimestamp = now.timestamp;
|
|
624
|
+
refreshed.serverPicoseconds = now.picoseconds;
|
|
625
|
+
}
|
|
612
626
|
}
|
|
627
|
+
this._internal_set_dataValue(refreshed, null);
|
|
613
628
|
}
|
|
614
|
-
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
|
|
629
|
+
cb(err, this.$dataValue);
|
|
630
|
+
},
|
|
631
|
+
context
|
|
632
|
+
);
|
|
618
633
|
answeredAtOnce = false;
|
|
619
634
|
} catch (err) {
|
|
620
635
|
errorLog("-------------- refresh call failed 2", this.browseName.toString(), this.nodeId.toString());
|
|
@@ -996,77 +1011,88 @@ export class UAVariableImpl<T extends UAVariableEvents & ListenerSignature<T> =
|
|
|
996
1011
|
}
|
|
997
1012
|
assert(write_func);
|
|
998
1013
|
|
|
999
|
-
write_func.call(
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
if (
|
|
1004
|
-
|
|
1005
|
-
|
|
1014
|
+
write_func.call(
|
|
1015
|
+
this,
|
|
1016
|
+
dataValue,
|
|
1017
|
+
(err?: Error | null, statusCode1?: StatusCode) => {
|
|
1018
|
+
if (!err) {
|
|
1019
|
+
// a write the setter answers Bad did not happen: the stored value stays as it was
|
|
1020
|
+
if (statusCode1?.isNotGood()) {
|
|
1021
|
+
callback?.(null, statusCode1);
|
|
1006
1022
|
return;
|
|
1007
1023
|
}
|
|
1024
|
+
dataValue && this.verifyVariantCompatibility(dataValue.value);
|
|
1008
1025
|
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
if (this.$dataValue.value.arrayType !== VariantArrayType.Array) {
|
|
1013
|
-
callback(null, StatusCodes.BadTypeMismatch);
|
|
1026
|
+
if (indexRange && !indexRange.isEmpty()) {
|
|
1027
|
+
if (!indexRange.isValid()) {
|
|
1028
|
+
callback?.(null, StatusCodes.BadIndexRangeInvalid);
|
|
1014
1029
|
return;
|
|
1015
1030
|
}
|
|
1016
|
-
// check that destination data is also an array
|
|
1017
|
-
assert(check_valid_array(this.$dataValue.value.dataType, this.$dataValue.value.value));
|
|
1018
|
-
const destArr = this.$dataValue.value.value;
|
|
1019
|
-
const result = indexRange.set_values(destArr, newArrayOrMatrix);
|
|
1020
1031
|
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1032
|
+
const newArrayOrMatrix = dataValue.value.value;
|
|
1033
|
+
|
|
1034
|
+
if (dataValue.value.arrayType === VariantArrayType.Array) {
|
|
1035
|
+
if (this.$dataValue.value.arrayType !== VariantArrayType.Array) {
|
|
1036
|
+
callback(null, StatusCodes.BadTypeMismatch);
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
1039
|
+
// check that destination data is also an array
|
|
1040
|
+
assert(check_valid_array(this.$dataValue.value.dataType, this.$dataValue.value.value));
|
|
1041
|
+
const destArr = this.$dataValue.value.value;
|
|
1042
|
+
const result = indexRange.set_values(destArr, newArrayOrMatrix);
|
|
1043
|
+
|
|
1044
|
+
if (result.statusCode.isNot(StatusCodes.Good)) {
|
|
1045
|
+
callback?.(null, result.statusCode);
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1048
|
+
dataValue.value.value = result.array;
|
|
1049
|
+
|
|
1050
|
+
// scrap original array so we detect range
|
|
1051
|
+
this.$dataValue.value.value = null;
|
|
1052
|
+
} else if (dataValue.value.arrayType === VariantArrayType.Matrix) {
|
|
1053
|
+
const dimensions = this.$dataValue.value.dimensions;
|
|
1054
|
+
if (this.$dataValue.value.arrayType !== VariantArrayType.Matrix || !dimensions) {
|
|
1055
|
+
// not a matrix !
|
|
1056
|
+
callback?.(null, StatusCodes.BadTypeMismatch);
|
|
1057
|
+
return;
|
|
1058
|
+
}
|
|
1059
|
+
const matrix = this.$dataValue.value.value;
|
|
1060
|
+
const result = indexRange.set_values_matrix(
|
|
1061
|
+
{
|
|
1062
|
+
matrix,
|
|
1063
|
+
dimensions
|
|
1064
|
+
},
|
|
1065
|
+
newArrayOrMatrix
|
|
1066
|
+
);
|
|
1067
|
+
if (result.statusCode.isNot(StatusCodes.Good)) {
|
|
1068
|
+
callback?.(null, result.statusCode);
|
|
1069
|
+
return;
|
|
1070
|
+
}
|
|
1071
|
+
dataValue.value.dimensions = this.$dataValue.value.dimensions;
|
|
1072
|
+
dataValue.value.value = result.matrix;
|
|
1073
|
+
|
|
1074
|
+
// scrap original array so we detect range
|
|
1075
|
+
this.$dataValue.value.value = null;
|
|
1076
|
+
} else {
|
|
1033
1077
|
callback?.(null, StatusCodes.BadTypeMismatch);
|
|
1034
1078
|
return;
|
|
1035
1079
|
}
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
newArrayOrMatrix
|
|
1043
|
-
);
|
|
1044
|
-
if (result.statusCode.isNot(StatusCodes.Good)) {
|
|
1045
|
-
callback?.(null, result.statusCode);
|
|
1046
|
-
return;
|
|
1080
|
+
}
|
|
1081
|
+
try {
|
|
1082
|
+
this._internal_set_dataValue(dataValue, indexRange);
|
|
1083
|
+
} catch (err) {
|
|
1084
|
+
if (types.isNativeError(err)) {
|
|
1085
|
+
warningLog(err.message);
|
|
1047
1086
|
}
|
|
1048
|
-
|
|
1049
|
-
dataValue.value.value = result.matrix;
|
|
1050
|
-
|
|
1051
|
-
// scrap original array so we detect range
|
|
1052
|
-
this.$dataValue.value.value = null;
|
|
1053
|
-
} else {
|
|
1054
|
-
callback?.(null, StatusCodes.BadTypeMismatch);
|
|
1087
|
+
callback?.(null, StatusCodes.BadInternalError);
|
|
1055
1088
|
return;
|
|
1056
1089
|
}
|
|
1057
1090
|
}
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
}
|
|
1064
|
-
callback?.(null, StatusCodes.BadInternalError);
|
|
1065
|
-
return;
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
callback?.(err || null, statusCode1);
|
|
1069
|
-
});
|
|
1091
|
+
callback?.(err || null, statusCode1);
|
|
1092
|
+
},
|
|
1093
|
+
context,
|
|
1094
|
+
indexRange
|
|
1095
|
+
);
|
|
1070
1096
|
return undefined;
|
|
1071
1097
|
}
|
|
1072
1098
|
|
|
@@ -1299,6 +1325,7 @@ export class UAVariableImpl<T extends UAVariableEvents & ListenerSignature<T> =
|
|
|
1299
1325
|
this._set_func = null;
|
|
1300
1326
|
this.refreshFunc = undefined;
|
|
1301
1327
|
this._refreshFuncWrapsGetter = false;
|
|
1328
|
+
this._getterIsAsync = false;
|
|
1302
1329
|
this._historyRead = UAVariableImpl.prototype._historyRead;
|
|
1303
1330
|
}
|
|
1304
1331
|
|
|
@@ -1344,11 +1371,47 @@ export class UAVariableImpl<T extends UAVariableEvents & ListenerSignature<T> =
|
|
|
1344
1371
|
});
|
|
1345
1372
|
}
|
|
1346
1373
|
|
|
1374
|
+
/**
|
|
1375
|
+
* why this context may not read the Value, or null when it may: the gates of readValue(),
|
|
1376
|
+
* applied to every read of readValueAsync, including one that joins a refresh in flight.
|
|
1377
|
+
* Runs once per sample of every monitored item: it allocates nothing
|
|
1378
|
+
*/
|
|
1379
|
+
private _readDenial(context: ISessionContext): StatusCode | null {
|
|
1380
|
+
if (context.isAccessRestricted(this)) {
|
|
1381
|
+
// same gate as readValue(): a variable bound with a simple get()
|
|
1382
|
+
// also gets a refreshFunc, sending every read down the asyncRefresh
|
|
1383
|
+
// branch of _readValueAsync - which calls the getter directly and never passes
|
|
1384
|
+
// through readValue(). Without this check here, a node hardened
|
|
1385
|
+
// with AccessRestrictions(EncryptionRequired) answered its value
|
|
1386
|
+
// over an unencrypted channel.
|
|
1387
|
+
return StatusCodes.BadSecurityModeInsufficient;
|
|
1388
|
+
} else if (!this.isReadable(context)) {
|
|
1389
|
+
return StatusCodes.BadNotReadable;
|
|
1390
|
+
} else if (!this.checkPermissionPrivate(context, PermissionType.Read)) {
|
|
1391
|
+
return StatusCodes.BadUserAccessDenied;
|
|
1392
|
+
} else if (
|
|
1393
|
+
// as in readValue(): the Read permission has just passed, only the access level is
|
|
1394
|
+
// left, unless a node replaced isUserReadable (see makeNotReadableIfEnabledFlagIsFalse)
|
|
1395
|
+
this.isUserReadable === UAVariableImpl.prototype.isUserReadable
|
|
1396
|
+
? !this.checkAccessLevelPrivate(context, AccessLevelFlag.CurrentRead)
|
|
1397
|
+
: !this.isUserReadable(context)
|
|
1398
|
+
) {
|
|
1399
|
+
return StatusCodes.BadNotReadable;
|
|
1400
|
+
}
|
|
1401
|
+
return null;
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1347
1404
|
private _readValueAsync(context: ISessionContext | null, callback: CallbackT<DataValue>, readAt?: PreciseClock): void {
|
|
1348
1405
|
context = context || SessionContext.defaultContext;
|
|
1349
1406
|
|
|
1350
1407
|
if (this.__waiting_callback) {
|
|
1351
|
-
// a refresh is in progress: its value answers this read too
|
|
1408
|
+
// a refresh is in progress: its value answers this read too, once this read's own
|
|
1409
|
+
// access checks have passed (they depend on its context, not on the first reader's)
|
|
1410
|
+
const joinerDenied = this._readDenial(context);
|
|
1411
|
+
if (joinerDenied) {
|
|
1412
|
+
callback(null, makeNowDataValue({ statusCode: joinerDenied }));
|
|
1413
|
+
return;
|
|
1414
|
+
}
|
|
1352
1415
|
this.__more_waiting_callbacks ??= [];
|
|
1353
1416
|
this.__more_waiting_callbacks.push(callback);
|
|
1354
1417
|
return;
|
|
@@ -1363,28 +1426,7 @@ export class UAVariableImpl<T extends UAVariableEvents & ListenerSignature<T> =
|
|
|
1363
1426
|
// runs once per sample of every monitored item, so nothing here allocates a closure or
|
|
1364
1427
|
// binds a function: the denial (if any) is a StatusCode, and the callback that releases
|
|
1365
1428
|
// the waiting readers is made once per variable
|
|
1366
|
-
|
|
1367
|
-
if (context.isAccessRestricted(this)) {
|
|
1368
|
-
// same gate as readValue(): a variable bound with a simple get()
|
|
1369
|
-
// also gets a refreshFunc, sending every read down the asyncRefresh
|
|
1370
|
-
// branch below - which calls the getter directly and never passes
|
|
1371
|
-
// through readValue(). Without this check here, a node hardened
|
|
1372
|
-
// with AccessRestrictions(EncryptionRequired) answered its value
|
|
1373
|
-
// over an unencrypted channel.
|
|
1374
|
-
denied = StatusCodes.BadSecurityModeInsufficient;
|
|
1375
|
-
} else if (!this.isReadable(context)) {
|
|
1376
|
-
denied = StatusCodes.BadNotReadable;
|
|
1377
|
-
} else if (!this.checkPermissionPrivate(context, PermissionType.Read)) {
|
|
1378
|
-
denied = StatusCodes.BadUserAccessDenied;
|
|
1379
|
-
} else if (
|
|
1380
|
-
// as in readValue(): the Read permission has just passed, only the access level is
|
|
1381
|
-
// left, unless a node replaced isUserReadable (see makeNotReadableIfEnabledFlagIsFalse)
|
|
1382
|
-
this.isUserReadable === UAVariableImpl.prototype.isUserReadable
|
|
1383
|
-
? !this.checkAccessLevelPrivate(context, AccessLevelFlag.CurrentRead)
|
|
1384
|
-
: !this.isUserReadable(context)
|
|
1385
|
-
) {
|
|
1386
|
-
denied = StatusCodes.BadNotReadable;
|
|
1387
|
-
}
|
|
1429
|
+
const denied = this._readDenial(context);
|
|
1388
1430
|
|
|
1389
1431
|
this.__satisfy_waiting_callbacks ??= (err: Error | null, dataValue?: DataValue) => {
|
|
1390
1432
|
// now call all pending callbacks, in the order of the reads
|
|
@@ -1411,9 +1453,9 @@ export class UAVariableImpl<T extends UAVariableEvents & ListenerSignature<T> =
|
|
|
1411
1453
|
this.__sharedReadClock = readAt !== undefined;
|
|
1412
1454
|
if (this.asyncRefresh === wrappedAsyncRefresh) {
|
|
1413
1455
|
// the thenified wrapper adds nothing when a callback is given
|
|
1414
|
-
asyncRefreshImpl.call(this, clock, satisfy_callbacks);
|
|
1456
|
+
asyncRefreshImpl.call(this, clock, satisfy_callbacks, context);
|
|
1415
1457
|
} else {
|
|
1416
|
-
this.asyncRefresh(clock, satisfy_callbacks);
|
|
1458
|
+
this.asyncRefresh(clock, satisfy_callbacks, context);
|
|
1417
1459
|
}
|
|
1418
1460
|
// consumed by asyncRefresh on entry; cleared here for an overridden one that never reads it
|
|
1419
1461
|
this.__sharedReadClock = false;
|
|
@@ -2190,9 +2232,28 @@ import { withCallback } from "thenify-ex";
|
|
|
2190
2232
|
|
|
2191
2233
|
// readValueAsync calls the unwrapped asyncRefresh directly: binding the thenified wrapper on
|
|
2192
2234
|
// every sample cost more than the refresh itself
|
|
2193
|
-
const asyncRefreshImpl: (
|
|
2194
|
-
UAVariableImpl
|
|
2195
|
-
|
|
2235
|
+
const asyncRefreshImpl: (
|
|
2236
|
+
this: UAVariableImpl,
|
|
2237
|
+
oldestDate: PreciseClock,
|
|
2238
|
+
callback: CallbackT<DataValue>,
|
|
2239
|
+
context?: ISessionContext
|
|
2240
|
+
) => void = UAVariableImpl.prototype.asyncRefresh;
|
|
2241
|
+
// the promise form when no callback is given, as withCallback() does: written out because that
|
|
2242
|
+
// wrapper takes the last argument for the callback, and the context comes after it
|
|
2243
|
+
UAVariableImpl.prototype.asyncRefresh = function (
|
|
2244
|
+
this: UAVariableImpl,
|
|
2245
|
+
oldestDate: PreciseClock,
|
|
2246
|
+
callback?: CallbackT<DataValue>,
|
|
2247
|
+
context?: ISessionContext
|
|
2248
|
+
): Promise<DataValue> | undefined {
|
|
2249
|
+
if (typeof callback === "function") {
|
|
2250
|
+
asyncRefreshImpl.call(this, oldestDate, callback, context);
|
|
2251
|
+
return undefined;
|
|
2252
|
+
}
|
|
2253
|
+
return new Promise<DataValue>((resolve, reject) => {
|
|
2254
|
+
asyncRefreshImpl.call(this, oldestDate, (err, dataValue) => (err ? reject(err) : resolve(dataValue as DataValue)), context);
|
|
2255
|
+
});
|
|
2256
|
+
} as UAVariableImpl["asyncRefresh"];
|
|
2196
2257
|
const wrappedAsyncRefresh = UAVariableImpl.prototype.asyncRefresh;
|
|
2197
2258
|
UAVariableImpl.prototype.writeValue = withCallback(UAVariableImpl.prototype.writeValue);
|
|
2198
2259
|
UAVariableImpl.prototype.writeAttribute = withCallback(UAVariableImpl.prototype.writeAttribute);
|
|
@@ -2337,7 +2398,8 @@ function turn_sync_to_async<T, D, R>(f: f1<T, D, R> | f2<T, D, R> | f3<T, D, R>,
|
|
|
2337
2398
|
callback(null, r);
|
|
2338
2399
|
};
|
|
2339
2400
|
} else {
|
|
2340
|
-
|
|
2401
|
+
// a callback-style function; it may declare the trailing context and indexRange parameters
|
|
2402
|
+
assert(f.length >= numberOfArgs + 1 && f.length <= numberOfArgs + 3);
|
|
2341
2403
|
return f;
|
|
2342
2404
|
}
|
|
2343
2405
|
}
|
|
@@ -2439,12 +2501,14 @@ function _Variable_bind_with_timestamped_get(
|
|
|
2439
2501
|
|
|
2440
2502
|
throw new Error(" Bind variable error: " + " the timestamped_get function must return a DataValue");
|
|
2441
2503
|
}
|
|
2504
|
+
this._getterIsAsync = isThenable(dataValue_verify);
|
|
2442
2505
|
_Variable_bind_with_async_refresh.call(this, { refreshFunc: async_refresh_func });
|
|
2443
|
-
} else if (options.timestamped_get.length === 1) {
|
|
2444
|
-
|
|
2506
|
+
} else if (options.timestamped_get.length === 1 || options.timestamped_get.length === 2) {
|
|
2507
|
+
// callback style: (callback) or (callback, context)
|
|
2508
|
+
_Variable_bind_with_async_refresh.call(this, { refreshFunc: options.timestamped_get as RefreshFunc });
|
|
2445
2509
|
} else {
|
|
2446
|
-
errorLog("timestamped_get has a invalid number of argument , should be 0 or
|
|
2447
|
-
throw new Error("timestamped_get has a invalid number of argument , should be 0 or
|
|
2510
|
+
errorLog("timestamped_get has a invalid number of argument , should be 0, 1 or 2 ");
|
|
2511
|
+
throw new Error("timestamped_get has a invalid number of argument , should be 0, 1 or 2 ");
|
|
2448
2512
|
}
|
|
2449
2513
|
}
|
|
2450
2514
|
|
|
@@ -2512,28 +2576,38 @@ function _Variable_bind_with_simple_set(this: UAVariableImpl, options: SimpleSet
|
|
|
2512
2576
|
const set_func = turn_sync_to_async<UAVariable, Variant, StatusCode>(options.set as VariableSetter, 1);
|
|
2513
2577
|
this._set_func = set_func;
|
|
2514
2578
|
|
|
2515
|
-
assert(set_func.length
|
|
2579
|
+
assert(set_func.length >= 2, " set function must have 2 arguments ( variant, callback)");
|
|
2516
2580
|
|
|
2581
|
+
// the trailing arguments (context, indexRange) are a rest parameter so that the length stays 2
|
|
2517
2582
|
this._timestamped_set_func = (
|
|
2518
2583
|
timestamped_value: DataValue,
|
|
2519
|
-
callback: (err: Error | null, statusCode: StatusCode, dataValue: DataValue) => void
|
|
2584
|
+
callback: (err: Error | null, statusCode: StatusCode, dataValue: DataValue) => void,
|
|
2585
|
+
...extra: [ISessionContext?, (NumericRange | null)?]
|
|
2520
2586
|
) => {
|
|
2521
2587
|
assert(timestamped_value instanceof DataValue);
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2588
|
+
(
|
|
2589
|
+
set_func as (
|
|
2590
|
+
this: UAVariable,
|
|
2591
|
+
value: Variant,
|
|
2592
|
+
callback: (err: Error | null, statusCode?: StatusCode) => void,
|
|
2593
|
+
...extra: unknown[]
|
|
2594
|
+
) => void
|
|
2595
|
+
).call(
|
|
2596
|
+
this,
|
|
2597
|
+
timestamped_value.value,
|
|
2598
|
+
(err: Error | null, statusCode?: StatusCode) => {
|
|
2599
|
+
// c8 ignore next
|
|
2600
|
+
if (!err && !statusCode) {
|
|
2601
|
+
errorLog(
|
|
2602
|
+
chalk.red("UAVariable Binding Error _set_func must return a StatusCode, check the bindVariable parameters")
|
|
2603
|
+
);
|
|
2604
|
+
errorLog(chalk.yellow("StatusCode.Good is assumed"));
|
|
2605
|
+
return callback(err, StatusCodes.Good, timestamped_value);
|
|
2606
|
+
}
|
|
2607
|
+
callback(err, statusCode ?? StatusCodes.BadInternalError, timestamped_value);
|
|
2608
|
+
},
|
|
2609
|
+
...extra
|
|
2610
|
+
);
|
|
2537
2611
|
};
|
|
2538
2612
|
}
|
|
2539
2613
|
|
|
@@ -2545,7 +2619,7 @@ function _Variable_bind_with_timestamped_set(
|
|
|
2545
2619
|
}
|
|
2546
2620
|
) {
|
|
2547
2621
|
assert(
|
|
2548
|
-
options.timestamped_set.length
|
|
2622
|
+
options.timestamped_set.length >= 1 && options.timestamped_set.length <= 4,
|
|
2549
2623
|
"timestamped_set must have 2 parameters timestamped_set: function(dataValue,callback){} or one paramater timestamped_set: function(dataValue): Promise<StatusCode>{}"
|
|
2550
2624
|
);
|
|
2551
2625
|
assert(!options.set, "should not specify set when timestamped_set_func exists ");
|
|
@@ -2554,7 +2628,7 @@ function _Variable_bind_with_timestamped_set(
|
|
|
2554
2628
|
// setter of its own binds the same default, and a closure per variable was 110 bytes each
|
|
2555
2629
|
let setter = callbackSetters.get(options.timestamped_set);
|
|
2556
2630
|
if (!setter) {
|
|
2557
|
-
setter = convertToCallbackFunction1<StatusCode, DataValue, UAVariable>(options.timestamped_set);
|
|
2631
|
+
setter = convertToCallbackFunction1<StatusCode, DataValue, UAVariable>(options.timestamped_set, 2);
|
|
2558
2632
|
callbackSetters.set(options.timestamped_set, setter);
|
|
2559
2633
|
}
|
|
2560
2634
|
this._timestamped_set_func = setter;
|
|
@@ -2562,12 +2636,41 @@ function _Variable_bind_with_timestamped_set(
|
|
|
2562
2636
|
/** the callback form of each setter function met so far, see _Variable_bind_with_timestamped_set */
|
|
2563
2637
|
const callbackSetters = new WeakMap<TimestampSetFunc, VariableDataValueSetterWithCallback>();
|
|
2564
2638
|
|
|
2639
|
+
/** an asynchronous setter that is told who writes (`timestamped_set_ctx`) */
|
|
2640
|
+
function _Variable_bind_with_context_setter(this: UAVariableImpl, setter: VariableDataValueSetterWithContext) {
|
|
2641
|
+
// the context and the index range come after the callback, in a rest parameter: the length stays 2
|
|
2642
|
+
this._timestamped_set_func = function (
|
|
2643
|
+
this: UAVariable,
|
|
2644
|
+
dataValue: DataValue,
|
|
2645
|
+
callback: (err: Error | null, statusCode?: StatusCode) => void,
|
|
2646
|
+
...extra: [ISessionContext?, (NumericRange | null)?]
|
|
2647
|
+
) {
|
|
2648
|
+
const [context, indexRange] = extra;
|
|
2649
|
+
let promise: Promise<StatusCode>;
|
|
2650
|
+
try {
|
|
2651
|
+
promise = setter.call(this, dataValue, { context, indexRange });
|
|
2652
|
+
} catch (err) {
|
|
2653
|
+
callback(err as Error);
|
|
2654
|
+
return;
|
|
2655
|
+
}
|
|
2656
|
+
Promise.resolve(promise).then(
|
|
2657
|
+
(statusCode) => callback(null, statusCode),
|
|
2658
|
+
(err) => callback(err as Error)
|
|
2659
|
+
);
|
|
2660
|
+
};
|
|
2661
|
+
}
|
|
2662
|
+
|
|
2565
2663
|
interface SetterOptions {
|
|
2566
2664
|
set?: SetFunc;
|
|
2567
2665
|
timestamped_set?: TimestampSetFunc;
|
|
2666
|
+
timestamped_set_ctx?: VariableDataValueSetterWithContext;
|
|
2568
2667
|
timestamped_get?: TimestampGetFunc;
|
|
2668
|
+
timestamped_get_ctx?: VariableDataValueGetterWithContext;
|
|
2569
2669
|
}
|
|
2570
2670
|
function bind_setter(this: UAVariableImpl, options: SetterOptions) {
|
|
2671
|
+
if (options.timestamped_set && options.timestamped_set_ctx) {
|
|
2672
|
+
throw new Error("bind_setter : options should not specify both timestamped_set and timestamped_set_ctx ");
|
|
2673
|
+
}
|
|
2571
2674
|
if (options.set && typeof options.set === "function") {
|
|
2572
2675
|
// variation 1
|
|
2573
2676
|
if (options.timestamped_set === undefined) {
|
|
@@ -2582,8 +2685,15 @@ function bind_setter(this: UAVariableImpl, options: SetterOptions) {
|
|
|
2582
2685
|
set: undefined,
|
|
2583
2686
|
timestamped_set: options.timestamped_set
|
|
2584
2687
|
});
|
|
2585
|
-
} else if (typeof options.
|
|
2586
|
-
//
|
|
2688
|
+
} else if (typeof options.timestamped_set_ctx === "function") {
|
|
2689
|
+
// variation 5: the setter is told who writes
|
|
2690
|
+
assert(
|
|
2691
|
+
typeof options.timestamped_get === "function" || typeof options.timestamped_get_ctx === "function",
|
|
2692
|
+
"timestamped_set_ctx must be used with timestamped_get or timestamped_get_ctx"
|
|
2693
|
+
);
|
|
2694
|
+
_Variable_bind_with_context_setter.call(this, options.timestamped_set_ctx);
|
|
2695
|
+
} else if (typeof options.timestamped_get === "function" || typeof options.timestamped_get_ctx === "function") {
|
|
2696
|
+
// a timestamped getter is specified but no setter
|
|
2587
2697
|
// => Value is read-only
|
|
2588
2698
|
_Variable_bind_with_timestamped_set.call(this, {
|
|
2589
2699
|
set: undefined,
|
|
@@ -2597,10 +2707,36 @@ function bind_setter(this: UAVariableImpl, options: SetterOptions) {
|
|
|
2597
2707
|
}
|
|
2598
2708
|
}
|
|
2599
2709
|
|
|
2600
|
-
type RefreshFunc = (callback: CallbackT<DataValue
|
|
2710
|
+
type RefreshFunc = (callback: CallbackT<DataValue>, context?: ISessionContext) => void;
|
|
2711
|
+
/**
|
|
2712
|
+
* variation 5: an asynchronous getter that is told who reads (`timestamped_get_ctx`). It is run by
|
|
2713
|
+
* the refresh only, like any asynchronous getter, with the session context of the read.
|
|
2714
|
+
*/
|
|
2715
|
+
function _Variable_bind_with_context_getter(this: UAVariableImpl, getter: VariableDataValueGetterWithContext) {
|
|
2716
|
+
_Variable_bind_with_async_refresh.call(this, {
|
|
2717
|
+
refreshFunc: (callback: CallbackT<DataValue>, context?: ISessionContext) => {
|
|
2718
|
+
let promise: Promise<DataValue>;
|
|
2719
|
+
try {
|
|
2720
|
+
promise = getter.call(this, { context });
|
|
2721
|
+
} catch (err) {
|
|
2722
|
+
callback(err as Error);
|
|
2723
|
+
return;
|
|
2724
|
+
}
|
|
2725
|
+
Promise.resolve(promise).then(
|
|
2726
|
+
(dataValue) => callback(null, dataValue),
|
|
2727
|
+
(err) => {
|
|
2728
|
+
errorLog("timestamped_get_ctx error: Variable is ", this.nodeId.toString(), this.browseName.toString());
|
|
2729
|
+
callback(err as Error);
|
|
2730
|
+
}
|
|
2731
|
+
);
|
|
2732
|
+
}
|
|
2733
|
+
});
|
|
2734
|
+
}
|
|
2735
|
+
|
|
2601
2736
|
interface GetterOptions {
|
|
2602
2737
|
get?: GetFunc;
|
|
2603
2738
|
timestamped_get?: TimestampGetFunc;
|
|
2739
|
+
timestamped_get_ctx?: VariableDataValueGetterWithContext;
|
|
2604
2740
|
refreshFunc?: RefreshFunc;
|
|
2605
2741
|
dataType?: DataType | string;
|
|
2606
2742
|
value?: unknown;
|
|
@@ -2615,6 +2751,9 @@ function bind_getter(this: UAVariableImpl, options: GetterOptions) {
|
|
|
2615
2751
|
get: undefined,
|
|
2616
2752
|
timestamped_get: options.timestamped_get
|
|
2617
2753
|
});
|
|
2754
|
+
} else if (typeof options.timestamped_get_ctx === "function") {
|
|
2755
|
+
// variation 5
|
|
2756
|
+
_Variable_bind_with_context_getter.call(this, options.timestamped_get_ctx);
|
|
2618
2757
|
} else if (typeof options.refreshFunc === "function") {
|
|
2619
2758
|
// variation 3
|
|
2620
2759
|
if (options.get !== undefined || options.timestamped_get !== undefined) {
|