@webex/plugin-meetings 1.151.7 → 1.153.1

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 (42) hide show
  1. package/dist/common/errors/captcha-error.js +64 -0
  2. package/dist/common/errors/captcha-error.js.map +1 -0
  3. package/dist/common/errors/password-error.js +64 -0
  4. package/dist/common/errors/password-error.js.map +1 -0
  5. package/dist/config.js +2 -1
  6. package/dist/config.js.map +1 -1
  7. package/dist/constants.js +33 -1
  8. package/dist/constants.js.map +1 -1
  9. package/dist/meeting/index.js +668 -432
  10. package/dist/meeting/index.js.map +1 -1
  11. package/dist/meeting/request.js +59 -32
  12. package/dist/meeting/request.js.map +1 -1
  13. package/dist/meeting/util.js +12 -0
  14. package/dist/meeting/util.js.map +1 -1
  15. package/dist/meeting-info/meeting-info-v2.js +142 -8
  16. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  17. package/dist/meeting-info/utilv2.js +12 -1
  18. package/dist/meeting-info/utilv2.js.map +1 -1
  19. package/dist/meetings/index.js +47 -19
  20. package/dist/meetings/index.js.map +1 -1
  21. package/dist/peer-connection-manager/index.js +16 -1
  22. package/dist/peer-connection-manager/index.js.map +1 -1
  23. package/dist/peer-connection-manager/util.js +28 -0
  24. package/dist/peer-connection-manager/util.js.map +1 -0
  25. package/package.json +5 -5
  26. package/src/common/errors/captcha-error.js +21 -0
  27. package/src/common/errors/password-error.js +21 -0
  28. package/src/config.js +2 -1
  29. package/src/constants.js +24 -0
  30. package/src/meeting/index.js +173 -3
  31. package/src/meeting/request.js +27 -0
  32. package/src/meeting/util.js +15 -4
  33. package/src/meeting-info/meeting-info-v2.js +67 -3
  34. package/src/meeting-info/utilv2.js +12 -1
  35. package/src/meetings/index.js +27 -8
  36. package/src/peer-connection-manager/index.js +19 -2
  37. package/src/peer-connection-manager/util.js +19 -0
  38. package/test/unit/spec/meeting/index.js +294 -3
  39. package/test/unit/spec/meeting-info/meetinginfov2.js +74 -1
  40. package/test/unit/spec/meetings/index.js +29 -0
  41. package/test/unit/spec/peerconnection-manager/index.js +66 -0
  42. package/test/unit/spec/peerconnection-manager/utils.js +25 -0
@@ -31,8 +31,11 @@ import {
31
31
  FLOOR_ACTION,
32
32
  SHARE_STATUS,
33
33
  METRICS_OPERATIONAL_MEASURES,
34
+ MEETING_INFO_FAILURE_REASON,
35
+ PASSWORD_STATUS,
34
36
  EVENTS,
35
- EVENT_TRIGGERS
37
+ EVENT_TRIGGERS,
38
+ _SIP_URI_
36
39
  } from '@webex/plugin-meetings/src/constants';
37
40
 
38
41
  import {
@@ -43,8 +46,11 @@ import {
43
46
  } from '../../../../src/common/errors/webex-errors';
44
47
  import WebExMeetingsErrors from '../../../../src/common/errors/webex-meetings-error';
45
48
  import ParameterError from '../../../../src/common/errors/parameter';
49
+ import PasswordError from '../../../../src/common/errors/password-error';
50
+ import CaptchaError from '../../../../src/common/errors/captcha-error';
46
51
  import DefaultSDKConfig from '../../../../src/config';
47
52
  import testUtils from '../../../utils/testUtils';
53
+ import {MeetingInfoV2CaptchaError, MeetingInfoV2PasswordError} from '../../../../src/meeting-info/meeting-info-v2';
48
54
 
49
55
  const {
50
56
  getBrowserName
@@ -98,7 +104,8 @@ describe('plugin-meetings', () => {
98
104
  },
99
105
  mediaSettings: {},
100
106
  metrics: {},
101
- stats: {}
107
+ stats: {},
108
+ experimental: {enableUnifiedMeetings: true}
102
109
  }
103
110
  }
104
111
  });
@@ -173,6 +180,9 @@ describe('plugin-meetings', () => {
173
180
  assert.instanceOf(meeting.meetingRequest, MeetingRequest);
174
181
  assert.instanceOf(meeting.locusInfo, LocusInfo);
175
182
  assert.instanceOf(meeting.mediaProperties, MediaProperties);
183
+ assert.equal(meeting.passwordStatus, PASSWORD_STATUS.UNKNOWN);
184
+ assert.equal(meeting.requiredCaptcha, null);
185
+ assert.equal(meeting.meetingInfoFailureReason, undefined);
176
186
  });
