@webex/plugin-meetings 3.0.0-beta.1 → 3.0.0-beta.2

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 (111) hide show
  1. package/dist/common/errors/webex-errors.js +5 -29
  2. package/dist/common/errors/webex-errors.js.map +1 -1
  3. package/dist/constants.js +15 -74
  4. package/dist/constants.js.map +1 -1
  5. package/dist/media/index.js +68 -213
  6. package/dist/media/index.js.map +1 -1
  7. package/dist/media/internal-media-core-wrapper.js +22 -0
  8. package/dist/media/internal-media-core-wrapper.js.map +1 -0
  9. package/dist/media/properties.js +20 -25
  10. package/dist/media/properties.js.map +1 -1
  11. package/dist/media/util.js +0 -27
  12. package/dist/media/util.js.map +1 -1
  13. package/dist/meeting/index.js +694 -432
  14. package/dist/meeting/index.js.map +1 -1
  15. package/dist/meeting/request.js +1 -0
  16. package/dist/meeting/request.js.map +1 -1
  17. package/dist/meeting/util.js +3 -44
  18. package/dist/meeting/util.js.map +1 -1
  19. package/dist/meetings/index.js +64 -5
  20. package/dist/meetings/index.js.map +1 -1
  21. package/dist/meetings/util.js +24 -1
  22. package/dist/meetings/util.js.map +1 -1
  23. package/dist/members/index.js +68 -0
  24. package/dist/members/index.js.map +1 -1
  25. package/dist/multistream/mediaRequestManager.js +132 -0
  26. package/dist/multistream/mediaRequestManager.js.map +1 -0
  27. package/dist/multistream/multistreamMedia.js +116 -0
  28. package/dist/multistream/multistreamMedia.js.map +1 -0
  29. package/dist/multistream/receiveSlot.js +209 -0
  30. package/dist/multistream/receiveSlot.js.map +1 -0
  31. package/dist/multistream/receiveSlotManager.js +195 -0
  32. package/dist/multistream/receiveSlotManager.js.map +1 -0
  33. package/dist/multistream/remoteMedia.js +284 -0
  34. package/dist/multistream/remoteMedia.js.map +1 -0
  35. package/dist/multistream/remoteMediaGroup.js +243 -0
  36. package/dist/multistream/remoteMediaGroup.js.map +1 -0
  37. package/dist/multistream/remoteMediaManager.js +1113 -0
  38. package/dist/multistream/remoteMediaManager.js.map +1 -0
  39. package/dist/reconnection-manager/index.js +109 -130
  40. package/dist/reconnection-manager/index.js.map +1 -1
  41. package/dist/roap/index.js +57 -240
  42. package/dist/roap/index.js.map +1 -1
  43. package/dist/roap/request.js +2 -114
  44. package/dist/roap/request.js.map +1 -1
  45. package/dist/roap/turnDiscovery.js +11 -5
  46. package/dist/roap/turnDiscovery.js.map +1 -1
  47. package/dist/statsAnalyzer/global.js +2 -0
  48. package/dist/statsAnalyzer/global.js.map +1 -1
  49. package/dist/statsAnalyzer/index.js +39 -36
  50. package/dist/statsAnalyzer/index.js.map +1 -1
  51. package/package.json +20 -19
  52. package/src/common/errors/webex-errors.js +0 -18
  53. package/src/constants.ts +139 -180
  54. package/src/media/index.js +60 -194
  55. package/src/media/internal-media-core-wrapper.ts +9 -0
  56. package/src/media/properties.js +19 -25
  57. package/src/media/util.js +0 -22
  58. package/src/meeting/index.js +565 -320
  59. package/src/meeting/request.js +1 -0
  60. package/src/meeting/util.js +3 -46
  61. package/src/meetings/index.js +30 -1
  62. package/src/meetings/util.js +23 -2
  63. package/src/members/index.js +48 -0
  64. package/src/multistream/mediaRequestManager.ts +164 -0
  65. package/src/multistream/multistreamMedia.ts +92 -0
  66. package/src/multistream/receiveSlot.ts +141 -0
  67. package/src/multistream/receiveSlotManager.ts +142 -0
  68. package/src/multistream/remoteMedia.ts +219 -0
  69. package/src/multistream/remoteMediaGroup.ts +224 -0
  70. package/src/multistream/remoteMediaManager.ts +911 -0
  71. package/src/reconnection-manager/index.js +40 -53
  72. package/src/roap/index.js +47 -207
  73. package/src/roap/request.js +1 -72
  74. package/src/roap/turnDiscovery.ts +12 -6
  75. package/src/statsAnalyzer/global.js +2 -0
  76. package/src/statsAnalyzer/index.js +32 -46
  77. package/test/integration/spec/journey.js +1 -1
  78. package/test/unit/spec/media/index.ts +223 -0
  79. package/test/unit/spec/media/properties.ts +73 -82
  80. package/test/unit/spec/meeting/effectsState.js +1 -3
  81. package/test/unit/spec/meeting/index.js +420 -228
  82. package/test/unit/spec/meeting/muteState.js +7 -0
  83. package/test/unit/spec/meeting/utils.js +61 -2
  84. package/test/unit/spec/meetings/index.js +0 -4
  85. package/test/unit/spec/members/index.js +164 -2
  86. package/test/unit/spec/multistream/mediaRequestManager.ts +511 -0
  87. package/test/unit/spec/multistream/receiveSlot.ts +104 -0
  88. package/test/unit/spec/multistream/receiveSlotManager.ts +173 -0
  89. package/test/unit/spec/multistream/remoteMedia.ts +217 -0
  90. package/test/unit/spec/multistream/remoteMediaGroup.ts +396 -0
  91. package/test/unit/spec/multistream/remoteMediaManager.ts +1251 -0
  92. package/test/unit/spec/roap/index.ts +63 -35
  93. package/test/unit/spec/stats-analyzer/index.js +19 -22
  94. package/dist/peer-connection-manager/index.js +0 -794
  95. package/dist/peer-connection-manager/index.js.map +0 -1
  96. package/dist/roap/collection.js +0 -73
  97. package/dist/roap/collection.js.map +0 -1
  98. package/dist/roap/handler.js +0 -337
  99. package/dist/roap/handler.js.map +0 -1
  100. package/dist/roap/state.js +0 -164
  101. package/dist/roap/state.js.map +0 -1
  102. package/dist/roap/util.js +0 -102
  103. package/dist/roap/util.js.map +0 -1
  104. package/src/peer-connection-manager/index.js +0 -723
  105. package/src/roap/collection.js +0 -63
  106. package/src/roap/handler.js +0 -252
  107. package/src/roap/state.js +0 -149
  108. package/src/roap/util.js +0 -93
  109. package/test/unit/spec/peerconnection-manager/index.js +0 -188
  110. package/test/unit/spec/peerconnection-manager/utils.js +0 -48
  111. package/test/unit/spec/roap/util.js +0 -30
@@ -20,7 +20,9 @@ import {
20
20
  _SIP_URI_,
21
21
  _MEETING_ID_,
22
22
  LOCUSINFO,
23
+ PC_BAIL_TIMEOUT,
23
24
  } from '@webex/plugin-meetings/src/constants';
