node-opcua-server 2.72.2 → 2.74.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 (42) hide show
  1. package/LICENSE +3 -1
  2. package/dist/helper.d.ts +10 -0
  3. package/dist/helper.js +76 -0
  4. package/dist/helper.js.map +1 -0
  5. package/dist/index.d.ts +1 -0
  6. package/dist/index.js +1 -0
  7. package/dist/index.js.map +1 -1
  8. package/dist/monitored_item.js +2 -1
  9. package/dist/monitored_item.js.map +1 -1
  10. package/dist/opcua_server.js +26 -22
  11. package/dist/opcua_server.js.map +1 -1
  12. package/dist/server_capabilities.js +5 -4
  13. package/dist/server_capabilities.js.map +1 -1
  14. package/dist/server_engine.d.ts +1 -1
  15. package/dist/server_engine.js +25 -25
  16. package/dist/server_engine.js.map +1 -1
  17. package/dist/server_publish_engine.d.ts +6 -5
  18. package/dist/server_publish_engine.js +22 -11
  19. package/dist/server_publish_engine.js.map +1 -1
  20. package/dist/server_publish_engine_for_orphan_subscriptions.js +3 -1
  21. package/dist/server_publish_engine_for_orphan_subscriptions.js.map +1 -1
  22. package/dist/server_session.d.ts +4 -3
  23. package/dist/server_session.js +7 -6
  24. package/dist/server_session.js.map +1 -1
  25. package/dist/server_subscription.d.ts +8 -2
  26. package/dist/server_subscription.js +75 -62
  27. package/dist/server_subscription.js.map +1 -1
  28. package/dist/sessions_compatible_for_transfer.d.ts +1 -1
  29. package/dist/sessions_compatible_for_transfer.js +3 -0
  30. package/dist/sessions_compatible_for_transfer.js.map +1 -1
  31. package/package.json +48 -47
  32. package/source/helper.ts +87 -0
  33. package/source/index.ts +2 -1
  34. package/source/monitored_item.ts +3 -2
  35. package/source/opcua_server.ts +27 -26
  36. package/source/server_capabilities.ts +5 -4
  37. package/source/server_engine.ts +29 -27
  38. package/source/server_publish_engine.ts +34 -21
  39. package/source/server_publish_engine_for_orphan_subscriptions.ts +6 -1
  40. package/source/server_session.ts +15 -13
  41. package/source/server_subscription.ts +89 -72
  42. package/source/sessions_compatible_for_transfer.ts +5 -1
@@ -91,9 +91,11 @@ function _adjust_lifeTimeCount(lifeTimeCount: number, maxKeepAliveCount: number,
91
91
  // "The lifetime count shall be a minimum of three times the keep keep-alive count."
92
92
  lifeTimeCount = Math.max(lifeTimeCount, maxKeepAliveCount * 3);
93
93
 
94
- const minTicks = Math.ceil((5 * 1000) / publishingInterval); // we want 5 seconds min
94
+ const minTicks = Math.ceil(Subscription.minimumLifetimeDuration / publishingInterval);
95
+ const maxTicks = Math.floor(Subscription.maximumLifetimeDuration / publishingInterval);
95
96
 
96
97
  lifeTimeCount = Math.max(minTicks, lifeTimeCount);
98
+ lifeTimeCount = Math.min(maxTicks, lifeTimeCount);
97
99
  return lifeTimeCount;
98
100
  }
99
101
 
@@ -186,7 +188,7 @@ function createSubscriptionDiagnostics(subscription: Subscription): Subscription
186
188
 
187
189
  const subscriptionDiagnostics = new SubscriptionDiagnosticsDataType({});
188
190
 
189
- const subscription_subscriptionDiagnostics = subscriptionDiagnostics as any;
191
+ const subscription_subscriptionDiagnostics = subscriptionDiagnostics as SubscriptionDiagnosticsDataTypePriv as any;
190
192
  subscription_subscriptionDiagnostics.$subscription = subscription;
