node-opcua-server 2.72.0 → 2.73.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.
Files changed (41) hide show
  1. package/dist/helper.d.ts +10 -0
  2. package/dist/helper.js +76 -0
  3. package/dist/helper.js.map +1 -0
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.js +1 -0
  6. package/dist/index.js.map +1 -1
  7. package/dist/monitored_item.js +2 -1
  8. package/dist/monitored_item.js.map +1 -1
  9. package/dist/opcua_server.js +5 -3
  10. package/dist/opcua_server.js.map +1 -1
  11. package/dist/server_capabilities.js +5 -4
  12. package/dist/server_capabilities.js.map +1 -1
  13. package/dist/server_engine.d.ts +1 -1
  14. package/dist/server_engine.js +25 -25
  15. package/dist/server_engine.js.map +1 -1
  16. package/dist/server_publish_engine.d.ts +1 -1
  17. package/dist/server_publish_engine.js +11 -6
  18. package/dist/server_publish_engine.js.map +1 -1
  19. package/dist/server_publish_engine_for_orphan_subscriptions.js +3 -1
  20. package/dist/server_publish_engine_for_orphan_subscriptions.js.map +1 -1
  21. package/dist/server_session.d.ts +4 -3
  22. package/dist/server_session.js +7 -6
  23. package/dist/server_session.js.map +1 -1
  24. package/dist/server_subscription.d.ts +8 -2
  25. package/dist/server_subscription.js +70 -59
  26. package/dist/server_subscription.js.map +1 -1
  27. package/dist/sessions_compatible_for_transfer.d.ts +1 -1
  28. package/dist/sessions_compatible_for_transfer.js +3 -0
  29. package/dist/sessions_compatible_for_transfer.js.map +1 -1
  30. package/package.json +38 -37
  31. package/source/helper.ts +87 -0
  32. package/source/index.ts +2 -1
  33. package/source/monitored_item.ts +3 -2
  34. package/source/opcua_server.ts +5 -3
  35. package/source/server_capabilities.ts +5 -4
  36. package/source/server_engine.ts +29 -27
  37. package/source/server_publish_engine.ts +15 -7
  38. package/source/server_publish_engine_for_orphan_subscriptions.ts +6 -1
  39. package/source/server_session.ts +15 -13
  40. package/source/server_subscription.ts +83 -69
  41. package/source/sessions_compatible_for_transfer.ts +5 -1
