@webex/plugin-meetings 3.7.0 → 3.8.0-next.10

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 (206) hide show
  1. package/dist/annotation/index.js +17 -0
  2. package/dist/annotation/index.js.map +1 -1
  3. package/dist/breakouts/breakout.js +1 -1
  4. package/dist/breakouts/index.js +1 -1
  5. package/dist/common/errors/join-forbidden-error.js +52 -0
  6. package/dist/common/errors/join-forbidden-error.js.map +1 -0
  7. package/dist/common/errors/{webinar-registration-error.js → join-webinar-error.js} +12 -12
  8. package/dist/common/errors/join-webinar-error.js.map +1 -0
  9. package/dist/common/errors/multistream-not-supported-error.js +53 -0
  10. package/dist/common/errors/multistream-not-supported-error.js.map +1 -0
  11. package/dist/config.js +3 -1
  12. package/dist/config.js.map +1 -1
  13. package/dist/constants.js +69 -6
  14. package/dist/constants.js.map +1 -1
  15. package/dist/index.js +16 -11
  16. package/dist/index.js.map +1 -1
  17. package/dist/interpretation/index.js +4 -4
  18. package/dist/interpretation/index.js.map +1 -1
  19. package/dist/interpretation/siLanguage.js +1 -1
  20. package/dist/locus-info/index.js +14 -3
  21. package/dist/locus-info/index.js.map +1 -1
  22. package/dist/locus-info/selfUtils.js +35 -17
  23. package/dist/locus-info/selfUtils.js.map +1 -1
  24. package/dist/media/MediaConnectionAwaiter.js +1 -0
  25. package/dist/media/MediaConnectionAwaiter.js.map +1 -1
  26. package/dist/media/properties.js +30 -16
  27. package/dist/media/properties.js.map +1 -1
  28. package/dist/meeting/brbState.js +167 -0
  29. package/dist/meeting/brbState.js.map +1 -0
  30. package/dist/meeting/in-meeting-actions.js +13 -1
  31. package/dist/meeting/in-meeting-actions.js.map +1 -1
  32. package/dist/meeting/index.js +1373 -1052
  33. package/dist/meeting/index.js.map +1 -1
  34. package/dist/meeting/locusMediaRequest.js +32 -11
  35. package/dist/meeting/locusMediaRequest.js.map +1 -1
  36. package/dist/meeting/muteState.js +1 -6
  37. package/dist/meeting/muteState.js.map +1 -1
  38. package/dist/meeting/request.js +51 -29
  39. package/dist/meeting/request.js.map +1 -1
  40. package/dist/meeting/request.type.js.map +1 -1
  41. package/dist/meeting/util.js +103 -67
  42. package/dist/meeting/util.js.map +1 -1
  43. package/dist/meeting-info/meeting-info-v2.js +115 -45
  44. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  45. package/dist/meeting-info/utilv2.js +6 -2
  46. package/dist/meeting-info/utilv2.js.map +1 -1
  47. package/dist/meetings/index.js +107 -55
  48. package/dist/meetings/index.js.map +1 -1
  49. package/dist/meetings/meetings.types.js +2 -0
  50. package/dist/meetings/meetings.types.js.map +1 -1
  51. package/dist/meetings/util.js +1 -1
  52. package/dist/meetings/util.js.map +1 -1
  53. package/dist/member/index.js +9 -0
  54. package/dist/member/index.js.map +1 -1
  55. package/dist/member/types.js.map +1 -1
  56. package/dist/member/util.js +39 -28
  57. package/dist/member/util.js.map +1 -1
  58. package/dist/members/util.js +4 -2
  59. package/dist/members/util.js.map +1 -1
  60. package/dist/metrics/constants.js +6 -1
  61. package/dist/metrics/constants.js.map +1 -1
  62. package/dist/multistream/remoteMedia.js +30 -15
  63. package/dist/multistream/remoteMedia.js.map +1 -1
  64. package/dist/multistream/remoteMediaManager.js +40 -8
  65. package/dist/multistream/remoteMediaManager.js.map +1 -1
  66. package/dist/multistream/sendSlotManager.js +24 -0
  67. package/dist/multistream/sendSlotManager.js.map +1 -1
  68. package/dist/reachability/clusterReachability.js +12 -15
  69. package/dist/reachability/clusterReachability.js.map +1 -1
  70. package/dist/reachability/index.js +471 -140
  71. package/dist/reachability/index.js.map +1 -1
  72. package/dist/{rtcMetrics/constants.js → reachability/reachability.types.js} +1 -5
  73. package/dist/reachability/reachability.types.js.map +1 -0
  74. package/dist/reachability/request.js +21 -8
  75. package/dist/reachability/request.js.map +1 -1
  76. package/dist/recording-controller/enums.js +8 -4
  77. package/dist/recording-controller/enums.js.map +1 -1
  78. package/dist/recording-controller/index.js +18 -9
  79. package/dist/recording-controller/index.js.map +1 -1
  80. package/dist/recording-controller/util.js +13 -9
  81. package/dist/recording-controller/util.js.map +1 -1
  82. package/dist/roap/index.js +15 -15
  83. package/dist/roap/index.js.map +1 -1
  84. package/dist/roap/request.js +45 -79
  85. package/dist/roap/request.js.map +1 -1
  86. package/dist/roap/turnDiscovery.js +3 -6
  87. package/dist/roap/turnDiscovery.js.map +1 -1
  88. package/dist/types/annotation/index.d.ts +5 -0
  89. package/dist/types/common/errors/join-forbidden-error.d.ts +15 -0
  90. package/dist/types/common/errors/{webinar-registration-error.d.ts → join-webinar-error.d.ts} +2 -2
  91. package/dist/types/common/errors/multistream-not-supported-error.d.ts +17 -0
  92. package/dist/types/config.d.ts +2 -0
  93. package/dist/types/constants.d.ts +54 -1
  94. package/dist/types/index.d.ts +3 -3
  95. package/dist/types/locus-info/index.d.ts +2 -1
  96. package/dist/types/meeting/brbState.d.ts +54 -0
  97. package/dist/types/meeting/in-meeting-actions.d.ts +12 -0
  98. package/dist/types/meeting/index.d.ts +86 -14
  99. package/dist/types/meeting/locusMediaRequest.d.ts +6 -3
  100. package/dist/types/meeting/request.d.ts +14 -3
  101. package/dist/types/meeting/request.type.d.ts +6 -0
  102. package/dist/types/meeting/util.d.ts +3 -3
  103. package/dist/types/meeting-info/meeting-info-v2.d.ts +30 -5
  104. package/dist/types/meetings/index.d.ts +20 -2
  105. package/dist/types/meetings/meetings.types.d.ts +8 -0
  106. package/dist/types/member/index.d.ts +1 -0
  107. package/dist/types/member/types.d.ts +7 -0
  108. package/dist/types/members/util.d.ts +2 -0
  109. package/dist/types/metrics/constants.d.ts +6 -1
  110. package/dist/types/multistream/remoteMediaManager.d.ts +10 -1
  111. package/dist/types/multistream/sendSlotManager.d.ts +8 -1
  112. package/dist/types/reachability/clusterReachability.d.ts +1 -10
  113. package/dist/types/reachability/index.d.ts +83 -36
  114. package/dist/types/reachability/reachability.types.d.ts +64 -0
  115. package/dist/types/reachability/request.d.ts +5 -1
  116. package/dist/types/recording-controller/enums.d.ts +5 -2
  117. package/dist/types/recording-controller/index.d.ts +1 -0
  118. package/dist/types/recording-controller/util.d.ts +2 -1
  119. package/dist/types/roap/request.d.ts +1 -13
  120. package/dist/webinar/index.js +390 -7
  121. package/dist/webinar/index.js.map +1 -1
  122. package/package.json +23 -22
  123. package/src/annotation/index.ts +16 -0
  124. package/src/common/errors/join-forbidden-error.ts +26 -0
  125. package/src/common/errors/join-webinar-error.ts +24 -0
  126. package/src/common/errors/multistream-not-supported-error.ts +30 -0
  127. package/src/config.ts +2 -0
  128. package/src/constants.ts +62 -3
  129. package/src/index.ts +5 -3
  130. package/src/interpretation/index.ts +3 -3
  131. package/src/locus-info/index.ts +20 -3
  132. package/src/locus-info/selfUtils.ts +24 -6
  133. package/src/media/MediaConnectionAwaiter.ts +2 -0
  134. package/src/media/properties.ts +34 -13
  135. package/src/meeting/brbState.ts +169 -0
  136. package/src/meeting/in-meeting-actions.ts +25 -0
  137. package/src/meeting/index.ts +485 -88
  138. package/src/meeting/locusMediaRequest.ts +38 -12
  139. package/src/meeting/muteState.ts +1 -6
  140. package/src/meeting/request.ts +30 -12
  141. package/src/meeting/request.type.ts +7 -0
  142. package/src/meeting/util.ts +32 -13
  143. package/src/meeting-info/meeting-info-v2.ts +83 -12
  144. package/src/meeting-info/utilv2.ts +17 -3
  145. package/src/meetings/index.ts +79 -20
  146. package/src/meetings/meetings.types.ts +10 -0
  147. package/src/meetings/util.ts +2 -1
  148. package/src/member/index.ts +9 -0
  149. package/src/member/types.ts +8 -0
  150. package/src/member/util.ts +34 -24
  151. package/src/members/util.ts +1 -0
  152. package/src/metrics/constants.ts +6 -1
  153. package/src/multistream/remoteMedia.ts +28 -15
  154. package/src/multistream/remoteMediaManager.ts +32 -10
  155. package/src/multistream/sendSlotManager.ts +31 -0
  156. package/src/reachability/clusterReachability.ts +5 -15
  157. package/src/reachability/index.ts +315 -75
  158. package/src/reachability/reachability.types.ts +85 -0
  159. package/src/reachability/request.ts +55 -31
  160. package/src/recording-controller/enums.ts +5 -2
  161. package/src/recording-controller/index.ts +17 -4
  162. package/src/recording-controller/util.ts +28 -9
  163. package/src/roap/index.ts +14 -13
  164. package/src/roap/request.ts +30 -44
  165. package/src/roap/turnDiscovery.ts +2 -4
  166. package/src/webinar/index.ts +235 -9
  167. package/test/unit/spec/annotation/index.ts +46 -1
  168. package/test/unit/spec/interpretation/index.ts +39 -1
  169. package/test/unit/spec/locus-info/index.js +292 -60
  170. package/test/unit/spec/locus-info/selfConstant.js +7 -0
  171. package/test/unit/spec/locus-info/selfUtils.js +101 -1
  172. package/test/unit/spec/media/properties.ts +15 -0
  173. package/test/unit/spec/meeting/brbState.ts +114 -0
  174. package/test/unit/spec/meeting/in-meeting-actions.ts +15 -1
  175. package/test/unit/spec/meeting/index.js +908 -124
  176. package/test/unit/spec/meeting/locusMediaRequest.ts +111 -66
  177. package/test/unit/spec/meeting/muteState.js +0 -24
  178. package/test/unit/spec/meeting/request.js +3 -26
  179. package/test/unit/spec/meeting/utils.js +73 -28
  180. package/test/unit/spec/meeting-info/meetinginfov2.js +46 -4
  181. package/test/unit/spec/meeting-info/utilv2.js +26 -0
  182. package/test/unit/spec/meetings/index.js +172 -18
  183. package/test/unit/spec/meetings/utils.js +10 -0
  184. package/test/unit/spec/member/util.js +52 -11
  185. package/test/unit/spec/members/utils.js +95 -0
  186. package/test/unit/spec/multistream/remoteMedia.ts +11 -7
  187. package/test/unit/spec/multistream/remoteMediaManager.ts +397 -118
  188. package/test/unit/spec/reachability/clusterReachability.ts +7 -0
  189. package/test/unit/spec/reachability/index.ts +391 -9
  190. package/test/unit/spec/reachability/request.js +48 -12
  191. package/test/unit/spec/recording-controller/index.js +61 -5
  192. package/test/unit/spec/recording-controller/util.js +39 -3
  193. package/test/unit/spec/roap/index.ts +48 -1
  194. package/test/unit/spec/roap/request.ts +51 -109
  195. package/test/unit/spec/roap/turnDiscovery.ts +202 -147
  196. package/test/unit/spec/webinar/index.ts +509 -0
  197. package/dist/common/errors/webinar-registration-error.js.map +0 -1
  198. package/dist/networkQualityMonitor/index.js +0 -227
  199. package/dist/networkQualityMonitor/index.js.map +0 -1
  200. package/dist/rtcMetrics/constants.js.map +0 -1
  201. package/dist/rtcMetrics/index.js +0 -197
  202. package/dist/rtcMetrics/index.js.map +0 -1
  203. package/dist/types/networkQualityMonitor/index.d.ts +0 -70
  204. package/dist/types/rtcMetrics/constants.d.ts +0 -4
  205. package/dist/types/rtcMetrics/index.d.ts +0 -71
  206. package/src/common/errors/webinar-registration-error.ts +0 -27
