@webex/plugin-meetings 3.0.0 → 3.1.0

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 (138) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/config.d.ts +1 -0
  4. package/dist/config.js +2 -1
  5. package/dist/config.js.map +1 -1
  6. package/dist/constants.d.ts +5 -4
  7. package/dist/constants.js +8 -4
  8. package/dist/constants.js.map +1 -1
  9. package/dist/index.d.ts +1 -1
  10. package/dist/index.js +6 -0
  11. package/dist/index.js.map +1 -1
  12. package/dist/interpretation/index.js +16 -2
  13. package/dist/interpretation/index.js.map +1 -1
  14. package/dist/interpretation/siLanguage.js +1 -1
  15. package/dist/locus-info/mediaSharesUtils.js +15 -1
  16. package/dist/locus-info/mediaSharesUtils.js.map +1 -1
  17. package/dist/locus-info/selfUtils.js +5 -0
  18. package/dist/locus-info/selfUtils.js.map +1 -1
  19. package/dist/media/MediaConnectionAwaiter.d.ts +61 -0
  20. package/dist/media/MediaConnectionAwaiter.js +163 -0
  21. package/dist/media/MediaConnectionAwaiter.js.map +1 -0
  22. package/dist/media/index.js +4 -1
  23. package/dist/media/index.js.map +1 -1
  24. package/dist/media/properties.js +4 -24
  25. package/dist/media/properties.js.map +1 -1
  26. package/dist/meeting/index.d.ts +26 -7
  27. package/dist/meeting/index.js +893 -677
  28. package/dist/meeting/index.js.map +1 -1
  29. package/dist/meeting/muteState.d.ts +2 -8
  30. package/dist/meeting/muteState.js +37 -25
  31. package/dist/meeting/muteState.js.map +1 -1
  32. package/dist/meeting/request.d.ts +3 -0
  33. package/dist/meeting/request.js +32 -23
  34. package/dist/meeting/request.js.map +1 -1
  35. package/dist/meeting/util.js +1 -0
  36. package/dist/meeting/util.js.map +1 -1
  37. package/dist/meeting-info/utilv2.js +4 -1
  38. package/dist/meeting-info/utilv2.js.map +1 -1
  39. package/dist/meetings/index.d.ts +8 -0
  40. package/dist/meetings/index.js +20 -0
  41. package/dist/meetings/index.js.map +1 -1
  42. package/dist/multistream/mediaRequestManager.d.ts +2 -1
  43. package/dist/multistream/mediaRequestManager.js +1 -1
  44. package/dist/multistream/mediaRequestManager.js.map +1 -1
  45. package/dist/multistream/remoteMediaGroup.d.ts +2 -0
  46. package/dist/multistream/remoteMediaGroup.js +16 -2
  47. package/dist/multistream/remoteMediaGroup.js.map +1 -1
  48. package/dist/multistream/remoteMediaManager.d.ts +15 -0
  49. package/dist/multistream/remoteMediaManager.js +179 -65
  50. package/dist/multistream/remoteMediaManager.js.map +1 -1
  51. package/dist/multistream/sendSlotManager.d.ts +9 -1
  52. package/dist/multistream/sendSlotManager.js +22 -0
  53. package/dist/multistream/sendSlotManager.js.map +1 -1
  54. package/dist/reachability/clusterReachability.d.ts +1 -0
  55. package/dist/reachability/clusterReachability.js +29 -15
  56. package/dist/reachability/clusterReachability.js.map +1 -1
  57. package/dist/reachability/index.d.ts +4 -0
  58. package/dist/reachability/index.js +18 -2
  59. package/dist/reachability/index.js.map +1 -1
  60. package/dist/reachability/request.js +12 -10
  61. package/dist/reachability/request.js.map +1 -1
  62. package/dist/reachability/util.d.ts +7 -0
  63. package/dist/reachability/util.js +19 -0
  64. package/dist/reachability/util.js.map +1 -1
  65. package/dist/reconnection-manager/index.js +2 -1
  66. package/dist/reconnection-manager/index.js.map +1 -1
  67. package/dist/roap/index.d.ts +10 -2
  68. package/dist/roap/index.js +15 -0
  69. package/dist/roap/index.js.map +1 -1
  70. package/dist/roap/request.js +3 -3
  71. package/dist/roap/request.js.map +1 -1
  72. package/dist/roap/turnDiscovery.d.ts +64 -17
  73. package/dist/roap/turnDiscovery.js +307 -126
  74. package/dist/roap/turnDiscovery.js.map +1 -1
  75. package/dist/statsAnalyzer/index.js +53 -30
  76. package/dist/statsAnalyzer/index.js.map +1 -1
  77. package/dist/webinar/index.js +1 -1
  78. package/package.json +22 -22
  79. package/src/config.ts +1 -0
  80. package/src/constants.ts +7 -3
  81. package/src/index.ts +1 -0
  82. package/src/interpretation/index.ts +18 -1
  83. package/src/locus-info/mediaSharesUtils.ts +16 -0
  84. package/src/locus-info/selfUtils.ts +5 -0
  85. package/src/media/MediaConnectionAwaiter.ts +174 -0
  86. package/src/media/index.ts +3 -1
  87. package/src/media/properties.ts +6 -31
  88. package/src/meeting/index.ts +321 -106
  89. package/src/meeting/muteState.ts +34 -20
  90. package/src/meeting/request.ts +18 -2
  91. package/src/meeting/util.ts +1 -0
  92. package/src/meeting-info/utilv2.ts +2 -1
  93. package/src/meetings/index.ts +18 -0
  94. package/src/multistream/mediaRequestManager.ts +4 -1
  95. package/src/multistream/remoteMediaGroup.ts +19 -0
  96. package/src/multistream/remoteMediaManager.ts +101 -16
  97. package/src/multistream/sendSlotManager.ts +28 -0
  98. package/src/reachability/clusterReachability.ts +20 -5
  99. package/src/reachability/index.ts +24 -1
  100. package/src/reachability/request.ts +15 -11
  101. package/src/reachability/util.ts +21 -0
  102. package/src/reconnection-manager/index.ts +1 -1
  103. package/src/roap/index.ts +25 -3
  104. package/src/roap/request.ts +3 -3
  105. package/src/roap/turnDiscovery.ts +244 -78
  106. package/src/statsAnalyzer/index.ts +63 -27
  107. package/test/integration/spec/journey.js +14 -14
  108. package/test/integration/spec/space-meeting.js +1 -1
  109. package/test/unit/spec/interpretation/index.ts +39 -3
  110. package/test/unit/spec/locus-info/index.js +28 -19
  111. package/test/unit/spec/locus-info/mediaSharesUtils.ts +9 -0
  112. package/test/unit/spec/locus-info/selfUtils.js +42 -12
  113. package/test/unit/spec/media/MediaConnectionAwaiter.ts +344 -0
  114. package/test/unit/spec/media/index.ts +89 -78
  115. package/test/unit/spec/media/properties.ts +16 -70
  116. package/test/unit/spec/meeting/index.js +638 -139
  117. package/test/unit/spec/meeting/muteState.js +219 -67
  118. package/test/unit/spec/meeting/request.js +21 -0
  119. package/test/unit/spec/meeting/utils.js +6 -1
  120. package/test/unit/spec/meeting-info/utilv2.js +6 -0
  121. package/test/unit/spec/meetings/index.js +40 -20
  122. package/test/unit/spec/multistream/mediaRequestManager.ts +20 -2
  123. package/test/unit/spec/multistream/remoteMediaGroup.ts +79 -1
  124. package/test/unit/spec/multistream/remoteMediaManager.ts +199 -1
  125. package/test/unit/spec/multistream/sendSlotManager.ts +50 -18
  126. package/test/unit/spec/reachability/clusterReachability.ts +86 -22
  127. package/test/unit/spec/reachability/index.ts +197 -60
  128. package/test/unit/spec/reachability/request.js +15 -7
  129. package/test/unit/spec/reachability/util.ts +32 -2
  130. package/test/unit/spec/reconnection-manager/index.js +28 -0
  131. package/test/unit/spec/roap/index.ts +61 -6
  132. package/test/unit/spec/roap/turnDiscovery.ts +298 -16
  133. package/test/unit/spec/stats-analyzer/index.js +179 -0
  134. package/dist/member/member.types.d.ts +0 -11
  135. package/dist/member/member.types.js +0 -17
  136. package/dist/member/member.types.js.map +0 -1
  137. package/src/member/member.types.ts +0 -13
  138. /package/test/unit/spec/locus-info/{lib/selfConstant.js → selfConstant.js} +0 -0
