@webex/plugin-meetings 3.12.0-next.62 → 3.12.0-next.64

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.
@@ -381,7 +381,7 @@ var SimultaneousInterpretation = _webexCore.WebexPlugin.extend({
381
381
  throw error;
382
382
  });
383
383
  },
384
- version: "3.12.0-next.62"
384
+ version: "3.12.0-next.64"
385
385
  });
386
386
  var _default = exports.default = SimultaneousInterpretation;
387
387
  //# sourceMappingURL=index.js.map
@@ -18,7 +18,7 @@ var SILanguage = _webexCore.WebexPlugin.extend({
18
18
  languageCode: 'number',
19
19
  languageName: 'string'
20
20
  },
21
- version: "3.12.0-next.62"
21
+ version: "3.12.0-next.64"
22
22
  });
23
23
  var _default = exports.default = SILanguage;
24
24
  //# sourceMappingURL=siLanguage.js.map
@@ -18,9 +18,11 @@ export interface DataSet {
18
18
  maxMs: number;
19
19
  exponent: number;
20
20
  };
21
+ heartbeatIntervalMs?: number;
21
22
  }
22
23
  export interface RootHashMessage {
23
24
  dataSets: Array<DataSet>;
25
+ heartbeatIntervalMs?: number;
24
26
  }
