@webex/plugin-meetings 3.11.0-next.3 → 3.11.0-next.30

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 (99) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/config.js +5 -1
  4. package/dist/config.js.map +1 -1
  5. package/dist/hashTree/hashTree.js +18 -0
  6. package/dist/hashTree/hashTree.js.map +1 -1
  7. package/dist/hashTree/hashTreeParser.js +603 -266
  8. package/dist/hashTree/hashTreeParser.js.map +1 -1
  9. package/dist/hashTree/types.js +4 -2
  10. package/dist/hashTree/types.js.map +1 -1
  11. package/dist/hashTree/utils.js +10 -0
  12. package/dist/hashTree/utils.js.map +1 -1
  13. package/dist/index.js +2 -1
  14. package/dist/index.js.map +1 -1
  15. package/dist/interceptors/constant.js +12 -0
  16. package/dist/interceptors/constant.js.map +1 -0
  17. package/dist/interceptors/dataChannelAuthToken.js +233 -0
  18. package/dist/interceptors/dataChannelAuthToken.js.map +1 -0
  19. package/dist/interceptors/index.js +7 -0
  20. package/dist/interceptors/index.js.map +1 -1
  21. package/dist/interpretation/index.js +1 -1
  22. package/dist/interpretation/siLanguage.js +1 -1
  23. package/dist/locus-info/index.js +80 -44
  24. package/dist/locus-info/index.js.map +1 -1
  25. package/dist/locus-info/types.js.map +1 -1
  26. package/dist/media/MediaConnectionAwaiter.js +57 -1
  27. package/dist/media/MediaConnectionAwaiter.js.map +1 -1
  28. package/dist/media/properties.js +4 -2
  29. package/dist/media/properties.js.map +1 -1
  30. package/dist/meeting/index.js +134 -40
  31. package/dist/meeting/index.js.map +1 -1
  32. package/dist/meeting/request.js +50 -0
  33. package/dist/meeting/request.js.map +1 -1
  34. package/dist/meeting/request.type.js.map +1 -1
  35. package/dist/meeting/util.js +108 -2
  36. package/dist/meeting/util.js.map +1 -1
  37. package/dist/meetings/index.js +76 -34
  38. package/dist/meetings/index.js.map +1 -1
  39. package/dist/metrics/constants.js +2 -1
  40. package/dist/metrics/constants.js.map +1 -1
  41. package/dist/multistream/mediaRequestManager.js +1 -1
  42. package/dist/multistream/mediaRequestManager.js.map +1 -1
  43. package/dist/multistream/remoteMediaManager.js +11 -0
  44. package/dist/multistream/remoteMediaManager.js.map +1 -1
  45. package/dist/reactions/reactions.type.js.map +1 -1
  46. package/dist/types/config.d.ts +3 -0
  47. package/dist/types/hashTree/hashTree.d.ts +7 -0
  48. package/dist/types/hashTree/hashTreeParser.d.ts +83 -12
  49. package/dist/types/hashTree/types.d.ts +3 -0
  50. package/dist/types/hashTree/utils.d.ts +6 -0
  51. package/dist/types/interceptors/constant.d.ts +5 -0
  52. package/dist/types/interceptors/dataChannelAuthToken.d.ts +35 -0
  53. package/dist/types/interceptors/index.d.ts +2 -1
  54. package/dist/types/locus-info/index.d.ts +9 -2
  55. package/dist/types/locus-info/types.d.ts +1 -0
  56. package/dist/types/media/MediaConnectionAwaiter.d.ts +10 -1
  57. package/dist/types/media/properties.d.ts +2 -1
  58. package/dist/types/meeting/index.d.ts +27 -5
  59. package/dist/types/meeting/request.d.ts +16 -1
  60. package/dist/types/meeting/request.type.d.ts +5 -0
  61. package/dist/types/meeting/util.d.ts +28 -0
  62. package/dist/types/meetings/index.d.ts +3 -1
  63. package/dist/types/metrics/constants.d.ts +1 -0
  64. package/dist/types/reactions/reactions.type.d.ts +1 -0
  65. package/dist/webinar/index.js +1 -1
  66. package/package.json +22 -22
  67. package/src/config.ts +3 -0
  68. package/src/hashTree/hashTree.ts +17 -0
  69. package/src/hashTree/hashTreeParser.ts +525 -188
  70. package/src/hashTree/types.ts +4 -0
  71. package/src/hashTree/utils.ts +9 -0
  72. package/src/index.ts +6 -1
  73. package/src/interceptors/constant.ts +6 -0
  74. package/src/interceptors/dataChannelAuthToken.ts +142 -0
  75. package/src/interceptors/index.ts +2 -1
  76. package/src/locus-info/index.ts +110 -35
  77. package/src/locus-info/types.ts +1 -0
  78. package/src/media/MediaConnectionAwaiter.ts +41 -1
  79. package/src/media/properties.ts +3 -1
  80. package/src/meeting/index.ts +101 -22
  81. package/src/meeting/request.ts +42 -0
  82. package/src/meeting/request.type.ts +6 -0
  83. package/src/meeting/util.ts +132 -1
  84. package/src/meetings/index.ts +88 -7
  85. package/src/metrics/constants.ts +1 -0
  86. package/src/multistream/mediaRequestManager.ts +1 -1
  87. package/src/multistream/remoteMediaManager.ts +13 -0
  88. package/src/reactions/reactions.type.ts +1 -0
  89. package/test/unit/spec/hashTree/hashTree.ts +66 -0
  90. package/test/unit/spec/hashTree/hashTreeParser.ts +1594 -162
  91. package/test/unit/spec/interceptors/dataChannelAuthToken.ts +141 -0
  92. package/test/unit/spec/locus-info/index.js +173 -45
  93. package/test/unit/spec/media/MediaConnectionAwaiter.ts +41 -1
  94. package/test/unit/spec/media/properties.ts +12 -3
  95. package/test/unit/spec/meeting/index.js +414 -62
  96. package/test/unit/spec/meeting/request.js +64 -0
  97. package/test/unit/spec/meeting/utils.js +294 -22
  98. package/test/unit/spec/meetings/index.js +550 -10
  99. package/test/unit/spec/multistream/remoteMediaManager.ts +30 -0