191
193
  // "sessionId"
192
194
  subscription_subscriptionDiagnostics.__defineGetter__(
@@ -300,13 +302,29 @@ function createSubscriptionDiagnostics(subscription: Subscription): Subscription
300
302
  "transferredToAltClientCount",
301
303
  "transferredToSameClientCount",
302
304
  "latePublishRequestCount",
303
- "currentKeepAliveCount",
304
- "currentLifetimeCount",
305
305
  "unacknowledgedMessageCount",
306
306
  "discardedMessageCount",
307
307
  "monitoringQueueOverflowCount",
308
308
  "eventQueueOverFlowCount"
309
309
  */
310
+ subscription_subscriptionDiagnostics.__defineGetter__(
311
+ "currentKeepAliveCount",
312
+ function (this: SubscriptionDiagnosticsDataTypePriv): number {
313
+ if (!this.$subscription) {
314
+ return 0;
315
+ }
316
+ return this.$subscription.currentKeepAliveCount;
317
+ }
318
+ );
319
+ subscription_subscriptionDiagnostics.__defineGetter__(
320
+ "currentLifetimeCount",
321
+ function (this: SubscriptionDiagnosticsDataTypePriv): number {
322
+ if (!this.$subscription) {
323
+ return 0;
324
+ }
325
+ return this.$subscription.currentLifetimeCount;
326
+ }
327
+ );
310
328
  // add object in Variable SubscriptionDiagnosticArray (i=2290) ( Array of SubscriptionDiagnostics)
311
329
  // add properties in Variable to reflect
312
330
  return subscriptionDiagnostics as SubscriptionDiagnosticsDataTypePriv;
@@ -453,15 +471,17 @@ export interface ServerCapabilitiesPartial {
453
471
  export class Subscription extends EventEmitter {
454
472
  public static minimumPublishingInterval = 50; // fastest possible
455
473
  public static defaultPublishingInterval = 1000; // one second
456
- public static maximumPublishingInterval: number = 1000 * 60 * 60 * 24 * 15; // 15 days
474
+ public static maximumPublishingInterval: number = 1000 * 60; // one minute
457
475
  public static maxNotificationPerPublishHighLimit = 1000;
476
+ public static minimumLifetimeDuration = 5 * 1000; // // we want 2 seconds minimum lifetime for any subscription
477
+ public static maximumLifetimeDuration = 60 * 60 * 1000; // 1 hour
458
478
 
459
479
  /**
460
480
  * maximum number of monitored item in a subscription to be used
461
481
  * when serverCapacity.maxMonitoredItems and serverCapacity.maxMonitoredItemsPerSubscription are not set.
462
482
  */
463
483
  public static defaultMaxMonitoredItemCount = 20000;
464
-
484
+
465
485
  /**
466
486
  * @deprecated use serverCapacity.maxMonitoredItems and serverCapacity.maxMonitoredItemsPerSubscription instead
467
487
  */
@@ -471,7 +491,6 @@ export class Subscription extends EventEmitter {
471
491
 
472
492
  public static registry = new ObjectRegistry();
473
493
 
474
- public sessionId: NodeId;
475
494
  public publishEngine?: IServerSidePublishEngine;
476
495
  public id: number;
477
496
  public priority: number;
@@ -518,10 +537,31 @@ export class Subscription extends EventEmitter {
518
537
  */
519
538
  public monitoredItemIdCounter: number;
520
539
 
521
- public state: SubscriptionState;
540
+ private _state: SubscriptionState = -1 as SubscriptionState;
541
+ public set state(value: SubscriptionState) {
542
+ if (this._state !== value) {
543
+ this._state = value;
544
+ this.emit("stateChanged", value);
545
+ }
546
+ }
547
+ public get state(): SubscriptionState {
548
+ return this._state;
549
+ }
550
+
522
551
  public messageSent: boolean;
523
552
  public $session?: ServerSession;
524
553
 
554
+ public get sessionId(): NodeId {
555
+ return this.$session ? this.$session.nodeId : NodeId.nullNodeId;
556
+ }
557
+
558
+ public get currentLifetimeCount(): number {
559
+ return this._life_time_counter;
560
+ }
561
+ public get currentKeepAliveCount(): number {
562
+ return this._keep_alive_counter;
563
+ }
564
+
525
565
  private _life_time_counter: number;
526
566
  private _keep_alive_counter = 0;
527
567
  private _pending_notifications: Queue<InternalNotification>;
@@ -541,7 +581,6 @@ export class Subscription extends EventEmitter {
541
581
 
542
582
  Subscription.registry.register(this);
543
583
 
544
- this.sessionId = options.sessionId || NodeId.nullNodeId;
545
584
  assert(this.sessionId instanceof NodeId, "expecting a sessionId NodeId");
546
585
 
547
586
  this.publishEngine = options.publishEngine!;
@@ -590,12 +629,13 @@ export class Subscription extends EventEmitter {
590
629
  this.messageSent = false;
591
630
 
592
631
  this.timerId = null;
593
- this._start_timer();
632
+ this._start_timer({ firstTime: true });
594
633
 
595
634
  debugLog(chalk.green(`creating subscription ${this.id}`));
596
635
 
597
636
  this.serverCapabilities = options.serverCapabilities;
598
- this.serverCapabilities.maxMonitoredItems = this.serverCapabilities.maxMonitoredItems || Subscription.defaultMaxMonitoredItemCount;
637
+ this.serverCapabilities.maxMonitoredItems =
638
+ this.serverCapabilities.maxMonitoredItems || Subscription.defaultMaxMonitoredItemCount;
599
639
  this.serverCapabilities.maxMonitoredItemsPerSubscription =
600
640
  this.serverCapabilities.maxMonitoredItemsPerSubscription || Subscription.defaultMaxMonitoredItemCount;
601
641
  this.globalCounter = options.globalCounter;
@@ -646,7 +686,7 @@ export class Subscription extends EventEmitter {
646
686
  // todo
647
687
  }
648
688
  this._stop_timer();
649
- this._start_timer();
689
+ this._start_timer({ firstTime: false });
650
690
  }
651
691
 
652
692
  /**
@@ -674,7 +714,7 @@ export class Subscription extends EventEmitter {
674
714
  * @private
675
715
  */
676
716
  public get keepAliveCounterHasExpired(): boolean {
677
- return this._keep_alive_counter >= this.maxKeepAliveCount;
717
+ return this._keep_alive_counter >= this.maxKeepAliveCount || this.state === SubscriptionState.LATE;
678
718
  }
679
719
 
680
720
  /**
@@ -691,6 +731,10 @@ export class Subscription extends EventEmitter {
691
731
  */
692
732
  public increaseLifeTimeCounter(): void {
693
733
  this._life_time_counter += 1;
734
+ if (this._life_time_counter >= this.lifeTimeCount) {
735
+ this.emit("lifeTimeExpired");
736
+ }
737
+ this.emit("lifeTimeCounterChanged", this._life_time_counter);
694
738
  }
695
739
 
696
740
  /**
@@ -823,8 +867,6 @@ export class Subscription extends EventEmitter {
823
867
  this._pending_notifications.clear();
824
868
  this._sent_notification_messages = [];
825
869
 
826
- this.sessionId = new NodeId();
827
-
828
870
  this.$session = undefined;
829
871
  this.removeAllListeners();
830
872
 
@@ -1401,7 +1443,9 @@ export class Subscription extends EventEmitter {
1401
1443
  " -> subscription.state === LATE , " +
1402
1444
  "because keepAlive Response cannot be send due to lack of PublishRequest"
1403
1445
  );
1404
- this.state = SubscriptionState.LATE;
1446
+ if (this.messageSent || this.keepAliveCounterHasExpired) {
1447
+ this.state = SubscriptionState.LATE;
1448
+ }
1405
1449
  }
1406
1450
  }
1407
1451
  }
@@ -1414,7 +1458,7 @@ export class Subscription extends EventEmitter {
1414
1458
  }
1415
1459
  }
1416
1460
 
1417
- private _start_timer() {
1461
+ private _start_timer({ firstTime }: { firstTime: boolean }) {
1418
1462
  debugLog(
1419
1463
  chalk.bgWhite.blue("Subscription#_start_timer subscriptionId="),
1420
1464
  this.id,
@@ -1433,7 +1477,12 @@ export class Subscription extends EventEmitter {
1433
1477
 
1434
1478
  // make sure that a keep-alive Message will be send at the end of the first publishing cycle
1435
1479
  // if there are no Notifications ready.
1436
- this._keep_alive_counter = this.maxKeepAliveCount;
1480
+ this._keep_alive_counter = 0; // this.maxKeepAliveCount;
1481
+
1482
+ if (firstTime) {
1483
+ assert(this.messageSent === false);
1484
+ assert(this.state === SubscriptionState.CREATING);
1485
+ }
1437
1486
 
1438
1487
  assert(this.publishingInterval >= Subscription.minimumPublishingInterval);
1439
1488
  this.timerId = setInterval(this._tick.bind(this), this.publishingInterval);
@@ -1452,14 +1501,6 @@ export class Subscription extends EventEmitter {
1452
1501
  if (doDebug) {
1453
1502
  debugLog(`Subscription#_tick id ${this.id} aborted=${this.aborted} state=${SubscriptionState[this.state]}`);
1454
1503
  }
1455
-
1456
- if (this.aborted) {
1457
- // xx console.log(" Log aborted")
1458
- // xx // underlying channel has been aborted ...
1459
- // xx self.publishEngine.cancelPendingPublishRequestBeforeChannelChange();
1460
- // xx // let's still increase lifetime counter to detect timeout
1461
- }
1462
-
1463
1504
  if (this.state === SubscriptionState.CLOSED) {
1464
1505
  warningLog(`Warning: Subscription#_tick id ${this.id} called while subscription is CLOSED`);
1465
1506
  return;
@@ -1480,17 +1521,19 @@ export class Subscription extends EventEmitter {
1480
1521
  );
1481
1522
  }
1482
1523
 
1524
+ // give a chance to the publish engine to cancel timed out publish requests
1483
1525
  this.publishEngine!._on_tick();
1484
1526
 
1485
1527
  this.publishIntervalCount += 1;
1486
1528
 
1487
- this.increaseLifeTimeCounter();
1529
+ if (this.state === SubscriptionState.LATE) {
1530
+ this.increaseLifeTimeCounter();
1531
+ }
1488
1532
 
1489
1533
  if (this.lifeTimeHasExpired) {
1490
1534
  /* istanbul ignore next */
1491
- if (doDebug) {
1492
- debugLog(chalk.red.bold(`Subscription ${this.id} has expired !!!!! => Terminating`));
1493
- }
1535
+ doDebug && debugLog(chalk.red.bold(`Subscription ${this.id} has expired !!!!! => Terminating`));
1536
+
1494
1537
  /**
1495
1538
  * notify the subscription owner that the subscription has expired by exceeding its life time.
1496
1539
  * @event expired
@@ -1499,7 +1542,7 @@ export class Subscription extends EventEmitter {
1499
1542
  this.emit("expired");
1500
1543
 
1501
1544
  // notify new terminated status only when subscription has timeout.
1502
- debugLog("adding StatusChangeNotification notification message for BadTimeout subscription = ", this.id);
1545
+ doDebug && debugLog("adding StatusChangeNotification notification message for BadTimeout subscription = ", this.id);
1503
1546
  this._addNotificationMessage(new StatusChangeNotification({ status: StatusCodes.BadTimeout }));
1504
1547
 
1505
1548
  // kill timer and delete monitored items and transfer pending notification messages
@@ -1511,23 +1554,21 @@ export class Subscription extends EventEmitter {
1511
1554
  const publishEngine = this.publishEngine!;
1512
1555
 
1513
1556
  // istanbul ignore next
1514
- if (doDebug) {
1515
- debugLog("Subscription#_tick self._pending_notifications= ", this._pending_notifications.size);
1516
- }
1557
+ doDebug && debugLog("Subscription#_tick self._pending_notifications= ", this._pending_notifications.size);
1517
1558
 
1518
1559
  if (
1519
1560
  publishEngine.pendingPublishRequestCount === 0 &&
1520
1561
  (this.hasPendingNotifications || this.hasUncollectedMonitoredItemNotifications)
1521
1562
  ) {
1522
1563
  // istanbul ignore next
1523
- if (doDebug) {
1564
+ doDebug &&
1524
1565
  debugLog(
1525
1566
  "subscription set to LATE hasPendingNotifications = ",
1526
1567
  this.hasPendingNotifications,
1527
1568
  " hasUncollectedMonitoredItemNotifications =",
1528
1569
  this.hasUncollectedMonitoredItemNotifications
1529
1570
  );
1530
- }
1571
+
1531
1572
  this.state = SubscriptionState.LATE;
1532
1573
  return;
1533
1574
  }
@@ -1542,7 +1583,11 @@ export class Subscription extends EventEmitter {
1542
1583
  this._process_keepAlive();
1543
1584
  }
1544
1585
  } else {
1545
- this._process_keepAlive();
1586
+ if (this.state !== SubscriptionState.LATE) {
1587
+ this._process_keepAlive();
1588
+ } else {
1589
+ this.resetKeepAliveCounter();
1590
+ }
1546
1591
  }
1547
1592
  }
1548
1593
 
@@ -1555,11 +1600,10 @@ export class Subscription extends EventEmitter {
1555
1600
  if (this.publishEngine!.send_keep_alive_response(this.id, future_sequence_number)) {
1556
1601
  this.messageSent = true;
1557
1602
  // istanbul ignore next
1558
- if (doDebug) {
1603
+ doDebug &&
1559
1604
  debugLog(
1560
1605
  ` -> Subscription#_sendKeepAliveResponse subscriptionId ${this.id} future_sequence_number ${future_sequence_number}`
1561
1606
  );
1562
- }
1563
1607
  /**
1564
1608
  * notify the subscription owner that a keepalive message has to be sent.
1565
1609
  * @event keepalive
@@ -1582,7 +1626,7 @@ export class Subscription extends EventEmitter {
1582
1626
  this._keep_alive_counter = 0;
1583
1627
 
1584
1628
  // istanbul ignore next
1585
- if (doDebug) {
1629
+ doDebug &&
1586
1630
  debugLog(
1587
1631
  " -> subscriptionId",
1588
1632
  this.id,
@@ -1590,7 +1634,6 @@ export class Subscription extends EventEmitter {
1590
1634
  this._keep_alive_counter,
1591
1635
  this.maxKeepAliveCount
1592
1636
  );
1593
- }
1594
1637
  }
1595
1638
 
1596
1639
  /**
@@ -1600,7 +1643,7 @@ export class Subscription extends EventEmitter {
1600
1643
  this._keep_alive_counter += 1;
1601
1644
 
1602
1645
  // istanbul ignore next
1603
- if (doDebug) {
1646
+ doDebug &&
1604
1647
  debugLog(
1605
1648
  " -> subscriptionId",
1606
1649
  this.id,
@@ -1608,7 +1651,6 @@ export class Subscription extends EventEmitter {
1608
1651
  this._keep_alive_counter,
1609
1652
  this.maxKeepAliveCount
1610
1653
  );
1611
- }
1612
1654
  }
1613
1655
 
1614
1656
  /**
@@ -1616,9 +1658,8 @@ export class Subscription extends EventEmitter {
1616
1658
  */
1617
1659
  private _addNotificationMessage(notificationData: QueueItem | StatusChangeNotification, monitoredItemId?: number) {
1618
1660
  // istanbul ignore next
1619
- if (doDebug) {
1620
- debugLog(chalk.yellow("Subscription#_addNotificationMessage"), notificationData.toString());
1621
- }
1661
+ doDebug && debugLog(chalk.yellow("Subscription#_addNotificationMessage"), notificationData.toString());
1662
+
1622
1663
  this._pending_notifications.push({
1623
1664
  monitoredItemId,
1624
1665
  notification: notificationData,
@@ -1633,9 +1674,7 @@ export class Subscription extends EventEmitter {
1633
1674
  */
1634
1675
  private _removePendingNotificationsFor(monitoredItemId: number) {
1635
1676
  const nbRemovedNotification = this._pending_notifications.filterOut((e) => e.monitoredItemId === monitoredItemId);
1636
- if (doDebug) {
1637
- debugLog(`Removed ${nbRemovedNotification} notifications`);
1638
- }
1677
+ doDebug && debugLog(`Removed ${nbRemovedNotification} notifications`);
1639
1678
  }
1640
1679
  /**
1641
1680
  * Extract the next Notification that is ready to be sent to the client.
@@ -1717,7 +1756,7 @@ export class Subscription extends EventEmitter {
1717
1756
  // Subscription is transferred to another Session, the queued NotificationMessages for this
1718
1757
  // Subscription are moved from the old to the new Session.
1719
1758
  if (maxNotificationMessagesInQueue <= this._sent_notification_messages.length) {
1720
- debugLog("discardOldSentNotifications = ", this._sent_notification_messages.length);
1759
+ doDebug && debugLog("discardOldSentNotifications = ", this._sent_notification_messages.length);
1721
1760
  this._sent_notification_messages.splice(this._sent_notification_messages.length - maxNotificationMessagesInQueue);
1722
1761
  }
1723
1762
  }
@@ -1812,26 +1851,4 @@ export class Subscription extends EventEmitter {
1812
1851
  }
1813
1852
  }
1814
1853
 
1815
- /**
1816
- * extract up to maxNotificationsPerPublish notifications
1817
- * @param the full array of monitored items
1818
- * @param maxNotificationsPerPublish the maximum number of notification to extract
1819
- * @return an extract of array of monitored item matching at most maxNotificationsPerPublish
1820
- * @private
1821
- */
1822
- function extract_notifications_chunk<T>(monitoredItems: Queue<T>, maxNotificationsPerPublish: number): T[] {
1823
- let n = maxNotificationsPerPublish === 0 ? monitoredItems.size : Math.min(monitoredItems.size, maxNotificationsPerPublish);
1824
-
1825
- const chunk_monitoredItems: T[] = [];
1826
- while (n) {
1827
- chunk_monitoredItems.push(monitoredItems.shift()!);
1828
- n--;
1829
- }
1830
- return chunk_monitoredItems;
1831
- }
1832
-
1833
- function filter_instanceof(Class: any, e: any): boolean {
1834
- return e instanceof Class;
1835
- }
1836
-
1837
1854
  assert(Subscription.maximumPublishingInterval < 2147483647, "maximumPublishingInterval cannot exceed (2**31-1) ms ");
@@ -2,7 +2,11 @@ import { assert } from "node-opcua-assert";
2
2
  import { UserIdentityToken, AnonymousIdentityToken, UserNameIdentityToken, X509IdentityToken } from "node-opcua-types";
3
3
  import { ServerSession } from "./server_session";
4
4
 
5
- export function sessionsCompatibleForTransfer(sessionSrc: ServerSession, sessionDest: ServerSession): boolean {
5
+ export function sessionsCompatibleForTransfer(sessionSrc: ServerSession | undefined, sessionDest: ServerSession): boolean {
6
+
7
+ if (!sessionSrc) {
8
+ return true;
9
+ }
6
10
  assert(sessionDest);
7
11
  assert(sessionSrc);
8
12
  if (!sessionSrc.userIdentityToken && !sessionDest.userIdentityToken) {