@@ -1,14 +1,18 @@
1
1
  import 'jsdom-global/register';
2
2
  import sinon from 'sinon';
3
3
  import {assert} from '@webex/test-helper-chai';
4
- import { cloneDeep } from 'lodash';
5
-
4
+ import {cloneDeep} from 'lodash';
5
+ import {EventEmitter} from 'events';
6
6
  import MockWebex from '@webex/test-helper-mock-webex';
7
7
  import Meetings from '@webex/plugin-meetings';
8
- import { LocalMuteRequest, LocusMediaRequest, RoapRequest } from "@webex/plugin-meetings/src/meeting/locusMediaRequest";
8
+ import {
9
+ LocalMuteRequest,
10
+ LocusMediaRequest,
11
+ RoapRequest,
12
+ } from '@webex/plugin-meetings/src/meeting/locusMediaRequest';
9
13
  import testUtils from '../../../utils/testUtils';
10
- import { Defer } from '@webex/common';
11
- import { IP_VERSION } from '../../../../src/constants';
14
+ import {Defer} from '@webex/common';
15
+ import {IP_VERSION} from '../../../../src/constants';
12
16
 
13
17
  describe('LocusMediaRequest.send()', () => {
14
18
  let locusMediaRequest: LocusMediaRequest;
@@ -16,10 +20,10 @@ describe('LocusMediaRequest.send()', () => {
16
20
  let mockWebex;
17
21
 
18
22
  const fakeLocusResponse = {
19
- locus: { something: 'whatever'}
23
+ locus: {something: 'whatever'},
20
24
  };
21
25
 
22
- const exampleRoapRequestBody:RoapRequest = {
26
+ const exampleRoapRequestBody: RoapRequest = {
23
27
  type: 'RoapMessage',
24
28
  mediaId: 'mediaId',
25
29
  selfUrl: 'fakeMeetingSelfUrl',
@@ -31,47 +35,67 @@ describe('LocusMediaRequest.send()', () => {
31
35
  tieBreaker: 0xfffffffe,
32
36
  },
33
37
  reachability: {
34
- 'wjfkm.wjfkm.*': {udp:{reachable: true}, tcp:{reachable:false}},
35
- '1eb65fdf-9643-417f-9974-ad72cae0e10f.59268c12-7a04-4b23-a1a1-4c74be03019a.*': {udp:{reachable: false}, tcp:{reachable:true}},
38
+ 'wjfkm.wjfkm.*': {udp: {reachable: true}, tcp: {reachable: false}},
39
+ '1eb65fdf-9643-417f-9974-ad72cae0e10f.59268c12-7a04-4b23-a1a1-4c74be03019a.*': {
40
+ udp: {reachable: false},
41
+ tcp: {reachable: true},
42
+ },
36
43
  },
37
- joinCookie: {
38
- anycastEntryPoint: 'aws-eu-west-1',
39
- clientIpAddress: 'some ip',
40
- timeShot: '2023-05-23T08:03:49Z',
44
+ clientMediaPreferences: {
45
+ preferTranscoding: false,
46
+ joinCookie: {
47
+ anycastEntryPoint: 'aws-eu-west-1',
48
+ clientIpAddress: 'some ip',
49
+ timeShot: '2023-05-23T08:03:49Z',
50
+ },
51
+ ipver: IP_VERSION.only_ipv4,
52
+ reachability: {
53
+ version: '1',
54
+ result: 'some fake reachability result',
55
+ },
41
56
  },
42
- ipVersion: IP_VERSION.only_ipv4,
43
57
  };
44
58
 
45
- const createExpectedRoapBody = (expectedMessageType, expectedMute:{audioMuted: boolean, videoMuted: boolean}) => {
59
+ const createExpectedRoapBody = (
60
+ expectedMessageType,
61
+ expectedMute: {audioMuted: boolean; videoMuted: boolean}
62
+ ) => {
46
63
  return {
47
- device: { url: 'deviceUrl', deviceType: 'deviceType', regionCode: 'regionCode' },
64
+ device: {url: 'deviceUrl', deviceType: 'deviceType', regionCode: 'regionCode'},
48
65
  correlationId: 'correlationId',
49
66
  localMedias: [
50
67
  {
51
68
  localSdp: `{"audioMuted":${expectedMute.audioMuted},"videoMuted":${expectedMute.videoMuted},"roapMessage":{"messageType":"${expectedMessageType}","sdps":["sdp"],"version":"2","seq":1,"tieBreaker":4294967294},"reachability":{"wjfkm.wjfkm.*":{"udp":{"reachable":true},"tcp":{"reachable":false}},"1eb65fdf-9643-417f-9974-ad72cae0e10f.59268c12-7a04-4b23-a1a1-4c74be03019a.*":{"udp":{"reachable":false},"tcp":{"reachable":true}}}}`,
52
- mediaId: 'mediaId'
53
- }
69
+ mediaId: 'mediaId',
70
+ },
54
71
  ],
55
72
  clientMediaPreferences: {
56
- preferTranscoding: true,
73
+ preferTranscoding: false,
57
74
  ipver: 4,
58
75
  joinCookie: {
59
76
  anycastEntryPoint: 'aws-eu-west-1',
60
77
  clientIpAddress: 'some ip',
61
- timeShot: '2023-05-23T08:03:49Z'
62
- }
63
- }
78
+ timeShot: '2023-05-23T08:03:49Z',
79
+ },
80
+ reachability: {
81
+ version: '1',
82
+ result: 'some fake reachability result',
83
+ },
84
+ },
64
85
  };
65
86
  };
66
87
 
67
- const exampleLocalMuteRequestBody:LocalMuteRequest = {
88
+ const exampleLocalMuteRequestBody: LocalMuteRequest = {
68
89
  type: 'LocalMute',
69
90
  mediaId: 'mediaId',
70
91
  selfUrl: 'fakeMeetingSelfUrl',
71
92
  muteOptions: {},
72
93
  };
73
94
 
74
- const createExpectedLocalMuteBody = (expectedMute:{audioMuted: boolean, videoMuted: boolean}, sequence = undefined) => {
95
+ const createExpectedLocalMuteBody = (
96
+ expectedMute: {audioMuted: boolean; videoMuted: boolean},
97
+ sequence = undefined
98
+ ) => {
75
99
  const body: any = {
76
100
  device: {
77
101
  url: 'deviceUrl',
@@ -87,10 +111,6 @@ describe('LocusMediaRequest.send()', () => {
87
111
  localSdp: `{"audioMuted":${expectedMute.audioMuted},"videoMuted":${expectedMute.videoMuted}}`,
88
112
  },
89
113
  ],
90
- clientMediaPreferences: {
91
- preferTranscoding: true,
92
- ipver: undefined,
93
- },
94
114
  };
95
115
 
96
116
  if (sequence) {
@@ -109,33 +129,37 @@ describe('LocusMediaRequest.send()', () => {
109
129
 
110
130
  mockWebex.internal = {
111
131
  newMetrics: {
112
- submitClientEvent: sinon.stub()
132
+ submitClientEvent: sinon.stub(),
113
133
  },
114
134
  };
115
135
 
116
- locusMediaRequest = new LocusMediaRequest({
117
- device: {
118
- url: 'deviceUrl',
119
- deviceType: 'deviceType',
120
- regionCode: 'regionCode',
136
+ locusMediaRequest = new LocusMediaRequest(
137
+ {
138
+ device: {
139
+ url: 'deviceUrl',
140
+ deviceType: 'deviceType',
141
+ regionCode: 'regionCode',
142
+ },
143
+ correlationId: 'correlationId',
144
+ meetingId: 'meetingId',
145
+ preferTranscoding: true,
121
146
  },
122
- correlationId: 'correlationId',
123
- meetingId: 'meetingId',
124
- preferTranscoding: true,
125
- }, {
126
- parent: mockWebex,
127
- });
147
+ {
148
+ parent: mockWebex,
149
+ }
150
+ );
128
151
  webexRequestStub = sinon.stub(locusMediaRequest, 'request').resolves(fakeLocusResponse);
129
- })
152
+ });
130
153
 
131
- const sendLocalMute = (muteOptions, overrides={}) => locusMediaRequest.send({...exampleLocalMuteRequestBody, ...overrides, muteOptions});
154
+ const sendLocalMute = (muteOptions, overrides = {}) =>
155
+ locusMediaRequest.send({...exampleLocalMuteRequestBody, ...overrides, muteOptions});
132
156
 
133
157
  const sendRoapMessage = (messageType) => {
134
158
  const request = cloneDeep(exampleRoapRequestBody);
135
159
 
136
160
  request.roapMessage.messageType = messageType;
137
161
  return locusMediaRequest.send(request);
138
- }
162
+ };
139
163
 
140
164
  /** Helper function that makes sure the LocusMediaRequest.confluenceState is 'created' */
141
165
  const ensureConfluenceCreated = async () => {
@@ -143,7 +167,7 @@ describe('LocusMediaRequest.send()', () => {
143
167
 
144
168
  webexRequestStub.resetHistory();
145
169
  mockWebex.internal.newMetrics.submitClientEvent.resetHistory();
146
- }
170
+ };
147
171
 
148
172
  const checkMetrics = (expectedMetrics: boolean = true) => {
149
173
  if (expectedMetrics) {
@@ -163,7 +187,7 @@ describe('LocusMediaRequest.send()', () => {
163
187
  } else {
164
188
  assert.notCalled(mockWebex.internal.newMetrics.submitClientEvent);
165
189
  }
166
- }
190
+ };
167
191
 
168
192
  it('sends a roap message', async () => {
169
193
  const result = await sendRoapMessage('OFFER');
@@ -174,6 +198,8 @@ describe('LocusMediaRequest.send()', () => {
174
198
  method: 'PUT',
175
199
  uri: 'fakeMeetingSelfUrl/media',
176
200
  body: createExpectedRoapBody('OFFER', {audioMuted: true, videoMuted: true}),
201
+ upload: sinon.match.instanceOf(EventEmitter),
202
+ download: sinon.match.instanceOf(EventEmitter),
177
203
  });
178
204
 
179
205
  checkMetrics();
@@ -203,6 +229,8 @@ describe('LocusMediaRequest.send()', () => {
203
229
  method: 'PUT',
204
230
  uri: 'fakeMeetingSelfUrl/media',
205
231
  body: createExpectedLocalMuteBody({audioMuted: false, videoMuted: false}),
232
+ upload: sinon.match.instanceOf(EventEmitter),
233
+ download: sinon.match.instanceOf(EventEmitter),
206
234
  });
207
235
 
208
236
  checkMetrics(false);
@@ -221,6 +249,8 @@ describe('LocusMediaRequest.send()', () => {
221
249
  method: 'PUT',
222
250
  uri: 'fakeMeetingSelfUrl/media',
223
251
  body: createExpectedLocalMuteBody({audioMuted: false, videoMuted: false}, sequence),
252
+ upload: sinon.match.instanceOf(EventEmitter),
253
+ download: sinon.match.instanceOf(EventEmitter),
224
254
  });
225
255
  });
226
256
 
@@ -230,15 +260,13 @@ describe('LocusMediaRequest.send()', () => {
230
260
  let result1;
231
261
  let result2;
232
262
 
233
- const promise1 = sendLocalMute({audioMuted: true, videoMuted: false})
234
- .then((result) => {
235
- result1 = result;
236
- });
263
+ const promise1 = sendLocalMute({audioMuted: true, videoMuted: false}).then((result) => {
264
+ result1 = result;
265
+ });
237
266
 
238
- const promise2 = sendLocalMute({audioMuted: false, videoMuted: true})
239
- .then((result) => {
240
- result2 = result;
241
- });
267
+ const promise2 = sendLocalMute({audioMuted: false, videoMuted: true}).then((result) => {
268
+ result2 = result;
269
+ });
242
270
 
243
271
  await testUtils.flushPromises();
244
272
 
@@ -251,6 +279,8 @@ describe('LocusMediaRequest.send()', () => {
251
279
  method: 'PUT',
252
280
  uri: 'fakeMeetingSelfUrl/media',
253
281
  body: createExpectedLocalMuteBody({audioMuted: false, videoMuted: true}),
282
+ upload: sinon.match.instanceOf(EventEmitter),
283
+ download: sinon.match.instanceOf(EventEmitter),
254
284
  });
255
285
 
256
286
  checkMetrics(false);
@@ -270,6 +300,8 @@ describe('LocusMediaRequest.send()', () => {
270
300
  method: 'PUT',
271
301
  uri: 'fakeMeetingSelfUrl/media',
272
302
  body: createExpectedLocalMuteBody({audioMuted: true, videoMuted: false}),
303
+ upload: sinon.match.instanceOf(EventEmitter),
304
+ download: sinon.match.instanceOf(EventEmitter),
273
305
  });
274
306
 
275
307
  checkMetrics(false);
@@ -289,6 +321,8 @@ describe('LocusMediaRequest.send()', () => {
289
321
  method: 'PUT',
290
322
  uri: 'fakeMeetingSelfUrl/media',
291
323
  body: createExpectedRoapBody('OFFER', {audioMuted: true, videoMuted: false}),
324
+ upload: sinon.match.instanceOf(EventEmitter),
325
+ download: sinon.match.instanceOf(EventEmitter),
292
326
  });
293
327
 
294
328
  checkMetrics();
@@ -321,10 +355,10 @@ describe('LocusMediaRequest.send()', () => {
321
355
  * after the processing cycle from which it was called is finished.
322
356
  * This helper function waits for this to happen - it's needed, because we're using
323
357
  * fake timers in these tests
324
- */
358
+ */
325
359
  const ensureQueueProcessingIsStarted = () => {
326
360
  clock.tick(1);
327
- }
361
+ };
328
362
  it('queues requests if there is one already in progress', async () => {
329
363
  results.push(sendRoapMessage('OFFER'));
330
364
 
@@ -335,6 +369,8 @@ describe('LocusMediaRequest.send()', () => {
335
369
  method: 'PUT',
336
370
  uri: 'fakeMeetingSelfUrl/media',
337
371
  body: createExpectedRoapBody('OFFER', {audioMuted: true, videoMuted: true}),
372
+ upload: sinon.match.instanceOf(EventEmitter),
373
+ download: sinon.match.instanceOf(EventEmitter),
338
374
  });
339
375
 
340
376
  webexRequestStub.resetHistory();
@@ -357,6 +393,8 @@ describe('LocusMediaRequest.send()', () => {
357
393
  method: 'PUT',
358
394
  uri: 'fakeMeetingSelfUrl/media',
359
395
  body: createExpectedRoapBody('OK', {audioMuted: true, videoMuted: true}),
396
+ upload: sinon.match.instanceOf(EventEmitter),
397
+ download: sinon.match.instanceOf(EventEmitter),
360
398
  });
361
399
 
362
400
  // promise returned by the first call to send OFFER should be resolved by now
@@ -379,6 +417,8 @@ describe('LocusMediaRequest.send()', () => {
379
417
  method: 'PUT',
380
418
  uri: 'fakeMeetingSelfUrl/media',
381
419
  body: createExpectedRoapBody('OFFER', {audioMuted: false, videoMuted: false}),
420
+ upload: sinon.match.instanceOf(EventEmitter),
421
+ download: sinon.match.instanceOf(EventEmitter),
382
422
  });
383
423
 
384
424
  webexRequestStub.resetHistory();
@@ -403,6 +443,8 @@ describe('LocusMediaRequest.send()', () => {
403
443
  method: 'PUT',
404
444
  uri: 'fakeMeetingSelfUrl/media',
405
445
  body: createExpectedRoapBody('OK', {audioMuted: false, videoMuted: true}),
446
+ upload: sinon.match.instanceOf(EventEmitter),
447
+ download: sinon.match.instanceOf(EventEmitter),
406
448
  });
407
449
 
408
450
  // promise returned by the first call to send OFFER should be resolved by now
@@ -431,16 +473,17 @@ describe('LocusMediaRequest.send()', () => {
431
473
 
432
474
  ensureQueueProcessingIsStarted();
433
475
 
434
- sendLocalMute({audioMuted: false, videoMuted: true})
435
- .then((response) => {
436
- result = response;
437
- });
476
+ sendLocalMute({audioMuted: false, videoMuted: true}).then((response) => {
477
+ result = response;
478
+ });
438
479
 
439
480
  // only roap offer should have been sent so far
440
481
  assert.calledOnceWithExactly(webexRequestStub, {
441
482
  method: 'PUT',
442
483
  uri: 'fakeMeetingSelfUrl/media',
443
484
  body: createExpectedRoapBody('OFFER', {audioMuted: true, videoMuted: true}),
485
+ upload: sinon.match.instanceOf(EventEmitter),
486
+ download: sinon.match.instanceOf(EventEmitter),
444
487
  });
445
488
  assert.equal(result, undefined); // sendLocalMute shouldn't resolve yet, as the request should be queued
446
489
  assert.equal(locusMediaRequest.isConfluenceCreated(), false);
@@ -457,10 +500,12 @@ describe('LocusMediaRequest.send()', () => {
457
500
  method: 'PUT',
458
501
  uri: 'fakeMeetingSelfUrl/media',
459
502
  body: createExpectedLocalMuteBody({audioMuted: false, videoMuted: true}),
503
+ upload: sinon.match.instanceOf(EventEmitter),
504
+ download: sinon.match.instanceOf(EventEmitter),
460
505
  });
461
506
 
462
507
  // check also the result once Locus replies to local mute
463
- const fakeLocusResponse = { response: 'ok'};
508
+ const fakeLocusResponse = {response: 'ok'};
464
509
  requestsToLocus[1].resolve(fakeLocusResponse);
465
510
  await testUtils.flushPromises();
466
511
  assert.deepEqual(result, fakeLocusResponse);
@@ -480,10 +525,9 @@ describe('LocusMediaRequest.send()', () => {
480
525
  assert.equal(locusMediaRequest.isConfluenceCreated(), true);
481
526
 
482
527
  // now send local mute
483
- sendLocalMute({audioMuted: false, videoMuted: true})
484
- .then((response) => {
485
- result = response;
486
- });
528
+ sendLocalMute({audioMuted: false, videoMuted: true}).then((response) => {
529
+ result = response;
530
+ });
487
531
 
488
532
  ensureQueueProcessingIsStarted();
489
533
 
@@ -492,8 +536,9 @@ describe('LocusMediaRequest.send()', () => {
492
536
  method: 'PUT',
493
537
  uri: 'fakeMeetingSelfUrl/media',
494
538
  body: createExpectedLocalMuteBody({audioMuted: false, videoMuted: true}),
539
+ upload: sinon.match.instanceOf(EventEmitter),
540
+ download: sinon.match.instanceOf(EventEmitter),
495
541
  });
496
542
  });
497
-
498
543
  });
499
- })
544
+ });
@@ -113,30 +113,6 @@ describe('plugin-meetings', () => {
113
113
  assert.isTrue(audio.isRemotelyMuted());
114
114
  });
115
115
 
116
- it('does not locally unmute on a server unmute', async () => {
117
- const setServerMutedSpy = meeting.mediaProperties.audioStream.setServerMuted;
118
-
119
- // simulate remote mute
120
- audio.handleServerRemoteMuteUpdate(meeting, true, true);
121
-
122
- assert.isTrue(audio.isRemotelyMuted());
123
- assert.isTrue(audio.isLocallyMuted());
124
-
125
- // mutes local
126
- assert.calledOnceWithExactly(setServerMutedSpy, true, 'remotelyMuted');
127
-
128
- setServerMutedSpy.resetHistory();
129
-
130
- // simulate remote unmute
131
- audio.handleServerRemoteMuteUpdate(meeting, false, true);
132
-
133
- assert.isFalse(audio.isRemotelyMuted());
134
- assert.isTrue(audio.isLocallyMuted());
135
-
136
- // does not unmute local
137
- assert.notCalled(setServerMutedSpy);
138
- });
139
-
140
116
  it('does local audio unmute if localAudioUnmuteRequired is received', async () => {
141
117
  // first we need to have the local stream user muted
142
118
  meeting.mediaProperties.audioStream.userMuted = true;
@@ -196,6 +196,7 @@ describe('plugin-meetings', () => {
196
196
  const permissionToken = 'permission-token';
197
197
  const installationId = 'installationId';
198
198
  const reachability = 'reachability';
199
+ const clientMediaPreferences = 'clientMediaPreferences';
199
200
 
200
201
  await meetingsRequest.joinMeeting({
201
202
  locusUrl,
@@ -204,6 +205,7 @@ describe('plugin-meetings', () => {
204
205
  roapMessage,
205
206
  reachability,
206
207
  permissionToken,
208
+ clientMediaPreferences
207
209
  });
208
210
  const requestParams = meetingsRequest.request.getCall(0).args[0];
209
211
 
@@ -214,6 +216,7 @@ describe('plugin-meetings', () => {
214
216
  assert.equal(requestParams.body.device.countryCode, 'US');
215
217
  assert.equal(requestParams.body.permissionToken, 'permission-token');
216
218
  assert.equal(requestParams.body.device.regionCode, 'WEST-COAST');
219
+ assert.equal(requestParams.body.clientMediaPreferences, 'clientMediaPreferences');
217
220
  assert.include(requestParams.body.device.localIp, '127.0.0');
218
221
  assert.deepEqual(requestParams.body.localMedias, [
219
222
  {localSdp: '{"roapMessage":"roap-message","reachability":"reachability"}'},
@@ -386,32 +389,6 @@ describe('plugin-meetings', () => {
386
389
 
387
390
  assert.deepEqual(requestParams.body.alias, undefined);
388
391
  });
389
-
390
- it('includes joinCookie and ipver correctly', async () => {
391
- const locusUrl = 'locusURL';
392
- const deviceUrl = 'deviceUrl';
393
- const correlationId = 'random-uuid';
394
- const roapMessage = 'roap-message';
395
- const permissionToken = 'permission-token';
396
-
397
- await meetingsRequest.joinMeeting({
398
- locusUrl,
399
- deviceUrl,
400
- correlationId,
401
- roapMessage,
402
- permissionToken,
403
- ipVersion: IP_VERSION.ipv4_and_ipv6,
404
- });
405
- const requestParams = meetingsRequest.request.getCall(0).args[0];
406
-
407
- assert.equal(requestParams.method, 'POST');
408
- assert.equal(requestParams.uri, `${locusUrl}/participant?alternateRedirect=true`);
409
- assert.deepEqual(requestParams.body.clientMediaPreferences, {
410
- joinCookie: {anycastEntryPoint: 'aws-eu-west-1'},
411
- preferTranscoding: true,
412
- ipver: 1,
413
- });
414
- });
415
392
  });
416
393
 
417
394
  describe('#pstn', () => {
@@ -22,6 +22,12 @@ describe('plugin-meetings', () => {
22
22
  meetings: Meetings,
23
23
  },
24
24
  });
25
+
26
+ webex.meetings.reachability = {
27
+ getReachabilityReportToAttachToRoap: sinon.stub().resolves({}),
28
+ getClientMediaPreferences: sinon.stub().resolves({}),
29
+ };
30
+
25
31
  const logger = {
26
32
  info: sandbox.stub(),
27
33
  log: sandbox.stub(),
@@ -39,6 +45,7 @@ describe('plugin-meetings', () => {
39
45
  meeting.cleanupLocalStreams = sinon.stub().returns(Promise.resolve());
40
46
  meeting.closeRemoteStreams = sinon.stub().returns(Promise.resolve());
41
47
  meeting.closePeerConnections = sinon.stub().returns(Promise.resolve());
48
+ meeting.stopPeriodicLogUpload = sinon.stub();
42
49
 
43
50
  meeting.unsetRemoteStreams = sinon.stub();
44
51
  meeting.unsetPeerConnections = sinon.stub();
@@ -64,6 +71,7 @@ describe('plugin-meetings', () => {
64
71
  assert.calledOnce(meeting.cleanupLocalStreams);
65
72
  assert.calledOnce(meeting.closeRemoteStreams);
66
73
  assert.calledOnce(meeting.closePeerConnections);
74
+ assert.calledOnce(meeting.stopPeriodicLogUpload);
67
75
 
68
76
  assert.calledOnce(meeting.unsetRemoteStreams);
69
77
  assert.calledOnce(meeting.unsetPeerConnections);
@@ -157,21 +165,6 @@ describe('plugin-meetings', () => {
157
165
  assert(LoggerProxy.logger.log.called, 'log called');
158
166
  });
159
167
  });
160
-
161
- describe('#handleDeviceLogging', () => {
162
- it('should not log if called without devices', () => {
163
- MeetingUtil.handleDeviceLogging();
164
- assert(!LoggerProxy.logger.log.called, 'log not called');
165
- });
166
-
167
- it('should log device settings', () => {
168
- const mockDevices = [{deviceId: 'device-1'}, {deviceId: 'device-2'}];
169
-
170
- assert(MeetingUtil.handleDeviceLogging, 'is defined');
171
- MeetingUtil.handleDeviceLogging(mockDevices);
172
- assert(LoggerProxy.logger.log.called, 'log called');
173
- });
174
- });
175
168
  });
176
169
 
177
170
  describe('addSequence', () => {
@@ -408,17 +401,39 @@ describe('plugin-meetings', () => {
408
401
  });
409
402
 
410
403
  it('#Should call `meetingRequest.joinMeeting', async () => {
404
+ meeting.isMultistream = true;
405
+
406
+ const FAKE_REACHABILITY_REPORT = {
407
+ id: 'fake reachability report',
408
+ };
409
+ const FAKE_CLIENT_MEDIA_PREFERENCES = {
410
+ id: 'fake client media preferences',
411
+ };
412
+
413
+ webex.meetings.reachability.getReachabilityReportToAttachToRoap.resolves(FAKE_REACHABILITY_REPORT);
414
+ webex.meetings.reachability.getClientMediaPreferences.resolves(FAKE_CLIENT_MEDIA_PREFERENCES);
415
+
416
+ sinon
417
+ .stub(webex.internal.device.ipNetworkDetector, 'supportsIpV4')
418
+ .get(() => true);
419
+ sinon
420
+ .stub(webex.internal.device.ipNetworkDetector, 'supportsIpV6')
421
+ .get(() => true);
422
+
411
423
  await MeetingUtil.joinMeeting(meeting, {
412
424
  reachability: 'reachability',
413
425
  roapMessage: 'roapMessage',
414
426
  });
415
427
 
428
+ assert.calledOnceWithExactly(webex.meetings.reachability.getReachabilityReportToAttachToRoap);
429
+ assert.calledOnceWithExactly(webex.meetings.reachability.getClientMediaPreferences, meeting.isMultistream, IP_VERSION.ipv4_and_ipv6);
430
+
416
431
  assert.calledOnce(meeting.meetingRequest.joinMeeting);
417
432
  const parameter = meeting.meetingRequest.joinMeeting.getCall(0).args[0];
418
433
 
419
434
  assert.equal(parameter.inviteeAddress, 'meetingJoinUrl');
420
- assert.equal(parameter.preferTranscoding, true);
421
- assert.equal(parameter.reachability, 'reachability');
435
+ assert.equal(parameter.reachability, FAKE_REACHABILITY_REPORT);
436
+ assert.equal(parameter.clientMediaPreferences, FAKE_CLIENT_MEDIA_PREFERENCES);
422
437
  assert.equal(parameter.roapMessage, 'roapMessage');
423
438
 
424
439
  assert.calledOnce(meeting.setLocus)
@@ -445,6 +460,40 @@ describe('plugin-meetings', () => {
445
460
  });
446
461
  });
447
462
 
463
+ it('should handle failed reachability report retrieval', async () => {
464
+ webex.meetings.reachability.getReachabilityReportToAttachToRoap.rejects(
465
+ new Error('fake error')
466
+ );
467
+ await MeetingUtil.joinMeeting(meeting, {});
468
+ // Verify meeting join still proceeds
469
+ assert.calledOnce(meeting.meetingRequest.joinMeeting);
470
+ });
471
+
472
+ it('should not attach reachability if there is no roap message', async () => {
473
+ await MeetingUtil.joinMeeting(meeting, {});
474
+
475
+ assert.notCalled(webex.meetings.reachability.getReachabilityReportToAttachToRoap);
476
+ assert.calledOnce(meeting.meetingRequest.joinMeeting);
477
+
478
+ const parameter = meeting.meetingRequest.joinMeeting.getCall(0).args[0];
479
+ assert.isUndefined(parameter.reachability);
480
+ assert.isUndefined(parameter.roapMessage);
481
+ });
482
+
483
+ it('should handle failed clientMediaPreferences retrieval', async () => {
484
+ webex.meetings.reachability.getClientMediaPreferences.rejects(new Error('fake error'));
485
+ meeting.isMultistream = true;
486
+ await MeetingUtil.joinMeeting(meeting, {});
487
+ // Verify meeting join still proceeds
488
+ assert.calledOnce(meeting.meetingRequest.joinMeeting);
489
+ const parameter = meeting.meetingRequest.joinMeeting.getCall(0).args[0];
490
+ assert.deepEqual(parameter.clientMediaPreferences, {
491
+ preferTranscoding: false,
492
+ ipver: 0,
493
+ joinCookie: undefined,
494
+ });
495
+ });
496
+
448
497
  it('#Should call meetingRequest.joinMeeting with breakoutsSupported=true when passed in as true', async () => {
449
498
  await MeetingUtil.joinMeeting(meeting, {
450
499
  breakoutsSupported: true,
@@ -480,17 +529,6 @@ describe('plugin-meetings', () => {
480
529
  assert.deepEqual(parameter.deviceCapabilities, ['TEST']);
481
530
  });
482
531
 
483
- it('#Should call meetingRequest.joinMeeting with preferTranscoding=false when multistream is enabled', async () => {
484
- meeting.isMultistream = true;
485
- await MeetingUtil.joinMeeting(meeting, {});
486
-
487
- assert.calledOnce(meeting.meetingRequest.joinMeeting);
488
- const parameter = meeting.meetingRequest.joinMeeting.getCall(0).args[0];
489
-
490
- assert.equal(parameter.inviteeAddress, 'meetingJoinUrl');
491
- assert.equal(parameter.preferTranscoding, false);
492
- });
493
-
494
532
  it('#Should fallback sipUrl if meetingJoinUrl does not exists', async () => {
495
533
  meeting.meetingJoinUrl = undefined;
496
534
  meeting.sipUri = 'sipUri';
@@ -733,6 +771,13 @@ describe('plugin-meetings', () => {
733
771
  });
734
772
  });
735
773
 
774
+ describe('canStartBreakout', () => {
775
+ it('works as expected', () => {
776
+ assert.deepEqual(MeetingUtil.canStartBreakout(['DISABLE_BREAKOUT_START']), false);
777
+ assert.deepEqual(MeetingUtil.canStartBreakout([]), true);
778
+ });
779
+ });
780
+
736
781
  describe('canBroadcastMessageToBreakout', () => {
737
782
  it('works as expected', () => {
738
783
  assert.deepEqual(