@webex/plugin-meetings 3.12.0-next.9 → 3.12.0-next.91
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 +8 -3
- package/dist/breakouts/breakout.js.map +1 -1
- package/dist/breakouts/index.js +26 -2
- package/dist/breakouts/index.js.map +1 -1
- package/dist/config.js +2 -2
- package/dist/config.js.map +1 -1
- package/dist/constants.js +35 -9
- 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 +67 -29
- 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 +13 -1
- package/dist/hashTree/constants.js.map +1 -1
- package/dist/hashTree/hashTreeParser.js +880 -382
- package/dist/hashTree/hashTreeParser.js.map +1 -1
- package/dist/hashTree/utils.js +42 -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/dataChannelAuthToken.js +75 -15
- package/dist/interceptors/dataChannelAuthToken.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/interpretation.types.js +7 -0
- package/dist/interpretation/interpretation.types.js.map +1 -0
- 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 +303 -88
- package/dist/locus-info/index.js.map +1 -1
- package/dist/locus-info/infoUtils.js +36 -5
- package/dist/locus-info/infoUtils.js.map +1 -1
- package/dist/locus-info/types.js +19 -0
- package/dist/locus-info/types.js.map +1 -1
- package/dist/media/index.js +3 -1
- package/dist/media/index.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 +5 -1
- package/dist/meeting/in-meeting-actions.js.map +1 -1
- package/dist/meeting/index.js +1233 -793
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/locusMediaRequest.js +229 -82
- package/dist/meeting/locusMediaRequest.js.map +1 -1
- package/dist/meeting/muteState.js +10 -1
- package/dist/meeting/muteState.js.map +1 -1
- package/dist/meeting/request.js +5 -2
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/util.js +35 -2
- package/dist/meeting/util.js.map +1 -1
- package/dist/meeting-info/meeting-info-v2.js +2 -2
- package/dist/meeting-info/meeting-info-v2.js.map +1 -1
- package/dist/meetings/index.js +231 -118
- 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 +79 -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 +30 -0
- package/dist/member/util.js.map +1 -1
- package/dist/members/index.js +101 -43
- package/dist/members/index.js.map +1 -1
- package/dist/members/util.js +8 -1
- package/dist/members/util.js.map +1 -1
- package/dist/metrics/constants.js +5 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/multistream/codec/constants.js +63 -0
- package/dist/multistream/codec/constants.js.map +1 -0
- package/dist/multistream/mediaRequestManager.js +62 -15
- package/dist/multistream/mediaRequestManager.js.map +1 -1
- package/dist/multistream/receiveSlot.js +9 -0
- package/dist/multistream/receiveSlot.js.map +1 -1
- package/dist/reachability/index.js +10 -13
- package/dist/reachability/index.js.map +1 -1
- package/dist/reachability/reachability.types.js +2 -0
- package/dist/reachability/reachability.types.js.map +1 -1
- package/dist/reachability/util.js +45 -0
- package/dist/reachability/util.js.map +1 -1
- package/dist/reactions/reactions.type.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 +2 -2
- package/dist/types/constants.d.ts +12 -1
- package/dist/types/controls-options-manager/constants.d.ts +6 -1
- package/dist/types/controls-options-manager/index.d.ts +16 -0
- package/dist/types/hashTree/constants.d.ts +2 -0
- package/dist/types/hashTree/hashTreeParser.d.ts +146 -17
- package/dist/types/hashTree/utils.d.ts +18 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/interceptors/locusRetry.d.ts +4 -4
- package/dist/types/interpretation/interpretation.types.d.ts +10 -0
- package/dist/types/locus-info/index.d.ts +50 -6
- package/dist/types/locus-info/infoUtils.d.ts +8 -0
- package/dist/types/locus-info/types.d.ts +21 -1
- package/dist/types/media/properties.d.ts +1 -0
- package/dist/types/meeting/in-meeting-actions.d.ts +4 -0
- package/dist/types/meeting/index.d.ts +94 -7
- package/dist/types/meeting/locusMediaRequest.d.ts +12 -0
- package/dist/types/meeting/request.d.ts +1 -0
- package/dist/types/meeting/util.d.ts +9 -0
- package/dist/types/meetings/index.d.ts +30 -18
- 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 +9 -0
- package/dist/types/members/index.d.ts +15 -2
- package/dist/types/members/util.d.ts +1 -5
- package/dist/types/metrics/constants.d.ts +4 -0
- package/dist/types/multistream/codec/constants.d.ts +7 -0
- package/dist/types/multistream/mediaRequestManager.d.ts +22 -5
- package/dist/types/reachability/index.d.ts +1 -1
- package/dist/types/reachability/reachability.types.d.ts +7 -0
- package/dist/types/reachability/util.d.ts +17 -0
- package/dist/types/reactions/reactions.type.d.ts +3 -0
- package/dist/webinar/index.js +305 -159
- 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 +3 -1
- package/src/breakouts/index.ts +31 -0
- package/src/config.ts +2 -2
- package/src/constants.ts +20 -3
- package/src/controls-options-manager/constants.ts +14 -1
- package/src/controls-options-manager/index.ts +87 -28
- package/src/controls-options-manager/util.ts +81 -1
- package/src/hashTree/constants.ts +16 -0
- package/src/hashTree/hashTreeParser.ts +580 -196
- package/src/hashTree/utils.ts +36 -0
- package/src/index.ts +6 -0
- package/src/interceptors/dataChannelAuthToken.ts +88 -12
- package/src/interceptors/locusRetry.ts +25 -4
- package/src/interpretation/index.ts +27 -9
- package/src/interpretation/interpretation.types.ts +11 -0
- package/src/locus-info/controlsUtils.ts +3 -1
- package/src/locus-info/index.ts +304 -100
- package/src/locus-info/infoUtils.ts +41 -6
- package/src/locus-info/types.ts +25 -1
- package/src/media/index.ts +3 -0
- package/src/media/properties.ts +1 -0
- package/src/meeting/in-meeting-actions.ts +8 -0
- package/src/meeting/index.ts +494 -67
- package/src/meeting/locusMediaRequest.ts +106 -6
- package/src/meeting/muteState.ts +10 -1
- package/src/meeting/request.ts +11 -0
- package/src/meeting/util.ts +47 -5
- package/src/meeting-info/meeting-info-v2.ts +4 -2
- package/src/meetings/index.ts +133 -78
- package/src/meetings/meetings.types.ts +19 -0
- package/src/meetings/request.ts +43 -0
- package/src/meetings/util.ts +97 -1
- package/src/member/index.ts +10 -0
- package/src/member/types.ts +1 -0
- package/src/member/util.ts +29 -0
- package/src/members/index.ts +70 -7
- package/src/members/util.ts +12 -4
- package/src/metrics/constants.ts +4 -0
- package/src/multistream/codec/constants.ts +58 -0
- package/src/multistream/mediaRequestManager.ts +119 -28
- package/src/multistream/receiveSlot.ts +18 -0
- package/src/reachability/index.ts +19 -15
- package/src/reachability/reachability.types.ts +11 -0
- package/src/reachability/util.ts +36 -0
- package/src/reactions/reactions.type.ts +3 -0
- package/src/recording-controller/index.ts +1 -2
- package/src/webinar/index.ts +214 -36
- package/test/unit/spec/aiEnableRequest/index.ts +86 -0
- package/test/unit/spec/breakouts/breakout.ts +9 -3
- package/test/unit/spec/breakouts/index.ts +49 -0
- package/test/unit/spec/controls-options-manager/index.js +244 -29
- package/test/unit/spec/controls-options-manager/util.js +165 -0
- package/test/unit/spec/hashTree/hashTreeParser.ts +1838 -180
- package/test/unit/spec/hashTree/utils.ts +125 -1
- package/test/unit/spec/interceptors/dataChannelAuthToken.ts +196 -0
- 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 +499 -81
- package/test/unit/spec/locus-info/infoUtils.js +184 -1
- package/test/unit/spec/media/index.ts +31 -0
- package/test/unit/spec/meeting/in-meeting-actions.ts +4 -0
- package/test/unit/spec/meeting/index.js +1615 -42
- package/test/unit/spec/meeting/locusMediaRequest.ts +302 -4
- package/test/unit/spec/meeting/muteState.js +81 -0
- package/test/unit/spec/meeting/request.js +12 -0
- package/test/unit/spec/meeting/utils.js +157 -1
- package/test/unit/spec/meeting-info/meetinginfov2.js +19 -10
- package/test/unit/spec/meetings/index.js +363 -36
- package/test/unit/spec/meetings/request.js +141 -0
- package/test/unit/spec/meetings/utils.js +189 -0
- package/test/unit/spec/member/index.js +7 -0
- package/test/unit/spec/member/util.js +89 -0
- package/test/unit/spec/members/index.js +216 -0
- package/test/unit/spec/members/request.js +37 -1
- package/test/unit/spec/members/utils.js +63 -0
- package/test/unit/spec/multistream/mediaRequestManager.ts +501 -37
- package/test/unit/spec/reachability/index.ts +59 -46
- package/test/unit/spec/reachability/util.ts +31 -0
- package/test/unit/spec/recording-controller/index.js +9 -8
- package/test/unit/spec/webinar/index.ts +329 -28
package/src/meeting/index.ts
CHANGED
|
@@ -22,7 +22,6 @@ import {
|
|
|
22
22
|
MediaConnectionEventNames,
|
|
23
23
|
MediaContent,
|
|
24
24
|
MediaType,
|
|
25
|
-
MediaCodecMimeType,
|
|
26
25
|
RemoteTrackType,
|
|
27
26
|
RoapMessage,
|
|
28
27
|
StatsAnalyzer,
|
|
@@ -31,7 +30,7 @@ import {
|
|
|
31
30
|
NetworkQualityMonitor,
|
|
32
31
|
StatsMonitor,
|
|
33
32
|
StatsMonitorEventNames,
|
|
34
|
-
|
|
33
|
+
MediaCodecMimeType,
|
|
35
34
|
} from '@webex/internal-media-core';
|
|
36
35
|
|
|
37
36
|
import {DataChannelTokenType} from '@webex/internal-plugin-llm';
|
|
@@ -137,6 +136,8 @@ import {
|
|
|
137
136
|
STAGE_MANAGER_TYPE,
|
|
138
137
|
LOCUSEVENT,
|
|
139
138
|
LOCUS_LLM_EVENT,
|
|
139
|
+
LLM_DEFAULT_SESSION,
|
|
140
|
+
LLM_PRACTICE_SESSION,
|
|
140
141
|
} from '../constants';
|
|
141
142
|
import BEHAVIORAL_METRICS from '../metrics/constants';
|
|
142
143
|
import ParameterError from '../common/errors/parameter';
|
|
@@ -190,6 +191,7 @@ import AIEnableRequest from '../aiEnableRequest';
|
|
|
190
191
|
const DEFAULT_ICE_PHASE_CALLBACK = () => 'JOIN_MEETING_FINAL';
|
|
191
192
|
|
|
192
193
|
const LLM_HEALTHCHECK_TIMER_MS = 3 * 60 * 1000;
|
|
194
|
+
const JOIN_WITH_MEDIA_RETRY_MAX_COUNT = 2;
|
|
193
195
|
|
|
194
196
|
const logRequest = (request: any, {logText = ''}) => {
|
|
195
197
|
LoggerProxy.logger.info(`${logText} - sending request`);
|
|
@@ -612,7 +614,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
612
614
|
webinar: any;
|
|
613
615
|
conversationUrl: string;
|
|
614
616
|
callStateForMetrics: CallStateForMetrics;
|
|
615
|
-
destination: string;
|
|
617
|
+
destination: string | LocusDTO;
|
|
616
618
|
destinationType: DESTINATION_TYPE;
|
|
617
619
|
deviceUrl: string;
|
|
618
620
|
hostId: string;
|
|
@@ -651,6 +653,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
651
653
|
floorGrantPending: boolean;
|
|
652
654
|
hasJoinedOnce: boolean;
|
|
653
655
|
hasWebsocketConnected: boolean;
|
|
656
|
+
private mercuryOnlineHandler?: () => void;
|
|
657
|
+
private mercuryOfflineHandler?: () => void;
|
|
654
658
|
inMeetingActions: InMeetingActions;
|
|
655
659
|
isLocalShareLive: boolean;
|
|
656
660
|
isRoapInProgress: boolean;
|
|
@@ -662,6 +666,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
662
666
|
joinedWith?: any;
|
|
663
667
|
selfId?: string;
|
|
664
668
|
roles: any[];
|
|
669
|
+
canNotViewTheParticipantList?: boolean;
|
|
665
670
|
// ... there is more ... see SelfUtils.parse()
|
|
666
671
|
// end of the group
|
|
667
672
|
locusMediaRequest?: LocusMediaRequest;
|
|
@@ -735,6 +740,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
735
740
|
isMoveToInProgress = false;
|
|
736
741
|
registrationIdStatus: string;
|
|
737
742
|
brbState: BrbState;
|
|
743
|
+
private promisesWaitingForPropUpdate: Record<string, Defer> = {};
|
|
738
744
|
|
|
739
745
|
voiceaListenerCallbacks: object = {
|
|
740
746
|
[VOICEAEVENTS.VOICEA_ANNOUNCEMENT]: (payload: Transcription['languageOptions']) => {
|
|
@@ -790,7 +796,13 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
790
796
|
private deferSDPAnswer?: Defer; // used for waiting for a response
|
|
791
797
|
private sdpResponseTimer?: ReturnType<typeof setTimeout>;
|
|
792
798
|
private hasMediaConnectionConnectedAtLeastOnce: boolean;
|
|
793
|
-
private joinWithMediaRetryInfo
|
|
799
|
+
private joinWithMediaRetryInfo: {
|
|
800
|
+
retryCount: number;
|
|
801
|
+
prevJoinResponse?: any;
|
|
802
|
+
firstError?: Error;
|
|
803
|
+
prevError?: Error;
|
|
804
|
+
};
|
|
805
|
+
|
|
794
806
|
private connectionStateHandler?: ConnectionStateHandler;
|
|
795
807
|
private iceCandidateErrors: Map<string, number>;
|
|
796
808
|
private iceCandidatesCount: number;
|
|
@@ -935,7 +947,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
935
947
|
this.simultaneousInterpretation = new SimultaneousInterpretation({}, {parent: this.webex});
|
|
936
948
|
|
|
937
949
|
// @ts-ignore
|
|
938
|
-
this.aiEnableRequest = new AIEnableRequest({}, {parent: this.webex});
|
|
950
|
+
this.aiEnableRequest = new AIEnableRequest({locusUrl: this.locusUrl}, {parent: this.webex});
|
|
939
951
|
|
|
940
952
|
/**
|
|
941
953
|
* @instance
|
|
@@ -966,6 +978,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
966
978
|
},
|
|
967
979
|
(csi: CSI) => (this.members.findMemberByCsi(csi) as any)?.id
|
|
968
980
|
);
|
|
981
|
+
|
|
969
982
|
/**
|
|
970
983
|
* Object containing helper classes for managing media requests for audio/video/screenshare (for multistream media connections)
|
|
971
984
|
* All multistream media requests sent out for this meeting have to go through them.
|
|
@@ -985,6 +998,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
985
998
|
mediaRequests
|
|
986
999
|
);
|
|
987
1000
|
},
|
|
1001
|
+
this.getIngressPayloadTypeCallback.bind(this),
|
|
988
1002
|
{
|
|
989
1003
|
// @ts-ignore - config coming from registerPlugin
|
|
990
1004
|
degradationPreferences: this.config.degradationPreferences,
|
|
@@ -1006,6 +1020,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1006
1020
|
mediaRequests
|
|
1007
1021
|
);
|
|
1008
1022
|
},
|
|
1023
|
+
this.getIngressPayloadTypeCallback.bind(this),
|
|
1009
1024
|
{
|
|
1010
1025
|
// @ts-ignore - config coming from registerPlugin
|
|
1011
1026
|
degradationPreferences: this.config.degradationPreferences,
|
|
@@ -1027,6 +1042,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1027
1042
|
mediaRequests
|
|
1028
1043
|
);
|
|
1029
1044
|
},
|
|
1045
|
+
this.getIngressPayloadTypeCallback.bind(this),
|
|
1030
1046
|
{
|
|
1031
1047
|
// @ts-ignore - config coming from registerPlugin
|
|
1032
1048
|
degradationPreferences: this.config.degradationPreferences,
|
|
@@ -1048,11 +1064,14 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1048
1064
|
mediaRequests
|
|
1049
1065
|
);
|
|
1050
1066
|
},
|
|
1067
|
+
this.getIngressPayloadTypeCallback.bind(this),
|
|
1051
1068
|
{
|
|
1052
1069
|
// @ts-ignore - config coming from registerPlugin
|
|
1053
1070
|
degradationPreferences: this.config.degradationPreferences,
|
|
1054
1071
|
kind: 'video',
|
|
1055
1072
|
trimRequestsToNumOfSources: false,
|
|
1073
|
+
// @ts-ignore - config coming from registerPlugin
|
|
1074
|
+
enableAv1: this.config.enableAv1SlidesSupport,
|
|
1056
1075
|
}
|
|
1057
1076
|
),
|
|
1058
1077
|
};
|
|
@@ -1557,6 +1576,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1557
1576
|
this.controlsOptionsManager = new ControlsOptionsManager(this.meetingRequest, {
|
|
1558
1577
|
locusUrl: this.locusInfo?.url,
|
|
1559
1578
|
displayHints: [],
|
|
1579
|
+
getControls: () => this.locusInfo?.controls,
|
|
1580
|
+
isWebinar: () => this.locusInfo?.info?.isWebinar,
|
|
1560
1581
|
});
|
|
1561
1582
|
|
|
1562
1583
|
this.setUpLocusInfoListeners();
|
|
@@ -1655,11 +1676,11 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1655
1676
|
/**
|
|
1656
1677
|
* Information needed for a retry of a call to joinWithMedia
|
|
1657
1678
|
* @instance
|
|
1658
|
-
* @type {{
|
|
1679
|
+
* @type {{retryCount: number; prevJoinResponse?: any}}
|
|
1659
1680
|
* @private
|
|
1660
1681
|
* @memberof Meeting
|
|
1661
1682
|
*/
|
|
1662
|
-
this.joinWithMediaRetryInfo = {
|
|
1683
|
+
this.joinWithMediaRetryInfo = {retryCount: 0, prevJoinResponse: undefined};
|
|
1663
1684
|
|
|
1664
1685
|
/**
|
|
1665
1686
|
* Connection state handler
|
|
@@ -1713,6 +1734,37 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1713
1734
|
this.mediaServerIp = undefined;
|
|
1714
1735
|
}
|
|
1715
1736
|
|
|
1737
|
+
/**
|
|
1738
|
+
* Get the ingress payload type for a given media type and codec mime type
|
|
1739
|
+
* @param {MediaType} mediaType - The media type
|
|
1740
|
+
* @param {MediaCodecMimeType} codecMimeType - The codec mime type
|
|
1741
|
+
* @returns {number | undefined} - The ingress payload type
|
|
1742
|
+
* @private
|
|
1743
|
+
* @memberof Meeting
|
|
1744
|
+
*/
|
|
1745
|
+
private getIngressPayloadTypeCallback(
|
|
1746
|
+
mediaType: MediaType,
|
|
1747
|
+
codecMimeType: MediaCodecMimeType
|
|
1748
|
+
): number | undefined {
|
|
1749
|
+
if (this.isMultistream) {
|
|
1750
|
+
try {
|
|
1751
|
+
return this.mediaProperties.webrtcMediaConnection.getIngressPayloadType(
|
|
1752
|
+
mediaType,
|
|
1753
|
+
codecMimeType
|
|
1754
|
+
);
|
|
1755
|
+
} catch (error) {
|
|
1756
|
+
LoggerProxy.logger.info(
|
|
1757
|
+
`Meeting:index#mediaRequestManager --> failed to get ingress payload type for mediaType=${mediaType}, codecMimeType=${codecMimeType}`,
|
|
1758
|
+
error
|
|
1759
|
+
);
|
|
1760
|
+
|
|
1761
|
+
return undefined;
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
return undefined;
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1716
1768
|
/**
|
|
1717
1769
|
* Temporary func to return webex object,
|
|
1718
1770
|
* in order to access internal plugin metrics
|
|
@@ -2789,7 +2841,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2789
2841
|
private setupLocusControlsListener() {
|
|
2790
2842
|
this.locusInfo.on(
|
|
2791
2843
|
LOCUSINFO.EVENTS.CONTROLS_RECORDING_UPDATED,
|
|
2792
|
-
({state, modifiedBy, lastModified}) => {
|
|
2844
|
+
({state, modifiedBy, lastModified, modifiedByServiceAppName, modifiedByServiceAppId}) => {
|
|
2793
2845
|
let event;
|
|
2794
2846
|
|
|
2795
2847
|
switch (state) {
|
|
@@ -2815,6 +2867,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2815
2867
|
state: state === RECORDING_STATE.RESUMED ? RECORDING_STATE.RECORDING : state,
|
|
2816
2868
|
modifiedBy,
|
|
2817
2869
|
lastModified,
|
|
2870
|
+
modifiedByServiceAppName,
|
|
2871
|
+
modifiedByServiceAppId,
|
|
2818
2872
|
};
|
|
2819
2873
|
Trigger.trigger(
|
|
2820
2874
|
this,
|
|
@@ -3459,13 +3513,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3459
3513
|
this.breakouts.locusUrlUpdate(url);
|
|
3460
3514
|
this.simultaneousInterpretation.locusUrlUpdate(url);
|
|
3461
3515
|
this.annotation.locusUrlUpdate(url);
|
|
3516
|
+
this.aiEnableRequest.locusUrlUpdate(url);
|
|
3462
3517
|
this.locusUrl = url;
|
|
3463
3518
|
this.locusId = this.locusUrl?.split('/').pop();
|
|
3464
3519
|
this.recordingController.setLocusUrl(this.locusUrl);
|
|
3465
3520
|
this.controlsOptionsManager.setLocusUrl(this.locusUrl, !!isMainLocus);
|
|
3466
3521
|
this.webinar.locusUrlUpdate(url);
|
|
3467
|
-
// @ts-ignore
|
|
3468
|
-
this.webex.internal.llm.setRefreshHandler(() => this.refreshDataChannelToken());
|
|
3469
3522
|
|
|
3470
3523
|
Trigger.trigger(
|
|
3471
3524
|
this,
|
|
@@ -3734,7 +3787,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3734
3787
|
});
|
|
3735
3788
|
this.updateLLMConnection();
|
|
3736
3789
|
});
|
|
3737
|
-
this.locusInfo.on(LOCUSINFO.EVENTS.SELF_ADMITTED_GUEST, (payload) => {
|
|
3790
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.SELF_ADMITTED_GUEST, async (payload) => {
|
|
3738
3791
|
this.stopKeepAlive();
|
|
3739
3792
|
|
|
3740
3793
|
if (payload) {
|
|
@@ -3761,13 +3814,15 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3761
3814
|
}
|
|
3762
3815
|
this.rtcMetrics?.sendNextMetrics();
|
|
3763
3816
|
|
|
3764
|
-
|
|
3817
|
+
try {
|
|
3818
|
+
await this.ensureDefaultDatachannelTokenAfterAdmit();
|
|
3819
|
+
} catch (error) {
|
|
3765
3820
|
LoggerProxy.logger.warn(
|
|
3766
3821
|
`Meeting:index#setUpLocusInfoSelfListener --> failed post-admit token prefetch flow: ${
|
|
3767
3822
|
error?.message || String(error)
|
|
3768
3823
|
}`
|
|
3769
3824
|
);
|
|
3770
|
-
}
|
|
3825
|
+
}
|
|
3771
3826
|
|
|
3772
3827
|
this.updateLLMConnection();
|
|
3773
3828
|
});
|
|
@@ -3813,6 +3868,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3813
3868
|
});
|
|
3814
3869
|
|
|
3815
3870
|
this.locusInfo.on(LOCUSINFO.EVENTS.SELF_CANNOT_VIEW_PARTICIPANT_LIST_CHANGE, (payload) => {
|
|
3871
|
+
// canViewTheParticipantList meeting action depends on this value, so we need to update meeting actions
|
|
3872
|
+
this.updateMeetingActions();
|
|
3873
|
+
|
|
3816
3874
|
Trigger.trigger(
|
|
3817
3875
|
this,
|
|
3818
3876
|
{
|
|
@@ -4007,7 +4065,14 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4007
4065
|
// is not changed by any delta event
|
|
4008
4066
|
if (object && Object.keys(object).length) {
|
|
4009
4067
|
Object.keys(object).forEach((key) => {
|
|
4068
|
+
const previousValue = this[key];
|
|
4069
|
+
|
|
4010
4070
|
this[key] = object[key];
|
|
4071
|
+
|
|
4072
|
+
if (this.promisesWaitingForPropUpdate[key] && previousValue !== object[key]) {
|
|
4073
|
+
this.promisesWaitingForPropUpdate[key].resolve();
|
|
4074
|
+
delete this.promisesWaitingForPropUpdate[key];
|
|
4075
|
+
}
|
|
4011
4076
|
});
|
|
4012
4077
|
}
|
|
4013
4078
|
}
|
|
@@ -4145,7 +4210,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4145
4210
|
}
|
|
4146
4211
|
|
|
4147
4212
|
/**
|
|
4148
|
-
* Transfer the moderator role to another eligible member
|
|
4213
|
+
* Transfer the moderator role to another eligible member.
|
|
4214
|
+
*
|
|
4215
|
+
* If the target member is currently joined in the current session, the request is
|
|
4216
|
+
* sent against the current locus. Otherwise, we search the breakout sessions and,
|
|
4217
|
+
* when the member is found in one, we send the request against that breakout
|
|
4218
|
+
* session's locus url instead.
|
|
4149
4219
|
* @param {String} memberId
|
|
4150
4220
|
* @param {Boolean} moderator
|
|
4151
4221
|
* @returns {Promise} see #members.transferHostToMember
|
|
@@ -4153,7 +4223,21 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4153
4223
|
* @memberof Meeting
|
|
4154
4224
|
*/
|
|
4155
4225
|
public transfer(memberId: string, moderator = true) {
|
|
4156
|
-
|
|
4226
|
+
const targetMember = this.members.membersCollection.get(memberId);
|
|
4227
|
+
|
|
4228
|
+
if (targetMember?.isInMeeting) {
|
|
4229
|
+
return this.members.transferHostToMember(memberId, moderator);
|
|
4230
|
+
}
|
|
4231
|
+
|
|
4232
|
+
const breakout = this.breakouts?.breakouts?.models?.find((bo: any) => {
|
|
4233
|
+
const breakoutMember = bo.members?.membersCollection?.get(memberId);
|
|
4234
|
+
|
|
4235
|
+
return breakoutMember?.isInMeeting;
|
|
4236
|
+
});
|
|
4237
|
+
|
|
4238
|
+
const breakoutLocusUrl = breakout?.breakoutRosterLocus?.url;
|
|
4239
|
+
|
|
4240
|
+
return this.members.transferHostToMember(memberId, moderator, breakoutLocusUrl);
|
|
4157
4241
|
}
|
|
4158
4242
|
|
|
4159
4243
|
/**
|
|
@@ -4611,6 +4695,13 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4611
4695
|
),
|
|
4612
4696
|
isAttendeeRequestAiAssistantDeclinedAll:
|
|
4613
4697
|
MeetingUtil.attendeeRequestAiAssistantDeclinedAll(this.userDisplayHints),
|
|
4698
|
+
isAnonymizeDisplayNamesEnabled: MeetingUtil.isAnonymizeDisplayNamesEnabled(
|
|
4699
|
+
this.userDisplayHints
|
|
4700
|
+
),
|
|
4701
|
+
canViewTheParticipantList: MeetingUtil.canViewTheParticipantList(
|
|
4702
|
+
this.userDisplayHints,
|
|
4703
|
+
this.canNotViewTheParticipantList ?? false
|
|
4704
|
+
),
|
|
4614
4705
|
}) || changed;
|
|
4615
4706
|
}
|
|
4616
4707
|
if (changed) {
|
|
@@ -4659,6 +4750,34 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4659
4750
|
this.sipUri = sipUri;
|
|
4660
4751
|
}
|
|
4661
4752
|
|
|
4753
|
+
/**
|
|
4754
|
+
* After initial locus setup, refreshes destination with synced locus data and optionally
|
|
4755
|
+
* performs deferred meeting info fetch when initial locus was incomplete.
|
|
4756
|
+
* @param {LocusDTO} locus
|
|
4757
|
+
* @returns {void}
|
|
4758
|
+
*/
|
|
4759
|
+
public async finalizeMeetingAfterInitialLocusSetup(locus: LocusDTO): Promise<void> {
|
|
4760
|
+
if (locus && this?.destinationType === DESTINATION_TYPE.LOCUS_ID) {
|
|
4761
|
+
// destination is initialized from the initial locus snapshot in constructor,
|
|
4762
|
+
// so refresh it after locus sync to avoid stale partial hash-tree data.
|
|
4763
|
+
this.destination = locus;
|
|
4764
|
+
}
|
|
4765
|
+
if (
|
|
4766
|
+
(!this.meetingInfo || isEmpty(this.meetingInfo)) &&
|
|
4767
|
+
(this.destination as LocusDTO)?.info &&
|
|
4768
|
+
!this.fetchMeetingInfoTimeoutId &&
|
|
4769
|
+
!MeetingsUtil.isOneOnOneCall(locus)
|
|
4770
|
+
) {
|
|
4771
|
+
try {
|
|
4772
|
+
await this.fetchMeetingInfo({});
|
|
4773
|
+
} catch (error: any) {
|
|
4774
|
+
LoggerProxy.logger.info(
|
|
4775
|
+
`Meeting:index#finalizeMeetingAfterInitialLocusSetup --> deferred fetchMeetingInfo failed: ${error.message}`
|
|
4776
|
+
);
|
|
4777
|
+
}
|
|
4778
|
+
}
|
|
4779
|
+
}
|
|
4780
|
+
|
|
4662
4781
|
/**
|
|
4663
4782
|
* Set the locus info the class instance. Should be called with the parsed locus
|
|
4664
4783
|
* we got in the join response.
|
|
@@ -5130,8 +5249,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5130
5249
|
public setMercuryListener() {
|
|
5131
5250
|
// Client will have a socket manager and handle reconnecting to mercury, when we reconnect to mercury
|
|
5132
5251
|
// if the meeting has active peer connections, it should try to reconnect.
|
|
5133
|
-
|
|
5134
|
-
this.webex.internal.mercury.on(ONLINE, () => {
|
|
5252
|
+
this.mercuryOnlineHandler = () => {
|
|
5135
5253
|
LoggerProxy.logger.info('Meeting:index#setMercuryListener --> Web socket online');
|
|
5136
5254
|
|
|
5137
5255
|
// Only send restore event when it was disconnected before and for connected later
|
|
@@ -5141,15 +5259,47 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5141
5259
|
});
|
|
5142
5260
|
}
|
|
5143
5261
|
this.hasWebsocketConnected = true;
|
|
5144
|
-
}
|
|
5262
|
+
};
|
|
5145
5263
|
|
|
5146
|
-
|
|
5147
|
-
this.webex.internal.mercury.on(OFFLINE, () => {
|
|
5264
|
+
this.mercuryOfflineHandler = () => {
|
|
5148
5265
|
LoggerProxy.logger.error('Meeting:index#setMercuryListener --> Web socket offline');
|
|
5149
5266
|
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MERCURY_CONNECTION_FAILURE, {
|
|
5150
5267
|
correlation_id: this.correlationId,
|
|
5151
5268
|
});
|
|
5152
|
-
}
|
|
5269
|
+
};
|
|
5270
|
+
|
|
5271
|
+
// @ts-ignore
|
|
5272
|
+
this.webex.internal.mercury.on(ONLINE, this.mercuryOnlineHandler);
|
|
5273
|
+
// @ts-ignore
|
|
5274
|
+
this.webex.internal.mercury.on(OFFLINE, this.mercuryOfflineHandler);
|
|
5275
|
+
}
|
|
5276
|
+
|
|
5277
|
+
/**
|
|
5278
|
+
* Removes this meeting's Mercury ONLINE/OFFLINE event listeners registered
|
|
5279
|
+
* by setMercuryListener(). Must be called before Locus /leave to avoid
|
|
5280
|
+
* unnecessary syncs/metrics triggered by events received while leaving
|
|
5281
|
+
* (per Locus team recommendation).
|
|
5282
|
+
*
|
|
5283
|
+
* Mercury is a process-wide singleton shared with other plugins, so we
|
|
5284
|
+
* pass the bound handler refs to .off() to avoid clearing every listener
|
|
5285
|
+
* for ONLINE/OFFLINE on the shared emitter.
|
|
5286
|
+
*
|
|
5287
|
+
* Idempotent: subsequent calls are no-ops because the handler refs are
|
|
5288
|
+
* cleared after detaching.
|
|
5289
|
+
* @private
|
|
5290
|
+
* @returns {void}
|
|
5291
|
+
*/
|
|
5292
|
+
private stopListeningForMercuryEvents() {
|
|
5293
|
+
if (this.mercuryOnlineHandler) {
|
|
5294
|
+
// @ts-ignore
|
|
5295
|
+
this.webex.internal.mercury.off(ONLINE, this.mercuryOnlineHandler);
|
|
5296
|
+
this.mercuryOnlineHandler = undefined;
|
|
5297
|
+
}
|
|
5298
|
+
if (this.mercuryOfflineHandler) {
|
|
5299
|
+
// @ts-ignore
|
|
5300
|
+
this.webex.internal.mercury.off(OFFLINE, this.mercuryOfflineHandler);
|
|
5301
|
+
this.mercuryOfflineHandler = undefined;
|
|
5302
|
+
}
|
|
5153
5303
|
}
|
|
5154
5304
|
|
|
5155
5305
|
/**
|
|
@@ -5460,7 +5610,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5460
5610
|
} = {}
|
|
5461
5611
|
) {
|
|
5462
5612
|
const {mediaOptions, joinOptions = {}} = options;
|
|
5463
|
-
const {
|
|
5613
|
+
const {retryCount, prevJoinResponse, prevError} = this.joinWithMediaRetryInfo;
|
|
5464
5614
|
|
|
5465
5615
|
if (!mediaOptions?.allowMediaInLobby) {
|
|
5466
5616
|
return Promise.reject(
|
|
@@ -5486,12 +5636,17 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5486
5636
|
);
|
|
5487
5637
|
}
|
|
5488
5638
|
|
|
5639
|
+
const shouldJoin =
|
|
5640
|
+
!joinResponse || // first try, when the join response is empty
|
|
5641
|
+
(prevError && prevError instanceof UserNotJoinedError) || // last try failed with UserNotJoinedError
|
|
5642
|
+
MeetingUtil.isUserInLeftState(this.locusInfo); // locus dropped the connection before we can re-try addMedia
|
|
5643
|
+
|
|
5489
5644
|
try {
|
|
5490
5645
|
let turnServerInfo;
|
|
5491
5646
|
let turnDiscoverySkippedReason;
|
|
5492
5647
|
let forceTurnDiscovery = false;
|
|
5493
5648
|
|
|
5494
|
-
if (
|
|
5649
|
+
if (shouldJoin) {
|
|
5495
5650
|
// This is the 1st attempt or a retry after join request failed -> we need to do a join with TURN discovery
|
|
5496
5651
|
|
|
5497
5652
|
const turnDiscoveryRequest = await this.roap.generateTurnDiscoveryRequestMessage(
|
|
@@ -5532,14 +5687,17 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5532
5687
|
|
|
5533
5688
|
const mediaResponse = await this.addMediaInternal(
|
|
5534
5689
|
() => {
|
|
5535
|
-
|
|
5690
|
+
// callback is not called when UserNotJoinedError is thrown
|
|
5691
|
+
return this.joinWithMediaRetryInfo.retryCount >= 1
|
|
5692
|
+
? 'JOIN_MEETING_FINAL'
|
|
5693
|
+
: 'JOIN_MEETING_RETRY';
|
|
5536
5694
|
},
|
|
5537
5695
|
turnServerInfo,
|
|
5538
5696
|
forceTurnDiscovery,
|
|
5539
5697
|
mediaOptions
|
|
5540
5698
|
);
|
|
5541
5699
|
|
|
5542
|
-
this.joinWithMediaRetryInfo = {
|
|
5700
|
+
this.joinWithMediaRetryInfo = {retryCount: 0, prevJoinResponse: undefined};
|
|
5543
5701
|
|
|
5544
5702
|
return {
|
|
5545
5703
|
join: joinResponse,
|
|
@@ -5553,8 +5711,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5553
5711
|
|
|
5554
5712
|
this.roap.abortTurnDiscovery();
|
|
5555
5713
|
|
|
5556
|
-
//
|
|
5557
|
-
let shouldRetry =
|
|
5714
|
+
// let's do a retry
|
|
5715
|
+
let shouldRetry =
|
|
5716
|
+
retryCount < 1 ||
|
|
5717
|
+
(error instanceof UserNotJoinedError && retryCount < JOIN_WITH_MEDIA_RETRY_MAX_COUNT);
|
|
5558
5718
|
|
|
5559
5719
|
if (
|
|
5560
5720
|
CallDiagnosticUtils.isSdpOfferCreationError(error) ||
|
|
@@ -5579,8 +5739,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5579
5739
|
});
|
|
5580
5740
|
}
|
|
5581
5741
|
|
|
5582
|
-
// we only want to call leave if join was successful and
|
|
5583
|
-
if (joined &&
|
|
5742
|
+
// we only want to call leave if join was successful, and we won't be doing any more retries
|
|
5743
|
+
if (joined && !shouldRetry) {
|
|
5584
5744
|
try {
|
|
5585
5745
|
await this.leave({resourceId: joinOptions?.resourceId, reason: 'joinWithMedia failure'});
|
|
5586
5746
|
} catch (e) {
|
|
@@ -5597,7 +5757,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5597
5757
|
reason: error.message,
|
|
5598
5758
|
stack: error.stack,
|
|
5599
5759
|
leaveErrorReason: leaveError?.message,
|
|
5600
|
-
isRetry,
|
|
5760
|
+
isRetry: retryCount > 0,
|
|
5601
5761
|
},
|
|
5602
5762
|
{
|
|
5603
5763
|
type: error.name,
|
|
@@ -5606,15 +5766,26 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5606
5766
|
|
|
5607
5767
|
if (shouldRetry) {
|
|
5608
5768
|
LoggerProxy.logger.warn('Meeting:index#joinWithMedia --> retrying call to joinWithMedia');
|
|
5609
|
-
this.joinWithMediaRetryInfo.
|
|
5769
|
+
this.joinWithMediaRetryInfo.retryCount = retryCount + 1;
|
|
5610
5770
|
this.joinWithMediaRetryInfo.prevJoinResponse = joinResponse;
|
|
5771
|
+
this.joinWithMediaRetryInfo.prevError = error;
|
|
5772
|
+
if (!this.joinWithMediaRetryInfo.firstError) {
|
|
5773
|
+
this.joinWithMediaRetryInfo.firstError = error;
|
|
5774
|
+
}
|
|
5611
5775
|
|
|
5612
5776
|
return this.joinWithMedia(options);
|
|
5613
5777
|
}
|
|
5614
5778
|
|
|
5615
|
-
|
|
5779
|
+
const {firstError} = this.joinWithMediaRetryInfo;
|
|
5616
5780
|
|
|
5617
|
-
|
|
5781
|
+
this.joinWithMediaRetryInfo = {
|
|
5782
|
+
retryCount: 0,
|
|
5783
|
+
prevJoinResponse: undefined,
|
|
5784
|
+
firstError: undefined,
|
|
5785
|
+
prevError: undefined,
|
|
5786
|
+
};
|
|
5787
|
+
|
|
5788
|
+
throw firstError ?? error;
|
|
5618
5789
|
}
|
|
5619
5790
|
}
|
|
5620
5791
|
|
|
@@ -5865,6 +6036,31 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5865
6036
|
}
|
|
5866
6037
|
};
|
|
5867
6038
|
|
|
6039
|
+
/**
|
|
6040
|
+
* Verifies the relay event was delivered for the active LLM session binding.
|
|
6041
|
+
* @param {RelayEvent} event Event object coming from LLM Connection
|
|
6042
|
+
* @returns {boolean}
|
|
6043
|
+
*/
|
|
6044
|
+
private isRelayEventRouteValid(event: RelayEvent): boolean {
|
|
6045
|
+
const route = event?.headers?.route;
|
|
6046
|
+
|
|
6047
|
+
if (!route) {
|
|
6048
|
+
return true;
|
|
6049
|
+
}
|
|
6050
|
+
|
|
6051
|
+
const {llm} = (this as any).webex.internal;
|
|
6052
|
+
const isPracticeSession = llm.isConnected(LLM_PRACTICE_SESSION);
|
|
6053
|
+
const expectedBinding = isPracticeSession
|
|
6054
|
+
? llm.getBinding(LLM_PRACTICE_SESSION)
|
|
6055
|
+
: llm.getBinding();
|
|
6056
|
+
|
|
6057
|
+
if (!expectedBinding || route === expectedBinding) {
|
|
6058
|
+
return true;
|
|
6059
|
+
}
|
|
6060
|
+
|
|
6061
|
+
return false;
|
|
6062
|
+
}
|
|
6063
|
+
|
|
5868
6064
|
/**
|
|
5869
6065
|
* Callback called when a relay event is received from meeting LLM Connection
|
|
5870
6066
|
* @param {RelayEvent} e Event object coming from LLM Connection
|
|
@@ -5872,6 +6068,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5872
6068
|
* @returns {void}
|
|
5873
6069
|
*/
|
|
5874
6070
|
private processRelayEvent = (e: RelayEvent): void => {
|
|
6071
|
+
if (!this.isRelayEventRouteValid(e)) {
|
|
6072
|
+
return;
|
|
6073
|
+
}
|
|
5875
6074
|
switch (e.data.relayType) {
|
|
5876
6075
|
case REACTION_RELAY_TYPES.REACTION:
|
|
5877
6076
|
if (
|
|
@@ -6002,6 +6201,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6002
6201
|
*/
|
|
6003
6202
|
private handleLLMOnline = (): void => {
|
|
6004
6203
|
this.restoreLLMSubscriptionsIfNeeded();
|
|
6204
|
+
this.locusInfo.syncAllHashTreeDatasets({onlyLLM: true});
|
|
6005
6205
|
|
|
6006
6206
|
Trigger.trigger(
|
|
6007
6207
|
this,
|
|
@@ -6301,8 +6501,57 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6301
6501
|
}
|
|
6302
6502
|
}
|
|
6303
6503
|
|
|
6504
|
+
/**
|
|
6505
|
+
* Removes LLM event listeners and clears the health check timer.
|
|
6506
|
+
* Must be called before Locus /leave to avoid unnecessary syncs triggered
|
|
6507
|
+
* by events received while leaving (per Locus team recommendation).
|
|
6508
|
+
* Idempotent: safe to call multiple times; .off() is a no-op when no
|
|
6509
|
+
* matching listener is registered.
|
|
6510
|
+
* @private
|
|
6511
|
+
* @returns {void}
|
|
6512
|
+
*/
|
|
6513
|
+
private stopListeningForLLMEvents() {
|
|
6514
|
+
// @ts-ignore - fix types
|
|
6515
|
+
this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
|
|
6516
|
+
// @ts-ignore - fix types
|
|
6517
|
+
this.webex.internal.llm.off(LOCUS_LLM_EVENT, this.processLocusLLMEvent);
|
|
6518
|
+
this.clearLLMHealthCheckTimer();
|
|
6519
|
+
}
|
|
6520
|
+
|
|
6521
|
+
/**
|
|
6522
|
+
* Stops listening on every event bus (LLM, Mercury, voicea/transcription,
|
|
6523
|
+
* annotation) that could otherwise deliver events to this meeting while
|
|
6524
|
+
* Locus is processing /leave or /end. Per the Locus team recommendation,
|
|
6525
|
+
* this must run before the Locus request is dispatched to avoid
|
|
6526
|
+
* unnecessary syncs triggered by in-flight events.
|
|
6527
|
+
*
|
|
6528
|
+
* Voicea (transcription) subscribes to llm 'event:relay.event' internally,
|
|
6529
|
+
* and the annotation plugin subscribes to both mercury and llm, so both
|
|
6530
|
+
* must be torn down alongside the direct LLM/Mercury listeners.
|
|
6531
|
+
*
|
|
6532
|
+
* Idempotent: safe to call multiple times; .off() is a no-op when no
|
|
6533
|
+
* matching listener is registered, and stopTranscription is guarded.
|
|
6534
|
+
* @private
|
|
6535
|
+
* @returns {void}
|
|
6536
|
+
*/
|
|
6537
|
+
private stopListeningForMeetingEvents() {
|
|
6538
|
+
this.stopListeningForLLMEvents();
|
|
6539
|
+
this.stopListeningForMercuryEvents();
|
|
6540
|
+
if (this.transcription) {
|
|
6541
|
+
this.stopTranscription();
|
|
6542
|
+
this.transcription = undefined;
|
|
6543
|
+
}
|
|
6544
|
+
this.annotation.deregisterEvents();
|
|
6545
|
+
}
|
|
6546
|
+
|
|
6304
6547
|
/**
|
|
6305
6548
|
* Disconnects and cleans up the default LLM session listeners/timers.
|
|
6549
|
+
*
|
|
6550
|
+
* Ownership-aware: only calls `disconnectLLM` when this meeting is the
|
|
6551
|
+
* current owner of the default LLM session (or when no owner is recorded).
|
|
6552
|
+
* Event listeners belonging to this meeting instance are always detached
|
|
6553
|
+
* so they do not receive another meeting's relay events.
|
|
6554
|
+
*
|
|
6306
6555
|
* @param {Object} options
|
|
6307
6556
|
* @param {boolean} [options.removeOnlineListener=true] removes the one-time online listener
|
|
6308
6557
|
* @param {boolean} [options.throwOnError=true] rethrows disconnect errors when true
|
|
@@ -6315,12 +6564,29 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6315
6564
|
removeOnlineListener?: boolean;
|
|
6316
6565
|
throwOnError?: boolean;
|
|
6317
6566
|
} = {}): Promise<void> => {
|
|
6567
|
+
// @ts-ignore - Fix type
|
|
6568
|
+
// @ts-ignore - Fix type
|
|
6569
|
+
const {currentOwner, isOwner} = this.webex.internal.llm.resolveSessionOwnership(
|
|
6570
|
+
this.id,
|
|
6571
|
+
LLM_DEFAULT_SESSION
|
|
6572
|
+
);
|
|
6573
|
+
|
|
6318
6574
|
try {
|
|
6319
|
-
|
|
6320
|
-
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6575
|
+
if (isOwner) {
|
|
6576
|
+
// @ts-ignore - Fix type
|
|
6577
|
+
await this.webex.internal.llm.disconnectLLM(
|
|
6578
|
+
{
|
|
6579
|
+
code: 3050,
|
|
6580
|
+
reason: 'done (permanent)',
|
|
6581
|
+
},
|
|
6582
|
+
LLM_DEFAULT_SESSION,
|
|
6583
|
+
this.id
|
|
6584
|
+
);
|
|
6585
|
+
} else {
|
|
6586
|
+
LoggerProxy.logger.info(
|
|
6587
|
+
`Meeting:index#cleanupLLMConneciton --> skipping disconnect; LLM owned by meeting ${currentOwner}, not ${this.id}`
|
|
6588
|
+
);
|
|
6589
|
+
}
|
|
6324
6590
|
} catch (error) {
|
|
6325
6591
|
LoggerProxy.logger.error(
|
|
6326
6592
|
'Meeting:index#cleanupLLMConneciton --> Failed to disconnect default LLM session',
|
|
@@ -6335,23 +6601,33 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6335
6601
|
// @ts-ignore - Fix type
|
|
6336
6602
|
this.webex.internal.llm.off('online', this.handleLLMOnline);
|
|
6337
6603
|
}
|
|
6338
|
-
|
|
6339
|
-
this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
|
|
6340
|
-
// @ts-ignore - Fix type
|
|
6341
|
-
this.webex.internal.llm.off(LOCUS_LLM_EVENT, this.processLocusLLMEvent);
|
|
6604
|
+
this.stopListeningForLLMEvents();
|
|
6342
6605
|
|
|
6343
|
-
|
|
6606
|
+
// Re-check ownership after awaiting disconnectLLM. If ownership changed
|
|
6607
|
+
// while cleanup was in flight, do not clear another meeting's owner tag.
|
|
6608
|
+
if (isOwner) {
|
|
6609
|
+
const {currentOwner: currentOwnerAfterCleanup} =
|
|
6610
|
+
// @ts-ignore - Fix type
|
|
6611
|
+
this.webex.internal.llm.resolveSessionOwnership(this.id, LLM_DEFAULT_SESSION);
|
|
6612
|
+
|
|
6613
|
+
if (currentOwnerAfterCleanup === this.id) {
|
|
6614
|
+
// @ts-ignore - Fix type
|
|
6615
|
+
this.webex.internal.llm.setOwnerMeetingId?.(undefined);
|
|
6616
|
+
}
|
|
6617
|
+
}
|
|
6344
6618
|
}
|
|
6345
6619
|
};
|
|
6346
6620
|
|
|
6347
6621
|
/**
|
|
6348
|
-
* Clears
|
|
6349
|
-
*
|
|
6622
|
+
* Clears data channel tokens associated with this meeting ownership.
|
|
6623
|
+
* Ownership checks are enforced in internal-plugin-llm.
|
|
6350
6624
|
* @returns {void}
|
|
6351
6625
|
*/
|
|
6352
6626
|
clearDataChannelToken(): void {
|
|
6353
6627
|
// @ts-ignore
|
|
6354
|
-
this.webex.internal.llm.
|
|
6628
|
+
this.webex.internal.llm.clearDatachannelToken(LLM_DEFAULT_SESSION, this.id);
|
|
6629
|
+
// @ts-ignore
|
|
6630
|
+
this.webex.internal.llm.clearDatachannelToken(LLM_PRACTICE_SESSION, this.id);
|
|
6355
6631
|
}
|
|
6356
6632
|
|
|
6357
6633
|
/**
|
|
@@ -6366,14 +6642,15 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6366
6642
|
|
|
6367
6643
|
if (datachannelToken) {
|
|
6368
6644
|
// @ts-ignore
|
|
6369
|
-
this.webex.internal.llm.setDatachannelToken(datachannelToken,
|
|
6645
|
+
this.webex.internal.llm.setDatachannelToken(datachannelToken, LLM_DEFAULT_SESSION, this.id);
|
|
6370
6646
|
}
|
|
6371
6647
|
|
|
6372
6648
|
if (practiceSessionDatachannelToken) {
|
|
6373
6649
|
// @ts-ignore
|
|
6374
6650
|
this.webex.internal.llm.setDatachannelToken(
|
|
6375
6651
|
practiceSessionDatachannelToken,
|
|
6376
|
-
|
|
6652
|
+
LLM_PRACTICE_SESSION,
|
|
6653
|
+
this.id
|
|
6377
6654
|
);
|
|
6378
6655
|
}
|
|
6379
6656
|
}
|
|
@@ -6386,7 +6663,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6386
6663
|
private async ensureDefaultDatachannelTokenAfterAdmit(): Promise<boolean> {
|
|
6387
6664
|
try {
|
|
6388
6665
|
// @ts-ignore
|
|
6389
|
-
const datachannelToken = this.webex.internal.llm.getDatachannelToken(
|
|
6666
|
+
const datachannelToken = this.webex.internal.llm.getDatachannelToken(
|
|
6667
|
+
LLM_DEFAULT_SESSION,
|
|
6668
|
+
this.id
|
|
6669
|
+
);
|
|
6390
6670
|
// @ts-ignore
|
|
6391
6671
|
const isDataChannelTokenEnabled = await this.webex.internal.llm.isDataChannelTokenEnabled();
|
|
6392
6672
|
|
|
@@ -6408,7 +6688,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6408
6688
|
// @ts-ignore
|
|
6409
6689
|
this.webex.internal.llm.setDatachannelToken(
|
|
6410
6690
|
fetchedDatachannelToken,
|
|
6411
|
-
|
|
6691
|
+
LLM_DEFAULT_SESSION,
|
|
6692
|
+
this.id
|
|
6412
6693
|
);
|
|
6413
6694
|
|
|
6414
6695
|
return true;
|
|
@@ -6435,15 +6716,58 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6435
6716
|
|
|
6436
6717
|
const isJoined = this.isJoined();
|
|
6437
6718
|
|
|
6438
|
-
|
|
6439
|
-
|
|
6440
|
-
|
|
6719
|
+
const dataChannelUrl = datachannelUrl;
|
|
6720
|
+
|
|
6721
|
+
// Ownership guard: when the default LLM session is already connected and
|
|
6722
|
+
// owned by a *different* Meeting instance, do not disconnect or reconfigure
|
|
6723
|
+
// it. Another meeting's `updateLLMConnection` must be ignored here to
|
|
6724
|
+
// avoid killing the socket it relies on. We only proceed to manage the
|
|
6725
|
+
// connection when this meeting is the current owner, or when no owner is
|
|
6726
|
+
// set yet (first claim).
|
|
6727
|
+
// @ts-ignore - Fix type
|
|
6728
|
+
const {currentOwner} = this.webex.internal.llm.resolveSessionOwnership(
|
|
6729
|
+
this.id,
|
|
6730
|
+
LLM_DEFAULT_SESSION
|
|
6441
6731
|
);
|
|
6442
6732
|
|
|
6443
|
-
|
|
6733
|
+
// Capture connectivity before any reconnect attempt. If LLM was already
|
|
6734
|
+
// connected, we must respect current ownership. If it was disconnected,
|
|
6735
|
+
// this flow may reclaim stale owner tags after a fresh connect.
|
|
6736
|
+
// @ts-ignore - Fix type
|
|
6737
|
+
const wasConnected = this.webex.internal.llm.isConnected();
|
|
6444
6738
|
|
|
6739
|
+
// Prefer ownership-scoped token read. For disconnected stale-owner reclaim
|
|
6740
|
+
// flows, fallback to ownerless read so initial register can still carry a
|
|
6741
|
+
// token and recover from stale ownership without 401/403 dead-end.
|
|
6445
6742
|
// @ts-ignore - Fix type
|
|
6446
|
-
|
|
6743
|
+
let datachannelToken = this.webex.internal.llm.getDatachannelToken(
|
|
6744
|
+
LLM_DEFAULT_SESSION,
|
|
6745
|
+
this.id
|
|
6746
|
+
);
|
|
6747
|
+
|
|
6748
|
+
if (!datachannelToken && !wasConnected && currentOwner && currentOwner !== this.id) {
|
|
6749
|
+
// @ts-ignore - Fix type
|
|
6750
|
+
datachannelToken = this.webex.internal.llm.getDatachannelToken(LLM_DEFAULT_SESSION);
|
|
6751
|
+
}
|
|
6752
|
+
|
|
6753
|
+
// @ts-ignore - Fix type
|
|
6754
|
+
if (wasConnected) {
|
|
6755
|
+
if (currentOwner && currentOwner !== this.id) {
|
|
6756
|
+
// Another meeting owns the live LLM socket. We must not disconnect
|
|
6757
|
+
// or reconfigure it -- doing so would tear down a session the
|
|
6758
|
+
// owning meeting still relies on. Locus/datachannel URL mismatch is
|
|
6759
|
+
// expected here (each meeting has its own locus URL) and is NOT a
|
|
6760
|
+
// valid signal of staleness, so we never reclaim from this path.
|
|
6761
|
+
// The only safe reclaim mechanism is the `finally`-block owner-tag
|
|
6762
|
+
// release in `cleanupLLMConneciton`, which fires when this meeting
|
|
6763
|
+
// itself is being torn down.
|
|
6764
|
+
LoggerProxy.logger.info(
|
|
6765
|
+
`Meeting:index#updateLLMConnection --> skipping; LLM owned by meeting ${currentOwner}, not ${this.id}`
|
|
6766
|
+
);
|
|
6767
|
+
|
|
6768
|
+
return undefined;
|
|
6769
|
+
}
|
|
6770
|
+
|
|
6447
6771
|
if (
|
|
6448
6772
|
// @ts-ignore - Fix type
|
|
6449
6773
|
url === this.webex.internal.llm.getLocusUrl() &&
|
|
@@ -6460,10 +6784,56 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6460
6784
|
return undefined;
|
|
6461
6785
|
}
|
|
6462
6786
|
|
|
6787
|
+
// Bind refresh handler before registration so interceptor-triggered token
|
|
6788
|
+
// refresh during register POST can resolve a valid handler.
|
|
6789
|
+
// Prefer this meeting as owner, but allow owner-less fallback when a stale
|
|
6790
|
+
// foreign owner tag is present on a disconnected session.
|
|
6791
|
+
const refreshHandlerOwnerMeetingId =
|
|
6792
|
+
currentOwner && currentOwner !== this.id ? undefined : this.id;
|
|
6793
|
+
const shouldAlignRefreshHandlerAfterOwnershipClaim = refreshHandlerOwnerMeetingId !== this.id;
|
|
6794
|
+
// @ts-ignore - Fix type
|
|
6795
|
+
this.webex.internal.llm.setRefreshHandler(
|
|
6796
|
+
() => this.refreshDataChannelToken(),
|
|
6797
|
+
LLM_DEFAULT_SESSION,
|
|
6798
|
+
refreshHandlerOwnerMeetingId
|
|
6799
|
+
);
|
|
6800
|
+
|
|
6463
6801
|
// @ts-ignore - Fix type
|
|
6464
6802
|
return this.webex.internal.llm
|
|
6465
6803
|
.registerAndConnect(url, dataChannelUrl, datachannelToken)
|
|
6466
6804
|
.then((registerAndConnectResult) => {
|
|
6805
|
+
this.locusInfo.syncAllHashTreeDatasets({onlyLLM: true});
|
|
6806
|
+
// Record ownership of the default LLM session for this meeting so
|
|
6807
|
+
// subsequent cross-meeting `updateLLMConnection` / `cleanupLLMConneciton`
|
|
6808
|
+
// calls can detect and skip work that doesn't belong to them.
|
|
6809
|
+
// @ts-ignore - Fix type
|
|
6810
|
+
const {isOwner} = this.webex.internal.llm.resolveSessionOwnership(
|
|
6811
|
+
this.id,
|
|
6812
|
+
LLM_DEFAULT_SESSION
|
|
6813
|
+
);
|
|
6814
|
+
const canReclaimAfterDisconnectedStart = !wasConnected;
|
|
6815
|
+
|
|
6816
|
+
// Refresh handler is pre-bound before registerAndConnect so token
|
|
6817
|
+
// refresh can work even during the registration request itself.
|
|
6818
|
+
if (isOwner || canReclaimAfterDisconnectedStart) {
|
|
6819
|
+
// Record ownership of the default LLM session for this meeting so
|
|
6820
|
+
// subsequent cross-meeting `updateLLMConnection` / `cleanupLLMConneciton`
|
|
6821
|
+
// calls can detect and skip work that doesn't belong to them.
|
|
6822
|
+
// @ts-ignore - Fix type
|
|
6823
|
+
this.webex.internal.llm.setOwnerMeetingId?.(this.id);
|
|
6824
|
+
|
|
6825
|
+
// If we pre-bound refresh ownerlessly (stale-owner reclaim path),
|
|
6826
|
+
// align the handler with the newly claimed owner immediately after
|
|
6827
|
+
// ownership is updated.
|
|
6828
|
+
if (shouldAlignRefreshHandlerAfterOwnershipClaim) {
|
|
6829
|
+
// @ts-ignore - Fix type
|
|
6830
|
+
this.webex.internal.llm.setRefreshHandler(
|
|
6831
|
+
() => this.refreshDataChannelToken(),
|
|
6832
|
+
LLM_DEFAULT_SESSION,
|
|
6833
|
+
this.id
|
|
6834
|
+
);
|
|
6835
|
+
}
|
|
6836
|
+
}
|
|
6467
6837
|
// @ts-ignore - Fix type
|
|
6468
6838
|
this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
|
|
6469
6839
|
// @ts-ignore - Fix type
|
|
@@ -7504,6 +7874,33 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7504
7874
|
}
|
|
7505
7875
|
}
|
|
7506
7876
|
});
|
|
7877
|
+
this.statsAnalyzer.on(StatsAnalyzerEventNames.STATS_UPDATE, (data) => {
|
|
7878
|
+
// Extract srtpCipher from transport stats
|
|
7879
|
+
let srtpCipher: string | undefined;
|
|
7880
|
+
for (const stats of data.stats.values()) {
|
|
7881
|
+
if (stats.type === 'transport' && stats.srtpCipher) {
|
|
7882
|
+
srtpCipher = stats.srtpCipher as string;
|
|
7883
|
+
break;
|
|
7884
|
+
}
|
|
7885
|
+
}
|
|
7886
|
+
|
|
7887
|
+
// Only emit event if srtpCipher has changed
|
|
7888
|
+
if (srtpCipher && srtpCipher !== this.mediaProperties.srtpCipher) {
|
|
7889
|
+
LoggerProxy.logger.info(
|
|
7890
|
+
`Meeting:index#setupStatsAnalyzerEventHandlers --> SRTP cipher changed from ${this.mediaProperties.srtpCipher} to ${srtpCipher}`
|
|
7891
|
+
);
|
|
7892
|
+
this.mediaProperties.srtpCipher = srtpCipher;
|
|
7893
|
+
Trigger.trigger(
|
|
7894
|
+
this,
|
|
7895
|
+
{
|
|
7896
|
+
file: 'meeting/index',
|
|
7897
|
+
function: 'setupStatsAnalyzerEventHandlers',
|
|
7898
|
+
},
|
|
7899
|
+
EVENT_TRIGGERS.MEETING_SRTP_CIPHER_UPDATED,
|
|
7900
|
+
{srtpCipher}
|
|
7901
|
+
);
|
|
7902
|
+
}
|
|
7903
|
+
});
|
|
7507
7904
|
};
|
|
7508
7905
|
|
|
7509
7906
|
getMediaConnectionDebugId() {
|
|
@@ -7550,6 +7947,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7550
7947
|
disableAudioMainDtx: this.config.experimental.disableAudioMainDtx,
|
|
7551
7948
|
// @ts-ignore - config coming from registerPlugin
|
|
7552
7949
|
enableAudioTwcc: this.config.enableAudioTwccForMultistream,
|
|
7950
|
+
// @ts-ignore - config coming from registerPlugin
|
|
7951
|
+
enableAv1SlidesSupport: this.config.enableAv1SlidesSupport,
|
|
7553
7952
|
stopIceGatheringAfterFirstRelayCandidate:
|
|
7554
7953
|
// @ts-ignore - config coming from registerPlugin
|
|
7555
7954
|
this.config.stopIceGatheringAfterFirstRelayCandidate,
|
|
@@ -8187,6 +8586,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
8187
8586
|
regionCode: this.webex.meetings.geoHintInfo?.regionCode,
|
|
8188
8587
|
},
|
|
8189
8588
|
preferTranscoding: !this.isMultistream,
|
|
8589
|
+
getCurrentSelfUrl: () => this.selfUrl,
|
|
8590
|
+
waitForSelfUrlChange: () => this.waitForSelfUrlChange(),
|
|
8190
8591
|
},
|
|
8191
8592
|
{
|
|
8192
8593
|
// @ts-ignore
|
|
@@ -8195,6 +8596,29 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
8195
8596
|
);
|
|
8196
8597
|
}
|
|
8197
8598
|
|
|
8599
|
+
/**
|
|
8600
|
+
* Waits for LocusInfo to update meeting.selfUrl, with a timeout fallback.
|
|
8601
|
+
* @returns {Promise<void>}
|
|
8602
|
+
* @private
|
|
8603
|
+
* @memberof Meeting
|
|
8604
|
+
*/
|
|
8605
|
+
private waitForSelfUrlChange(): Promise<void> {
|
|
8606
|
+
if (!this.promisesWaitingForPropUpdate.selfUrl) {
|
|
8607
|
+
const pendingSelfUrlUpdate = new Defer();
|
|
8608
|
+
|
|
8609
|
+
this.promisesWaitingForPropUpdate.selfUrl = pendingSelfUrlUpdate;
|
|
8610
|
+
|
|
8611
|
+
setTimeout(() => {
|
|
8612
|
+
if (this.promisesWaitingForPropUpdate.selfUrl === pendingSelfUrlUpdate) {
|
|
8613
|
+
delete this.promisesWaitingForPropUpdate.selfUrl;
|
|
8614
|
+
pendingSelfUrlUpdate.resolve();
|
|
8615
|
+
}
|
|
8616
|
+
}, 5000);
|
|
8617
|
+
}
|
|
8618
|
+
|
|
8619
|
+
return this.promisesWaitingForPropUpdate.selfUrl.promise;
|
|
8620
|
+
}
|
|
8621
|
+
|
|
8198
8622
|
/**
|
|
8199
8623
|
* Creates a media connection to the server. Media connection is required for sending or receiving any audio/video.
|
|
8200
8624
|
*
|
|
@@ -8387,7 +8811,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
8387
8811
|
numTransports,
|
|
8388
8812
|
isMultistream: this.isMultistream,
|
|
8389
8813
|
retriedWithTurnServer: this.addMediaData.retriedWithTurnServer,
|
|
8390
|
-
isJoinWithMediaRetry: this.joinWithMediaRetryInfo.
|
|
8814
|
+
isJoinWithMediaRetry: this.joinWithMediaRetryInfo.retryCount > 0,
|
|
8391
8815
|
...reachabilityMetrics,
|
|
8392
8816
|
...iceCandidateErrors,
|
|
8393
8817
|
iceCandidatesCount: this.iceCandidatesCount,
|
|
@@ -8432,7 +8856,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
8432
8856
|
turnServerUsed: this.turnServerUsed,
|
|
8433
8857
|
retriedWithTurnServer: this.addMediaData.retriedWithTurnServer,
|
|
8434
8858
|
isMultistream: this.isMultistream,
|
|
8435
|
-
isJoinWithMediaRetry: this.joinWithMediaRetryInfo.
|
|
8859
|
+
isJoinWithMediaRetry: this.joinWithMediaRetryInfo.retryCount > 0,
|
|
8436
8860
|
signalingState:
|
|
8437
8861
|
this.mediaProperties.webrtcMediaConnection?.multistreamConnection?.pc?.pc
|
|
8438
8862
|
?.signalingState ||
|
|
@@ -8796,6 +9220,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
8796
9220
|
options: {meetingId: this.id},
|
|
8797
9221
|
});
|
|
8798
9222
|
LoggerProxy.logger.log('Meeting:index#leave --> Leaving a meeting');
|
|
9223
|
+
this.stopListeningForMeetingEvents();
|
|
8799
9224
|
|
|
8800
9225
|
return MeetingUtil.leaveMeeting(this, options)
|
|
8801
9226
|
.then(async (leave) => {
|
|
@@ -9661,6 +10086,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
9661
10086
|
locus_id: this.locusId,
|
|
9662
10087
|
});
|
|
9663
10088
|
|
|
10089
|
+
this.stopListeningForMeetingEvents();
|
|
10090
|
+
|
|
9664
10091
|
return MeetingUtil.endMeetingForAll(this)
|
|
9665
10092
|
.then(async (end) => {
|
|
9666
10093
|
this.meetingFiniteStateMachine.end();
|
|
@@ -9722,12 +10149,15 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
9722
10149
|
}
|
|
9723
10150
|
this.queuedMediaUpdates = [];
|
|
9724
10151
|
|
|
9725
|
-
|
|
9726
|
-
|
|
9727
|
-
|
|
9728
|
-
|
|
9729
|
-
|
|
10152
|
+
// Listener teardown (transcription, annotation, llm/mercury) runs in
|
|
10153
|
+
// stopListeningForMeetingEvents() before /leave and /end so events
|
|
10154
|
+
// received mid-teardown do not trigger Locus syncs. Calling it here
|
|
10155
|
+
// again would double-emit MEETING_STOPPED_RECEIVING_TRANSCRIPTION
|
|
10156
|
+
// because stopTranscription() always fires its trigger.
|
|
10157
|
+
//
|
|
10158
|
+
// Ownership-aware token clear is encapsulated inside clearDataChannelToken().
|
|
9730
10159
|
this.clearDataChannelToken();
|
|
10160
|
+
|
|
9731
10161
|
await this.cleanupLLMConneciton({throwOnError: false});
|
|
9732
10162
|
};
|
|
9733
10163
|
|
|
@@ -9804,15 +10234,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
9804
10234
|
* @public
|
|
9805
10235
|
* @memberof Meeting
|
|
9806
10236
|
*/
|
|
9807
|
-
public sendReaction(reactionType:
|
|
10237
|
+
public sendReaction(reactionType: string, skinToneType?: SkinToneType) {
|
|
9808
10238
|
const reactionChannelUrl = this.locusInfo?.controls?.reactions?.reactionChannelUrl as string;
|
|
9809
10239
|
const participantId = this.members.selfId;
|
|
9810
10240
|
|
|
9811
|
-
const reactionData = Reactions[reactionType];
|
|
10241
|
+
const reactionData = Reactions[reactionType] || {type: reactionType};
|
|
9812
10242
|
|
|
9813
|
-
if (!reactionData) {
|
|
9814
|
-
return Promise.reject(new Error(`${reactionType} is not a valid reaction.`));
|
|
9815
|
-
}
|
|
9816
10243
|
const skinToneData = SkinTones[skinToneType] || SkinTones.normal;
|
|
9817
10244
|
const reaction: Reaction = {
|
|
9818
10245
|
...reactionData,
|