@@ -1,6 +1,9 @@
1
1
  import {assert} from '@webex/test-helper-chai';
2
2
 
3
- import {convertStunUrlToTurn} from '@webex/plugin-meetings/src/reachability/util';
3
+ import {
4
+ convertStunUrlToTurn,
5
+ convertStunUrlToTurnTls,
6
+ } from '@webex/plugin-meetings/src/reachability/util';
4
7
 
5
8
  describe('plugin-meetings/src/reachability/util', () => {
6
9
  describe('#convertStunUrlToTurn()', () => {
@@ -34,7 +37,34 @@ describe('plugin-meetings/src/reachability/util', () => {
34
37
  });
35
38
 
36
39
  it('show fail if stunUrl is not a STUN url', () => {
37
- assert.throws(() => convertStunUrlToTurn('http://webex.com', 'tcp'), 'Not a STUN URL: http://webex.com');
40
+ assert.throws(
41
+ () => convertStunUrlToTurn('http://webex.com', 'tcp'),
42
+ 'Not a STUN URL: http://webex.com'
43
+ );
44
+ });
45
+ });
46
+
47
+ describe('#convertStunUrlToTurnTls()', () => {
48
+ it(`should convert to a turns url`, () => {
49
+ const turnsUrl = convertStunUrlToTurnTls(
50
+ 'stun:external-media91.public.wjfkm-a-10.prod.infra.webex.com:443'
51
+ );
52
+
53
+ assert.equal(
54
+ turnsUrl,
55
+ 'turns:external-media91.public.wjfkm-a-10.prod.infra.webex.com:443?transport=tcp'
56
+ );
57
+ });
58
+
59
+ it('show fail if stunUrl is not a valid url', () => {
60
+ assert.throws(() => convertStunUrlToTurn('not a url', 'tcp'), 'Invalid URL: not a url');
61
+ });
62
+
63
+ it('show fail if stunUrl is not a STUN url', () => {
64
+ assert.throws(
65
+ () => convertStunUrlToTurn('http://webex.com', 'tcp'),
66
+ 'Not a STUN URL: http://webex.com'
67
+ );
38
68
  });
39
69
  });
40
70
  });
