@webex/plugin-meetings 3.11.0 → 3.12.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/aiEnableRequest/index.js +184 -0
- package/dist/aiEnableRequest/index.js.map +1 -0
- package/dist/aiEnableRequest/utils.js +36 -0
- package/dist/aiEnableRequest/utils.js.map +1 -0
- package/dist/annotation/index.js +14 -5
- package/dist/annotation/index.js.map +1 -1
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/config.js +5 -1
- package/dist/config.js.map +1 -1
- package/dist/constants.js +28 -6
- package/dist/constants.js.map +1 -1
- package/dist/hashTree/constants.js +3 -1
- package/dist/hashTree/constants.js.map +1 -1
- package/dist/hashTree/hashTree.js +18 -0
- package/dist/hashTree/hashTree.js.map +1 -1
- package/dist/hashTree/hashTreeParser.js +709 -380
- package/dist/hashTree/hashTreeParser.js.map +1 -1
- package/dist/hashTree/types.js +4 -2
- package/dist/hashTree/types.js.map +1 -1
- package/dist/hashTree/utils.js +10 -0
- package/dist/hashTree/utils.js.map +1 -1
- package/dist/index.js +11 -2
- package/dist/index.js.map +1 -1
- package/dist/interceptors/constant.js +12 -0
- package/dist/interceptors/constant.js.map +1 -0
- package/dist/interceptors/dataChannelAuthToken.js +290 -0
- package/dist/interceptors/dataChannelAuthToken.js.map +1 -0
- package/dist/interceptors/index.js +7 -0
- package/dist/interceptors/index.js.map +1 -1
- package/dist/interceptors/utils.js +27 -0
- package/dist/interceptors/utils.js.map +1 -0
- package/dist/interpretation/index.js +2 -2
- package/dist/interpretation/index.js.map +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/controlsUtils.js +5 -3
- package/dist/locus-info/controlsUtils.js.map +1 -1
- package/dist/locus-info/index.js +217 -79
- package/dist/locus-info/index.js.map +1 -1
- package/dist/locus-info/selfUtils.js +1 -0
- package/dist/locus-info/selfUtils.js.map +1 -1
- package/dist/locus-info/types.js.map +1 -1
- package/dist/media/MediaConnectionAwaiter.js +57 -1
- package/dist/media/MediaConnectionAwaiter.js.map +1 -1
- package/dist/media/properties.js +4 -2
- package/dist/media/properties.js.map +1 -1
- package/dist/meeting/in-meeting-actions.js +7 -1
- package/dist/meeting/in-meeting-actions.js.map +1 -1
- package/dist/meeting/index.js +1082 -861
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/request.js +50 -0
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/request.type.js.map +1 -1
- package/dist/meeting/util.js +133 -3
- package/dist/meeting/util.js.map +1 -1
- package/dist/meetings/index.js +100 -45
- package/dist/meetings/index.js.map +1 -1
- package/dist/member/index.js +10 -0
- package/dist/member/index.js.map +1 -1
- package/dist/member/util.js +10 -0
- package/dist/member/util.js.map +1 -1
- package/dist/metrics/constants.js +2 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/multistream/mediaRequestManager.js +9 -60
- package/dist/multistream/mediaRequestManager.js.map +1 -1
- package/dist/multistream/remoteMediaManager.js +11 -0
- package/dist/multistream/remoteMediaManager.js.map +1 -1
- package/dist/reachability/index.js +18 -10
- package/dist/reachability/index.js.map +1 -1
- package/dist/reactions/reactions.type.js.map +1 -1
- package/dist/reconnection-manager/index.js +0 -1
- package/dist/reconnection-manager/index.js.map +1 -1
- package/dist/types/aiEnableRequest/index.d.ts +5 -0
- package/dist/types/aiEnableRequest/utils.d.ts +2 -0
- package/dist/types/config.d.ts +3 -0
- package/dist/types/constants.d.ts +23 -1
- package/dist/types/hashTree/constants.d.ts +1 -0
- package/dist/types/hashTree/hashTree.d.ts +7 -0
- package/dist/types/hashTree/hashTreeParser.d.ts +99 -14
- package/dist/types/hashTree/types.d.ts +3 -0
- package/dist/types/hashTree/utils.d.ts +6 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/interceptors/constant.d.ts +5 -0
- package/dist/types/interceptors/dataChannelAuthToken.d.ts +43 -0
- package/dist/types/interceptors/index.d.ts +2 -1
- package/dist/types/interceptors/utils.d.ts +1 -0
- package/dist/types/locus-info/index.d.ts +21 -2
- package/dist/types/locus-info/types.d.ts +1 -0
- package/dist/types/media/MediaConnectionAwaiter.d.ts +10 -1
- package/dist/types/media/properties.d.ts +2 -1
- package/dist/types/meeting/in-meeting-actions.d.ts +6 -0
- package/dist/types/meeting/index.d.ts +38 -6
- package/dist/types/meeting/request.d.ts +16 -1
- package/dist/types/meeting/request.type.d.ts +5 -0
- package/dist/types/meeting/util.d.ts +31 -0
- package/dist/types/meetings/index.d.ts +4 -2
- package/dist/types/member/index.d.ts +1 -0
- package/dist/types/member/util.d.ts +5 -0
- package/dist/types/metrics/constants.d.ts +1 -0
- package/dist/types/multistream/mediaRequestManager.d.ts +0 -23
- package/dist/types/reactions/reactions.type.d.ts +1 -0
- package/dist/types/webinar/utils.d.ts +6 -0
- package/dist/webinar/index.js +260 -90
- package/dist/webinar/index.js.map +1 -1
- package/dist/webinar/utils.js +25 -0
- package/dist/webinar/utils.js.map +1 -0
- package/package.json +24 -23
- package/src/aiEnableRequest/README.md +84 -0
- package/src/aiEnableRequest/index.ts +170 -0
- package/src/aiEnableRequest/utils.ts +25 -0
- package/src/annotation/index.ts +27 -7
- package/src/config.ts +3 -0
- package/src/constants.ts +29 -1
- package/src/hashTree/constants.ts +1 -0
- package/src/hashTree/hashTree.ts +17 -0
- package/src/hashTree/hashTreeParser.ts +627 -249
- package/src/hashTree/types.ts +4 -0
- package/src/hashTree/utils.ts +9 -0
- package/src/index.ts +8 -1
- package/src/interceptors/constant.ts +6 -0
- package/src/interceptors/dataChannelAuthToken.ts +170 -0
- package/src/interceptors/index.ts +2 -1
- package/src/interceptors/utils.ts +16 -0
- package/src/interpretation/index.ts +2 -2
- package/src/locus-info/controlsUtils.ts +11 -0
- package/src/locus-info/index.ts +231 -61
- package/src/locus-info/selfUtils.ts +1 -0
- package/src/locus-info/types.ts +1 -0
- package/src/media/MediaConnectionAwaiter.ts +41 -1
- package/src/media/properties.ts +3 -1
- package/src/meeting/in-meeting-actions.ts +12 -0
- package/src/meeting/index.ts +205 -44
- package/src/meeting/request.ts +42 -0
- package/src/meeting/request.type.ts +6 -0
- package/src/meeting/util.ts +160 -2
- package/src/meetings/index.ts +135 -41
- package/src/member/index.ts +10 -0
- package/src/member/util.ts +12 -0
- package/src/metrics/constants.ts +1 -0
- package/src/multistream/mediaRequestManager.ts +4 -54
- package/src/multistream/remoteMediaManager.ts +13 -0
- package/src/reachability/index.ts +9 -0
- package/src/reactions/reactions.type.ts +1 -0
- package/src/reconnection-manager/index.ts +0 -1
- package/src/webinar/index.ts +162 -5
- package/src/webinar/utils.ts +16 -0
- package/test/unit/spec/aiEnableRequest/index.ts +981 -0
- package/test/unit/spec/aiEnableRequest/utils.ts +130 -0
- package/test/unit/spec/annotation/index.ts +69 -7
- package/test/unit/spec/hashTree/hashTree.ts +66 -0
- package/test/unit/spec/hashTree/hashTreeParser.ts +1869 -189
- package/test/unit/spec/interceptors/dataChannelAuthToken.ts +210 -0
- package/test/unit/spec/interceptors/utils.ts +75 -0
- package/test/unit/spec/locus-info/controlsUtils.js +29 -0
- package/test/unit/spec/locus-info/index.js +383 -46
- package/test/unit/spec/media/MediaConnectionAwaiter.ts +41 -1
- package/test/unit/spec/media/properties.ts +12 -3
- package/test/unit/spec/meeting/in-meeting-actions.ts +8 -2
- package/test/unit/spec/meeting/index.js +716 -115
- package/test/unit/spec/meeting/request.js +70 -0
- package/test/unit/spec/meeting/utils.js +438 -26
- package/test/unit/spec/meetings/index.js +652 -31
- package/test/unit/spec/member/index.js +28 -4
- package/test/unit/spec/member/util.js +65 -27
- package/test/unit/spec/multistream/mediaRequestManager.ts +2 -85
- package/test/unit/spec/multistream/remoteMediaManager.ts +30 -0
- package/test/unit/spec/reachability/index.ts +23 -0
- package/test/unit/spec/reconnection-manager/index.js +4 -8
- package/test/unit/spec/webinar/index.ts +348 -36
- package/test/unit/spec/webinar/utils.ts +39 -0
package/src/meeting/util.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {LocalCameraStream, LocalMicrophoneStream} from '@webex/media-helpers';
|
|
2
|
+
import url from 'url';
|
|
2
3
|
|
|
3
4
|
import {cloneDeep} from 'lodash';
|
|
4
5
|
import {MeetingNotActiveError, UserNotJoinedError} from '../common/errors/webex-errors';
|
|
@@ -24,6 +25,7 @@ import PermissionError from '../common/errors/permission';
|
|
|
24
25
|
import PasswordError from '../common/errors/password-error';
|
|
25
26
|
import CaptchaError from '../common/errors/captcha-error';
|
|
26
27
|
import Trigger from '../common/events/trigger-proxy';
|
|
28
|
+
import {ServerRoles} from '../member/types';
|
|
27
29
|
|
|
28
30
|
const MeetingUtil = {
|
|
29
31
|
parseLocusJoin: (response) => {
|
|
@@ -32,6 +34,7 @@ const MeetingUtil = {
|
|
|
32
34
|
// First todo: add check for existance
|
|
33
35
|
parsed.locus = response.body.locus;
|
|
34
36
|
parsed.dataSets = response.body.dataSets;
|
|
37
|
+
parsed.metadata = response.body.metaData;
|
|
35
38
|
parsed.mediaConnections = response.body.mediaConnections;
|
|
36
39
|
parsed.locusUrl = parsed.locus.url;
|
|
37
40
|
parsed.locusId = parsed.locus.url.split('/').pop();
|
|
@@ -47,6 +50,124 @@ const MeetingUtil = {
|
|
|
47
50
|
return parsed;
|
|
48
51
|
},
|
|
49
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Sanitizes a WebSocket URL by extracting only protocol, host, and pathname
|
|
55
|
+
* Returns concatenated protocol + host + pathname for safe logging
|
|
56
|
+
* Note: This is used for logging only; URL matching uses partial matching via _urlsPartiallyMatch
|
|
57
|
+
* @param {string} urlString - The URL to sanitize
|
|
58
|
+
* @returns {string} Sanitized URL or empty string if parsing fails
|
|
59
|
+
*/
|
|
60
|
+
sanitizeWebSocketUrl: (urlString: string): string => {
|
|
61
|
+
if (!urlString || typeof urlString !== 'string') {
|
|
62
|
+
return '';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const parsedUrl = url.parse(urlString);
|
|
67
|
+
const protocol = parsedUrl.protocol || '';
|
|
68
|
+
const host = parsedUrl.host || '';
|
|
69
|
+
|
|
70
|
+
// If we don't have at least protocol and host, it's not a valid URL
|
|
71
|
+
if (!protocol || !host) {
|
|
72
|
+
return '';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const pathname = parsedUrl.pathname || '';
|
|
76
|
+
|
|
77
|
+
// Strip trailing slash if pathname is just '/'
|
|
78
|
+
const normalizedPathname = pathname === '/' ? '' : pathname;
|
|
79
|
+
|
|
80
|
+
return `${protocol}//${host}${normalizedPathname}`;
|
|
81
|
+
} catch (error) {
|
|
82
|
+
LoggerProxy.logger.warn(
|
|
83
|
+
`Meeting:util#sanitizeWebSocketUrl --> unable to parse URL: ${error}`
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
return '';
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Checks if two URLs partially match using an endsWith approach
|
|
92
|
+
* Combines host and pathname, then checks if one ends with the other
|
|
93
|
+
* This handles cases where one URL goes through a proxy (e.g., /webproxy/) while the other is direct
|
|
94
|
+
* @param {string} url1 - First URL to compare
|
|
95
|
+
* @param {string} url2 - Second URL to compare
|
|
96
|
+
* @returns {boolean} True if one URL path ends with the other (partial match), false otherwise
|
|
97
|
+
*/
|
|
98
|
+
_urlsPartiallyMatch: (url1: string, url2: string): boolean => {
|
|
99
|
+
if (!url1 || !url2) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
const parsedUrl1 = url.parse(url1);
|
|
105
|
+
const parsedUrl2 = url.parse(url2);
|
|
106
|
+
|
|
107
|
+
const host1 = parsedUrl1.host || '';
|
|
108
|
+
const host2 = parsedUrl2.host || '';
|
|
109
|
+
const pathname1 = parsedUrl1.pathname || '';
|
|
110
|
+
const pathname2 = parsedUrl2.pathname || '';
|
|
111
|
+
|
|
112
|
+
// If either failed to parse, they don't match
|
|
113
|
+
if (!host1 || !host2 || !pathname1 || !pathname2) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Combine host and pathname for comparison
|
|
118
|
+
const combined1 = host1 + pathname1;
|
|
119
|
+
const combined2 = host2 + pathname2;
|
|
120
|
+
|
|
121
|
+
// Check if one combined path ends with the other (handles proxy URLs)
|
|
122
|
+
return combined1.endsWith(combined2) || combined2.endsWith(combined1);
|
|
123
|
+
} catch (e) {
|
|
124
|
+
LoggerProxy.logger.warn('Meeting:util#_urlsPartiallyMatch --> error comparing URLs', e);
|
|
125
|
+
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Gets socket URL information for metrics, including whether the socket URLs match
|
|
132
|
+
* Uses partial matching to handle proxy URLs (e.g., URLs with /webproxy/ prefix)
|
|
133
|
+
* @param {Object} webex - The webex instance
|
|
134
|
+
* @returns {Object} Object with hasMismatchedSocket, mercurySocketUrl, and deviceSocketUrl properties
|
|
135
|
+
*/
|
|
136
|
+
getSocketUrlInfo: (
|
|
137
|
+
webex: any
|
|
138
|
+
): {hasMismatchedSocket: boolean; mercurySocketUrl: string; deviceSocketUrl: string} => {
|
|
139
|
+
try {
|
|
140
|
+
const mercuryUrl = webex?.internal?.mercury?.socket?.url;
|
|
141
|
+
const deviceUrl = webex?.internal?.device?.webSocketUrl;
|
|
142
|
+
|
|
143
|
+
const sanitizedMercuryUrl = MeetingUtil.sanitizeWebSocketUrl(mercuryUrl);
|
|
144
|
+
const sanitizedDeviceUrl = MeetingUtil.sanitizeWebSocketUrl(deviceUrl);
|
|
145
|
+
|
|
146
|
+
// Only report a mismatch if both URLs are present and they don't match
|
|
147
|
+
// If either URL is missing, we can't determine if there's a mismatch, so return false
|
|
148
|
+
let hasMismatchedSocket = false;
|
|
149
|
+
if (sanitizedMercuryUrl && sanitizedDeviceUrl) {
|
|
150
|
+
hasMismatchedSocket = !MeetingUtil._urlsPartiallyMatch(mercuryUrl, deviceUrl);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
hasMismatchedSocket,
|
|
155
|
+
mercurySocketUrl: sanitizedMercuryUrl,
|
|
156
|
+
deviceSocketUrl: sanitizedDeviceUrl,
|
|
157
|
+
};
|
|
158
|
+
} catch (error) {
|
|
159
|
+
LoggerProxy.logger.warn(
|
|
160
|
+
`Meeting:util#getSocketUrlInfo --> error getting socket URL info: ${error}`
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
hasMismatchedSocket: false,
|
|
165
|
+
mercurySocketUrl: '',
|
|
166
|
+
deviceSocketUrl: '',
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
|
|
50
171
|
remoteUpdateAudioVideo: (meeting, audioMuted?: boolean, videoMuted?: boolean) => {
|
|
51
172
|
if (!meeting) {
|
|
52
173
|
return Promise.reject(new ParameterError('You need a meeting object.'));
|
|
@@ -203,6 +324,7 @@ const MeetingUtil = {
|
|
|
203
324
|
const parsed = MeetingUtil.parseLocusJoin(res);
|
|
204
325
|
meeting.setLocus(parsed);
|
|
205
326
|
meeting.isoLocalClientMeetingJoinTime = res?.headers?.date; // read from header if exist, else fall back to system clock : https://jira-eng-gpk2.cisco.com/jira/browse/SPARK-555657
|
|
327
|
+
const socketUrlInfo = MeetingUtil.getSocketUrlInfo(webex);
|
|
206
328
|
webex.internal.newMetrics.submitClientEvent({
|
|
207
329
|
name: 'client.locus.join.response',
|
|
208
330
|
payload: {
|
|
@@ -210,6 +332,9 @@ const MeetingUtil = {
|
|
|
210
332
|
identifiers: {
|
|
211
333
|
trackingId: res.headers.trackingid,
|
|
212
334
|
},
|
|
335
|
+
eventData: {
|
|
336
|
+
...socketUrlInfo,
|
|
337
|
+
},
|
|
213
338
|
},
|
|
214
339
|
options: {
|
|
215
340
|
meetingId: meeting.id,
|
|
@@ -220,12 +345,19 @@ const MeetingUtil = {
|
|
|
220
345
|
return parsed;
|
|
221
346
|
})
|
|
222
347
|
.catch((err) => {
|
|
348
|
+
const socketUrlInfo = MeetingUtil.getSocketUrlInfo(webex);
|
|
223
349
|
webex.internal.newMetrics.submitClientEvent({
|
|
224
350
|
name: 'client.locus.join.response',
|
|
225
351
|
payload: {
|
|
226
352
|
identifiers: {meetingLookupUrl: meeting.meetingInfo?.meetingLookupUrl},
|
|
353
|
+
eventData: {
|
|
354
|
+
...socketUrlInfo,
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
options: {
|
|
358
|
+
meetingId: meeting.id,
|
|
359
|
+
rawError: err,
|
|
227
360
|
},
|
|
228
|
-
options: {meetingId: meeting.id, rawError: err},
|
|
229
361
|
});
|
|
230
362
|
|
|
231
363
|
throw err;
|
|
@@ -237,6 +369,7 @@ const MeetingUtil = {
|
|
|
237
369
|
meeting.stopPeriodicLogUpload();
|
|
238
370
|
|
|
239
371
|
meeting.breakouts.cleanUp();
|
|
372
|
+
meeting.webinar.cleanUp();
|
|
240
373
|
meeting.simultaneousInterpretation.cleanUp();
|
|
241
374
|
meeting.locusMediaRequest = undefined;
|
|
242
375
|
|
|
@@ -261,8 +394,10 @@ const MeetingUtil = {
|
|
|
261
394
|
.then(() => meeting.stopKeepAlive())
|
|
262
395
|
.then(() => {
|
|
263
396
|
if (meeting.config?.enableAutomaticLLM) {
|
|
264
|
-
meeting.
|
|
397
|
+
return meeting.cleanupLLMConneciton({throwOnError: false});
|
|
265
398
|
}
|
|
399
|
+
|
|
400
|
+
return undefined;
|
|
266
401
|
});
|
|
267
402
|
},
|
|
268
403
|
|
|
@@ -528,6 +663,11 @@ const MeetingUtil = {
|
|
|
528
663
|
displayHints.includes(DISPLAY_HINTS.LEAVE_TRANSFER_HOST_END_MEETING) ||
|
|
529
664
|
displayHints.includes(DISPLAY_HINTS.LEAVE_END_MEETING),
|
|
530
665
|
|
|
666
|
+
requireHostEndMeetingBeforeLeave: (displayHints) =>
|
|
667
|
+
displayHints.includes(DISPLAY_HINTS.REQUIRE_HOST_END_MEETING_BEFORE_LEAVE) ||
|
|
668
|
+
(!displayHints.includes(DISPLAY_HINTS.LEAVE_TRANSFER_HOST_END_MEETING) &&
|
|
669
|
+
displayHints.includes(DISPLAY_HINTS.END_MEETING)),
|
|
670
|
+
|
|
531
671
|
canManageBreakout: (displayHints) => displayHints.includes(DISPLAY_HINTS.BREAKOUT_MANAGEMENT),
|
|
532
672
|
|
|
533
673
|
canStartBreakout: (displayHints) => !displayHints.includes(DISPLAY_HINTS.DISABLE_BREAKOUT_START),
|
|
@@ -772,6 +912,24 @@ const MeetingUtil = {
|
|
|
772
912
|
return locusDeltaRequest;
|
|
773
913
|
},
|
|
774
914
|
|
|
915
|
+
canAttendeeRequestAiAssistantEnabled: (displayHints = [], roles: any[] = []) => {
|
|
916
|
+
const isHostOrCoHost =
|
|
917
|
+
roles.includes(ServerRoles.Cohost) || roles.includes(ServerRoles.Moderator);
|
|
918
|
+
|
|
919
|
+
if (isHostOrCoHost) {
|
|
920
|
+
return false;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
if (displayHints.includes(DISPLAY_HINTS.ATTENDEE_REQUEST_AI_ASSISTANT_ENABLED)) {
|
|
924
|
+
return true;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
return false;
|
|
928
|
+
},
|
|
929
|
+
|
|
930
|
+
attendeeRequestAiAssistantDeclinedAll: (displayHints = []) =>
|
|
931
|
+
displayHints.includes(DISPLAY_HINTS.ATTENDEE_REQUEST_AI_ASSISTANT_DECLINED_ALL),
|
|
932
|
+
|
|
775
933
|
selfSupportsFeature: (feature: SELF_POLICY, userPolicies: Record<SELF_POLICY, boolean>) => {
|
|
776
934
|
if (!userPolicies) {
|
|
777
935
|
return true;
|
package/src/meetings/index.ts
CHANGED
|
@@ -69,6 +69,7 @@ import JoinForbiddenError from '../common/errors/join-forbidden-error';
|
|
|
69
69
|
import {HashTreeMessage} from '../hashTree/hashTreeParser';
|
|
70
70
|
import {HashTreeObject} from '../hashTree/types';
|
|
71
71
|
import {isSelf} from '../hashTree/utils';
|
|
72
|
+
import {createLocusFromHashTreeMessage} from '../locus-info';
|
|
72
73
|
|
|
73
74
|
let mediaLogger;
|
|
74
75
|
|
|
@@ -195,6 +196,8 @@ export default class Meetings extends WebexPlugin {
|
|
|
195
196
|
preferredWebexSite: any;
|
|
196
197
|
reachability: Reachability;
|
|
197
198
|
registered: any;
|
|
199
|
+
registrationPromise: Promise<void>;
|
|
200
|
+
unregistrationPromise: Promise<void>;
|
|
198
201
|
request: any;
|
|
199
202
|
geoHintInfo: any;
|
|
200
203
|
meetingInfo: any;
|
|
@@ -520,41 +523,45 @@ export default class Meetings extends WebexPlugin {
|
|
|
520
523
|
// };
|
|
521
524
|
// rather then locus object change to locus url
|
|
522
525
|
|
|
523
|
-
if (data.eventType
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
return;
|
|
535
|
-
}
|
|
526
|
+
if (data.eventType === LOCUSEVENT.HASH_TREE_DATA_UPDATED) {
|
|
527
|
+
// We're about to create a new meeting object from this hash tree message.
|
|
528
|
+
// There is some existing (pre-hash trees) SDK logic here that requires a locus object
|
|
529
|
+
// (at the very minimum we need locus.url to be set)
|
|
530
|
+
// so we try to create locus from the received hash tree message
|
|
531
|
+
// it will not be complete, in most cases it will only have the self part, but that's still better than nothing
|
|
532
|
+
const {locus} = createLocusFromHashTreeMessage(data.stateElementsMessage);
|
|
533
|
+
|
|
534
|
+
data.locus = locus;
|
|
535
|
+
}
|
|
536
536
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
)
|
|
546
|
-
// just ignore the event as its already ended and not active
|
|
547
|
-
LoggerProxy.logger.warn(
|
|
548
|
-
'Meetings:index#handleLocusEvent --> Locus event received for meeting, after it was ended.'
|
|
549
|
-
);
|
|
537
|
+
if (
|
|
538
|
+
data.locus &&
|
|
539
|
+
data.locus.fullState &&
|
|
540
|
+
data.locus.fullState.state === LOCUS.STATE.INACTIVE
|
|
541
|
+
) {
|
|
542
|
+
// just ignore the event as its already ended and not active
|
|
543
|
+
LoggerProxy.logger.warn(
|
|
544
|
+
'Meetings:index#handleLocusEvent --> Locus event received for meeting, after it was ended.'
|
|
545
|
+
);
|
|
550
546
|
|
|
551
|
-
|
|
552
|
-
}
|
|
547
|
+
return;
|
|
553
548
|
}
|
|
554
549
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
550
|
+
// When its wireless share or guest and user leaves the meeting we dont have to keep the meeting object
|
|
551
|
+
// Any future events will be neglected
|
|
552
|
+
|
|
553
|
+
if (
|
|
554
|
+
data.locus &&
|
|
555
|
+
data.locus.self &&
|
|
556
|
+
data.locus.self.state === _LEFT_ &&
|
|
557
|
+
data.locus.self.removed === true
|
|
558
|
+
) {
|
|
559
|
+
// just ignore the event as its already ended and not active
|
|
560
|
+
LoggerProxy.logger.warn(
|
|
561
|
+
'Meetings:index#handleLocusEvent --> Locus event received for meeting, after it was ended.'
|
|
562
|
+
);
|
|
563
|
+
|
|
564
|
+
return;
|
|
558
565
|
}
|
|
559
566
|
|
|
560
567
|
this.create(data.locus, DESTINATION_TYPE.LOCUS_ID, useRandomDelayForInfo)
|
|
@@ -624,7 +631,7 @@ export default class Meetings extends WebexPlugin {
|
|
|
624
631
|
}
|
|
625
632
|
|
|
626
633
|
/**
|
|
627
|
-
* handles locus events through mercury that are not roap
|
|
634
|
+
* handles locus events through mercury that are not roap or approval request events
|
|
628
635
|
* @param {Object} envelope
|
|
629
636
|
* @param {Object} envelope.data
|
|
630
637
|
* @param {String} envelope.data.eventType
|
|
@@ -637,7 +644,11 @@ export default class Meetings extends WebexPlugin {
|
|
|
637
644
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
|
638
645
|
const {eventType} = data;
|
|
639
646
|
|
|
640
|
-
if (
|
|
647
|
+
if (
|
|
648
|
+
eventType &&
|
|
649
|
+
eventType !== LOCUSEVENT.MESSAGE_ROAP &&
|
|
650
|
+
eventType !== LOCUSEVENT.APPROVAL_REQUEST
|
|
651
|
+
) {
|
|
641
652
|
this.handleLocusEvent(data, true);
|
|
642
653
|
}
|
|
643
654
|
}
|
|
@@ -929,9 +940,20 @@ export default class Meetings extends WebexPlugin {
|
|
|
929
940
|
* @returns {Promise} A promise that resolves when the step is completed.
|
|
930
941
|
*/
|
|
931
942
|
executeRegistrationStep(step: () => Promise<any>, stepName: string) {
|
|
932
|
-
return step()
|
|
933
|
-
|
|
934
|
-
|
|
943
|
+
return step()
|
|
944
|
+
.then(() => {
|
|
945
|
+
LoggerProxy.logger.info(
|
|
946
|
+
`Meetings:index#executeRegistrationStep --> INFO, ${stepName} completed`
|
|
947
|
+
);
|
|
948
|
+
this.registrationStatus[stepName] = true;
|
|
949
|
+
})
|
|
950
|
+
.catch((error) => {
|
|
951
|
+
LoggerProxy.logger.error(
|
|
952
|
+
`Meetings:index#executeRegistrationStep --> ERROR, ${stepName} failed: ${error.message}`
|
|
953
|
+
);
|
|
954
|
+
|
|
955
|
+
return Promise.reject(error);
|
|
956
|
+
});
|
|
935
957
|
}
|
|
936
958
|
|
|
937
959
|
/**
|
|
@@ -944,7 +966,33 @@ export default class Meetings extends WebexPlugin {
|
|
|
944
966
|
* @memberof Meetings
|
|
945
967
|
*/
|
|
946
968
|
public register(deviceRegistrationOptions?: DeviceRegistrationOptions): Promise<any> {
|
|
947
|
-
this.
|
|
969
|
+
if (this.unregistrationPromise) {
|
|
970
|
+
LoggerProxy.logger.info(
|
|
971
|
+
'Meetings:index#register --> INFO, Meetings plugin unregistration in progress, waiting to register'
|
|
972
|
+
);
|
|
973
|
+
|
|
974
|
+
this.registrationPromise = this.unregistrationPromise
|
|
975
|
+
.catch(() => {}) // It doesn't matter what happened during unregistration
|
|
976
|
+
.finally(() => {
|
|
977
|
+
LoggerProxy.logger.info(
|
|
978
|
+
'Meetings:index#register --> INFO, Meetings plugin unregistration completed, proceeding to register'
|
|
979
|
+
);
|
|
980
|
+
|
|
981
|
+
this.registrationPromise = null;
|
|
982
|
+
|
|
983
|
+
return this.register(deviceRegistrationOptions);
|
|
984
|
+
});
|
|
985
|
+
|
|
986
|
+
return this.registrationPromise;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
if (this.registrationPromise) {
|
|
990
|
+
LoggerProxy.logger.info(
|
|
991
|
+
'Meetings:index#register --> INFO, Meetings plugin registration in progress, returning existing promise'
|
|
992
|
+
);
|
|
993
|
+
|
|
994
|
+
return this.registrationPromise;
|
|
995
|
+
}
|
|
948
996
|
|
|
949
997
|
// @ts-ignore
|
|
950
998
|
if (!this.webex.canAuthorize) {
|
|
@@ -963,7 +1011,11 @@ export default class Meetings extends WebexPlugin {
|
|
|
963
1011
|
return Promise.resolve();
|
|
964
1012
|
}
|
|
965
1013
|
|
|
966
|
-
|
|
1014
|
+
LoggerProxy.logger.info('Meetings:index#register --> INFO, Registering Meetings plugin');
|
|
1015
|
+
|
|
1016
|
+
this.registrationStatus = clone(INITIAL_REGISTRATION_STATUS);
|
|
1017
|
+
|
|
1018
|
+
this.registrationPromise = Promise.all([
|
|
967
1019
|
this.executeRegistrationStep(() => this.fetchUserPreferredWebexSite(), 'fetchWebexSite'),
|
|
968
1020
|
this.executeRegistrationStep(() => this.getGeoHint(), 'getGeoHint'),
|
|
969
1021
|
this.executeRegistrationStep(
|
|
@@ -1022,7 +1074,12 @@ export default class Meetings extends WebexPlugin {
|
|
|
1022
1074
|
});
|
|
1023
1075
|
|
|
1024
1076
|
return Promise.reject(error);
|
|
1077
|
+
})
|
|
1078
|
+
.finally(() => {
|
|
1079
|
+
this.registrationPromise = null;
|
|
1025
1080
|
});
|
|
1081
|
+
|
|
1082
|
+
return this.registrationPromise;
|
|
1026
1083
|
}
|
|
1027
1084
|
|
|
1028
1085
|
/**
|
|
@@ -1034,6 +1091,35 @@ export default class Meetings extends WebexPlugin {
|
|
|
1034
1091
|
* @memberof Meetings
|
|
1035
1092
|
*/
|
|
1036
1093
|
unregister() {
|
|
1094
|
+
if (this.unregistrationPromise) {
|
|
1095
|
+
LoggerProxy.logger.info(
|
|
1096
|
+
'Meetings:index#unregister --> INFO, Meetings plugin unregistration in progress, returning existing promise'
|
|
1097
|
+
);
|
|
1098
|
+
|
|
1099
|
+
return this.unregistrationPromise;
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
if (this.registrationPromise) {
|
|
1103
|
+
LoggerProxy.logger.info(
|
|
1104
|
+
'Meetings:index#unregister --> INFO, Meetings plugin registration in progress, waiting to unregister'
|
|
1105
|
+
);
|
|
1106
|
+
|
|
1107
|
+
// Wait for registration to complete (success or failure), then call unregister again
|
|
1108
|
+
this.unregistrationPromise = this.registrationPromise
|
|
1109
|
+
.catch(() => {}) // It doesn't matter what happened during registration
|
|
1110
|
+
.finally(() => {
|
|
1111
|
+
LoggerProxy.logger.info(
|
|
1112
|
+
'Meetings:index#unregister --> INFO, Meetings plugin registration completed, proceeding to unregister'
|
|
1113
|
+
);
|
|
1114
|
+
|
|
1115
|
+
this.unregistrationPromise = null;
|
|
1116
|
+
|
|
1117
|
+
return this.unregister();
|
|
1118
|
+
});
|
|
1119
|
+
|
|
1120
|
+
return this.unregistrationPromise;
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1037
1123
|
if (!this.registered) {
|
|
1038
1124
|
LoggerProxy.logger.info(
|
|
1039
1125
|
'Meetings:index#unregister --> INFO, Meetings plugin already unregistered'
|
|
@@ -1044,10 +1130,14 @@ export default class Meetings extends WebexPlugin {
|
|
|
1044
1130
|
|
|
1045
1131
|
this.stopListeningForEvents();
|
|
1046
1132
|
|
|
1047
|
-
|
|
1133
|
+
this.unregistrationPromise =
|
|
1048
1134
|
// @ts-ignore
|
|
1049
1135
|
this.webex.internal.mercury
|
|
1050
|
-
|
|
1136
|
+
// Use code 3050 with a non-reconnecting reason to prevent Mercury auto-reconnect
|
|
1137
|
+
// during unregister. Without this, disconnect() defaults to code 1000/"Done" which
|
|
1138
|
+
// force-closes as "Done (forced)" - a normalReconnectReason that triggers auto-reconnect,
|
|
1139
|
+
// causing a race condition with device.unregister().
|
|
1140
|
+
.disconnect({code: 3050, reason: 'meetings unregister'})
|
|
1051
1141
|
// @ts-ignore
|
|
1052
1142
|
.then(() => this.webex.internal.device.unregister())
|
|
1053
1143
|
.catch((error) => {
|
|
@@ -1077,7 +1167,11 @@ export default class Meetings extends WebexPlugin {
|
|
|
1077
1167
|
this.registered = false;
|
|
1078
1168
|
this.registrationStatus = clone(INITIAL_REGISTRATION_STATUS);
|
|
1079
1169
|
})
|
|
1080
|
-
|
|
1170
|
+
.finally(() => {
|
|
1171
|
+
this.unregistrationPromise = null;
|
|
1172
|
+
});
|
|
1173
|
+
|
|
1174
|
+
return this.unregistrationPromise;
|
|
1081
1175
|
}
|
|
1082
1176
|
|
|
1083
1177
|
/**
|
package/src/member/index.ts
CHANGED
|
@@ -13,6 +13,7 @@ export type MemberId = string;
|
|
|
13
13
|
export default class Member {
|
|
14
14
|
associatedUser: MemberId | null; // deprecated, use associatedUsers instead
|
|
15
15
|
associatedUsers: Set<MemberId>; // users associated with this device, empty if this member is not a device
|
|
16
|
+
canApproveAIEnablement: boolean;
|
|
16
17
|
canReclaimHost: boolean;
|
|
17
18
|
id: MemberId;
|
|
18
19
|
isAudioMuted: any;
|
|
@@ -291,6 +292,14 @@ export default class Member {
|
|
|
291
292
|
*/
|
|
292
293
|
this.isPresenterAssignmentProhibited = null;
|
|
293
294
|
|
|
295
|
+
/**
|
|
296
|
+
* @instance
|
|
297
|
+
* @type {Boolean}
|
|
298
|
+
* @public
|
|
299
|
+
* @memberof Member
|
|
300
|
+
*/
|
|
301
|
+
this.canApproveAIEnablement = null;
|
|
302
|
+
|
|
294
303
|
/**
|
|
295
304
|
* @instance
|
|
296
305
|
* @type {Boolean}
|
|
@@ -360,6 +369,7 @@ export default class Member {
|
|
|
360
369
|
MemberUtil.isModeratorAssignmentProhibited(participant);
|
|
361
370
|
this.isPresenterAssignmentProhibited =
|
|
362
371
|
MemberUtil.isPresenterAssignmentProhibited(participant);
|
|
372
|
+
this.canApproveAIEnablement = MemberUtil.canApproveAIEnablement(participant);
|
|
363
373
|
this.processStatus(participant);
|
|
364
374
|
this.processRoles(participant);
|
|
365
375
|
// must be done last
|
package/src/member/util.ts
CHANGED
|
@@ -42,6 +42,18 @@ const MemberUtil = {
|
|
|
42
42
|
return participant.canReclaimHostRole || false;
|
|
43
43
|
},
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* @param {Object} participant - The locus participant object.
|
|
47
|
+
* @returns {Boolean}
|
|
48
|
+
*/
|
|
49
|
+
canApproveAIEnablement: (participant) => {
|
|
50
|
+
if (!participant) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return !participant.attendeeRequestAiAssistantNotAllowed;
|
|
55
|
+
},
|
|
56
|
+
|
|
45
57
|
/**
|
|
46
58
|
* @param {Object} participant - The locus participant object.
|
|
47
59
|
* @returns {[ServerRoleShape]}
|
package/src/metrics/constants.ts
CHANGED
|
@@ -90,6 +90,7 @@ const BEHAVIORAL_METRICS = {
|
|
|
90
90
|
MEDIA_ISSUE_DETECTED: 'js_sdk_media_issue_detected',
|
|
91
91
|
LOCUS_CLASSIC_VS_HASH_TREE_MISMATCH: 'js_sdk_locus_classic_vs_hash_tree_mismatch',
|
|
92
92
|
LOCUS_HASH_TREE_UNSUPPORTED_OPERATION: 'js_sdk_locus_hash_tree_unsupported_operation',
|
|
93
|
+
MEDIA_STILL_NOT_CONNECTED: 'js_sdk_media_still_not_connected',
|
|
93
94
|
};
|
|
94
95
|
|
|
95
96
|
export {BEHAVIORAL_METRICS as default};
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
RecommendedOpusBitrates,
|
|
11
11
|
NamedMediaGroup,
|
|
12
12
|
} from '@webex/internal-media-core';
|
|
13
|
-
import {cloneDeepWith, debounce
|
|
13
|
+
import {cloneDeepWith, debounce} from 'lodash';
|
|
14
14
|
|
|
15
15
|
import LoggerProxy from '../common/logs/logger-proxy';
|
|
16
16
|
|
|
@@ -94,8 +94,6 @@ export class MediaRequestManager {
|
|
|
94
94
|
|
|
95
95
|
private debouncedSourceUpdateListener: () => void;
|
|
96
96
|
|
|
97
|
-
private previousStreamRequests: Array<StreamRequest> = [];
|
|
98
|
-
|
|
99
97
|
private trimRequestsToNumOfSources: boolean;
|
|
100
98
|
private numTotalSources: number;
|
|
101
99
|
private numLiveSources: number;
|
|
@@ -161,36 +159,6 @@ export class MediaRequestManager {
|
|
|
161
159
|
}
|
|
162
160
|
}
|
|
163
161
|
|
|
164
|
-
/**
|
|
165
|
-
* Returns true if two stream requests are the same, false otherwise.
|
|
166
|
-
*
|
|
167
|
-
* @param {StreamRequest} streamRequestA - Stream request A for comparison.
|
|
168
|
-
* @param {StreamRequest} streamRequestB - Stream request B for comparison.
|
|
169
|
-
* @returns {boolean} - Whether they are equal.
|
|
170
|
-
*/
|
|
171
|
-
// eslint-disable-next-line class-methods-use-this
|
|
172
|
-
public isEqual(streamRequestA: StreamRequest, streamRequestB: StreamRequest) {
|
|
173
|
-
return (
|
|
174
|
-
JSON.stringify(streamRequestA._toJmpStreamRequest()) ===
|
|
175
|
-
JSON.stringify(streamRequestB._toJmpStreamRequest())
|
|
176
|
-
);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Compares new stream requests to previous ones and determines
|
|
181
|
-
* if they are the same.
|
|
182
|
-
*
|
|
183
|
-
* @param {StreamRequest[]} newRequests - Array with new requests.
|
|
184
|
-
* @returns {boolean} - True if they are equal, false otherwise.
|
|
185
|
-
*/
|
|
186
|
-
private checkIsNewRequestsEqualToPrev(newRequests: StreamRequest[]) {
|
|
187
|
-
return (
|
|
188
|
-
!isEmpty(this.previousStreamRequests) &&
|
|
189
|
-
this.previousStreamRequests.length === newRequests.length &&
|
|
190
|
-
this.previousStreamRequests.every((req, idx) => this.isEqual(req, newRequests[idx]))
|
|
191
|
-
);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
162
|
/**
|
|
195
163
|
* Returns the maxPayloadBitsPerSecond per Stream
|
|
196
164
|
*
|
|
@@ -230,15 +198,6 @@ export class MediaRequestManager {
|
|
|
230
198
|
return (mediaRequest.codecInfo.maxFs * maxFps) / 100;
|
|
231
199
|
}
|
|
232
200
|
|
|
233
|
-
/**
|
|
234
|
-
* Clears the previous stream requests.
|
|
235
|
-
*
|
|
236
|
-
* @returns {void}
|
|
237
|
-
*/
|
|
238
|
-
public clearPreviousRequests(): void {
|
|
239
|
-
this.previousStreamRequests = [];
|
|
240
|
-
}
|
|
241
|
-
|
|
242
201
|
/** Modifies the passed in clientRequests and makes sure that in total they don't ask
|
|
243
202
|
* for more streams than there are available.
|
|
244
203
|
*
|
|
@@ -356,7 +315,7 @@ export class MediaRequestManager {
|
|
|
356
315
|
mr.receiveSlots.map((receiveSlot) => receiveSlot.wcmeReceiveSlot),
|
|
357
316
|
this.getMaxPayloadBitsPerSecond(mr),
|
|
358
317
|
mr.codecInfo && [
|
|
359
|
-
|
|
318
|
+
WcmeCodecInfo.fromH264(
|
|
360
319
|
0x80,
|
|
361
320
|
new H264Codec(
|
|
362
321
|
mr.codecInfo.maxFs,
|
|
@@ -372,17 +331,8 @@ export class MediaRequestManager {
|
|
|
372
331
|
}
|
|
373
332
|
});
|
|
374
333
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
if (!this.checkIsNewRequestsEqualToPrev(streamRequests)) {
|
|
378
|
-
this.sendMediaRequestsCallback(streamRequests);
|
|
379
|
-
this.previousStreamRequests = streamRequests;
|
|
380
|
-
LoggerProxy.logger.info(`multistream:sendRequests --> media requests sent. `);
|
|
381
|
-
} else {
|
|
382
|
-
LoggerProxy.logger.info(
|
|
383
|
-
`multistream:sendRequests --> detected duplicate WCME requests, skipping them... `
|
|
384
|
-
);
|
|
385
|
-
}
|
|
334
|
+
this.sendMediaRequestsCallback(streamRequests);
|
|
335
|
+
LoggerProxy.logger.info(`multistream:sendRequests --> media requests sent. `);
|
|
386
336
|
}
|
|
387
337
|
|
|
388
338
|
public addRequest(mediaRequest: MediaRequest, commit = true): MediaRequestId {
|
|
@@ -67,6 +67,18 @@ const AllEqualLayout: VideoLayout = {
|
|
|
67
67
|
],
|
|
68
68
|
};
|
|
69
69
|
|
|
70
|
+
// An "all equal" grid, with size up to 5 x 5 = 25:
|
|
71
|
+
const AllEqual25Layout: VideoLayout = {
|
|
72
|
+
activeSpeakerVideoPaneGroups: [
|
|
73
|
+
{
|
|
74
|
+
id: 'main',
|
|
75
|
+
numPanes: 25,
|
|
76
|
+
size: 'best',
|
|
77
|
+
priority: 255,
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
};
|
|
81
|
+
|
|
70
82
|
// A layout with just a single remote active speaker video pane:
|
|
71
83
|
const SingleLayout: VideoLayout = {
|
|
72
84
|
activeSpeakerVideoPaneGroups: [
|
|
@@ -164,6 +176,7 @@ export const DefaultConfiguration: Configuration = {
|
|
|
164
176
|
|
|
165
177
|
layouts: {
|
|
166
178
|
AllEqual: AllEqualLayout,
|
|
179
|
+
AllEqual25: AllEqual25Layout,
|
|
167
180
|
OnePlusFive: OnePlusFiveLayout,
|
|
168
181
|
Single: SingleLayout,
|
|
169
182
|
Stage: Stage2x2With6ThumbnailsLayout,
|