@webex/plugin-meetings 3.12.0-next.1 → 3.12.0-next.10

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 (38) hide show
  1. package/dist/aiEnableRequest/index.js +1 -1
  2. package/dist/breakouts/breakout.js +1 -1
  3. package/dist/breakouts/index.js +1 -1
  4. package/dist/hashTree/constants.js +10 -1
  5. package/dist/hashTree/constants.js.map +1 -1
  6. package/dist/hashTree/hashTreeParser.js +20 -11
  7. package/dist/hashTree/hashTreeParser.js.map +1 -1
  8. package/dist/hashTree/utils.js +22 -0
  9. package/dist/hashTree/utils.js.map +1 -1
  10. package/dist/interpretation/index.js +1 -1
  11. package/dist/interpretation/siLanguage.js +1 -1
  12. package/dist/meeting/index.js +427 -323
  13. package/dist/meeting/index.js.map +1 -1
  14. package/dist/metrics/constants.js +5 -1
  15. package/dist/metrics/constants.js.map +1 -1
  16. package/dist/multistream/sendSlotManager.js +116 -2
  17. package/dist/multistream/sendSlotManager.js.map +1 -1
  18. package/dist/types/hashTree/constants.d.ts +1 -0
  19. package/dist/types/hashTree/utils.d.ts +11 -0
  20. package/dist/types/meeting/index.d.ts +24 -1
  21. package/dist/types/metrics/constants.d.ts +4 -0
  22. package/dist/types/multistream/sendSlotManager.d.ts +23 -1
  23. package/dist/webinar/index.js +325 -220
  24. package/dist/webinar/index.js.map +1 -1
  25. package/package.json +15 -15
  26. package/src/hashTree/constants.ts +9 -0
  27. package/src/hashTree/hashTreeParser.ts +21 -14
  28. package/src/hashTree/utils.ts +17 -0
  29. package/src/meeting/index.ts +165 -57
  30. package/src/metrics/constants.ts +5 -0
  31. package/src/multistream/sendSlotManager.ts +97 -3
  32. package/src/webinar/index.ts +120 -18
  33. package/test/unit/spec/hashTree/hashTreeParser.ts +238 -0
  34. package/test/unit/spec/hashTree/utils.ts +88 -1
  35. package/test/unit/spec/meeting/index.js +179 -48
  36. package/test/unit/spec/meetings/index.js +3 -3
  37. package/test/unit/spec/multistream/sendSlotManager.ts +135 -36
  38. package/test/unit/spec/webinar/index.ts +193 -8
@@ -38,6 +38,7 @@ import {
38
38
  import {
39
39
  ConnectionState,
40
40
  MediaConnectionEventNames,
41
+ MediaCodecMimeType,
41
42
  StatsAnalyzerEventNames,
42
43
  StatsMonitorEventNames,
43
44
  Errors,
@@ -1552,6 +1553,22 @@ describe('plugin-meetings', () => {
1552
1553
  EVENT_TRIGGERS.MEETING_STOPPED_RECEIVING_TRANSCRIPTION
1553
1554
  );
1554
1555
  });
1556
+
1557
+ it('should stop listening to voicea events even when transcription is undefined', () => {
1558
+ meeting.transcription = undefined;
1559
+ meeting.stopTranscription();
1560
+ assert.equal(webex.internal.voicea.off.callCount, 4);
1561
+ assert.equal(meeting.areVoiceaEventsSetup, false);
1562
+ assert.calledWith(
1563
+ TriggerProxy.trigger,
1564
+ sinon.match.instanceOf(Meeting),
1565
+ {
1566
+ file: 'meeting/index',
1567
+ function: 'triggerStopReceivingTranscriptionEvent',
1568
+ },
1569
+ EVENT_TRIGGERS.MEETING_STOPPED_RECEIVING_TRANSCRIPTION
1570
+ );
1571
+ });
1555
1572
  });
1556
1573
 
