@webex/plugin-meetings 1.151.6 → 1.153.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/common/errors/captcha-error.js +64 -0
- package/dist/common/errors/captcha-error.js.map +1 -0
- package/dist/common/errors/password-error.js +64 -0
- package/dist/common/errors/password-error.js.map +1 -0
- package/dist/config.js +2 -1
- package/dist/config.js.map +1 -1
- package/dist/constants.js +33 -1
- package/dist/constants.js.map +1 -1
- package/dist/meeting/index.js +674 -433
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/request.js +59 -32
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/util.js +12 -0
- package/dist/meeting/util.js.map +1 -1
- package/dist/meeting-info/meeting-info-v2.js +142 -8
- package/dist/meeting-info/meeting-info-v2.js.map +1 -1
- package/dist/meeting-info/utilv2.js +12 -1
- package/dist/meeting-info/utilv2.js.map +1 -1
- package/dist/meetings/index.js +47 -19
- package/dist/meetings/index.js.map +1 -1
- package/dist/peer-connection-manager/index.js +10 -1
- package/dist/peer-connection-manager/index.js.map +1 -1
- package/package.json +5 -5
- package/src/common/errors/captcha-error.js +21 -0
- package/src/common/errors/password-error.js +21 -0
- package/src/config.js +2 -1
- package/src/constants.js +24 -0
- package/src/meeting/index.js +180 -5
- package/src/meeting/request.js +27 -0
- package/src/meeting/util.js +15 -4
- package/src/meeting-info/meeting-info-v2.js +67 -3
- package/src/meeting-info/utilv2.js +12 -1
- package/src/meetings/index.js +27 -8
- package/src/peer-connection-manager/index.js +13 -2
- package/test/unit/spec/meeting/index.js +304 -2
- package/test/unit/spec/meeting-info/meetinginfov2.js +74 -1
- package/test/unit/spec/meetings/index.js +29 -0
- package/test/unit/spec/peerconnection-manager/index.js +66 -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);
|
|
@@ -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
|
+
});
|