177
187
  });
178
188
  describe('#invite', () => {
@@ -632,7 +642,7 @@ describe('plugin-meetings', () => {
632
642
  });
633
643
 
634
644
  it('should create new correlation ID when already joined', async () => {
635
- meeting.isCreated = false;
645
+ meeting.hasJoinedOnce = true;
636
646
  await meeting.join();
637
647
  sinon.assert.called(meeting.setCorrelationId);
638
648
  });
@@ -653,6 +663,14 @@ describe('plugin-meetings', () => {
653
663
  });
654
664
  });
655
665
  });
666
+ it('should fail if password is required', async () => {
667
+ meeting.passwordStatus = PASSWORD_STATUS.REQUIRED;
668
+ await assert.isRejected(meeting.join(), PasswordError);
669
+ });
670
+ it('should fail if captcha is required', async () => {
671
+ meeting.requiredCaptcha = {captchaId: 'aaa'};
672
+ await assert.isRejected(meeting.join(), CaptchaError);
673
+ });
656
674
  describe('total failure', () => {
657
675
  beforeEach(() => {
658
676
  MeetingUtil.isPinOrGuest = sinon.stub().returns(false);
@@ -2010,6 +2028,279 @@ describe('plugin-meetings', () => {
2010
2028
  });
2011
2029
  });
2012
2030
 
2031
+ describe('#fetchMeetingInfo', () => {
2032
+ const FAKE_DESTINATION = 'something@somecompany.com';
2033
+ const FAKE_TYPE = _SIP_URI_;
2034
+ const FAKE_PASSWORD = '123abc';
2035
+ const FAKE_CAPTCHA_CODE = 'a1b2c3XYZ';
2036
+ const FAKE_CAPTCHA_ID = '987654321';
2037
+ const FAKE_CAPTCHA_IMAGE_URL = 'http://captchaimage';
2038
+ const FAKE_CAPTCHA_AUDIO_URL = 'http://captchaaudio';
2039
+ const FAKE_CAPTCHA_REFRESH_URL = 'http://captcharefresh';
2040
+ const FAKE_MEETING_INFO = {
2041
+ conversationUrl: 'some_convo_url',
2042
+ locusUrl: 'some_locus_url',
2043
+ sipUrl: 'some_sip_url', // or sipMeetingUri
2044
+ meetingNumber: '123456', // this.config.experimental.enableUnifiedMeetings
2045
+ hostId: 'some_host_id' // this.owner;
2046
+ };
2047
+ const FAKE_SDK_CAPTCHA_INFO = {
2048
+ captchaId: FAKE_CAPTCHA_ID,
2049
+ verificationImageURL: FAKE_CAPTCHA_IMAGE_URL,
2050
+ verificationAudioURL: FAKE_CAPTCHA_AUDIO_URL,
2051
+ refreshURL: FAKE_CAPTCHA_REFRESH_URL
2052
+ };
2053
+ const FAKE_WBXAPPAPI_CAPTCHA_INFO = {
2054
+ captchaID: `${FAKE_CAPTCHA_ID}-2`,
2055
+ verificationImageURL: `${FAKE_CAPTCHA_IMAGE_URL}-2`,
2056
+ verificationAudioURL: `${FAKE_CAPTCHA_AUDIO_URL}-2`,
2057
+ refreshURL: `${FAKE_CAPTCHA_REFRESH_URL}-2`
2058
+ };
2059
+
2060
+
2061
+ it('calls meetingInfoProvider with all the right parameters and parses the result', async () => {
2062
+ meeting.attrs.meetingInfoProvider = {fetchMeetingInfo: sinon.stub().resolves({body: FAKE_MEETING_INFO})};
2063
+ meeting.requiredCaptcha = FAKE_SDK_CAPTCHA_INFO;
2064
+ await meeting.fetchMeetingInfo({
2065
+ destination: FAKE_DESTINATION, type: FAKE_TYPE, password: FAKE_PASSWORD, captchaCode: FAKE_CAPTCHA_CODE
2066
+ });
2067
+
2068
+ assert.calledWith(meeting.attrs.meetingInfoProvider.fetchMeetingInfo, FAKE_DESTINATION, FAKE_TYPE, FAKE_PASSWORD, {code: FAKE_CAPTCHA_CODE, id: FAKE_CAPTCHA_ID});
2069
+
2070
+ assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
2071
+ assert.equal(meeting.passwordStatus, PASSWORD_STATUS.NOT_REQUIRED);
2072
+ assert.equal(meeting.meetingInfoFailureReason, MEETING_INFO_FAILURE_REASON.NONE);
2073
+ assert.equal(meeting.requiredCaptcha, null);
2074
+ });
2075
+
2076
+ it('fails if captchaCode is provided when captcha not needed', async () => {
2077
+ meeting.attrs.meetingInfoProvider = {fetchMeetingInfo: sinon.stub().resolves({body: FAKE_MEETING_INFO})};
2078
+ meeting.requiredCaptcha = null;
2079
+
2080
+ await assert.isRejected(meeting.fetchMeetingInfo({
2081
+ destination: FAKE_DESTINATION, type: FAKE_TYPE, captchaCode: FAKE_CAPTCHA_CODE
2082
+ }), Error, 'fetchMeetingInfo() called with captchaCode when captcha was not required');
2083
+
2084
+ assert.notCalled(meeting.attrs.meetingInfoProvider.fetchMeetingInfo);
2085
+ });
2086
+
2087
+ it('fails if password is provided when not required', async () => {
2088
+ meeting.attrs.meetingInfoProvider = {fetchMeetingInfo: sinon.stub().resolves({body: FAKE_MEETING_INFO})};
2089
+ meeting.passwordStatus = PASSWORD_STATUS.NOT_REQUIRED;
2090
+
2091
+ await assert.isRejected(meeting.fetchMeetingInfo({
2092
+ destination: FAKE_DESTINATION, type: FAKE_TYPE, password: FAKE_PASSWORD
2093
+ }), Error, 'fetchMeetingInfo() called with password when password was not required');
2094
+
2095
+ assert.notCalled(meeting.attrs.meetingInfoProvider.fetchMeetingInfo);
2096
+ });
2097
+
2098
+ it('handles meetingInfoProvider requiring password', async () => {
2099
+ meeting.attrs.meetingInfoProvider = {
2100
+ fetchMeetingInfo: sinon.stub().throws(new MeetingInfoV2PasswordError(403004, FAKE_MEETING_INFO))
2101
+ };
2102
+
2103
+ await assert.isRejected(meeting.fetchMeetingInfo({
2104
+ destination: FAKE_DESTINATION, type: FAKE_TYPE
2105
+ }), PasswordError);
2106
+
2107
+ assert.calledWith(meeting.attrs.meetingInfoProvider.fetchMeetingInfo, FAKE_DESTINATION, FAKE_TYPE, null, null);
2108
+
2109
+ assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
2110
+ assert.equal(meeting.meetingInfoFailureReason, MEETING_INFO_FAILURE_REASON.WRONG_PASSWORD);
2111
+ assert.equal(meeting.requiredCaptcha, null);
2112
+ assert.equal(meeting.passwordStatus, PASSWORD_STATUS.REQUIRED);
2113
+ });
2114
+
2115
+ it('handles meetingInfoProvider requiring captcha because of wrong password', async () => {
2116
+ meeting.attrs.meetingInfoProvider = {
2117
+ fetchMeetingInfo: sinon.stub().throws(new MeetingInfoV2CaptchaError(423005, FAKE_SDK_CAPTCHA_INFO))
2118
+ };
2119
+ meeting.requiredCaptcha = null;
2120
+
2121
+ await assert.isRejected(meeting.fetchMeetingInfo({
2122
+ destination: FAKE_DESTINATION, type: FAKE_TYPE, password: 'aaa'
2123
+ }), CaptchaError);
2124
+
2125
+ assert.calledWith(meeting.attrs.meetingInfoProvider.fetchMeetingInfo, FAKE_DESTINATION, FAKE_TYPE, 'aaa', null);
2126
+
2127
+ assert.deepEqual(meeting.meetingInfo, {});
2128
+ assert.equal(meeting.meetingInfoFailureReason, MEETING_INFO_FAILURE_REASON.WRONG_PASSWORD);
2129
+ assert.equal(meeting.passwordStatus, PASSWORD_STATUS.REQUIRED);
2130
+ assert.deepEqual(meeting.requiredCaptcha, {
2131
+ captchaId: FAKE_CAPTCHA_ID,
2132
+ verificationImageURL: FAKE_CAPTCHA_IMAGE_URL,
2133
+ verificationAudioURL: FAKE_CAPTCHA_AUDIO_URL,
2134
+ refreshURL: FAKE_CAPTCHA_REFRESH_URL
2135
+ });
2136
+ });
2137
+
2138
+ it('handles meetingInfoProvider requiring captcha because of wrong captcha', async () => {
2139
+ meeting.attrs.meetingInfoProvider = {
2140
+ fetchMeetingInfo: sinon.stub().throws(new MeetingInfoV2CaptchaError(423005, FAKE_SDK_CAPTCHA_INFO))
2141
+ };
2142
+ meeting.requiredCaptcha = FAKE_SDK_CAPTCHA_INFO;
2143
+
2144
+ await assert.isRejected(meeting.fetchMeetingInfo({
2145
+ destination: FAKE_DESTINATION, type: FAKE_TYPE, password: 'aaa', captchaCode: 'bbb'
2146
+ }), CaptchaError);
2147
+
2148
+ assert.calledWith(meeting.attrs.meetingInfoProvider.fetchMeetingInfo, FAKE_DESTINATION, FAKE_TYPE, 'aaa', {code: 'bbb', id: FAKE_CAPTCHA_ID});
2149
+
2150
+ assert.deepEqual(meeting.meetingInfo, {});
2151
+ assert.equal(meeting.meetingInfoFailureReason, MEETING_INFO_FAILURE_REASON.WRONG_CAPTCHA);
2152
+ assert.equal(meeting.passwordStatus, PASSWORD_STATUS.REQUIRED);
2153
+ assert.deepEqual(meeting.requiredCaptcha, FAKE_SDK_CAPTCHA_INFO);
2154
+ });
2155
+
2156
+ it('handles successful response when good password is passed', async () => {
2157
+ meeting.attrs.meetingInfoProvider = {
2158
+ fetchMeetingInfo: sinon.stub().resolves(
2159
+ {
2160
+ statusCode: 200,
2161
+ body: FAKE_MEETING_INFO
2162
+ }
2163
+ )
2164
+ };
2165
+ meeting.passwordStatus = PASSWORD_STATUS.REQUIRED;
2166
+
2167
+ await meeting.fetchMeetingInfo({
2168
+ destination: FAKE_DESTINATION, type: FAKE_TYPE, password: 'aaa'
2169
+ });
2170
+
2171
+ assert.calledWith(meeting.attrs.meetingInfoProvider.fetchMeetingInfo, FAKE_DESTINATION, FAKE_TYPE, 'aaa', null);
2172
+
2173
+ assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
2174
+ assert.equal(meeting.meetingInfoFailureReason, MEETING_INFO_FAILURE_REASON.NONE);
2175
+ assert.equal(meeting.passwordStatus, PASSWORD_STATUS.VERIFIED);
2176
+ assert.equal(meeting.requiredCaptcha, null);
2177
+ });
2178
+
2179
+ it('refreshes captcha when captcha was required and we received 403 error code', async () => {
2180
+ const refreshedCaptcha = {
2181
+ captchaID: FAKE_WBXAPPAPI_CAPTCHA_INFO.captchaID,
2182
+ verificationImageURL: FAKE_WBXAPPAPI_CAPTCHA_INFO.verificationImageURL,
2183
+ verificationAudioURL: FAKE_WBXAPPAPI_CAPTCHA_INFO.verificationAudioURL
2184
+ };
2185
+
2186
+ meeting.attrs.meetingInfoProvider = {
2187
+ fetchMeetingInfo: sinon.stub().throws(new MeetingInfoV2PasswordError(403004, FAKE_MEETING_INFO))
2188
+ };
2189
+ meeting.meetingRequest.refreshCaptcha = sinon.stub().returns(Promise.resolve(
2190
+ {
2191
+ body: refreshedCaptcha
2192
+ }
2193
+ ));
2194
+ meeting.passwordStatus = PASSWORD_STATUS.REQUIRED;
2195
+ meeting.requiredCaptcha = FAKE_SDK_CAPTCHA_INFO;
2196
+
2197
+ await assert.isRejected(meeting.fetchMeetingInfo({
2198
+ destination: FAKE_DESTINATION, type: FAKE_TYPE, password: 'aaa', captchaCode: 'bbb'
2199
+ }));
2200
+
2201
+ assert.calledWith(meeting.attrs.meetingInfoProvider.fetchMeetingInfo, FAKE_DESTINATION, FAKE_TYPE, 'aaa', {code: 'bbb', id: FAKE_CAPTCHA_ID});
2202
+
2203
+ assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
2204
+ assert.equal(meeting.meetingInfoFailureReason, MEETING_INFO_FAILURE_REASON.WRONG_PASSWORD);
2205
+ assert.equal(meeting.passwordStatus, PASSWORD_STATUS.REQUIRED);
2206
+ assert.deepEqual(meeting.requiredCaptcha, {
2207
+ captchaId: refreshedCaptcha.captchaID,
2208
+ verificationImageURL: refreshedCaptcha.verificationImageURL,
2209
+ verificationAudioURL: refreshedCaptcha.verificationAudioURL,
2210
+ refreshURL: FAKE_SDK_CAPTCHA_INFO.refreshURL // refresh url doesn't change
2211
+ });
2212
+ });
2213
+ });
2214
+
2215
+ describe('#refreshCaptcha', () => {
2216
+ it('fails if no captcha required', async () => {
2217
+ assert.isRejected(meeting.refreshCaptcha(), Error);
2218
+ });
2219
+ it('sends correct request to captcha service refresh url', async () => {
2220
+ const REFRESH_URL = 'https://something.webex.com/captchaservice/v1/captchas/refresh?blablabla=something&captchaID=xxx';
2221
+ const EXPECTED_REFRESH_URL = 'https://something.webex.com/captchaservice/v1/captchas/refresh?blablabla=something&captchaID=xxx&siteFullName=something.webex.com';
2222
+
2223
+ const FAKE_SDK_CAPTCHA_INFO = {
2224
+ captchaId: 'some id',
2225
+ verificationImageURL: 'some image url',
2226
+ verificationAudioURL: 'some audio url',
2227
+ refreshURL: REFRESH_URL
2228
+ };
2229
+
2230
+ const FAKE_REFRESHED_CAPTCHA = {
2231
+ captchaID: 'some id',
2232
+ verificationImageURL: 'some image url',
2233
+ verificationAudioURL: 'some audio url'
2234
+ };
2235
+
2236
+ // setup the meeting so that a captcha is required
2237
+ meeting.attrs.meetingInfoProvider = {
2238
+ fetchMeetingInfo: sinon.stub().throws(new MeetingInfoV2CaptchaError(423005, FAKE_SDK_CAPTCHA_INFO))
2239
+ };
2240
+
2241
+ await assert.isRejected(meeting.fetchMeetingInfo({
2242
+ destination: 'something@somecompany.com', type: _SIP_URI_, password: ''
2243
+ }), CaptchaError);
2244
+
2245
+ assert.deepEqual(meeting.requiredCaptcha, FAKE_SDK_CAPTCHA_INFO);
2246
+ meeting.meetingRequest.refreshCaptcha = sinon.stub().returns(Promise.resolve({body: FAKE_REFRESHED_CAPTCHA}));
2247
+
2248
+ // test the captcha refresh
2249
+ await meeting.refreshCaptcha();
2250
+
2251
+ assert.calledWith(meeting.meetingRequest.refreshCaptcha,
2252
+ {
2253
+ captchaRefreshUrl: EXPECTED_REFRESH_URL,
2254
+ captchaId: FAKE_SDK_CAPTCHA_INFO.captchaId
2255
+ });
2256
+
2257
+ assert.deepEqual(meeting.requiredCaptcha, {
2258
+ captchaId: FAKE_REFRESHED_CAPTCHA.captchaID,
2259
+ verificationImageURL: FAKE_REFRESHED_CAPTCHA.verificationImageURL,
2260
+ verificationAudioURL: FAKE_REFRESHED_CAPTCHA.verificationAudioURL,
2261
+ refreshURL: FAKE_SDK_CAPTCHA_INFO.refreshURL // refresh url doesn't change
2262
+ });
2263
+ });
2264
+ });
2265
+
2266
+ describe('#verifyPassword', () => {
2267
+ it('calls fetchMeetingInfo() with the passed password and captcha code', async () => {
2268
+ // simulate successful case
2269
+ meeting.fetchMeetingInfo = sinon.stub().resolves();
2270
+ const result = await meeting.verifyPassword('password', 'captcha id');
2271
+
2272
+ assert.equal(result.isPasswordValid, true);
2273
+ assert.equal(result.requiredCaptcha, null);
2274
+ assert.equal(result.failureReason, MEETING_INFO_FAILURE_REASON.NONE);
2275
+ });
2276
+ it('handles PasswordError returned by fetchMeetingInfo', async () => {
2277
+ meeting.fetchMeetingInfo = sinon.stub().callsFake(() => {
2278
+ meeting.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.WRONG_PASSWORD;
2279
+
2280
+ return Promise.reject(new PasswordError());
2281
+ });
2282
+ const result = await meeting.verifyPassword('password', 'captcha id');
2283
+
2284
+ assert.equal(result.isPasswordValid, false);
2285
+ assert.equal(result.requiredCaptcha, null);
2286
+ assert.equal(result.failureReason, MEETING_INFO_FAILURE_REASON.WRONG_PASSWORD);
2287
+ });
2288
+ it('handles CaptchaError returned by fetchMeetingInfo', async () => {
2289
+ const FAKE_CAPTCHA = {captchaId: 'some catcha id...'};
2290
+
2291
+ meeting.fetchMeetingInfo = sinon.stub().callsFake(() => {
2292
+ meeting.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.WRONG_CAPTCHA;
2293
+ meeting.requiredCaptcha = FAKE_CAPTCHA;
2294
+
2295
+ return Promise.reject(new CaptchaError());
2296
+ });
2297
+ const result = await meeting.verifyPassword('password', 'captcha id');
2298
+
2299
+ assert.equal(result.isPasswordValid, false);
2300
+ assert.deepEqual(result.requiredCaptcha, FAKE_CAPTCHA);
2301
+ assert.equal(result.failureReason, MEETING_INFO_FAILURE_REASON.WRONG_CAPTCHA);
2302
+ });
2303
+ });
2013
2304
  describe('#mediaNegotiatedEvent', () => {
2014
2305
  it('should have #mediaNegotiatedEvent', () => {
2015
2306
  assert.exists(meeting.mediaNegotiatedEvent);
@@ -12,7 +12,7 @@ import {
12
12
  _MEETING_ID_,
13
13
  _PERSONAL_ROOM_
14
14
  } from '@webex/plugin-meetings/src/constants';
15
- import MeetingInfo from '@webex/plugin-meetings/src/meeting-info/meeting-info-v2';
15
+ import MeetingInfo, {MeetingInfoV2PasswordError, MeetingInfoV2CaptchaError} from '@webex/plugin-meetings/src/meeting-info/meeting-info-v2';
16
16
  import MeetingInfoUtil from '@webex/plugin-meetings/src/meeting-info/utilv2';
17
17
 
18
18
  describe('plugin-meetings', () => {
@@ -80,6 +80,79 @@ describe('plugin-meetings', () => {
80
80
  MeetingInfoUtil.getDestinationType.restore();
81
81
  MeetingInfoUtil.getRequestBody.restore();
82
82
  });
83
+
84
+ it('should fetch meeting info with provided password and captcha code', async () => {
85
+ await meetingInfo.fetchMeetingInfo('1234323', _MEETING_ID_, 'abc', {id: '999', code: 'aabbcc11'});
86
+
87
+ assert.calledWith(webex.request, {
88
+ method: 'POST',
89
+ service: 'webex-appapi-service',
90
+ resource: 'meetingInfo',
91
+ body: {
92
+ supportHostKey: true,
93
+ meetingKey: '1234323',
94
+ password: 'abc',
95
+ captchaID: '999',
96
+ captchaVerifyCode: 'aabbcc11'
97
+ }
98
+ });
99
+ });
100
+
101
+ it('should throw MeetingInfoV2PasswordError for 403 response', async () => {
102
+ const FAKE_MEETING_INFO = {blablabla: 'some_fake_meeting_info'};
103
+
104
+ webex.request = sinon.stub().rejects({statusCode: 403, body: {code: 403000, data: {meetingInfo: FAKE_MEETING_INFO}}});
105
+
106
+ try {
107
+ await meetingInfo.fetchMeetingInfo('1234323', _MEETING_ID_, 'abc', {id: '999', code: 'aabbcc11'});
108
+ assert.fail('fetchMeetingInfo should have thrown, but has not done that');
109
+ }
110
+ catch (err) {
111
+ assert.instanceOf(err, MeetingInfoV2PasswordError);
112
+ assert.deepEqual(err.meetingInfo, FAKE_MEETING_INFO);
113
+ assert.equal(err.wbxAppApiCode, 403000);
114
+ }
115
+ });
116
+
117
+ describe('should throw MeetingInfoV2CaptchaError for 423 response', () => {
118
+ const runTest = async (wbxAppApiCode, expectedIsPasswordRequired) => {
119
+ webex.request = sinon.stub().rejects(
120
+ {
121
+ statusCode: 423,
122
+ body: {
123
+ code: wbxAppApiCode,
124
+ captchaID: 'fake_captcha_id',
125
+ verificationImageURL: 'fake_image_url',
126
+ verificationAudioURL: 'fake_audio_url',
127
+ refreshURL: 'fake_refresh_url'
128
+ }
129
+ }
130
+ );
131
+ try {
132
+ await meetingInfo.fetchMeetingInfo('1234323', _MEETING_ID_, 'abc', {id: '999', code: 'aabbcc11'});
133
+ assert.fail('fetchMeetingInfo should have thrown, but has not done that');
134
+ }
135
+ catch (err) {
136
+ assert.instanceOf(err, MeetingInfoV2CaptchaError);
137
+ assert.deepEqual(err.captchaInfo, {
138
+ captchaId: 'fake_captcha_id',
139
+ verificationImageURL: 'fake_image_url',
140
+ verificationAudioURL: 'fake_audio_url',
141
+ refreshURL: 'fake_refresh_url'
142
+ });
143
+ assert.equal(err.wbxAppApiCode, wbxAppApiCode);
144
+ assert.equal(err.isPasswordRequired, expectedIsPasswordRequired);
145
+ }
146
+ };
147
+
148
+ it('should throw MeetingInfoV2CaptchaError for 423 response (wbxappapi code 423005)', async () => {
149
+ await runTest(423005, true);
150
+ });
151
+
152
+ it('should throw MeetingInfoV2CaptchaError for 423 response (wbxappapi code 423001)', async () => {
153
+ await runTest(423001, false);
154
+ });
155
+ });
83
156
  });
84
157
  });
85
158
  });
@@ -68,6 +68,12 @@ skipInBrowser(describe)('plugin-meetings', () => {
68
68
  }
69
69
  });
70
70
 
71
+ Object.assign(webex.meetings.config, {
72
+ experimental: {
73
+ enableUnifiedMeetings: true
74
+ }
75
+ });
76
+
71
77
  Object.assign(webex.internal, {
72
78
  device: {
73
79
  deviceType: 'FAKE_DEVICE',
@@ -97,6 +103,29 @@ skipInBrowser(describe)('plugin-meetings', () => {
97
103
  assert.calledOnce(MeetingsUtil.triggerH264Codec);
98
104
  });
99
105
 
106
+ describe('#_toggleUnifiedMeetings', () => {
107
+ it('should have toggleUnifiedMeetings', () => {
108
+ assert.equal(typeof webex.meetings._toggleUnifiedMeetings, 'function');
109
+ });
110
+
111
+ describe('success', () => {
112
+ it('should update meeting info to v1', () => {
113
+ webex.meetings._toggleUnifiedMeetings(false);
114
+ assert.equal(webex.meetings.config.experimental.enableUnifiedMeetings, false);
115
+ assert.equal(webex.meetings.meetingInfo.constructor.name, 'MeetingInfo');
116
+ });
117
+ });
118
+
119
+ describe('failure', () => {
120
+ it('should not accept non boolean input', () => {
121
+ const currentEnableUnifiedMeetings = webex.meetings.config.experimental.enableUnifiedMeetings;
122
+
123
+ webex.meetings._toggleUnifiedMeetings('test');
124
+ assert.equal(webex.meetings.config.experimental.enableUnifiedMeetings, currentEnableUnifiedMeetings);
125
+ });
126
+ });
127
+ });
128
+
100
129
  describe('Public API Contracts', () => {
101
130
  describe('#register', () => {
102
131
  it('emits an event and resolves when register succeeds', (done) => {
@@ -0,0 +1,66 @@
1
+
2
+ import {assert} from '@webex/test-helper-chai';
3
+ import sinon from 'sinon';
4
+ import PeerConnectionManager from '@webex/plugin-meetings/src/peer-connection-manager/index';
5
+ import StaticConfig from '@webex/plugin-meetings/src/common/config';
6
+
7
+ describe('Peerconnection Manager', () => {
8
+ describe('Methods', () => {
9
+ describe('setRemoteSessionDetails', () => {
10
+ it('change the start bitrate on remoteSDP', async () => {
11
+ StaticConfig.set({bandwidth: {audio: 50, video: 500, startBitrate: 2000}});
12
+ let result = null;
13
+ const setRemoteDescription = sinon.stub().callsFake((args) => {
14
+ result = args;
15
+
16
+ return Promise.resolve();
17
+ });
18
+ const remoteSdp = 'v=0\r\n' +
19
+ 'm=video 5004 UDP/TLS/RTP/SAVPF 102 127 97 99\r\n' +
20
+ 'a=fmtp:102 profile-level-id=42e016;packetization-mode=1;max-mbps=244800;max-fs=8160;max-fps=3000;max-dpb=12240;max-rcmd-nalu-size=196608;level-asymmetry-allowed=1\r\n' +
21
+ 'a=rtpmap:127 H264/90000\r\n' +
22
+ 'a=fmtp:127 profile-level-id=42e016;max-mbps=244800;max-fs=8160;max-fps=3000;max-dpb=12240;max-rcmd-nalu-size=196608;level-asymmetry-allowed=1\r\n';
23
+
24
+ const resultSdp = 'v=0\r\n' +
25
+ 'm=video 5004 UDP/TLS/RTP/SAVPF 102 127 97 99\r\n' +
26
+ 'a=fmtp:102 profile-level-id=42e016;packetization-mode=1;max-mbps=244800;max-fs=8160;max-fps=3000;max-dpb=12240;max-rcmd-nalu-size=196608;level-asymmetry-allowed=1;x-google-start-bitrate=2000\r\n' +
27
+ 'a=rtpmap:127 H264/90000\r\n' +
28
+ 'a=fmtp:127 profile-level-id=42e016;max-mbps=244800;max-fs=8160;max-fps=3000;max-dpb=12240;max-rcmd-nalu-size=196608;level-asymmetry-allowed=1;x-google-start-bitrate=2000\r\n';
29
+ const peerConnection = {
30
+ signalingState: 'have-local-offer',
31
+ setRemoteDescription
32
+
33
+ };
34
+
35
+ await PeerConnectionManager.setRemoteSessionDetails(peerConnection, 'answer', remoteSdp, {});
36
+
37
+ assert.equal(result.sdp, resultSdp);
38
+ });
39
+
40
+ it('dont change the start bitrate on remoteSDP if default value is 0', async () => {
41
+ StaticConfig.set({bandwidth: {audio: 50, video: 500, startBitrate: 0}});
42
+ let result = null;
43
+ const setRemoteDescription = sinon.stub().callsFake((args) => {
44
+ result = args;
45
+
46
+ return Promise.resolve();
47
+ });
48
+ const remoteSdp = 'v=0\r\n' +
49
+ 'm=video 5004 UDP/TLS/RTP/SAVPF 102 127 97 99\r\n' +
50
+ 'a=fmtp:102 profile-level-id=42e016;packetization-mode=1;max-mbps=244800;max-fs=8160;max-fps=3000;max-dpb=12240;max-rcmd-nalu-size=196608;level-asymmetry-allowed=1\r\n' +
51
+ 'a=rtpmap:127 H264/90000\r\n' +
52
+ 'a=fmtp:127 profile-level-id=42e016;max-mbps=244800;max-fs=8160;max-fps=3000;max-dpb=12240;max-rcmd-nalu-size=196608;level-asymmetry-allowed=1\r\n';
53
+
54
+ const peerConnection = {
55
+ signalingState: 'have-local-offer',
56
+ setRemoteDescription
57
+
58
+ };
59
+
60
+ await PeerConnectionManager.setRemoteSessionDetails(peerConnection, 'answer', remoteSdp, {});
61
+
62
+ assert.equal(result.sdp, remoteSdp);
63
+ });
64
+ });
65
+ });
66
+ });
@@ -0,0 +1,25 @@
1
+
2
+ import {assert} from '@webex/test-helper-chai';
3
+ import PeerConnectionUtils from '@webex/plugin-meetings/src/peer-connection-manager/util';
4
+
5
+ describe('Peerconnection Manager', () => {
6
+ describe('Utils', () => {
7
+ describe('convertCLineToIpv4', () => {
8
+ it('changes ipv6 to ipv4 default', () => {
9
+ const localSdp = 'v=0\r\n' +
10
+ 'm=video 5004 UDP/TLS/RTP/SAVPF 102 127 97 99\r\n' +
11
+ 'c=IN IP6 2607:fb90:d27c:b314:211a:32dd:c47f:ffe\r\n' +
12
+ 'a=rtpmap:127 H264/90000\r\n';
13
+ const resultSdp = 'v=0\r\n' +
14
+ 'm=video 5004 UDP/TLS/RTP/SAVPF 102 127 97 99\r\n' +
15
+ 'c=IN IP4 0.0.0.0\r\n' +
16
+ 'a=rtpmap:127 H264/90000\r\n';
17
+
18
+
19
+ const temp = PeerConnectionUtils.convertCLineToIpv4(localSdp);
20
+
21
+ assert.equal(temp, resultSdp);
22
+ });
23
+ });
24
+ });
25
+ });