@@ -0,0 +1,87 @@
1
+ import * as util from "util";
2
+ import { OPCUAServer } from "./opcua_server";
3
+ import { ServerEngine } from "./server_engine";
4
+ import { ServerSession } from "./server_session";
5
+ import { Subscription, SubscriptionState } from "./server_subscription";
6
+
7
+ const consolelog = (...args: any) => {
8
+ const d = new Date();
9
+ const t = d.toTimeString().split(" ")[0] + "." + d.getMilliseconds().toString().padStart(3, "0");
10
+ console.log.apply(console, [t, ...args]);
11
+ };
12
+ const doDebug = false;
13
+
14
+ /**
15
+ *
16
+ * @private
17
+ */
18
+ export function installSessionLogging(server: OPCUAServer) {
19
+ installSessionLoggingOnEngine(server.engine);
20
+ }
21
+
22
+ const info = (subscription: Subscription) => {
23
+ return util.format(
24
+ subscription.subscriptionId,
25
+ SubscriptionState[subscription.state].padEnd(9),
26
+ subscription.state,
27
+ "kac=",
28
+ subscription.currentKeepAliveCount.toString().padStart(3)+
29
+ "/"+
30
+ subscription.maxKeepAliveCount.toString().padStart(3),
31
+ "ltc=",
32
+ subscription.currentLifetimeCount.toString().padStart(3)+
33
+ "/"+
34
+ subscription.lifeTimeCount.toString().padStart(3),
35
+ "prc=",
36
+ subscription.publishEngine?.pendingPublishRequestCount.toString().padStart(3),
37
+ "pi=",
38
+ subscription.publishingInterval
39
+ );
40
+ };
41
+
42
+ export function installSubscriptionMonitoring(subscription: Subscription) {
43
+ consolelog("new_subscription", subscription.subscriptionId, info(subscription));
44
+ subscription.on("lifeTimeExpired", () => {
45
+ consolelog("lifeTimeExpired".padEnd(45), info(subscription));
46
+ });
47
+ if (true || doDebug) {
48
+ subscription.on("lifeTimeCounterChanged", (ltc: number) => {
49
+ consolelog("subscription lifeTimeCounterChanged".padEnd(45), info(subscription));
50
+ });
51
+ }
52
+ subscription.on("expired", () => {
53
+ consolelog("subscription expired".padEnd(45), info(subscription));
54
+ });
55
+ subscription.on("stateChanged", (state: SubscriptionState) => {
56
+ consolelog("subscription stateChanged".padEnd(45), info(subscription));
57
+ });
58
+ subscription.on("terminate", () => {
59
+ consolelog("subscription terminated".padEnd(45), info(subscription));
60
+ });
61
+ subscription.on("keepalive", () => {
62
+ consolelog("subscription keepalive".padEnd(45), info(subscription));
63
+ });
64
+ }
65
+ export function installSessionLoggingOnEngine(serverEngine: ServerEngine) {
66
+ function on_create_session(session: ServerSession) {
67
+ try {
68
+ session.on("activate_session", function () {
69
+ consolelog("activate_session");
70
+ });
71
+
72
+ session.on("statusChanged", (status) => {
73
+ consolelog("session status changed: ", status);
74
+ });
75
+ session.on("new_subscription", function (subscription) {
76
+ installSubscriptionMonitoring(subscription);
77
+ });
78
+ } catch (err) {
79
+ consolelog((err as any).message);
80
+ }
81
+ }
82
+ serverEngine.on("create_session", on_create_session);
83
+ serverEngine.once("session_closed", function (session) {
84
+ consolelog("session is closed");
85
+ serverEngine.removeListener("create_session", on_create_session);
86
+ });
87
+ }
package/source/index.ts CHANGED
@@ -12,4 +12,5 @@ export * from "./server_capabilities";
12
12
  export * from "./server_engine";
13
13
  export * from "./opcua_server";
14
14
  export * from "./monitored_item";
15
- export * from "./user_manager";
15
+ export * from "./user_manager";
16
+ export * from "./helper";
@@ -25,7 +25,7 @@ import {
25
25
  coerceTimestampsToReturn,
26
26
  sameStatusCode
27
27
  } from "node-opcua-data-value";
28
- import { checkDebugFlag, make_debugLog } from "node-opcua-debug";
28
+ import { checkDebugFlag, make_debugLog, make_warningLog } from "node-opcua-debug";
29
29
  import { ExtensionObject } from "node-opcua-extension-object";
30
30
  import { NodeId } from "node-opcua-nodeid";
31
31
  import { NumericalRange0, NumericRange } from "node-opcua-numeric-range";