25
+ import {MediaConnection as MC} from '@webex/internal-media-core';
24
26
  import * as StatsAnalyzerModule from '@webex/plugin-meetings/src/statsAnalyzer';
25
27
  import EventsScope from '@webex/plugin-meetings/src/common/events/events-scope';
26
28
  import Meetings, {CONSTANTS} from '@webex/plugin-meetings';
@@ -32,7 +34,6 @@ import LocusInfo from '@webex/plugin-meetings/src/locus-info';
32
34
  import MediaProperties from '@webex/plugin-meetings/src/media/properties';
33
35
  import MeetingUtil from '@webex/plugin-meetings/src/meeting/util';
34
36
  import Media from '@webex/plugin-meetings/src/media/index';
35
- import PeerConnectionManager from '@webex/plugin-meetings/src/peer-connection-manager';
36
37
  import ReconnectionManager from '@webex/plugin-meetings/src/reconnection-manager';
37
38
  import MediaUtil from '@webex/plugin-meetings/src/media/util';
38
39
  import LoggerProxy from '@webex/plugin-meetings/src/common/logs/logger-proxy';
@@ -178,7 +179,6 @@ describe('plugin-meetings', () => {
178
179
  TriggerProxy.trigger = sinon.stub().returns(true);
179
180
  Metrics.postEvent = sinon.stub();
180
181
  Metrics.initialSetup(null, webex);
181
- MediaUtil.createPeerConnection = sinon.stub().returns({});
182
182
  MediaUtil.createMediaStream = sinon.stub().returns(true);
183
183
 
184
184
  uuid1 = uuid.v4();
@@ -222,7 +222,6 @@ describe('plugin-meetings', () => {
222
222
  assert.equal(meeting.userId, uuid1);
223
223
  assert.equal(meeting.resource, uuid2);
224
224
  assert.equal(meeting.deviceUrl, uuid3);
225
- assert.equal(meeting.roapSeq, -1);
226
225
  assert.deepEqual(meeting.meetingInfo, {});
227
226
  assert.instanceOf(meeting.members, Members);
228
227
  assert.instanceOf(meeting.roap, Roap);
@@ -909,22 +908,25 @@ describe('plugin-meetings', () => {
909
908
  applyClientStateLocally: sinon.stub().returns(Promise.resolve(true))
910
909
  };
911
910
 
911
+ let fakeMediaConnection;
912
+
912
913
  beforeEach(() => {
914
+ fakeMediaConnection = {
915
+ close: sinon.stub(),
916
+ getConnectionState: sinon.stub().returns(MC.ConnectionState.Connected),
917
+ initiateOffer: sinon.stub().resolves({}),
918
+ on: sinon.stub(),
919
+ };
913
920
  meeting.mediaProperties.setMediaDirection = sinon.stub().returns(true);
914
- meeting.mediaProperties.waitForIceConnectedState = sinon.stub().resolves();
921
+ meeting.mediaProperties.waitForMediaConnectionConnected = sinon.stub().resolves();
915
922
  meeting.mediaProperties.getCurrentConnectionType = sinon.stub().resolves('udp');
916
923
  meeting.audio = muteStateStub;
917
924
  meeting.video = muteStateStub;
918
- Media.attachMedia = sinon.stub().returns(Promise.resolve([test1, test2]));
925
+ Media.createMediaConnection = sinon.stub().returns(fakeMediaConnection);
919
926
  meeting.setMercuryListener = sinon.stub().returns(true);
920
- meeting.setRemoteStream = sinon.stub().returns(true);
927
+ meeting.setupMediaConnectionListeners = sinon.stub();
921
928
  meeting.setMercuryListener = sinon.stub();
922
- meeting.roap.sendRoapMediaRequest = sinon.stub().returns(new Promise((resolve) => {
923
- meeting.mediaProperties.peerConnection.connectionState = CONSTANTS.CONNECTION_STATE.CONNECTED;
924
- resolve();
925
- }));
926
929
  meeting.roap.doTurnDiscovery = sinon.stub().resolves({turnServerInfo: {}, turnDiscoverySkippedReason: undefined});
927
- PeerConnectionManager.setContentSlides = sinon.stub().returns(true);
928
930
  });
929
931
 
930
932
  it('should have #addMedia', () => {
@@ -932,16 +934,17 @@ describe('plugin-meetings', () => {
932
934
  });
933
935
 
934
936
  it('should reject promise if meeting is not active', async () => {
935
- await meeting.addMedia().catch((err) => {
936
- assert.instanceOf(err, MeetingNotActiveError);
937
- });
937
+ const result = await assert.isRejected(meeting.addMedia());
938
+
939
+ assert.instanceOf(result, MeetingNotActiveError);
938
940
  });
939
941
 
940
942
  it('should reject promise if user already in left state', async () => {
941
943
  meeting.meetingState = 'ACTIVE';
942
- await meeting.addMedia().catch((err) => {
943
- assert.instanceOf(err, UserNotJoinedError);
944
- });
944
+ meeting.locusInfo.parsedLocus = {self: {state: 'LEFT'}};
945
+ const result = await assert.isRejected(meeting.addMedia());
946
+
947
+ assert.instanceOf(result, UserNotJoinedError);
945
948
  });
946
949
 
947
950
  it('should reject promise if user is in lobby ', async () => {
@@ -960,24 +963,29 @@ describe('plugin-meetings', () => {
960
963
 
961
964
  it('should reset the statsAnalyzer to null if addMedia throws an error', async () => {
962
965
  meeting.meetingState = 'ACTIVE';
963
- meeting.statsAnalyzer = true;
964
- await meeting.addMedia().catch((err) => {
965
- assert.exists(err);
966
- assert.isNull(meeting.statsAnalyzer);
967
- assert(Metrics.sendBehavioralMetric.calledOnce);
968
- assert.calledWith(
969
- Metrics.sendBehavioralMetric,
970
- BEHAVIORAL_METRICS.ADD_MEDIA_FAILURE, {
971
- correlation_id: meeting.correlationId,
972
- locus_id: meeting.locusUrl.split('/').pop(),
973
- reason: err.message,
974
- stack: err.stack,
975
- code: err.code,
976
- turnDiscoverySkippedReason: undefined,
977
- turnServerUsed: true
978
- }
979
- );
966
+ // setup the mock to return an incomplete object - this will cause addMedia to fail
967
+ // because some methods (like on() or initiateOffer()) are missing
968
+ Media.createMediaConnection = sinon.stub().returns({
969
+ close: sinon.stub(),
980
970
  });
971
+ // set a statsAnalyzer on the meeting so that we can check that it gets reset to null
972
+ meeting.statsAnalyzer = {stopAnalyzer: sinon.stub().resolves()};
973
+ const error = await assert.isRejected(meeting.addMedia());
974
+
975
+ assert.isNull(meeting.statsAnalyzer);
976
+ assert(Metrics.sendBehavioralMetric.calledOnce);
977
+ assert.calledWith(
978
+ Metrics.sendBehavioralMetric,
979
+ BEHAVIORAL_METRICS.ADD_MEDIA_FAILURE, {
980
+ correlation_id: meeting.correlationId,
981
+ locus_id: meeting.locusUrl.split('/').pop(),
982
+ reason: error.message,
983
+ stack: error.stack,
984
+ code: error.code,
985
+ turnDiscoverySkippedReason: undefined,
986
+ turnServerUsed: true
987
+ }
988
+ );
981
989
  });
982
990
 
983
991
  it('checks metrics called with skipped reason config', async () => {
@@ -1000,37 +1008,44 @@ describe('plugin-meetings', () => {
1000
1008
  });
1001
1009
  });
1002
1010
 
1003
- it('should reset the peerConnection to null if addMedia throws an error', async () => {
1011
+ it('should reset the webrtcMediaConnection to null if addMedia throws an error', async () => {
1004
1012
  meeting.meetingState = 'ACTIVE';
1005
- meeting.mediaProperties.peerConnection = true;
1006
- await meeting.addMedia().catch((err) => {
1007
- assert.exists(err);
1008
- assert.isNull(meeting.mediaProperties.peerConnection);
1009
- assert(Metrics.sendBehavioralMetric.calledOnce);
1010
- assert.calledWith(
1011
- Metrics.sendBehavioralMetric,
1012
- BEHAVIORAL_METRICS.ADD_MEDIA_FAILURE, {
1013
- correlation_id: meeting.correlationId,
1014
- locus_id: meeting.locusUrl.split('/').pop(),
1015
- reason: err.message,
1016
- stack: err.stack,
1017
- turnDiscoverySkippedReason: undefined,
1018
- turnServerUsed: true
1019
- }
1020
- );
1013
+ // setup the mock so that a media connection is created, but its initiateOffer() method fails
1014
+ Media.createMediaConnection = sinon.stub().returns({
1015
+ initiateOffer: sinon.stub().throws(new Error('fake error')),
1016
+ close: sinon.stub(),
1021
1017
  });
1018
+ const result = await assert.isRejected(meeting.addMedia());
1019
+
1020
+ assert.instanceOf(result, Error);
1021
+ assert.isNull(meeting.mediaProperties.webrtcMediaConnection);
1022
+
1023
+ assert(Metrics.sendBehavioralMetric.calledOnce);
1024
+ assert.calledWith(
1025
+ Metrics.sendBehavioralMetric,
1026
+ BEHAVIORAL_METRICS.ADD_MEDIA_FAILURE, sinon.match({
1027
+ correlation_id: meeting.correlationId,
1028
+ locus_id: meeting.locusUrl.split('/').pop(),
1029
+ reason: result.message,
1030
+ turnDiscoverySkippedReason: undefined,
1031
+ turnServerUsed: true
1032
+ })
1033
+ );
1022
1034
  });
1023
1035
 
1024
1036
  it('should work the second time addMedia is called in case the first time fails', async () => {
1025
1037
  meeting.meetingState = 'ACTIVE';
1026
1038
 
1027
- try {
1028
- await meeting.addMedia();
1029
- assert.fail('addMedia should have thrown an exception.');
1030
- }
1031
- catch (err) {
1032
- assert.exists(err);
1033
- }
1039
+ // setup the mock to cause addMedia() to fail
1040
+ Media.createMediaConnection = sinon.stub().returns({
1041
+ initiateOffer: sinon.stub().throws(new Error('fake error')),
1042
+ close: sinon.stub(),
1043
+ });
1044
+
1045
+ await assert.isRejected(meeting.addMedia());
1046
+
1047
+ // reset the mock to a good one, that doesn't fail
1048
+ Media.createMediaConnection = sinon.stub().returns(fakeMediaConnection);
1034
1049
 
1035
1050
  try {
1036
1051
  await meeting.addMedia({
@@ -1044,12 +1059,11 @@ describe('plugin-meetings', () => {
1044
1059
 
1045
1060
  it('if an error occurs after media request has already been sent, and the user waits until the server kicks them out, a UserNotJoinedError should be thrown when attempting to addMedia again', async () => {
1046
1061
  meeting.meetingState = 'ACTIVE';
1047
- meeting.roap.sendRoapMediaRequest = sinon.stub().returns(new Promise((resolve) => {
1048
- meeting.mediaProperties.peerConnection.connectionState = CONSTANTS.CONNECTION_STATE.CONNECTED;
1049
- resolve();
1050
- }).then(() => {
1051
- throw new Error('sample error thrown');
1052
- }));
1062
+ // setup the mock to cause addMedia() to fail
1063
+ Media.createMediaConnection = sinon.stub().returns({
1064
+ initiateOffer: sinon.stub().throws(new Error('fake error')),
1065
+ close: sinon.stub(),
1066
+ });
1053
1067
  await meeting.addMedia().catch((err) => {
1054
1068
  assert.exists(err);
1055
1069
  });
@@ -1062,21 +1076,16 @@ describe('plugin-meetings', () => {
1062
1076
 
1063
1077
  it('if an error occurs after media request has already been sent, and the user does NOT wait until the server kicks them out, the user should be able to addMedia successfully', async () => {
1064
1078
  meeting.meetingState = 'ACTIVE';
1065
- meeting.roap.sendRoapMediaRequest = sinon.stub().returns(new Promise((resolve) => {
1066
- meeting.mediaProperties.peerConnection.connectionState = CONSTANTS.CONNECTION_STATE.CONNECTED;
1067
- resolve();
1068
- }).then(() => {
1069
- throw new Error('sample error thrown');
1070
- }));
1079
+ // setup the mock to cause addMedia() to fail
1080
+ Media.createMediaConnection = sinon.stub().returns({
1081
+ initiateOffer: sinon.stub().throws(new Error('fake error')),
1082
+ close: sinon.stub(),
1083
+ });
1071
1084
  await meeting.addMedia().catch((err) => {
1072
1085
  assert.exists(err);
1073
1086
  });
1074
1087
 
1075
- meeting.mediaProperties.peerConnection = {};
1076
- meeting.roap.sendRoapMediaRequest = sinon.stub().returns(new Promise((resolve) => {
1077
- meeting.mediaProperties.peerConnection.connectionState = CONSTANTS.CONNECTION_STATE.CONNECTED;
1078
- resolve();
1079
- }));
1088
+ Media.createMediaConnection = sinon.stub().returns(fakeMediaConnection);
1080
1089
  await meeting.addMedia().catch((err) => {
1081
1090
  assert.fail('No error should appear: ', err);
1082
1091
  });
@@ -1086,7 +1095,6 @@ describe('plugin-meetings', () => {
1086
1095
  meeting.roap.doTurnDiscovery = sinon.stub().resolves({turnServerInfo: undefined, turnDiscoverySkippedReason: undefined});
1087
1096
 
1088
1097
  meeting.meetingState = 'ACTIVE';
1089
- MediaUtil.createPeerConnection.resetHistory();
1090
1098
  const media = meeting.addMedia({
1091
1099
  mediaSettings: {}
1092
1100
  });
@@ -1096,12 +1104,10 @@ describe('plugin-meetings', () => {
1096
1104
  assert.calledOnce(meeting.roap.doTurnDiscovery);
1097
1105
  assert.calledWith(meeting.roap.doTurnDiscovery, meeting, false);
1098
1106
  assert.calledOnce(meeting.mediaProperties.setMediaDirection);
1099
- assert.calledOnce(Media.attachMedia);
1107
+ assert.calledOnce(Media.createMediaConnection);
1108
+ assert.calledWith(Media.createMediaConnection, sinon.match.any, sinon.match({turnServerInfo: undefined}));
1100
1109
  assert.calledOnce(meeting.setMercuryListener);
1101
- assert.calledOnce(meeting.setRemoteStream);
1102
- assert.calledOnce(meeting.roap.sendRoapMediaRequest);
1103
- assert.calledOnce(MediaUtil.createPeerConnection);
1104
- assert.calledWith(MediaUtil.createPeerConnection, undefined);
1110
+ assert.calledOnce(fakeMediaConnection.initiateOffer);
1105
1111
  /* statsAnalyzer is initiated inside of addMedia so there isn't
1106
1112
  * a good way to mock it without mocking the constructor
1107
1113
  */
@@ -1113,7 +1119,7 @@ describe('plugin-meetings', () => {
1113
1119
  const FAKE_TURN_PASSWORD = 'some-password';
1114
1120
 
1115
1121
  meeting.meetingState = 'ACTIVE';
1116
- MediaUtil.createPeerConnection.resetHistory();
1122
+ Media.createMediaConnection.resetHistory();
1117
1123
 
1118
1124
 
1119
1125
  meeting.roap.doTurnDiscovery = sinon.stub().resolves({
@@ -1132,31 +1138,39 @@ describe('plugin-meetings', () => {
1132
1138
  await media;
1133
1139
  assert.calledOnce(meeting.roap.doTurnDiscovery);
1134
1140
  assert.calledWith(meeting.roap.doTurnDiscovery, meeting, false);
1135
- assert.calledOnce(MediaUtil.createPeerConnection);
1136
- assert.calledWith(MediaUtil.createPeerConnection, {
1137
- url: FAKE_TURN_URL,
1138
- username: FAKE_TURN_USER,
1139
- password: FAKE_TURN_PASSWORD
1140
- });
1141
+ assert.calledOnce(Media.createMediaConnection);
1142
+ assert.calledWith(Media.createMediaConnection, sinon.match.any, sinon.match({
1143
+ turnServerInfo: {
1144
+ url: FAKE_TURN_URL,
1145
+ username: FAKE_TURN_USER,
1146
+ password: FAKE_TURN_PASSWORD
1147
+ }
1148
+ }));
1149
+ assert.calledOnce(fakeMediaConnection.initiateOffer);
1141
1150
  });
1142
1151
 
1143
- it('should attach the media and return promise', async () => {
1144
- meeting.roap.doTurnDiscovery = sinon.stub().resolves({turnServerInfo: undefined, turnDiscoverySkippedReason: undefined});
1152
+ it('should attach the media and return WebExMeetingsErrors when connection does not reach CONNECTED state', async () => {
1145
1153
  meeting.meetingState = 'ACTIVE';
1146
- meeting.mediaProperties.peerConnection.connectionState = 'DISCONNECTED';
1154
+ fakeMediaConnection.getConnectionState = sinon.stub().returns(MC.ConnectionState.Connecting);
1155
+ const clock = sinon.useFakeTimers();
1147
1156
  const media = meeting.addMedia({
1148
1157
  mediaSettings: {}
1149
1158
  });
1150
1159
 
1160
+ await clock.tickAsync(4000 /* meetingState timer, hardcoded inside addMedia */ + PC_BAIL_TIMEOUT /* connection state timer */);
1161
+ await testUtils.flushPromises();
1162
+
1151
1163
  assert.exists(media);
1152
1164
  await media.catch((err) => {
1153
1165
  assert.instanceOf(err, WebExMeetingsErrors);
1154
1166
  });
1167
+
1168
+ clock.restore();
1155
1169
  });
1156
1170
 
1157
- it('should reject if waitForIceConnectedState() rejects', async () => {
1171
+ it('should reject if waitForMediaConnectionConnected() rejects', async () => {
1158
1172
  meeting.meetingState = 'ACTIVE';
1159
- meeting.mediaProperties.waitForIceConnectedState.rejects(new Error('fake error'));
1173
+ meeting.mediaProperties.waitForMediaConnectionConnected.rejects(new Error('fake error'));
1160
1174
 
1161
1175
  let errorThrown = false;
1162
1176
 
@@ -1346,7 +1360,6 @@ describe('plugin-meetings', () => {
1346
1360
  meeting.statsAnalyzer = {stopAnalyzer: sinon.stub().resolves()};
1347
1361
  meeting.unsetRemoteStream = sinon.stub().returns(true);
1348
1362
  meeting.unsetPeerConnections = sinon.stub().returns(true);
1349
- meeting.roap.stop = sinon.stub().returns(Promise.resolve());
1350
1363
  meeting.logger.error = sinon.stub().returns(true);
1351
1364
 
1352
1365
  // A meeting needs to be joined to leave
@@ -1371,7 +1384,6 @@ describe('plugin-meetings', () => {
1371
1384
  assert.calledOnce(meeting.unsetLocalShareTrack);
1372
1385
  assert.calledOnce(meeting.unsetRemoteTracks);
1373
1386
  assert.calledOnce(meeting.unsetPeerConnections);
1374
- assert.calledOnce(meeting.roap.stop);
1375
1387
  });
1376
1388
  describe('after audio/video is defined', () => {
1377
1389
  let handleClientRequest;
@@ -1484,61 +1496,6 @@ describe('plugin-meetings', () => {
1484
1496
  });
1485
1497
  });
1486
1498
 
1487
- describe('getter: isLocalShareLive', () => {
1488
- const LIVE = 'live';
1489
- const ENDED = 'ended';
1490
- const SENDRECV = 'sendrecv';
1491
- const RECVONLY = 'reconly';
1492
- let sandbox;
1493
- let _direction;
1494
- let _trackReadyState = ENDED;
1495
-
1496
- beforeEach(() => {
1497
- sandbox = sinon.createSandbox();
1498
- sandbox.stub(meeting.mediaProperties, 'shareTrack').value(true);
1499
- sandbox.stub(meeting.mediaProperties, 'peerConnection').value({
1500
- shareTransceiver: {
1501
- get direction() {
1502
- return _direction;
1503
- },
1504
- sender: {
1505
- track: {
1506
- get readyState() {
1507
- return _trackReadyState;
1508
- }
1509
- }
1510
- }
1511
- }
1512
- });
1513
- });
1514
-
1515
- afterEach(() => {
1516
- sandbox.restore();
1517
- sandbox = null;
1518
- });
1519
-
1520
- it('sets isLocalShareLive to true when sharing screen', () => {
1521
- _direction = SENDRECV;
1522
- _trackReadyState = LIVE;
1523
-
1524
- assert.isTrue(meeting.isLocalShareLive);
1525
- });
1526
-
1527
- it('sets isLocalShareLive to false when not sharing screen', () => {
1528
- _direction = RECVONLY;
1529
- _trackReadyState = ENDED;
1530
-
1531
- assert.isFalse(meeting.isLocalShareLive);
1532
- });
1533
-
1534
- it('sets isLocalShareLive to false when track is live but share direction is recv only', () => {
1535
- _direction = RECVONLY;
1536
- _trackReadyState = LIVE;
1537
-
1538
- assert.isFalse(meeting.isLocalShareLive);
1539
- });
1540
- });
1541
-
1542
1499
  describe('stops share immediately', () => {
1543
1500
  let sandbox;
1544
1501
 
@@ -1556,7 +1513,6 @@ describe('plugin-meetings', () => {
1556
1513
  const receiveShare = false;
1557
1514
  const stream = 'stream';
1558
1515
 
1559
- sandbox.stub(meeting.mediaProperties, 'peerConnection').value({shareTransceiver: true});
1560
1516
  sandbox.stub(MeetingUtil, 'getTrack').returns({videoTrack: true});
1561
1517
  MeetingUtil.validateOptions = sinon.stub().returns(Promise.resolve(true));
1562
1518
  sandbox.stub(meeting, 'canUpdateMedia').returns(true);
@@ -1620,42 +1576,6 @@ describe('plugin-meetings', () => {
1620
1576
  sandbox = null;
1621
1577
  });
1622
1578
 
1623
- it('calls handleShareTrackEnded if sharing is out of sync', async () => {
1624
- const sendShare = true;
1625
- const receiveShare = false;
1626
- const stream = 'stream';
1627
- const SENDRECV = 'sendrecv';
1628
- const delay = 1e3;
1629
-
1630
- MeetingUtil.validateOptions = sinon.stub().returns(Promise.resolve(true));
1631
- MeetingUtil.updateTransceiver = sinon.stub().returns(Promise.resolve(true));
1632
- sandbox.stub(meeting, 'canUpdateMedia').returns(true);
1633
- sandbox.stub(MeetingUtil, 'getTrack').returns({videoTrack: null});
1634
- sandbox.stub(meeting, 'setLocalShareTrack');
1635
- sandbox.stub(meeting, 'unsetLocalShareTrack');
1636
- sandbox.stub(meeting, 'checkForStopShare').returns(false);
1637
-
1638
- sandbox.stub(meeting, 'isLocalShareLive').value(false);
1639
- sandbox.stub(meeting, 'handleShareTrackEnded');
1640
- sandbox.stub(meeting.mediaProperties, 'peerConnection').value({
1641
- shareTransceiver: {
1642
- direction: SENDRECV
1643
- }
1644
- });
1645
- sandbox.useFakeTimers();
1646
-
1647
- await meeting.updateShare({
1648
- sendShare,
1649
- receiveShare,
1650
- stream,
1651
- skipSignalingCheck: true
1652
- });
1653
- // simulate the setTimeout in code
1654
- sandbox.clock.tick(delay);
1655
-
1656
- assert.calledOnce(meeting.handleShareTrackEnded);
1657
- });
1658
-
1659
1579
  it('handleShareTrackEnded triggers an event', () => {
1660
1580
  const stream = 'stream';
1661
1581
  const {EVENT_TYPES} = CONSTANTS;
@@ -1917,6 +1837,11 @@ describe('plugin-meetings', () => {
1917
1837
  });
1918
1838
 
1919
1839
  describe('#updateAudio', () => {
1840
+ const FAKE_AUDIO_TRACK = {
1841
+ id: 'fake audio track',
1842
+ getSettings: sinon.stub().returns({}),
1843
+ };
1844
+
1920
1845
  describe('when canUpdateMedia is true', () => {
1921
1846
  beforeEach(() => {
1922
1847
  meeting.canUpdateMedia = sinon.stub().returns(true);
@@ -1924,20 +1849,33 @@ describe('plugin-meetings', () => {
1924
1849
  describe('when options are valid', () => {
1925
1850
  beforeEach(() => {
1926
1851
  MeetingUtil.validateOptions = sinon.stub().returns(Promise.resolve());
1852
+ meeting.mediaProperties.mediaDirection = {
1853
+ sendAudio: false,
1854
+ sendVideo: true,
1855
+ sendShare: false,
1856
+ receiveAudio: false,
1857
+ receiveVideo: true,
1858
+ receiveShare: true,
1859
+ };
1860
+ meeting.mediaProperties.webrtcMediaConnection = {updateSendReceiveOptions: sinon.stub()};
1861
+ sinon.stub(MeetingUtil, 'getTrack').returns({audioTrack: FAKE_AUDIO_TRACK});
1927
1862
  });
1928
- describe('when mediaDirection is undefined', () => {
1929
- beforeEach(() => {
1930
- meeting.mediaProperties.mediaDirection = null;
1931
- MeetingUtil.updateTransceiver = sinon.stub();
1863
+ it('calls this.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions', () => meeting.updateAudio({
1864
+ sendAudio: true,
1865
+ receiveAudio: true,
1866
+ stream: {id: 'fake stream'}
1867
+ }).then(() => {
1868
+ assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions);
1869
+ assert.calledWith(meeting.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions, {
1870
+ send: {audio: FAKE_AUDIO_TRACK},
1871
+ receive: {
1872
+ audio: true, video: true, screenShareVideo: true, remoteQualityLevel: 'HIGH'
1873
+ }
1932
1874
  });
1933
-
1934
- it('sets previousMediaDirection to an empty object', () => meeting.updateAudio({
1935
- sendAudio: true,
1936
- receiveAudio: true
1937
- }).then(() => {
1938
- assert.calledOnce(MeetingUtil.updateTransceiver);
1939
- }));
1940
- });
1875
+ }));
1876
+ });
1877
+ afterEach(() => {
1878
+ sinon.restore();
1941
1879
  });
1942
1880
  });
1943
1881
  });
@@ -1988,10 +1926,36 @@ describe('plugin-meetings', () => {
1988
1926
  let sandbox;
1989
1927
  const mockLocalStream = {id: 'mock local stream'};
1990
1928
  const mockLocalShare = {id: 'mock local share stream'};
1929
+ const FAKE_TRACKS = {
1930
+ audio: {
1931
+ id: 'fake audio track',
1932
+ getSettings: sinon.stub().returns({}),
1933
+ },
1934
+ video: {
1935
+ id: 'fake video track',
1936
+ getSettings: sinon.stub().returns({}),
1937
+ },
1938
+ screenshareVideo: {
1939
+ id: 'fake share track',
1940
+ getSettings: sinon.stub().returns({}),
1941
+ },
1942
+
1943
+ };
1991
1944
 
1992
1945
  beforeEach(() => {
1993
1946
  sandbox = sinon.createSandbox();
1994
1947
  meeting.mediaProperties.mediaDirection = {sendShare: true};
1948
+ // setup the stub to return the right tracks
1949
+ sandbox.stub(MeetingUtil, 'getTrack').callsFake((stream) => {
1950
+ if (stream === mockLocalStream) {
1951
+ return {audioTrack: FAKE_TRACKS.audio, videoTrack: FAKE_TRACKS.video};
1952
+ }
1953
+ if (stream === mockLocalShare) {
1954
+ return {audioTrack: null, videoTrack: FAKE_TRACKS.screenshareVideo};
1955
+ }
1956
+
1957
+ return {audioTrack: null, videoTrack: null};
1958
+ });
1995
1959
  });
1996
1960
 
1997
1961
  afterEach(() => {
@@ -2011,7 +1975,9 @@ describe('plugin-meetings', () => {
2011
1975
  };
2012
1976
 
2013
1977
  sandbox.stub(meeting, 'canUpdateMedia').returns(false);
2014
- sandbox.stub(Media, 'updateMedia').resolves();
1978
+ meeting.mediaProperties.webrtcMediaConnection = {
1979
+ updateSendReceiveOptions: sinon.stub().resolves({})
1980
+ };
2015
1981
 
2016
1982
  let myPromiseResolved = false;
2017
1983
 
@@ -2025,18 +1991,30 @@ describe('plugin-meetings', () => {
2025
1991
  });
2026
1992
 
2027
1993
  // verify that nothing was done
2028
- assert.notCalled(Media.updateMedia);
1994
+ assert.notCalled(meeting.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions);
2029
1995
 
2030
1996
  // now trigger processing of the queue
2031
1997
  meeting.canUpdateMedia.restore();
2032
1998
  sandbox.stub(meeting, 'canUpdateMedia').returns(true);
2033
- meeting.updateMedia = sinon.stub().returns(Promise.resolve());
2034
1999
 
2035
2000
  meeting.processNextQueuedMediaUpdate();
2036
2001
  await testUtils.flushPromises();
2037
2002
 
2038
- // and check that meeting.updateMedia is called with the original args
2039
- assert.calledWith(meeting.updateMedia, {localStream: mockLocalStream, localShare: mockLocalShare, mediaSettings});
2003
+ // and check that updateSendReceiveOptions is called with the original args
2004
+ assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions);
2005
+ assert.calledWith(meeting.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions, {
2006
+ send: {
2007
+ audio: FAKE_TRACKS.audio,
2008
+ video: FAKE_TRACKS.video,
2009
+ screenShareVideo: FAKE_TRACKS.screenshareVideo,
2010
+ },
2011
+ receive: {
2012
+ audio: true,
2013
+ video: true,
2014
+ screenShareVideo: true,
2015
+ remoteQualityLevel: 'HIGH'
2016
+ }
2017
+ });
2040
2018
  assert.isTrue(myPromiseResolved);
2041
2019
  });
2042
2020
  });
@@ -2282,7 +2260,7 @@ describe('plugin-meetings', () => {
2282
2260
  meeting.mediaProperties.mediaDirection = mediaDirection;
2283
2261
  meeting.canUpdateMedia = sinon.stub().returns(true);
2284
2262
  MeetingUtil.validateOptions = sinon.stub().returns(Promise.resolve());
2285
- MeetingUtil.updateTransceiver = sinon.stub().returns(Promise.resolve());
2263
+ meeting.updateVideo = sinon.stub().resolves();
2286
2264
  sinon.stub(MeetingUtil, 'getTrack').returns({videoTrack: fakeTrack});
2287
2265
  });
2288
2266
 
@@ -2842,7 +2820,6 @@ describe('plugin-meetings', () => {
2842
2820
  meeting.statsAnalyzer = {stopAnalyzer: sinon.stub().resolves()};
2843
2821
  meeting.unsetRemoteStream = sinon.stub().returns(true);
2844
2822
  meeting.unsetPeerConnections = sinon.stub().returns(true);
2845
- meeting.roap.stop = sinon.stub().returns(Promise.resolve());
2846
2823
  meeting.logger.error = sinon.stub().returns(true);
2847
2824
 
2848
2825
  // A meeting needs to be joined to end
@@ -2867,7 +2844,6 @@ describe('plugin-meetings', () => {
2867
2844
  assert.calledOnce(meeting?.unsetLocalShareTrack);
2868
2845
  assert.calledOnce(meeting?.unsetRemoteTracks);
2869
2846
  assert.calledOnce(meeting?.unsetPeerConnections);
2870
- assert.calledOnce(meeting?.roap?.stop);
2871
2847
  });
2872
2848
  });
2873
2849
 
@@ -3295,31 +3271,254 @@ describe('plugin-meetings', () => {
3295
3271
  assert.calledOnce(meeting.stopShare);
3296
3272
  });
3297
3273
  });
3298
- describe('#setRemoteStream', () => {
3274
+ describe('#setupMediaConnectionListeners', () => {
3275
+ let eventListeners;
3276
+
3299
3277
  beforeEach(() => {
3278
+ eventListeners = {};
3300
3279
  meeting.statsAnalyzer = {startAnalyzer: sinon.stub()};
3280
+ meeting.mediaProperties.webrtcMediaConnection = {
3281
+ // mock the on() method and store all the listeners
3282
+ on: sinon.stub().callsFake((event, listener) => {
3283
+ eventListeners[event] = listener;
3284
+ })
3285
+ };
3301
3286
  });
3302
- it('should trigger a media:ready event when remote stream track ontrack is fired', () => {
3303
- const pc = {};
3304
3287
 
3305
- meeting.setRemoteStream(pc);
3306
- pc.ontrack({track: 'track', transceiver: {mid: '0'}});
3288
+ it('should register for all the correct RoapMediaConnection events', () => {
3289
+ meeting.setupMediaConnectionListeners();
3290
+ assert.isFunction(eventListeners[MC.Event.ROAP_STARTED]);
3291
+ assert.isFunction(eventListeners[MC.Event.ROAP_DONE]);
3292
+ assert.isFunction(eventListeners[MC.Event.ROAP_FAILURE]);
3293
+ assert.isFunction(eventListeners[MC.Event.ROAP_MESSAGE_TO_SEND]);
3294
+ assert.isFunction(eventListeners[MC.Event.REMOTE_TRACK_ADDED]);
3295
+ assert.isFunction(eventListeners[MC.Event.CONNECTION_STATE_CHANGED]);
3296
+ });
3297
+
3298
+ it('should trigger a media:ready event when REMOTE_TRACK_ADDED is fired', () => {
3299
+ meeting.setupMediaConnectionListeners();
3300
+ eventListeners[MC.Event.REMOTE_TRACK_ADDED]({track: 'track', type: MC.RemoteTrackType.AUDIO});
3307
3301
  assert.equal(TriggerProxy.trigger.getCall(1).args[2], 'media:ready');
3308
3302
  assert.deepEqual(TriggerProxy.trigger.getCall(1).args[3], {type: 'remoteAudio', stream: true});
3309
3303
 
3310
- pc.ontrack({track: 'track', transceiver: {mid: '1'}});
3304
+ eventListeners[MC.Event.REMOTE_TRACK_ADDED]({track: 'track', type: MC.RemoteTrackType.VIDEO});
3311
3305
  assert.equal(TriggerProxy.trigger.getCall(2).args[2], 'media:ready');
3312
3306
  assert.deepEqual(TriggerProxy.trigger.getCall(2).args[3], {type: 'remoteVideo', stream: true});
3313
3307
 
3314
- pc.ontrack({transceiver: {mid: '2'}, track: 'track'});
3308
+ eventListeners[MC.Event.REMOTE_TRACK_ADDED]({track: 'track', type: MC.RemoteTrackType.SCREENSHARE_VIDEO});
3315
3309
  assert.equal(TriggerProxy.trigger.getCall(3).args[2], 'media:ready');
3316
3310
  assert.deepEqual(TriggerProxy.trigger.getCall(3).args[3], {type: 'remoteShare', stream: true});
3311
+ });
3317
3312
 
3313
+ describe('should send correct metrics for ROAP_FAILURE event', () => {
3314
+ const fakeErrorMessage = 'test error';
3315
+ const fakeRootCauseName = 'root cause name';
3316
+ const fakeErrorName = 'test error name';
3318
3317
 
3319
- // special case for safari
3320
- pc.ontrack({target: {audioTransceiver: {receiver: {track: {id: 'trackId'}}}}, transceiver: {}, track: {id: 'trackId'}});
3321
- assert.equal(TriggerProxy.trigger.getCall(1).args[2], 'media:ready');
3322
- assert.deepEqual(TriggerProxy.trigger.getCall(1).args[3], {type: 'remoteAudio', stream: true});
3318
+ beforeEach(() => {
3319
+ meeting.setupMediaConnectionListeners();
3320
+ });
3321
+
3322
+ const checkMetricSent = (event) => {
3323
+ assert.calledOnce(Metrics.postEvent);
3324
+ assert.calledWithMatch(Metrics.postEvent, {event, meetingId: meeting.id, data: {canProceed: false}});
3325
+ };
3326
+
3327
+ const checkBehavioralMetricSent = (metricName, expectedCode, expectedReason, expectedMetadataType) => {
3328
+ assert.calledOnce(Metrics.sendBehavioralMetric);
3329
+ assert.calledWith(
3330
+ Metrics.sendBehavioralMetric,
3331
+ metricName,
3332
+ {
3333
+ code: expectedCode,
3334
+ correlation_id: meeting.correlationId,
3335
+ reason: expectedReason,
3336
+ stack: sinon.match.any
3337
+ },
3338
+ {
3339
+ type: expectedMetadataType
3340
+ }
3341
+ );
3342
+ };
3343
+
3344
+ it('should send metrics for SdpOfferCreationError error', () => {
3345
+ const fakeError = new MC.Errors.SdpOfferCreationError(fakeErrorMessage, {name: fakeErrorName, cause: {name: fakeRootCauseName}});
3346
+
3347
+ eventListeners[MC.Event.ROAP_FAILURE](fakeError);
3348
+
3349
+ checkMetricSent(eventType.LOCAL_SDP_GENERATED);
3350
+ checkBehavioralMetricSent(BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE, MC.Errors.ErrorCode.SdpOfferCreationError, fakeErrorMessage, fakeRootCauseName);
3351
+ });
3352
+
3353
+ it('should send metrics for SdpOfferHandlingError error', () => {
3354
+ const fakeError = new MC.Errors.SdpOfferHandlingError(fakeErrorMessage, {name: fakeErrorName, cause: {name: fakeRootCauseName}});
3355
+
3356
+ eventListeners[MC.Event.ROAP_FAILURE](fakeError);
3357
+
3358
+ checkMetricSent(eventType.REMOTE_SDP_RECEIVED);
3359
+ checkBehavioralMetricSent(BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE, MC.Errors.ErrorCode.SdpOfferHandlingError, fakeErrorMessage, fakeRootCauseName);
3360
+ });
3361
+
3362
+ it('should send metrics for SdpAnswerHandlingError error', () => {
3363
+ const fakeError = new MC.Errors.SdpAnswerHandlingError(fakeErrorMessage, {name: fakeErrorName, cause: {name: fakeRootCauseName}});
3364
+
3365
+ eventListeners[MC.Event.ROAP_FAILURE](fakeError);
3366
+
3367
+ checkMetricSent(eventType.REMOTE_SDP_RECEIVED);
3368
+ checkBehavioralMetricSent(BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE, MC.Errors.ErrorCode.SdpAnswerHandlingError, fakeErrorMessage, fakeRootCauseName);
3369
+ });
3370
+
3371
+ it('should send metrics for SdpError error', () => {
3372
+ // SdpError is usually without a cause
3373
+ const fakeError = new MC.Errors.SdpError(fakeErrorMessage, {name: fakeErrorName});
3374
+
3375
+ eventListeners[MC.Event.ROAP_FAILURE](fakeError);
3376
+
3377
+ checkMetricSent(eventType.LOCAL_SDP_GENERATED);
3378
+ // expectedMetadataType is the error name in this case
3379
+ checkBehavioralMetricSent(BEHAVIORAL_METRICS.INVALID_ICE_CANDIDATE, MC.Errors.ErrorCode.SdpError, fakeErrorMessage, fakeErrorName);
3380
+ });
3381
+
3382
+ it('should send metrics for IceGatheringError error', () => {
3383
+ // IceGatheringError is usually without a cause
3384
+ const fakeError = new MC.Errors.IceGatheringError(fakeErrorMessage, {name: fakeErrorName});
3385
+
3386
+ eventListeners[MC.Event.ROAP_FAILURE](fakeError);
3387
+
3388
+ checkMetricSent(eventType.LOCAL_SDP_GENERATED);
3389
+ // expectedMetadataType is the error name in this case
3390
+ checkBehavioralMetricSent(BEHAVIORAL_METRICS.INVALID_ICE_CANDIDATE, MC.Errors.ErrorCode.IceGatheringError, fakeErrorMessage, fakeErrorName);
3391
+ });
3392
+ });
3393
+
3394
+ describe('handles MC.Event.ROAP_MESSAGE_TO_SEND correctly', () => {
3395
+ let sendRoapOKStub;
3396
+ let sendRoapMediaRequestStub;
3397
+ let sendRoapAnswerStub;
3398
+ let sendRoapErrorStub;
3399
+
3400
+ beforeEach(() => {
3401
+ sendRoapOKStub = sinon.stub(meeting.roap, 'sendRoapOK').resolves({});
3402
+ sendRoapMediaRequestStub = sinon.stub(meeting.roap, 'sendRoapMediaRequest').resolves({});
3403
+ sendRoapAnswerStub = sinon.stub(meeting.roap, 'sendRoapAnswer').resolves({});
3404
+ sendRoapErrorStub = sinon.stub(meeting.roap, 'sendRoapError').resolves({});
3405
+
3406
+ meeting.setupMediaConnectionListeners();
3407
+ });
3408
+
3409
+ it('handles OK message correctly', () => {
3410
+ eventListeners[MC.Event.ROAP_MESSAGE_TO_SEND]({roapMessage: {messageType: 'OK', seq: 1}});
3411
+
3412
+ assert.calledOnce(Metrics.postEvent);
3413
+ assert.calledWithMatch(Metrics.postEvent, {event: eventType.REMOTE_SDP_RECEIVED, meetingId: meeting.id});
3414
+
3415
+ assert.calledOnce(sendRoapOKStub);
3416
+ assert.calledWith(sendRoapOKStub, {seq: 1, mediaId: meeting.mediaId, correlationId: meeting.correlationId});
3417
+ });
3418
+
3419
+ it('handles OFFER message correctly', () => {
3420
+ eventListeners[MC.Event.ROAP_MESSAGE_TO_SEND]({
3421
+ roapMessage: {
3422
+ messageType: 'OFFER',
3423
+ seq: 1,
3424
+ sdp: 'fake sdp',
3425
+ tieBreaker: 12345,
3426
+ }
3427
+ });
3428
+
3429
+ assert.calledOnce(Metrics.postEvent);
3430
+ assert.calledWithMatch(Metrics.postEvent, {event: eventType.LOCAL_SDP_GENERATED, meetingId: meeting.id});
3431
+
3432
+ assert.calledOnce(sendRoapMediaRequestStub);
3433
+ assert.calledWith(sendRoapMediaRequestStub, {
3434
+ seq: 1, sdp: 'fake sdp', tieBreaker: 12345, meeting, reconnect: false
3435
+ });
3436
+ });
3437
+
3438
+ it('handles ANSWER message correctly', () => {
3439
+ eventListeners[MC.Event.ROAP_MESSAGE_TO_SEND]({
3440
+ roapMessage: {
3441
+ messageType: 'ANSWER',
3442
+ seq: 10,
3443
+ sdp: 'fake sdp answer',
3444
+ tieBreaker: 12345,
3445
+ }
3446
+ });
3447
+
3448
+ assert.calledOnce(Metrics.postEvent);
3449
+ assert.calledWithMatch(Metrics.postEvent, {event: eventType.REMOTE_SDP_RECEIVED, meetingId: meeting.id});
3450
+
3451
+ assert.calledOnce(sendRoapAnswerStub);
3452
+ assert.calledWith(sendRoapAnswerStub, {
3453
+ seq: 10, sdp: 'fake sdp answer', mediaId: meeting.mediaId, correlationId: meeting.correlationId
3454
+ });
3455
+ });
3456
+
3457
+ it('sends metrics if fails to send roap ANSWER message', async () => {
3458
+ sendRoapAnswerStub.rejects(new Error('sending answer failed'));
3459
+
3460
+ await eventListeners[MC.Event.ROAP_MESSAGE_TO_SEND]({
3461
+ roapMessage: {
3462
+ messageType: 'ANSWER',
3463
+ seq: 10,
3464
+ sdp: 'fake sdp answer',
3465
+ tieBreaker: 12345,
3466
+ }
3467
+ });
3468
+ await testUtils.flushPromises();
3469
+
3470
+ assert.calledOnce(Metrics.sendBehavioralMetric);
3471
+ assert.calledWithMatch(Metrics.sendBehavioralMetric, BEHAVIORAL_METRICS.ROAP_ANSWER_FAILURE, {
3472
+ correlation_id: meeting.correlationId,
3473
+ locus_id: meeting.locusUrl.split('/').pop(),
3474
+ reason: 'sending answer failed'
3475
+ });
3476
+ });
3477
+
3478
+ [MC.ErrorType.CONFLICT, MC.ErrorType.DOUBLECONFLICT].forEach((errorType) =>
3479
+ it(`handles ERROR message indicating glare condition correctly (errorType=${errorType})`, () => {
3480
+ eventListeners[MC.Event.ROAP_MESSAGE_TO_SEND]({
3481
+ roapMessage: {
3482
+ messageType: 'ERROR',
3483
+ seq: 10,
3484
+ errorType,
3485
+ tieBreaker: 12345,
3486
+ }
3487
+ });
3488
+
3489
+ assert.calledOnce(Metrics.sendBehavioralMetric);
3490
+ assert.calledWithMatch(Metrics.sendBehavioralMetric, BEHAVIORAL_METRICS.ROAP_GLARE_CONDITION, {
3491
+ correlation_id: meeting.correlationId,
3492
+ locus_id: meeting.locusUrl.split('/').pop(),
3493
+ sequence: 10
3494
+ });
3495
+
3496
+ assert.calledOnce(sendRoapErrorStub);
3497
+ assert.calledWith(sendRoapErrorStub, {
3498
+ seq: 10, errorType, mediaId: meeting.mediaId, correlationId: meeting.correlationId
3499
+ });
3500
+ }));
3501
+
3502
+ it('handles ERROR message indicating other errors correctly', () => {
3503
+ eventListeners[MC.Event.ROAP_MESSAGE_TO_SEND]({
3504
+ roapMessage: {
3505
+ messageType: 'ERROR',
3506
+ seq: 10,
3507
+ errorType: MC.ErrorType.FAILED,
3508
+ tieBreaker: 12345,
3509
+ }
3510
+ });
3511
+
3512
+ assert.notCalled(Metrics.sendBehavioralMetric);
3513
+
3514
+ assert.calledOnce(sendRoapErrorStub);
3515
+ assert.calledWith(sendRoapErrorStub, {
3516
+ seq: 10,
3517
+ errorType: MC.ErrorType.FAILED,
3518
+ mediaId: meeting.mediaId,
3519
+ correlationId: meeting.correlationId
3520
+ });
3521
+ });
3323
3522
  });
3324
3523
  });
3325
3524
  describe('#setUpLocusInfoSelfListener', () => {
@@ -3514,13 +3713,13 @@ describe('plugin-meetings', () => {
3514
3713
  });
3515
3714
  });
3516
3715
  describe('#closePeerConnections', () => {
3517
- it('should close the peer connections, and return a promise', async () => {
3518
- PeerConnectionManager.close = sinon.stub().returns(Promise.resolve());
3716
+ it('should close the webrtc media connection, and return a promise', async () => {
3717
+ meeting.mediaProperties.webrtcMediaConnection = {close: sinon.stub()};
3519
3718
  const pcs = meeting.closePeerConnections();
3520
3719
 
3521
3720
  assert.exists(pcs.then);
3522
3721
  await pcs;
3523
- assert.calledOnce(PeerConnectionManager.close);
3722
+ assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.close);
3524
3723
  });
3525
3724
  });
3526
3725
  describe('#unsetPeerConnections', () => {
@@ -3687,13 +3886,6 @@ describe('plugin-meetings', () => {
3687
3886
  });
3688
3887
  });
3689
3888
  });
3690
- describe('#setRoapSeq', () => {
3691
- it('should set the roap seq and return null', () => {
3692
- assert.equal(-1, meeting.roapSeq);
3693
- meeting.setRoapSeq(1);
3694
- assert.equal(meeting.roapSeq, 1);
3695
- });
3696
- });
3697
3889
  describe('#setCorrelationId', () => {
3698
3890
  it('should set the correlationId and return undefined', () => {
3699
3891
  assert.ok(meeting.correlationId);