@webex/plugin-meetings 3.0.0-next.2 → 3.0.0-next.21
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.
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/constants.d.ts +3 -9
- package/dist/constants.js +4 -9
- package/dist/constants.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/interpretation/index.js +3 -3
- package/dist/interpretation/index.js.map +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/mediaSharesUtils.js +15 -1
- package/dist/locus-info/mediaSharesUtils.js.map +1 -1
- package/dist/media/index.js +4 -1
- package/dist/media/index.js.map +1 -1
- package/dist/mediaQualityMetrics/config.d.ts +9 -1
- package/dist/mediaQualityMetrics/config.js +10 -1
- package/dist/mediaQualityMetrics/config.js.map +1 -1
- package/dist/meeting/index.d.ts +18 -7
- package/dist/meeting/index.js +745 -567
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/muteState.d.ts +2 -8
- package/dist/meeting/muteState.js +37 -25
- package/dist/meeting/muteState.js.map +1 -1
- package/dist/meeting/request.d.ts +3 -0
- package/dist/meeting/request.js +32 -23
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/util.js +1 -0
- package/dist/meeting/util.js.map +1 -1
- package/dist/multistream/mediaRequestManager.d.ts +1 -2
- package/dist/multistream/mediaRequestManager.js.map +1 -1
- package/dist/multistream/remoteMediaGroup.d.ts +1 -1
- package/dist/multistream/remoteMediaGroup.js.map +1 -1
- package/dist/multistream/remoteMediaManager.d.ts +1 -2
- package/dist/multistream/remoteMediaManager.js.map +1 -1
- package/dist/multistream/sendSlotManager.d.ts +1 -2
- package/dist/multistream/sendSlotManager.js.map +1 -1
- package/dist/reachability/request.js +12 -10
- package/dist/reachability/request.js.map +1 -1
- package/dist/reconnection-manager/index.js +2 -1
- package/dist/reconnection-manager/index.js.map +1 -1
- package/dist/roap/index.d.ts +10 -2
- package/dist/roap/index.js +15 -0
- package/dist/roap/index.js.map +1 -1
- package/dist/roap/request.js +3 -3
- package/dist/roap/request.js.map +1 -1
- package/dist/roap/turnDiscovery.d.ts +64 -17
- package/dist/roap/turnDiscovery.js +307 -126
- package/dist/roap/turnDiscovery.js.map +1 -1
- package/dist/statsAnalyzer/index.js +61 -41
- package/dist/statsAnalyzer/index.js.map +1 -1
- package/dist/webinar/index.js +1 -1
- package/package.json +22 -22
- package/src/constants.ts +3 -9
- package/src/index.ts +1 -0
- package/src/interpretation/index.ts +2 -2
- package/src/locus-info/mediaSharesUtils.ts +16 -0
- package/src/media/index.ts +3 -1
- package/src/mediaQualityMetrics/config.ts +11 -1
- package/src/meeting/index.ts +264 -90
- package/src/meeting/muteState.ts +34 -20
- package/src/meeting/request.ts +18 -2
- package/src/meeting/util.ts +1 -0
- package/src/multistream/mediaRequestManager.ts +1 -1
- package/src/multistream/remoteMediaGroup.ts +1 -1
- package/src/multistream/remoteMediaManager.ts +1 -2
- package/src/multistream/sendSlotManager.ts +1 -2
- package/src/reachability/request.ts +15 -11
- package/src/reconnection-manager/index.ts +1 -1
- package/src/roap/index.ts +25 -3
- package/src/roap/request.ts +3 -3
- package/src/roap/turnDiscovery.ts +244 -78
- package/src/statsAnalyzer/index.ts +72 -43
- package/test/integration/spec/journey.js +14 -14
- package/test/integration/spec/space-meeting.js +1 -1
- package/test/unit/spec/interpretation/index.ts +4 -1
- package/test/unit/spec/locus-info/mediaSharesUtils.ts +9 -0
- package/test/unit/spec/media/index.ts +89 -78
- package/test/unit/spec/meeting/index.js +611 -125
- package/test/unit/spec/meeting/muteState.js +219 -67
- package/test/unit/spec/meeting/request.js +21 -0
- package/test/unit/spec/meeting/utils.js +6 -1
- package/test/unit/spec/meetings/index.js +27 -20
- package/test/unit/spec/multistream/remoteMediaGroup.ts +0 -1
- package/test/unit/spec/multistream/remoteMediaManager.ts +0 -1
- package/test/unit/spec/reachability/request.js +15 -7
- package/test/unit/spec/reconnection-manager/index.js +28 -0
- package/test/unit/spec/roap/index.ts +61 -6
- package/test/unit/spec/roap/turnDiscovery.ts +298 -16
- package/test/unit/spec/stats-analyzer/index.js +183 -8
- package/dist/member/member.types.d.ts +0 -11
- package/dist/member/member.types.js +0 -17
- package/dist/member/member.types.js.map +0 -1
- package/src/member/member.types.ts +0 -13
|
@@ -121,7 +121,7 @@ describe('TurnDiscovery', () => {
|
|
|
121
121
|
});
|
|
122
122
|
|
|
123
123
|
// checks that OK roap message was sent or not sent and that the result is as expected
|
|
124
|
-
const checkResult = async (resultPromise, expectedRoapMessageSent, expectedResult) => {
|
|
124
|
+
const checkResult = async (resultPromise, expectedRoapMessageSent, expectedResult, expectedSkipReason?: string) => {
|
|
125
125
|
let turnServerInfo, turnDiscoverySkippedReason;
|
|
126
126
|
|
|
127
127
|
if (expectedRoapMessageSent === 'OK') {
|
|
@@ -150,7 +150,7 @@ describe('TurnDiscovery', () => {
|
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
assert.deepEqual(turnServerInfo, expectedResult);
|
|
153
|
-
assert.
|
|
153
|
+
assert.equal(turnDiscoverySkippedReason, expectedSkipReason);
|
|
154
154
|
};
|
|
155
155
|
|
|
156
156
|
it('sends TURN_DISCOVERY_REQUEST, waits for response and sends OK', async () => {
|
|
@@ -302,7 +302,7 @@ describe('TurnDiscovery', () => {
|
|
|
302
302
|
// @ts-ignore
|
|
303
303
|
mockRoapRequest.sendRoap.resetHistory();
|
|
304
304
|
|
|
305
|
-
await checkResult(result, undefined, undefined);
|
|
305
|
+
await checkResult(result, undefined, undefined, 'failure: Unexpected token o in JSON at position 1');
|
|
306
306
|
checkFailureMetricsSent();
|
|
307
307
|
});
|
|
308
308
|
|
|
@@ -366,7 +366,7 @@ describe('TurnDiscovery', () => {
|
|
|
366
366
|
// @ts-ignore
|
|
367
367
|
mockRoapRequest.sendRoap.resetHistory();
|
|
368
368
|
|
|
369
|
-
await checkResult(result, undefined, undefined);
|
|
369
|
+
await checkResult(result, undefined, undefined, 'failure: TURN_DISCOVERY_RESPONSE in http response has unexpected messageType: {"seq":"0","messageType":"ERROR"}');
|
|
370
370
|
});
|
|
371
371
|
});
|
|
372
372
|
});
|
|
@@ -494,7 +494,7 @@ describe('TurnDiscovery', () => {
|
|
|
494
494
|
assert.isUndefined(turnDiscoverySkippedReason);
|
|
495
495
|
});
|
|
496
496
|
|
|
497
|
-
it('resolves with undefined if sending the request fails', async () => {
|
|
497
|
+
it('resolves with undefined turnServerInfo if sending the request fails', async () => {
|
|
498
498
|
const td = new TurnDiscovery(mockRoapRequest);
|
|
499
499
|
|
|
500
500
|
mockRoapRequest.sendRoap = sinon.fake.rejects(new Error('fake error'));
|
|
@@ -504,11 +504,11 @@ describe('TurnDiscovery', () => {
|
|
|
504
504
|
const {turnServerInfo, turnDiscoverySkippedReason} = result;
|
|
505
505
|
|
|
506
506
|
assert.isUndefined(turnServerInfo);
|
|
507
|
-
assert.
|
|
507
|
+
assert.equal(turnDiscoverySkippedReason, 'failure: fake error');
|
|
508
508
|
checkFailureMetricsSent();
|
|
509
509
|
});
|
|
510
510
|
|
|
511
|
-
it('resolves with undefined when cluster is reachable', async () => {
|
|
511
|
+
it('resolves with undefined turnServerInfo when cluster is reachable', async () => {
|
|
512
512
|
const prev = testMeeting.webex.meetings.reachability.isAnyPublicClusterReachable;
|
|
513
513
|
testMeeting.webex.meetings.reachability.isAnyPublicClusterReachable = () =>
|
|
514
514
|
Promise.resolve(true);
|
|
@@ -523,7 +523,7 @@ describe('TurnDiscovery', () => {
|
|
|
523
523
|
testMeeting.webex.meetings.reachability.isAnyPublicClusterReachable = prev;
|
|
524
524
|
});
|
|
525
525
|
|
|
526
|
-
it("resolves with undefined if we don't get a response within 10s", async () => {
|
|
526
|
+
it("resolves with undefined turnServerInfo if we don't get a response within 10s", async () => {
|
|
527
527
|
const td = new TurnDiscovery(mockRoapRequest);
|
|
528
528
|
|
|
529
529
|
const promise = td.doTurnDiscovery(testMeeting, false);
|
|
@@ -534,11 +534,11 @@ describe('TurnDiscovery', () => {
|
|
|
534
534
|
const {turnServerInfo, turnDiscoverySkippedReason} = await promise;
|
|
535
535
|
|
|
536
536
|
assert.isUndefined(turnServerInfo);
|
|
537
|
-
assert.
|
|
537
|
+
assert.equal(turnDiscoverySkippedReason, 'failure: Timed out waiting for TURN_DISCOVERY_RESPONSE');
|
|
538
538
|
checkFailureMetricsSent();
|
|
539
539
|
});
|
|
540
540
|
|
|
541
|
-
it('resolves with undefined if the response does not have all the headers we expect', async () => {
|
|
541
|
+
it('resolves with undefined turnServerInfo if the response does not have all the headers we expect', async () => {
|
|
542
542
|
const td = new TurnDiscovery(mockRoapRequest);
|
|
543
543
|
const turnDiscoveryPromise = td.doTurnDiscovery(testMeeting, false);
|
|
544
544
|
|
|
@@ -559,11 +559,11 @@ describe('TurnDiscovery', () => {
|
|
|
559
559
|
const {turnServerInfo, turnDiscoverySkippedReason} = await turnDiscoveryPromise;
|
|
560
560
|
|
|
561
561
|
assert.isUndefined(turnServerInfo);
|
|
562
|
-
assert.
|
|
562
|
+
assert.equal(turnDiscoverySkippedReason, `failure: TURN_DISCOVERY_RESPONSE from test missing some headers: ["x-cisco-turn-url=${FAKE_TURN_URL}","x-cisco-turn-username=${FAKE_TURN_USERNAME}"]`);
|
|
563
563
|
checkFailureMetricsSent();
|
|
564
564
|
});
|
|
565
565
|
|
|
566
|
-
it('resolves with undefined if the response does not have any headers', async () => {
|
|
566
|
+
it('resolves with undefined turnServerInfo if the response does not have any headers', async () => {
|
|
567
567
|
const td = new TurnDiscovery(mockRoapRequest);
|
|
568
568
|
const turnDiscoveryPromise = td.doTurnDiscovery(testMeeting, false);
|
|
569
569
|
|
|
@@ -576,11 +576,11 @@ describe('TurnDiscovery', () => {
|
|
|
576
576
|
const {turnServerInfo, turnDiscoverySkippedReason} = await turnDiscoveryPromise;
|
|
577
577
|
|
|
578
578
|
assert.isUndefined(turnServerInfo);
|
|
579
|
-
assert.
|
|
579
|
+
assert.equal(turnDiscoverySkippedReason, 'failure: TURN_DISCOVERY_RESPONSE from test missing some headers: undefined');
|
|
580
580
|
checkFailureMetricsSent();
|
|
581
581
|
});
|
|
582
582
|
|
|
583
|
-
it('resolves with undefined if the response has empty headers array', async () => {
|
|
583
|
+
it('resolves with undefined turnServerInfo if the response has empty headers array', async () => {
|
|
584
584
|
const td = new TurnDiscovery(mockRoapRequest);
|
|
585
585
|
const turnDiscoveryPromise = td.doTurnDiscovery(testMeeting, false);
|
|
586
586
|
|
|
@@ -596,7 +596,7 @@ describe('TurnDiscovery', () => {
|
|
|
596
596
|
const {turnServerInfo, turnDiscoverySkippedReason} = await turnDiscoveryPromise;
|
|
597
597
|
|
|
598
598
|
assert.isUndefined(turnServerInfo);
|
|
599
|
-
assert.
|
|
599
|
+
assert.equal(turnDiscoverySkippedReason, 'failure: TURN_DISCOVERY_RESPONSE from test missing some headers: []');
|
|
600
600
|
checkFailureMetricsSent();
|
|
601
601
|
});
|
|
602
602
|
|
|
@@ -636,7 +636,7 @@ describe('TurnDiscovery', () => {
|
|
|
636
636
|
const {turnServerInfo, turnDiscoverySkippedReason} = await turnDiscoveryPromise;
|
|
637
637
|
|
|
638
638
|
assert.isUndefined(turnServerInfo);
|
|
639
|
-
assert.
|
|
639
|
+
assert.equal(turnDiscoverySkippedReason, 'failure: fake error');
|
|
640
640
|
checkFailureMetricsSent();
|
|
641
641
|
});
|
|
642
642
|
});
|
|
@@ -676,4 +676,286 @@ describe('TurnDiscovery', () => {
|
|
|
676
676
|
assert.notCalled(mockRoapRequest.sendRoap);
|
|
677
677
|
});
|
|
678
678
|
});
|
|
679
|
+
|
|
680
|
+
describe('generateTurnDiscoveryRequestMessage', () => {
|
|
681
|
+
let td;
|
|
682
|
+
|
|
683
|
+
beforeEach(() => {
|
|
684
|
+
td = new TurnDiscovery(mockRoapRequest);
|
|
685
|
+
sinon.stub(td, 'getSkipReason').resolves(undefined);
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
it('generates TURN_DISCOVERY_REQUEST message irrespective of skip reason when called with isForced=true', async () => {
|
|
689
|
+
td.getSkipReason.resolves('reachability');
|
|
690
|
+
|
|
691
|
+
const result = await td.generateTurnDiscoveryRequestMessage(testMeeting, true);
|
|
692
|
+
|
|
693
|
+
assert.deepEqual(result, {
|
|
694
|
+
roapMessage: {
|
|
695
|
+
messageType: 'TURN_DISCOVERY_REQUEST',
|
|
696
|
+
version: '2',
|
|
697
|
+
seq: 0,
|
|
698
|
+
headers: ['includeAnswerInHttpResponse', 'noOkInTransaction'],
|
|
699
|
+
},
|
|
700
|
+
turnDiscoverySkippedReason: undefined,
|
|
701
|
+
});
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
it('takes into account skip reason when called with isForced=false', async () => {
|
|
705
|
+
td.getSkipReason.resolves('reachability');
|
|
706
|
+
|
|
707
|
+
const result = await td.generateTurnDiscoveryRequestMessage(testMeeting, false);
|
|
708
|
+
|
|
709
|
+
assert.deepEqual(result, {
|
|
710
|
+
roapMessage: undefined,
|
|
711
|
+
turnDiscoverySkippedReason: 'reachability',
|
|
712
|
+
});
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
it('generates TURN_DISCOVERY_REQUEST message if there is no skip reason when called with isForced=false', async () => {
|
|
716
|
+
const result = await td.generateTurnDiscoveryRequestMessage(testMeeting, false);
|
|
717
|
+
|
|
718
|
+
assert.deepEqual(result, {
|
|
719
|
+
roapMessage: {
|
|
720
|
+
messageType: 'TURN_DISCOVERY_REQUEST',
|
|
721
|
+
version: '2',
|
|
722
|
+
seq: 0,
|
|
723
|
+
headers: ['includeAnswerInHttpResponse', 'noOkInTransaction'],
|
|
724
|
+
},
|
|
725
|
+
turnDiscoverySkippedReason: undefined,
|
|
726
|
+
});
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
it('returns "already in progress" if TURN_DISCOVERY_REQUEST was already generated', async () => {
|
|
730
|
+
// 1st call
|
|
731
|
+
await td.generateTurnDiscoveryRequestMessage(testMeeting, true);
|
|
732
|
+
|
|
733
|
+
// 2nd call
|
|
734
|
+
const result = await td.generateTurnDiscoveryRequestMessage(testMeeting, true);
|
|
735
|
+
|
|
736
|
+
assert.deepEqual(result, {
|
|
737
|
+
roapMessage: undefined,
|
|
738
|
+
turnDiscoverySkippedReason: 'already in progress',
|
|
739
|
+
});
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
it('returns "already in progress" if doTurnDiscovery was called and not completed', async () => {
|
|
743
|
+
let promiseResolve;
|
|
744
|
+
|
|
745
|
+
// set it up so that doTurnDiscovery doesn't complete
|
|
746
|
+
mockRoapRequest.sendRoap = sinon.fake.returns(new Promise((resolve) => {
|
|
747
|
+
promiseResolve = resolve;
|
|
748
|
+
}));
|
|
749
|
+
td.doTurnDiscovery(testMeeting, false, true);
|
|
750
|
+
|
|
751
|
+
// now call generateTurnDiscoveryRequestMessage
|
|
752
|
+
const result = await td.generateTurnDiscoveryRequestMessage(testMeeting, true);
|
|
753
|
+
|
|
754
|
+
assert.deepEqual(result, {
|
|
755
|
+
roapMessage: undefined,
|
|
756
|
+
turnDiscoverySkippedReason: 'already in progress',
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
// resolve the promise, just so that we don't leave it hanging
|
|
760
|
+
promiseResolve();
|
|
761
|
+
});
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
describe('handleTurnDiscoveryHttpResponse', () => {
|
|
765
|
+
let td;
|
|
766
|
+
let roapMessage;
|
|
767
|
+
|
|
768
|
+
beforeEach(() => {
|
|
769
|
+
roapMessage = {
|
|
770
|
+
seq: 1,
|
|
771
|
+
messageType: 'TURN_DISCOVERY_RESPONSE',
|
|
772
|
+
errorType: undefined,
|
|
773
|
+
errorCause: undefined,
|
|
774
|
+
headers: [
|
|
775
|
+
`x-cisco-turn-url=${FAKE_TURN_URL}`,
|
|
776
|
+
`x-cisco-turn-username=${FAKE_TURN_USERNAME}`,
|
|
777
|
+
`x-cisco-turn-password=${FAKE_TURN_PASSWORD}`,
|
|
778
|
+
'noOkInTransaction'
|
|
779
|
+
],
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
td = new TurnDiscovery(mockRoapRequest);
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
// checks if another TURN discovery can be started without any problem
|
|
786
|
+
const checkNextTurnDiscovery = async () => {
|
|
787
|
+
// after each test check that another TURN discovery can be started without any problems
|
|
788
|
+
const secondMessage = await td.generateTurnDiscoveryRequestMessage(testMeeting, true);
|
|
789
|
+
|
|
790
|
+
assert.isDefined(secondMessage.roapMessage);
|
|
791
|
+
};
|
|
792
|
+
|
|
793
|
+
it('works as expected when called with undefined httpResponse', async () => {
|
|
794
|
+
await td.generateTurnDiscoveryRequestMessage(testMeeting, true);
|
|
795
|
+
|
|
796
|
+
const result = await td.handleTurnDiscoveryHttpResponse(testMeeting, undefined);
|
|
797
|
+
|
|
798
|
+
assert.deepEqual(result, {
|
|
799
|
+
turnServerInfo: undefined,
|
|
800
|
+
turnDiscoverySkippedReason: 'missing http response',
|
|
801
|
+
});
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
[
|
|
805
|
+
{testCase: 'is missing mediaConnections', httpResponse: {}},
|
|
806
|
+
{testCase: 'is missing mediaConnections[0]', httpResponse: {mediaConnections: []}},
|
|
807
|
+
{testCase: 'is missing mediaConnections[0].remoteSdp', httpResponse: {mediaConnections: [{}]}},
|
|
808
|
+
{testCase: 'is missing roapMesssage in mediaConnections[0].remoteSdp', httpResponse: {mediaConnections: [{remoteSdp: JSON.stringify({something: "whatever"})}]}},
|
|
809
|
+
].forEach(({testCase, httpResponse}) => {
|
|
810
|
+
it(`handles httpResponse that ${testCase}`, async () => {
|
|
811
|
+
await td.generateTurnDiscoveryRequestMessage(testMeeting, true);
|
|
812
|
+
|
|
813
|
+
const result = await td.handleTurnDiscoveryHttpResponse(testMeeting, httpResponse);
|
|
814
|
+
|
|
815
|
+
assert.deepEqual(result, {
|
|
816
|
+
turnServerInfo: undefined,
|
|
817
|
+
turnDiscoverySkippedReason: 'missing http response',
|
|
818
|
+
});
|
|
819
|
+
});
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
it('handles httpResponse with invalid JSON in mediaConnections[0].remoteSdp', async () => {
|
|
823
|
+
await td.generateTurnDiscoveryRequestMessage(testMeeting, true);
|
|
824
|
+
|
|
825
|
+
const result = await td.handleTurnDiscoveryHttpResponse(testMeeting, {mediaConnections: [{remoteSdp: 'not a json'}]});
|
|
826
|
+
|
|
827
|
+
assert.deepEqual(result, {
|
|
828
|
+
turnServerInfo: undefined,
|
|
829
|
+
turnDiscoverySkippedReason: 'failure: Unexpected token o in JSON at position 1',
|
|
830
|
+
});
|
|
831
|
+
});
|
|
832
|
+
|
|
833
|
+
it('fails when called before generateTurnDiscoveryRequestMessage() was called', async () => {
|
|
834
|
+
const httpResponse = {mediaConnections: [{remoteSdp: JSON.stringify({roapMessage})}]};
|
|
835
|
+
await assert.isRejected(td.handleTurnDiscoveryHttpResponse(testMeeting, httpResponse),
|
|
836
|
+
'handleTurnDiscoveryHttpResponse() called before generateTurnDiscoveryRequestMessage()');
|
|
837
|
+
});
|
|
838
|
+
|
|
839
|
+
it('works as expected when called with valid httpResponse', async () => {
|
|
840
|
+
const httpResponse = {mediaConnections: [{remoteSdp: JSON.stringify({roapMessage})}]};
|
|
841
|
+
|
|
842
|
+
// we spy on handleTurnDiscoveryResponse and check that it's called so that we don't have to repeat
|
|
843
|
+
// all the edge case tests here, they're already covered in other tests that call handleTurnDiscoveryResponse
|
|
844
|
+
const handleTurnDiscoveryResponseSpy = sinon.spy(td, 'handleTurnDiscoveryResponse');
|
|
845
|
+
|
|
846
|
+
await td.generateTurnDiscoveryRequestMessage(testMeeting, true);
|
|
847
|
+
const result = await td.handleTurnDiscoveryHttpResponse(testMeeting, httpResponse);
|
|
848
|
+
|
|
849
|
+
assert.deepEqual(result, {
|
|
850
|
+
turnServerInfo: {
|
|
851
|
+
url: FAKE_TURN_URL,
|
|
852
|
+
username: FAKE_TURN_USERNAME,
|
|
853
|
+
password: FAKE_TURN_PASSWORD,
|
|
854
|
+
},
|
|
855
|
+
turnDiscoverySkippedReason: undefined,
|
|
856
|
+
});
|
|
857
|
+
|
|
858
|
+
assert.calledOnceWithExactly(handleTurnDiscoveryResponseSpy, roapMessage, 'in http response');
|
|
859
|
+
});
|
|
860
|
+
|
|
861
|
+
it('works as expected when httpResponse is missing some headers', async () => {
|
|
862
|
+
roapMessage.headers = [
|
|
863
|
+
`x-cisco-turn-url=${FAKE_TURN_URL}`, // missing headers for username and password
|
|
864
|
+
];
|
|
865
|
+
|
|
866
|
+
const httpResponse = {mediaConnections: [{remoteSdp: JSON.stringify({roapMessage})}]};
|
|
867
|
+
|
|
868
|
+
// we spy on handleTurnDiscoveryResponse and check that it's called so that we don't have to repeat
|
|
869
|
+
// all the edge case tests here, they're already covered in other tests that call handleTurnDiscoveryResponse
|
|
870
|
+
// we test just this 1 edge case here to confirm that when handleTurnDiscoveryResponse rejects, we get the correct result
|
|
871
|
+
const handleTurnDiscoveryResponseSpy = sinon.spy(td, 'handleTurnDiscoveryResponse');
|
|
872
|
+
|
|
873
|
+
await td.generateTurnDiscoveryRequestMessage(testMeeting, true);
|
|
874
|
+
const result = await td.handleTurnDiscoveryHttpResponse(testMeeting, httpResponse);
|
|
875
|
+
|
|
876
|
+
assert.deepEqual(result, {
|
|
877
|
+
turnServerInfo: undefined,
|
|
878
|
+
turnDiscoverySkippedReason: 'failure: TURN_DISCOVERY_RESPONSE in http response missing some headers: ["x-cisco-turn-url=turns:fakeTurnServer.com:443?transport=tcp"]',
|
|
879
|
+
});
|
|
880
|
+
assert.calledOnceWithExactly(handleTurnDiscoveryResponseSpy, roapMessage, 'in http response');
|
|
881
|
+
|
|
882
|
+
checkNextTurnDiscovery();
|
|
883
|
+
});
|
|
884
|
+
|
|
885
|
+
it('sends OK when required', async () => {
|
|
886
|
+
roapMessage.headers = [
|
|
887
|
+
`x-cisco-turn-url=${FAKE_TURN_URL}`,
|
|
888
|
+
`x-cisco-turn-username=${FAKE_TURN_USERNAME}`,
|
|
889
|
+
`x-cisco-turn-password=${FAKE_TURN_PASSWORD}`,
|
|
890
|
+
// noOkInTransaction is missing
|
|
891
|
+
];
|
|
892
|
+
const httpResponse = {mediaConnections: [{remoteSdp: JSON.stringify({roapMessage})}]};
|
|
893
|
+
|
|
894
|
+
await td.generateTurnDiscoveryRequestMessage(testMeeting, true);
|
|
895
|
+
const result = await td.handleTurnDiscoveryHttpResponse(testMeeting, httpResponse);
|
|
896
|
+
|
|
897
|
+
assert.deepEqual(result, {
|
|
898
|
+
turnServerInfo: {
|
|
899
|
+
url: FAKE_TURN_URL,
|
|
900
|
+
username: FAKE_TURN_USERNAME,
|
|
901
|
+
password: FAKE_TURN_PASSWORD,
|
|
902
|
+
},
|
|
903
|
+
turnDiscoverySkippedReason: undefined,
|
|
904
|
+
});
|
|
905
|
+
|
|
906
|
+
// check that OK was sent along with the metric for it
|
|
907
|
+
await checkRoapMessageSent('OK', 0);
|
|
908
|
+
|
|
909
|
+
assert.calledWith(
|
|
910
|
+
Metrics.sendBehavioralMetric,
|
|
911
|
+
BEHAVIORAL_METRICS.TURN_DISCOVERY_REQUIRES_OK,
|
|
912
|
+
sinon.match({
|
|
913
|
+
correlation_id: testMeeting.correlationId,
|
|
914
|
+
locus_id: FAKE_LOCUS_ID,
|
|
915
|
+
})
|
|
916
|
+
);
|
|
917
|
+
|
|
918
|
+
checkNextTurnDiscovery();
|
|
919
|
+
});
|
|
920
|
+
|
|
921
|
+
describe('abort', () => {
|
|
922
|
+
it('allows starting a new TURN discovery', async () => {
|
|
923
|
+
let result;
|
|
924
|
+
|
|
925
|
+
// this mock is required for doTurnDiscovery() to work
|
|
926
|
+
mockRoapRequest.sendRoap = sinon.fake.resolves({
|
|
927
|
+
mediaConnections: [
|
|
928
|
+
{
|
|
929
|
+
mediaId: '464ff97f-4bda-466a-ad06-3a22184a2274',
|
|
930
|
+
remoteSdp: `{"roapMessage": {"messageType":"TURN_DISCOVERY_RESPONSE","seq":"0","headers": ["x-cisco-turn-url=${FAKE_TURN_URL}","x-cisco-turn-username=${FAKE_TURN_USERNAME}","x-cisco-turn-password=${FAKE_TURN_PASSWORD}", "noOkInTransaction"]}}`,
|
|
931
|
+
},
|
|
932
|
+
],
|
|
933
|
+
});
|
|
934
|
+
|
|
935
|
+
result = await td.generateTurnDiscoveryRequestMessage(testMeeting, true);
|
|
936
|
+
assert.isDefined(result.roapMessage);
|
|
937
|
+
|
|
938
|
+
td.abort();
|
|
939
|
+
|
|
940
|
+
result = await td.generateTurnDiscoveryRequestMessage(testMeeting, true);
|
|
941
|
+
assert.isDefined(result.roapMessage);
|
|
942
|
+
|
|
943
|
+
td.abort();
|
|
944
|
+
|
|
945
|
+
// check also that doTurnDiscovery() works after abort()
|
|
946
|
+
result = await td.doTurnDiscovery(testMeeting, false);
|
|
947
|
+
});
|
|
948
|
+
|
|
949
|
+
it('does nothing when called outside of a TURN discovery', async () => {
|
|
950
|
+
let result;
|
|
951
|
+
|
|
952
|
+
// call abort() without any other calls before it - it should do nothing
|
|
953
|
+
// there is not much we can check, so afterwards we just check that we can start a new TURN discovery
|
|
954
|
+
td.abort();
|
|
955
|
+
|
|
956
|
+
result = await td.generateTurnDiscoveryRequestMessage(testMeeting, true);
|
|
957
|
+
assert.isDefined(result.roapMessage);
|
|
958
|
+
});
|
|
959
|
+
});
|
|
960
|
+
});
|
|
679
961
|
});
|
|
@@ -776,13 +776,11 @@ describe('plugin-meetings', () => {
|
|
|
776
776
|
await progressTime();
|
|
777
777
|
|
|
778
778
|
assert.strictEqual(
|
|
779
|
-
mqeData.intervalMetadata.
|
|
780
|
-
.information,
|
|
779
|
+
mqeData.intervalMetadata.microphoneInfo.deviceName,
|
|
781
780
|
'fake-microphone'
|
|
782
781
|
);
|
|
783
782
|
assert.strictEqual(
|
|
784
|
-
mqeData.intervalMetadata.
|
|
785
|
-
.information,
|
|
783
|
+
mqeData.intervalMetadata.cameraInfo.deviceName,
|
|
786
784
|
'fake-camera'
|
|
787
785
|
);
|
|
788
786
|
});
|
|
@@ -796,13 +794,11 @@ describe('plugin-meetings', () => {
|
|
|
796
794
|
await progressTime();
|
|
797
795
|
|
|
798
796
|
assert.strictEqual(
|
|
799
|
-
mqeData.intervalMetadata.
|
|
800
|
-
.information,
|
|
797
|
+
mqeData.intervalMetadata.microphoneInfo.deviceName,
|
|
801
798
|
_UNKNOWN_
|
|
802
799
|
);
|
|
803
800
|
assert.strictEqual(
|
|
804
|
-
mqeData.intervalMetadata.
|
|
805
|
-
.information,
|
|
801
|
+
mqeData.intervalMetadata.cameraInfo.deviceName,
|
|
806
802
|
_UNKNOWN_
|
|
807
803
|
);
|
|
808
804
|
});
|
|
@@ -1543,6 +1539,185 @@ describe('plugin-meetings', () => {
|
|
|
1543
1539
|
},
|
|
1544
1540
|
]);
|
|
1545
1541
|
});
|
|
1542
|
+
|
|
1543
|
+
it('has three streams for video senders for simulcast', async () => {
|
|
1544
|
+
pc.getTransceiverStats = sinon.stub().resolves({
|
|
1545
|
+
audio: {
|
|
1546
|
+
senders: [fakeStats.audio.senders[0]],
|
|
1547
|
+
receivers: [fakeStats.audio.receivers[0]],
|
|
1548
|
+
},
|
|
1549
|
+
video: {
|
|
1550
|
+
senders: [
|
|
1551
|
+
{
|
|
1552
|
+
localTrackLabel: 'fake-camera',
|
|
1553
|
+
report: [
|
|
1554
|
+
{
|
|
1555
|
+
type: 'outbound-rtp',
|
|
1556
|
+
bytesSent: 1,
|
|
1557
|
+
framesSent: 0,
|
|
1558
|
+
packetsSent: 0,
|
|
1559
|
+
},
|
|
1560
|
+
{
|
|
1561
|
+
type: 'outbound-rtp',
|
|
1562
|
+
bytesSent: 0,
|
|
1563
|
+
framesSent: 0,
|
|
1564
|
+
packetsSent: 0,
|
|
1565
|
+
},
|
|
1566
|
+
{
|
|
1567
|
+
type: 'outbound-rtp',
|
|
1568
|
+
bytesSent: 1000,
|
|
1569
|
+
framesSent: 1,
|
|
1570
|
+
packetsSent: 1,
|
|
1571
|
+
},
|
|
1572
|
+
{
|
|
1573
|
+
type: 'remote-inbound-rtp',
|
|
1574
|
+
packetsLost: 0,
|
|
1575
|
+
},
|
|
1576
|
+
{
|
|
1577
|
+
type: 'candidate-pair',
|
|
1578
|
+
state: 'succeeded',
|
|
1579
|
+
localCandidateId: 'fake-candidate-id',
|
|
1580
|
+
},
|
|
1581
|
+
{
|
|
1582
|
+
type: 'candidate-pair',
|
|
1583
|
+
state: 'failed',
|
|
1584
|
+
localCandidateId: 'bad-candidate-id',
|
|
1585
|
+
},
|
|
1586
|
+
{
|
|
1587
|
+
type: 'local-candidate',
|
|
1588
|
+
id: 'fake-candidate-id',
|
|
1589
|
+
protocol: 'tcp',
|
|
1590
|
+
},
|
|
1591
|
+
],
|
|
1592
|
+
},
|
|
1593
|
+
],
|
|
1594
|
+
receivers: [fakeStats.video.receivers[0]],
|
|
1595
|
+
},
|
|
1596
|
+
screenShareAudio: {
|
|
1597
|
+
senders: [fakeStats.audio.senders[0]],
|
|
1598
|
+
receivers: [fakeStats.audio.receivers[0]],
|
|
1599
|
+
},
|
|
1600
|
+
screenShareVideo: {
|
|
1601
|
+
senders: [fakeStats.video.senders[0]],
|
|
1602
|
+
receivers: [fakeStats.video.receivers[0]],
|
|
1603
|
+
},
|
|
1604
|
+
});
|
|
1605
|
+
|
|
1606
|
+
await startStatsAnalyzer({expected: {receiveVideo: true}});
|
|
1607
|
+
|
|
1608
|
+
await progressTime();
|
|
1609
|
+
|
|
1610
|
+
assert.deepEqual(mqeData.videoTransmit[0].streams, [
|
|
1611
|
+
{
|
|
1612
|
+
common: {
|
|
1613
|
+
codec: 'H264',
|
|
1614
|
+
csi: [],
|
|
1615
|
+
duplicateSsci: 0,
|
|
1616
|
+
requestedBitrate: 0,
|
|
1617
|
+
requestedFrames: 0,
|
|
1618
|
+
rtpPackets: 0,
|
|
1619
|
+
ssci: 0,
|
|
1620
|
+
transmittedBitrate: 0.13333333333333333,
|
|
1621
|
+
transmittedFrameRate: 0
|
|
1622
|
+
},
|
|
1623
|
+
h264CodecProfile: 'BP',
|
|
1624
|
+
isAvatar: false,
|
|
1625
|
+
isHardwareEncoded: false,
|
|
1626
|
+
localConfigurationChanges: 2,
|
|
1627
|
+
maxFrameQp: 0,
|
|
1628
|
+
maxNoiseLevel: 0,
|
|
1629
|
+
minRegionQp: 0,
|
|
1630
|
+
remoteConfigurationChanges: 0,
|
|
1631
|
+
requestedFrameSize: 0,
|
|
1632
|
+
requestedKeyFrames: 0,
|
|
1633
|
+
transmittedFrameSize: 0,
|
|
1634
|
+
transmittedHeight: 0,
|
|
1635
|
+
transmittedKeyFrames: 0,
|
|
1636
|
+
transmittedKeyFramesClient: 0,
|
|
1637
|
+
transmittedKeyFramesConfigurationChange: 0,
|
|
1638
|
+
transmittedKeyFramesFeedback: 0,
|
|
1639
|
+
transmittedKeyFramesLocalDrop: 0,
|
|
1640
|
+
transmittedKeyFramesOtherLayer: 0,
|
|
1641
|
+
transmittedKeyFramesPeriodic: 0,
|
|
1642
|
+
transmittedKeyFramesSceneChange: 0,
|
|
1643
|
+
transmittedKeyFramesStartup: 0,
|
|
1644
|
+
transmittedKeyFramesUnknown: 0,
|
|
1645
|
+
transmittedWidth: 0,
|
|
1646
|
+
},
|
|
1647
|
+
{
|
|
1648
|
+
common: {
|
|
1649
|
+
codec: 'H264',
|
|
1650
|
+
csi: [],
|
|
1651
|
+
duplicateSsci: 0,
|
|
1652
|
+
requestedBitrate: 0,
|
|
1653
|
+
requestedFrames: 0,
|
|
1654
|
+
rtpPackets: 0,
|
|
1655
|
+
ssci: 0,
|
|
1656
|
+
transmittedBitrate: 0,
|
|
1657
|
+
transmittedFrameRate: 0,
|
|
1658
|
+
},
|
|
1659
|
+
h264CodecProfile: 'BP',
|
|
1660
|
+
isAvatar: false,
|
|
1661
|
+
isHardwareEncoded: false,
|
|
1662
|
+
localConfigurationChanges: 2,
|
|
1663
|
+
maxFrameQp: 0,
|
|
1664
|
+
maxNoiseLevel: 0,
|
|
1665
|
+
minRegionQp: 0,
|
|
1666
|
+
remoteConfigurationChanges: 0,
|
|
1667
|
+
requestedFrameSize: 0,
|
|
1668
|
+
requestedKeyFrames: 0,
|
|
1669
|
+
transmittedFrameSize: 0,
|
|
1670
|
+
transmittedHeight: 0,
|
|
1671
|
+
transmittedKeyFrames: 0,
|
|
1672
|
+
transmittedKeyFramesClient: 0,
|
|
1673
|
+
transmittedKeyFramesConfigurationChange: 0,
|
|
1674
|
+
transmittedKeyFramesFeedback: 0,
|
|
1675
|
+
transmittedKeyFramesLocalDrop: 0,
|
|
1676
|
+
transmittedKeyFramesOtherLayer: 0,
|
|
1677
|
+
transmittedKeyFramesPeriodic: 0,
|
|
1678
|
+
transmittedKeyFramesSceneChange: 0,
|
|
1679
|
+
transmittedKeyFramesStartup: 0,
|
|
1680
|
+
transmittedKeyFramesUnknown: 0,
|
|
1681
|
+
transmittedWidth: 0,
|
|
1682
|
+
},
|
|
1683
|
+
{
|
|
1684
|
+
common: {
|
|
1685
|
+
codec: 'H264',
|
|
1686
|
+
csi: [],
|
|
1687
|
+
duplicateSsci: 0,
|
|
1688
|
+
requestedBitrate: 0,
|
|
1689
|
+
requestedFrames: 0,
|
|
1690
|
+
rtpPackets: 1,
|
|
1691
|
+
ssci: 0,
|
|
1692
|
+
transmittedBitrate: 133.33333333333334,
|
|
1693
|
+
transmittedFrameRate: 0,
|
|
1694
|
+
},
|
|
1695
|
+
h264CodecProfile: 'BP',
|
|
1696
|
+
isAvatar: false,
|
|
1697
|
+
isHardwareEncoded: false,
|
|
1698
|
+
localConfigurationChanges: 2,
|
|
1699
|
+
maxFrameQp: 0,
|
|
1700
|
+
maxNoiseLevel: 0,
|
|
1701
|
+
minRegionQp: 0,
|
|
1702
|
+
remoteConfigurationChanges: 0,
|
|
1703
|
+
requestedFrameSize: 0,
|
|
1704
|
+
requestedKeyFrames: 0,
|
|
1705
|
+
transmittedFrameSize: 0,
|
|
1706
|
+
transmittedHeight: 0,
|
|
1707
|
+
transmittedKeyFrames: 0,
|
|
1708
|
+
transmittedKeyFramesClient: 0,
|
|
1709
|
+
transmittedKeyFramesConfigurationChange: 0,
|
|
1710
|
+
transmittedKeyFramesFeedback: 0,
|
|
1711
|
+
transmittedKeyFramesLocalDrop: 0,
|
|
1712
|
+
transmittedKeyFramesOtherLayer: 0,
|
|
1713
|
+
transmittedKeyFramesPeriodic: 0,
|
|
1714
|
+
transmittedKeyFramesSceneChange: 0,
|
|
1715
|
+
transmittedKeyFramesStartup: 0,
|
|
1716
|
+
transmittedKeyFramesUnknown: 0,
|
|
1717
|
+
transmittedWidth: 0,
|
|
1718
|
+
}
|
|
1719
|
+
]);
|
|
1720
|
+
});
|
|
1546
1721
|
});
|
|
1547
1722
|
});
|
|
1548
1723
|
});
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
var _Object$defineProperty = require("@babel/runtime-corejs2/core-js/object/define-property");
|
|
4
|
-
_Object$defineProperty(exports, "__esModule", {
|
|
5
|
-
value: true
|
|
6
|
-
});
|
|
7
|
-
exports.MediaStatus = void 0;
|
|
8
|
-
// values are inherited from locus so don't update these
|
|
9
|
-
var MediaStatus = exports.MediaStatus = /*#__PURE__*/function (MediaStatus) {
|
|
10
|
-
MediaStatus["RECVONLY"] = "RECVONLY";
|
|
11
|
-
MediaStatus["SENDONLY"] = "SENDONLY";
|
|
12
|
-
MediaStatus["SENDRECV"] = "SENDRECV";
|
|
13
|
-
MediaStatus["INACTIVE"] = "INACTIVE";
|
|
14
|
-
MediaStatus["UNKNOWN"] = "UNKNOWN";
|
|
15
|
-
return MediaStatus;
|
|
16
|
-
}({}); // participant has not added media in the meeting
|
|
17
|
-
//# sourceMappingURL=member.types.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"names":["MediaStatus","exports"],"sources":["member.types.ts"],"sourcesContent":["// values are inherited from locus so don't update these\nexport enum MediaStatus {\n RECVONLY = 'RECVONLY', // participant only receiving and not sending\n SENDONLY = 'SENDONLY', // participant only sending and not receiving\n SENDRECV = 'SENDRECV', // participant both sending and receiving\n INACTIVE = 'INACTIVE', // participant is not connected to media source\n UNKNOWN = 'UNKNOWN', // participant has not added media in the meeting\n}\n\nexport interface IMediaStatus {\n audio: MediaStatus;\n video: MediaStatus;\n}\n"],"mappings":";;;;;;;AAAA;AAAA,IACYA,WAAW,GAAAC,OAAA,CAAAD,WAAA,0BAAXA,WAAW;EAAXA,WAAW;EAAXA,WAAW;EAAXA,WAAW;EAAXA,WAAW;EAAXA,WAAW;EAAA,OAAXA,WAAW;AAAA,OAKA"}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
// values are inherited from locus so don't update these
|
|
2
|
-
export enum MediaStatus {
|
|
3
|
-
RECVONLY = 'RECVONLY', // participant only receiving and not sending
|
|
4
|
-
SENDONLY = 'SENDONLY', // participant only sending and not receiving
|
|
5
|
-
SENDRECV = 'SENDRECV', // participant both sending and receiving
|
|
6
|
-
INACTIVE = 'INACTIVE', // participant is not connected to media source
|
|
7
|
-
UNKNOWN = 'UNKNOWN', // participant has not added media in the meeting
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface IMediaStatus {
|
|
11
|
-
audio: MediaStatus;
|
|
12
|
-
video: MediaStatus;
|
|
13
|
-
}
|