@webex/plugin-meetings 1.151.5 → 1.152.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 (44) 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 +674 -433
  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/index.js +5 -0
  16. package/dist/meeting-info/index.js.map +1 -1
  17. package/dist/meeting-info/meeting-info-v2.js +142 -8
  18. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  19. package/dist/meeting-info/utilv2.js +12 -1
  20. package/dist/meeting-info/utilv2.js.map +1 -1
  21. package/dist/meetings/index.js +31 -22
  22. package/dist/meetings/index.js.map +1 -1
  23. package/dist/peer-connection-manager/index.js +10 -1
  24. package/dist/peer-connection-manager/index.js.map +1 -1
  25. package/dist/personal-meeting-room/index.js +6 -9
  26. package/dist/personal-meeting-room/index.js.map +1 -1
  27. package/package.json +5 -5
  28. package/src/common/errors/captcha-error.js +21 -0
  29. package/src/common/errors/password-error.js +21 -0
  30. package/src/config.js +2 -1
  31. package/src/constants.js +24 -0
  32. package/src/meeting/index.js +180 -5
  33. package/src/meeting/request.js +27 -0
  34. package/src/meeting/util.js +15 -4
  35. package/src/meeting-info/index.js +6 -1
  36. package/src/meeting-info/meeting-info-v2.js +67 -3
  37. package/src/meeting-info/utilv2.js +12 -1
  38. package/src/meetings/index.js +12 -9
  39. package/src/peer-connection-manager/index.js +13 -2
  40. package/src/personal-meeting-room/index.js +5 -6
  41. package/test/unit/spec/meeting/index.js +304 -2
  42. package/test/unit/spec/meeting-info/meetinginfov2.js +93 -3
  43. package/test/unit/spec/peerconnection-manager/index.js +66 -0
  44. package/test/unit/spec/personal-meeting-room/personal-meeting-room.js +33 -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', () => {
@@ -625,6 +635,17 @@ describe('plugin-meetings', () => {
625
635
  await meeting.join({receiveTranscription: true});
626
636
  assert.calledOnce(meeting.receiveTranscription);
627
637
  });
638
+
639
+ it('should not create new correlation ID on join immediately after create', async () => {
640
+ await meeting.join();
641
+ sinon.assert.notCalled(meeting.setCorrelationId);
642
+ });
643
+
644
+ it('should create new correlation ID when already joined', async () => {
645
+ meeting.isCreated = false;
646
+ await meeting.join();
647
+ sinon.assert.called(meeting.setCorrelationId);
648
+ });
628
649
  });
629
650
  describe('failure', () => {
630
651
  beforeEach(() => {
@@ -642,6 +663,14 @@ describe('plugin-meetings', () => {
642
663
  });
643
664
  });
644
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
+ });
645
674
  describe('total failure', () => {
646
675
  beforeEach(() => {
647
676
  MeetingUtil.isPinOrGuest = sinon.stub().returns(false);
@@ -1999,6 +2028,279 @@ describe('plugin-meetings', () => {
1999
2028
  });
2000
2029
  });
2001
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
+ });
2002
2304
  describe('#mediaNegotiatedEvent', () => {
2003
2305
  it('should have #mediaNegotiatedEvent', () => {
2004
2306
  assert.exists(meeting.mediaNegotiatedEvent);
@@ -9,9 +9,10 @@ import Device from '@webex/internal-plugin-device';
9
9
  import Mercury from '@webex/internal-plugin-mercury';
10
10
  import Meetings from '@webex/plugin-meetings/src/meetings';
11
11
  import {
12
- _MEETING_ID_
12
+ _MEETING_ID_,
13
+ _PERSONAL_ROOM_
13
14
  } from '@webex/plugin-meetings/src/constants';
14
- 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';
15
16
  import MeetingInfoUtil from '@webex/plugin-meetings/src/meeting-info/utilv2';
16
17
 
17
18
  describe('plugin-meetings', () => {
@@ -32,7 +33,8 @@ describe('plugin-meetings', () => {
32
33
  device: {
33
34
  deviceType: 'FAKE_DEVICE',
34
35
  register: sinon.stub().returns(Promise.resolve()),
35
- unregister: sinon.stub().returns(Promise.resolve())
36
+ unregister: sinon.stub().returns(Promise.resolve()),
37
+ userId: '01824b9b-adef-4b10-b5c1-8a2fe2fb7c0e'
36
38
  },
37
39
  mercury: {
38
40
  connect: sinon.stub().returns(Promise.resolve()),
@@ -63,6 +65,94 @@ describe('plugin-meetings', () => {
63
65
  MeetingInfoUtil.getDestinationType.restore();
64
66
  MeetingInfoUtil.getRequestBody.restore();
65
67
  });
68
+ it('should fetch meeting info for the personal meeting room type', async () => {
69
+ sinon.stub(MeetingInfoUtil, 'getDestinationType').returns(Promise.resolve({type: 'MEETING_ID', destination: '123456'}));
70
+ sinon.stub(MeetingInfoUtil, 'getRequestBody').returns(Promise.resolve({meetingKey: '1234323'}));
71
+
72
+ await meetingInfo.fetchMeetingInfo({
73
+ type: _PERSONAL_ROOM_
74
+ });
75
+
76
+ assert.calledWith(webex.request, {
77
+ method: 'POST', service: 'webex-appapi-service', resource: 'meetingInfo', body: {meetingKey: '1234323'}
78
+ });
79
+
80
+ MeetingInfoUtil.getDestinationType.restore();
81
+ MeetingInfoUtil.getRequestBody.restore();
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
+ });
66
156
  });
67
157
  });
68
158
  });
@@ -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,33 @@
1
+ /*!
2
+ * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
+ */
4
+
5
+ import {assert} from '@webex/test-helper-chai';
6
+ import sinon from 'sinon';
7
+ import {
8
+ _PERSONAL_ROOM_
9
+ } from '@webex/plugin-meetings/src/constants';
10
+ import PersonalMeetingRoom from '@webex/plugin-meetings/src/personal-meeting-room';
11
+
12
+ describe('personal-meeting-room', () => {
13
+ let meetingInfo;
14
+ let pmr;
15
+
16
+ beforeEach(() => {
17
+ meetingInfo = {
18
+ fetchMeetingInfo: sinon.stub().returns(Promise.resolve(
19
+ {body: {isPmr: true}}
20
+ ))
21
+ };
22
+ pmr = new PersonalMeetingRoom({meetingInfo}, {parent: {}});
23
+ });
24
+
25
+
26
+ describe('#get()', () => {
27
+ it('returns personal meeting room info', async () => {
28
+ await pmr.get();
29
+ assert.calledOnce(meetingInfo.fetchMeetingInfo);
30
+ assert.calledWith(meetingInfo.fetchMeetingInfo, {type: _PERSONAL_ROOM_});
31
+ });
32
+ });
33
+ });