@webex/plugin-meetings 3.12.0-next.6 → 3.12.0-next.61
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 +6 -3
- package/dist/constants.js.map +1 -1
- package/dist/controls-options-manager/constants.js +11 -1
- package/dist/controls-options-manager/constants.js.map +1 -1
- package/dist/controls-options-manager/index.js +38 -24
- package/dist/controls-options-manager/index.js.map +1 -1
- package/dist/controls-options-manager/util.js +91 -0
- package/dist/controls-options-manager/util.js.map +1 -1
- package/dist/hashTree/constants.js +10 -1
- package/dist/hashTree/constants.js.map +1 -1
- package/dist/hashTree/hashTreeParser.js +716 -370
- package/dist/hashTree/hashTreeParser.js.map +1 -1
- package/dist/hashTree/utils.js +22 -0
- package/dist/hashTree/utils.js.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/interceptors/locusRetry.js +23 -8
- package/dist/interceptors/locusRetry.js.map +1 -1
- package/dist/interpretation/index.js +10 -1
- package/dist/interpretation/index.js.map +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/controlsUtils.js +4 -1
- package/dist/locus-info/controlsUtils.js.map +1 -1
- package/dist/locus-info/index.js +289 -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 +907 -535
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/util.js +19 -2
- package/dist/meeting/util.js.map +1 -1
- package/dist/meetings/index.js +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 +2 -0
- package/dist/types/controls-options-manager/constants.d.ts +6 -1
- package/dist/types/controls-options-manager/index.d.ts +10 -0
- package/dist/types/hashTree/constants.d.ts +1 -0
- package/dist/types/hashTree/hashTreeParser.d.ts +92 -16
- package/dist/types/hashTree/utils.d.ts +11 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/interceptors/locusRetry.d.ts +4 -4
- package/dist/types/locus-info/index.d.ts +46 -6
- package/dist/types/locus-info/types.d.ts +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 +87 -3
- 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 +361 -235
- package/dist/webinar/index.js.map +1 -1
- package/package.json +22 -22
- package/src/aiEnableRequest/index.ts +16 -0
- package/src/breakouts/breakout.ts +3 -1
- package/src/breakouts/index.ts +31 -0
- package/src/config.ts +2 -0
- package/src/constants.ts +5 -1
- package/src/controls-options-manager/constants.ts +14 -1
- package/src/controls-options-manager/index.ts +47 -24
- package/src/controls-options-manager/util.ts +81 -1
- package/src/hashTree/constants.ts +9 -0
- package/src/hashTree/hashTreeParser.ts +429 -183
- package/src/hashTree/utils.ts +17 -0
- package/src/index.ts +5 -0
- package/src/interceptors/locusRetry.ts +25 -4
- package/src/interpretation/index.ts +25 -8
- package/src/locus-info/controlsUtils.ts +3 -1
- package/src/locus-info/index.ts +291 -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 +388 -33
- package/src/meeting/util.ts +20 -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 +162 -21
- 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 +1508 -149
- package/test/unit/spec/hashTree/utils.ts +88 -1
- package/test/unit/spec/interceptors/locusRetry.ts +205 -4
- package/test/unit/spec/interpretation/index.ts +26 -4
- package/test/unit/spec/locus-info/controlsUtils.js +172 -57
- package/test/unit/spec/locus-info/index.js +475 -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 +1131 -49
- package/test/unit/spec/meeting/muteState.js +3 -0
- package/test/unit/spec/meeting/utils.js +33 -0
- package/test/unit/spec/meetings/index.js +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 +141 -16
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,7 @@ import {
|
|
|
137
136
|
STAGE_MANAGER_TYPE,
|
|
138
137
|
LOCUSEVENT,
|
|
139
138
|
LOCUS_LLM_EVENT,
|
|
139
|
+
LLM_PRACTICE_SESSION,
|
|
140
140
|
} from '../constants';
|
|
141
141
|
import BEHAVIORAL_METRICS from '../metrics/constants';
|
|
142
142
|
import ParameterError from '../common/errors/parameter';
|
|
@@ -612,7 +612,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
612
612
|
webinar: any;
|
|
613
613
|
conversationUrl: string;
|
|
614
614
|
callStateForMetrics: CallStateForMetrics;
|
|
615
|
-
destination: string;
|
|
615
|
+
destination: string | LocusDTO;
|
|
616
616
|
destinationType: DESTINATION_TYPE;
|
|
617
617
|
deviceUrl: string;
|
|
618
618
|
hostId: string;
|
|
@@ -651,6 +651,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
651
651
|
floorGrantPending: boolean;
|
|
652
652
|
hasJoinedOnce: boolean;
|
|
653
653
|
hasWebsocketConnected: boolean;
|
|
654
|
+
private mercuryOnlineHandler?: () => void;
|
|
655
|
+
private mercuryOfflineHandler?: () => void;
|
|
654
656
|
inMeetingActions: InMeetingActions;
|
|
655
657
|
isLocalShareLive: boolean;
|
|
656
658
|
isRoapInProgress: boolean;
|
|
@@ -935,7 +937,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
935
937
|
this.simultaneousInterpretation = new SimultaneousInterpretation({}, {parent: this.webex});
|
|
936
938
|
|
|
937
939
|
// @ts-ignore
|
|
938
|
-
this.aiEnableRequest = new AIEnableRequest({}, {parent: this.webex});
|
|
940
|
+
this.aiEnableRequest = new AIEnableRequest({locusUrl: this.locusUrl}, {parent: this.webex});
|
|
939
941
|
|
|
940
942
|
/**
|
|
941
943
|
* @instance
|
|
@@ -966,6 +968,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
966
968
|
},
|
|
967
969
|
(csi: CSI) => (this.members.findMemberByCsi(csi) as any)?.id
|
|
968
970
|
);
|
|
971
|
+
|
|
969
972
|
/**
|
|
970
973
|
* Object containing helper classes for managing media requests for audio/video/screenshare (for multistream media connections)
|
|
971
974
|
* All multistream media requests sent out for this meeting have to go through them.
|
|
@@ -985,6 +988,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
985
988
|
mediaRequests
|
|
986
989
|
);
|
|
987
990
|
},
|
|
991
|
+
this.getIngressPayloadTypeCallback.bind(this),
|
|
988
992
|
{
|
|
989
993
|
// @ts-ignore - config coming from registerPlugin
|
|
990
994
|
degradationPreferences: this.config.degradationPreferences,
|
|
@@ -1006,6 +1010,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1006
1010
|
mediaRequests
|
|
1007
1011
|
);
|
|
1008
1012
|
},
|
|
1013
|
+
this.getIngressPayloadTypeCallback.bind(this),
|
|
1009
1014
|
{
|
|
1010
1015
|
// @ts-ignore - config coming from registerPlugin
|
|
1011
1016
|
degradationPreferences: this.config.degradationPreferences,
|
|
@@ -1027,6 +1032,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1027
1032
|
mediaRequests
|
|
1028
1033
|
);
|
|
1029
1034
|
},
|
|
1035
|
+
this.getIngressPayloadTypeCallback.bind(this),
|
|
1030
1036
|
{
|
|
1031
1037
|
// @ts-ignore - config coming from registerPlugin
|
|
1032
1038
|
degradationPreferences: this.config.degradationPreferences,
|
|
@@ -1048,11 +1054,14 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1048
1054
|
mediaRequests
|
|
1049
1055
|
);
|
|
1050
1056
|
},
|
|
1057
|
+
this.getIngressPayloadTypeCallback.bind(this),
|
|
1051
1058
|
{
|
|
1052
1059
|
// @ts-ignore - config coming from registerPlugin
|
|
1053
1060
|
degradationPreferences: this.config.degradationPreferences,
|
|
1054
1061
|
kind: 'video',
|
|
1055
1062
|
trimRequestsToNumOfSources: false,
|
|
1063
|
+
// @ts-ignore - config coming from registerPlugin
|
|
1064
|
+
enableAv1: this.config.enableAv1SlidesSupport,
|
|
1056
1065
|
}
|
|
1057
1066
|
),
|
|
1058
1067
|
};
|
|
@@ -1713,6 +1722,37 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1713
1722
|
this.mediaServerIp = undefined;
|
|
1714
1723
|
}
|
|
1715
1724
|
|
|
1725
|
+
/**
|
|
1726
|
+
* Get the ingress payload type for a given media type and codec mime type
|
|
1727
|
+
* @param {MediaType} mediaType - The media type
|
|
1728
|
+
* @param {MediaCodecMimeType} codecMimeType - The codec mime type
|
|
1729
|
+
* @returns {number | undefined} - The ingress payload type
|
|
1730
|
+
* @private
|
|
1731
|
+
* @memberof Meeting
|
|
1732
|
+
*/
|
|
1733
|
+
private getIngressPayloadTypeCallback(
|
|
1734
|
+
mediaType: MediaType,
|
|
1735
|
+
codecMimeType: MediaCodecMimeType
|
|
1736
|
+
): number | undefined {
|
|
1737
|
+
if (this.isMultistream) {
|
|
1738
|
+
try {
|
|
1739
|
+
return this.mediaProperties.webrtcMediaConnection.getIngressPayloadType(
|
|
1740
|
+
mediaType,
|
|
1741
|
+
codecMimeType
|
|
1742
|
+
);
|
|
1743
|
+
} catch (error) {
|
|
1744
|
+
LoggerProxy.logger.info(
|
|
1745
|
+
`Meeting:index#mediaRequestManager --> failed to get ingress payload type for mediaType=${mediaType}, codecMimeType=${codecMimeType}`,
|
|
1746
|
+
error
|
|
1747
|
+
);
|
|
1748
|
+
|
|
1749
|
+
return undefined;
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
return undefined;
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1716
1756
|
/**
|
|
1717
1757
|
* Temporary func to return webex object,
|
|
1718
1758
|
* in order to access internal plugin metrics
|
|
@@ -2789,7 +2829,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2789
2829
|
private setupLocusControlsListener() {
|
|
2790
2830
|
this.locusInfo.on(
|
|
2791
2831
|
LOCUSINFO.EVENTS.CONTROLS_RECORDING_UPDATED,
|
|
2792
|
-
({state, modifiedBy, lastModified}) => {
|
|
2832
|
+
({state, modifiedBy, lastModified, modifiedByServiceAppName, modifiedByServiceAppId}) => {
|
|
2793
2833
|
let event;
|
|
2794
2834
|
|
|
2795
2835
|
switch (state) {
|
|
@@ -2815,6 +2855,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2815
2855
|
state: state === RECORDING_STATE.RESUMED ? RECORDING_STATE.RECORDING : state,
|
|
2816
2856
|
modifiedBy,
|
|
2817
2857
|
lastModified,
|
|
2858
|
+
modifiedByServiceAppName,
|
|
2859
|
+
modifiedByServiceAppId,
|
|
2818
2860
|
};
|
|
2819
2861
|
Trigger.trigger(
|
|
2820
2862
|
this,
|
|
@@ -3459,6 +3501,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3459
3501
|
this.breakouts.locusUrlUpdate(url);
|
|
3460
3502
|
this.simultaneousInterpretation.locusUrlUpdate(url);
|
|
3461
3503
|
this.annotation.locusUrlUpdate(url);
|
|
3504
|
+
this.aiEnableRequest.locusUrlUpdate(url);
|
|
3462
3505
|
this.locusUrl = url;
|
|
3463
3506
|
this.locusId = this.locusUrl?.split('/').pop();
|
|
3464
3507
|
this.recordingController.setLocusUrl(this.locusUrl);
|
|
@@ -3734,7 +3777,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3734
3777
|
});
|
|
3735
3778
|
this.updateLLMConnection();
|
|
3736
3779
|
});
|
|
3737
|
-
this.locusInfo.on(LOCUSINFO.EVENTS.SELF_ADMITTED_GUEST,
|
|
3780
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.SELF_ADMITTED_GUEST, (payload) => {
|
|
3738
3781
|
this.stopKeepAlive();
|
|
3739
3782
|
|
|
3740
3783
|
if (payload) {
|
|
@@ -3760,6 +3803,15 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3760
3803
|
});
|
|
3761
3804
|
}
|
|
3762
3805
|
this.rtcMetrics?.sendNextMetrics();
|
|
3806
|
+
|
|
3807
|
+
this.ensureDefaultDatachannelTokenAfterAdmit().catch((error) => {
|
|
3808
|
+
LoggerProxy.logger.warn(
|
|
3809
|
+
`Meeting:index#setUpLocusInfoSelfListener --> failed post-admit token prefetch flow: ${
|
|
3810
|
+
error?.message || String(error)
|
|
3811
|
+
}`
|
|
3812
|
+
);
|
|
3813
|
+
});
|
|
3814
|
+
|
|
3763
3815
|
this.updateLLMConnection();
|
|
3764
3816
|
});
|
|
3765
3817
|
|
|
@@ -4602,6 +4654,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4602
4654
|
),
|
|
4603
4655
|
isAttendeeRequestAiAssistantDeclinedAll:
|
|
4604
4656
|
MeetingUtil.attendeeRequestAiAssistantDeclinedAll(this.userDisplayHints),
|
|
4657
|
+
isAnonymizeDisplayNamesEnabled: MeetingUtil.isAnonymizeDisplayNamesEnabled(
|
|
4658
|
+
this.userDisplayHints
|
|
4659
|
+
),
|
|
4605
4660
|
}) || changed;
|
|
4606
4661
|
}
|
|
4607
4662
|
if (changed) {
|
|
@@ -4650,6 +4705,34 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4650
4705
|
this.sipUri = sipUri;
|
|
4651
4706
|
}
|
|
4652
4707
|
|
|
4708
|
+
/**
|
|
4709
|
+
* After initial locus setup, refreshes destination with synced locus data and optionally
|
|
4710
|
+
* performs deferred meeting info fetch when initial locus was incomplete.
|
|
4711
|
+
* @param {LocusDTO} locus
|
|
4712
|
+
* @returns {void}
|
|
4713
|
+
*/
|
|
4714
|
+
public async finalizeMeetingAfterInitialLocusSetup(locus: LocusDTO): Promise<void> {
|
|
4715
|
+
if (locus && this?.destinationType === DESTINATION_TYPE.LOCUS_ID) {
|
|
4716
|
+
// destination is initialized from the initial locus snapshot in constructor,
|
|
4717
|
+
// so refresh it after locus sync to avoid stale partial hash-tree data.
|
|
4718
|
+
this.destination = locus;
|
|
4719
|
+
}
|
|
4720
|
+
if (
|
|
4721
|
+
(!this.meetingInfo || isEmpty(this.meetingInfo)) &&
|
|
4722
|
+
(this.destination as LocusDTO)?.info &&
|
|
4723
|
+
!this.fetchMeetingInfoTimeoutId &&
|
|
4724
|
+
!MeetingsUtil.isOneOnOneCall(locus)
|
|
4725
|
+
) {
|
|
4726
|
+
try {
|
|
4727
|
+
await this.fetchMeetingInfo({});
|
|
4728
|
+
} catch (error: any) {
|
|
4729
|
+
LoggerProxy.logger.info(
|
|
4730
|
+
`Meeting:index#finalizeMeetingAfterInitialLocusSetup --> deferred fetchMeetingInfo failed: ${error.message}`
|
|
4731
|
+
);
|
|
4732
|
+
}
|
|
4733
|
+
}
|
|
4734
|
+
}
|
|
4735
|
+
|
|
4653
4736
|
/**
|
|
4654
4737
|
* Set the locus info the class instance. Should be called with the parsed locus
|
|
4655
4738
|
* we got in the join response.
|
|
@@ -5121,8 +5204,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5121
5204
|
public setMercuryListener() {
|
|
5122
5205
|
// Client will have a socket manager and handle reconnecting to mercury, when we reconnect to mercury
|
|
5123
5206
|
// if the meeting has active peer connections, it should try to reconnect.
|
|
5124
|
-
|
|
5125
|
-
this.webex.internal.mercury.on(ONLINE, () => {
|
|
5207
|
+
this.mercuryOnlineHandler = () => {
|
|
5126
5208
|
LoggerProxy.logger.info('Meeting:index#setMercuryListener --> Web socket online');
|
|
5127
5209
|
|
|
5128
5210
|
// Only send restore event when it was disconnected before and for connected later
|
|
@@ -5132,15 +5214,47 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5132
5214
|
});
|
|
5133
5215
|
}
|
|
5134
5216
|
this.hasWebsocketConnected = true;
|
|
5135
|
-
}
|
|
5217
|
+
};
|
|
5136
5218
|
|
|
5137
|
-
|
|
5138
|
-
this.webex.internal.mercury.on(OFFLINE, () => {
|
|
5219
|
+
this.mercuryOfflineHandler = () => {
|
|
5139
5220
|
LoggerProxy.logger.error('Meeting:index#setMercuryListener --> Web socket offline');
|
|
5140
5221
|
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MERCURY_CONNECTION_FAILURE, {
|
|
5141
5222
|
correlation_id: this.correlationId,
|
|
5142
5223
|
});
|
|
5143
|
-
}
|
|
5224
|
+
};
|
|
5225
|
+
|
|
5226
|
+
// @ts-ignore
|
|
5227
|
+
this.webex.internal.mercury.on(ONLINE, this.mercuryOnlineHandler);
|
|
5228
|
+
// @ts-ignore
|
|
5229
|
+
this.webex.internal.mercury.on(OFFLINE, this.mercuryOfflineHandler);
|
|
5230
|
+
}
|
|
5231
|
+
|
|
5232
|
+
/**
|
|
5233
|
+
* Removes this meeting's Mercury ONLINE/OFFLINE event listeners registered
|
|
5234
|
+
* by setMercuryListener(). Must be called before Locus /leave to avoid
|
|
5235
|
+
* unnecessary syncs/metrics triggered by events received while leaving
|
|
5236
|
+
* (per Locus team recommendation).
|
|
5237
|
+
*
|
|
5238
|
+
* Mercury is a process-wide singleton shared with other plugins, so we
|
|
5239
|
+
* pass the bound handler refs to .off() to avoid clearing every listener
|
|
5240
|
+
* for ONLINE/OFFLINE on the shared emitter.
|
|
5241
|
+
*
|
|
5242
|
+
* Idempotent: subsequent calls are no-ops because the handler refs are
|
|
5243
|
+
* cleared after detaching.
|
|
5244
|
+
* @private
|
|
5245
|
+
* @returns {void}
|
|
5246
|
+
*/
|
|
5247
|
+
private stopListeningForMercuryEvents() {
|
|
5248
|
+
if (this.mercuryOnlineHandler) {
|
|
5249
|
+
// @ts-ignore
|
|
5250
|
+
this.webex.internal.mercury.off(ONLINE, this.mercuryOnlineHandler);
|
|
5251
|
+
this.mercuryOnlineHandler = undefined;
|
|
5252
|
+
}
|
|
5253
|
+
if (this.mercuryOfflineHandler) {
|
|
5254
|
+
// @ts-ignore
|
|
5255
|
+
this.webex.internal.mercury.off(OFFLINE, this.mercuryOfflineHandler);
|
|
5256
|
+
this.mercuryOfflineHandler = undefined;
|
|
5257
|
+
}
|
|
5144
5258
|
}
|
|
5145
5259
|
|
|
5146
5260
|
/**
|
|
@@ -5856,6 +5970,31 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5856
5970
|
}
|
|
5857
5971
|
};
|
|
5858
5972
|
|
|
5973
|
+
/**
|
|
5974
|
+
* Verifies the relay event was delivered for the active LLM session binding.
|
|
5975
|
+
* @param {RelayEvent} event Event object coming from LLM Connection
|
|
5976
|
+
* @returns {boolean}
|
|
5977
|
+
*/
|
|
5978
|
+
private isRelayEventRouteValid(event: RelayEvent): boolean {
|
|
5979
|
+
const route = event?.headers?.route;
|
|
5980
|
+
|
|
5981
|
+
if (!route) {
|
|
5982
|
+
return true;
|
|
5983
|
+
}
|
|
5984
|
+
|
|
5985
|
+
const {llm} = (this as any).webex.internal;
|
|
5986
|
+
const isPracticeSession = llm.isConnected(LLM_PRACTICE_SESSION);
|
|
5987
|
+
const expectedBinding = isPracticeSession
|
|
5988
|
+
? llm.getBinding(LLM_PRACTICE_SESSION)
|
|
5989
|
+
: llm.getBinding();
|
|
5990
|
+
|
|
5991
|
+
if (!expectedBinding || route === expectedBinding) {
|
|
5992
|
+
return true;
|
|
5993
|
+
}
|
|
5994
|
+
|
|
5995
|
+
return false;
|
|
5996
|
+
}
|
|
5997
|
+
|
|
5859
5998
|
/**
|
|
5860
5999
|
* Callback called when a relay event is received from meeting LLM Connection
|
|
5861
6000
|
* @param {RelayEvent} e Event object coming from LLM Connection
|
|
@@ -5863,6 +6002,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5863
6002
|
* @returns {void}
|
|
5864
6003
|
*/
|
|
5865
6004
|
private processRelayEvent = (e: RelayEvent): void => {
|
|
6005
|
+
if (!this.isRelayEventRouteValid(e)) {
|
|
6006
|
+
return;
|
|
6007
|
+
}
|
|
5866
6008
|
switch (e.data.relayType) {
|
|
5867
6009
|
case REACTION_RELAY_TYPES.REACTION:
|
|
5868
6010
|
if (
|
|
@@ -5960,6 +6102,30 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5960
6102
|
);
|
|
5961
6103
|
}
|
|
5962
6104
|
|
|
6105
|
+
/**
|
|
6106
|
+
* Restores LLM subchannel subscriptions after reconnect when captions are active.
|
|
6107
|
+
* @returns {void}
|
|
6108
|
+
*/
|
|
6109
|
+
private restoreLLMSubscriptionsIfNeeded(): void {
|
|
6110
|
+
try {
|
|
6111
|
+
// @ts-ignore
|
|
6112
|
+
const isCaptionBoxOn = this.webex.internal.voicea?.getIsCaptionBoxOn?.();
|
|
6113
|
+
|
|
6114
|
+
if (!isCaptionBoxOn) {
|
|
6115
|
+
return;
|
|
6116
|
+
}
|
|
6117
|
+
|
|
6118
|
+
// @ts-ignore
|
|
6119
|
+
this.webex.internal.voicea.updateSubchannelSubscriptions({subscribe: ['transcription']});
|
|
6120
|
+
} catch (error) {
|
|
6121
|
+
const msg = error?.message || String(error);
|
|
6122
|
+
|
|
6123
|
+
LoggerProxy.logger.warn(
|
|
6124
|
+
`Meeting:index#restoreLLMSubscriptionsIfNeeded --> failed to restore subscriptions after LLM online: ${msg}`
|
|
6125
|
+
);
|
|
6126
|
+
}
|
|
6127
|
+
}
|
|
6128
|
+
|
|
5963
6129
|
/**
|
|
5964
6130
|
* This is a callback for the LLM event that is triggered when it comes online
|
|
5965
6131
|
* This method in turn will trigger an event to the developers that the LLM is connected
|
|
@@ -5968,8 +6134,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5968
6134
|
* @returns {null}
|
|
5969
6135
|
*/
|
|
5970
6136
|
private handleLLMOnline = (): void => {
|
|
5971
|
-
|
|
5972
|
-
|
|
6137
|
+
this.restoreLLMSubscriptionsIfNeeded();
|
|
6138
|
+
|
|
5973
6139
|
Trigger.trigger(
|
|
5974
6140
|
this,
|
|
5975
6141
|
{
|
|
@@ -6200,6 +6366,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6200
6366
|
this.saveDataChannelToken(join);
|
|
6201
6367
|
// @ts-ignore - config coming from registerPlugin
|
|
6202
6368
|
if (this.config.enableAutomaticLLM) {
|
|
6369
|
+
// @ts-ignore
|
|
6370
|
+
this.webex.internal.llm.off('online', this.handleLLMOnline);
|
|
6203
6371
|
// @ts-ignore
|
|
6204
6372
|
this.webex.internal.llm.on('online', this.handleLLMOnline);
|
|
6205
6373
|
this.updateLLMConnection()
|
|
@@ -6266,8 +6434,57 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6266
6434
|
}
|
|
6267
6435
|
}
|
|
6268
6436
|
|
|
6437
|
+
/**
|
|
6438
|
+
* Removes LLM event listeners and clears the health check timer.
|
|
6439
|
+
* Must be called before Locus /leave to avoid unnecessary syncs triggered
|
|
6440
|
+
* by events received while leaving (per Locus team recommendation).
|
|
6441
|
+
* Idempotent: safe to call multiple times; .off() is a no-op when no
|
|
6442
|
+
* matching listener is registered.
|
|
6443
|
+
* @private
|
|
6444
|
+
* @returns {void}
|
|
6445
|
+
*/
|
|
6446
|
+
private stopListeningForLLMEvents() {
|
|
6447
|
+
// @ts-ignore - fix types
|
|
6448
|
+
this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
|
|
6449
|
+
// @ts-ignore - fix types
|
|
6450
|
+
this.webex.internal.llm.off(LOCUS_LLM_EVENT, this.processLocusLLMEvent);
|
|
6451
|
+
this.clearLLMHealthCheckTimer();
|
|
6452
|
+
}
|
|
6453
|
+
|
|
6454
|
+
/**
|
|
6455
|
+
* Stops listening on every event bus (LLM, Mercury, voicea/transcription,
|
|
6456
|
+
* annotation) that could otherwise deliver events to this meeting while
|
|
6457
|
+
* Locus is processing /leave or /end. Per the Locus team recommendation,
|
|
6458
|
+
* this must run before the Locus request is dispatched to avoid
|
|
6459
|
+
* unnecessary syncs triggered by in-flight events.
|
|
6460
|
+
*
|
|
6461
|
+
* Voicea (transcription) subscribes to llm 'event:relay.event' internally,
|
|
6462
|
+
* and the annotation plugin subscribes to both mercury and llm, so both
|
|
6463
|
+
* must be torn down alongside the direct LLM/Mercury listeners.
|
|
6464
|
+
*
|
|
6465
|
+
* Idempotent: safe to call multiple times; .off() is a no-op when no
|
|
6466
|
+
* matching listener is registered, and stopTranscription is guarded.
|
|
6467
|
+
* @private
|
|
6468
|
+
* @returns {void}
|
|
6469
|
+
*/
|
|
6470
|
+
private stopListeningForMeetingEvents() {
|
|
6471
|
+
this.stopListeningForLLMEvents();
|
|
6472
|
+
this.stopListeningForMercuryEvents();
|
|
6473
|
+
if (this.transcription) {
|
|
6474
|
+
this.stopTranscription();
|
|
6475
|
+
this.transcription = undefined;
|
|
6476
|
+
}
|
|
6477
|
+
this.annotation.deregisterEvents();
|
|
6478
|
+
}
|
|
6479
|
+
|
|
6269
6480
|
/**
|
|
6270
6481
|
* Disconnects and cleans up the default LLM session listeners/timers.
|
|
6482
|
+
*
|
|
6483
|
+
* Ownership-aware: only calls `disconnectLLM` when this meeting is the
|
|
6484
|
+
* current owner of the default LLM session (or when no owner is recorded).
|
|
6485
|
+
* Event listeners belonging to this meeting instance are always detached
|
|
6486
|
+
* so they do not receive another meeting's relay events.
|
|
6487
|
+
*
|
|
6271
6488
|
* @param {Object} options
|
|
6272
6489
|
* @param {boolean} [options.removeOnlineListener=true] removes the one-time online listener
|
|
6273
6490
|
* @param {boolean} [options.throwOnError=true] rethrows disconnect errors when true
|
|
@@ -6280,12 +6497,22 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6280
6497
|
removeOnlineListener?: boolean;
|
|
6281
6498
|
throwOnError?: boolean;
|
|
6282
6499
|
} = {}): Promise<void> => {
|
|
6500
|
+
// @ts-ignore - Fix type
|
|
6501
|
+
const currentOwner = this.webex.internal.llm.getOwnerMeetingId();
|
|
6502
|
+
const isOwner = !currentOwner || currentOwner === this.id;
|
|
6503
|
+
|
|
6283
6504
|
try {
|
|
6284
|
-
|
|
6285
|
-
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
|
|
6505
|
+
if (isOwner) {
|
|
6506
|
+
// @ts-ignore - Fix type
|
|
6507
|
+
await this.webex.internal.llm.disconnectLLM({
|
|
6508
|
+
code: 3050,
|
|
6509
|
+
reason: 'done (permanent)',
|
|
6510
|
+
});
|
|
6511
|
+
} else {
|
|
6512
|
+
LoggerProxy.logger.info(
|
|
6513
|
+
`Meeting:index#cleanupLLMConneciton --> skipping disconnect; LLM owned by meeting ${currentOwner}, not ${this.id}`
|
|
6514
|
+
);
|
|
6515
|
+
}
|
|
6289
6516
|
} catch (error) {
|
|
6290
6517
|
LoggerProxy.logger.error(
|
|
6291
6518
|
'Meeting:index#cleanupLLMConneciton --> Failed to disconnect default LLM session',
|
|
@@ -6300,12 +6527,18 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6300
6527
|
// @ts-ignore - Fix type
|
|
6301
6528
|
this.webex.internal.llm.off('online', this.handleLLMOnline);
|
|
6302
6529
|
}
|
|
6303
|
-
|
|
6304
|
-
this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
|
|
6305
|
-
// @ts-ignore - Fix type
|
|
6306
|
-
this.webex.internal.llm.off(LOCUS_LLM_EVENT, this.processLocusLLMEvent);
|
|
6530
|
+
this.stopListeningForLLMEvents();
|
|
6307
6531
|
|
|
6308
|
-
this
|
|
6532
|
+
// If this meeting owned (or could have owned) the default LLM session,
|
|
6533
|
+
// always release the owner tag here regardless of whether disconnectLLM
|
|
6534
|
+
// resolved. `disconnectLLM` only clears the owner on its success path,
|
|
6535
|
+
// so a failed disconnect would otherwise leave a stale owner pointing
|
|
6536
|
+
// at a torn-down meeting and permanently block other meetings'
|
|
6537
|
+
// `updateLLMConnection` calls via the ownership guard.
|
|
6538
|
+
if (isOwner) {
|
|
6539
|
+
// @ts-ignore - Fix type
|
|
6540
|
+
this.webex.internal.llm.setOwnerMeetingId?.(undefined);
|
|
6541
|
+
}
|
|
6309
6542
|
}
|
|
6310
6543
|
};
|
|
6311
6544
|
|
|
@@ -6343,6 +6576,52 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6343
6576
|
}
|
|
6344
6577
|
}
|
|
6345
6578
|
|
|
6579
|
+
/**
|
|
6580
|
+
* Ensures default-session data channel token exists after lobby admission.
|
|
6581
|
+
* Some lobby users do not receive a token until they are admitted.
|
|
6582
|
+
* @returns {Promise<boolean>} true when a new token is fetched and cached
|
|
6583
|
+
*/
|
|
6584
|
+
private async ensureDefaultDatachannelTokenAfterAdmit(): Promise<boolean> {
|
|
6585
|
+
try {
|
|
6586
|
+
// @ts-ignore
|
|
6587
|
+
const datachannelToken = this.webex.internal.llm.getDatachannelToken();
|
|
6588
|
+
// @ts-ignore
|
|
6589
|
+
const isDataChannelTokenEnabled = await this.webex.internal.llm.isDataChannelTokenEnabled();
|
|
6590
|
+
|
|
6591
|
+
if (!isDataChannelTokenEnabled || datachannelToken) {
|
|
6592
|
+
return false;
|
|
6593
|
+
}
|
|
6594
|
+
|
|
6595
|
+
const response = await this.meetingRequest.fetchDatachannelToken({
|
|
6596
|
+
locusUrl: this.locusUrl,
|
|
6597
|
+
requestingParticipantId: this.members.selfId,
|
|
6598
|
+
isPracticeSession: false,
|
|
6599
|
+
});
|
|
6600
|
+
const fetchedDatachannelToken = response?.body?.datachannelToken;
|
|
6601
|
+
|
|
6602
|
+
if (!fetchedDatachannelToken) {
|
|
6603
|
+
return false;
|
|
6604
|
+
}
|
|
6605
|
+
|
|
6606
|
+
// @ts-ignore
|
|
6607
|
+
this.webex.internal.llm.setDatachannelToken(
|
|
6608
|
+
fetchedDatachannelToken,
|
|
6609
|
+
DataChannelTokenType.Default
|
|
6610
|
+
);
|
|
6611
|
+
|
|
6612
|
+
return true;
|
|
6613
|
+
} catch (error) {
|
|
6614
|
+
const msg = error?.message || String(error);
|
|
6615
|
+
|
|
6616
|
+
LoggerProxy.logger.warn(
|
|
6617
|
+
`Meeting:index#ensureDefaultDatachannelTokenAfterAdmit --> failed to proactively fetch default data channel token after admit: ${msg}`,
|
|
6618
|
+
{statusCode: error?.statusCode}
|
|
6619
|
+
);
|
|
6620
|
+
|
|
6621
|
+
return false;
|
|
6622
|
+
}
|
|
6623
|
+
}
|
|
6624
|
+
|
|
6346
6625
|
/**
|
|
6347
6626
|
* Connects to low latency mercury and reconnects if the address has changed
|
|
6348
6627
|
* It will also disconnect if called when the meeting has ended
|
|
@@ -6361,8 +6640,33 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6361
6640
|
|
|
6362
6641
|
const dataChannelUrl = datachannelUrl;
|
|
6363
6642
|
|
|
6643
|
+
// Ownership guard: when the default LLM session is already connected and
|
|
6644
|
+
// owned by a *different* Meeting instance, do not disconnect or reconfigure
|
|
6645
|
+
// it. Another meeting's `updateLLMConnection` must be ignored here to
|
|
6646
|
+
// avoid killing the socket it relies on. We only proceed to manage the
|
|
6647
|
+
// connection when this meeting is the current owner, or when no owner is
|
|
6648
|
+
// set yet (first claim).
|
|
6649
|
+
// @ts-ignore - Fix type
|
|
6650
|
+
const currentOwner = this.webex.internal.llm.getOwnerMeetingId();
|
|
6651
|
+
|
|
6364
6652
|
// @ts-ignore - Fix type
|
|
6365
6653
|
if (this.webex.internal.llm.isConnected()) {
|
|
6654
|
+
if (currentOwner && currentOwner !== this.id) {
|
|
6655
|
+
// Another meeting owns the live LLM socket. We must not disconnect
|
|
6656
|
+
// or reconfigure it -- doing so would tear down a session the
|
|
6657
|
+
// owning meeting still relies on. Locus/datachannel URL mismatch is
|
|
6658
|
+
// expected here (each meeting has its own locus URL) and is NOT a
|
|
6659
|
+
// valid signal of staleness, so we never reclaim from this path.
|
|
6660
|
+
// The only safe reclaim mechanism is the `finally`-block owner-tag
|
|
6661
|
+
// release in `cleanupLLMConneciton`, which fires when this meeting
|
|
6662
|
+
// itself is being torn down.
|
|
6663
|
+
LoggerProxy.logger.info(
|
|
6664
|
+
`Meeting:index#updateLLMConnection --> skipping; LLM owned by meeting ${currentOwner}, not ${this.id}`
|
|
6665
|
+
);
|
|
6666
|
+
|
|
6667
|
+
return undefined;
|
|
6668
|
+
}
|
|
6669
|
+
|
|
6366
6670
|
if (
|
|
6367
6671
|
// @ts-ignore - Fix type
|
|
6368
6672
|
url === this.webex.internal.llm.getLocusUrl() &&
|
|
@@ -6383,6 +6687,11 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6383
6687
|
return this.webex.internal.llm
|
|
6384
6688
|
.registerAndConnect(url, dataChannelUrl, datachannelToken)
|
|
6385
6689
|
.then((registerAndConnectResult) => {
|
|
6690
|
+
// Record ownership of the default LLM session for this meeting so
|
|
6691
|
+
// subsequent cross-meeting `updateLLMConnection` / `cleanupLLMConneciton`
|
|
6692
|
+
// calls can detect and skip work that doesn't belong to them.
|
|
6693
|
+
// @ts-ignore - Fix type
|
|
6694
|
+
this.webex.internal.llm.setOwnerMeetingId?.(this.id);
|
|
6386
6695
|
// @ts-ignore - Fix type
|
|
6387
6696
|
this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
|
|
6388
6697
|
// @ts-ignore - Fix type
|
|
@@ -7423,6 +7732,33 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7423
7732
|
}
|
|
7424
7733
|
}
|
|
7425
7734
|
});
|
|
7735
|
+
this.statsAnalyzer.on(StatsAnalyzerEventNames.STATS_UPDATE, (data) => {
|
|
7736
|
+
// Extract srtpCipher from transport stats
|
|
7737
|
+
let srtpCipher: string | undefined;
|
|
7738
|
+
for (const stats of data.stats.values()) {
|
|
7739
|
+
if (stats.type === 'transport' && stats.srtpCipher) {
|
|
7740
|
+
srtpCipher = stats.srtpCipher as string;
|
|
7741
|
+
break;
|
|
7742
|
+
}
|
|
7743
|
+
}
|
|
7744
|
+
|
|
7745
|
+
// Only emit event if srtpCipher has changed
|
|
7746
|
+
if (srtpCipher && srtpCipher !== this.mediaProperties.srtpCipher) {
|
|
7747
|
+
LoggerProxy.logger.info(
|
|
7748
|
+
`Meeting:index#setupStatsAnalyzerEventHandlers --> SRTP cipher changed from ${this.mediaProperties.srtpCipher} to ${srtpCipher}`
|
|
7749
|
+
);
|
|
7750
|
+
this.mediaProperties.srtpCipher = srtpCipher;
|
|
7751
|
+
Trigger.trigger(
|
|
7752
|
+
this,
|
|
7753
|
+
{
|
|
7754
|
+
file: 'meeting/index',
|
|
7755
|
+
function: 'setupStatsAnalyzerEventHandlers',
|
|
7756
|
+
},
|
|
7757
|
+
EVENT_TRIGGERS.MEETING_SRTP_CIPHER_UPDATED,
|
|
7758
|
+
{srtpCipher}
|
|
7759
|
+
);
|
|
7760
|
+
}
|
|
7761
|
+
});
|
|
7426
7762
|
};
|
|
7427
7763
|
|
|
7428
7764
|
getMediaConnectionDebugId() {
|
|
@@ -7469,6 +7805,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7469
7805
|
disableAudioMainDtx: this.config.experimental.disableAudioMainDtx,
|
|
7470
7806
|
// @ts-ignore - config coming from registerPlugin
|
|
7471
7807
|
enableAudioTwcc: this.config.enableAudioTwccForMultistream,
|
|
7808
|
+
// @ts-ignore - config coming from registerPlugin
|
|
7809
|
+
enableAv1SlidesSupport: this.config.enableAv1SlidesSupport,
|
|
7472
7810
|
stopIceGatheringAfterFirstRelayCandidate:
|
|
7473
7811
|
// @ts-ignore - config coming from registerPlugin
|
|
7474
7812
|
this.config.stopIceGatheringAfterFirstRelayCandidate,
|
|
@@ -8716,6 +9054,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
8716
9054
|
});
|
|
8717
9055
|
LoggerProxy.logger.log('Meeting:index#leave --> Leaving a meeting');
|
|
8718
9056
|
|
|
9057
|
+
this.stopListeningForMeetingEvents();
|
|
9058
|
+
|
|
8719
9059
|
return MeetingUtil.leaveMeeting(this, options)
|
|
8720
9060
|
.then(async (leave) => {
|
|
8721
9061
|
// CA team recommends submitting this *after* locus /leave
|
|
@@ -9580,6 +9920,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
9580
9920
|
locus_id: this.locusId,
|
|
9581
9921
|
});
|
|
9582
9922
|
|
|
9923
|
+
this.stopListeningForMeetingEvents();
|
|
9924
|
+
|
|
9583
9925
|
return MeetingUtil.endMeetingForAll(this)
|
|
9584
9926
|
.then(async (end) => {
|
|
9585
9927
|
this.meetingFiniteStateMachine.end();
|
|
@@ -9641,12 +9983,28 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
9641
9983
|
}
|
|
9642
9984
|
this.queuedMediaUpdates = [];
|
|
9643
9985
|
|
|
9644
|
-
|
|
9645
|
-
|
|
9986
|
+
// Listener teardown (transcription, annotation, llm/mercury) runs in
|
|
9987
|
+
// stopListeningForMeetingEvents() before /leave and /end so events
|
|
9988
|
+
// received mid-teardown do not trigger Locus syncs. Calling it here
|
|
9989
|
+
// again would double-emit MEETING_STOPPED_RECEIVING_TRANSCRIPTION
|
|
9990
|
+
// because stopTranscription() always fires its trigger.
|
|
9991
|
+
//
|
|
9992
|
+
// Ownership-aware token clear: only clear the shared LLM data channel
|
|
9993
|
+
// tokens when this meeting owns (or no meeting owns) the default LLM
|
|
9994
|
+
// session. Otherwise we would wipe tokens still in use by another
|
|
9995
|
+
// meeting's active LLM connection.
|
|
9996
|
+
// @ts-ignore - Fix type
|
|
9997
|
+
const currentOwner = this.webex.internal.llm.getOwnerMeetingId();
|
|
9998
|
+
const isOwner = !currentOwner || currentOwner === this.id;
|
|
9646
9999
|
|
|
9647
|
-
|
|
10000
|
+
if (isOwner) {
|
|
10001
|
+
this.clearDataChannelToken();
|
|
10002
|
+
} else {
|
|
10003
|
+
LoggerProxy.logger.info(
|
|
10004
|
+
`Meeting:index#clearMeetingData --> skipping clearDataChannelToken; LLM owned by meeting ${currentOwner}, not ${this.id}`
|
|
10005
|
+
);
|
|
10006
|
+
}
|
|
9648
10007
|
|
|
9649
|
-
this.clearDataChannelToken();
|
|
9650
10008
|
await this.cleanupLLMConneciton({throwOnError: false});
|
|
9651
10009
|
};
|
|
9652
10010
|
|
|
@@ -9723,15 +10081,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
9723
10081
|
* @public
|
|
9724
10082
|
* @memberof Meeting
|
|
9725
10083
|
*/
|
|
9726
|
-
public sendReaction(reactionType:
|
|
10084
|
+
public sendReaction(reactionType: string, skinToneType?: SkinToneType) {
|
|
9727
10085
|
const reactionChannelUrl = this.locusInfo?.controls?.reactions?.reactionChannelUrl as string;
|
|
9728
10086
|
const participantId = this.members.selfId;
|
|
9729
10087
|
|
|
9730
|
-
const reactionData = Reactions[reactionType];
|
|
10088
|
+
const reactionData = Reactions[reactionType] || {type: reactionType};
|
|
9731
10089
|
|
|
9732
|
-
if (!reactionData) {
|
|
9733
|
-
return Promise.reject(new Error(`${reactionType} is not a valid reaction.`));
|
|
9734
|
-
}
|
|
9735
10090
|
const skinToneData = SkinTones[skinToneType] || SkinTones.normal;
|
|
9736
10091
|
const reaction: Reaction = {
|
|
9737
10092
|
...reactionData,
|