@@ -41,14 +41,23 @@ describe('MediaProperties', () => {
41
41
  describe('waitForMediaConnectionConnected', () => {
42
42
  it('resolves if media connection is connected', async () => {
43
43
  const waitForMediaConnectionConnectedResult = new Defer();
44
+ const correlationId = 'aaaa-bbbb-cccc-dddd';
44
45
 
45
- sinon
46
+ let capturedInstance;
47
+ const stub = sinon
46
48
  .stub(MediaConnectionAwaiter.prototype, 'waitForMediaConnectionConnected')
47
- .returns(waitForMediaConnectionConnectedResult.promise);
49
+ .callsFake(function () {
50
+ capturedInstance = this;
51
+ return waitForMediaConnectionConnectedResult.promise;
52
+ });
48
53
 
49
54
  waitForMediaConnectionConnectedResult.resolve();
50
55
 
51
- await mediaProperties.waitForMediaConnectionConnected();
56
+ await mediaProperties.waitForMediaConnectionConnected(correlationId);
57
+
58
+ assert.calledOnce(stub);
59
+ assert.equal(capturedInstance.correlationId, correlationId);
60
+ assert.equal(capturedInstance.webrtcMediaConnection, mockMC);
52
61
  });
53
62
  it('rejects if media connection is not connected', async () => {
54
63
  const waitForMediaConnectionConnectedResult = new Defer();
@@ -123,7 +123,6 @@ import {EVENT_TRIGGERS as VOICEAEVENTS} from '@webex/internal-plugin-voicea';
123
123
  import {createBrbState} from '@webex/plugin-meetings/src/meeting/brbState';
124
124
  import JoinForbiddenError from '../../../../src/common/errors/join-forbidden-error';
125
125
  import {EventEmitter} from 'stream';
126
-
127
126
  describe('plugin-meetings', () => {
128
127
  const logger = {
129
128
  info: () => {},
@@ -265,6 +264,7 @@ describe('plugin-meetings', () => {
265
264
  stopReachability: sinon.stub(),
266
265
  isSubnetReachable: sinon.stub().returns(true),
267
266
  };
267
+ webex.internal.llm.isDataChannelTokenEnabled = sinon.stub().resolves(false)
268
268
  webex.internal.llm.on = sinon.stub();
269
269
  webex.internal.newMetrics.callDiagnosticLatencies = new CallDiagnosticLatencies(
270
270
  {},
@@ -1249,7 +1249,7 @@ describe('plugin-meetings', () => {
1249
1249
  });
1250
1250
 
1251
1251
  [
1252
- {errorName: 'SdpOfferCreationError', description: 'if we fail to create the offer on first attempt'},
1252
+ {errorName: 'SdpOfferCreationError', description: 'if we fail to create the offer on first attempt'},
1253
1253
  {errorName: 'WebrtcApiNotAvailableError', description: 'if RTCPeerConnection is not available'},
1254
1254
  ].forEach(({errorName, description}) => {
1255
1255
  it(`should not attempt a retry ${description}`, async () => {
@@ -1882,6 +1882,53 @@ describe('plugin-meetings', () => {
1882
1882
  fakeProcessedReaction
1883
1883
  );
1884
1884
  });
1885
+
1886
+ it('should process if participantId does not exist in membersCollection but has displayName in Webinar', () => {
1887
+ LoggerProxy.logger.warn = sinon.stub();
1888
+ meeting.isReactionsSupported = sinon.stub().returns(true);
1889
+ meeting.config.receiveReactions = true;
1890
+ meeting.locusInfo.info = {isWebinar: true};
1891
+ const fakeSendersName = 'Fake reactors name';
1892
+ const fakeReactionPayload = {
1893
+ type: 'fake_type',
1894
+ codepoints: 'fake_codepoints',
1895
+ shortcodes: 'fake_shortcodes',
1896
+ tone: {
1897
+ type: 'fake_tone_type',
1898
+ codepoints: 'fake_tone_codepoints',
1899
+ shortcodes: 'fake_tone_shortcodes',
1900
+ },
1901
+ };
1902
+ const fakeSenderPayload = {
1903
+ displayName: 'Fake reactors name',
1904
+ participantId: 'fake_participant_id',
1905
+ };
1906
+ const fakeProcessedReaction = {
1907
+ reaction: fakeReactionPayload,
1908
+ sender: {
1909
+ id: fakeSenderPayload.participantId,
1910
+ name: fakeSendersName,
1911
+ },
1912
+ };
1913
+ const fakeRelayEvent = {
1914
+ data: {
1915
+ relayType: REACTION_RELAY_TYPES.REACTION,
1916
+ reaction: fakeReactionPayload,
1917
+ sender: fakeSenderPayload,
1918
+ },
1919
+ };
1920
+ meeting.processRelayEvent(fakeRelayEvent);
1921
+ assert.calledWith(
1922
+ TriggerProxy.trigger,
1923
+ sinon.match.instanceOf(Meeting),
1924
+ {
1925
+ file: 'meeting/index',
1926
+ function: 'join',
1927
+ },
1928
+ EVENT_TRIGGERS.MEETING_RECEIVE_REACTIONS,
1929
+ fakeProcessedReaction
1930
+ );
1931
+ });
1885
1932
  });
1886
1933
 
1887
1934
  describe('#handleLLMOnline', () => {
@@ -3028,6 +3075,111 @@ describe('plugin-meetings', () => {
3028
3075
  checkWorking({allowMediaInLobby: true});
3029
3076
  });
3030
3077
 
3078
+ const setupLobbyTest = () => {
3079
+ meeting.roap.doTurnDiscovery = sinon
3080
+ .stub()
3081
+ .resolves({turnServerInfo: undefined, turnDiscoverySkippedReason: undefined});
3082
+
3083
+ meeting.meetingState = 'ACTIVE';
3084
+ meeting.locusInfo.parsedLocus = {self: {state: 'IDLE'}};
3085
+ meeting.isUserUnadmitted = true;
3086
+
3087
+ // Mock locusMediaRequest
3088
+ meeting.locusMediaRequest = {
3089
+ send: sinon.stub().resolves(),
3090
+ isConfluenceCreated: sinon.stub().returns(false),
3091
+ };
3092
+
3093
+ sinon.stub(RemoteMediaManagerModule, 'RemoteMediaManager').returns({
3094
+ start: sinon.stub().resolves(),
3095
+ on: sinon.stub(),
3096
+ logAllReceiveSlots: sinon.stub(),
3097
+ });
3098
+
3099
+ meeting.isMultistream = true;
3100
+
3101
+ const createFakeStream = (id) => ({
3102
+ on: sinon.stub(),
3103
+ off: sinon.stub(),
3104
+ userMuted: false,
3105
+ systemMuted: false,
3106
+ get muted() {
3107
+ return this.userMuted || this.systemMuted;
3108
+ },
3109
+ setUnmuteAllowed: sinon.stub(),
3110
+ setUserMuted: sinon.stub(),
3111
+ outputStream: {
3112
+ getTracks: () => [{id}],
3113
+ },
3114
+ getSettings: sinon.stub().returns({}),
3115
+ });
3116
+
3117
+ return {
3118
+ fakeMicrophoneStream: createFakeStream('fake mic'),
3119
+ fakeCameraStream: createFakeStream('fake camera'),
3120
+ };
3121
+ };
3122
+
3123
+ it('should not publish any local streams when in the lobby and allowPublishMediaInLobby is false', async () => {
3124
+ const {fakeMicrophoneStream, fakeCameraStream} = setupLobbyTest();
3125
+
3126
+ const publishStreamStub = sinon.stub();
3127
+ fakeMediaConnection.createSendSlot = sinon.stub().returns({
3128
+ publishStream: publishStreamStub,
3129
+ unpublishStream: sinon.stub(),
3130
+ setNamedMediaGroups: sinon.stub(),
3131
+ });
3132
+
3133
+ await meeting.addMedia({
3134
+ allowMediaInLobby: true,
3135
+ allowPublishMediaInLobby: false,
3136
+ audioEnabled: true,
3137
+ videoEnabled: true,
3138
+ localStreams: {
3139
+ microphone: fakeMicrophoneStream,
3140
+ camera: fakeCameraStream,
3141
+ },
3142
+ });
3143
+
3144
+ assert.notCalled(publishStreamStub);
3145
+ });
3146
+
3147
+ it('should publish local streams when in the lobby and allowPublishMediaInLobby is true', async () => {
3148
+ const {fakeMicrophoneStream, fakeCameraStream} = setupLobbyTest();
3149
+
3150
+ const audioSlot = {
3151
+ publishStream: sinon.stub(),
3152
+ unpublishStream: sinon.stub(),
3153
+ setNamedMediaGroups: sinon.stub(),
3154
+ };
3155
+ const videoSlot = {
3156
+ publishStream: sinon.stub(),
3157
+ unpublishStream: sinon.stub(),
3158
+ setNamedMediaGroups: sinon.stub(),
3159
+ };
3160
+
3161
+ fakeMediaConnection.createSendSlot = sinon.stub().callsFake((mediaType) => {
3162
+ if (mediaType === 'AUDIO-MAIN') {
3163
+ return audioSlot;
3164
+ }
3165
+ return videoSlot;
3166
+ });
3167
+
3168
+ await meeting.addMedia({
3169
+ allowMediaInLobby: true,
3170
+ allowPublishMediaInLobby: true,
3171
+ audioEnabled: true,
3172
+ videoEnabled: true,
3173
+ localStreams: {
3174
+ microphone: fakeMicrophoneStream,
3175
+ camera: fakeCameraStream,
3176
+ },
3177
+ });
3178
+
3179
+ assert.calledOnceWithExactly(audioSlot.publishStream, fakeMicrophoneStream);
3180
+ assert.calledOnceWithExactly(videoSlot.publishStream, fakeCameraStream);
3181
+ });
3182
+
3031
3183
  it('should create rtcMetrics and pass them to Media.createMediaConnection()', async () => {
3032
3184
  const setIntervalOriginal = window.setInterval;
3033
3185
  window.setInterval = sinon.stub().returns(1);
@@ -9149,7 +9301,10 @@ describe('plugin-meetings', () => {
9149
9301
 
9150
9302
  // check that the right things were called by the callback
9151
9303
  assert.calledOnceWithExactly(meeting.waitForRemoteSDPAnswer);
9152
- assert.calledOnceWithExactly(meeting.mediaProperties.waitForMediaConnectionConnected);
9304
+ assert.calledOnceWithExactly(
9305
+ meeting.mediaProperties.waitForMediaConnectionConnected,
9306
+ meeting.correlationId
9307
+ );
9153
9308
  });
9154
9309
  });
9155
9310
 
@@ -12424,16 +12579,20 @@ describe('plugin-meetings', () => {
12424
12579
  webex.internal.llm.isConnected = sinon.stub().returns(false);
12425
12580
  webex.internal.llm.getLocusUrl = sinon.stub();
12426
12581
  webex.internal.llm.getDatachannelUrl = sinon.stub();
12427
- webex.internal.llm.registerAndConnect = sinon
12428
- .stub()
12429
- .returns(Promise.resolve('something'));
12430
- webex.internal.llm.disconnectLLM = sinon.stub().returns(Promise.resolve());
12431
- meeting.webex.internal.llm.on = sinon.stub();
12432
- meeting.webex.internal.llm.off = sinon.stub();
12582
+ webex.internal.llm.registerAndConnect = sinon.stub().resolves('something');
12583
+ webex.internal.llm.disconnectLLM = sinon.stub().resolves();
12584
+ webex.internal.llm.on = sinon.stub();
12585
+ webex.internal.llm.off = sinon.stub();
12586
+ webex.internal.llm.getDatachannelToken = sinon.stub().returns(undefined);
12587
+ webex.internal.llm.setDatachannelToken = sinon.stub();
12588
+
12433
12589
  meeting.processRelayEvent = sinon.stub();
12590
+ meeting.processLocusLLMEvent = sinon.stub();
12591
+ meeting.clearLLMHealthCheckTimer = sinon.stub();
12592
+ meeting.startLLMHealthCheckTimer = sinon.stub();
12593
+
12434
12594
  meeting.webinar.isJoinPracticeSessionDataChannel = sinon.stub().returns(false);
12435
12595
  });
12436
-
12437
12596
  it('does not connect if the call is not joined yet', async () => {
12438
12597
  meeting.joinedWith = {state: 'any other state'};
12439
12598
  webex.internal.llm.getLocusUrl.returns('a url');
@@ -12447,31 +12606,21 @@ describe('plugin-meetings', () => {
12447
12606
  assert.equal(result, undefined);
12448
12607
  assert.notCalled(meeting.webex.internal.llm.on);
12449
12608
  });
12450
-
12451
12609
  it('returns undefined if llm is already connected and the locus url is unchanged', async () => {
12452
12610
  meeting.joinedWith = {state: 'JOINED'};
12453
- webex.internal.llm.isConnected.returns(true);
12454
- webex.internal.llm.getLocusUrl.returns('a url');
12455
- webex.internal.llm.getDatachannelUrl.returns('a datachannel url');
12456
-
12457
- meeting.locusInfo = {url: 'a url', info: {datachannelUrl: 'a datachannel url'}};
12458
-
12459
- const result = await meeting.updateLLMConnection();
12460
-
12461
- assert.notCalled(webex.internal.llm.registerAndConnect);
12462
- assert.notCalled(webex.internal.llm.disconnectLLM);
12463
- assert.equal(result, undefined);
12464
- assert.notCalled(meeting.webex.internal.llm.on);
12465
- });
12466
-
12467
- it('connects if not already connected', async () => {
12468
- meeting.joinedWith = {state: 'JOINED'};
12469
- meeting.locusInfo = {url: 'a url', info: {datachannelUrl: 'a datachannel url'}};
12611
+ meeting.locusInfo = {
12612
+ url: 'a url',
12613
+ info: {datachannelUrl: 'a datachannel url'}
12614
+ };
12470
12615
 
12471
12616
  const result = await meeting.updateLLMConnection();
12472
-
12473
12617
  assert.notCalled(webex.internal.llm.disconnectLLM);
12474
- assert.calledWith(webex.internal.llm.registerAndConnect, 'a url', 'a datachannel url');
12618
+ assert.calledWithExactly(
12619
+ webex.internal.llm.registerAndConnect,
12620
+ 'a url',
12621
+ 'a datachannel url',
12622
+ undefined
12623
+ );
12475
12624
  assert.equal(result, 'something');
12476
12625
  assert.calledWithExactly(
12477
12626
  meeting.webex.internal.llm.off,
@@ -12494,27 +12643,49 @@ describe('plugin-meetings', () => {
12494
12643
  meeting.processLocusLLMEvent
12495
12644
  );
12496
12645
  });
12646
+ it('connects if not already connected', async () => {
12647
+ meeting.joinedWith = {state: 'JOINED'};
12648
+ meeting.locusInfo = {url: 'a url', info: {datachannelUrl: 'a datachannel url'}};
12497
12649
 
12498
- it('disconnects if first if the locus url has changed', async () => {
12650
+ const result = await meeting.updateLLMConnection();
12651
+
12652
+ assert.notCalled(webex.internal.llm.disconnectLLM);
12653
+ assert.calledWithExactly(
12654
+ webex.internal.llm.registerAndConnect,
12655
+ 'a url',
12656
+ 'a datachannel url',
12657
+ undefined
12658
+ );
12659
+ assert.equal(result, 'something');
12660
+ });
12661
+ it('disconnects if the locus url has changed', async () => {
12499
12662
  meeting.joinedWith = {state: 'JOINED'};
12663
+
12500
12664
  webex.internal.llm.isConnected.returns(true);
12501
12665
  webex.internal.llm.getLocusUrl.returns('a url');
12502
- webex.internal.llm.getDatachannelUrl.returns('a datachannel url');
12503
12666
 
12504
- meeting.locusInfo = {url: 'a different url', info: {datachannelUrl: 'a datachannel url'}};
12667
+ meeting.locusInfo = {
12668
+ url: 'a different url',
12669
+ info: {datachannelUrl: 'a datachannel url'},
12670
+ self: {}
12671
+ };
12505
12672
 
12506
12673
  const result = await meeting.updateLLMConnection();
12507
12674
 
12508
- assert.calledWith(webex.internal.llm.disconnectLLM, {
12509
- code: 3050,
12510
- reason: 'done (permanent)',
12511
- });
12512
- assert.calledWith(
12675
+ assert.calledWithExactly(
12676
+ webex.internal.llm.disconnectLLM,
12677
+ {code: 3050, reason: 'done (permanent)'}
12678
+ );
12679
+
12680
+ assert.calledWithExactly(
12513
12681
  webex.internal.llm.registerAndConnect,
12514
12682
  'a different url',
12515
- 'a datachannel url'
12683
+ 'a datachannel url',
12684
+ undefined
12516
12685
  );
12686
+
12517
12687
  assert.equal(result, 'something');
12688
+
12518
12689
  assert.calledWithExactly(
12519
12690
  meeting.webex.internal.llm.off,
12520
12691
  'event:relay.event',
@@ -12526,6 +12697,7 @@ describe('plugin-meetings', () => {
12526
12697
  meeting.processLocusLLMEvent
12527
12698
  );
12528
12699
  assert.callCount(meeting.webex.internal.llm.off, 4);
12700
+
12529
12701
  assert.calledWithExactly(
12530
12702
  meeting.webex.internal.llm.on,
12531
12703
  'event:relay.event',
@@ -12537,27 +12709,33 @@ describe('plugin-meetings', () => {
12537
12709
  meeting.processLocusLLMEvent
12538
12710
  );
12539
12711
  });
12540
-
12541
- it('disconnects it first if the data channel url has changed', async () => {
12712
+ it('disconnects if the data channel url has changed', async () => {
12542
12713
  meeting.joinedWith = {state: 'JOINED'};
12543
12714
  webex.internal.llm.isConnected.returns(true);
12544
12715
  webex.internal.llm.getLocusUrl.returns('a url');
12545
- webex.internal.llm.getDatachannelUrl.returns('a datachannel url');
12546
12716
 
12547
- meeting.locusInfo = {url: 'a url', info: {datachannelUrl: 'a different datachannel url'}};
12717
+ meeting.locusInfo = {
12718
+ url: 'a url',
12719
+ info: {datachannelUrl: 'a different datachannel url'},
12720
+ self: {}
12721
+ };
12548
12722
 
12549
12723
  const result = await meeting.updateLLMConnection();
12550
12724
 
12551
- assert.calledWith(webex.internal.llm.disconnectLLM, {
12552
- code: 3050,
12553
- reason: 'done (permanent)',
12554
- });
12555
- assert.calledWith(
12725
+ assert.calledWithExactly(
12726
+ webex.internal.llm.disconnectLLM,
12727
+ {code: 3050, reason: 'done (permanent)'}
12728
+ );
12729
+
12730
+ assert.calledWithExactly(
12556
12731
  webex.internal.llm.registerAndConnect,
12557
12732
  'a url',
12558
- 'a different datachannel url'
12733
+ 'a different datachannel url',
12734
+ undefined
12559
12735
  );
12736
+
12560
12737
  assert.equal(result, 'something');
12738
+
12561
12739
  assert.calledWithExactly(
12562
12740
  meeting.webex.internal.llm.off,
12563
12741
  'event:relay.event',
@@ -12568,6 +12746,7 @@ describe('plugin-meetings', () => {
12568
12746
  'event:locus.state_message',
12569
12747
  meeting.processLocusLLMEvent
12570
12748
  );
12749
+
12571
12750
  assert.calledWithExactly(
12572
12751
  meeting.webex.internal.llm.on,
12573
12752
  'event:relay.event',
@@ -12579,7 +12758,6 @@ describe('plugin-meetings', () => {
12579
12758
  meeting.processLocusLLMEvent
12580
12759
  );
12581
12760
  });
12582
-
12583
12761
  it('disconnects when the state is not JOINED', async () => {
12584
12762
  meeting.joinedWith = {state: 'any other state'};
12585
12763
  webex.internal.llm.isConnected.returns(true);
@@ -12589,35 +12767,140 @@ describe('plugin-meetings', () => {
12589
12767
 
12590
12768
  const result = await meeting.updateLLMConnection();
12591
12769
 
12592
- assert.calledWith(webex.internal.llm.disconnectLLM, undefined);
12770
+ assert.calledWith(webex.internal.llm.disconnectLLM, {
12771
+ code: 3050,
12772
+ reason: 'done (permanent)',
12773
+ });
12593
12774
  assert.notCalled(webex.internal.llm.registerAndConnect);
12594
12775
  assert.equal(result, undefined);
12776
+ });
12777
+ it('connects practice session data channel when PS started', async () => {
12778
+ meeting.joinedWith = {state: 'JOINED'};
12779
+ meeting.locusInfo = {
12780
+ url: 'a url',
12781
+ info: {
12782
+ datachannelUrl: 'a datachannel url',
12783
+ practiceSessionDatachannelUrl: 'ps-url',
12784
+ },
12785
+ };
12786
+ meeting.webinar.isJoinPracticeSessionDataChannel.returns(true);
12787
+
12788
+ await meeting.updateLLMConnection();
12789
+
12595
12790
  assert.calledWithExactly(
12596
- meeting.webex.internal.llm.off,
12597
- 'event:relay.event',
12598
- meeting.processRelayEvent
12791
+ webex.internal.llm.registerAndConnect,
12792
+ 'a url',
12793
+ 'ps-url',
12794
+ undefined
12795
+ );
12796
+ });
12797
+ it('passes dataChannelToken to registerAndConnect', async () => {
12798
+ meeting.joinedWith = {state: 'JOINED'};
12799
+ meeting.locusInfo = {
12800
+ url: 'a url',
12801
+ info: {datachannelUrl: 'a datachannel url'},
12802
+ self: {datachannelToken: 'token-123'},
12803
+ };
12804
+
12805
+ webex.internal.llm.getDatachannelToken.returns(undefined);
12806
+
12807
+ await meeting.updateLLMConnection();
12808
+
12809
+ assert.calledWithExactly(
12810
+ webex.internal.llm.registerAndConnect,
12811
+ 'a url',
12812
+ 'a datachannel url',
12813
+ 'token-123'
12599
12814
  );
12600
12815
  assert.calledWithExactly(
12601
- meeting.webex.internal.llm.off,
12602
- 'event:locus.state_message',
12603
- meeting.processLocusLLMEvent
12816
+ webex.internal.llm.setDatachannelToken,
12817
+ 'token-123',
12818
+ 'default'
12604
12819
  );
12605
12820
  });
12821
+ it('prefers refreshed token over locus self token', async () => {
12822
+ meeting.joinedWith = {state: 'JOINED'};
12823
+ meeting.locusInfo = {
12824
+ url: 'a url',
12825
+ info: {datachannelUrl: 'a datachannel url'},
12826
+ self: {datachannelToken: 'locus-token'},
12827
+ };
12828
+
12829
+ webex.internal.llm.getDatachannelToken
12830
+ .withArgs('default')
12831
+ .returns('refreshed-token');
12832
+
12833
+ await meeting.updateLLMConnection();
12606
12834
 
12607
- it('connect ps data channel if ps started in webinar', async () => {
12835
+ assert.calledWithExactly(
12836
+ webex.internal.llm.registerAndConnect,
12837
+ 'a url',
12838
+ 'a datachannel url',
12839
+ 'refreshed-token'
12840
+ );
12841
+
12842
+ assert.notCalled(webex.internal.llm.setDatachannelToken);
12843
+ });
12844
+ it('uses practice session token when in PS even if refreshed token exists', async () => {
12608
12845
  meeting.joinedWith = {state: 'JOINED'};
12846
+
12609
12847
  meeting.locusInfo = {
12610
12848
  url: 'a url',
12611
12849
  info: {
12612
12850
  datachannelUrl: 'a datachannel url',
12613
- practiceSessionDatachannelUrl: 'a ps datachannel url',
12851
+ practiceSessionDatachannelUrl: 'ps-url',
12852
+ },
12853
+ self: {
12854
+ datachannelToken: 'locus-token',
12855
+ practiceSessionDatachannelToken: 'ps-token',
12614
12856
  },
12615
12857
  };
12616
- meeting.webinar.isJoinPracticeSessionDataChannel = sinon.stub().returns(true);
12858
+
12859
+ meeting.webinar.isJoinPracticeSessionDataChannel.returns(true);
12860
+
12861
+ webex.internal.llm.getDatachannelToken
12862
+ .withArgs(true).returns('refreshed-ps-token') // refreshed practice token
12863
+ .withArgs(false).returns('refreshed-normal-token'); // refreshed normal token
12864
+
12617
12865
  await meeting.updateLLMConnection();
12618
12866
 
12619
- assert.notCalled(webex.internal.llm.disconnectLLM);
12620
- assert.calledWith(webex.internal.llm.registerAndConnect, 'a url', 'a ps datachannel url');
12867
+ assert.calledWithExactly(
12868
+ webex.internal.llm.registerAndConnect,
12869
+ 'a url',
12870
+ 'ps-url',
12871
+ 'ps-token'
12872
+ );
12873
+ assert.calledWithExactly(
12874
+ webex.internal.llm.setDatachannelToken,
12875
+ 'ps-token',
12876
+ 'practiceSession'
12877
+ );
12878
+ });
12879
+
12880
+ it('does not pass token when data channel with jwt token is disabled', async () => {
12881
+ meeting.joinedWith = {state: 'JOINED'};
12882
+ meeting.locusInfo = {
12883
+ url: 'a url',
12884
+ info: {datachannelUrl: 'a datachannel url'},
12885
+ self: {datachannelToken: 'token-123'}
12886
+ };
12887
+
12888
+ webex.internal.llm.getDatachannelToken.returns(undefined);
12889
+ webex.internal.llm.isDataChannelTokenEnabled = sinon.stub().resolves(false);
12890
+
12891
+ await meeting.updateLLMConnection();
12892
+
12893
+ assert.calledWithExactly(
12894
+ webex.internal.llm.registerAndConnect,
12895
+ 'a url',
12896
+ 'a datachannel url',
12897
+ 'token-123'
12898
+ );
12899
+ assert.calledWithExactly(
12900
+ webex.internal.llm.setDatachannelToken,
12901
+ 'token-123',
12902
+ 'default'
12903
+ );
12621
12904
  });
12622
12905
  });
12623
12906
 
@@ -12628,6 +12911,7 @@ describe('plugin-meetings', () => {
12628
12911
 
12629
12912
  it('should read the locus object, set on the meeting and return null', () => {
12630
12913
  const dataSets = {someFakeStuff: 'dataSet'};
12914
+ const metadata = {some: 'metadata'};
12631
12915
 
12632
12916
  meeting.setLocus({
12633
12917
  mediaConnections: [test1],
@@ -12637,12 +12921,14 @@ describe('plugin-meetings', () => {
12637
12921
  mediaId: uuid3,
12638
12922
  locus: {host: {id: uuid4}},
12639
12923
  dataSets,
12924
+ metadata,
12640
12925
  });
12641
12926
  assert.calledOnce(meeting.locusInfo.initialSetup);
12642
12927
  assert.calledWith(meeting.locusInfo.initialSetup, {
12643
12928
  trigger: 'join-response',
12644
12929
  locus: {host: {id: uuid4}},
12645
12930
  dataSets,
12931
+ metadata,
12646
12932
  });
12647
12933
  assert.equal(meeting.mediaConnections, test1);
12648
12934
  assert.equal(meeting.locusUrl, url1);
@@ -14184,6 +14470,72 @@ describe('plugin-meetings', () => {
14184
14470
  assert.calledOnce(meeting.meetingRequest.keepAlive);
14185
14471
  });
14186
14472
  });
14473
+ describe('#refreshDataChannelToken()', () => {
14474
+ let meeting;
14475
+
14476
+ beforeEach(() => {
14477
+ meeting = Object.create(Meeting.prototype);
14478
+ meeting.locusUrl = 'https://locus.example.com';
14479
+ meeting.meetingRequest = {
14480
+ fetchDatachannelToken: sinon.stub().resolves({
14481
+ body: { datachannelToken: 'mock-token' },
14482
+ }),
14483
+ };
14484
+ meeting.members = {
14485
+ selfId: 'self-123',
14486
+ };
14487
+ meeting.webinar = {
14488
+ isJoinPracticeSessionDataChannel: sinon.stub().returns(true),
14489
+ };
14490
+ });
14491
+
14492
+ it('calls fetchDatachannelToken with correct parameters', async () => {
14493
+ await meeting.refreshDataChannelToken();
14494
+
14495
+ sinon.assert.calledOnce(meeting.meetingRequest.fetchDatachannelToken);
14496
+
14497
+ sinon.assert.calledWith(
14498
+ meeting.meetingRequest.fetchDatachannelToken,
14499
+ {
14500
+ locusUrl: 'https://locus.example.com',
14501
+ requestingParticipantId: 'self-123',
14502
+ isPracticeSession: true,
14503
+ }
14504
+ );
14505
+ });
14506
+
14507
+ it('returns the correct structured result', async () => {
14508
+ const result = await meeting.refreshDataChannelToken();
14509
+
14510
+ expect(result).to.deep.equal({
14511
+ body: {
14512
+ datachannelToken: 'mock-token',
14513
+ dataChannelTokenType: 'practiceSession',
14514
+ },
14515
+ });
14516
+ });
14517
+ });
14518
+ describe('#getDataChannelTokenType', () => {
14519
+ it('returns PracticeSession when webinar is in practice session mode', () => {
14520
+ meeting.webinar = {
14521
+ isJoinPracticeSessionDataChannel: sinon.stub().returns(true),
14522
+ };
14523
+
14524
+ const result = meeting.getDataChannelTokenType();
14525
+
14526
+ expect(result).to.equal('practiceSession');
14527
+ });
14528
+
14529
+ it('returns Default when not in practice session mode', () => {
14530
+ meeting.webinar = {
14531
+ isJoinPracticeSessionDataChannel: sinon.stub().returns(false),
14532
+ };
14533
+
14534
+ const result = meeting.getDataChannelTokenType();
14535
+
14536
+ expect(result).to.equal('default');
14537
+ });
14538
+ });
14187
14539
  describe('#stopKeepAlive', () => {
14188
14540
  let clock;
14189
14541
  const defaultKeepAliveUrl = 'keep.alive.url';