@webex/plugin-meetings 3.12.0-next.5 → 3.12.0-next.50
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/AGENTS.md +9 -0
- package/dist/aiEnableRequest/index.js +15 -2
- package/dist/aiEnableRequest/index.js.map +1 -1
- package/dist/breakouts/breakout.js +6 -2
- package/dist/breakouts/breakout.js.map +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/constants.js +6 -3
- package/dist/constants.js.map +1 -1
- package/dist/controls-options-manager/constants.js +11 -1
- package/dist/controls-options-manager/constants.js.map +1 -1
- package/dist/controls-options-manager/index.js +38 -24
- package/dist/controls-options-manager/index.js.map +1 -1
- package/dist/controls-options-manager/util.js +91 -0
- package/dist/controls-options-manager/util.js.map +1 -1
- package/dist/hashTree/constants.js +10 -1
- package/dist/hashTree/constants.js.map +1 -1
- package/dist/hashTree/hashTreeParser.js +593 -358
- package/dist/hashTree/hashTreeParser.js.map +1 -1
- package/dist/hashTree/utils.js +22 -0
- package/dist/hashTree/utils.js.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/interceptors/locusRetry.js +23 -8
- package/dist/interceptors/locusRetry.js.map +1 -1
- package/dist/interpretation/index.js +10 -1
- package/dist/interpretation/index.js.map +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/controlsUtils.js +4 -1
- package/dist/locus-info/controlsUtils.js.map +1 -1
- package/dist/locus-info/index.js +277 -86
- package/dist/locus-info/index.js.map +1 -1
- package/dist/locus-info/types.js +16 -0
- package/dist/locus-info/types.js.map +1 -1
- package/dist/media/properties.js +1 -0
- package/dist/media/properties.js.map +1 -1
- package/dist/meeting/in-meeting-actions.js +3 -1
- package/dist/meeting/in-meeting-actions.js.map +1 -1
- package/dist/meeting/index.js +842 -521
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/util.js +19 -2
- package/dist/meeting/util.js.map +1 -1
- package/dist/meetings/index.js +199 -77
- package/dist/meetings/index.js.map +1 -1
- package/dist/meetings/meetings.types.js +6 -1
- package/dist/meetings/meetings.types.js.map +1 -1
- package/dist/meetings/request.js +39 -0
- package/dist/meetings/request.js.map +1 -1
- package/dist/meetings/util.js +67 -5
- package/dist/meetings/util.js.map +1 -1
- package/dist/member/index.js +10 -0
- package/dist/member/index.js.map +1 -1
- package/dist/member/types.js.map +1 -1
- package/dist/member/util.js +3 -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/recording-controller/index.js +1 -3
- package/dist/recording-controller/index.js.map +1 -1
- package/dist/types/config.d.ts +1 -0
- package/dist/types/constants.d.ts +2 -0
- package/dist/types/controls-options-manager/constants.d.ts +6 -1
- package/dist/types/controls-options-manager/index.d.ts +10 -0
- package/dist/types/hashTree/constants.d.ts +1 -0
- package/dist/types/hashTree/hashTreeParser.d.ts +61 -15
- package/dist/types/hashTree/utils.d.ts +11 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/interceptors/locusRetry.d.ts +4 -4
- package/dist/types/locus-info/index.d.ts +46 -6
- package/dist/types/locus-info/types.d.ts +17 -1
- package/dist/types/media/properties.d.ts +1 -0
- package/dist/types/meeting/in-meeting-actions.d.ts +2 -0
- package/dist/types/meeting/index.d.ts +70 -1
- package/dist/types/meeting/util.d.ts +8 -0
- package/dist/types/meetings/index.d.ts +18 -1
- package/dist/types/meetings/meetings.types.d.ts +15 -0
- package/dist/types/meetings/request.d.ts +14 -0
- package/dist/types/member/index.d.ts +1 -0
- package/dist/types/member/types.d.ts +1 -0
- package/dist/types/member/util.d.ts +1 -0
- package/dist/types/metrics/constants.d.ts +1 -0
- package/dist/webinar/index.js +361 -235
- package/dist/webinar/index.js.map +1 -1
- package/package.json +22 -22
- package/src/aiEnableRequest/index.ts +16 -0
- package/src/breakouts/breakout.ts +2 -1
- package/src/config.ts +1 -0
- package/src/constants.ts +5 -1
- package/src/controls-options-manager/constants.ts +14 -1
- package/src/controls-options-manager/index.ts +47 -24
- package/src/controls-options-manager/util.ts +81 -1
- package/src/hashTree/constants.ts +9 -0
- package/src/hashTree/hashTreeParser.ts +306 -160
- package/src/hashTree/utils.ts +17 -0
- package/src/index.ts +5 -0
- package/src/interceptors/locusRetry.ts +25 -4
- package/src/interpretation/index.ts +25 -8
- package/src/locus-info/controlsUtils.ts +3 -1
- package/src/locus-info/index.ts +276 -93
- package/src/locus-info/types.ts +19 -1
- package/src/media/properties.ts +1 -0
- package/src/meeting/in-meeting-actions.ts +4 -0
- package/src/meeting/index.ts +315 -26
- package/src/meeting/util.ts +20 -2
- package/src/meetings/index.ts +104 -43
- package/src/meetings/meetings.types.ts +19 -0
- package/src/meetings/request.ts +43 -0
- package/src/meetings/util.ts +80 -1
- package/src/member/index.ts +10 -0
- package/src/member/types.ts +1 -0
- package/src/member/util.ts +3 -0
- package/src/metrics/constants.ts +1 -0
- package/src/recording-controller/index.ts +1 -2
- package/src/webinar/index.ts +162 -21
- package/test/unit/spec/aiEnableRequest/index.ts +86 -0
- package/test/unit/spec/breakouts/breakout.ts +7 -3
- package/test/unit/spec/controls-options-manager/index.js +140 -29
- package/test/unit/spec/controls-options-manager/util.js +165 -0
- package/test/unit/spec/hashTree/hashTreeParser.ts +1294 -191
- package/test/unit/spec/hashTree/utils.ts +88 -1
- package/test/unit/spec/interceptors/locusRetry.ts +205 -4
- package/test/unit/spec/interpretation/index.ts +26 -4
- package/test/unit/spec/locus-info/controlsUtils.js +172 -57
- package/test/unit/spec/locus-info/index.js +443 -81
- package/test/unit/spec/meeting/in-meeting-actions.ts +2 -0
- package/test/unit/spec/meeting/index.js +836 -41
- package/test/unit/spec/meeting/muteState.js +3 -0
- package/test/unit/spec/meeting/utils.js +33 -0
- package/test/unit/spec/meetings/index.js +275 -10
- package/test/unit/spec/meetings/request.js +141 -0
- package/test/unit/spec/meetings/utils.js +161 -0
- package/test/unit/spec/member/index.js +7 -0
- package/test/unit/spec/member/util.js +24 -0
- package/test/unit/spec/recording-controller/index.js +9 -8
- package/test/unit/spec/webinar/index.ts +141 -16
package/src/meetings/index.ts
CHANGED
|
@@ -55,10 +55,12 @@ import PasswordError from '../common/errors/password-error';
|
|
|
55
55
|
import CaptchaError from '../common/errors/captcha-error';
|
|
56
56
|
import MeetingCollection from './collection';
|
|
57
57
|
import {
|
|
58
|
+
FetchSitePreferencesMeViaSiteOptions,
|
|
58
59
|
MEETING_KEY,
|
|
59
60
|
INoiseReductionEffect,
|
|
60
61
|
IVirtualBackgroundEffect,
|
|
61
62
|
MeetingRegistrationStatus,
|
|
63
|
+
SitePreferencesResponse,
|
|
62
64
|
} from './meetings.types';
|
|
63
65
|
import MeetingsUtil from './util';
|
|
64
66
|
import PermissionError from '../common/errors/permission';
|
|
@@ -69,7 +71,9 @@ import JoinForbiddenError from '../common/errors/join-forbidden-error';
|
|
|
69
71
|
import {HashTreeMessage} from '../hashTree/hashTreeParser';
|
|
70
72
|
import {HashTreeObject} from '../hashTree/types';
|
|
71
73
|
import {isSelf} from '../hashTree/utils';
|
|
74
|
+
|
|
72
75
|
import {createLocusFromHashTreeMessage, findMeetingForHashTreeMessage} from '../locus-info';
|
|
76
|
+
import {LocusDTO} from '../locus-info/types';
|
|
73
77
|
|
|
74
78
|
let mediaLogger;
|
|
75
79
|
|
|
@@ -313,7 +317,7 @@ export default class Meetings extends WebexPlugin {
|
|
|
313
317
|
const breakoutLocus = this.meetingCollection.getActiveBreakoutLocus(breakoutUrl);
|
|
314
318
|
|
|
315
319
|
const isSelfJoined = newLocus?.self?.state === _JOINED_;
|
|
316
|
-
const isSelfMoved = newLocus
|
|
320
|
+
const isSelfMoved = MeetingsUtil.isSelfMovedOrBreakoutEnded(newLocus);
|
|
317
321
|
// @ts-ignore
|
|
318
322
|
const deviceFromNewLocus = MeetingsUtil.getThisDevice(newLocus, this.webex.internal.device.url);
|
|
319
323
|
const isResourceMovedOnThisDevice =
|
|
@@ -390,7 +394,7 @@ export default class Meetings extends WebexPlugin {
|
|
|
390
394
|
private isNeedHandleLocusDTO(meeting: any, newLocus: any) {
|
|
391
395
|
if (newLocus) {
|
|
392
396
|
const isNewLocusAsBreakout = MeetingsUtil.isBreakoutLocusDTO(newLocus);
|
|
393
|
-
const isSelfMoved = newLocus
|
|
397
|
+
const isSelfMoved = MeetingsUtil.isSelfMovedOrBreakoutEnded(newLocus);
|
|
394
398
|
const isSelfMovedToLobby =
|
|
395
399
|
newLocus?.self?.devices[0]?.intent?.reason === _ON_HOLD_LOBBY_ &&
|
|
396
400
|
newLocus?.self?.devices[0]?.intent?.type === _WAIT_;
|
|
@@ -435,14 +439,11 @@ export default class Meetings extends WebexPlugin {
|
|
|
435
439
|
if (existingMeeting) {
|
|
436
440
|
return existingMeeting;
|
|
437
441
|
}
|
|
438
|
-
|
|
439
442
|
if (data.eventType === LOCUSEVENT.HASH_TREE_DATA_UPDATED) {
|
|
440
443
|
// need to check if maybe this event indicates a move to/from breakout
|
|
441
444
|
const meetingForHashTreeMessage = findMeetingForHashTreeMessage(
|
|
442
|
-
data
|
|
443
|
-
this.meetingCollection
|
|
444
|
-
// @ts-ignore
|
|
445
|
-
this.webex.internal.device.url
|
|
445
|
+
data?.stateElementsMessage,
|
|
446
|
+
this.meetingCollection
|
|
446
447
|
);
|
|
447
448
|
|
|
448
449
|
if (meetingForHashTreeMessage) {
|
|
@@ -492,7 +493,6 @@ export default class Meetings extends WebexPlugin {
|
|
|
492
493
|
*/
|
|
493
494
|
private handleLocusEvent(data: LocusEvent, useRandomDelayForInfo = false) {
|
|
494
495
|
let meeting = this.getCorrespondingMeetingByLocus(data);
|
|
495
|
-
|
|
496
496
|
// @ts-ignore
|
|
497
497
|
if (this.config.experimental.storeLocusHashTreeEventsForDebugging) {
|
|
498
498
|
storeEventForDebugging('mercury', data);
|
|
@@ -586,17 +586,21 @@ export default class Meetings extends WebexPlugin {
|
|
|
586
586
|
this.create(data.locus, DESTINATION_TYPE.LOCUS_ID, useRandomDelayForInfo)
|
|
587
587
|
.then(async (newMeeting) => {
|
|
588
588
|
meeting = newMeeting;
|
|
589
|
-
|
|
590
589
|
try {
|
|
591
590
|
// It's a new meeting so initialize the locus data
|
|
592
|
-
await meeting.locusInfo.initialSetup(
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
591
|
+
await meeting.locusInfo.initialSetup(
|
|
592
|
+
{
|
|
593
|
+
trigger:
|
|
594
|
+
data.eventType === LOCUSEVENT.SDK_LOCUS_FROM_SYNC_MEETINGS
|
|
595
|
+
? 'get-loci-response'
|
|
596
|
+
: 'locus-message',
|
|
597
|
+
locus: data.locus,
|
|
598
|
+
hashTreeMessage: data.stateElementsMessage,
|
|
599
|
+
},
|
|
600
|
+
(locus: LocusDTO) => {
|
|
601
|
+
meeting.finalizeMeetingAfterInitialLocusSetup(locus);
|
|
602
|
+
}
|
|
603
|
+
);
|
|
600
604
|
} catch (error) {
|
|
601
605
|
LoggerProxy.logger.warn(
|
|
602
606
|
`Meetings:index#handleLocusEvent --> Error initializing locus data: ${error.message}`
|
|
@@ -1403,6 +1407,31 @@ export default class Meetings extends WebexPlugin {
|
|
|
1403
1407
|
return this.personalMeetingRoom;
|
|
1404
1408
|
}
|
|
1405
1409
|
|
|
1410
|
+
/**
|
|
1411
|
+
* Fetches site preferences for the provided Webex site, or the preferred Webex site.
|
|
1412
|
+
* This is used to determine capabilities of the site, such as whether scheduling a webinar is supported.
|
|
1413
|
+
*
|
|
1414
|
+
* @param {object} [options]
|
|
1415
|
+
* @param {string} [options.siteUrl] - Webex site URL. Defaults to preferredWebexSite, for example "cisco.webex.com".
|
|
1416
|
+
* @param {string} [options.siteName] - Site name query override. Defaults to the site name derived from siteUrl, for example "cisco" for "cisco.webex.com".
|
|
1417
|
+
* @param {SitePreferenceSelectOption[]} [options.selectOptions] - Preference sections to fetch. Defaults to 'scheduling'.
|
|
1418
|
+
* @returns {Promise<SitePreferencesResponse>} site preferences response body
|
|
1419
|
+
* @throws {ParameterError}
|
|
1420
|
+
* @public
|
|
1421
|
+
* @memberof Meetings
|
|
1422
|
+
* @example
|
|
1423
|
+
* const preferences = await webex.meetings.fetchSitePreferencesMeViaSite();
|
|
1424
|
+
* const supportScheduleWebinar = preferences?.scheduling?.supportScheduleWebinar;
|
|
1425
|
+
*/
|
|
1426
|
+
public fetchSitePreferencesMeViaSite(
|
|
1427
|
+
options: FetchSitePreferencesMeViaSiteOptions = {}
|
|
1428
|
+
): Promise<SitePreferencesResponse> {
|
|
1429
|
+
return this.request.fetchSitePreferencesMeViaSite({
|
|
1430
|
+
...options,
|
|
1431
|
+
siteUrl: options.siteUrl || this.preferredWebexSite,
|
|
1432
|
+
});
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1406
1435
|
/**
|
|
1407
1436
|
* Returns basic information about a meeting that exists or
|
|
1408
1437
|
* used to exist in the MeetingCollection
|
|
@@ -1765,6 +1794,7 @@ export default class Meetings extends WebexPlugin {
|
|
|
1765
1794
|
extraParams: infoExtraParams,
|
|
1766
1795
|
sendCAevents: !!callStateForMetrics?.correlationId, // if client sends correlation id as argument of public create(), then it means that this meeting creation is part of a pre-join intent from user
|
|
1767
1796
|
};
|
|
1797
|
+
const shouldDeferMeetingInfoFetch = type === DESTINATION_TYPE.LOCUS_ID && !destination?.info;
|
|
1768
1798
|
|
|
1769
1799
|
if (meetingInfo) {
|
|
1770
1800
|
meeting.injectMeetingInfo(meetingInfo, meetingInfoOptions, meetingLookupUrl);
|
|
@@ -1776,8 +1806,12 @@ export default class Meetings extends WebexPlugin {
|
|
|
1776
1806
|
waitingTime
|
|
1777
1807
|
);
|
|
1778
1808
|
meeting.parseMeetingInfo(undefined, destination);
|
|
1779
|
-
} else {
|
|
1809
|
+
} else if (!shouldDeferMeetingInfoFetch) {
|
|
1780
1810
|
await meeting.fetchMeetingInfo(meetingInfoOptions);
|
|
1811
|
+
} else {
|
|
1812
|
+
LoggerProxy.logger.info(
|
|
1813
|
+
'Meetings:index#createMeeting --> defer fetchMeetingInfo for incomplete locus, will do it after locus initialSetup'
|
|
1814
|
+
);
|
|
1781
1815
|
}
|
|
1782
1816
|
}
|
|
1783
1817
|
} catch (err) {
|
|
@@ -1811,7 +1845,11 @@ export default class Meetings extends WebexPlugin {
|
|
|
1811
1845
|
// For type LOCUS_ID we need to parse the locus object to get the information
|
|
1812
1846
|
// about the caller and callee
|
|
1813
1847
|
// Meeting Added event will be created in `handleLocusEvent`
|
|
1814
|
-
if
|
|
1848
|
+
// Only emit MEETING_ADDED if the meeting still exists in the collection.
|
|
1849
|
+
// If fetchMeetingInfo failed and the meeting was destroyed in the catch block,
|
|
1850
|
+
// skip emitting to prevent orphaned meeting references on the consumer side.
|
|
1851
|
+
// @ts-ignore - getMeetingByType types value as object but accepts strings (same as handleLocusEvent)
|
|
1852
|
+
if (type !== DESTINATION_TYPE.LOCUS_ID && this.getMeetingByType(_ID_, meeting.id)) {
|
|
1815
1853
|
if (!meeting.sipUri) {
|
|
1816
1854
|
meeting.setSipUri(destination);
|
|
1817
1855
|
}
|
|
@@ -1886,23 +1924,20 @@ export default class Meetings extends WebexPlugin {
|
|
|
1886
1924
|
* @public
|
|
1887
1925
|
* @memberof Meetings
|
|
1888
1926
|
*/
|
|
1889
|
-
public syncMeetings({keepOnlyLocusMeetings = true} = {}): Promise<void> {
|
|
1927
|
+
public async syncMeetings({keepOnlyLocusMeetings = true} = {}): Promise<void> {
|
|
1890
1928
|
// @ts-ignore
|
|
1891
1929
|
if (this.webex.credentials.isUnverifiedGuest) {
|
|
1892
1930
|
LoggerProxy.logger.info(
|
|
1893
|
-
'Meetings:index#syncMeetings --> skipping
|
|
1931
|
+
'Meetings:index#syncMeetings --> user is unverified guest, skipping calling Locus for meeting sync'
|
|
1894
1932
|
);
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
return this.request
|
|
1900
|
-
.getActiveMeetings()
|
|
1901
|
-
.then((locusArray) => {
|
|
1902
|
-
const activeLocusUrl = [];
|
|
1933
|
+
} else {
|
|
1934
|
+
try {
|
|
1935
|
+
const locusArray = await this.request.getActiveMeetings();
|
|
1936
|
+
const activeLocusUrl: string[] = [];
|
|
1903
1937
|
|
|
1904
1938
|
if (locusArray?.loci && locusArray.loci.length > 0) {
|
|
1905
1939
|
const lociToUpdate = this.sortLocusArrayToUpdate(locusArray.loci);
|
|
1940
|
+
|
|
1906
1941
|
lociToUpdate.forEach((locus) => {
|
|
1907
1942
|
activeLocusUrl.push(locus.url);
|
|
1908
1943
|
this.handleLocusEvent({
|
|
@@ -1920,21 +1955,48 @@ export default class Meetings extends WebexPlugin {
|
|
|
1920
1955
|
// (they had a locusUrl previously but are no longer active) in the sync
|
|
1921
1956
|
for (const meeting of Object.values(meetingsCollection)) {
|
|
1922
1957
|
// @ts-ignore
|
|
1923
|
-
const {locusUrl} = meeting;
|
|
1958
|
+
const {locusUrl, locusInfo} = meeting;
|
|
1924
1959
|
if ((keepOnlyLocusMeetings || locusUrl) && !activeLocusUrl.includes(locusUrl)) {
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1960
|
+
const globalMeetingId = locusInfo?.info?.globalMeetingId;
|
|
1961
|
+
|
|
1962
|
+
if (
|
|
1963
|
+
globalMeetingId &&
|
|
1964
|
+
locusArray?.loci?.some(
|
|
1965
|
+
(locus: LocusDTO) => locus.info?.globalMeetingId === globalMeetingId
|
|
1966
|
+
)
|
|
1967
|
+
) {
|
|
1968
|
+
// don't destroy the meeting as Locus API still returned some Locus that shares
|
|
1969
|
+
// the same globalMeetingId - that happens for example if a webinar user (who hasn't scheduled it)
|
|
1970
|
+
// is in a breakout and gets moved to a different breakout while we were offline
|
|
1971
|
+
} else {
|
|
1972
|
+
// destroy function also uploads logs
|
|
1973
|
+
// @ts-ignore
|
|
1974
|
+
this.destroy(meeting, MEETING_REMOVED_REASON.NO_MEETINGS_TO_SYNC);
|
|
1975
|
+
}
|
|
1928
1976
|
}
|
|
1929
1977
|
}
|
|
1930
1978
|
}
|
|
1931
|
-
})
|
|
1932
|
-
.catch((error) => {
|
|
1979
|
+
} catch (error) {
|
|
1933
1980
|
LoggerProxy.logger.error(
|
|
1934
1981
|
`Meetings:index#syncMeetings --> failed to sync meetings, ${error}`
|
|
1935
1982
|
);
|
|
1936
|
-
throw
|
|
1937
|
-
}
|
|
1983
|
+
throw error;
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
// Trigger hash tree syncs for all remaining meetings
|
|
1988
|
+
const remainingMeetings = this.meetingCollection.getAll();
|
|
1989
|
+
const syncPromises = [];
|
|
1990
|
+
|
|
1991
|
+
for (const meeting of Object.values(remainingMeetings) as any[]) {
|
|
1992
|
+
if (meeting.locusInfo) {
|
|
1993
|
+
syncPromises.push(meeting.locusInfo.syncAllHashTreeDatasets());
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
if (syncPromises.length > 0) {
|
|
1998
|
+
await Promise.all(syncPromises);
|
|
1999
|
+
}
|
|
1938
2000
|
}
|
|
1939
2001
|
|
|
1940
2002
|
/**
|
|
@@ -1950,8 +2012,8 @@ export default class Meetings extends WebexPlugin {
|
|
|
1950
2012
|
this.breakoutLocusForHandleLater = [];
|
|
1951
2013
|
const lociToUpdate = [...mainLoci];
|
|
1952
2014
|
breakoutLoci.forEach((breakoutLocus) => {
|
|
1953
|
-
const associateMainLocus = mainLoci.find(
|
|
1954
|
-
(mainLocus
|
|
2015
|
+
const associateMainLocus = mainLoci.find((mainLocus) =>
|
|
2016
|
+
MeetingsUtil.isMainAssociatedWithBreakout(mainLocus, breakoutLocus)
|
|
1955
2017
|
);
|
|
1956
2018
|
const existCorrespondingMeeting = this.getCorrespondingMeetingByLocus({
|
|
1957
2019
|
eventType: LOCUSEVENT.SDK_NO_EVENT,
|
|
@@ -1979,7 +2041,7 @@ export default class Meetings extends WebexPlugin {
|
|
|
1979
2041
|
* @public
|
|
1980
2042
|
* @memberof Meetings
|
|
1981
2043
|
*/
|
|
1982
|
-
checkHandleBreakoutLocus(newCreatedLocus) {
|
|
2044
|
+
checkHandleBreakoutLocus(newCreatedLocus: any) {
|
|
1983
2045
|
if (
|
|
1984
2046
|
!newCreatedLocus ||
|
|
1985
2047
|
!this.breakoutLocusForHandleLater ||
|
|
@@ -1990,9 +2052,8 @@ export default class Meetings extends WebexPlugin {
|
|
|
1990
2052
|
if (MeetingsUtil.isBreakoutLocusDTO(newCreatedLocus)) {
|
|
1991
2053
|
return;
|
|
1992
2054
|
}
|
|
1993
|
-
const existIndex = this.breakoutLocusForHandleLater.findIndex(
|
|
1994
|
-
(breakoutLocus)
|
|
1995
|
-
breakoutLocus.controls?.breakout?.url === newCreatedLocus.controls?.breakout?.url
|
|
2055
|
+
const existIndex = this.breakoutLocusForHandleLater.findIndex((breakoutLocus: any) =>
|
|
2056
|
+
MeetingsUtil.isMainAssociatedWithBreakout(newCreatedLocus, breakoutLocus)
|
|
1996
2057
|
);
|
|
1997
2058
|
|
|
1998
2059
|
if (existIndex < 0) {
|
|
@@ -31,3 +31,22 @@ export type MeetingRegistrationStatus = {
|
|
|
31
31
|
mercuryConnect: boolean;
|
|
32
32
|
checkH264Support: boolean;
|
|
33
33
|
};
|
|
34
|
+
|
|
35
|
+
export enum SitePreferenceSelectOption {
|
|
36
|
+
SCHEDULING = 'scheduling',
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type FetchSitePreferencesMeViaSiteOptions = {
|
|
40
|
+
siteUrl?: string;
|
|
41
|
+
siteName?: string;
|
|
42
|
+
selectOptions?: SitePreferenceSelectOption[];
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const DEFAULT_SITE_PREFERENCE_SELECT_OPTIONS = [SitePreferenceSelectOption.SCHEDULING];
|
|
46
|
+
|
|
47
|
+
export type SitePreferencesResponse = {
|
|
48
|
+
scheduling?: {
|
|
49
|
+
supportScheduleWebinar?: boolean;
|
|
50
|
+
webinarWebLink?: string;
|
|
51
|
+
};
|
|
52
|
+
};
|
package/src/meetings/request.ts
CHANGED
|
@@ -2,7 +2,14 @@
|
|
|
2
2
|
import {StatelessWebexPlugin} from '@webex/webex-core';
|
|
3
3
|
|
|
4
4
|
import LoggerProxy from '../common/logs/logger-proxy';
|
|
5
|
+
import ParameterError from '../common/errors/parameter';
|
|
5
6
|
import {HTTP_VERBS, API, RESOURCE} from '../constants';
|
|
7
|
+
import {
|
|
8
|
+
DEFAULT_SITE_PREFERENCE_SELECT_OPTIONS,
|
|
9
|
+
type FetchSitePreferencesMeViaSiteOptions,
|
|
10
|
+
type SitePreferencesResponse,
|
|
11
|
+
} from './meetings.types';
|
|
12
|
+
import MeetingsUtil from './util';
|
|
6
13
|
|
|
7
14
|
/**
|
|
8
15
|
* @class MeetingRequest
|
|
@@ -45,6 +52,42 @@ export default class MeetingRequest extends StatelessWebexPlugin {
|
|
|
45
52
|
return this.webex.internal.services.getMeetingPreferences();
|
|
46
53
|
}
|
|
47
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Fetches site preferences from a given site given a select option and a siteUrl with an optional siteName. If siteName is not provided, it will be derived from the siteUrl. If siteUrl is not provided, it will throw an error. If selectOptions is not provided, it will default to scheduling.
|
|
57
|
+
*
|
|
58
|
+
* @param {object} [options]
|
|
59
|
+
* @param {string} [options.siteUrl] - Webex site URL, for example "cisco.webex.com".
|
|
60
|
+
* @param {string} [options.siteName] - Site name query override. Defaults to the site name derived from options.siteUrl, e.g., "cisco".
|
|
61
|
+
* @param {SitePreferenceSelectOption[]} [options.selectOptions] - Preference sections to fetch. Defaults to 'scheduling'.
|
|
62
|
+
* @returns {Promise<SitePreferencesResponse>} site preferences response body
|
|
63
|
+
* @throws {ParameterError}
|
|
64
|
+
* @public
|
|
65
|
+
* @memberof MeetingRequest
|
|
66
|
+
*/
|
|
67
|
+
fetchSitePreferencesMeViaSite(
|
|
68
|
+
options: FetchSitePreferencesMeViaSiteOptions = {}
|
|
69
|
+
): Promise<SitePreferencesResponse> {
|
|
70
|
+
const {siteUrl, selectOptions = DEFAULT_SITE_PREFERENCE_SELECT_OPTIONS} = options;
|
|
71
|
+
|
|
72
|
+
if (!siteUrl) {
|
|
73
|
+
throw new ParameterError(
|
|
74
|
+
'No siteUrl available. Call register() before fetching site preferences or provide options.siteUrl.'
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// @ts-ignore - config comes from registerPlugin
|
|
79
|
+
const multipartSitePrefixList = this.config.meetings.multipartSitePrefixList || [];
|
|
80
|
+
const siteName = options.siteName || MeetingsUtil.getSiteName(siteUrl, multipartSitePrefixList);
|
|
81
|
+
|
|
82
|
+
// @ts-ignore
|
|
83
|
+
return this.request({
|
|
84
|
+
method: HTTP_VERBS.GET,
|
|
85
|
+
uri: `https://${siteUrl}/wbxappapi/v1/users/me/preference?select=${encodeURIComponent(
|
|
86
|
+
selectOptions.join(',')
|
|
87
|
+
)}&siteurl=${encodeURIComponent(siteName)}`,
|
|
88
|
+
}).then((res: any) => res.body);
|
|
89
|
+
}
|
|
90
|
+
|
|
48
91
|
// locus federation, determines and populate locus if the responseBody has remote URLs to fetch locus details
|
|
49
92
|
|
|
50
93
|
/**
|
package/src/meetings/util.ts
CHANGED
|
@@ -18,6 +18,7 @@ import Trigger from '../common/events/trigger-proxy';
|
|
|
18
18
|
import BEHAVIORAL_METRICS from '../metrics/constants';
|
|
19
19
|
import Metrics from '../metrics';
|
|
20
20
|
import {MEETING_KEY} from './meetings.types';
|
|
21
|
+
import {EndMeetingReason, LocusFullState} from '../locus-info/types';
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* Meetings Media Codec Missing Event
|
|
@@ -152,6 +153,30 @@ MeetingsUtil.parseDefaultSiteFromMeetingPreferences = (userPreferences) => {
|
|
|
152
153
|
return result;
|
|
153
154
|
};
|
|
154
155
|
|
|
156
|
+
MeetingsUtil.getSiteName = (site: string, multipartSitePrefixList: string[] = []) => {
|
|
157
|
+
if (!site) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
let siteName: string | undefined;
|
|
162
|
+
|
|
163
|
+
multipartSitePrefixList.forEach((multipartSitePrefix) => {
|
|
164
|
+
if (!siteName && site.includes(multipartSitePrefix)) {
|
|
165
|
+
const secondDot = site.indexOf('.', site.indexOf('.') + 1);
|
|
166
|
+
|
|
167
|
+
siteName = site.substring(0, secondDot);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
if (siteName) {
|
|
172
|
+
return siteName;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
siteName = site.substring(0, site.indexOf('.'));
|
|
176
|
+
|
|
177
|
+
return siteName;
|
|
178
|
+
};
|
|
179
|
+
|
|
155
180
|
/**
|
|
156
181
|
* Will check to see if the H.264 media codec is supported.
|
|
157
182
|
* @async
|
|
@@ -266,6 +291,35 @@ MeetingsUtil.getThisDevice = (newLocus: any, deviceUrl: string) => {
|
|
|
266
291
|
return null;
|
|
267
292
|
};
|
|
268
293
|
|
|
294
|
+
/**
|
|
295
|
+
* Checks if the fullState indicates the meeting has fully ended (not just a breakout move).
|
|
296
|
+
* @param {Object} fullState locus fullState data
|
|
297
|
+
* @returns {boolean}
|
|
298
|
+
*/
|
|
299
|
+
MeetingsUtil.isWholeMeetingEnded = (fullState: LocusFullState): boolean => {
|
|
300
|
+
return (
|
|
301
|
+
fullState.state === LOCUS.STATE.INACTIVE &&
|
|
302
|
+
fullState.endMeetingReason !== EndMeetingReason.breakoutEnded
|
|
303
|
+
);
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Checks if the self state in a locus indicates a breakout move or breakout end.
|
|
308
|
+
* Returns true when:
|
|
309
|
+
* - self state is LEFT with reason MOVED (regular breakout move), OR
|
|
310
|
+
* - fullState is INACTIVE with endMeetingReason BREAKOUT_ENDED (breakout session ended)
|
|
311
|
+
* @param {Object} locus locus data
|
|
312
|
+
* @returns {boolean}
|
|
313
|
+
*/
|
|
314
|
+
MeetingsUtil.isSelfMovedOrBreakoutEnded = (locus: any): boolean => {
|
|
315
|
+
const isSelfLeftMoved = locus?.self?.state === _LEFT_ && locus?.self?.reason === _MOVED_;
|
|
316
|
+
const isBreakoutEnded =
|
|
317
|
+
locus?.fullState?.state === LOCUS.STATE.INACTIVE &&
|
|
318
|
+
locus?.fullState?.endMeetingReason === EndMeetingReason.breakoutEnded;
|
|
319
|
+
|
|
320
|
+
return isSelfLeftMoved || isBreakoutEnded;
|
|
321
|
+
};
|
|
322
|
+
|
|
269
323
|
/**
|
|
270
324
|
* get self device joined status from locus data
|
|
271
325
|
* @param {Object} meeting current meeting data
|
|
@@ -294,7 +348,10 @@ MeetingsUtil.joinedOnThisDevice = (meeting: any, newLocus: any, deviceUrl: strin
|
|
|
294
348
|
* @private
|
|
295
349
|
*/
|
|
296
350
|
MeetingsUtil.isBreakoutLocusDTO = (newLocus: any) => {
|
|
297
|
-
return
|
|
351
|
+
return (
|
|
352
|
+
newLocus?.controls?.breakout?.sessionType === BREAKOUTS.SESSION_TYPES.BREAKOUT ||
|
|
353
|
+
!!newLocus?.info?.isBreakout
|
|
354
|
+
);
|
|
298
355
|
};
|
|
299
356
|
|
|
300
357
|
/**
|
|
@@ -310,4 +367,26 @@ MeetingsUtil.isValidBreakoutLocus = (locus: any) => {
|
|
|
310
367
|
|
|
311
368
|
return isLocusAsBreakout && !inActiveStatus && selfJoined;
|
|
312
369
|
};
|
|
370
|
+
/**
|
|
371
|
+
* check if the breakout locus is associated with the main locus by comparing the breakout control url or the replaces info in self device
|
|
372
|
+
* @param {Object} mainLocus main locus data
|
|
373
|
+
* @param {Object} breakoutLocus breakout locus data
|
|
374
|
+
* @returns {boolean}
|
|
375
|
+
* @private
|
|
376
|
+
*/
|
|
377
|
+
MeetingsUtil.isMainAssociatedWithBreakout = (mainLocus: any, breakoutLocus: any) => {
|
|
378
|
+
if (
|
|
379
|
+
mainLocus.controls?.breakout?.url &&
|
|
380
|
+
mainLocus.controls?.breakout?.url === breakoutLocus.controls?.breakout?.url
|
|
381
|
+
) {
|
|
382
|
+
return true;
|
|
383
|
+
}
|
|
384
|
+
const deviceUrl = breakoutLocus?.self?.deviceUrl;
|
|
385
|
+
const replaceInfo = MeetingsUtil.getThisDevice(breakoutLocus, deviceUrl)?.replaces?.[0];
|
|
386
|
+
if (replaceInfo?.locusUrl && replaceInfo.locusUrl === mainLocus.url) {
|
|
387
|
+
return true;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return false;
|
|
391
|
+
};
|
|
313
392
|
export default MeetingsUtil;
|
package/src/member/index.ts
CHANGED
|
@@ -27,6 +27,7 @@ export default class Member {
|
|
|
27
27
|
isModerator: any;
|
|
28
28
|
isModeratorAssignmentProhibited: any;
|
|
29
29
|
isPresenterAssignmentProhibited: any;
|
|
30
|
+
isAttendeeAssignmentProhibited: any;
|
|
30
31
|
isMutable: any;
|
|
31
32
|
isNotAdmitted: any;
|
|
32
33
|
isRecording: any;
|
|
@@ -292,6 +293,14 @@ export default class Member {
|
|
|
292
293
|
*/
|
|
293
294
|
this.isPresenterAssignmentProhibited = null;
|
|
294
295
|
|
|
296
|
+
/**
|
|
297
|
+
* @instance
|
|
298
|
+
* @type {Boolean}
|
|
299
|
+
* @public
|
|
300
|
+
* @memberof Member
|
|
301
|
+
*/
|
|
302
|
+
this.isAttendeeAssignmentProhibited = null;
|
|
303
|
+
|
|
295
304
|
/**
|
|
296
305
|
* @instance
|
|
297
306
|
* @type {Boolean}
|
|
@@ -369,6 +378,7 @@ export default class Member {
|
|
|
369
378
|
MemberUtil.isModeratorAssignmentProhibited(participant);
|
|
370
379
|
this.isPresenterAssignmentProhibited =
|
|
371
380
|
MemberUtil.isPresenterAssignmentProhibited(participant);
|
|
381
|
+
this.isAttendeeAssignmentProhibited = MemberUtil.isAttendeeAssignmentProhibited(participant);
|
|
372
382
|
this.canApproveAIEnablement = MemberUtil.canApproveAIEnablement(participant);
|
|
373
383
|
this.processStatus(participant);
|
|
374
384
|
this.processRoles(participant);
|
package/src/member/types.ts
CHANGED
|
@@ -103,6 +103,7 @@ export interface Participant {
|
|
|
103
103
|
moderator: boolean; // Locus docs say this is deprecated and role control should be used instead
|
|
104
104
|
moderatorAssignmentNotAllowed: boolean;
|
|
105
105
|
presenterAssignmentNotAllowed: boolean;
|
|
106
|
+
attendeeAssignmentNotAllowed?: boolean;
|
|
106
107
|
person: ParticipantPerson;
|
|
107
108
|
resourceGuest: boolean;
|
|
108
109
|
state: string; // probably one of MEETING_STATE.STATES
|
package/src/member/util.ts
CHANGED
|
@@ -140,6 +140,9 @@ const MemberUtil = {
|
|
|
140
140
|
isPresenterAssignmentProhibited: (participant: Participant) =>
|
|
141
141
|
participant && participant.presenterAssignmentNotAllowed,
|
|
142
142
|
|
|
143
|
+
isAttendeeAssignmentProhibited: (participant: Participant) =>
|
|
144
|
+
!!(participant && participant.attendeeAssignmentNotAllowed),
|
|
145
|
+
|
|
143
146
|
/**
|
|
144
147
|
* checks to see if the participant id is the same as the passed id
|
|
145
148
|
* there are multiple ids that can be used
|
package/src/metrics/constants.ts
CHANGED
|
@@ -96,6 +96,7 @@ const BEHAVIORAL_METRICS = {
|
|
|
96
96
|
SET_CUSTOM_CODEC_PARAMETERS_USED: 'js_sdk_set_custom_codec_parameters_used',
|
|
97
97
|
MARK_CUSTOM_CODEC_PARAMETERS_FOR_DELETION_USED:
|
|
98
98
|
'js_sdk_mark_custom_codec_parameters_for_deletion_used',
|
|
99
|
+
HASH_TREE_SYNC_FAILURE: 'js_sdk_hash_tree_sync_failure',
|
|
99
100
|
};
|
|
100
101
|
|
|
101
102
|
export {BEHAVIORAL_METRICS as default};
|
|
@@ -261,8 +261,7 @@ export default class RecordingController {
|
|
|
261
261
|
|
|
262
262
|
LoggerProxy.logger.log(`RecordingController:index#recordingControls --> ${record}`);
|
|
263
263
|
|
|
264
|
-
|
|
265
|
-
return this.request.request({
|
|
264
|
+
return this.request.locusDeltaRequest({
|
|
266
265
|
uri: `${this.locusUrl}/${CONTROLS}`,
|
|
267
266
|
body: {
|
|
268
267
|
record,
|