@@ -71,6 +71,7 @@ const defaultItemToMonitor: ReadValueIdOptions = new ReadValueId({
71
71
 
72
72
  const debugLog = make_debugLog(__filename);
73
73
  const doDebug = checkDebugFlag(__filename);
74
+ const warningLog = make_warningLog(__filename);
74
75
 
75
76
  function _adjust_sampling_interval(samplingInterval: number, node_minimumSamplingInterval: number): number {
76
77
  assert(typeof node_minimumSamplingInterval === "number", "expecting a number");
@@ -216,7 +217,7 @@ function apply_dataChange_filter(this: MonitoredItem, newDataValue: DataValue, o
216
217
  debugLog("timestampHasChanged ", timestampHasChanged(newDataValue.sourceTimestamp, oldDataValue.sourceTimestamp));
217
218
  }
218
219
  } catch (err) {
219
- console.log(err);
220
+ warningLog(err);
220
221
  }
221
222
  }
222
223
  switch (trigger) {
@@ -203,6 +203,7 @@ const default_build_info: BuildInfoOptions = {
203
203
  const minSessionTimeout = 100; // 100 milliseconds
204
204
  const defaultSessionTimeout = 1000 * 30; // 30 seconds
205
205
  const maxSessionTimeout = 1000 * 60 * 50; // 50 minutes
206
+ let unnamed_session_count = 0;
206
207
 
207
208
  type ResponseClassType =
208
209
  | typeof BrowseResponse
@@ -252,7 +253,7 @@ function moveSessionToChannel(session: ServerSession, channel: ServerSecureChann
252
253
  }
253
254
 
254
255
  async function _attempt_to_close_some_old_unactivated_session(server: OPCUAServer) {
255
- const session = server.engine!.getOldestUnactivatedSession();
256
+ const session = server.engine!.getOldestInactiveSession();
256
257
  if (session) {
257
258
  await server.engine!.closeSession(session.authenticationToken, false, "Forcing");
258
259
  }
@@ -1786,7 +1787,7 @@ export class OPCUAServer extends OPCUABaseServer {
1786
1787
  assert(session.sessionTimeout === revisedSessionTimeout);
1787
1788
 
1788
1789
  session.clientDescription = request.clientDescription;
1789
- session.sessionName = request.sessionName || "<unknown session name>";
1790
+ session.sessionName = request.sessionName || `<unknown session name ${unnamed_session_count++}>`;
1790
1791
 
1791
1792
  // Depending upon on the SecurityPolicy and the SecurityMode of the SecureChannel, the exchange of
1792
1793
  // ApplicationInstanceCertificates and Nonces may be optional and the signatures may be empty. See
@@ -2239,6 +2240,7 @@ export class OPCUAServer extends OPCUABaseServer {
2239
2240
  }
2240
2241
  return channel.send_response("MSG", response1, message);
2241
2242
  } catch (err) {
2243
+ warningLog(err);
2242
2244
  // istanbul ignore next
2243
2245
  if (err instanceof Error) {
2244
2246
  // istanbul ignore next
@@ -3136,7 +3138,7 @@ export class OPCUAServer extends OPCUABaseServer {
3136
3138
 
3137
3139
  sendResponse(response);
3138
3140
  } catch (err) {
3139
- console.log(err);
3141
+ warningLog(err);
3140
3142
  return sendError(StatusCodes.BadInternalError);
3141
3143
  }
3142
3144
  }
@@ -154,9 +154,9 @@ export type ServerCapabilitiesOptions = Partial<IServerCapabilities>;
154
154
  export const defaultServerCapabilities: IServerCapabilities = {
155
155
  maxBrowseContinuationPoints: 0,
156
156
  maxHistoryContinuationPoints: 0,
157
- maxStringLength: 65535,
158
- maxArrayLength: 65535,
159
- maxByteStringLength: 65535,
157
+ maxStringLength: 16 * 1024 * 1024,
158
+ maxArrayLength: 1024 * 1024,
159
+ maxByteStringLength: 16 * 1024 * 1024,
160
160
  maxQueryContinuationPoints: 0,
161
161
 
162
162
  minSupportedSampleRate: 100,
@@ -245,7 +245,8 @@ export class ServerCapabilities implements IServerCapabilities {
245
245
  // new in 1.05
246
246
  this.maxSessions = options.maxSessions || defaultServerCapabilities.maxSessions;
247
247
 
248
- this.maxSubscriptionsPerSession = options.maxSubscriptionsPerSession || defaultServerCapabilities.maxSubscriptionsPerSession;
248
+ this.maxSubscriptionsPerSession =
249
+ options.maxSubscriptionsPerSession || defaultServerCapabilities.maxSubscriptionsPerSession;
249
250
  this.maxSubscriptions = options.maxSubscriptions || defaultServerCapabilities.maxSubscriptions;
250
251
  this.maxMonitoredItems = options.maxMonitoredItems || defaultServerCapabilities.maxMonitoredItems;
251
252
  this.maxMonitoredItemsPerSubscription =
@@ -5,7 +5,7 @@ import { EventEmitter } from "events";
5
5
  import * as async from "async";
6
6
  import * as chalk from "chalk";
7
7
  import { assert } from "node-opcua-assert";
8
-
8
+ import { BinaryStream} from "node-opcua-binary-stream";
9
9
  import {
10
10
  addElement,
11
11
  AddressSpace,
@@ -399,6 +399,8 @@ export class ServerEngine extends EventEmitter {
399
399
  Object.values(this._sessions).forEach((session: ServerSession) => {
400
400
  counter += session.currentSubscriptionCount;
401
401
  });
402
+ // we also need to add the orphan subscriptions
403
+ counter += this._orphanPublishEngine ? this._orphanPublishEngine .subscriptions.length : 0;
402
404
  return counter;
403
405
  });
404
406
 
@@ -1007,15 +1009,15 @@ export class ServerEngine extends EventEmitter {
1007
1009
  );
1008
1010
 
1009
1011
  bindStandardScalar(VariableIds.Server_ServerCapabilities_MaxArrayLength, DataType.UInt32, () => {
1010
- return this.serverCapabilities.maxArrayLength;
1012
+ return Math.min(this.serverCapabilities.maxArrayLength, Variant.maxArrayLength);
1011
1013
  });
1012
1014
 
1013
1015
  bindStandardScalar(VariableIds.Server_ServerCapabilities_MaxStringLength, DataType.UInt32, () => {
1014
- return this.serverCapabilities.maxStringLength;
1016
+ return Math.min(this.serverCapabilities.maxStringLength, BinaryStream.maxStringLength);
1015
1017
  });
1016
1018
 
1017
1019
  bindStandardScalar(VariableIds.Server_ServerCapabilities_MaxByteStringLength, DataType.UInt32, () => {
1018
- return this.serverCapabilities.maxByteStringLength;
1020
+ return Math.min(this.serverCapabilities.maxByteStringLength, BinaryStream.maxByteStringLength);
1019
1021
  });
1020
1022
 
1021
1023
  const bindOperationLimits = (operationLimits: OperationLimits) => {
@@ -1598,13 +1600,17 @@ export class ServerEngine extends EventEmitter {
1598
1600
  });
1599
1601
  }
1600
1602
 
1601
- public getOldestUnactivatedSession(): ServerSession | null {
1602
- const tmp = Object.values(this._sessions).filter((session1: ServerSession) => {
1603
- return session1.status === "new";
1604
- });
1603
+ public getOldestInactiveSession(): ServerSession | null {
1604
+ // search screwed or closed session first
1605
+ let tmp = Object.values(this._sessions).filter(
1606
+ (session1: ServerSession) =>
1607
+ session1.status === "screwed" || session1.status === "disposed" || session1.status === "closed"
1608
+ );
1605
1609
  if (tmp.length === 0) {
1606
- return null;
1610
+ // if none available, tap into the session that are not yet activated
1611
+ tmp = Object.values(this._sessions).filter((session1: ServerSession) => session1.status === "new");
1607
1612
  }
1613
+ if (tmp.length === 0) return null;
1608
1614
  let session = tmp[0];
1609
1615
  for (let i = 1; i < tmp.length; i++) {
1610
1616
  const c = tmp[i];
@@ -1812,10 +1818,6 @@ export class ServerEngine extends EventEmitter {
1812
1818
  if (!subscription) {
1813
1819
  return new TransferResult({ statusCode: StatusCodes.BadSubscriptionIdInvalid });
1814
1820
  }
1815
- // istanbul ignore next
1816
- if (!subscription.$session) {
1817
- return new TransferResult({ statusCode: StatusCodes.BadInternalError });
1818
- }
1819
1821
 
1820
1822
  // check that session have same userIdentity
1821
1823
  if (!sessionsCompatibleForTransfer(subscription.$session, session)) {
@@ -1846,9 +1848,11 @@ export class ServerEngine extends EventEmitter {
1846
1848
 
1847
1849
  const nbSubscriptionBefore = session.publishEngine.subscriptionCount;
1848
1850
 
1849
- subscription.$session._unexposeSubscriptionDiagnostics(subscription);
1851
+ if (subscription.$session) {
1852
+ subscription.$session._unexposeSubscriptionDiagnostics(subscription);
1853
+ }
1854
+
1850
1855
  await ServerSidePublishEngine.transferSubscription(subscription, session.publishEngine, sendInitialValues);
1851
-
1852
1856
  subscription.$session = session;
1853
1857
 
1854
1858
  session._exposeSubscriptionDiagnostics(subscription);
@@ -1983,7 +1987,7 @@ export class ServerEngine extends EventEmitter {
1983
1987
  }
1984
1988
 
1985
1989
  private _exposeSubscriptionDiagnostics(subscription: Subscription): void {
1986
- debugLog("ServerEngine#_exposeSubscriptionDiagnostics");
1990
+ debugLog("ServerEngine#_exposeSubscriptionDiagnostics", subscription.subscriptionId);
1987
1991
  const subscriptionDiagnosticsArray = this._getServerSubscriptionDiagnosticsArrayNode();
1988
1992
  const subscriptionDiagnostics = subscription.subscriptionDiagnostics;
1989
1993
  assert((subscriptionDiagnostics as any).$subscription === subscription);
@@ -1995,19 +1999,19 @@ export class ServerEngine extends EventEmitter {
1995
1999
  }
1996
2000
 
1997
2001
  protected _unexposeSubscriptionDiagnostics(subscription: Subscription): void {
1998
- const subscriptionDiagnosticsArray = this._getServerSubscriptionDiagnosticsArrayNode();
2002
+ const serverSubscriptionDiagnosticsArray = this._getServerSubscriptionDiagnosticsArrayNode();
1999
2003
  const subscriptionDiagnostics = subscription.subscriptionDiagnostics;
2000
2004
  assert(subscriptionDiagnostics instanceof SubscriptionDiagnosticsDataType);
2001
- if (subscriptionDiagnostics && subscriptionDiagnosticsArray) {
2002
- const node = (subscriptionDiagnosticsArray as any)[subscription.id];
2003
- removeElement(subscriptionDiagnosticsArray, subscriptionDiagnostics);
2005
+ if (subscriptionDiagnostics && serverSubscriptionDiagnosticsArray) {
2006
+ const node = (serverSubscriptionDiagnosticsArray as any)[subscription.id];
2007
+ removeElement(serverSubscriptionDiagnosticsArray, subscriptionDiagnostics);
2004
2008
  /*assert(
2005
2009
  !(subscriptionDiagnosticsArray as any)[subscription.id],
2006
2010
  " subscription node must have been removed from subscriptionDiagnosticsArray"
2007
2011
  );
2008
2012
  */
2009
2013
  }
2010
- debugLog("ServerEngine#_unexposeSubscriptionDiagnostics");
2014
+ debugLog("ServerEngine#_unexposeSubscriptionDiagnostics", subscription.subscriptionId);
2011
2015
  }
2012
2016
 
2013
2017
  /**
@@ -2200,16 +2204,14 @@ export class ServerEngine extends EventEmitter {
2200
2204
  private _getServerSubscriptionDiagnosticsArrayNode(): UADynamicVariableArray<SubscriptionDiagnosticsDataType> | null {
2201
2205
  // istanbul ignore next
2202
2206
  if (!this.addressSpace) {
2203
- if (doDebug) {
2204
- console.warn("ServerEngine#_getServerSubscriptionDiagnosticsArray : no addressSpace");
2205
- }
2207
+ doDebug && debugLog("ServerEngine#_getServerSubscriptionDiagnosticsArray : no addressSpace");
2208
+
2206
2209
  return null; // no addressSpace
2207
2210
  }
2208
2211
  const subscriptionDiagnosticsType = this.addressSpace.findVariableType("SubscriptionDiagnosticsType");
2209
2212
  if (!subscriptionDiagnosticsType) {
2210
- if (doDebug) {
2211
- console.warn("ServerEngine#_getServerSubscriptionDiagnosticsArray " + ": cannot find SubscriptionDiagnosticsType");
2212
- }
2213
+ doDebug &&
2214
+ debugLog("ServerEngine#_getServerSubscriptionDiagnosticsArray " + ": cannot find SubscriptionDiagnosticsType");
2213
2215
  }
2214
2216
 
2215
2217
  // SubscriptionDiagnosticsArray = i=2290
@@ -116,7 +116,7 @@ export class ServerSidePublishEngine extends EventEmitter implements IServerSide
116
116
  subscription: Subscription,
117
117
  destPublishEngine: ServerSidePublishEngine,
118
118
  sendInitialValues: boolean
119
- ): Promise<void> {
119
+ ): Promise<Subscription> {
120
120
  const srcPublishEngine = subscription.publishEngine as any as ServerSidePublishEngine;
121
121
 
122
122
  assert(!destPublishEngine.getSubscriptionById(subscription.id));
@@ -135,7 +135,9 @@ export class ServerSidePublishEngine extends EventEmitter implements IServerSide
135
135
  // to the old Session.
136
136
  subscription.notifyTransfer();
137
137
 
138
- destPublishEngine.add_subscription(srcPublishEngine.detach_subscription(subscription));
138
+ const tmp = srcPublishEngine.detach_subscription(subscription);
139
+ destPublishEngine.add_subscription(tmp);
140
+
139
141
  subscription.resetLifeTimeCounter();
140
142
  if (sendInitialValues) {
141
143
  /* A Boolean parameter with the following values:
@@ -158,6 +160,8 @@ export class ServerSidePublishEngine extends EventEmitter implements IServerSide
158
160
 
159
161
  assert(destPublishEngine.getSubscriptionById(subscription.id));
160
162
  assert(!srcPublishEngine.getSubscriptionById(subscription.id));
163
+
164
+ return subscription;
161
165
  }
162
166
 
163
167
  public maxPublishRequestInQueue = 0;
@@ -321,16 +325,16 @@ export class ServerSidePublishEngine extends EventEmitter implements IServerSide
321
325
  }
322
326
 
323
327
  public on_close_subscription(subscription: IClosedOrTransferredSubscription): void {
324
- debugLog("ServerSidePublishEngine#on_close_subscription", subscription.id);
328
+ doDebug && debugLog("ServerSidePublishEngine#on_close_subscription", subscription.id);
325
329
  if (subscription.hasPendingNotifications) {
326
- debugLog(
330
+ doDebug && debugLog(
327
331
  "ServerSidePublishEngine#on_close_subscription storing subscription",
328
332
  subscription.id,
329
333
  " to _closed_subscriptions because it has pending notification"
330
334
  );
331
335
  this._closed_subscriptions.push(subscription);
332
336
  } else {
333
- debugLog("ServerSidePublishEngine#on_close_subscription disposing subscription", subscription.id);
337
+ doDebug && debugLog("ServerSidePublishEngine#on_close_subscription disposing subscription", subscription.id);
334
338
  // subscription is no longer needed
335
339
  subscription.dispose();
336
340
  }
@@ -357,7 +361,7 @@ export class ServerSidePublishEngine extends EventEmitter implements IServerSide
357
361
  public findLateSubscriptions(): Subscription[] {
358
362
  const subscriptions = Object.values(this._subscriptions);
359
363
  return subscriptions.filter((subscription: Subscription) => {
360
- return subscription.state === SubscriptionState.LATE && subscription.publishingEnabled;
364
+ return (subscription.state === SubscriptionState.LATE || !subscription.messageSent) && subscription.publishingEnabled;
361
365
  });
362
366
  }
363
367
 
@@ -479,6 +483,9 @@ export class ServerSidePublishEngine extends EventEmitter implements IServerSide
479
483
  s.timeToKeepAlive +
480
484
  " m?=" +
481
485
  s.hasUncollectedMonitoredItemNotifications +
486
+ " " +
487
+ SubscriptionState[s.state] +
488
+ " " + s.messageSent +
482
489
  "]"
483
490
  )
484
491
  .join(" \n")
@@ -493,6 +500,7 @@ export class ServerSidePublishEngine extends EventEmitter implements IServerSide
493
500
  const starving_subscription = /* this.findSubscriptionWaitingForFirstPublish() || */ findLateSubscriptionSortedByPriority();
494
501
  return starving_subscription;
495
502
  }
503
+
496
504
  private _feed_late_subscription() {
497
505
  setImmediate(() => {
498
506
  if (!this.pendingPublishRequestCount) {
@@ -500,7 +508,7 @@ export class ServerSidePublishEngine extends EventEmitter implements IServerSide
500
508
  }
501
509
  const starving_subscription = this._find_starving_subscription();
502
510
  if (starving_subscription) {
503
- debugLog(chalk.bgWhite.red("feeding most late subscription subscriptionId = "), starving_subscription.id);
511
+ doDebug && debugLog(chalk.bgWhite.red("feeding most late subscription subscriptionId = "), starving_subscription.id);
504
512
  starving_subscription.process_subscription();
505
513
  }
506
514
  });
@@ -5,6 +5,7 @@
5
5
  import * as chalk from "chalk";
6
6
 
7
7
  import { checkDebugFlag, make_debugLog } from "node-opcua-debug";
8
+ import { NodeId } from "node-opcua-nodeid";
8
9
 
9
10
  import { ServerSidePublishEngine, ServerSidePublishEngineOptions } from "./server_publish_engine";
10
11
  import { Subscription } from "./server_subscription";
@@ -28,6 +29,10 @@ export class ServerSidePublishEngineForOrphanSubscription extends ServerSidePubl
28
29
 
29
30
  public add_subscription(subscription: Subscription): Subscription {
30
31
  debugLog(chalk.bgCyan.yellow.bold(" adding live subscription with id="), subscription.id, " to orphan");
32
+
33
+ // detach subscription from old seession
34
+ subscription.$session = undefined;
35
+
31
36
  super.add_subscription(subscription);
32
37
  // also add an event handler to detected when the subscription has ended
33
38
  // so we can automatically remove it from the orphan table
@@ -38,7 +43,7 @@ export class ServerSidePublishEngineForOrphanSubscription extends ServerSidePubl
38
43
  // xx publish_engine.detach_subscription(subscription);
39
44
  // Xx subscription.dispose();
40
45
  };
41
- subscription.on("expired", (subscription as any)._expired_func);
46
+ subscription.once("expired", (subscription as any)._expired_func);
42
47
  return subscription;
43
48
  }
44
49
 
@@ -72,6 +72,7 @@ interface SessionSecurityDiagnosticsDataTypeEx extends SessionSecurityDiagnostic
72
72
  $session: any;
73
73
  }
74
74
 
75
+ export type SessionStatus = "new" | "active" | "screwed" | "disposed" | "closed";
75
76
  /**
76
77
  *
77
78
  * A Server session object.
@@ -99,13 +100,12 @@ export class ServerSession extends EventEmitter implements ISubscriber, ISession
99
100
  public static registry = new ObjectRegistry();
100
101
  public static maxPublishRequestInQueue = 100;
101
102
 
102
- public __status = "";
103
+ public __status: SessionStatus = "new";
103
104
  public parent: ServerEngine;
104
105
  public authenticationToken: NodeId;
105
106
  public nodeId: NodeId;
106
107
  public sessionName = "";
107
108
 
108
-
109
109
  public publishEngine: ServerSidePublishEngine;
110
110
  public sessionObject: any;
111
111
  public readonly creationDate: Date;
@@ -227,18 +227,21 @@ export class ServerSession extends EventEmitter implements ISubscriber, ISession
227
227
  * the first transaction is the creation of the session
228
228
  */
229
229
  public get clientLastContactTime(): number {
230
- const lastSeen = this._watchDogData ? this._watchDogData.lastSeen : minOPCUADate.getTime();
230
+ const lastSeen = this._watchDogData ? this._watchDogData.lastSeen : minOPCUADate.getTime();
231
231
  return WatchDog.lastSeenToDuration(lastSeen);
232
232
  }
233
233
 
234
- public get status(): string {
234
+ public get status(): SessionStatus {
235
235
  return this.__status;
236
236
  }
237
237
 
238
- public set status(value: string) {
238
+ public set status(value: SessionStatus) {
239
239
  if (value === "active") {
240
240
  this._createSessionObjectInAddressSpace();
241
241
  }
242
+ if (this.__status !== value) {
243
+ this.emit("statusChanged", value);
244
+ }
242
245
  this.__status = value;
243
246
  }
244
247
 
@@ -304,7 +307,7 @@ export class ServerSession extends EventEmitter implements ISubscriber, ISession
304
307
  const propName = lowerFirstLetter(counterName + "Count");
305
308
  // istanbul ignore next
306
309
  if (!Object.prototype.hasOwnProperty.call(this._sessionDiagnostics, propName)) {
307
- errorLog("incrementRequestTotalCounter: cannot find", propName);
310
+ errorLog("incrementRequestTotalCounter: cannot find", propName);
308
311
  // xx return;
309
312
  } else {
310
313
  (this._sessionDiagnostics as any)[propName].totalCount += 1;
@@ -444,7 +447,7 @@ export class ServerSession extends EventEmitter implements ISubscriber, ISession
444
447
  assert(this.currentSubscriptionCount === 0);
445
448
 
446
449
  this.status = "closed";
447
-
450
+
448
451
  this._detach_channel();
449
452
 
450
453
  /**
@@ -559,10 +562,10 @@ export class ServerSession extends EventEmitter implements ISubscriber, ISession
559
562
 
560
563
  public _detach_channel(): void {
561
564
  const channel = this.channel;
562
-
565
+
563
566
  // istanbul ignore next
564
567
  if (!channel) {
565
- return;
568
+ return;
566
569
  // already detached !
567
570
  // throw new Error("expecting a valid channel");
568
571
  }
@@ -682,13 +685,13 @@ export class ServerSession extends EventEmitter implements ISubscriber, ISession
682
685
 
683
686
  Object.defineProperty(this._sessionDiagnostics, "sessionId", {
684
687
  get(this: SessionDiagnosticsDataTypeEx) {
685
- return this.$session?.nodeId;
688
+ return this.$session ? this.$session.nodeId : NodeId.nullNodeId;
686
689
  }
687
690
  });
688
691
 
689
692
  Object.defineProperty(this._sessionDiagnostics, "sessionName", {
690
693
  get(this: SessionDiagnosticsDataTypeEx) {
691
- return this.$session?.sessionName.toString();
694
+ return this.$session ? this.$session.sessionName.toString() : "";
692
695
  }
693
696
  });
694
697
 
@@ -910,8 +913,7 @@ export class ServerSession extends EventEmitter implements ISubscriber, ISession
910
913
  assert(this.nodeId instanceof NodeId);
911
914
 
912
915
  subscription.$session = this;
913
-
914
- subscription.sessionId = this.nodeId;
916
+ assert(subscription.sessionId === this.nodeId);
915
917
 
916
918
  this._cumulatedSubscriptionCount += 1;
917
919