@webex/plugin-meetings 3.11.0-next.29 → 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 (35) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/index.js +2 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/interceptors/constant.js +12 -0
  6. package/dist/interceptors/constant.js.map +1 -0
  7. package/dist/interceptors/dataChannelAuthToken.js +233 -0
  8. package/dist/interceptors/dataChannelAuthToken.js.map +1 -0
  9. package/dist/interceptors/index.js +7 -0
  10. package/dist/interceptors/index.js.map +1 -1
  11. package/dist/interpretation/index.js +1 -1
  12. package/dist/interpretation/siLanguage.js +1 -1
  13. package/dist/meeting/index.js +109 -26
  14. package/dist/meeting/index.js.map +1 -1
  15. package/dist/meeting/request.js +50 -0
  16. package/dist/meeting/request.js.map +1 -1
  17. package/dist/meeting/request.type.js.map +1 -1
  18. package/dist/types/interceptors/constant.d.ts +5 -0
  19. package/dist/types/interceptors/dataChannelAuthToken.d.ts +35 -0
  20. package/dist/types/interceptors/index.d.ts +2 -1
  21. package/dist/types/meeting/index.d.ts +19 -0
  22. package/dist/types/meeting/request.d.ts +16 -1
  23. package/dist/types/meeting/request.type.d.ts +5 -0
  24. package/dist/webinar/index.js +1 -1
  25. package/package.json +3 -3
  26. package/src/index.ts +6 -1
  27. package/src/interceptors/constant.ts +6 -0
  28. package/src/interceptors/dataChannelAuthToken.ts +142 -0
  29. package/src/interceptors/index.ts +2 -1
  30. package/src/meeting/index.ts +73 -3
  31. package/src/meeting/request.ts +42 -0
  32. package/src/meeting/request.type.ts +6 -0
  33. package/test/unit/spec/interceptors/dataChannelAuthToken.ts +141 -0
  34. package/test/unit/spec/meeting/index.js +250 -59
  35. package/test/unit/spec/meeting/request.js +64 -0
@@ -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
  {},
@@ -12579,16 +12579,20 @@ describe('plugin-meetings', () => {
12579
12579
  webex.internal.llm.isConnected = sinon.stub().returns(false);
12580
12580
  webex.internal.llm.getLocusUrl = sinon.stub();
12581
12581
  webex.internal.llm.getDatachannelUrl = sinon.stub();
12582
- webex.internal.llm.registerAndConnect = sinon
12583
- .stub()
12584
- .returns(Promise.resolve('something'));
12585
- webex.internal.llm.disconnectLLM = sinon.stub().returns(Promise.resolve());
12586
- meeting.webex.internal.llm.on = sinon.stub();
12587
- 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
+
12588
12589
  meeting.processRelayEvent = sinon.stub();
12590
+ meeting.processLocusLLMEvent = sinon.stub();
12591
+ meeting.clearLLMHealthCheckTimer = sinon.stub();
12592
+ meeting.startLLMHealthCheckTimer = sinon.stub();
12593
+
12589
12594
  meeting.webinar.isJoinPracticeSessionDataChannel = sinon.stub().returns(false);
12590
12595
  });