25
27
  export interface HashTreeMessage {
26
28
  dataSets: Array<DataSet>;
@@ -88,7 +90,6 @@ declare class HashTreeParser {
88
90
  locusInfoUpdateCallback: LocusInfoUpdateCallback;
89
91
  visibleDataSets: VisibleDataSetInfo[];
90
92
  debugId: string;
91
- heartbeatIntervalMs?: number;
92
93
  private excludedDataSets;
93
94
  state: 'active' | 'stopped';
94
95
  private syncQueue;
@@ -96,6 +97,7 @@ declare class HashTreeParser {
96
97
  private syncAllBackoffType;
97
98
  private dataSetsSyncedDuringBackoff;
98
99
  private syncQueueProcessingPromise;
100
+ private topLevelHeartbeatIntervalMs?;
99
101
  /**
100
102
  * Constructor for HashTreeParser
101
103
  * @param {Object} options
@@ -774,7 +774,7 @@ var Webinar = _webexCore.WebexPlugin.extend({
774
774
  }, _callee1);
775
775
  }))();
776
776
  },
777
- version: "3.12.0-next.62"
777
+ version: "3.12.0-next.64"
778
778
  });
779
779
  var _default = exports.default = Webinar;
780
780
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -44,12 +44,12 @@
44
44
  "@webex/event-dictionary-ts": "^1.0.2138",
45
45
  "@webex/jest-config-legacy": "0.0.0",
46
46
  "@webex/legacy-tools": "0.0.0",
47
- "@webex/plugin-rooms": "3.12.0-next.17",
48
- "@webex/test-helper-chai": "3.12.0-next.1",
49
- "@webex/test-helper-mocha": "3.12.0-next.1",
50
- "@webex/test-helper-mock-webex": "3.12.0-next.1",
51
- "@webex/test-helper-retry": "3.12.0-next.1",
52
- "@webex/test-helper-test-users": "3.12.0-next.1",
47
+ "@webex/plugin-rooms": "3.12.0-next.18",
48
+ "@webex/test-helper-chai": "3.12.0-next.2",
49
+ "@webex/test-helper-mocha": "3.12.0-next.2",
50
+ "@webex/test-helper-mock-webex": "3.12.0-next.2",
51
+ "@webex/test-helper-retry": "3.12.0-next.2",
52
+ "@webex/test-helper-test-users": "3.12.0-next.2",
53
53
  "chai": "^4.3.4",
54
54
  "chai-as-promised": "^7.1.1",
55
55
  "eslint": "^8.24.0",
@@ -61,22 +61,22 @@
61
61
  "typescript": "^4.7.4"
62
62
  },
63
63
  "dependencies": {
64
- "@webex/common": "3.12.0-next.1",
64
+ "@webex/common": "3.12.0-next.2",
65
65
  "@webex/internal-media-core": "2.25.1",
66
- "@webex/internal-plugin-conversation": "3.12.0-next.17",
67
- "@webex/internal-plugin-device": "3.12.0-next.15",
68
- "@webex/internal-plugin-llm": "3.12.0-next.18",
69
- "@webex/internal-plugin-mercury": "3.12.0-next.16",
70
- "@webex/internal-plugin-metrics": "3.12.0-next.15",
71
- "@webex/internal-plugin-support": "3.12.0-next.17",
72
- "@webex/internal-plugin-user": "3.12.0-next.16",
73
- "@webex/internal-plugin-voicea": "3.12.0-next.18",
74
- "@webex/media-helpers": "3.12.0-next.4",
75
- "@webex/plugin-people": "3.12.0-next.16",
76
- "@webex/plugin-rooms": "3.12.0-next.17",
66
+ "@webex/internal-plugin-conversation": "3.12.0-next.18",
67
+ "@webex/internal-plugin-device": "3.12.0-next.16",
68
+ "@webex/internal-plugin-llm": "3.12.0-next.19",
69
+ "@webex/internal-plugin-mercury": "3.12.0-next.17",
70
+ "@webex/internal-plugin-metrics": "3.12.0-next.16",
71
+ "@webex/internal-plugin-support": "3.12.0-next.18",
72
+ "@webex/internal-plugin-user": "3.12.0-next.17",
73
+ "@webex/internal-plugin-voicea": "3.12.0-next.19",
74
+ "@webex/media-helpers": "3.12.0-next.5",
75
+ "@webex/plugin-people": "3.12.0-next.17",
76
+ "@webex/plugin-rooms": "3.12.0-next.18",
77
77
  "@webex/ts-sdp": "^1.8.1",
78
78
  "@webex/web-capabilities": "^1.10.0",
79
- "@webex/webex-core": "3.12.0-next.15",
79
+ "@webex/webex-core": "3.12.0-next.16",
80
80
  "ampersand-collection": "^2.0.2",
81
81
  "bowser": "^2.11.0",
82
82
  "btoa": "^1.2.1",
@@ -94,5 +94,5 @@
94
94
  "//": [
95
95
  "TODO: upgrade jwt-decode when moving to node 18"
96
96
  ],
97
- "version": "3.12.0-next.62"
97
+ "version": "3.12.0-next.64"
98
98
  }
@@ -26,10 +26,12 @@ export interface DataSet {
26
26
  maxMs: number;
27
27
  exponent: number;
28
28
  };
29
+ heartbeatIntervalMs?: number;
29
30
  }
30
31
 
31
32
  export interface RootHashMessage {
32
33
  dataSets: Array<DataSet>;
34
+ heartbeatIntervalMs?: number;
33
35
  }
34
36
  export interface HashTreeMessage {
35
37
  dataSets: Array<DataSet>;
@@ -123,7 +125,6 @@ class HashTreeParser {
123
125
  locusInfoUpdateCallback: LocusInfoUpdateCallback;
124
126
  visibleDataSets: VisibleDataSetInfo[];
125
127
  debugId: string;
126
- heartbeatIntervalMs?: number;
127
128
  private excludedDataSets: string[];
128
129
  state: 'active' | 'stopped';
129
130
  private syncQueue: Array<{dataSetName: string; reason: string; isInitialization?: boolean}> = [];
@@ -133,6 +134,8 @@ class HashTreeParser {
133
134
  // datasets that received messages during the syncAllDatasets backoff sleep and should be skipped
134
135
  private dataSetsSyncedDuringBackoff: Set<string> = new Set();
135
136
  private syncQueueProcessingPromise: Promise<void> = Promise.resolve();
137
+ // top-level heartbeat interval from the most recent message, used as fallback when dataset-level value is missing
138
+ private topLevelHeartbeatIntervalMs?: number;
136
139
 
137
140
  /**
138
141
  * Constructor for HashTreeParser
@@ -802,6 +805,7 @@ class HashTreeParser {
802
805
  maxMs: receivedDataSet.backoff.maxMs,
803
806
  exponent: receivedDataSet.backoff.exponent,
804
807
  };
808
+ this.dataSets[receivedDataSet.name].heartbeatIntervalMs = receivedDataSet.heartbeatIntervalMs;
805
809
  LoggerProxy.logger.info(
806
810
  `HashTreeParser#updateDataSetInfo --> ${this.debugId} updated "${receivedDataSet.name}" dataset to version=${receivedDataSet.version}, root=${receivedDataSet.root}`
807
811
  );
@@ -1156,9 +1160,10 @@ class HashTreeParser {
1156
1160
  return;
1157
1161
  }
1158
1162
 
1159
- if (message.heartbeatIntervalMs) {
1160
- this.heartbeatIntervalMs = message.heartbeatIntervalMs;
1163
+ if (message.heartbeatIntervalMs !== undefined) {
1164
+ this.topLevelHeartbeatIntervalMs = message.heartbeatIntervalMs;
1161
1165
  }
1166
+
1162
1167
  if (this.isEndMessage(message)) {
1163
1168
  LoggerProxy.logger.info(
1164
1169
  `HashTreeParser#handleMessage --> ${this.debugId} received sentinel END MEETING message`
@@ -1687,25 +1692,24 @@ class HashTreeParser {
1687
1692
  * @returns {void}
1688
1693
  */
1689
1694
  private resetHeartbeatWatchdogs(receivedDataSets: Array<DataSet>): void {
1690
- if (!this.heartbeatIntervalMs) {
1691
- return;
1692
- }
1693
-
1694
1695
  for (const receivedDataSet of receivedDataSets) {
1695
1696
  const dataSet = this.dataSets[receivedDataSet.name];
1696
1697
 
1697
- if (!dataSet?.hashTree) {
1698
- // eslint-disable-next-line no-continue
1699
- continue;
1700
- }
1701
-
1702
1698
  if (dataSet.heartbeatWatchdogTimer) {
1703
1699
  clearTimeout(dataSet.heartbeatWatchdogTimer);
1704
1700
  dataSet.heartbeatWatchdogTimer = undefined;
1705
1701
  }
1706
1702
 
1703
+ // dataset-level heartbeatIntervalMs takes priority; fall back to top-level common value
1704
+ const heartbeatIntervalMs = dataSet?.heartbeatIntervalMs ?? this.topLevelHeartbeatIntervalMs;
1705
+
1706
+ if (!dataSet?.hashTree || !heartbeatIntervalMs) {
1707
+ // eslint-disable-next-line no-continue
1708
+ continue;
1709
+ }
1710
+
1707
1711
  const backoffTime = this.getWeightedBackoffTime(dataSet.backoff);
1708
- const delay = this.heartbeatIntervalMs + backoffTime;
1712
+ const delay = heartbeatIntervalMs + backoffTime;
1709
1713
 
1710
1714
  dataSet.heartbeatWatchdogTimer = setTimeout(() => {
1711
1715
  dataSet.heartbeatWatchdogTimer = undefined;
@@ -1754,6 +1758,7 @@ class HashTreeParser {
1754
1758
  );
1755
1759
  this.stopAllTimers();
1756
1760
  this.syncQueue = [];
1761
+ this.topLevelHeartbeatIntervalMs = undefined;
1757
1762
  this.syncAllBackoffType = SyncAllBackoffType.NONE;
1758
1763
  this.dataSetsSyncedDuringBackoff = new Set();
1759
1764
  Object.values(this.dataSets).forEach((dataSet) => {
@@ -117,6 +117,7 @@ function createDataSet(name: string, leafCount: number, version = 1) {
117
117
  name,
118
118
  idleMs: 1000,
119
119
  backoff: {maxMs: 1000, exponent: 2},
120
+ heartbeatIntervalMs: 5000,
120
121
  };
121
122
  }
122
123
 
@@ -3042,7 +3043,6 @@ describe('HashTreeParser', () => {
3042
3043
  ],
3043
3044
  visibleDataSetsUrl,
3044
3045
  locusUrl,
3045
- heartbeatIntervalMs,
3046
3046
  };
3047
3047
 
3048
3048
  parser.handleMessage(heartbeatMessage, 'initial heartbeat');
@@ -3112,7 +3112,6 @@ describe('HashTreeParser', () => {
3112
3112
  ],
3113
3113
  visibleDataSetsUrl,
3114
3114
  locusUrl,
3115
- heartbeatIntervalMs,
3116
3115
  };
3117
3116
 
3118
3117
  parser.handleMessage(heartbeatMessage, 'self heartbeat');
@@ -3148,7 +3147,6 @@ describe('HashTreeParser', () => {
3148
3147
 
3149
3148
  it('sets watchdog timers for each data set in the message', async () => {
3150
3149
  const parser = createHashTreeParser();
3151
- const heartbeatIntervalMs = 5000;
3152
3150
 
3153
3151
  // Send heartbeat with multiple datasets
3154
3152
  const heartbeatMessage = {
@@ -3165,7 +3163,6 @@ describe('HashTreeParser', () => {
3165
3163
  ],
3166
3164
  visibleDataSetsUrl,
3167
3165
  locusUrl,
3168
- heartbeatIntervalMs,
3169
3166
  };
3170
3167
 
3171
3168
  parser.handleMessage(heartbeatMessage, 'multi-dataset heartbeat');
@@ -3179,7 +3176,6 @@ describe('HashTreeParser', () => {
3179
3176
 
3180
3177
  it('resets the watchdog timer for a specific data set when a new heartbeat for it is received', async () => {
3181
3178
  const parser = createHashTreeParser();
3182
- const heartbeatIntervalMs = 5000;
3183
3179
 
3184
3180
  // Send first heartbeat for 'main'
3185
3181
  const heartbeat1 = {
@@ -3191,7 +3187,6 @@ describe('HashTreeParser', () => {
3191
3187
  ],
3192
3188
  visibleDataSetsUrl,
3193
3189
  locusUrl,
3194
- heartbeatIntervalMs,
3195
3190
  };
3196
3191
 
3197
3192
  parser.handleMessage(heartbeat1, 'first heartbeat');
@@ -3212,7 +3207,6 @@ describe('HashTreeParser', () => {
3212
3207
  ],
3213
3208
  visibleDataSetsUrl,
3214
3209
  locusUrl,
3215
- heartbeatIntervalMs,
3216
3210
  };
3217
3211
 
3218
3212
  parser.handleMessage(heartbeat2, 'second heartbeat');
@@ -3231,7 +3225,6 @@ describe('HashTreeParser', () => {
3231
3225
 
3232
3226
  it('resets the watchdog timer when a normal message (with locusStateElements) is received', async () => {
3233
3227
  const parser = createHashTreeParser();
3234
- const heartbeatIntervalMs = 5000;
3235
3228
 
3236
3229
  // Send initial heartbeat to start the watchdog for 'main'
3237
3230
  const heartbeat = {
@@ -3243,7 +3236,6 @@ describe('HashTreeParser', () => {
3243
3236
  ],
3244
3237
  visibleDataSetsUrl,
3245
3238
  locusUrl,
3246
- heartbeatIntervalMs,
3247
3239
  };
3248
3240
 
3249
3241
  parser.handleMessage(heartbeat, 'initial heartbeat');
@@ -3271,7 +3263,6 @@ describe('HashTreeParser', () => {
3271
3263
  data: {someData: 'value'},
3272
3264
  },
3273
3265
  ],
3274
- heartbeatIntervalMs,
3275
3266
  };
3276
3267
 
3277
3268
  parser.handleMessage(normalMessage, 'normal message');
@@ -3285,12 +3276,17 @@ describe('HashTreeParser', () => {
3285
3276
  const parser = createHashTreeParser();
3286
3277
 
3287
3278
  // Send a heartbeat message without heartbeatIntervalMs
3288
- const heartbeatMessage = createHeartbeatMessage(
3289
- 'main',
3290
- 16,
3291
- 1100,
3292
- parser.dataSets.main.hashTree.getRootHash()
3293
- );
3279
+ const heartbeatMessage = {
3280
+ dataSets: [
3281
+ {
3282
+ ...createDataSet('main', 16, 1100),
3283
+ root: parser.dataSets.main.hashTree.getRootHash(),
3284
+ heartbeatIntervalMs: undefined,
3285
+ },
3286
+ ],
3287
+ visibleDataSetsUrl,
3288
+ locusUrl,
3289
+ };
3294
3290
 
3295
3291
  parser.handleMessage(heartbeatMessage, 'heartbeat without interval');
3296
3292
 
@@ -3299,7 +3295,6 @@ describe('HashTreeParser', () => {
3299
3295
 
3300
3296
  it('stops all watchdog timers when meeting ends via sentinel message', async () => {
3301
3297
  const parser = createHashTreeParser();
3302
- const heartbeatIntervalMs = 5000;
3303
3298
 
3304
3299
  // Send heartbeat for multiple datasets
3305
3300
  const heartbeat = {
@@ -3316,7 +3311,6 @@ describe('HashTreeParser', () => {
3316
3311
  ],
3317
3312
  visibleDataSetsUrl,
3318
3313
  locusUrl,
3319
- heartbeatIntervalMs,
3320
3314
  };
3321
3315
 
3322
3316
  parser.handleMessage(heartbeat, 'initial heartbeat');
@@ -3367,7 +3361,6 @@ describe('HashTreeParser', () => {
3367
3361
  };
3368
3362
 
3369
3363
  const parser = createHashTreeParser(initialLocus, metadata);
3370
- const heartbeatIntervalMs = 5000;
3371
3364
 
3372
3365
  // Set Math.random to return 1 so that backoff = 1^exponent * maxMs = maxMs
3373
3366
  mathRandomStub.returns(1);
@@ -3389,7 +3382,6 @@ describe('HashTreeParser', () => {
3389
3382
  ],
3390
3383
  visibleDataSetsUrl,
3391
3384
  locusUrl,
3392
- heartbeatIntervalMs,
3393
3385
  };
3394
3386
 
3395
3387
  parser.handleMessage(heartbeat, 'heartbeat');
@@ -3439,7 +3431,6 @@ describe('HashTreeParser', () => {
3439
3431
 
3440
3432
  it('does not set watchdog for data sets without a hash tree', async () => {
3441
3433
  const parser = createHashTreeParser();
3442
- const heartbeatIntervalMs = 5000;
3443
3434
 
3444
3435
  // 'atd-active' is in the initial locus but is not visible (no hash tree)
3445
3436
  // Send heartbeat mentioning a non-visible dataset
@@ -3453,7 +3444,6 @@ describe('HashTreeParser', () => {
3453
3444
  ],
3454
3445
  visibleDataSetsUrl,
3455
3446
  locusUrl,
3456
- heartbeatIntervalMs,
3457
3447
  };
3458
3448
 
3459
3449
  parser.handleMessage(heartbeatMessage, 'heartbeat with non-visible dataset');
@@ -3477,7 +3467,6 @@ describe('HashTreeParser', () => {
3477
3467
  ],
3478
3468
  visibleDataSetsUrl,
3479
3469
  locusUrl,
3480
- heartbeatIntervalMs,
3481
3470
  };
3482
3471
 
3483
3472
  parser.handleMessage(heartbeatMessage, 'initial heartbeat');
@@ -3531,6 +3520,122 @@ describe('HashTreeParser', () => {
3531
3520
  // And the watchdog should still be running
3532
3521
  expect(parser.dataSets.main.heartbeatWatchdogTimer).to.not.be.undefined;
3533
3522
  });
3523
+
3524
+ it('uses dataset-level heartbeatIntervalMs over top-level value', async () => {
3525
+ const parser = createHashTreeParser();
3526
+ const datasetLevelInterval = 3000;
3527
+ const topLevelInterval = 8000;
3528
+
3529
+ // Send heartbeat with both top-level and dataset-level heartbeatIntervalMs
3530
+ const heartbeatMessage = {
3531
+ dataSets: [
3532
+ {
3533
+ ...createDataSet('main', 16, 1100),
3534
+ root: parser.dataSets.main.hashTree.getRootHash(),
3535
+ heartbeatIntervalMs: datasetLevelInterval,
3536
+ },
3537
+ ],
3538
+ visibleDataSetsUrl,
3539
+ locusUrl,
3540
+ heartbeatIntervalMs: topLevelInterval,
3541
+ };
3542
+
3543
+ parser.handleMessage(heartbeatMessage, 'heartbeat with both levels');
3544
+
3545
+ expect(parser.dataSets.main.heartbeatWatchdogTimer).to.not.be.undefined;
3546
+
3547
+ // Mock sync responses
3548
+ const mainDataSetUrl = parser.dataSets.main.url;
3549
+ mockGetHashesFromLocusResponse(
3550
+ mainDataSetUrl,
3551
+ new Array(16).fill('00000000000000000000000000000000'),
3552
+ createDataSet('main', 16, 1101)
3553
+ );
3554
+ mockSendSyncRequestResponse(mainDataSetUrl, null);
3555
+
3556
+ // Watchdog should NOT fire at the top-level interval (8000ms)
3557
+ // It should fire at the dataset-level interval (3000ms)
3558
+ await clock.tickAsync(datasetLevelInterval - 1);
3559
+ assert.notCalled(webexRequest);
3560
+
3561
+ await clock.tickAsync(1);
3562
+ // Now at datasetLevelInterval, watchdog should have fired
3563
+ assert.calledWith(
3564
+ webexRequest,
3565
+ sinon.match({
3566
+ method: 'GET',
3567
+ uri: `${mainDataSetUrl}/hashtree`,
3568
+ })
3569
+ );
3570
+ });
3571
+
3572
+ it('falls back to top-level heartbeatIntervalMs when dataset-level is missing', async () => {
3573
+ const parser = createHashTreeParser();
3574
+ const topLevelInterval = 7000;
3575
+
3576
+ // Send heartbeat with top-level heartbeatIntervalMs but no dataset-level
3577
+ const heartbeatMessage = {
3578
+ dataSets: [
3579
+ {
3580
+ ...createDataSet('main', 16, 1100),
3581
+ root: parser.dataSets.main.hashTree.getRootHash(),
3582
+ heartbeatIntervalMs: undefined,
3583
+ },
3584
+ ],
3585
+ visibleDataSetsUrl,
3586
+ locusUrl,
3587
+ heartbeatIntervalMs: topLevelInterval,
3588
+ };
3589
+
3590
+ parser.handleMessage(heartbeatMessage, 'heartbeat with top-level only');
3591
+
3592
+ expect(parser.dataSets.main.heartbeatWatchdogTimer).to.not.be.undefined;
3593
+
3594
+ // Mock sync responses
3595
+ const mainDataSetUrl = parser.dataSets.main.url;
3596
+ mockGetHashesFromLocusResponse(
3597
+ mainDataSetUrl,
3598
+ new Array(16).fill('00000000000000000000000000000000'),
3599
+ createDataSet('main', 16, 1101)
3600
+ );
3601
+ mockSendSyncRequestResponse(mainDataSetUrl, null);
3602
+
3603
+ // Should fire at the top-level interval
3604
+ await clock.tickAsync(topLevelInterval - 1);
3605
+ assert.notCalled(webexRequest);
3606
+
3607
+ await clock.tickAsync(1);
3608
+ assert.calledWith(
3609
+ webexRequest,
3610
+ sinon.match({
3611
+ method: 'GET',
3612
+ uri: `${mainDataSetUrl}/hashtree`,
3613
+ })
3614
+ );
3615
+ });
3616
+
3617
+ it('does not start watchdog when dataset-level heartbeatIntervalMs is 0 even if top-level is set', async () => {
3618
+ const parser = createHashTreeParser();
3619
+
3620
+ // Send heartbeat with dataset-level 0 and a top-level value
3621
+ const heartbeatMessage = {
3622
+ dataSets: [
3623
+ {
3624
+ ...createDataSet('main', 16, 1100),
3625
+ root: parser.dataSets.main.hashTree.getRootHash(),
3626
+ heartbeatIntervalMs: 0,
3627
+ },
3628
+ ],
3629
+ visibleDataSetsUrl,
3630
+ locusUrl,
3631
+ heartbeatIntervalMs: 5000,
3632
+ };
3633
+
3634
+ parser.handleMessage(heartbeatMessage, 'heartbeat with dataset-level 0');
3635
+
3636
+ // Dataset-level 0 means no watchdog, should NOT fall back to top-level
3637
+ expect(parser.dataSets.main.heartbeatWatchdogTimer).to.be.undefined;
3638
+ });
3534
3639
  });
3535
3640
 
3536
3641
  });
@@ -5196,7 +5301,6 @@ describe('HashTreeParser', () => {
5196
5301
  ],
5197
5302
  visibleDataSetsUrl,
5198
5303
  locusUrl,
5199
- heartbeatIntervalMs: 5000,
5200
5304
  locusStateElements: [
5201
5305
  {
5202
5306
  htMeta: {