node-opcua-server 2.56.3 → 2.60.0

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.
@@ -20,7 +20,8 @@ import {
20
20
  ServerSecureChannelLayer,
21
21
  ServerSecureChannelParent,
22
22
  toURI,
23
- AsymmetricAlgorithmSecurityHeader
23
+ AsymmetricAlgorithmSecurityHeader,
24
+ IServerSessionBase
24
25
  } from "node-opcua-secure-channel";
25
26
  import { UserTokenType } from "node-opcua-service-endpoints";
26
27
  import { EndpointDescription } from "node-opcua-service-endpoints";
@@ -78,6 +79,11 @@ function extractChannelData(channel: ServerSecureChannelLayer): IChannelData {
78
79
  }
79
80
 
80
81
  function dumpChannelInfo(channels: ServerSecureChannelLayer[]): void {
82
+ function d(s: IServerSessionBase) {
83
+ return `[ status=${s.status} lastSeen=${s.clientLastContactTime.toFixed(0)}ms sessionName=${s.sessionName} timeout=${
84
+ s.sessionTimeout
85
+ } ]`;
86
+ }
81
87
  function dumpChannel(channel: ServerSecureChannelLayer): void {
82
88
  console.log("------------------------------------------------------");
83
89
  console.log(" channelId = ", channel.channelId);
@@ -87,6 +93,8 @@ function dumpChannelInfo(channels: ServerSecureChannelLayer[]): void {
87
93
  console.log("");
88
94
  console.log(" bytesWritten = ", channel.bytesWritten);
89
95
  console.log(" bytesRead = ", channel.bytesRead);
96
+ console.log(" sessions = ", Object.keys(channel.sessionTokens).length);
97
+ console.log(Object.values(channel.sessionTokens).map(d).join("\n"));
90
98
 
91
99
  const socket = (channel as any).transport?._socket;
92
100
  if (!socket) {
@@ -181,6 +189,10 @@ function getUniqueName(name: string, collection: { [key: string]: number }) {
181
189
  return name;
182
190
  }
183
191
  }
192
+
193
+ interface ServerSecureChannelLayerPriv extends ServerSecureChannelLayer {
194
+ _unpreregisterChannelEvent?: () => void;
195
+ }
184
196
  /**
185
197
  * OPCUAServerEndPoint a Server EndPoint.
186
198
  * A sever end point is listening to one port
@@ -264,6 +276,7 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
264
276
  this._privateKey = emptyPrivateKeyPEM;
265
277
 
266
278
  assert(Object.keys(this._channels).length === 0, "OPCUAServerEndPoint channels must have been deleted");
279
+
267
280
  this._channels = {};
268
281
  this.serverInfo = new ApplicationDescription({});
269
282
 
@@ -660,7 +673,7 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
660
673
 
661
674
  private _on_client_connection(socket: Socket) {
662
675
  // a client is attempting a connection on the socket
663
- (socket as any).setNoDelay(true);
676
+ socket.setNoDelay(true);
664
677
 
665
678
  debugLog("OPCUAServerEndPoint#_on_client_connection", this._started);
666
679
  if (!this._started) {
@@ -673,6 +686,20 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
673
686
  socket.end();
674
687
  return;
675
688
  }
689
+ const deny_connection = () => {
690
+ console.log(
691
+ chalk.bgWhite.cyan(
692
+ "OPCUAServerEndPoint#_on_client_connection " +
693
+ "The maximum number of connection has been reached - Connection is refused"
694
+ )
695
+ );
696
+ const reason = "maxConnections reached (" + this.maxConnections + ")";
697
+ const socketData = extractSocketData(socket, reason);
698
+ this.emit("connectionRefused", socketData);
699
+
700
+ socket.end();
701
+ socket.destroy();
702
+ };
676
703
 
677
704
  const establish_connection = () => {
678
705
  const nbConnections = Object.keys(this._channels).length;
@@ -684,18 +711,7 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
684
711
  this.maxConnections
685
712
  );
686
713
  if (nbConnections >= this.maxConnections) {
687
- console.log(
688
- chalk.bgWhite.cyan(
689
- "OPCUAServerEndPoint#_on_client_connection " +
690
- "The maximum number of connection has been reached - Connection is refused"
691
- )
692
- );
693
- const reason = "maxConnections reached (" + this.maxConnections + ")";
694
- const socketData = extractSocketData(socket, reason);
695
- this.emit("connectionRefused", socketData);
696
-
697
- socket.end();
698
- (socket as any).destroy();
714
+ deny_connection();
699
715
  return;
700
716
  }
701
717
 
@@ -708,6 +724,8 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
708
724
  timeout: this.timeout
709
725
  });
710
726
 
727
+ debugLog("channel Timeout = >", channel.timeout);
728
+
711
729
  socket.resume();
712
730
 
713
731
  this._preregisterChannel(channel);
@@ -722,7 +740,7 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
722
740
  this.emit("openSecureChannelFailure", socketData, channelData);
723
741
 
724
742
  socket.end();
725
- (socket as any).destroy();
743
+ socket.destroy();
726
744
  } else {
727
745
  debugLog("server receiving a client connection");
728
746
  this._registerChannel(channel);
@@ -734,13 +752,13 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
734
752
  this.emit("message", message, channel, this);
735
753
  });
736
754
  };
755
+
737
756
  // Each SecureChannel exists until it is explicitly closed or until the last token has expired and the overlap
738
757
  // period has elapsed. A Server application should limit the number of SecureChannels.
739
758
  // To protect against misbehaving Clients and denial of service attacks, the Server shall close the oldest
740
759
  // SecureChannel that has no Session assigned before reaching the maximum number of supported SecureChannels.
741
- this._prevent_DDOS_Attack(establish_connection);
760
+ this._prevent_DDOS_Attack(establish_connection, deny_connection);
742
761
  }
743
-
744
762
  private _preregisterChannel(channel: ServerSecureChannelLayer) {
745
763
  // _preregisterChannel is used to keep track of channel for which
746
764
  // that are in early stage of the hand shaking process.
@@ -750,14 +768,14 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
750
768
 
751
769
  assert(!Object.prototype.hasOwnProperty.call(this._channels, channel.hashKey), " channel already preregistered!");
752
770
 
753
- this._channels[channel.hashKey] = channel;
754
-
755
- (channel as any)._unpreregisterChannelEvent = () => {
771
+ const channelPriv = <ServerSecureChannelLayerPriv>channel;
772
+ this._channels[channel.hashKey] = channelPriv;
773
+ channelPriv._unpreregisterChannelEvent = () => {
756
774
  debugLog("Channel received an abort event during the preregistration phase");
757
775
  this._un_pre_registerChannel(channel);
758
776
  channel.dispose();
759
777
  };
760
- channel.on("abort", (channel as any)._unpreregisterChannelEvent);
778
+ channel.on("abort", channelPriv._unpreregisterChannelEvent);
761
779
  }
762
780
 
763
781
  private _un_pre_registerChannel(channel: ServerSecureChannelLayer) {
@@ -765,11 +783,12 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
765
783
  debugLog("Already un preregistered ?", channel.hashKey);
766
784
  return;
767
785
  }
768
-
769
786
  delete this._channels[channel.hashKey];
770
- assert(typeof (channel as any)._unpreregisterChannelEvent === "function");
771
- channel.removeListener("abort", (channel as any)._unpreregisterChannelEvent);
772
- (channel as any)._unpreregisterChannelEvent = null;
787
+ const channelPriv = <ServerSecureChannelLayerPriv>channel;
788
+ if (typeof channelPriv._unpreregisterChannelEvent === "function") {
789
+ channel.removeListener("abort", channelPriv._unpreregisterChannelEvent!);
790
+ channelPriv._unpreregisterChannelEvent = undefined;
791
+ }
773
792
  }
774
793
 
775
794
  /**
@@ -861,23 +880,23 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
861
880
  /**
862
881
  * @private
863
882
  */
864
- private _prevent_DDOS_Attack(establish_connection: () => void) {
883
+ private _prevent_DDOS_Attack(establish_connection: () => void, deny_connection: () => void) {
865
884
  const nbConnections = this.activeChannelCount;
866
885
 
867
886
  if (nbConnections >= this.maxConnections) {
868
887
  // istanbul ignore next
869
- console.log(chalk.bgRed.white("PREVENTING DDOS ATTACK => Closing unused channels"));
888
+ errorLog(chalk.bgRed.white("PREVENTING DDOS ATTACK => maxConnection =" + this.maxConnections));
870
889
 
871
890
  const unused_channels: ServerSecureChannelLayer[] = this.getChannels().filter((channel1: ServerSecureChannelLayer) => {
872
891
  return !channel1.isOpened && !channel1.hasSession;
873
892
  });
874
893
  if (unused_channels.length === 0) {
875
894
  // all channels are in used , we cannot get any
876
-
895
+ errorLog("All Channel are in used ! let cancel this one");
877
896
  // istanbul ignore next
878
897
  console.log(" - all channel are used !!!!");
879
898
  dumpChannelInfo(this.getChannels());
880
- setImmediate(establish_connection);
899
+ setTimeout(deny_connection, 10);
881
900
  return;
882
901
  }
883
902
  // istanbul ignore next
@@ -888,7 +907,7 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
888
907
  );
889
908
  }
890
909
  const channel = unused_channels[0];
891
-
910
+ errorLog("Closing channel ", channel.hashKey);
892
911
  channel.close(() => {
893
912
  // istanbul ignore next
894
913
  if (doDebug) {
@@ -26,7 +26,6 @@ import {
26
26
  UAVariable,
27
27
  UAServerDiagnostics,
28
28
  BindVariableOptions,
29
- MethodFunctorCallback,
30
29
  ISessionContext,
31
30
  DTServerStatus,
32
31
  resolveOpaqueOnAddressSpace,
@@ -81,7 +80,8 @@ import {
81
80
  WriteValue,
82
81
  ReadValueId,
83
82
  TimeZoneDataType,
84
- ProgramDiagnosticDataType
83
+ ProgramDiagnosticDataType,
84
+ CallMethodResultOptions
85
85
  } from "node-opcua-types";
86
86
  import { DataType, isValidVariant, Variant, VariantArrayType } from "node-opcua-variant";
87
87
 
@@ -116,7 +116,7 @@ function setSubscriptionDurable(
116
116
  this: ServerEngine,
117
117
  inputArguments: Variant[],
118
118
  context: ISessionContext,
119
- callback: MethodFunctorCallback
119
+ callback: CallbackT<CallMethodResultOptions>
120
120
  ) {
121
121
  // see https://reference.opcfoundation.org/v104/Core/docs/Part5/9.3/
122
122
  // https://reference.opcfoundation.org/v104/Core/docs/Part4/6.8/
@@ -201,7 +201,7 @@ function getMonitoredItemsId(
201
201
  this: ServerEngine,
202
202
  inputArguments: Variant[],
203
203
  context: ISessionContext,
204
- callback: MethodFunctorCallback
204
+ callback: CallbackT<CallMethodResultOptions>
205
205
  ) {
206
206
  assert(Array.isArray(inputArguments));
207
207
  assert(typeof callback === "function");
@@ -229,8 +229,8 @@ function getMonitoredItemsId(
229
229
  const callMethodResult = new CallMethodResult({
230
230
  statusCode: result.statusCode,
231
231
  outputArguments: [
232
- { dataType: DataType.UInt32, arrayType: VariantArrayType.Array, value: new Uint32Array(result.serverHandles) },
233
- { dataType: DataType.UInt32, arrayType: VariantArrayType.Array, value: new Uint32Array(result.clientHandles) }
232
+ { dataType: DataType.UInt32, arrayType: VariantArrayType.Array, value: result.serverHandles },
233
+ { dataType: DataType.UInt32, arrayType: VariantArrayType.Array, value: result.clientHandles }
234
234
  ]
235
235
  });
236
236
  callback(null, callMethodResult);
@@ -473,8 +473,7 @@ export class ServerEngine extends EventEmitter {
473
473
  this._orphanPublishEngine.shutdown();
474
474
  }
475
475
 
476
- // xx console.log("xxxxxxxxx ServerEngine.shutdown must terminate "+ tokens.length," sessions");
477
-
476
+
478
477
  for (const token of tokens) {
479
478
  this.closeSession(token, true, "Terminated");
480
479
  }
@@ -642,7 +641,7 @@ export class ServerEngine extends EventEmitter {
642
641
  public setServerState(serverState: ServerState): void {
643
642
  assert(serverState !== null && serverState !== undefined);
644
643
  this.addressSpace?.rootFolder?.objects?.server?.serverStatus?.state?.setValueFromSource({
645
- dataType: DataType.UInt32,
644
+ dataType: DataType.Int32,
646
645
  value: serverState
647
646
  });
648
647
  }
@@ -817,8 +816,7 @@ export class ServerEngine extends EventEmitter {
817
816
 
818
817
  // TimeZoneDataType
819
818
  const timeZoneDataType = addressSpace.findDataType(resolveNodeId(DataTypeIds.TimeZoneDataType))!;
820
- // xx console.log(timeZoneDataType.toString());
821
-
819
+
822
820
  const timeZone = new TimeZoneDataType({
823
821
  daylightSavingInOffset: /* boolean*/ false,
824
822
  offset: /* int16 */ 0
@@ -1076,36 +1074,36 @@ export class ServerEngine extends EventEmitter {
1076
1074
  // mainly for compliance
1077
1075
 
1078
1076
  // The version number for the data type description. i=104
1079
- bindStandardScalar(VariableIds.DataTypeDescriptionType_DataTypeVersion, DataType.UInt16, () => {
1080
- return 0.0;
1077
+ bindStandardScalar(VariableIds.DataTypeDescriptionType_DataTypeVersion, DataType.String, () => {
1078
+ return "0";
1081
1079
  });
1082
1080
 
1083
1081
  const namingRuleDataTypeNode = addressSpace.findDataType(resolveNodeId(DataTypeIds.NamingRuleType))! as UADataType;
1084
- // xx console.log(nrt.toString());
1082
+
1085
1083
  if (namingRuleDataTypeNode) {
1086
1084
  const namingRuleType = (namingRuleDataTypeNode as any)._getEnumerationInfo().nameIndex; // getEnumeration("NamingRuleType");
1087
1085
  if (!namingRuleType) {
1088
1086
  throw new Error("Cannot find Enumeration definition for NamingRuleType");
1089
1087
  }
1090
1088
  // i=111
1091
- bindStandardScalar(VariableIds.ModellingRuleType_NamingRule, DataType.UInt16, () => {
1089
+ bindStandardScalar(VariableIds.ModellingRuleType_NamingRule, DataType.Int32, () => {
1092
1090
  return 0;
1093
1091
  });
1094
1092
 
1095
1093
  // i=112
1096
- bindStandardScalar(VariableIds.ModellingRule_Mandatory_NamingRule, DataType.UInt16, () => {
1094
+ bindStandardScalar(VariableIds.ModellingRule_Mandatory_NamingRule, DataType.Int32, () => {
1097
1095
  return namingRuleType.Mandatory ? namingRuleType.Mandatory.value : 0;
1098
1096
  });
1099
1097
 
1100
1098
  // i=113
1101
- bindStandardScalar(VariableIds.ModellingRule_Optional_NamingRule, DataType.UInt16, () => {
1099
+ bindStandardScalar(VariableIds.ModellingRule_Optional_NamingRule, DataType.Int32, () => {
1102
1100
  return namingRuleType.Optional ? namingRuleType.Optional.value : 0;
1103
1101
  });
1104
1102
  // i=114
1105
- bindStandardScalar(VariableIds.ModellingRule_ExposesItsArray_NamingRule, DataType.UInt16, () => {
1103
+ bindStandardScalar(VariableIds.ModellingRule_ExposesItsArray_NamingRule, DataType.Int32, () => {
1106
1104
  return namingRuleType.ExposesItsArray ? namingRuleType.ExposesItsArray.value : 0;
1107
1105
  });
1108
- bindStandardScalar(VariableIds.ModellingRule_MandatoryPlaceholder_NamingRule, DataType.UInt16, () => {
1106
+ bindStandardScalar(VariableIds.ModellingRule_MandatoryPlaceholder_NamingRule, DataType.Int32, () => {
1109
1107
  return namingRuleType.MandatoryPlaceholder ? namingRuleType.MandatoryPlaceholder.value : 0;
1110
1108
  });
1111
1109
  }
@@ -1162,7 +1160,6 @@ export class ServerEngine extends EventEmitter {
1162
1160
  if (samplingIntervalDiagnosticsArray) {
1163
1161
  addressSpace.deleteNode(samplingIntervalDiagnosticsArray);
1164
1162
  const s = serverDiagnosticsNode.getComponents();
1165
- // xx console.log(s.map((x) => x.browseName.toString()).join(" "));
1166
1163
  }
1167
1164
 
1168
1165
  const subscriptionDiagnosticsArrayNode = serverDiagnosticsNode.getComponentByName(
@@ -1233,9 +1230,9 @@ export class ServerEngine extends EventEmitter {
1233
1230
  node.onFirstBrowseAction = undefined;
1234
1231
  } catch (err) {
1235
1232
  if (err instanceof Error) {
1236
- console.log("onFirstBrowseAction method has failed", err.message);
1233
+ errorLog("onFirstBrowseAction method has failed", err.message);
1237
1234
  }
1238
- console.log(err);
1235
+ errorLog(err);
1239
1236
  }
1240
1237
  assert(node.onFirstBrowseAction === undefined, "expansion can only be made once");
1241
1238
  }
@@ -1720,7 +1717,6 @@ export class ServerEngine extends EventEmitter {
1720
1717
  }
1721
1718
  const subscription = session.publishEngine.getSubscriptionById(subscriptionId);
1722
1719
  if (subscription) {
1723
- // xx console.log("foundSubscription ", subscriptionId, " in session", session.sessionName);
1724
1720
  subscriptions.push(subscription);
1725
1721
  }
1726
1722
  });
@@ -1880,7 +1876,7 @@ export class ServerEngine extends EventEmitter {
1880
1876
  const referenceTime = new Date(Date.now() - maxAge);
1881
1877
 
1882
1878
  assert(callback instanceof Function);
1883
- const objectMap: any = {};
1879
+ const objectMap: Record<string, BaseNode> = {};
1884
1880
  for (const nodeToRefresh of nodesToRefresh) {
1885
1881
  // only consider node for which the caller wants to read the Value attribute
1886
1882
  // assuming that Value is requested if attributeId is missing,
@@ -1903,13 +1899,15 @@ export class ServerEngine extends EventEmitter {
1903
1899
 
1904
1900
  objectMap[key] = obj;
1905
1901
  }
1906
- if (Object.keys(objectMap).length === 0) {
1902
+
1903
+ const objectArray = Object.values(objectMap);
1904
+ if (objectArray.length === 0) {
1907
1905
  // nothing to do
1908
1906
  return callback(null, []);
1909
1907
  }
1910
1908
  // perform all asyncRefresh in parallel
1911
1909
  async.map(
1912
- objectMap,
1910
+ objectArray,
1913
1911
  (obj: BaseNode, inner_callback: DataValueCallback) => {
1914
1912
  if (obj.nodeClass !== NodeClass.Variable) {
1915
1913
  inner_callback(
@@ -1920,7 +1918,19 @@ export class ServerEngine extends EventEmitter {
1920
1918
  );
1921
1919
  return;
1922
1920
  }
1923
- (obj as UAVariable).asyncRefresh(referenceTime, inner_callback);
1921
+ try {
1922
+ (obj as UAVariable).asyncRefresh(referenceTime, (err, dataValue) => {
1923
+ inner_callback(err, dataValue);
1924
+ });
1925
+ } catch (err) {
1926
+
1927
+ // istanbul ignore next
1928
+ if (!(err instanceof Error)) {
1929
+ throw new Error("internal error");
1930
+ }
1931
+ errorLog("asyncRefresh internal error", err);
1932
+ inner_callback(err);
1933
+ }
1924
1934
  },
1925
1935
  (err?: Error | null, arrResult?: (DataValue | undefined)[]) => {
1926
1936
  callback(err || null, arrResult as DataValue[]);
@@ -2087,7 +2097,7 @@ export class ServerEngine extends EventEmitter {
2087
2097
  "HistoryReadDetails " +
2088
2098
  historyReadDetails.toString();
2089
2099
  if (doDebug) {
2090
- console.log(chalk.cyan("ServerEngine#_historyReadSingleNode "), chalk.white.bold(msg));
2100
+ debugLog(chalk.cyan("ServerEngine#_historyReadSingleNode "), chalk.white.bold(msg));
2091
2101
  }
2092
2102
  const err = new Error(msg);
2093
2103
  // object has no historyRead method
@@ -2132,13 +2142,12 @@ export class ServerEngine extends EventEmitter {
2132
2142
  if (methodNode && methodNode.bindMethod) {
2133
2143
  methodNode.bindMethod(func);
2134
2144
  } else {
2135
- console.log(
2145
+ warningLog(
2136
2146
  chalk.yellow("WARNING: cannot bind a method with id ") +
2137
2147
  chalk.cyan(nodeId.toString()) +
2138
2148
  chalk.yellow(". please check your nodeset.xml file or add this node programmatically")
2139
2149
  );
2140
-
2141
- console.log(traceFromThisProjectOnly());
2150
+ warningLog(traceFromThisProjectOnly());
2142
2151
  }
2143
2152
  }
2144
2153
 
@@ -23,7 +23,7 @@ import {
23
23
  } from "node-opcua-address-space";
24
24
 
25
25
  import { assert } from "node-opcua-assert";
26
- import { randomGuid } from "node-opcua-basic-types";
26
+ import { minOPCUADate, randomGuid } from "node-opcua-basic-types";
27
27
  import { SessionDiagnosticsDataType, SessionSecurityDiagnosticsDataType, SubscriptionDiagnosticsDataType } from "node-opcua-common";
28
28
  import { QualifiedName, NodeClass } from "node-opcua-data-model";
29
29
  import { checkDebugFlag, make_debugLog } from "node-opcua-debug";
@@ -34,7 +34,7 @@ import { WatchDog } from "node-opcua-utils";
34
34
  import { lowerFirstLetter } from "node-opcua-utils";
35
35
  import { ISubscriber, IWatchdogData2 } from "node-opcua-utils";
36
36
 
37
- import { IServerSession, ServerSecureChannelLayer } from "node-opcua-secure-channel";
37
+ import { IServerSession, IServerSessionBase, ServerSecureChannelLayer } from "node-opcua-secure-channel";
38
38
  import { ApplicationDescription, UserIdentityToken, CreateSubscriptionRequestOptions, EndpointDescription } from "node-opcua-types";
39
39
 
40
40
  import { ServerSidePublishEngine } from "./server_publish_engine";
@@ -93,7 +93,7 @@ interface SessionSecurityDiagnosticsDataTypeEx extends SessionSecurityDiagnostic
93
93
  * SessionDiagnosticsArray Variable and notifies any other Clients who were subscribed to this entry.
94
94
  *
95
95
  */
96
- export class ServerSession extends EventEmitter implements ISubscriber, ISessionBase, IServerSession {
96
+ export class ServerSession extends EventEmitter implements ISubscriber, ISessionBase, IServerSession, IServerSessionBase {
97
97
  public static registry = new ObjectRegistry();
98
98
  public static maxPublishRequestInQueue = 100;
99
99
 
@@ -196,7 +196,7 @@ export class ServerSession extends EventEmitter implements ISubscriber, ISession
196
196
  assert(!this.sessionObject, " sessionObject has not been cleared !");
197
197
 
198
198
  this.parent = null as any as ServerEngine;
199
- this.authenticationToken = NodeId.nullNodeId;
199
+ this.authenticationToken = new NodeId();
200
200
 
201
201
  if (this.publishEngine) {
202
202
  this.publishEngine.dispose();
@@ -219,8 +219,13 @@ export class ServerSession extends EventEmitter implements ISubscriber, ISession
219
219
  return this.creationDate;
220
220
  }
221
221
 
222
+ /**
223
+ * return the number of milisecond since last session transaction occurs from client
224
+ * the first transaction is the creation of the session
225
+ */
222
226
  public get clientLastContactTime(): number {
223
- return this._watchDogData!.lastSeen;
227
+ const lastSeen = this._watchDogData ? this._watchDogData.lastSeen : minOPCUADate.getTime();
228
+ return WatchDog.lastSeenToDuration(lastSeen);
224
229
  }
225
230
 
226
231
  public get status(): string {
@@ -435,6 +440,9 @@ export class ServerSession extends EventEmitter implements ISubscriber, ISession
435
440
  assert(this.currentSubscriptionCount === 0);
436
441
 
437
442
  this.status = "closed";
443
+
444
+ this._detach_channel();
445
+
438
446
  /**
439
447
  * @event session_closed
440
448
  * @param deleteSubscriptions {Boolean}
@@ -547,8 +555,12 @@ export class ServerSession extends EventEmitter implements ISubscriber, ISession
547
555
 
548
556
  public _detach_channel(): void {
549
557
  const channel = this.channel;
558
+
559
+ // istanbul ignore next
550
560
  if (!channel) {
551
- throw new Error("expecting a valid channel");
561
+ return;
562
+ // already detached !
563
+ // throw new Error("expecting a valid channel");
552
564
  }
553
565
  assert(this.nonce && this.nonce instanceof Buffer);
554
566
  assert(this.authenticationToken);
@@ -407,12 +407,12 @@ export interface GetMonitoredItemsResult {
407
407
  * array of serverHandles for all MonitoredItems of the subscription
408
408
  * identified by subscriptionId.
409
409
  */
410
- serverHandles: number[];
410
+ serverHandles: Uint32Array;
411
411
  /**
412
412
  * array of clientHandles for all MonitoredItems of the subscription
413
413
  * identified by subscriptionId.
414
414
  */
415
- clientHandles: number[];
415
+ clientHandles: Uint32Array;
416
416
  statusCode: StatusCode;
417
417
  }
418
418
 
@@ -790,7 +790,7 @@ export class Subscription extends EventEmitter {
790
790
  this._pending_notifications.clear();
791
791
  this._sent_notification_messages = [];
792
792
 
793
- this.sessionId = NodeId.nullNodeId;
793
+ this.sessionId = new NodeId();
794
794
 
795
795
  this.$session = undefined;
796
796
  this.removeAllListeners();
@@ -1143,19 +1143,23 @@ export class Subscription extends EventEmitter {
1143
1143
  *
1144
1144
  */
1145
1145
  public getMonitoredItems(): GetMonitoredItemsResult {
1146
+ const monitoredItems = Object.keys(this.monitoredItems);
1147
+ const monitoredItemCount = monitoredItems.length;
1146
1148
  const result: GetMonitoredItemsResult = {
1147
- clientHandles: [] as number[],
1148
- serverHandles: [] as number[],
1149
+ clientHandles: new Uint32Array(monitoredItemCount),
1150
+ serverHandles: new Uint32Array(monitoredItemCount),
1149
1151
  statusCode: StatusCodes.Good
1150
1152
  };
1151
- for (const monitoredItemId of Object.keys(this.monitoredItems)) {
1152
- const monitoredItem = this.getMonitoredItem(parseInt(monitoredItemId, 10))!;
1153
- result.clientHandles.push(monitoredItem.clientHandle!);
1153
+ for (let index = 0; index < monitoredItemCount; index++) {
1154
+ const monitoredItemId = monitoredItems[index];
1155
+ const serverHandle = parseInt(monitoredItemId, 10);
1156
+ const monitoredItem = this.getMonitoredItem(serverHandle)!;
1157
+ result.clientHandles[index] = monitoredItem.clientHandle;
1154
1158
  // TODO: serverHandle is defined anywhere in the OPCUA Specification 1.02
1155
1159
  // I am not sure what shall be reported for serverHandle...
1156
1160
  // using monitoredItem.monitoredItemId instead...
1157
1161
  // May be a clarification in the OPCUA Spec is required.
1158
- result.serverHandles.push(parseInt(monitoredItemId, 10));
1162
+ result.serverHandles[index] = serverHandle;
1159
1163
  }
1160
1164
  return result;
1161
1165
  }
@@ -1 +1 @@
1
- require("node-opcua-pki/bin/crypto_create_CA");
1
+ require("node-opcua-pki/bin/crypto_create_CA");