12591
-
12592
12596
  it('does not connect if the call is not joined yet', async () => {
12593
12597
  meeting.joinedWith = {state: 'any other state'};
12594
12598
  webex.internal.llm.getLocusUrl.returns('a url');
@@ -12602,31 +12606,21 @@ describe('plugin-meetings', () => {
12602
12606
  assert.equal(result, undefined);
12603
12607
  assert.notCalled(meeting.webex.internal.llm.on);
12604
12608
  });
12605
-
12606
12609
  it('returns undefined if llm is already connected and the locus url is unchanged', async () => {
12607
12610
  meeting.joinedWith = {state: 'JOINED'};
12608
- webex.internal.llm.isConnected.returns(true);
12609
- webex.internal.llm.getLocusUrl.returns('a url');
12610
- webex.internal.llm.getDatachannelUrl.returns('a datachannel url');
12611
-
12612
- meeting.locusInfo = {url: 'a url', info: {datachannelUrl: 'a datachannel url'}};
12613
-
12614
- const result = await meeting.updateLLMConnection();
12615
-
12616
- assert.notCalled(webex.internal.llm.registerAndConnect);
12617
- assert.notCalled(webex.internal.llm.disconnectLLM);
12618
- assert.equal(result, undefined);
12619
- assert.notCalled(meeting.webex.internal.llm.on);
12620
- });
12621
-
12622
- it('connects if not already connected', async () => {
12623
- meeting.joinedWith = {state: 'JOINED'};
12624
- meeting.locusInfo = {url: 'a url', info: {datachannelUrl: 'a datachannel url'}};
12611
+ meeting.locusInfo = {
12612
+ url: 'a url',
12613
+ info: {datachannelUrl: 'a datachannel url'}
12614
+ };
12625
12615
 
12626
12616
  const result = await meeting.updateLLMConnection();
12627
-
12628
12617
  assert.notCalled(webex.internal.llm.disconnectLLM);
12629
- 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
+ );
12630
12624
  assert.equal(result, 'something');
12631
12625
  assert.calledWithExactly(
12632
12626
  meeting.webex.internal.llm.off,
@@ -12649,27 +12643,49 @@ describe('plugin-meetings', () => {
12649
12643
  meeting.processLocusLLMEvent
12650
12644
  );
12651
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'}};
12652
12649
 
12653
- 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 () => {
12654
12662
  meeting.joinedWith = {state: 'JOINED'};
12663
+
12655
12664
  webex.internal.llm.isConnected.returns(true);
12656
12665
  webex.internal.llm.getLocusUrl.returns('a url');
12657
- webex.internal.llm.getDatachannelUrl.returns('a datachannel url');
12658
12666
 
12659
- 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
+ };
12660
12672
 
12661
12673
  const result = await meeting.updateLLMConnection();
12662
12674
 
12663
- assert.calledWith(webex.internal.llm.disconnectLLM, {
12664
- code: 3050,
12665
- reason: 'done (permanent)',
12666
- });
12667
- assert.calledWith(
12675
+ assert.calledWithExactly(
12676
+ webex.internal.llm.disconnectLLM,
12677
+ {code: 3050, reason: 'done (permanent)'}
12678
+ );
12679
+
12680
+ assert.calledWithExactly(
12668
12681
  webex.internal.llm.registerAndConnect,
12669
12682
  'a different url',
12670
- 'a datachannel url'
12683
+ 'a datachannel url',
12684
+ undefined
12671
12685
  );
12686
+
12672
12687
  assert.equal(result, 'something');
12688
+
12673
12689
  assert.calledWithExactly(
12674
12690
  meeting.webex.internal.llm.off,
12675
12691
  'event:relay.event',
@@ -12681,6 +12697,7 @@ describe('plugin-meetings', () => {
12681
12697
  meeting.processLocusLLMEvent
12682
12698
  );
12683
12699
  assert.callCount(meeting.webex.internal.llm.off, 4);
12700
+
12684
12701
  assert.calledWithExactly(
12685
12702
  meeting.webex.internal.llm.on,
12686
12703
  'event:relay.event',
@@ -12692,27 +12709,33 @@ describe('plugin-meetings', () => {
12692
12709
  meeting.processLocusLLMEvent
12693
12710
  );
12694
12711
  });
12695
-
12696
- it('disconnects it first if the data channel url has changed', async () => {
12712
+ it('disconnects if the data channel url has changed', async () => {
12697
12713
  meeting.joinedWith = {state: 'JOINED'};
12698
12714
  webex.internal.llm.isConnected.returns(true);
12699
12715
  webex.internal.llm.getLocusUrl.returns('a url');
12700
- webex.internal.llm.getDatachannelUrl.returns('a datachannel url');
12701
12716
 
12702
- 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
+ };
12703
12722
 
12704
12723
  const result = await meeting.updateLLMConnection();
12705
12724
 
12706
- assert.calledWith(webex.internal.llm.disconnectLLM, {
12707
- code: 3050,
12708
- reason: 'done (permanent)',
12709
- });
12710
- assert.calledWith(
12725
+ assert.calledWithExactly(
12726
+ webex.internal.llm.disconnectLLM,
12727
+ {code: 3050, reason: 'done (permanent)'}
12728
+ );
12729
+
12730
+ assert.calledWithExactly(
12711
12731
  webex.internal.llm.registerAndConnect,
12712
12732
  'a url',
12713
- 'a different datachannel url'
12733
+ 'a different datachannel url',
12734
+ undefined
12714
12735
  );
12736
+
12715
12737
  assert.equal(result, 'something');
12738
+
12716
12739
  assert.calledWithExactly(
12717
12740
  meeting.webex.internal.llm.off,
12718
12741
  'event:relay.event',
@@ -12723,6 +12746,7 @@ describe('plugin-meetings', () => {
12723
12746
  'event:locus.state_message',
12724
12747
  meeting.processLocusLLMEvent
12725
12748
  );
12749
+
12726
12750
  assert.calledWithExactly(
12727
12751
  meeting.webex.internal.llm.on,
12728
12752
  'event:relay.event',
@@ -12734,7 +12758,6 @@ describe('plugin-meetings', () => {
12734
12758
  meeting.processLocusLLMEvent
12735
12759
  );
12736
12760
  });
12737
-
12738
12761
  it('disconnects when the state is not JOINED', async () => {
12739
12762
  meeting.joinedWith = {state: 'any other state'};
12740
12763
  webex.internal.llm.isConnected.returns(true);
@@ -12750,32 +12773,134 @@ describe('plugin-meetings', () => {
12750
12773
  });
12751
12774
  assert.notCalled(webex.internal.llm.registerAndConnect);
12752
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
+
12753
12790
  assert.calledWithExactly(
12754
- meeting.webex.internal.llm.off,
12755
- 'event:relay.event',
12756
- meeting.processRelayEvent
12791
+ webex.internal.llm.registerAndConnect,
12792
+ 'a url',
12793
+ 'ps-url',
12794
+ undefined
12757
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
+
12758
12809
  assert.calledWithExactly(
12759
- meeting.webex.internal.llm.off,
12760
- 'event:locus.state_message',
12761
- meeting.processLocusLLMEvent
12810
+ webex.internal.llm.registerAndConnect,
12811
+ 'a url',
12812
+ 'a datachannel url',
12813
+ 'token-123'
12814
+ );
12815
+ assert.calledWithExactly(
12816
+ webex.internal.llm.setDatachannelToken,
12817
+ 'token-123',
12818
+ 'default'
12762
12819
  );
12763
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();
12764
12834
 
12765
- 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 () => {
12766
12845
  meeting.joinedWith = {state: 'JOINED'};
12846
+
12767
12847
  meeting.locusInfo = {
12768
12848
  url: 'a url',
12769
12849
  info: {
12770
12850
  datachannelUrl: 'a datachannel url',
12771
- practiceSessionDatachannelUrl: 'a ps datachannel url',
12851
+ practiceSessionDatachannelUrl: 'ps-url',
12852
+ },
12853
+ self: {
12854
+ datachannelToken: 'locus-token',
12855
+ practiceSessionDatachannelToken: 'ps-token',
12772
12856
  },
12773
12857
  };
12774
- 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
+
12775
12865
  await meeting.updateLLMConnection();
12776
12866
 
12777
- assert.notCalled(webex.internal.llm.disconnectLLM);
12778
- 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
+ );
12779
12904
  });
12780
12905
  });
12781
12906
 
@@ -14345,6 +14470,72 @@ describe('plugin-meetings', () => {
14345
14470
  assert.calledOnce(meeting.meetingRequest.keepAlive);
14346
14471
  });
14347
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
+ });
14348
14539
  describe('#stopKeepAlive', () => {
14349
14540
  let clock;
14350
14541
  const defaultKeepAliveUrl = 'keep.alive.url';
@@ -115,6 +115,7 @@ describe('plugin-meetings', () => {
115
115
  });
116
116
 
117
117
  describe('#changeVideoLayout', () => {
118
+
118
119
  const locusUrl = 'locusURL';
119
120
  const deviceUrl = 'deviceUrl';
120
121
  const layoutType = 'Equal';
@@ -918,4 +919,67 @@ describe('plugin-meetings', () => {
918
919
  });
919
920
  });
920
921
  });
922
+
923
+ describe('#fetchDatachannelToken', () => {
924
+ const locusUrl = 'https://locus.example.com/locus/api/v1/loci/123';
925
+ const participantId = 'participant-123';
926
+
927
+ it('sends GET request to regular datachannel token endpoint', async () => {
928
+ await meetingsRequest.fetchDatachannelToken({
929
+ locusUrl,
930
+ requestingParticipantId: participantId,
931
+ isPracticeSession: false,
932
+ });
933
+
934
+ assert.calledOnceWithExactly(locusDeltaRequestSpy, {
935
+ method: 'GET',
936
+ uri: `${locusUrl}/participant/${participantId}/datachannel/token`,
937
+ });
938
+ });
939
+
940
+ it('sends GET request to practice session datachannel token endpoint', async () => {
941
+ await meetingsRequest.fetchDatachannelToken({
942
+ locusUrl,
943
+ requestingParticipantId: participantId,
944
+ isPracticeSession: true,
945
+ });
946
+
947
+ assert.calledOnceWithExactly(locusDeltaRequestSpy, {
948
+ method: 'GET',
949
+ uri: `${locusUrl}/participant/${participantId}/practiceSession/datachannel/token`,
950
+ });
951
+ });
952
+
953
+ it('throws if locusUrl or participantId is missing', async () => {
954
+ await assert.isRejected(
955
+ meetingsRequest.fetchDatachannelToken({
956
+ locusUrl: null,
957
+ requestingParticipantId: participantId,
958
+ }),
959
+ /locusUrl and participantId are required/
960
+ );
961
+
962
+ await assert.isRejected(
963
+ meetingsRequest.fetchDatachannelToken({
964
+ locusUrl,
965
+ requestingParticipantId: null,
966
+ }),
967
+ /locusUrl and participantId are required/
968
+ );
969
+ });
970
+
971
+ it('logs and rethrows error when locusDeltaRequest fails', async () => {
972
+ const error = new Error('network error');
973
+ locusDeltaRequestSpy.restore();
974
+ sinon.stub(meetingsRequest, 'locusDeltaRequest').rejects(error);
975
+
976
+ await assert.isRejected(
977
+ meetingsRequest.fetchDatachannelToken({
978
+ locusUrl,
979
+ requestingParticipantId: participantId,
980
+ }),
981
+ /network error/
982
+ );
983
+ });
984
+ });
921
985
  });