@webex/plugin-meetings 3.7.0-wxcc.1 → 3.8.0-next.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.
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/constants.js +15 -3
- package/dist/constants.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/selfUtils.js +5 -0
- package/dist/locus-info/selfUtils.js.map +1 -1
- package/dist/media/MediaConnectionAwaiter.js +1 -0
- package/dist/media/MediaConnectionAwaiter.js.map +1 -1
- package/dist/media/properties.js +30 -16
- package/dist/media/properties.js.map +1 -1
- package/dist/meeting/brbState.js +167 -0
- package/dist/meeting/brbState.js.map +1 -0
- package/dist/meeting/index.js +367 -296
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/muteState.js +1 -6
- package/dist/meeting/muteState.js.map +1 -1
- package/dist/meeting-info/meeting-info-v2.js +19 -12
- package/dist/meeting-info/meeting-info-v2.js.map +1 -1
- package/dist/meeting-info/utilv2.js +5 -1
- package/dist/meeting-info/utilv2.js.map +1 -1
- package/dist/metrics/constants.js +2 -0
- package/dist/metrics/constants.js.map +1 -1
- package/dist/reachability/index.js +31 -3
- package/dist/reachability/index.js.map +1 -1
- package/dist/types/constants.d.ts +9 -2
- package/dist/types/meeting/brbState.d.ts +54 -0
- package/dist/types/meeting/index.d.ts +23 -0
- package/dist/types/meeting-info/meeting-info-v2.d.ts +3 -1
- package/dist/types/metrics/constants.d.ts +2 -0
- package/dist/types/reachability/index.d.ts +9 -1
- package/dist/webinar/index.js +1 -1
- package/package.json +23 -23
- package/src/constants.ts +10 -2
- package/src/locus-info/selfUtils.ts +5 -0
- package/src/media/MediaConnectionAwaiter.ts +2 -0
- package/src/media/properties.ts +34 -13
- package/src/meeting/brbState.ts +169 -0
- package/src/meeting/index.ts +120 -27
- package/src/meeting/muteState.ts +1 -6
- package/src/meeting-info/meeting-info-v2.ts +9 -1
- package/src/meeting-info/utilv2.ts +14 -2
- package/src/metrics/constants.ts +2 -0
- package/src/reachability/index.ts +29 -1
- package/test/unit/spec/locus-info/selfUtils.js +10 -0
- package/test/unit/spec/media/properties.ts +15 -0
- package/test/unit/spec/meeting/brbState.ts +114 -0
- package/test/unit/spec/meeting/index.js +101 -33
- package/test/unit/spec/meeting/muteState.js +0 -24
- package/test/unit/spec/meeting-info/utilv2.js +9 -0
- package/test/unit/spec/reachability/index.ts +120 -10
package/package.json
CHANGED
|
@@ -43,13 +43,13 @@
|
|
|
43
43
|
"@webex/eslint-config-legacy": "0.0.0",
|
|
44
44
|
"@webex/jest-config-legacy": "0.0.0",
|
|
45
45
|
"@webex/legacy-tools": "0.0.0",
|
|
46
|
-
"@webex/plugin-meetings": "3.
|
|
47
|
-
"@webex/plugin-rooms": "3.7.0-
|
|
48
|
-
"@webex/test-helper-chai": "3.7.0-
|
|
49
|
-
"@webex/test-helper-mocha": "3.7.0-
|
|
50
|
-
"@webex/test-helper-mock-webex": "3.7.0-
|
|
51
|
-
"@webex/test-helper-retry": "3.7.0-
|
|
52
|
-
"@webex/test-helper-test-users": "3.7.0-
|
|
46
|
+
"@webex/plugin-meetings": "3.8.0-next.1",
|
|
47
|
+
"@webex/plugin-rooms": "3.7.0-next.25",
|
|
48
|
+
"@webex/test-helper-chai": "3.7.0-next.18",
|
|
49
|
+
"@webex/test-helper-mocha": "3.7.0-next.18",
|
|
50
|
+
"@webex/test-helper-mock-webex": "3.7.0-next.18",
|
|
51
|
+
"@webex/test-helper-retry": "3.7.0-next.18",
|
|
52
|
+
"@webex/test-helper-test-users": "3.7.0-next.18",
|
|
53
53
|
"chai": "^4.3.4",
|
|
54
54
|
"chai-as-promised": "^7.1.1",
|
|
55
55
|
"eslint": "^8.24.0",
|
|
@@ -61,22 +61,22 @@
|
|
|
61
61
|
"typescript": "^4.7.4"
|
|
62
62
|
},
|
|
63
63
|
"dependencies": {
|
|
64
|
-
"@webex/common": "3.7.0-
|
|
65
|
-
"@webex/event-dictionary-ts": "^1.0.
|
|
66
|
-
"@webex/internal-media-core": "2.14.
|
|
67
|
-
"@webex/internal-plugin-conversation": "3.7.0-
|
|
68
|
-
"@webex/internal-plugin-device": "3.7.0-
|
|
69
|
-
"@webex/internal-plugin-llm": "3.
|
|
70
|
-
"@webex/internal-plugin-mercury": "3.7.0-
|
|
71
|
-
"@webex/internal-plugin-metrics": "3.7.0-
|
|
72
|
-
"@webex/internal-plugin-support": "3.7.0-
|
|
73
|
-
"@webex/internal-plugin-user": "3.7.0-
|
|
74
|
-
"@webex/internal-plugin-voicea": "3.
|
|
75
|
-
"@webex/media-helpers": "3.7.0-
|
|
76
|
-
"@webex/plugin-people": "3.7.0-
|
|
77
|
-
"@webex/plugin-rooms": "3.7.0-
|
|
64
|
+
"@webex/common": "3.7.0-next.18",
|
|
65
|
+
"@webex/event-dictionary-ts": "^1.0.1688",
|
|
66
|
+
"@webex/internal-media-core": "2.14.4",
|
|
67
|
+
"@webex/internal-plugin-conversation": "3.7.0-next.25",
|
|
68
|
+
"@webex/internal-plugin-device": "3.7.0-next.18",
|
|
69
|
+
"@webex/internal-plugin-llm": "3.8.0-next.1",
|
|
70
|
+
"@webex/internal-plugin-mercury": "3.7.0-next.23",
|
|
71
|
+
"@webex/internal-plugin-metrics": "3.7.0-next.18",
|
|
72
|
+
"@webex/internal-plugin-support": "3.7.0-next.26",
|
|
73
|
+
"@webex/internal-plugin-user": "3.7.0-next.18",
|
|
74
|
+
"@webex/internal-plugin-voicea": "3.8.0-next.1",
|
|
75
|
+
"@webex/media-helpers": "3.7.0-next.25",
|
|
76
|
+
"@webex/plugin-people": "3.7.0-next.23",
|
|
77
|
+
"@webex/plugin-rooms": "3.7.0-next.25",
|
|
78
78
|
"@webex/web-capabilities": "^1.4.0",
|
|
79
|
-
"@webex/webex-core": "3.7.0-
|
|
79
|
+
"@webex/webex-core": "3.7.0-next.18",
|
|
80
80
|
"ampersand-collection": "^2.0.2",
|
|
81
81
|
"bowser": "^2.11.0",
|
|
82
82
|
"btoa": "^1.2.1",
|
|
@@ -92,5 +92,5 @@
|
|
|
92
92
|
"//": [
|
|
93
93
|
"TODO: upgrade jwt-decode when moving to node 18"
|
|
94
94
|
],
|
|
95
|
-
"version": "3.
|
|
95
|
+
"version": "3.8.0-next.1"
|
|
96
96
|
}
|
package/src/constants.ts
CHANGED
|
@@ -202,7 +202,7 @@ export const RETRY_TIMEOUT = 3000;
|
|
|
202
202
|
export const ICE_AND_DTLS_CONNECTION_TIMEOUT = 20000;
|
|
203
203
|
export const ROAP_OFFER_ANSWER_EXCHANGE_TIMEOUT = 35000;
|
|
204
204
|
export const WEBINAR_ERROR_WEBCAST = [403026];
|
|
205
|
-
export const
|
|
205
|
+
export const WEBINAR_ERROR_REGISTRATION_ID = [403037, 403137];
|
|
206
206
|
export const JOIN_BEFORE_HOST = 403003;
|
|
207
207
|
|
|
208
208
|
// ******************** REGEX **********************
|
|
@@ -1331,14 +1331,22 @@ export const PASSWORD_STATUS = {
|
|
|
1331
1331
|
VERIFIED: 'VERIFIED', // client has already provided the password and it has been verified, client can proceed to call join()
|
|
1332
1332
|
};
|
|
1333
1333
|
|
|
1334
|
+
export const REGISTRATION_ID_STATUS = {
|
|
1335
|
+
NOT_REQUIRED: 'NOT_REQUIRED', // registrationId is not required to join the meeting
|
|
1336
|
+
REQUIRED: 'REQUIRED', // client needs to provide the registrationId by calling verifyRegistrationId() before calling join()
|
|
1337
|
+
UNKNOWN: 'UNKNOWN', // we are waiting for information from the backend if registrationId is required or not
|
|
1338
|
+
VERIFIED: 'VERIFIED', // client has already provided the registrationId and it has been verified, client can proceed to call join()
|
|
1339
|
+
};
|
|
1340
|
+
|
|
1334
1341
|
export const MEETING_INFO_FAILURE_REASON = {
|
|
1335
1342
|
NONE: 'NONE', // meeting info was retrieved succesfully
|
|
1336
1343
|
WRONG_PASSWORD: 'WRONG_PASSWORD', // meeting requires password and no password or wrong one was provided
|
|
1337
1344
|
WRONG_CAPTCHA: 'WRONG_CAPTCHA', // wbxappapi requires a captcha code or a wrong captcha code was provided
|
|
1345
|
+
WRONG_REGISTRATION_ID: 'WRONG_REGISTRATION_ID', // meeting requires registrationId and no registrationId or wrong one was provided
|
|
1338
1346
|
POLICY: 'POLICY', // meeting info request violates some meeting policy
|
|
1339
1347
|
WEBINAR_REGISTRATION: 'WEBINAR_REGISTRATION', // webinar need registration
|
|
1340
1348
|
NEED_JOIN_WITH_WEBCAST: 'NEED_JOIN_WITH_WEBCAST', // webinar need using webcast join
|
|
1341
|
-
|
|
1349
|
+
WEBINAR_NEED_REGISTRATION_ID: 'WEBINAR_NEED_REGISTRATION_ID', // webinar need registrationID
|
|
1342
1350
|
NOT_REACH_JBH: 'NOT_REACH_JBH', // Meeting is not allow to access since not reach JBH (join before host) time
|
|
1343
1351
|
JOIN_FORBIDDEN: 'JOIN_FORBIDDEN', // meeting is not allow join
|
|
1344
1352
|
OTHER: 'OTHER', // any other error (network, etc)
|
|
@@ -441,6 +441,11 @@ SelfUtils.mutedByOthersChanged = (oldSelf, changedSelf) => {
|
|
|
441
441
|
return false;
|
|
442
442
|
}
|
|
443
443
|
|
|
444
|
+
// there is no need to trigger user update if no one muted user
|
|
445
|
+
if (changedSelf.selfIdentity === changedSelf.modifiedBy) {
|
|
446
|
+
return false;
|
|
447
|
+
}
|
|
448
|
+
|
|
444
449
|
return (
|
|
445
450
|
changedSelf.remoteMuted !== null &&
|
|
446
451
|
(oldSelf.remoteMuted !== changedSelf.remoteMuted ||
|
package/src/media/properties.ts
CHANGED
|
@@ -287,24 +287,45 @@ export default class MediaProperties {
|
|
|
287
287
|
selectedCandidatePairChanges: number;
|
|
288
288
|
numTransports: number;
|
|
289
289
|
}> {
|
|
290
|
-
const allStatsReports = [];
|
|
291
|
-
|
|
292
290
|
try {
|
|
293
|
-
const
|
|
294
|
-
|
|
291
|
+
const allStatsReports = [];
|
|
292
|
+
|
|
293
|
+
await new Promise((resolve, reject) => {
|
|
294
|
+
const timeout = setTimeout(() => {
|
|
295
|
+
reject(new Error('timed out'));
|
|
296
|
+
}, 1000);
|
|
297
|
+
|
|
298
|
+
this.webrtcMediaConnection
|
|
299
|
+
.getStats()
|
|
300
|
+
.then((statsResult) => {
|
|
301
|
+
clearTimeout(timeout);
|
|
302
|
+
statsResult.forEach((report) => allStatsReports.push(report));
|
|
303
|
+
resolve(allStatsReports);
|
|
304
|
+
})
|
|
305
|
+
.catch((error) => {
|
|
306
|
+
clearTimeout(timeout);
|
|
307
|
+
reject(error);
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
const connectionType = this.getConnectionType(allStatsReports);
|
|
312
|
+
const {selectedCandidatePairChanges, numTransports} = this.getTransportInfo(allStatsReports);
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
connectionType,
|
|
316
|
+
selectedCandidatePairChanges,
|
|
317
|
+
numTransports,
|
|
318
|
+
};
|
|
295
319
|
} catch (error) {
|
|
296
320
|
LoggerProxy.logger.warn(
|
|
297
321
|
`Media:properties#getCurrentConnectionInfo --> getStats() failed: ${error}`
|
|
298
322
|
);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
const connectionType = this.getConnectionType(allStatsReports);
|
|
302
|
-
const {selectedCandidatePairChanges, numTransports} = this.getTransportInfo(allStatsReports);
|
|
303
323
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
324
|
+
return {
|
|
325
|
+
connectionType: 'unknown',
|
|
326
|
+
selectedCandidatePairChanges: -1,
|
|
327
|
+
numTransports: 0,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
309
330
|
}
|
|
310
331
|
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import {MediaType} from '@webex/internal-media-core';
|
|
2
|
+
import LoggerProxy from '../common/logs/logger-proxy';
|
|
3
|
+
import type Meeting from '.';
|
|
4
|
+
import SendSlotManager from '../multistream/sendSlotManager';
|
|
5
|
+
|
|
6
|
+
export const createBrbState = (meeting: Meeting, enabled: boolean) => {
|
|
7
|
+
LoggerProxy.logger.info(
|
|
8
|
+
`Meeting:brbState#createBrbState: creating BrbState for meeting id ${meeting?.id}`
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
const brbState = new BrbState(meeting, enabled);
|
|
12
|
+
|
|
13
|
+
return brbState;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/** The purpose of this class is to manage the local and remote brb state
|
|
17
|
+
* and make sure that the server state always matches the last requested state by the client.
|
|
18
|
+
*/
|
|
19
|
+
export class BrbState {
|
|
20
|
+
state: {
|
|
21
|
+
client: {
|
|
22
|
+
enabled: boolean;
|
|
23
|
+
};
|
|
24
|
+
server: {
|
|
25
|
+
enabled: boolean;
|
|
26
|
+
};
|
|
27
|
+
syncToServerInProgress: boolean;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
meeting: Meeting;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Constructor
|
|
34
|
+
*
|
|
35
|
+
* @param {Meeting} meeting - the meeting object
|
|
36
|
+
* @param {boolean} enabled - whether the client audio/video is enabled at all
|
|
37
|
+
*/
|
|
38
|
+
constructor(meeting: Meeting, enabled: boolean) {
|
|
39
|
+
this.meeting = meeting;
|
|
40
|
+
this.state = {
|
|
41
|
+
client: {
|
|
42
|
+
enabled,
|
|
43
|
+
},
|
|
44
|
+
server: {
|
|
45
|
+
enabled: false,
|
|
46
|
+
},
|
|
47
|
+
syncToServerInProgress: false,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Enables/disables brb
|
|
53
|
+
*
|
|
54
|
+
* @param {boolean} enabled
|
|
55
|
+
* @param {SendSlotManager} sendSlotManager
|
|
56
|
+
* @returns {Promise}
|
|
57
|
+
*/
|
|
58
|
+
public enable(enabled: boolean, sendSlotManager: SendSlotManager) {
|
|
59
|
+
this.state.client.enabled = enabled;
|
|
60
|
+
|
|
61
|
+
return this.applyClientStateToServer(sendSlotManager);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Updates the server local and remote brb values so that they match the current client desired state.
|
|
66
|
+
*
|
|
67
|
+
* @param {SendSlotManager} sendSlotManager
|
|
68
|
+
* @returns {Promise}
|
|
69
|
+
*/
|
|
70
|
+
private applyClientStateToServer(sendSlotManager: SendSlotManager) {
|
|
71
|
+
if (this.state.syncToServerInProgress) {
|
|
72
|
+
LoggerProxy.logger.info(
|
|
73
|
+
`Meeting:brbState#applyClientStateToServer: request to server in progress, we need to wait for it to complete`
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
return Promise.resolve();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const remoteBrbRequiresSync = this.state.client.enabled !== this.state.server.enabled;
|
|
80
|
+
|
|
81
|
+
LoggerProxy.logger.info(
|
|
82
|
+
`Meeting:brbState#applyClientStateToServer: remoteBrbRequiresSync: ${remoteBrbRequiresSync}`
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
if (!remoteBrbRequiresSync) {
|
|
86
|
+
LoggerProxy.logger.info(
|
|
87
|
+
`Meeting:brbState#applyClientStateToServer: client state already matching server state, nothing to do`
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
return Promise.resolve();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
this.state.syncToServerInProgress = true;
|
|
94
|
+
|
|
95
|
+
return this.sendLocalBrbStateToServer(sendSlotManager)
|
|
96
|
+
.then(() => {
|
|
97
|
+
this.state.syncToServerInProgress = false;
|
|
98
|
+
LoggerProxy.logger.info(
|
|
99
|
+
`Meeting:brbState#applyClientStateToServer: sync with server completed`
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
// need to check if a new sync is required, because this.state.client may have changed while we were doing the current sync
|
|
103
|
+
this.applyClientStateToServer(sendSlotManager);
|
|
104
|
+
})
|
|
105
|
+
.catch((e) => {
|
|
106
|
+
this.state.syncToServerInProgress = false;
|
|
107
|
+
LoggerProxy.logger.warn(`Meeting:brbState#applyClientStateToServer: error: ${e}`);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Send the local brb state to the server
|
|
113
|
+
*
|
|
114
|
+
* @param {SendSlotManager} sendSlotManager
|
|
115
|
+
* @returns {Promise}
|
|
116
|
+
*/
|
|
117
|
+
private async sendLocalBrbStateToServer(sendSlotManager: SendSlotManager) {
|
|
118
|
+
const {enabled} = this.state.client;
|
|
119
|
+
|
|
120
|
+
if (!this.meeting.isMultistream) {
|
|
121
|
+
const errorMessage = 'Meeting:brbState#sendLocalBrbStateToServer: Not a multistream meeting';
|
|
122
|
+
const error = new Error(errorMessage);
|
|
123
|
+
|
|
124
|
+
LoggerProxy.logger.error(error);
|
|
125
|
+
|
|
126
|
+
return Promise.reject(error);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!this.meeting.mediaProperties.webrtcMediaConnection) {
|
|
130
|
+
const errorMessage =
|
|
131
|
+
'Meeting:brbState#sendLocalBrbStateToServer: WebRTC media connection is not defined';
|
|
132
|
+
const error = new Error(errorMessage);
|
|
133
|
+
|
|
134
|
+
LoggerProxy.logger.error(error);
|
|
135
|
+
|
|
136
|
+
return Promise.reject(error);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// this logic should be applied only to multistream meetings
|
|
140
|
+
return this.meeting.meetingRequest
|
|
141
|
+
.setBrb({
|
|
142
|
+
enabled,
|
|
143
|
+
locusUrl: this.meeting.locusUrl,
|
|
144
|
+
deviceUrl: this.meeting.deviceUrl,
|
|
145
|
+
selfId: this.meeting.selfId,
|
|
146
|
+
})
|
|
147
|
+
.then(() => {
|
|
148
|
+
sendSlotManager.setSourceStateOverride(MediaType.VideoMain, enabled ? 'away' : null);
|
|
149
|
+
})
|
|
150
|
+
.catch((error) => {
|
|
151
|
+
LoggerProxy.logger.error('Meeting:brbState#sendLocalBrbStateToServer: Error ', error);
|
|
152
|
+
|
|
153
|
+
return Promise.reject(error);
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* This method should be called whenever the server brb state is changed
|
|
159
|
+
*
|
|
160
|
+
* @param {Boolean} [enabled] true if user has brb enabled, false otherwise
|
|
161
|
+
* @returns {undefined}
|
|
162
|
+
*/
|
|
163
|
+
public handleServerBrbUpdate(enabled?: boolean) {
|
|
164
|
+
LoggerProxy.logger.info(
|
|
165
|
+
`Meeting:brbState#handleServerBrbUpdate: updating server brb to (${enabled})`
|
|
166
|
+
);
|
|
167
|
+
this.state.server.enabled = !!enabled;
|
|
168
|
+
}
|
|
169
|
+
}
|
package/src/meeting/index.ts
CHANGED
|
@@ -122,8 +122,9 @@ import {
|
|
|
122
122
|
ROAP_OFFER_ANSWER_EXCHANGE_TIMEOUT,
|
|
123
123
|
NAMED_MEDIA_GROUP_TYPE_AUDIO,
|
|
124
124
|
WEBINAR_ERROR_WEBCAST,
|
|
125
|
-
|
|
125
|
+
WEBINAR_ERROR_REGISTRATION_ID,
|
|
126
126
|
JOIN_BEFORE_HOST,
|
|
127
|
+
REGISTRATION_ID_STATUS,
|
|
127
128
|
} from '../constants';
|
|
128
129
|
import BEHAVIORAL_METRICS from '../metrics/constants';
|
|
129
130
|
import ParameterError from '../common/errors/parameter';
|
|
@@ -163,6 +164,7 @@ import {LocusMediaRequest} from './locusMediaRequest';
|
|
|
163
164
|
import {ConnectionStateHandler, ConnectionStateEvent} from './connectionStateHandler';
|
|
164
165
|
import JoinWebinarError from '../common/errors/join-webinar-error';
|
|
165
166
|
import Member from '../member';
|
|
167
|
+
import {BrbState, createBrbState} from './brbState';
|
|
166
168
|
import MultistreamNotSupportedError from '../common/errors/multistream-not-supported-error';
|
|
167
169
|
import JoinForbiddenError from '../common/errors/join-forbidden-error';
|
|
168
170
|
|
|
@@ -255,6 +257,7 @@ export enum ScreenShareFloorStatus {
|
|
|
255
257
|
|
|
256
258
|
type FetchMeetingInfoParams = {
|
|
257
259
|
password?: string;
|
|
260
|
+
registrationId?: string;
|
|
258
261
|
captchaCode?: string;
|
|
259
262
|
extraParams?: Record<string, any>;
|
|
260
263
|
sendCAevents?: boolean;
|
|
@@ -649,6 +652,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
649
652
|
turnServerUsed: boolean;
|
|
650
653
|
areVoiceaEventsSetup = false;
|
|
651
654
|
isMoveToInProgress = false;
|
|
655
|
+
registrationIdStatus: string;
|
|
656
|
+
brbState: BrbState;
|
|
652
657
|
|
|
653
658
|
voiceaListenerCallbacks: object = {
|
|
654
659
|
[VOICEAEVENTS.VOICEA_ANNOUNCEMENT]: (payload: Transcription['languageOptions']) => {
|
|
@@ -1345,6 +1350,16 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1345
1350
|
*/
|
|
1346
1351
|
this.passwordStatus = PASSWORD_STATUS.UNKNOWN;
|
|
1347
1352
|
|
|
1353
|
+
/**
|
|
1354
|
+
* registrationId status. If it's REGISTRATIONID_STATUS.REQUIRED then verifyRegistrationId() needs to be called
|
|
1355
|
+
* with the correct registrationId before calling join()
|
|
1356
|
+
* @instance
|
|
1357
|
+
* @type {REGISTRATION_ID_STATUS}
|
|
1358
|
+
* @public
|
|
1359
|
+
* @memberof Meeting
|
|
1360
|
+
*/
|
|
1361
|
+
this.registrationIdStatus = REGISTRATION_ID_STATUS.UNKNOWN;
|
|
1362
|
+
|
|
1348
1363
|
/**
|
|
1349
1364
|
* Information about required captcha. If null, then no captcha is required. status. If it's PASSWORD_STATUS.REQUIRED then verifyPassword() needs to be called
|
|
1350
1365
|
* with the correct password before calling join()
|
|
@@ -1657,6 +1672,15 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1657
1672
|
this.passwordStatus = PASSWORD_STATUS.NOT_REQUIRED;
|
|
1658
1673
|
}
|
|
1659
1674
|
|
|
1675
|
+
if (
|
|
1676
|
+
this.registrationIdStatus === REGISTRATION_ID_STATUS.REQUIRED ||
|
|
1677
|
+
this.registrationIdStatus === REGISTRATION_ID_STATUS.VERIFIED
|
|
1678
|
+
) {
|
|
1679
|
+
this.registrationIdStatus = REGISTRATION_ID_STATUS.VERIFIED;
|
|
1680
|
+
} else {
|
|
1681
|
+
this.registrationIdStatus = REGISTRATION_ID_STATUS.NOT_REQUIRED;
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1660
1684
|
Trigger.trigger(
|
|
1661
1685
|
this,
|
|
1662
1686
|
{
|
|
@@ -1700,7 +1724,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1700
1724
|
* @private
|
|
1701
1725
|
*/
|
|
1702
1726
|
private prepForFetchMeetingInfo(
|
|
1703
|
-
{
|
|
1727
|
+
{
|
|
1728
|
+
password = null,
|
|
1729
|
+
registrationId = null,
|
|
1730
|
+
captchaCode = null,
|
|
1731
|
+
extraParams = {},
|
|
1732
|
+
}: FetchMeetingInfoParams,
|
|
1704
1733
|
caller: string
|
|
1705
1734
|
): Promise<void> {
|
|
1706
1735
|
// when fetch meeting info is called directly by the client, we want to clear out the random timer for sdk to do it
|
|
@@ -1740,6 +1769,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1740
1769
|
captchaCode = null,
|
|
1741
1770
|
extraParams = {},
|
|
1742
1771
|
sendCAevents = false,
|
|
1772
|
+
registrationId = null,
|
|
1743
1773
|
}): Promise<void> {
|
|
1744
1774
|
try {
|
|
1745
1775
|
const captchaInfo = captchaCode
|
|
@@ -1755,7 +1785,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1755
1785
|
this.config.installedOrgID,
|
|
1756
1786
|
this.locusId,
|
|
1757
1787
|
extraParams,
|
|
1758
|
-
{meetingId: this.id, sendCAevents}
|
|
1788
|
+
{meetingId: this.id, sendCAevents},
|
|
1789
|
+
registrationId
|
|
1759
1790
|
);
|
|
1760
1791
|
|
|
1761
1792
|
this.parseMeetingInfo(info?.body, this.destination, info?.errors);
|
|
@@ -1777,14 +1808,15 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1777
1808
|
this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.WEBINAR_REGISTRATION;
|
|
1778
1809
|
if (WEBINAR_ERROR_WEBCAST.includes(err.wbxAppApiCode)) {
|
|
1779
1810
|
this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.NEED_JOIN_WITH_WEBCAST;
|
|
1780
|
-
} else if (
|
|
1781
|
-
this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.
|
|
1811
|
+
} else if (WEBINAR_ERROR_REGISTRATION_ID.includes(err.wbxAppApiCode)) {
|
|
1812
|
+
this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.WEBINAR_NEED_REGISTRATION_ID;
|
|
1782
1813
|
}
|
|
1783
1814
|
this.meetingInfoFailureCode = err.wbxAppApiCode;
|
|
1784
1815
|
|
|
1785
1816
|
if (err.meetingInfo) {
|
|
1786
1817
|
this.meetingInfo = err.meetingInfo;
|
|
1787
1818
|
}
|
|
1819
|
+
this.requiredCaptcha = null;
|
|
1788
1820
|
|
|
1789
1821
|
throw new JoinWebinarError();
|
|
1790
1822
|
} else if (err instanceof MeetingInfoV2JoinForbiddenError) {
|
|
@@ -1829,9 +1861,13 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1829
1861
|
`Meeting:index#fetchMeetingInfo --> Info Unable to fetch meeting info for ${this.destination} - captcha required (code=${err?.body?.code}).`
|
|
1830
1862
|
);
|
|
1831
1863
|
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1864
|
+
if (this.requiredCaptcha) {
|
|
1865
|
+
this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.WRONG_CAPTCHA;
|
|
1866
|
+
} else if (err.isRegistrationIdRequired) {
|
|
1867
|
+
this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.WRONG_REGISTRATION_ID;
|
|
1868
|
+
} else {
|
|
1869
|
+
this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.WRONG_PASSWORD;
|
|
1870
|
+
}
|
|
1835
1871
|
|
|
1836
1872
|
this.meetingInfoFailureCode = err.wbxAppApiCode;
|
|
1837
1873
|
|
|
@@ -1839,6 +1875,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1839
1875
|
this.passwordStatus = PASSWORD_STATUS.REQUIRED;
|
|
1840
1876
|
}
|
|
1841
1877
|
|
|
1878
|
+
if (err.isRegistrationIdRequired) {
|
|
1879
|
+
this.registrationIdStatus = REGISTRATION_ID_STATUS.REQUIRED;
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1842
1882
|
this.requiredCaptcha = err.captchaInfo;
|
|
1843
1883
|
throw new CaptchaError();
|
|
1844
1884
|
} else {
|
|
@@ -1979,6 +2019,48 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1979
2019
|
});
|
|
1980
2020
|
}
|
|
1981
2021
|
|
|
2022
|
+
/**
|
|
2023
|
+
* Checks if the supplied registrationId is correct. It returns a promise with information whether the
|
|
2024
|
+
* registrationId and captcha code were correct or not.
|
|
2025
|
+
* @param {String | undefined} registrationId - can be undefined if only captcha was required
|
|
2026
|
+
* @param {String | undefined} captchaCode - can be undefined if captcha was not required by the server
|
|
2027
|
+
* @param {Boolean} sendCAevents - whether Call Analyzer events should be sent when fetching meeting information
|
|
2028
|
+
* @public
|
|
2029
|
+
* @memberof Meeting
|
|
2030
|
+
* @returns {Promise<{isRegistrationIdValid: boolean, requiredCaptcha: boolean, failureReason: MEETING_INFO_FAILURE_REASON}>}
|
|
2031
|
+
*/
|
|
2032
|
+
public verifyRegistrationId(registrationId: string, captchaCode: string, sendCAevents = false) {
|
|
2033
|
+
return this.fetchMeetingInfo({
|
|
2034
|
+
registrationId,
|
|
2035
|
+
captchaCode,
|
|
2036
|
+
sendCAevents,
|
|
2037
|
+
})
|
|
2038
|
+
.then(() => {
|
|
2039
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.VERIFY_REGISTRATION_ID_SUCCESS);
|
|
2040
|
+
|
|
2041
|
+
return {
|
|
2042
|
+
isRegistrationIdValid: true,
|
|
2043
|
+
requiredCaptcha: null,
|
|
2044
|
+
failureReason: MEETING_INFO_FAILURE_REASON.NONE,
|
|
2045
|
+
};
|
|
2046
|
+
})
|
|
2047
|
+
.catch((error) => {
|
|
2048
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.VERIFY_REGISTRATION_ID_ERROR);
|
|
2049
|
+
|
|
2050
|
+
if (error instanceof JoinWebinarError || error instanceof CaptchaError) {
|
|
2051
|
+
return {
|
|
2052
|
+
isRegistrationIdValid: this.registrationIdStatus === REGISTRATION_ID_STATUS.VERIFIED,
|
|
2053
|
+
requiredCaptcha: this.requiredCaptcha,
|
|
2054
|
+
failureReason:
|
|
2055
|
+
error instanceof JoinWebinarError
|
|
2056
|
+
? MEETING_INFO_FAILURE_REASON.WRONG_REGISTRATION_ID
|
|
2057
|
+
: this.meetingInfoFailureReason,
|
|
2058
|
+
};
|
|
2059
|
+
}
|
|
2060
|
+
throw error;
|
|
2061
|
+
});
|
|
2062
|
+
}
|
|
2063
|
+
|
|
1982
2064
|
/**
|
|
1983
2065
|
* Refreshes the captcha. As a result the meeting will have new captcha id, image and audio.
|
|
1984
2066
|
* If the refresh operation fails, meeting remains with the old captcha properties.
|
|
@@ -3349,6 +3431,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3349
3431
|
// The second on is if the audio is muted, we need to tell the statsAnalyzer when
|
|
3350
3432
|
// the audio is muted or the user is not willing to send media
|
|
3351
3433
|
this.locusInfo.on(LOCUSINFO.EVENTS.MEDIA_STATUS_CHANGE, (status) => {
|
|
3434
|
+
LoggerProxy.logger.info(
|
|
3435
|
+
'Meeting:index#setUpLocusInfoSelfListener --> MEDIA_STATUS_CHANGE received, processing...'
|
|
3436
|
+
);
|
|
3437
|
+
|
|
3352
3438
|
if (this.statsAnalyzer) {
|
|
3353
3439
|
this.statsAnalyzer.updateMediaStatus({
|
|
3354
3440
|
actual: status,
|
|
@@ -3362,6 +3448,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3362
3448
|
receiveShare: this.mediaProperties.mediaDirection?.receiveShare,
|
|
3363
3449
|
},
|
|
3364
3450
|
});
|
|
3451
|
+
} else {
|
|
3452
|
+
LoggerProxy.logger.warn(
|
|
3453
|
+
'Meeting:index#setUpLocusInfoSelfListener --> MEDIA_STATUS_CHANGE, statsAnalyzer is not available.'
|
|
3454
|
+
);
|
|
3365
3455
|
}
|
|
3366
3456
|
});
|
|
3367
3457
|
|
|
@@ -3407,6 +3497,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3407
3497
|
});
|
|
3408
3498
|
|
|
3409
3499
|
this.locusInfo.on(LOCUSINFO.EVENTS.SELF_MEETING_BRB_CHANGED, (payload) => {
|
|
3500
|
+
this.brbState?.handleServerBrbUpdate(payload?.brb?.enabled);
|
|
3410
3501
|
Trigger.trigger(
|
|
3411
3502
|
this,
|
|
3412
3503
|
{
|
|
@@ -3650,22 +3741,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3650
3741
|
return Promise.reject(error);
|
|
3651
3742
|
}
|
|
3652
3743
|
|
|
3653
|
-
|
|
3654
|
-
return this.meetingRequest
|
|
3655
|
-
.setBrb({
|
|
3656
|
-
enabled,
|
|
3657
|
-
locusUrl: this.locusUrl,
|
|
3658
|
-
deviceUrl: this.deviceUrl,
|
|
3659
|
-
selfId: this.selfId,
|
|
3660
|
-
})
|
|
3661
|
-
.then(() => {
|
|
3662
|
-
this.sendSlotManager.setSourceStateOverride(MediaType.VideoMain, enabled ? 'away' : null);
|
|
3663
|
-
})
|
|
3664
|
-
.catch((error) => {
|
|
3665
|
-
LoggerProxy.logger.error('Meeting:index#beRightBack --> Error ', error);
|
|
3666
|
-
|
|
3667
|
-
return Promise.reject(error);
|
|
3668
|
-
});
|
|
3744
|
+
return this.brbState.enable(enabled, this.sendSlotManager);
|
|
3669
3745
|
}
|
|
3670
3746
|
|
|
3671
3747
|
/**
|
|
@@ -5718,7 +5794,14 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5718
5794
|
return undefined;
|
|
5719
5795
|
}
|
|
5720
5796
|
// @ts-ignore - Fix type
|
|
5721
|
-
await this.webex.internal.llm.disconnectLLM(
|
|
5797
|
+
await this.webex.internal.llm.disconnectLLM(
|
|
5798
|
+
isJoined
|
|
5799
|
+
? {
|
|
5800
|
+
code: 3050,
|
|
5801
|
+
reason: 'done (permanent)',
|
|
5802
|
+
}
|
|
5803
|
+
: undefined
|
|
5804
|
+
);
|
|
5722
5805
|
// @ts-ignore - Fix type
|
|
5723
5806
|
this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
|
|
5724
5807
|
}
|
|
@@ -6099,9 +6182,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6099
6182
|
* @returns {undefined}
|
|
6100
6183
|
*/
|
|
6101
6184
|
public roapMessageReceived = (roapMessage: RoapMessage) => {
|
|
6102
|
-
const mediaServer =
|
|
6185
|
+
const mediaServer =
|
|
6186
|
+
roapMessage.messageType === 'ANSWER'
|
|
6187
|
+
? MeetingsUtil.getMediaServer(roapMessage.sdp)
|
|
6188
|
+
: undefined;
|
|
6103
6189
|
|
|
6104
|
-
if (this.isMultistream && mediaServer !== 'homer') {
|
|
6190
|
+
if (this.isMultistream && mediaServer && mediaServer !== 'homer') {
|
|
6105
6191
|
throw new MultistreamNotSupportedError(
|
|
6106
6192
|
`Client asked for multistream backend (Homer), but got ${mediaServer} instead`
|
|
6107
6193
|
);
|
|
@@ -6716,6 +6802,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6716
6802
|
new RtcMetrics(this.webex, {meetingId: this.id}, this.correlationId)
|
|
6717
6803
|
: undefined;
|
|
6718
6804
|
|
|
6805
|
+
// ongoing reachability checks slow down new media connections especially on Firefox, so we stop them
|
|
6806
|
+
this.getWebexObject().meetings.reachability.stopReachability();
|
|
6807
|
+
|
|
6719
6808
|
const mc = Media.createMediaConnection(
|
|
6720
6809
|
this.isMultistream,
|
|
6721
6810
|
this.getMediaConnectionDebugId(),
|
|
@@ -7432,6 +7521,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7432
7521
|
|
|
7433
7522
|
this.audio = createMuteState(AUDIO, this, audioEnabled);
|
|
7434
7523
|
this.video = createMuteState(VIDEO, this, videoEnabled);
|
|
7524
|
+
this.brbState = createBrbState(this, false);
|
|
7435
7525
|
|
|
7436
7526
|
try {
|
|
7437
7527
|
await this.setUpLocalStreamReferences(localStreams);
|
|
@@ -7467,6 +7557,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7467
7557
|
throw error;
|
|
7468
7558
|
}
|
|
7469
7559
|
}
|
|
7560
|
+
|
|
7561
|
+
LoggerProxy.logger.info(`${LOG_HEADER} media connected, finalizing...`);
|
|
7562
|
+
|
|
7470
7563
|
if (this.mediaProperties.hasLocalShareStream()) {
|
|
7471
7564
|
await this.enqueueScreenShareFloorRequest();
|
|
7472
7565
|
}
|
package/src/meeting/muteState.ts
CHANGED
|
@@ -379,12 +379,7 @@ export class MuteState {
|
|
|
379
379
|
}
|
|
380
380
|
if (muted !== undefined) {
|
|
381
381
|
this.state.server.remoteMute = muted;
|
|
382
|
-
|
|
383
|
-
// We never want to unmute the local stream from a server remote mute update.
|
|
384
|
-
// Moderated unmute is handled by a different function.
|
|
385
|
-
if (muted) {
|
|
386
|
-
this.muteLocalStream(meeting, muted, 'remotelyMuted');
|
|
387
|
-
}
|
|
382
|
+
this.muteLocalStream(meeting, muted, 'remotelyMuted');
|
|
388
383
|
}
|
|
389
384
|
}
|
|
390
385
|
|