1557
1574
  describe('#setCaptionLanguage', () => {
@@ -1965,11 +1982,12 @@ describe('plugin-meetings', () => {
1965
1982
  describe('#handleLLMOnline', () => {
1966
1983
  beforeEach(() => {
1967
1984
  webex.internal.llm.off = sinon.stub();
1985
+ webex.internal.voicea.getIsCaptionBoxOn = sinon.stub().returns(false);
1986
+ webex.internal.voicea.updateSubchannelSubscriptions = sinon.stub();
1968
1987
  });
1969
1988
 
1970
- it('turns off llm online, emits transcription connected events', () => {
1989
+ it('emits transcription connected events', () => {
1971
1990
  meeting.handleLLMOnline();
1972
- assert.calledOnceWithExactly(webex.internal.llm.off, 'online', meeting.handleLLMOnline);
1973
1991
  assert.calledWith(
1974
1992
  TriggerProxy.trigger,
1975
1993
  sinon.match.instanceOf(Meeting),
@@ -1980,6 +1998,24 @@ describe('plugin-meetings', () => {
1980
1998
  EVENT_TRIGGERS.MEETING_TRANSCRIPTION_CONNECTED
1981
1999
  );
1982
2000
  });
2001
+
2002
+ it('restores transcription subscription when caption intent is enabled', () => {
2003
+ webex.internal.voicea.getIsCaptionBoxOn.returns(true);
2004
+
2005
+ meeting.handleLLMOnline();
2006
+
2007
+ assert.calledOnceWithExactly(webex.internal.voicea.updateSubchannelSubscriptions, {
2008
+ subscribe: ['transcription'],
2009
+ });
2010
+ });
2011
+
2012
+ it('does not restore transcription subscription when caption intent is disabled', () => {
2013
+ webex.internal.voicea.getIsCaptionBoxOn.returns(false);
2014
+
2015
+ meeting.handleLLMOnline();
2016
+
2017
+ assert.notCalled(webex.internal.voicea.updateSubchannelSubscriptions);
2018
+ });
1983
2019
  });
1984
2020
 
1985
2021
  describe('#join', () => {
@@ -1999,6 +2035,7 @@ describe('plugin-meetings', () => {
1999
2035
  it('should have #join', () => {
2000
2036
  assert.exists(meeting.join);
2001
2037
  });
2038
+
2002
2039
  beforeEach(() => {
2003
2040
  setCorrelationIdSpy = sinon.spy(meeting, 'setCorrelationId');
2004
2041
  meeting.setLocus = sinon.stub().returns(true);
@@ -2152,7 +2189,6 @@ describe('plugin-meetings', () => {
2152
2189
  await meeting.join().catch(() => {
2153
2190
  assert.calledOnce(MeetingUtil.joinMeeting);
2154
2191
 
2155
- // Assert that client.locus.join.response error event is not sent from this function, it is now emitted from MeetingUtil.joinMeeting
2156
2192
  assert.calledOnce(webex.internal.newMetrics.submitClientEvent);
2157
2193
  assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
2158
2194
  name: 'client.call.initiated',
@@ -2184,6 +2220,7 @@ describe('plugin-meetings', () => {
2184
2220
  });
2185
2221
  });
2186
2222
  });
2223
+
2187
2224
  describe('lmm, transcription & permissionTokenRefresh decoupling', () => {
2188
2225
  beforeEach(() => {
2189
2226
  sandbox.stub(MeetingUtil, 'joinMeeting').returns(Promise.resolve(joinMeetingResult));
@@ -2254,7 +2291,6 @@ describe('plugin-meetings', () => {
2254
2291
  const locusInfoParseStub = sinon.stub(meeting.locusInfo, 'parse');
2255
2292
  sinon.stub(meeting, 'isJoined').returns(true);
2256
2293
 
2257
- // Set up llm.on stub to capture the registered listener when updateLLMConnection is called
2258
2294
  let locusLLMEventListener;
2259
2295
  meeting.webex.internal.llm.on = sinon.stub().callsFake((eventName, callback) => {
2260
2296
  if (eventName === 'event:locus.state_message') {
@@ -2263,16 +2299,12 @@ describe('plugin-meetings', () => {
2263
2299
  });
2264
2300
  meeting.webex.internal.llm.off = sinon.stub();
2265
2301
 
2266
- // we need the real meeting.updateLLMConnection not the mock
2267
2302
  meeting.updateLLMConnection.restore();
2268
2303
 
2269
- // Call updateLLMConnection to register the listener
2270
2304
  await meeting.updateLLMConnection();
2271
2305
 
2272
- // Verify the listener was registered and we captured it
2273
2306
  assert.isDefined(locusLLMEventListener, 'LLM event listener should be registered');
2274
2307
 
2275
- // Now trigger the event
2276
2308
  const eventData = {
2277
2309
  eventType: 'locus.state_message',
2278
2310
  stateElementsMessage: {
@@ -2292,13 +2324,10 @@ describe('plugin-meetings', () => {
2292
2324
  sinon.stub(meeting.webex.internal.llm, 'hasEverConnected').value(true);
2293
2325
  sinon.stub(meeting.webex.internal.llm, 'registerAndConnect').resolves({});
2294
2326
 
2295
- // Restore the real updateLLMConnection
2296
2327
  meeting.updateLLMConnection.restore();
2297
2328
 
2298
- // Call updateLLMConnection to start the timer
2299
2329
  await meeting.updateLLMConnection();
2300
2330
 
2301
- // Fast forward time by 3 minutes
2302
2331
  fakeClock.tick(3 * 60 * 1000);
2303
2332
 
2304
2333
  assert.calledWith(
@@ -2323,18 +2352,14 @@ describe('plugin-meetings', () => {
2323
2352
  .stub(meeting.webex.internal.llm, 'getDatachannelUrl')
2324
2353
  .returns('https://datachannel1.example.com');
2325
2354
 
2326
- // Restore the real updateLLMConnection
2327
2355
  meeting.updateLLMConnection.restore();
2328
2356
 
2329
- // First, connect LLM and start the timer
2330
2357
  isJoinedStub.returns(true);
2331
2358
  meeting.webex.internal.llm.isConnected.returns(false);
2332
2359
  await meeting.updateLLMConnection();
2333
2360
 
2334
- // Verify timer was started
2335
2361
  assert.exists(meeting.llmHealthCheckTimer);
2336
2362
 
2337
- // Now simulate that we're no longer joined
2338
2363
  isJoinedStub.returns(false);
2339
2364
  meeting.webex.internal.llm.isConnected.returns(true);
2340
2365
 
@@ -2342,10 +2367,8 @@ describe('plugin-meetings', () => {
2342
2367
 
2343
2368
  assert.calledOnce(meeting.webex.internal.llm.disconnectLLM);
2344
2369
 
2345
- // Verify the timer was cleared (should be undefined)
2346
2370
  assert.isUndefined(meeting.llmHealthCheckTimer);
2347
2371
 
2348
- // Fast forward time to ensure no metric is sent
2349
2372
  Metrics.sendBehavioralMetric.resetHistory();
2350
2373
  fakeClock.tick(3 * 60 * 1000);
2351
2374
 
@@ -2380,7 +2403,6 @@ describe('plugin-meetings', () => {
2380
2403
  .stub()
2381
2404
  .rejects(new CaptchaError('bad captcha'));
2382
2405
  const stateMachineFailSpy = sinon.spy(meeting.meetingFiniteStateMachine, 'fail');
2383
- const joinMeetingOptionsSpy = sinon.spy(MeetingUtil, 'joinMeetingOptions');
2384
2406
 
2385
2407
  try {
2386
2408
  await meeting.join();
@@ -2394,8 +2416,7 @@ describe('plugin-meetings', () => {
2394
2416
  );
2395
2417
  assert.instanceOf(error, CaptchaError);
2396
2418
  assert.equal(error.message, 'bad captcha');
2397
- // should not get to the end promise chain, which does do the join
2398
- assert.notCalled(joinMeetingOptionsSpy);
2419
+ assert.notCalled(MeetingUtil.joinMeeting);
2399
2420
  }
2400
2421
  });
2401
2422
 
@@ -2404,7 +2425,6 @@ describe('plugin-meetings', () => {
2404
2425
  .stub()
2405
2426
  .rejects(new PasswordError('bad password'));
2406
2427
  const stateMachineFailSpy = sinon.spy(meeting.meetingFiniteStateMachine, 'fail');
2407
- const joinMeetingOptionsSpy = sinon.spy(MeetingUtil.joinMeetingOptions);
2408
2428
 
2409
2429
  try {
2410
2430
  await meeting.join();
@@ -2418,8 +2438,7 @@ describe('plugin-meetings', () => {
2418
2438
  );
2419
2439
  assert.instanceOf(error, PasswordError);
2420
2440
  assert.equal(error.message, 'bad password');
2421
- // should not get to the end promise chain, which does do the join
2422
- assert.notCalled(joinMeetingOptionsSpy);
2441
+ assert.notCalled(MeetingUtil.joinMeeting);
2423
2442
  }
2424
2443
  });
2425
2444
 
@@ -2428,7 +2447,6 @@ describe('plugin-meetings', () => {
2428
2447
  .stub()
2429
2448
  .rejects(new PermissionError('bad permission'));
2430
2449
  const stateMachineFailSpy = sinon.spy(meeting.meetingFiniteStateMachine, 'fail');
2431
- const joinMeetingOptionsSpy = sinon.spy(MeetingUtil.joinMeetingOptions);
2432
2450
 
2433
2451
  try {
2434
2452
  await meeting.join();
@@ -2442,14 +2460,14 @@ describe('plugin-meetings', () => {
2442
2460
  );
2443
2461
  assert.instanceOf(error, PermissionError);
2444
2462
  assert.equal(error.message, 'bad permission');
2445
- // should not get to the end promise chain, which does do the join
2446
- assert.notCalled(joinMeetingOptionsSpy);
2463
+ assert.notCalled(MeetingUtil.joinMeeting);
2447
2464
  }
2448
2465
  });
2449
2466
  });
2450
2467
  });
2451
2468
  });
2452
2469
 
2470
+
2453
2471
  describe('#addMedia', () => {
2454
2472
  const muteStateStub = {
2455
2473
  handleClientRequest: sinon.stub().returns(Promise.resolve(true)),
@@ -9198,8 +9216,8 @@ describe('plugin-meetings', () => {
9198
9216
  const fakeMultistreamRoapMediaConnection = {
9199
9217
  createSendSlot: () => {
9200
9218
  return {
9201
- setCodecParameters: sinon.stub().resolves(),
9202
- deleteCodecParameters: sinon.stub().resolves(),
9219
+ setCustomCodecParameters: sinon.stub().resolves(),
9220
+ markCustomCodecParametersForDeletion: sinon.stub().resolves(),
9203
9221
  };
9204
9222
  },
9205
9223
  };
@@ -9222,27 +9240,29 @@ describe('plugin-meetings', () => {
9222
9240
  }
9223
9241
  );
9224
9242
 
9225
- it('should set the codec parameters when shouldEnableMusicMode is true', async () => {
9243
+ it('should set custom codec parameters when shouldEnableMusicMode is true', async () => {
9226
9244
  await meeting.enableMusicMode(true);
9227
9245
  assert.calledOnceWithExactly(
9228
- meeting.sendSlotManager.getSlot(MediaType.AudioMain).setCodecParameters,
9246
+ meeting.sendSlotManager.getSlot(MediaType.AudioMain).setCustomCodecParameters,
9247
+ MediaCodecMimeType.OPUS,
9229
9248
  {
9230
9249
  maxaveragebitrate: '64000',
9231
9250
  maxplaybackrate: '48000',
9232
9251
  }
9233
9252
  );
9234
9253
  assert.notCalled(
9235
- meeting.sendSlotManager.getSlot(MediaType.AudioMain).deleteCodecParameters
9254
+ meeting.sendSlotManager.getSlot(MediaType.AudioMain).markCustomCodecParametersForDeletion
9236
9255
  );
9237
9256
  });
9238
9257
 
9239
- it('should set the codec parameters when shouldEnableMusicMode is false', async () => {
9258
+ it('should mark custom codec parameters for deletion when shouldEnableMusicMode is false', async () => {
9240
9259
  await meeting.enableMusicMode(false);
9241
9260
  assert.calledOnceWithExactly(
9242
- meeting.sendSlotManager.getSlot(MediaType.AudioMain).deleteCodecParameters,
9261
+ meeting.sendSlotManager.getSlot(MediaType.AudioMain).markCustomCodecParametersForDeletion,
9262
+ MediaCodecMimeType.OPUS,
9243
9263
  ['maxaveragebitrate', 'maxplaybackrate']
9244
9264
  );
9245
- assert.notCalled(meeting.sendSlotManager.getSlot(MediaType.AudioMain).setCodecParameters);
9265
+ assert.notCalled(meeting.sendSlotManager.getSlot(MediaType.AudioMain).setCustomCodecParameters);
9246
9266
  });
9247
9267
  });
9248
9268
 
@@ -10397,14 +10417,24 @@ describe('plugin-meetings', () => {
10397
10417
  );
10398
10418
  done();
10399
10419
  });
10400
- it('listens to the self admitted guest event', (done) => {
10420
+ it('listens to the self admitted guest event without blocking on token prefetch', async () => {
10401
10421
  meeting.stopKeepAlive = sinon.stub();
10402
10422
  meeting.updateLLMConnection = sinon.stub();
10423
+ let resolvePrefetch;
10424
+
10425
+ meeting.ensureDefaultDatachannelTokenAfterAdmit = sinon
10426
+ .stub()
10427
+ .returns(new Promise((resolve) => {
10428
+ resolvePrefetch = resolve;
10429
+ }));
10403
10430
  meeting.rtcMetrics = {
10404
10431
  sendNextMetrics: sinon.stub(),
10405
10432
  };
10433
+
10406
10434
  meeting.locusInfo.emit({function: 'test', file: 'test'}, 'SELF_ADMITTED_GUEST', test1);
10435
+
10407
10436
  assert.calledOnceWithExactly(meeting.stopKeepAlive);
10437
+ assert.calledOnceWithExactly(meeting.ensureDefaultDatachannelTokenAfterAdmit);
10408
10438
  assert.calledThrice(TriggerProxy.trigger);
10409
10439
  assert.calledWith(
10410
10440
  TriggerProxy.trigger,
@@ -10423,7 +10453,11 @@ describe('plugin-meetings', () => {
10423
10453
  correlation_id: meeting.correlationId,
10424
10454
  }
10425
10455
  );
10426
- done();
10456
+
10457
+ resolvePrefetch(false);
10458
+ await Promise.resolve();
10459
+
10460
+ assert.calledOnce(meeting.updateLLMConnection);
10427
10461
  });
10428
10462
 
10429
10463
  it('listens to the breakouts changed event', () => {
@@ -12745,6 +12779,93 @@ describe('plugin-meetings', () => {
12745
12779
  });
12746
12780
  });
12747
12781
 
12782
+ describe('#saveDataChannelToken', () => {
12783
+ beforeEach(() => {
12784
+ webex.internal.llm.setDatachannelToken = sinon.stub();
12785
+ });
12786
+
12787
+ it('saves datachannelToken into LLM as Default', () => {
12788
+ meeting.saveDataChannelToken({
12789
+ locus: {
12790
+ self: {datachannelToken: 'default-token'},
12791
+ },
12792
+ });
12793
+
12794
+ assert.calledWithExactly(
12795
+ webex.internal.llm.setDatachannelToken,
12796
+ 'default-token',
12797
+ 'llm-default-session'
12798
+ );
12799
+ });
12800
+
12801
+ it('saves practiceSessionDatachannelToken into LLM as PracticeSession', () => {
12802
+ meeting.saveDataChannelToken({
12803
+ locus: {
12804
+ self: {practiceSessionDatachannelToken: 'ps-token'},
12805
+ },
12806
+ });
12807
+
12808
+ assert.calledWithExactly(
12809
+ webex.internal.llm.setDatachannelToken,
12810
+ 'ps-token',
12811
+ 'llm-practice-session'
12812
+ );
12813
+ });
12814
+
12815
+ it('saves both tokens when both are present', () => {
12816
+ meeting.saveDataChannelToken({
12817
+ locus: {
12818
+ self: {
12819
+ datachannelToken: 'default-token',
12820
+ practiceSessionDatachannelToken: 'ps-token',
12821
+ },
12822
+ },
12823
+ });
12824
+
12825
+ assert.calledTwice(webex.internal.llm.setDatachannelToken);
12826
+ assert.calledWithExactly(
12827
+ webex.internal.llm.setDatachannelToken,
12828
+ 'default-token',
12829
+ 'llm-default-session'
12830
+ );
12831
+ assert.calledWithExactly(
12832
+ webex.internal.llm.setDatachannelToken,
12833
+ 'ps-token',
12834
+ 'llm-practice-session'
12835
+ );
12836
+ });
12837
+
12838
+ it('does not call setDatachannelToken when no tokens are present', () => {
12839
+ meeting.saveDataChannelToken({locus: {self: {}}});
12840
+
12841
+ assert.notCalled(webex.internal.llm.setDatachannelToken);
12842
+ });
12843
+
12844
+ it('handles undefined join gracefully', () => {
12845
+ meeting.saveDataChannelToken(undefined);
12846
+
12847
+ assert.notCalled(webex.internal.llm.setDatachannelToken);
12848
+ });
12849
+
12850
+ it('handles missing locus.self gracefully', () => {
12851
+ meeting.saveDataChannelToken({locus: {}});
12852
+
12853
+ assert.notCalled(webex.internal.llm.setDatachannelToken);
12854
+ });
12855
+ });
12856
+
12857
+ describe('#clearDataChannelToken', () => {
12858
+ beforeEach(() => {
12859
+ webex.internal.llm.resetDatachannelTokens = sinon.stub();
12860
+ });
12861
+
12862
+ it('calls resetDatachannelTokens on LLM', () => {
12863
+ meeting.clearDataChannelToken();
12864
+
12865
+ assert.calledOnce(webex.internal.llm.resetDatachannelTokens);
12866
+ });
12867
+ });
12868
+
12748
12869
  describe('#updateLLMConnection', () => {
12749
12870
  beforeEach(() => {
12750
12871
  webex.internal.llm.isConnected = sinon.stub().returns(false);
@@ -13011,15 +13132,14 @@ describe('plugin-meetings', () => {
13011
13132
  undefined
13012
13133
  );
13013
13134
  });
13014
- it('passes dataChannelToken to registerAndConnect', async () => {
13135
+ it('passes dataChannelToken from LLM to registerAndConnect', async () => {
13015
13136
  meeting.joinedWith = {state: 'JOINED'};
13016
13137
  meeting.locusInfo = {
13017
13138
  url: 'a url',
13018
13139
  info: {datachannelUrl: 'a datachannel url'},
13019
- self: {datachannelToken: 'token-123'},
13020
13140
  };
13021
13141
 
13022
- webex.internal.llm.getDatachannelToken.returns(undefined);
13142
+ webex.internal.llm.getDatachannelToken.withArgs('llm-default-session').returns('token-123');
13023
13143
 
13024
13144
  await meeting.updateLLMConnection();
13025
13145
 
@@ -13029,17 +13149,16 @@ describe('plugin-meetings', () => {
13029
13149
  'a datachannel url',
13030
13150
  'token-123'
13031
13151
  );
13032
- assert.calledWithExactly(webex.internal.llm.setDatachannelToken, 'token-123', 'llm-default-session');
13152
+ assert.notCalled(webex.internal.llm.setDatachannelToken);
13033
13153
  });
13034
- it('prefers refreshed token over locus self token', async () => {
13154
+ it('passes undefined token when LLM has no token stored', async () => {
13035
13155
  meeting.joinedWith = {state: 'JOINED'};
13036
13156
  meeting.locusInfo = {
13037
13157
  url: 'a url',
13038
13158
  info: {datachannelUrl: 'a datachannel url'},
13039
- self: {datachannelToken: 'locus-token'},
13040
13159
  };
13041
13160
 
13042
- webex.internal.llm.getDatachannelToken.withArgs('llm-default-session').returns('refreshed-token');
13161
+ webex.internal.llm.getDatachannelToken.returns(undefined);
13043
13162
 
13044
13163
  await meeting.updateLLMConnection();
13045
13164
 
@@ -13047,7 +13166,7 @@ describe('plugin-meetings', () => {
13047
13166
  webex.internal.llm.registerAndConnect,
13048
13167
  'a url',
13049
13168
  'a datachannel url',
13050
- 'refreshed-token'
13169
+ undefined
13051
13170
  );
13052
13171
 
13053
13172
  assert.notCalled(webex.internal.llm.setDatachannelToken);
@@ -13058,7 +13177,6 @@ describe('plugin-meetings', () => {
13058
13177
  meeting.locusInfo = {
13059
13178
  url: 'a url',
13060
13179
  info: {datachannelUrl: 'a datachannel url'},
13061
- self: {datachannelToken: 'token-123'},
13062
13180
  };
13063
13181
 
13064
13182
  webex.internal.llm.getDatachannelToken.returns(undefined);
@@ -13070,9 +13188,9 @@ describe('plugin-meetings', () => {
13070
13188
  webex.internal.llm.registerAndConnect,
13071
13189
  'a url',
13072
13190
  'a datachannel url',
13073
- 'token-123'
13191
+ undefined
13074
13192
  );
13075
- assert.calledWithExactly(webex.internal.llm.setDatachannelToken, 'token-123', 'llm-default-session');
13193
+ assert.notCalled(webex.internal.llm.setDatachannelToken);
13076
13194
  });
13077
13195
 
13078
13196
  describe('#clearMeetingData', () => {
@@ -13083,7 +13201,7 @@ describe('plugin-meetings', () => {
13083
13201
  meeting.annotation.deregisterEvents = sinon.stub();
13084
13202
  meeting.clearLLMHealthCheckTimer = sinon.stub();
13085
13203
  meeting.stopTranscription = sinon.stub();
13086
- meeting.transcription = {};
13204
+ meeting.clearDataChannelToken = sinon.stub();
13087
13205
  meeting.shareStatus = 'no-share';
13088
13206
  });
13089
13207
 
@@ -13107,6 +13225,8 @@ describe('plugin-meetings', () => {
13107
13225
  );
13108
13226
  assert.calledOnce(meeting.clearLLMHealthCheckTimer);
13109
13227
  assert.calledOnce(meeting.stopTranscription);
13228
+ assert.isUndefined(meeting.transcription);
13229
+ assert.calledOnce(meeting.clearDataChannelToken);
13110
13230
  assert.calledOnce(meeting.annotation.deregisterEvents);
13111
13231
  });
13112
13232
  it('continues cleanup when disconnectLLM fails during meeting data cleanup', async () => {
@@ -13127,8 +13247,19 @@ describe('plugin-meetings', () => {
13127
13247
  );
13128
13248
  assert.calledOnce(meeting.clearLLMHealthCheckTimer);
13129
13249
  assert.calledOnce(meeting.stopTranscription);
13250
+ assert.isUndefined(meeting.transcription);
13251
+ assert.calledOnce(meeting.clearDataChannelToken);
13130
13252
  assert.calledOnce(meeting.annotation.deregisterEvents);
13131
13253
  });
13254
+ it('always calls stopTranscription even when transcription is undefined', async () => {
13255
+ meeting.transcription = undefined;
13256
+
13257
+ await meeting.clearMeetingData();
13258
+
13259
+ assert.calledOnce(meeting.stopTranscription);
13260
+ assert.isUndefined(meeting.transcription);
13261
+ assert.calledOnce(meeting.clearDataChannelToken);
13262
+ });
13132
13263
  });
13133
13264
  });
13134
13265
 
@@ -1285,10 +1285,10 @@ describe('plugin-meetings', () => {
1285
1285
  assert.exists(result.dispose);
1286
1286
  });
1287
1287
 
1288
- it('creates noise reduction effect with ST model', async () => {
1288
+ it('creates noise reduction effect with OFMV model', async () => {
1289
1289
  const result = await webex.meetings.createNoiseReductionEffect({
1290
1290
  audioContext: {},
1291
- model: 'st',
1291
+ model: 'ofmv',
1292
1292
  });
1293
1293
 
1294
1294
  assert.exists(result);
@@ -1300,7 +1300,7 @@ describe('plugin-meetings', () => {
1300
1300
  authToken: 'fake_token',
1301
1301
  mode: 'WORKLET',
1302
1302
  avoidSimd: false,
1303
- model: 'st',
1303
+ model: 'ofmv',
1304
1304
  });
1305
1305
  assert.exists(result.enable);
1306
1306
  assert.exists(result.disable);