@@ -144,6 +144,34 @@ describe('plugin-meetings', () => {
144
144
  });
145
145
  });
146
146
 
147
+ // this can happen when we land on a video mesh node
148
+ it('does not use TURN server if TURN url is an empty string', async () => {
149
+ const rm = new ReconnectionManager(fakeMeeting);
150
+
151
+ fakeMeeting.roap.doTurnDiscovery.resolves({
152
+ turnServerInfo: {
153
+ url: '',
154
+ username: 'whatever',
155
+ password: 'whatever',
156
+ },
157
+ turnDiscoverySkippedReason: undefined,
158
+ });
159
+
160
+ await rm.reconnect();
161
+
162
+ assert.calledOnce(fakeMeeting.roap.doTurnDiscovery);
163
+ assert.calledWith(fakeMeeting.roap.doTurnDiscovery, fakeMeeting, true, true);
164
+ assert.calledOnce(fakeMediaConnection.reconnect);
165
+ assert.calledWith(fakeMediaConnection.reconnect, []);
166
+
167
+ assert.calledWith(fakeMeeting.webex.internal.newMetrics.submitClientEvent, {
168
+ name: 'client.media.reconnecting',
169
+ options: {
170
+ meetingId: rm.meeting.id,
171
+ },
172
+ });
173
+ });
174
+
147
175
  it('does not clear previous requests and re-request media for non-multistream meetings', async () => {
148
176
  fakeMeeting.isMultistream = false;
149
177
  const rm = new ReconnectionManager(fakeMeeting);
@@ -13,15 +13,23 @@ import BEHAVIORAL_METRICS from '@webex/plugin-meetings/src/metrics/constants';
13
13
  import { IP_VERSION } from '../../../../src/constants';
14
14
 
15
15
  describe('Roap', () => {
16
+ let webex;
17
+
18
+ const RESULT = {something: 'some value'};
19
+ const meeting = {id: 'some meeting id'} as Meeting;
20
+
21
+ beforeEach(() => {
22
+ webex = new MockWebex({});
23
+ });
24
+
25
+ afterEach(() => {
26
+ sinon.restore();
27
+ });
28
+
16
29
  describe('doTurnDiscovery', () => {
17
30
  [false, true].forEach(function (isReconnecting) {
18
31
  [false, true, undefined].forEach(function (isForced) {
19
32
  it(`calls this.turnDiscovery.doTurnDiscovery() and forwards all the arguments when isReconnecting = ${isReconnecting} and isForced = ${isForced}`, async () => {
20
- const webex = new MockWebex({});
21
-
22
- const RESULT = {something: 'some value'};
23
- const meeting = {id: 'some meeting id'} as Meeting;
24
-
25
33
  const doTurnDiscoveryStub = sinon
26
34
  .stub(TurnDiscovery.prototype, 'doTurnDiscovery')
27
35
  .resolves(RESULT);
@@ -32,11 +40,58 @@ describe('Roap', () => {
32
40
 
33
41
  assert.calledOnceWithExactly(doTurnDiscoveryStub, meeting, isReconnecting, isForced);
34
42
  assert.deepEqual(result, RESULT);
43
+ });
44
+ });
45
+ });
46
+
47
+ describe('generateTurnDiscoveryRequestMessage', () => {
48
+ [false, true].forEach(function (isForced) {
49
+ it(`calls this.turnDiscovery.generateTurnDiscoveryRequestMessage with isForced=${isForced}`, async () => {
50
+ const generateTurnDiscoveryRequestMessageStub = sinon
51
+ .stub(TurnDiscovery.prototype, 'generateTurnDiscoveryRequestMessage')
52
+ .resolves(RESULT);
53
+
54
+ const roap = new Roap({}, {parent: webex});
35
55
 
36
- sinon.restore();
56
+ const result = await roap.generateTurnDiscoveryRequestMessage(meeting, isForced);
57
+
58
+ assert.calledOnceWithExactly(generateTurnDiscoveryRequestMessageStub, meeting, isForced);
59
+ assert.deepEqual(result, RESULT);
37
60
  });
38
61
  });
39
62
  });
63
+
64
+ describe('handleTurnDiscoveryHttpResponse', () => {
65
+ it('calls this.turnDiscovery.handleTurnDiscoveryHttpResponse', async () => {
66
+ const handleTurnDiscoveryHttpResponseStub = sinon
67
+ .stub(TurnDiscovery.prototype, 'handleTurnDiscoveryHttpResponse')
68
+ .resolves(RESULT);
69
+
70
+ const httpReponse = {some: 'http response'};
71
+
72
+ const roap = new Roap({}, {parent: webex});
73
+
74
+ const result = await roap.handleTurnDiscoveryHttpResponse(meeting, httpReponse);
75
+
76
+ assert.calledOnceWithExactly(handleTurnDiscoveryHttpResponseStub, meeting, httpReponse);
77
+ assert.deepEqual(result, RESULT);
78
+ });
79
+ });
80
+
81
+ describe('abortTurnDiscovery', () => {
82
+ it('calls this.turnDiscovery.abort', async () => {
83
+ const abortStub = sinon
84
+ .stub(TurnDiscovery.prototype, 'abort')
85
+ .returns(RESULT);
86
+
87
+ const roap = new Roap({}, {parent: webex});
88
+
89
+ const result = await roap.abortTurnDiscovery();
90
+
91
+ assert.calledOnceWithExactly(abortStub);
92
+ assert.deepEqual(result, RESULT);
93
+ });
94
+ });
40
95
  });
41
96
 
42
97
  describe('sendRoapMediaRequest', () => {
@@ -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.isUndefined(turnDiscoverySkippedReason);
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.isUndefined(turnDiscoverySkippedReason);
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.isUndefined(turnDiscoverySkippedReason);
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.isUndefined(turnDiscoverySkippedReason);
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.isUndefined(turnDiscoverySkippedReason);
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.isUndefined(turnDiscoverySkippedReason);
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.isUndefined(turnDiscoverySkippedReason);
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
  });