@webex/plugin-meetings 3.12.0-next.7 → 3.12.0-next.70
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 -0
- package/dist/config.js.map +1 -1
- package/dist/constants.js +30 -7
- 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 +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 +298 -87
- package/dist/locus-info/index.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 +3 -1
- package/dist/meeting/in-meeting-actions.js.map +1 -1
- package/dist/meeting/index.js +1046 -689
- package/dist/meeting/index.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 +20 -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 -78
- 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 +3 -0
- package/dist/member/util.js.map +1 -1
- package/dist/metrics/constants.js +4 -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/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 -0
- package/dist/types/constants.d.ts +9 -1
- 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 +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/types.d.ts +21 -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 +78 -5
- package/dist/types/meeting/request.d.ts +1 -0
- package/dist/types/meeting/util.d.ts +8 -0
- package/dist/types/meetings/index.d.ts +30 -2
- 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 +3 -0
- package/dist/types/multistream/codec/constants.d.ts +7 -0
- package/dist/types/multistream/mediaRequestManager.d.ts +22 -5
- 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 -0
- package/src/constants.ts +13 -2
- 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 +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 +293 -97
- 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 +4 -0
- package/src/meeting/index.ts +386 -48
- package/src/meeting/muteState.ts +10 -1
- package/src/meeting/request.ts +11 -0
- package/src/meeting/util.ts +21 -2
- package/src/meeting-info/meeting-info-v2.ts +4 -2
- package/src/meetings/index.ts +134 -44
- 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 +3 -0
- package/src/metrics/constants.ts +3 -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/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 +140 -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 +487 -81
- package/test/unit/spec/media/index.ts +31 -0
- package/test/unit/spec/meeting/in-meeting-actions.ts +2 -0
- package/test/unit/spec/meeting/index.js +1240 -37
- 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 +33 -0
- package/test/unit/spec/meeting-info/meetinginfov2.js +19 -10
- package/test/unit/spec/meetings/index.js +360 -10
- 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 +24 -0
- package/test/unit/spec/multistream/mediaRequestManager.ts +501 -37
- 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';
|
|
@@ -612,7 +613,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
612
613
|
webinar: any;
|
|
613
614
|
conversationUrl: string;
|
|
614
615
|
callStateForMetrics: CallStateForMetrics;
|
|
615
|
-
destination: string;
|
|
616
|
+
destination: string | LocusDTO;
|
|
616
617
|
destinationType: DESTINATION_TYPE;
|
|
617
618
|
deviceUrl: string;
|
|
618
619
|
hostId: string;
|
|
@@ -651,6 +652,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
651
652
|
floorGrantPending: boolean;
|
|
652
653
|
hasJoinedOnce: boolean;
|
|
653
654
|
hasWebsocketConnected: boolean;
|
|
655
|
+
private mercuryOnlineHandler?: () => void;
|
|
656
|
+
private mercuryOfflineHandler?: () => void;
|
|
654
657
|
inMeetingActions: InMeetingActions;
|
|
655
658
|
isLocalShareLive: boolean;
|
|
656
659
|
isRoapInProgress: boolean;
|
|
@@ -935,7 +938,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
935
938
|
this.simultaneousInterpretation = new SimultaneousInterpretation({}, {parent: this.webex});
|
|
936
939
|
|
|
937
940
|
// @ts-ignore
|
|
938
|
-
this.aiEnableRequest = new AIEnableRequest({}, {parent: this.webex});
|
|
941
|
+
this.aiEnableRequest = new AIEnableRequest({locusUrl: this.locusUrl}, {parent: this.webex});
|
|
939
942
|
|
|
940
943
|
/**
|
|
941
944
|
* @instance
|
|
@@ -966,6 +969,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
966
969
|
},
|
|
967
970
|
(csi: CSI) => (this.members.findMemberByCsi(csi) as any)?.id
|
|
968
971
|
);
|
|
972
|
+
|
|
969
973
|
/**
|
|
970
974
|
* Object containing helper classes for managing media requests for audio/video/screenshare (for multistream media connections)
|
|
971
975
|
* All multistream media requests sent out for this meeting have to go through them.
|
|
@@ -985,6 +989,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
985
989
|
mediaRequests
|
|
986
990
|
);
|
|
987
991
|
},
|
|
992
|
+
this.getIngressPayloadTypeCallback.bind(this),
|
|
988
993
|
{
|
|
989
994
|
// @ts-ignore - config coming from registerPlugin
|
|
990
995
|
degradationPreferences: this.config.degradationPreferences,
|
|
@@ -1006,6 +1011,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1006
1011
|
mediaRequests
|
|
1007
1012
|
);
|
|
1008
1013
|
},
|
|
1014
|
+
this.getIngressPayloadTypeCallback.bind(this),
|
|
1009
1015
|
{
|
|
1010
1016
|
// @ts-ignore - config coming from registerPlugin
|
|
1011
1017
|
degradationPreferences: this.config.degradationPreferences,
|
|
@@ -1027,6 +1033,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1027
1033
|
mediaRequests
|
|
1028
1034
|
);
|
|
1029
1035
|
},
|
|
1036
|
+
this.getIngressPayloadTypeCallback.bind(this),
|
|
1030
1037
|
{
|
|
1031
1038
|
// @ts-ignore - config coming from registerPlugin
|
|
1032
1039
|
degradationPreferences: this.config.degradationPreferences,
|
|
@@ -1048,11 +1055,14 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1048
1055
|
mediaRequests
|
|
1049
1056
|
);
|
|
1050
1057
|
},
|
|
1058
|
+
this.getIngressPayloadTypeCallback.bind(this),
|
|
1051
1059
|
{
|
|
1052
1060
|
// @ts-ignore - config coming from registerPlugin
|
|
1053
1061
|
degradationPreferences: this.config.degradationPreferences,
|
|
1054
1062
|
kind: 'video',
|
|
1055
1063
|
trimRequestsToNumOfSources: false,
|
|
1064
|
+
// @ts-ignore - config coming from registerPlugin
|
|
1065
|
+
enableAv1: this.config.enableAv1SlidesSupport,
|
|
1056
1066
|
}
|
|
1057
1067
|
),
|
|
1058
1068
|
};
|
|
@@ -1713,6 +1723,37 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1713
1723
|
this.mediaServerIp = undefined;
|
|
1714
1724
|
}
|
|
1715
1725
|
|
|
1726
|
+
/**
|
|
1727
|
+
* Get the ingress payload type for a given media type and codec mime type
|
|
1728
|
+
* @param {MediaType} mediaType - The media type
|
|
1729
|
+
* @param {MediaCodecMimeType} codecMimeType - The codec mime type
|
|
1730
|
+
* @returns {number | undefined} - The ingress payload type
|
|
1731
|
+
* @private
|
|
1732
|
+
* @memberof Meeting
|
|
1733
|
+
*/
|
|
1734
|
+
private getIngressPayloadTypeCallback(
|
|
1735
|
+
mediaType: MediaType,
|
|
1736
|
+
codecMimeType: MediaCodecMimeType
|
|
1737
|
+
): number | undefined {
|
|
1738
|
+
if (this.isMultistream) {
|
|
1739
|
+
try {
|
|
1740
|
+
return this.mediaProperties.webrtcMediaConnection.getIngressPayloadType(
|
|
1741
|
+
mediaType,
|
|
1742
|
+
codecMimeType
|
|
1743
|
+
);
|
|
1744
|
+
} catch (error) {
|
|
1745
|
+
LoggerProxy.logger.info(
|
|
1746
|
+
`Meeting:index#mediaRequestManager --> failed to get ingress payload type for mediaType=${mediaType}, codecMimeType=${codecMimeType}`,
|
|
1747
|
+
error
|
|
1748
|
+
);
|
|
1749
|
+
|
|
1750
|
+
return undefined;
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
return undefined;
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1716
1757
|
/**
|
|
1717
1758
|
* Temporary func to return webex object,
|
|
1718
1759
|
* in order to access internal plugin metrics
|
|
@@ -2789,7 +2830,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2789
2830
|
private setupLocusControlsListener() {
|
|
2790
2831
|
this.locusInfo.on(
|
|
2791
2832
|
LOCUSINFO.EVENTS.CONTROLS_RECORDING_UPDATED,
|
|
2792
|
-
({state, modifiedBy, lastModified}) => {
|
|
2833
|
+
({state, modifiedBy, lastModified, modifiedByServiceAppName, modifiedByServiceAppId}) => {
|
|
2793
2834
|
let event;
|
|
2794
2835
|
|
|
2795
2836
|
switch (state) {
|
|
@@ -2815,6 +2856,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2815
2856
|
state: state === RECORDING_STATE.RESUMED ? RECORDING_STATE.RECORDING : state,
|
|
2816
2857
|
modifiedBy,
|
|
2817
2858
|
lastModified,
|
|
2859
|
+
modifiedByServiceAppName,
|
|
2860
|
+
modifiedByServiceAppId,
|
|
2818
2861
|
};
|
|
2819
2862
|
Trigger.trigger(
|
|
2820
2863
|
this,
|
|
@@ -3459,13 +3502,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3459
3502
|
this.breakouts.locusUrlUpdate(url);
|
|
3460
3503
|
this.simultaneousInterpretation.locusUrlUpdate(url);
|
|
3461
3504
|
this.annotation.locusUrlUpdate(url);
|
|
3505
|
+
this.aiEnableRequest.locusUrlUpdate(url);
|
|
3462
3506
|
this.locusUrl = url;
|
|
3463
3507
|
this.locusId = this.locusUrl?.split('/').pop();
|
|
3464
3508
|
this.recordingController.setLocusUrl(this.locusUrl);
|
|
3465
3509
|
this.controlsOptionsManager.setLocusUrl(this.locusUrl, !!isMainLocus);
|
|
3466
3510
|
this.webinar.locusUrlUpdate(url);
|
|
3467
|
-
// @ts-ignore
|
|
3468
|
-
this.webex.internal.llm.setRefreshHandler(() => this.refreshDataChannelToken());
|
|
3469
3511
|
|
|
3470
3512
|
Trigger.trigger(
|
|
3471
3513
|
this,
|
|
@@ -3734,7 +3776,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3734
3776
|
});
|
|
3735
3777
|
this.updateLLMConnection();
|
|
3736
3778
|
});
|
|
3737
|
-
this.locusInfo.on(LOCUSINFO.EVENTS.SELF_ADMITTED_GUEST, (payload) => {
|
|
3779
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.SELF_ADMITTED_GUEST, async (payload) => {
|
|
3738
3780
|
this.stopKeepAlive();
|
|
3739
3781
|
|
|
3740
3782
|
if (payload) {
|
|
@@ -3761,13 +3803,15 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3761
3803
|
}
|
|
3762
3804
|
this.rtcMetrics?.sendNextMetrics();
|
|
3763
3805
|
|
|
3764
|
-
|
|
3806
|
+
try {
|
|
3807
|
+
await this.ensureDefaultDatachannelTokenAfterAdmit();
|
|
3808
|
+
} catch (error) {
|
|
3765
3809
|
LoggerProxy.logger.warn(
|
|
3766
3810
|
`Meeting:index#setUpLocusInfoSelfListener --> failed post-admit token prefetch flow: ${
|
|
3767
3811
|
error?.message || String(error)
|
|
3768
3812
|
}`
|
|
3769
3813
|
);
|
|
3770
|
-
}
|
|
3814
|
+
}
|
|
3771
3815
|
|
|
3772
3816
|
this.updateLLMConnection();
|
|
3773
3817
|
});
|
|
@@ -4611,6 +4655,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4611
4655
|
),
|
|
4612
4656
|
isAttendeeRequestAiAssistantDeclinedAll:
|
|
4613
4657
|
MeetingUtil.attendeeRequestAiAssistantDeclinedAll(this.userDisplayHints),
|
|
4658
|
+
isAnonymizeDisplayNamesEnabled: MeetingUtil.isAnonymizeDisplayNamesEnabled(
|
|
4659
|
+
this.userDisplayHints
|
|
4660
|
+
),
|
|
4614
4661
|
}) || changed;
|
|
4615
4662
|
}
|
|
4616
4663
|
if (changed) {
|
|
@@ -4659,6 +4706,34 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4659
4706
|
this.sipUri = sipUri;
|
|
4660
4707
|
}
|
|
4661
4708
|
|
|
4709
|
+
/**
|
|
4710
|
+
* After initial locus setup, refreshes destination with synced locus data and optionally
|
|
4711
|
+
* performs deferred meeting info fetch when initial locus was incomplete.
|
|
4712
|
+
* @param {LocusDTO} locus
|
|
4713
|
+
* @returns {void}
|
|
4714
|
+
*/
|
|
4715
|
+
public async finalizeMeetingAfterInitialLocusSetup(locus: LocusDTO): Promise<void> {
|
|
4716
|
+
if (locus && this?.destinationType === DESTINATION_TYPE.LOCUS_ID) {
|
|
4717
|
+
// destination is initialized from the initial locus snapshot in constructor,
|
|
4718
|
+
// so refresh it after locus sync to avoid stale partial hash-tree data.
|
|
4719
|
+
this.destination = locus;
|
|
4720
|
+
}
|
|
4721
|
+
if (
|
|
4722
|
+
(!this.meetingInfo || isEmpty(this.meetingInfo)) &&
|
|
4723
|
+
(this.destination as LocusDTO)?.info &&
|
|
4724
|
+
!this.fetchMeetingInfoTimeoutId &&
|
|
4725
|
+
!MeetingsUtil.isOneOnOneCall(locus)
|
|
4726
|
+
) {
|
|
4727
|
+
try {
|
|
4728
|
+
await this.fetchMeetingInfo({});
|
|
4729
|
+
} catch (error: any) {
|
|
4730
|
+
LoggerProxy.logger.info(
|
|
4731
|
+
`Meeting:index#finalizeMeetingAfterInitialLocusSetup --> deferred fetchMeetingInfo failed: ${error.message}`
|
|
4732
|
+
);
|
|
4733
|
+
}
|
|
4734
|
+
}
|
|
4735
|
+
}
|
|
4736
|
+
|
|
4662
4737
|
/**
|
|
4663
4738
|
* Set the locus info the class instance. Should be called with the parsed locus
|
|
4664
4739
|
* we got in the join response.
|
|
@@ -5130,8 +5205,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5130
5205
|
public setMercuryListener() {
|
|
5131
5206
|
// Client will have a socket manager and handle reconnecting to mercury, when we reconnect to mercury
|
|
5132
5207
|
// if the meeting has active peer connections, it should try to reconnect.
|
|
5133
|
-
|
|
5134
|
-
this.webex.internal.mercury.on(ONLINE, () => {
|
|
5208
|
+
this.mercuryOnlineHandler = () => {
|
|
5135
5209
|
LoggerProxy.logger.info('Meeting:index#setMercuryListener --> Web socket online');
|
|
5136
5210
|
|
|
5137
5211
|
// Only send restore event when it was disconnected before and for connected later
|
|
@@ -5141,15 +5215,47 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5141
5215
|
});
|
|
5142
5216
|
}
|
|
5143
5217
|
this.hasWebsocketConnected = true;
|
|
5144
|
-
}
|
|
5218
|
+
};
|
|
5145
5219
|
|
|
5146
|
-
|
|
5147
|
-
this.webex.internal.mercury.on(OFFLINE, () => {
|
|
5220
|
+
this.mercuryOfflineHandler = () => {
|
|
5148
5221
|
LoggerProxy.logger.error('Meeting:index#setMercuryListener --> Web socket offline');
|
|
5149
5222
|
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MERCURY_CONNECTION_FAILURE, {
|
|
5150
5223
|
correlation_id: this.correlationId,
|
|
5151
5224
|
});
|
|
5152
|
-
}
|
|
5225
|
+
};
|
|
5226
|
+
|
|
5227
|
+
// @ts-ignore
|
|
5228
|
+
this.webex.internal.mercury.on(ONLINE, this.mercuryOnlineHandler);
|
|
5229
|
+
// @ts-ignore
|
|
5230
|
+
this.webex.internal.mercury.on(OFFLINE, this.mercuryOfflineHandler);
|
|
5231
|
+
}
|
|
5232
|
+
|
|
5233
|
+
/**
|
|
5234
|
+
* Removes this meeting's Mercury ONLINE/OFFLINE event listeners registered
|
|
5235
|
+
* by setMercuryListener(). Must be called before Locus /leave to avoid
|
|
5236
|
+
* unnecessary syncs/metrics triggered by events received while leaving
|
|
5237
|
+
* (per Locus team recommendation).
|
|
5238
|
+
*
|
|
5239
|
+
* Mercury is a process-wide singleton shared with other plugins, so we
|
|
5240
|
+
* pass the bound handler refs to .off() to avoid clearing every listener
|
|
5241
|
+
* for ONLINE/OFFLINE on the shared emitter.
|
|
5242
|
+
*
|
|
5243
|
+
* Idempotent: subsequent calls are no-ops because the handler refs are
|
|
5244
|
+
* cleared after detaching.
|
|
5245
|
+
* @private
|
|
5246
|
+
* @returns {void}
|
|
5247
|
+
*/
|
|
5248
|
+
private stopListeningForMercuryEvents() {
|
|
5249
|
+
if (this.mercuryOnlineHandler) {
|
|
5250
|
+
// @ts-ignore
|
|
5251
|
+
this.webex.internal.mercury.off(ONLINE, this.mercuryOnlineHandler);
|
|
5252
|
+
this.mercuryOnlineHandler = undefined;
|
|
5253
|
+
}
|
|
5254
|
+
if (this.mercuryOfflineHandler) {
|
|
5255
|
+
// @ts-ignore
|
|
5256
|
+
this.webex.internal.mercury.off(OFFLINE, this.mercuryOfflineHandler);
|
|
5257
|
+
this.mercuryOfflineHandler = undefined;
|
|
5258
|
+
}
|
|
5153
5259
|
}
|
|
5154
5260
|
|
|
5155
5261
|
/**
|
|
@@ -5865,6 +5971,31 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5865
5971
|
}
|
|
5866
5972
|
};
|
|
5867
5973
|
|
|
5974
|
+
/**
|
|
5975
|
+
* Verifies the relay event was delivered for the active LLM session binding.
|
|
5976
|
+
* @param {RelayEvent} event Event object coming from LLM Connection
|
|
5977
|
+
* @returns {boolean}
|
|
5978
|
+
*/
|
|
5979
|
+
private isRelayEventRouteValid(event: RelayEvent): boolean {
|
|
5980
|
+
const route = event?.headers?.route;
|
|
5981
|
+
|
|
5982
|
+
if (!route) {
|
|
5983
|
+
return true;
|
|
5984
|
+
}
|
|
5985
|
+
|
|
5986
|
+
const {llm} = (this as any).webex.internal;
|
|
5987
|
+
const isPracticeSession = llm.isConnected(LLM_PRACTICE_SESSION);
|
|
5988
|
+
const expectedBinding = isPracticeSession
|
|
5989
|
+
? llm.getBinding(LLM_PRACTICE_SESSION)
|
|
5990
|
+
: llm.getBinding();
|
|
5991
|
+
|
|
5992
|
+
if (!expectedBinding || route === expectedBinding) {
|
|
5993
|
+
return true;
|
|
5994
|
+
}
|
|
5995
|
+
|
|
5996
|
+
return false;
|
|
5997
|
+
}
|
|
5998
|
+
|
|
5868
5999
|
/**
|
|
5869
6000
|
* Callback called when a relay event is received from meeting LLM Connection
|
|
5870
6001
|
* @param {RelayEvent} e Event object coming from LLM Connection
|
|
@@ -5872,6 +6003,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5872
6003
|
* @returns {void}
|
|
5873
6004
|
*/
|
|
5874
6005
|
private processRelayEvent = (e: RelayEvent): void => {
|
|
6006
|
+
if (!this.isRelayEventRouteValid(e)) {
|
|
6007
|
+
return;
|
|
6008
|
+
}
|
|
5875
6009
|
switch (e.data.relayType) {
|
|
5876
6010
|
case REACTION_RELAY_TYPES.REACTION:
|
|
5877
6011
|
if (
|
|
@@ -6002,6 +6136,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6002
6136
|
*/
|
|
6003
6137
|
private handleLLMOnline = (): void => {
|
|
6004
6138
|
this.restoreLLMSubscriptionsIfNeeded();
|
|
6139
|
+
this.locusInfo.syncAllHashTreeDatasets({onlyLLM: true});
|
|
6005
6140
|
|
|
6006
6141
|
Trigger.trigger(
|
|
6007
6142
|
this,
|
|
@@ -6301,8 +6436,57 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6301
6436
|
}
|
|
6302
6437
|
}
|
|
6303
6438
|
|
|
6439
|
+
/**
|
|
6440
|
+
* Removes LLM event listeners and clears the health check timer.
|
|
6441
|
+
* Must be called before Locus /leave to avoid unnecessary syncs triggered
|
|
6442
|
+
* by events received while leaving (per Locus team recommendation).
|
|
6443
|
+
* Idempotent: safe to call multiple times; .off() is a no-op when no
|
|
6444
|
+
* matching listener is registered.
|
|
6445
|
+
* @private
|
|
6446
|
+
* @returns {void}
|
|
6447
|
+
*/
|
|
6448
|
+
private stopListeningForLLMEvents() {
|
|
6449
|
+
// @ts-ignore - fix types
|
|
6450
|
+
this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
|
|
6451
|
+
// @ts-ignore - fix types
|
|
6452
|
+
this.webex.internal.llm.off(LOCUS_LLM_EVENT, this.processLocusLLMEvent);
|
|
6453
|
+
this.clearLLMHealthCheckTimer();
|
|
6454
|
+
}
|
|
6455
|
+
|
|
6456
|
+
/**
|
|
6457
|
+
* Stops listening on every event bus (LLM, Mercury, voicea/transcription,
|
|
6458
|
+
* annotation) that could otherwise deliver events to this meeting while
|
|
6459
|
+
* Locus is processing /leave or /end. Per the Locus team recommendation,
|
|
6460
|
+
* this must run before the Locus request is dispatched to avoid
|
|
6461
|
+
* unnecessary syncs triggered by in-flight events.
|
|
6462
|
+
*
|
|
6463
|
+
* Voicea (transcription) subscribes to llm 'event:relay.event' internally,
|
|
6464
|
+
* and the annotation plugin subscribes to both mercury and llm, so both
|
|
6465
|
+
* must be torn down alongside the direct LLM/Mercury listeners.
|
|
6466
|
+
*
|
|
6467
|
+
* Idempotent: safe to call multiple times; .off() is a no-op when no
|
|
6468
|
+
* matching listener is registered, and stopTranscription is guarded.
|
|
6469
|
+
* @private
|
|
6470
|
+
* @returns {void}
|
|
6471
|
+
*/
|
|
6472
|
+
private stopListeningForMeetingEvents() {
|
|
6473
|
+
this.stopListeningForLLMEvents();
|
|
6474
|
+
this.stopListeningForMercuryEvents();
|
|
6475
|
+
if (this.transcription) {
|
|
6476
|
+
this.stopTranscription();
|
|
6477
|
+
this.transcription = undefined;
|
|
6478
|
+
}
|
|
6479
|
+
this.annotation.deregisterEvents();
|
|
6480
|
+
}
|
|
6481
|
+
|
|
6304
6482
|
/**
|
|
6305
6483
|
* Disconnects and cleans up the default LLM session listeners/timers.
|
|
6484
|
+
*
|
|
6485
|
+
* Ownership-aware: only calls `disconnectLLM` when this meeting is the
|
|
6486
|
+
* current owner of the default LLM session (or when no owner is recorded).
|
|
6487
|
+
* Event listeners belonging to this meeting instance are always detached
|
|
6488
|
+
* so they do not receive another meeting's relay events.
|
|
6489
|
+
*
|
|
6306
6490
|
* @param {Object} options
|
|
6307
6491
|
* @param {boolean} [options.removeOnlineListener=true] removes the one-time online listener
|
|
6308
6492
|
* @param {boolean} [options.throwOnError=true] rethrows disconnect errors when true
|
|
@@ -6315,12 +6499,29 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6315
6499
|
removeOnlineListener?: boolean;
|
|
6316
6500
|
throwOnError?: boolean;
|
|
6317
6501
|
} = {}): Promise<void> => {
|
|
6502
|
+
// @ts-ignore - Fix type
|
|
6503
|
+
// @ts-ignore - Fix type
|
|
6504
|
+
const {currentOwner, isOwner} = this.webex.internal.llm.resolveSessionOwnership(
|
|
6505
|
+
this.id,
|
|
6506
|
+
LLM_DEFAULT_SESSION
|
|
6507
|
+
);
|
|
6508
|
+
|
|
6318
6509
|
try {
|
|
6319
|
-
|
|
6320
|
-
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6510
|
+
if (isOwner) {
|
|
6511
|
+
// @ts-ignore - Fix type
|
|
6512
|
+
await this.webex.internal.llm.disconnectLLM(
|
|
6513
|
+
{
|
|
6514
|
+
code: 3050,
|
|
6515
|
+
reason: 'done (permanent)',
|
|
6516
|
+
},
|
|
6517
|
+
LLM_DEFAULT_SESSION,
|
|
6518
|
+
this.id
|
|
6519
|
+
);
|
|
6520
|
+
} else {
|
|
6521
|
+
LoggerProxy.logger.info(
|
|
6522
|
+
`Meeting:index#cleanupLLMConneciton --> skipping disconnect; LLM owned by meeting ${currentOwner}, not ${this.id}`
|
|
6523
|
+
);
|
|
6524
|
+
}
|
|
6324
6525
|
} catch (error) {
|
|
6325
6526
|
LoggerProxy.logger.error(
|
|
6326
6527
|
'Meeting:index#cleanupLLMConneciton --> Failed to disconnect default LLM session',
|
|
@@ -6335,23 +6536,33 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6335
6536
|
// @ts-ignore - Fix type
|
|
6336
6537
|
this.webex.internal.llm.off('online', this.handleLLMOnline);
|
|
6337
6538
|
}
|
|
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);
|
|
6539
|
+
this.stopListeningForLLMEvents();
|
|
6342
6540
|
|
|
6343
|
-
|
|
6541
|
+
// Re-check ownership after awaiting disconnectLLM. If ownership changed
|
|
6542
|
+
// while cleanup was in flight, do not clear another meeting's owner tag.
|
|
6543
|
+
if (isOwner) {
|
|
6544
|
+
const {currentOwner: currentOwnerAfterCleanup} =
|
|
6545
|
+
// @ts-ignore - Fix type
|
|
6546
|
+
this.webex.internal.llm.resolveSessionOwnership(this.id, LLM_DEFAULT_SESSION);
|
|
6547
|
+
|
|
6548
|
+
if (currentOwnerAfterCleanup === this.id) {
|
|
6549
|
+
// @ts-ignore - Fix type
|
|
6550
|
+
this.webex.internal.llm.setOwnerMeetingId?.(undefined);
|
|
6551
|
+
}
|
|
6552
|
+
}
|
|
6344
6553
|
}
|
|
6345
6554
|
};
|
|
6346
6555
|
|
|
6347
6556
|
/**
|
|
6348
|
-
* Clears
|
|
6349
|
-
*
|
|
6557
|
+
* Clears data channel tokens associated with this meeting ownership.
|
|
6558
|
+
* Ownership checks are enforced in internal-plugin-llm.
|
|
6350
6559
|
* @returns {void}
|
|
6351
6560
|
*/
|
|
6352
6561
|
clearDataChannelToken(): void {
|
|
6353
6562
|
// @ts-ignore
|
|
6354
|
-
this.webex.internal.llm.
|
|
6563
|
+
this.webex.internal.llm.clearDatachannelToken(LLM_DEFAULT_SESSION, this.id);
|
|
6564
|
+
// @ts-ignore
|
|
6565
|
+
this.webex.internal.llm.clearDatachannelToken(LLM_PRACTICE_SESSION, this.id);
|
|
6355
6566
|
}
|
|
6356
6567
|
|
|
6357
6568
|
/**
|
|
@@ -6366,14 +6577,15 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6366
6577
|
|
|
6367
6578
|
if (datachannelToken) {
|
|
6368
6579
|
// @ts-ignore
|
|
6369
|
-
this.webex.internal.llm.setDatachannelToken(datachannelToken,
|
|
6580
|
+
this.webex.internal.llm.setDatachannelToken(datachannelToken, LLM_DEFAULT_SESSION, this.id);
|
|
6370
6581
|
}
|
|
6371
6582
|
|
|
6372
6583
|
if (practiceSessionDatachannelToken) {
|
|
6373
6584
|
// @ts-ignore
|
|
6374
6585
|
this.webex.internal.llm.setDatachannelToken(
|
|
6375
6586
|
practiceSessionDatachannelToken,
|
|
6376
|
-
|
|
6587
|
+
LLM_PRACTICE_SESSION,
|
|
6588
|
+
this.id
|
|
6377
6589
|
);
|
|
6378
6590
|
}
|
|
6379
6591
|
}
|
|
@@ -6386,7 +6598,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6386
6598
|
private async ensureDefaultDatachannelTokenAfterAdmit(): Promise<boolean> {
|
|
6387
6599
|
try {
|
|
6388
6600
|
// @ts-ignore
|
|
6389
|
-
const datachannelToken = this.webex.internal.llm.getDatachannelToken(
|
|
6601
|
+
const datachannelToken = this.webex.internal.llm.getDatachannelToken(
|
|
6602
|
+
LLM_DEFAULT_SESSION,
|
|
6603
|
+
this.id
|
|
6604
|
+
);
|
|
6390
6605
|
// @ts-ignore
|
|
6391
6606
|
const isDataChannelTokenEnabled = await this.webex.internal.llm.isDataChannelTokenEnabled();
|
|
6392
6607
|
|
|
@@ -6408,7 +6623,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6408
6623
|
// @ts-ignore
|
|
6409
6624
|
this.webex.internal.llm.setDatachannelToken(
|
|
6410
6625
|
fetchedDatachannelToken,
|
|
6411
|
-
|
|
6626
|
+
LLM_DEFAULT_SESSION,
|
|
6627
|
+
this.id
|
|
6412
6628
|
);
|
|
6413
6629
|
|
|
6414
6630
|
return true;
|
|
@@ -6435,15 +6651,58 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6435
6651
|
|
|
6436
6652
|
const isJoined = this.isJoined();
|
|
6437
6653
|
|
|
6438
|
-
|
|
6439
|
-
|
|
6440
|
-
|
|
6654
|
+
const dataChannelUrl = datachannelUrl;
|
|
6655
|
+
|
|
6656
|
+
// Ownership guard: when the default LLM session is already connected and
|
|
6657
|
+
// owned by a *different* Meeting instance, do not disconnect or reconfigure
|
|
6658
|
+
// it. Another meeting's `updateLLMConnection` must be ignored here to
|
|
6659
|
+
// avoid killing the socket it relies on. We only proceed to manage the
|
|
6660
|
+
// connection when this meeting is the current owner, or when no owner is
|
|
6661
|
+
// set yet (first claim).
|
|
6662
|
+
// @ts-ignore - Fix type
|
|
6663
|
+
const {currentOwner} = this.webex.internal.llm.resolveSessionOwnership(
|
|
6664
|
+
this.id,
|
|
6665
|
+
LLM_DEFAULT_SESSION
|
|
6441
6666
|
);
|
|
6442
6667
|
|
|
6443
|
-
|
|
6668
|
+
// Capture connectivity before any reconnect attempt. If LLM was already
|
|
6669
|
+
// connected, we must respect current ownership. If it was disconnected,
|
|
6670
|
+
// this flow may reclaim stale owner tags after a fresh connect.
|
|
6671
|
+
// @ts-ignore - Fix type
|
|
6672
|
+
const wasConnected = this.webex.internal.llm.isConnected();
|
|
6444
6673
|
|
|
6674
|
+
// Prefer ownership-scoped token read. For disconnected stale-owner reclaim
|
|
6675
|
+
// flows, fallback to ownerless read so initial register can still carry a
|
|
6676
|
+
// token and recover from stale ownership without 401/403 dead-end.
|
|
6445
6677
|
// @ts-ignore - Fix type
|
|
6446
|
-
|
|
6678
|
+
let datachannelToken = this.webex.internal.llm.getDatachannelToken(
|
|
6679
|
+
LLM_DEFAULT_SESSION,
|
|
6680
|
+
this.id
|
|
6681
|
+
);
|
|
6682
|
+
|
|
6683
|
+
if (!datachannelToken && !wasConnected && currentOwner && currentOwner !== this.id) {
|
|
6684
|
+
// @ts-ignore - Fix type
|
|
6685
|
+
datachannelToken = this.webex.internal.llm.getDatachannelToken(LLM_DEFAULT_SESSION);
|
|
6686
|
+
}
|
|
6687
|
+
|
|
6688
|
+
// @ts-ignore - Fix type
|
|
6689
|
+
if (wasConnected) {
|
|
6690
|
+
if (currentOwner && currentOwner !== this.id) {
|
|
6691
|
+
// Another meeting owns the live LLM socket. We must not disconnect
|
|
6692
|
+
// or reconfigure it -- doing so would tear down a session the
|
|
6693
|
+
// owning meeting still relies on. Locus/datachannel URL mismatch is
|
|
6694
|
+
// expected here (each meeting has its own locus URL) and is NOT a
|
|
6695
|
+
// valid signal of staleness, so we never reclaim from this path.
|
|
6696
|
+
// The only safe reclaim mechanism is the `finally`-block owner-tag
|
|
6697
|
+
// release in `cleanupLLMConneciton`, which fires when this meeting
|
|
6698
|
+
// itself is being torn down.
|
|
6699
|
+
LoggerProxy.logger.info(
|
|
6700
|
+
`Meeting:index#updateLLMConnection --> skipping; LLM owned by meeting ${currentOwner}, not ${this.id}`
|
|
6701
|
+
);
|
|
6702
|
+
|
|
6703
|
+
return undefined;
|
|
6704
|
+
}
|
|
6705
|
+
|
|
6447
6706
|
if (
|
|
6448
6707
|
// @ts-ignore - Fix type
|
|
6449
6708
|
url === this.webex.internal.llm.getLocusUrl() &&
|
|
@@ -6460,10 +6719,56 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6460
6719
|
return undefined;
|
|
6461
6720
|
}
|
|
6462
6721
|
|
|
6722
|
+
// Bind refresh handler before registration so interceptor-triggered token
|
|
6723
|
+
// refresh during register POST can resolve a valid handler.
|
|
6724
|
+
// Prefer this meeting as owner, but allow owner-less fallback when a stale
|
|
6725
|
+
// foreign owner tag is present on a disconnected session.
|
|
6726
|
+
const refreshHandlerOwnerMeetingId =
|
|
6727
|
+
currentOwner && currentOwner !== this.id ? undefined : this.id;
|
|
6728
|
+
const shouldAlignRefreshHandlerAfterOwnershipClaim = refreshHandlerOwnerMeetingId !== this.id;
|
|
6729
|
+
// @ts-ignore - Fix type
|
|
6730
|
+
this.webex.internal.llm.setRefreshHandler(
|
|
6731
|
+
() => this.refreshDataChannelToken(),
|
|
6732
|
+
LLM_DEFAULT_SESSION,
|
|
6733
|
+
refreshHandlerOwnerMeetingId
|
|
6734
|
+
);
|
|
6735
|
+
|
|
6463
6736
|
// @ts-ignore - Fix type
|
|
6464
6737
|
return this.webex.internal.llm
|
|
6465
6738
|
.registerAndConnect(url, dataChannelUrl, datachannelToken)
|
|
6466
6739
|
.then((registerAndConnectResult) => {
|
|
6740
|
+
this.locusInfo.syncAllHashTreeDatasets({onlyLLM: true});
|
|
6741
|
+
// Record ownership of the default LLM session for this meeting so
|
|
6742
|
+
// subsequent cross-meeting `updateLLMConnection` / `cleanupLLMConneciton`
|
|
6743
|
+
// calls can detect and skip work that doesn't belong to them.
|
|
6744
|
+
// @ts-ignore - Fix type
|
|
6745
|
+
const {isOwner} = this.webex.internal.llm.resolveSessionOwnership(
|
|
6746
|
+
this.id,
|
|
6747
|
+
LLM_DEFAULT_SESSION
|
|
6748
|
+
);
|
|
6749
|
+
const canReclaimAfterDisconnectedStart = !wasConnected;
|
|
6750
|
+
|
|
6751
|
+
// Refresh handler is pre-bound before registerAndConnect so token
|
|
6752
|
+
// refresh can work even during the registration request itself.
|
|
6753
|
+
if (isOwner || canReclaimAfterDisconnectedStart) {
|
|
6754
|
+
// Record ownership of the default LLM session for this meeting so
|
|
6755
|
+
// subsequent cross-meeting `updateLLMConnection` / `cleanupLLMConneciton`
|
|
6756
|
+
// calls can detect and skip work that doesn't belong to them.
|
|
6757
|
+
// @ts-ignore - Fix type
|
|
6758
|
+
this.webex.internal.llm.setOwnerMeetingId?.(this.id);
|
|
6759
|
+
|
|
6760
|
+
// If we pre-bound refresh ownerlessly (stale-owner reclaim path),
|
|
6761
|
+
// align the handler with the newly claimed owner immediately after
|
|
6762
|
+
// ownership is updated.
|
|
6763
|
+
if (shouldAlignRefreshHandlerAfterOwnershipClaim) {
|
|
6764
|
+
// @ts-ignore - Fix type
|
|
6765
|
+
this.webex.internal.llm.setRefreshHandler(
|
|
6766
|
+
() => this.refreshDataChannelToken(),
|
|
6767
|
+
LLM_DEFAULT_SESSION,
|
|
6768
|
+
this.id
|
|
6769
|
+
);
|
|
6770
|
+
}
|
|
6771
|
+
}
|
|
6467
6772
|
// @ts-ignore - Fix type
|
|
6468
6773
|
this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
|
|
6469
6774
|
// @ts-ignore - Fix type
|
|
@@ -7504,6 +7809,33 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7504
7809
|
}
|
|
7505
7810
|
}
|
|
7506
7811
|
});
|
|
7812
|
+
this.statsAnalyzer.on(StatsAnalyzerEventNames.STATS_UPDATE, (data) => {
|
|
7813
|
+
// Extract srtpCipher from transport stats
|
|
7814
|
+
let srtpCipher: string | undefined;
|
|
7815
|
+
for (const stats of data.stats.values()) {
|
|
7816
|
+
if (stats.type === 'transport' && stats.srtpCipher) {
|
|
7817
|
+
srtpCipher = stats.srtpCipher as string;
|
|
7818
|
+
break;
|
|
7819
|
+
}
|
|
7820
|
+
}
|
|
7821
|
+
|
|
7822
|
+
// Only emit event if srtpCipher has changed
|
|
7823
|
+
if (srtpCipher && srtpCipher !== this.mediaProperties.srtpCipher) {
|
|
7824
|
+
LoggerProxy.logger.info(
|
|
7825
|
+
`Meeting:index#setupStatsAnalyzerEventHandlers --> SRTP cipher changed from ${this.mediaProperties.srtpCipher} to ${srtpCipher}`
|
|
7826
|
+
);
|
|
7827
|
+
this.mediaProperties.srtpCipher = srtpCipher;
|
|
7828
|
+
Trigger.trigger(
|
|
7829
|
+
this,
|
|
7830
|
+
{
|
|
7831
|
+
file: 'meeting/index',
|
|
7832
|
+
function: 'setupStatsAnalyzerEventHandlers',
|
|
7833
|
+
},
|
|
7834
|
+
EVENT_TRIGGERS.MEETING_SRTP_CIPHER_UPDATED,
|
|
7835
|
+
{srtpCipher}
|
|
7836
|
+
);
|
|
7837
|
+
}
|
|
7838
|
+
});
|
|
7507
7839
|
};
|
|
7508
7840
|
|
|
7509
7841
|
getMediaConnectionDebugId() {
|
|
@@ -7550,6 +7882,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7550
7882
|
disableAudioMainDtx: this.config.experimental.disableAudioMainDtx,
|
|
7551
7883
|
// @ts-ignore - config coming from registerPlugin
|
|
7552
7884
|
enableAudioTwcc: this.config.enableAudioTwccForMultistream,
|
|
7885
|
+
// @ts-ignore - config coming from registerPlugin
|
|
7886
|
+
enableAv1SlidesSupport: this.config.enableAv1SlidesSupport,
|
|
7553
7887
|
stopIceGatheringAfterFirstRelayCandidate:
|
|
7554
7888
|
// @ts-ignore - config coming from registerPlugin
|
|
7555
7889
|
this.config.stopIceGatheringAfterFirstRelayCandidate,
|
|
@@ -8797,6 +9131,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
8797
9131
|
});
|
|
8798
9132
|
LoggerProxy.logger.log('Meeting:index#leave --> Leaving a meeting');
|
|
8799
9133
|
|
|
9134
|
+
this.stopListeningForMeetingEvents();
|
|
9135
|
+
|
|
8800
9136
|
return MeetingUtil.leaveMeeting(this, options)
|
|
8801
9137
|
.then(async (leave) => {
|
|
8802
9138
|
// CA team recommends submitting this *after* locus /leave
|
|
@@ -9661,6 +9997,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
9661
9997
|
locus_id: this.locusId,
|
|
9662
9998
|
});
|
|
9663
9999
|
|
|
10000
|
+
this.stopListeningForMeetingEvents();
|
|
10001
|
+
|
|
9664
10002
|
return MeetingUtil.endMeetingForAll(this)
|
|
9665
10003
|
.then(async (end) => {
|
|
9666
10004
|
this.meetingFiniteStateMachine.end();
|
|
@@ -9722,12 +10060,15 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
9722
10060
|
}
|
|
9723
10061
|
this.queuedMediaUpdates = [];
|
|
9724
10062
|
|
|
9725
|
-
|
|
9726
|
-
|
|
9727
|
-
|
|
9728
|
-
|
|
9729
|
-
|
|
10063
|
+
// Listener teardown (transcription, annotation, llm/mercury) runs in
|
|
10064
|
+
// stopListeningForMeetingEvents() before /leave and /end so events
|
|
10065
|
+
// received mid-teardown do not trigger Locus syncs. Calling it here
|
|
10066
|
+
// again would double-emit MEETING_STOPPED_RECEIVING_TRANSCRIPTION
|
|
10067
|
+
// because stopTranscription() always fires its trigger.
|
|
10068
|
+
//
|
|
10069
|
+
// Ownership-aware token clear is encapsulated inside clearDataChannelToken().
|
|
9730
10070
|
this.clearDataChannelToken();
|
|
10071
|
+
|
|
9731
10072
|
await this.cleanupLLMConneciton({throwOnError: false});
|
|
9732
10073
|
};
|
|
9733
10074
|
|
|
@@ -9804,15 +10145,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
9804
10145
|
* @public
|
|
9805
10146
|
* @memberof Meeting
|
|
9806
10147
|
*/
|
|
9807
|
-
public sendReaction(reactionType:
|
|
10148
|
+
public sendReaction(reactionType: string, skinToneType?: SkinToneType) {
|
|
9808
10149
|
const reactionChannelUrl = this.locusInfo?.controls?.reactions?.reactionChannelUrl as string;
|
|
9809
10150
|
const participantId = this.members.selfId;
|
|
9810
10151
|
|
|
9811
|
-
const reactionData = Reactions[reactionType];
|
|
10152
|
+
const reactionData = Reactions[reactionType] || {type: reactionType};
|
|
9812
10153
|
|
|
9813
|
-
if (!reactionData) {
|
|
9814
|
-
return Promise.reject(new Error(`${reactionType} is not a valid reaction.`));
|
|
9815
|
-
}
|
|
9816
10154
|
const skinToneData = SkinTones[skinToneType] || SkinTones.normal;
|
|
9817
10155
|
const reaction: Reaction = {
|
|
9818
10156
|
...reactionData,
|