@webex/plugin-meetings 3.12.0-mobius-socket.1 → 3.12.0-mobius-socket.3
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 +3 -2
- package/dist/breakouts/index.js.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/constants.js +6 -3
- package/dist/constants.js.map +1 -1
- package/dist/controls-options-manager/constants.js +11 -1
- package/dist/controls-options-manager/constants.js.map +1 -1
- package/dist/controls-options-manager/index.js +38 -24
- package/dist/controls-options-manager/index.js.map +1 -1
- package/dist/controls-options-manager/util.js +91 -0
- package/dist/controls-options-manager/util.js.map +1 -1
- package/dist/hashTree/constants.js +10 -1
- package/dist/hashTree/constants.js.map +1 -1
- package/dist/hashTree/hashTreeParser.js +651 -382
- 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/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 +848 -582
- 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 +205 -77
- package/dist/meetings/index.js.map +1 -1
- package/dist/meetings/meetings.types.js +6 -1
- package/dist/meetings/meetings.types.js.map +1 -1
- package/dist/meetings/request.js +39 -0
- package/dist/meetings/request.js.map +1 -1
- package/dist/meetings/util.js +67 -5
- package/dist/meetings/util.js.map +1 -1
- package/dist/member/index.js +10 -0
- package/dist/member/index.js.map +1 -1
- package/dist/member/types.js.map +1 -1
- package/dist/member/util.js +3 -0
- package/dist/member/util.js.map +1 -1
- package/dist/metrics/constants.js +4 -1
- package/dist/metrics/constants.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 +1 -0
- package/dist/types/constants.d.ts +2 -0
- package/dist/types/controls-options-manager/constants.d.ts +6 -1
- package/dist/types/controls-options-manager/index.d.ts +10 -0
- package/dist/types/hashTree/constants.d.ts +1 -0
- package/dist/types/hashTree/hashTreeParser.d.ts +83 -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 +65 -1
- package/dist/types/meeting/util.d.ts +8 -0
- package/dist/types/meetings/index.d.ts +20 -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/reactions/reactions.type.d.ts +3 -0
- package/dist/webinar/index.js +68 -17
- 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 +1 -0
- package/src/config.ts +1 -0
- package/src/constants.ts +5 -1
- package/src/controls-options-manager/constants.ts +14 -1
- package/src/controls-options-manager/index.ts +47 -24
- package/src/controls-options-manager/util.ts +81 -1
- package/src/hashTree/constants.ts +9 -0
- package/src/hashTree/hashTreeParser.ts +375 -197
- 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/properties.ts +1 -0
- package/src/meeting/in-meeting-actions.ts +4 -0
- package/src/meeting/index.ts +260 -23
- package/src/meeting/util.ts +20 -2
- package/src/meetings/index.ts +109 -43
- package/src/meetings/meetings.types.ts +19 -0
- package/src/meetings/request.ts +43 -0
- package/src/meetings/util.ts +80 -1
- package/src/member/index.ts +10 -0
- package/src/member/types.ts +1 -0
- package/src/member/util.ts +3 -0
- package/src/metrics/constants.ts +3 -0
- 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 +88 -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 +2 -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 +1263 -157
- 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/meeting/in-meeting-actions.ts +2 -0
- package/test/unit/spec/meeting/index.js +902 -14
- 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 +309 -10
- package/test/unit/spec/meetings/request.js +141 -0
- package/test/unit/spec/meetings/utils.js +161 -0
- package/test/unit/spec/member/index.js +7 -0
- package/test/unit/spec/member/util.js +24 -0
- package/test/unit/spec/recording-controller/index.js +9 -8
- package/test/unit/spec/webinar/index.ts +81 -16
package/src/media/properties.ts
CHANGED
|
@@ -43,6 +43,7 @@ export default class MediaProperties {
|
|
|
43
43
|
shareAudioStream?: LocalSystemAudioStream;
|
|
44
44
|
videoDeviceId: any;
|
|
45
45
|
videoStream?: LocalCameraStream;
|
|
46
|
+
srtpCipher: string | undefined;
|
|
46
47
|
namespace = MEETINGS;
|
|
47
48
|
mediaIssueCounters: {[key: string]: number} = {};
|
|
48
49
|
throttledSendMediaIssueMetric: ReturnType<typeof throttle>;
|
|
@@ -120,6 +120,7 @@ interface IInMeetingActions {
|
|
|
120
120
|
canDisablePollingQA?: boolean;
|
|
121
121
|
canAttendeeRequestAiAssistantEnabled?: boolean;
|
|
122
122
|
isAttendeeRequestAiAssistantDeclinedAll?: boolean;
|
|
123
|
+
isAnonymizeDisplayNamesEnabled?: boolean;
|
|
123
124
|
}
|
|
124
125
|
|
|
125
126
|
/**
|
|
@@ -346,6 +347,8 @@ export default class InMeetingActions implements IInMeetingActions {
|
|
|
346
347
|
|
|
347
348
|
isAttendeeRequestAiAssistantDeclinedAll = null;
|
|
348
349
|
|
|
350
|
+
isAnonymizeDisplayNamesEnabled = null;
|
|
351
|
+
|
|
349
352
|
/**
|
|
350
353
|
* Returns all meeting action options
|
|
351
354
|
* @returns {Object}
|
|
@@ -460,6 +463,7 @@ export default class InMeetingActions implements IInMeetingActions {
|
|
|
460
463
|
canDisablePollingQA: this.canDisablePollingQA,
|
|
461
464
|
canAttendeeRequestAiAssistantEnabled: this.canAttendeeRequestAiAssistantEnabled,
|
|
462
465
|
isAttendeeRequestAiAssistantDeclinedAll: this.isAttendeeRequestAiAssistantDeclinedAll,
|
|
466
|
+
isAnonymizeDisplayNamesEnabled: this.isAnonymizeDisplayNamesEnabled,
|
|
463
467
|
});
|
|
464
468
|
|
|
465
469
|
/**
|
package/src/meeting/index.ts
CHANGED
|
@@ -137,6 +137,7 @@ import {
|
|
|
137
137
|
STAGE_MANAGER_TYPE,
|
|
138
138
|
LOCUSEVENT,
|
|
139
139
|
LOCUS_LLM_EVENT,
|
|
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
|
|
@@ -2789,7 +2792,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2789
2792
|
private setupLocusControlsListener() {
|
|
2790
2793
|
this.locusInfo.on(
|
|
2791
2794
|
LOCUSINFO.EVENTS.CONTROLS_RECORDING_UPDATED,
|
|
2792
|
-
({state, modifiedBy, lastModified}) => {
|
|
2795
|
+
({state, modifiedBy, lastModified, modifiedByServiceAppName, modifiedByServiceAppId}) => {
|
|
2793
2796
|
let event;
|
|
2794
2797
|
|
|
2795
2798
|
switch (state) {
|
|
@@ -2815,6 +2818,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2815
2818
|
state: state === RECORDING_STATE.RESUMED ? RECORDING_STATE.RECORDING : state,
|
|
2816
2819
|
modifiedBy,
|
|
2817
2820
|
lastModified,
|
|
2821
|
+
modifiedByServiceAppName,
|
|
2822
|
+
modifiedByServiceAppId,
|
|
2818
2823
|
};
|
|
2819
2824
|
Trigger.trigger(
|
|
2820
2825
|
this,
|
|
@@ -3459,6 +3464,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3459
3464
|
this.breakouts.locusUrlUpdate(url);
|
|
3460
3465
|
this.simultaneousInterpretation.locusUrlUpdate(url);
|
|
3461
3466
|
this.annotation.locusUrlUpdate(url);
|
|
3467
|
+
this.aiEnableRequest.locusUrlUpdate(url);
|
|
3462
3468
|
this.locusUrl = url;
|
|
3463
3469
|
this.locusId = this.locusUrl?.split('/').pop();
|
|
3464
3470
|
this.recordingController.setLocusUrl(this.locusUrl);
|
|
@@ -4611,6 +4617,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4611
4617
|
),
|
|
4612
4618
|
isAttendeeRequestAiAssistantDeclinedAll:
|
|
4613
4619
|
MeetingUtil.attendeeRequestAiAssistantDeclinedAll(this.userDisplayHints),
|
|
4620
|
+
isAnonymizeDisplayNamesEnabled: MeetingUtil.isAnonymizeDisplayNamesEnabled(
|
|
4621
|
+
this.userDisplayHints
|
|
4622
|
+
),
|
|
4614
4623
|
}) || changed;
|
|
4615
4624
|
}
|
|
4616
4625
|
if (changed) {
|
|
@@ -4659,6 +4668,33 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4659
4668
|
this.sipUri = sipUri;
|
|
4660
4669
|
}
|
|
4661
4670
|
|
|
4671
|
+
/**
|
|
4672
|
+
* After initial locus setup, refreshes destination with synced locus data and optionally
|
|
4673
|
+
* performs deferred meeting info fetch when initial locus was incomplete.
|
|
4674
|
+
* @param {LocusDTO} locus
|
|
4675
|
+
* @returns {void}
|
|
4676
|
+
*/
|
|
4677
|
+
public async finalizeMeetingAfterInitialLocusSetup(locus: LocusDTO): Promise<void> {
|
|
4678
|
+
if (locus && this?.destinationType === DESTINATION_TYPE.LOCUS_ID) {
|
|
4679
|
+
// destination is initialized from the initial locus snapshot in constructor,
|
|
4680
|
+
// so refresh it after locus sync to avoid stale partial hash-tree data.
|
|
4681
|
+
this.destination = locus;
|
|
4682
|
+
}
|
|
4683
|
+
if (
|
|
4684
|
+
(!this.meetingInfo || isEmpty(this.meetingInfo)) &&
|
|
4685
|
+
(this.destination as LocusDTO)?.info &&
|
|
4686
|
+
!this.fetchMeetingInfoTimeoutId
|
|
4687
|
+
) {
|
|
4688
|
+
try {
|
|
4689
|
+
await this.fetchMeetingInfo({});
|
|
4690
|
+
} catch (error: any) {
|
|
4691
|
+
LoggerProxy.logger.info(
|
|
4692
|
+
`Meeting:index#finalizeMeetingAfterInitialLocusSetup --> deferred fetchMeetingInfo failed: ${error.message}`
|
|
4693
|
+
);
|
|
4694
|
+
}
|
|
4695
|
+
}
|
|
4696
|
+
}
|
|
4697
|
+
|
|
4662
4698
|
/**
|
|
4663
4699
|
* Set the locus info the class instance. Should be called with the parsed locus
|
|
4664
4700
|
* we got in the join response.
|
|
@@ -5130,8 +5166,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5130
5166
|
public setMercuryListener() {
|
|
5131
5167
|
// Client will have a socket manager and handle reconnecting to mercury, when we reconnect to mercury
|
|
5132
5168
|
// if the meeting has active peer connections, it should try to reconnect.
|
|
5133
|
-
|
|
5134
|
-
this.webex.internal.mercury.on(ONLINE, () => {
|
|
5169
|
+
this.mercuryOnlineHandler = () => {
|
|
5135
5170
|
LoggerProxy.logger.info('Meeting:index#setMercuryListener --> Web socket online');
|
|
5136
5171
|
|
|
5137
5172
|
// Only send restore event when it was disconnected before and for connected later
|
|
@@ -5141,15 +5176,47 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5141
5176
|
});
|
|
5142
5177
|
}
|
|
5143
5178
|
this.hasWebsocketConnected = true;
|
|
5144
|
-
}
|
|
5179
|
+
};
|
|
5145
5180
|
|
|
5146
|
-
|
|
5147
|
-
this.webex.internal.mercury.on(OFFLINE, () => {
|
|
5181
|
+
this.mercuryOfflineHandler = () => {
|
|
5148
5182
|
LoggerProxy.logger.error('Meeting:index#setMercuryListener --> Web socket offline');
|
|
5149
5183
|
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MERCURY_CONNECTION_FAILURE, {
|
|
5150
5184
|
correlation_id: this.correlationId,
|
|
5151
5185
|
});
|
|
5152
|
-
}
|
|
5186
|
+
};
|
|
5187
|
+
|
|
5188
|
+
// @ts-ignore
|
|
5189
|
+
this.webex.internal.mercury.on(ONLINE, this.mercuryOnlineHandler);
|
|
5190
|
+
// @ts-ignore
|
|
5191
|
+
this.webex.internal.mercury.on(OFFLINE, this.mercuryOfflineHandler);
|
|
5192
|
+
}
|
|
5193
|
+
|
|
5194
|
+
/**
|
|
5195
|
+
* Removes this meeting's Mercury ONLINE/OFFLINE event listeners registered
|
|
5196
|
+
* by setMercuryListener(). Must be called before Locus /leave to avoid
|
|
5197
|
+
* unnecessary syncs/metrics triggered by events received while leaving
|
|
5198
|
+
* (per Locus team recommendation).
|
|
5199
|
+
*
|
|
5200
|
+
* Mercury is a process-wide singleton shared with other plugins, so we
|
|
5201
|
+
* pass the bound handler refs to .off() to avoid clearing every listener
|
|
5202
|
+
* for ONLINE/OFFLINE on the shared emitter.
|
|
5203
|
+
*
|
|
5204
|
+
* Idempotent: subsequent calls are no-ops because the handler refs are
|
|
5205
|
+
* cleared after detaching.
|
|
5206
|
+
* @private
|
|
5207
|
+
* @returns {void}
|
|
5208
|
+
*/
|
|
5209
|
+
private stopListeningForMercuryEvents() {
|
|
5210
|
+
if (this.mercuryOnlineHandler) {
|
|
5211
|
+
// @ts-ignore
|
|
5212
|
+
this.webex.internal.mercury.off(ONLINE, this.mercuryOnlineHandler);
|
|
5213
|
+
this.mercuryOnlineHandler = undefined;
|
|
5214
|
+
}
|
|
5215
|
+
if (this.mercuryOfflineHandler) {
|
|
5216
|
+
// @ts-ignore
|
|
5217
|
+
this.webex.internal.mercury.off(OFFLINE, this.mercuryOfflineHandler);
|
|
5218
|
+
this.mercuryOfflineHandler = undefined;
|
|
5219
|
+
}
|
|
5153
5220
|
}
|
|
5154
5221
|
|
|
5155
5222
|
/**
|
|
@@ -5865,6 +5932,31 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5865
5932
|
}
|
|
5866
5933
|
};
|
|
5867
5934
|
|
|
5935
|
+
/**
|
|
5936
|
+
* Verifies the relay event was delivered for the active LLM session binding.
|
|
5937
|
+
* @param {RelayEvent} event Event object coming from LLM Connection
|
|
5938
|
+
* @returns {boolean}
|
|
5939
|
+
*/
|
|
5940
|
+
private isRelayEventRouteValid(event: RelayEvent): boolean {
|
|
5941
|
+
const route = event?.headers?.route;
|
|
5942
|
+
|
|
5943
|
+
if (!route) {
|
|
5944
|
+
return true;
|
|
5945
|
+
}
|
|
5946
|
+
|
|
5947
|
+
const {llm} = (this as any).webex.internal;
|
|
5948
|
+
const isPracticeSession = llm.isConnected(LLM_PRACTICE_SESSION);
|
|
5949
|
+
const expectedBinding = isPracticeSession
|
|
5950
|
+
? llm.getBinding(LLM_PRACTICE_SESSION)
|
|
5951
|
+
: llm.getBinding();
|
|
5952
|
+
|
|
5953
|
+
if (!expectedBinding || route === expectedBinding) {
|
|
5954
|
+
return true;
|
|
5955
|
+
}
|
|
5956
|
+
|
|
5957
|
+
return false;
|
|
5958
|
+
}
|
|
5959
|
+
|
|
5868
5960
|
/**
|
|
5869
5961
|
* Callback called when a relay event is received from meeting LLM Connection
|
|
5870
5962
|
* @param {RelayEvent} e Event object coming from LLM Connection
|
|
@@ -5872,6 +5964,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5872
5964
|
* @returns {void}
|
|
5873
5965
|
*/
|
|
5874
5966
|
private processRelayEvent = (e: RelayEvent): void => {
|
|
5967
|
+
if (!this.isRelayEventRouteValid(e)) {
|
|
5968
|
+
return;
|
|
5969
|
+
}
|
|
5875
5970
|
switch (e.data.relayType) {
|
|
5876
5971
|
case REACTION_RELAY_TYPES.REACTION:
|
|
5877
5972
|
if (
|
|
@@ -6301,8 +6396,57 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6301
6396
|
}
|
|
6302
6397
|
}
|
|
6303
6398
|
|
|
6399
|
+
/**
|
|
6400
|
+
* Removes LLM event listeners and clears the health check timer.
|
|
6401
|
+
* Must be called before Locus /leave to avoid unnecessary syncs triggered
|
|
6402
|
+
* by events received while leaving (per Locus team recommendation).
|
|
6403
|
+
* Idempotent: safe to call multiple times; .off() is a no-op when no
|
|
6404
|
+
* matching listener is registered.
|
|
6405
|
+
* @private
|
|
6406
|
+
* @returns {void}
|
|
6407
|
+
*/
|
|
6408
|
+
private stopListeningForLLMEvents() {
|
|
6409
|
+
// @ts-ignore - fix types
|
|
6410
|
+
this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
|
|
6411
|
+
// @ts-ignore - fix types
|
|
6412
|
+
this.webex.internal.llm.off(LOCUS_LLM_EVENT, this.processLocusLLMEvent);
|
|
6413
|
+
this.clearLLMHealthCheckTimer();
|
|
6414
|
+
}
|
|
6415
|
+
|
|
6416
|
+
/**
|
|
6417
|
+
* Stops listening on every event bus (LLM, Mercury, voicea/transcription,
|
|
6418
|
+
* annotation) that could otherwise deliver events to this meeting while
|
|
6419
|
+
* Locus is processing /leave or /end. Per the Locus team recommendation,
|
|
6420
|
+
* this must run before the Locus request is dispatched to avoid
|
|
6421
|
+
* unnecessary syncs triggered by in-flight events.
|
|
6422
|
+
*
|
|
6423
|
+
* Voicea (transcription) subscribes to llm 'event:relay.event' internally,
|
|
6424
|
+
* and the annotation plugin subscribes to both mercury and llm, so both
|
|
6425
|
+
* must be torn down alongside the direct LLM/Mercury listeners.
|
|
6426
|
+
*
|
|
6427
|
+
* Idempotent: safe to call multiple times; .off() is a no-op when no
|
|
6428
|
+
* matching listener is registered, and stopTranscription is guarded.
|
|
6429
|
+
* @private
|
|
6430
|
+
* @returns {void}
|
|
6431
|
+
*/
|
|
6432
|
+
private stopListeningForMeetingEvents() {
|
|
6433
|
+
this.stopListeningForLLMEvents();
|
|
6434
|
+
this.stopListeningForMercuryEvents();
|
|
6435
|
+
if (this.transcription) {
|
|
6436
|
+
this.stopTranscription();
|
|
6437
|
+
this.transcription = undefined;
|
|
6438
|
+
}
|
|
6439
|
+
this.annotation.deregisterEvents();
|
|
6440
|
+
}
|
|
6441
|
+
|
|
6304
6442
|
/**
|
|
6305
6443
|
* Disconnects and cleans up the default LLM session listeners/timers.
|
|
6444
|
+
*
|
|
6445
|
+
* Ownership-aware: only calls `disconnectLLM` when this meeting is the
|
|
6446
|
+
* current owner of the default LLM session (or when no owner is recorded).
|
|
6447
|
+
* Event listeners belonging to this meeting instance are always detached
|
|
6448
|
+
* so they do not receive another meeting's relay events.
|
|
6449
|
+
*
|
|
6306
6450
|
* @param {Object} options
|
|
6307
6451
|
* @param {boolean} [options.removeOnlineListener=true] removes the one-time online listener
|
|
6308
6452
|
* @param {boolean} [options.throwOnError=true] rethrows disconnect errors when true
|
|
@@ -6315,12 +6459,22 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6315
6459
|
removeOnlineListener?: boolean;
|
|
6316
6460
|
throwOnError?: boolean;
|
|
6317
6461
|
} = {}): Promise<void> => {
|
|
6462
|
+
// @ts-ignore - Fix type
|
|
6463
|
+
const currentOwner = this.webex.internal.llm.getOwnerMeetingId();
|
|
6464
|
+
const isOwner = !currentOwner || currentOwner === this.id;
|
|
6465
|
+
|
|
6318
6466
|
try {
|
|
6319
|
-
|
|
6320
|
-
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6467
|
+
if (isOwner) {
|
|
6468
|
+
// @ts-ignore - Fix type
|
|
6469
|
+
await this.webex.internal.llm.disconnectLLM({
|
|
6470
|
+
code: 3050,
|
|
6471
|
+
reason: 'done (permanent)',
|
|
6472
|
+
});
|
|
6473
|
+
} else {
|
|
6474
|
+
LoggerProxy.logger.info(
|
|
6475
|
+
`Meeting:index#cleanupLLMConneciton --> skipping disconnect; LLM owned by meeting ${currentOwner}, not ${this.id}`
|
|
6476
|
+
);
|
|
6477
|
+
}
|
|
6324
6478
|
} catch (error) {
|
|
6325
6479
|
LoggerProxy.logger.error(
|
|
6326
6480
|
'Meeting:index#cleanupLLMConneciton --> Failed to disconnect default LLM session',
|
|
@@ -6335,12 +6489,18 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6335
6489
|
// @ts-ignore - Fix type
|
|
6336
6490
|
this.webex.internal.llm.off('online', this.handleLLMOnline);
|
|
6337
6491
|
}
|
|
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);
|
|
6492
|
+
this.stopListeningForLLMEvents();
|
|
6342
6493
|
|
|
6343
|
-
this
|
|
6494
|
+
// If this meeting owned (or could have owned) the default LLM session,
|
|
6495
|
+
// always release the owner tag here regardless of whether disconnectLLM
|
|
6496
|
+
// resolved. `disconnectLLM` only clears the owner on its success path,
|
|
6497
|
+
// so a failed disconnect would otherwise leave a stale owner pointing
|
|
6498
|
+
// at a torn-down meeting and permanently block other meetings'
|
|
6499
|
+
// `updateLLMConnection` calls via the ownership guard.
|
|
6500
|
+
if (isOwner) {
|
|
6501
|
+
// @ts-ignore - Fix type
|
|
6502
|
+
this.webex.internal.llm.setOwnerMeetingId?.(undefined);
|
|
6503
|
+
}
|
|
6344
6504
|
}
|
|
6345
6505
|
};
|
|
6346
6506
|
|
|
@@ -6442,8 +6602,33 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6442
6602
|
|
|
6443
6603
|
const dataChannelUrl = datachannelUrl;
|
|
6444
6604
|
|
|
6605
|
+
// Ownership guard: when the default LLM session is already connected and
|
|
6606
|
+
// owned by a *different* Meeting instance, do not disconnect or reconfigure
|
|
6607
|
+
// it. Another meeting's `updateLLMConnection` must be ignored here to
|
|
6608
|
+
// avoid killing the socket it relies on. We only proceed to manage the
|
|
6609
|
+
// connection when this meeting is the current owner, or when no owner is
|
|
6610
|
+
// set yet (first claim).
|
|
6611
|
+
// @ts-ignore - Fix type
|
|
6612
|
+
const currentOwner = this.webex.internal.llm.getOwnerMeetingId();
|
|
6613
|
+
|
|
6445
6614
|
// @ts-ignore - Fix type
|
|
6446
6615
|
if (this.webex.internal.llm.isConnected()) {
|
|
6616
|
+
if (currentOwner && currentOwner !== this.id) {
|
|
6617
|
+
// Another meeting owns the live LLM socket. We must not disconnect
|
|
6618
|
+
// or reconfigure it -- doing so would tear down a session the
|
|
6619
|
+
// owning meeting still relies on. Locus/datachannel URL mismatch is
|
|
6620
|
+
// expected here (each meeting has its own locus URL) and is NOT a
|
|
6621
|
+
// valid signal of staleness, so we never reclaim from this path.
|
|
6622
|
+
// The only safe reclaim mechanism is the `finally`-block owner-tag
|
|
6623
|
+
// release in `cleanupLLMConneciton`, which fires when this meeting
|
|
6624
|
+
// itself is being torn down.
|
|
6625
|
+
LoggerProxy.logger.info(
|
|
6626
|
+
`Meeting:index#updateLLMConnection --> skipping; LLM owned by meeting ${currentOwner}, not ${this.id}`
|
|
6627
|
+
);
|
|
6628
|
+
|
|
6629
|
+
return undefined;
|
|
6630
|
+
}
|
|
6631
|
+
|
|
6447
6632
|
if (
|
|
6448
6633
|
// @ts-ignore - Fix type
|
|
6449
6634
|
url === this.webex.internal.llm.getLocusUrl() &&
|
|
@@ -6464,6 +6649,11 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6464
6649
|
return this.webex.internal.llm
|
|
6465
6650
|
.registerAndConnect(url, dataChannelUrl, datachannelToken)
|
|
6466
6651
|
.then((registerAndConnectResult) => {
|
|
6652
|
+
// Record ownership of the default LLM session for this meeting so
|
|
6653
|
+
// subsequent cross-meeting `updateLLMConnection` / `cleanupLLMConneciton`
|
|
6654
|
+
// calls can detect and skip work that doesn't belong to them.
|
|
6655
|
+
// @ts-ignore - Fix type
|
|
6656
|
+
this.webex.internal.llm.setOwnerMeetingId?.(this.id);
|
|
6467
6657
|
// @ts-ignore - Fix type
|
|
6468
6658
|
this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
|
|
6469
6659
|
// @ts-ignore - Fix type
|
|
@@ -7504,6 +7694,33 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7504
7694
|
}
|
|
7505
7695
|
}
|
|
7506
7696
|
});
|
|
7697
|
+
this.statsAnalyzer.on(StatsAnalyzerEventNames.STATS_UPDATE, (data) => {
|
|
7698
|
+
// Extract srtpCipher from transport stats
|
|
7699
|
+
let srtpCipher: string | undefined;
|
|
7700
|
+
for (const stats of data.stats.values()) {
|
|
7701
|
+
if (stats.type === 'transport' && stats.srtpCipher) {
|
|
7702
|
+
srtpCipher = stats.srtpCipher as string;
|
|
7703
|
+
break;
|
|
7704
|
+
}
|
|
7705
|
+
}
|
|
7706
|
+
|
|
7707
|
+
// Only emit event if srtpCipher has changed
|
|
7708
|
+
if (srtpCipher && srtpCipher !== this.mediaProperties.srtpCipher) {
|
|
7709
|
+
LoggerProxy.logger.info(
|
|
7710
|
+
`Meeting:index#setupStatsAnalyzerEventHandlers --> SRTP cipher changed from ${this.mediaProperties.srtpCipher} to ${srtpCipher}`
|
|
7711
|
+
);
|
|
7712
|
+
this.mediaProperties.srtpCipher = srtpCipher;
|
|
7713
|
+
Trigger.trigger(
|
|
7714
|
+
this,
|
|
7715
|
+
{
|
|
7716
|
+
file: 'meeting/index',
|
|
7717
|
+
function: 'setupStatsAnalyzerEventHandlers',
|
|
7718
|
+
},
|
|
7719
|
+
EVENT_TRIGGERS.MEETING_SRTP_CIPHER_UPDATED,
|
|
7720
|
+
{srtpCipher}
|
|
7721
|
+
);
|
|
7722
|
+
}
|
|
7723
|
+
});
|
|
7507
7724
|
};
|
|
7508
7725
|
|
|
7509
7726
|
getMediaConnectionDebugId() {
|
|
@@ -8797,6 +9014,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
8797
9014
|
});
|
|
8798
9015
|
LoggerProxy.logger.log('Meeting:index#leave --> Leaving a meeting');
|
|
8799
9016
|
|
|
9017
|
+
this.stopListeningForMeetingEvents();
|
|
9018
|
+
|
|
8800
9019
|
return MeetingUtil.leaveMeeting(this, options)
|
|
8801
9020
|
.then(async (leave) => {
|
|
8802
9021
|
// CA team recommends submitting this *after* locus /leave
|
|
@@ -9661,6 +9880,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
9661
9880
|
locus_id: this.locusId,
|
|
9662
9881
|
});
|
|
9663
9882
|
|
|
9883
|
+
this.stopListeningForMeetingEvents();
|
|
9884
|
+
|
|
9664
9885
|
return MeetingUtil.endMeetingForAll(this)
|
|
9665
9886
|
.then(async (end) => {
|
|
9666
9887
|
this.meetingFiniteStateMachine.end();
|
|
@@ -9722,12 +9943,28 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
9722
9943
|
}
|
|
9723
9944
|
this.queuedMediaUpdates = [];
|
|
9724
9945
|
|
|
9725
|
-
|
|
9726
|
-
|
|
9946
|
+
// Listener teardown (transcription, annotation, llm/mercury) runs in
|
|
9947
|
+
// stopListeningForMeetingEvents() before /leave and /end so events
|
|
9948
|
+
// received mid-teardown do not trigger Locus syncs. Calling it here
|
|
9949
|
+
// again would double-emit MEETING_STOPPED_RECEIVING_TRANSCRIPTION
|
|
9950
|
+
// because stopTranscription() always fires its trigger.
|
|
9951
|
+
//
|
|
9952
|
+
// Ownership-aware token clear: only clear the shared LLM data channel
|
|
9953
|
+
// tokens when this meeting owns (or no meeting owns) the default LLM
|
|
9954
|
+
// session. Otherwise we would wipe tokens still in use by another
|
|
9955
|
+
// meeting's active LLM connection.
|
|
9956
|
+
// @ts-ignore - Fix type
|
|
9957
|
+
const currentOwner = this.webex.internal.llm.getOwnerMeetingId();
|
|
9958
|
+
const isOwner = !currentOwner || currentOwner === this.id;
|
|
9727
9959
|
|
|
9728
|
-
|
|
9960
|
+
if (isOwner) {
|
|
9961
|
+
this.clearDataChannelToken();
|
|
9962
|
+
} else {
|
|
9963
|
+
LoggerProxy.logger.info(
|
|
9964
|
+
`Meeting:index#clearMeetingData --> skipping clearDataChannelToken; LLM owned by meeting ${currentOwner}, not ${this.id}`
|
|
9965
|
+
);
|
|
9966
|
+
}
|
|
9729
9967
|
|
|
9730
|
-
this.clearDataChannelToken();
|
|
9731
9968
|
await this.cleanupLLMConneciton({throwOnError: false});
|
|
9732
9969
|
};
|
|
9733
9970
|
|
package/src/meeting/util.ts
CHANGED
|
@@ -371,6 +371,7 @@ const MeetingUtil = {
|
|
|
371
371
|
meeting.breakouts.cleanUp();
|
|
372
372
|
meeting.webinar.cleanUp();
|
|
373
373
|
meeting.simultaneousInterpretation.cleanUp();
|
|
374
|
+
meeting.locusInfo.cleanUp();
|
|
374
375
|
meeting.locusMediaRequest = undefined;
|
|
375
376
|
|
|
376
377
|
meeting.webex?.internal?.newMetrics?.callDiagnosticMetrics?.clearEventLimitsForCorrelationId(
|
|
@@ -845,6 +846,19 @@ const MeetingUtil = {
|
|
|
845
846
|
requestBody.sequence = sequence;
|
|
846
847
|
},
|
|
847
848
|
|
|
849
|
+
/**
|
|
850
|
+
* Checks if Locus API response contains a Locus DTO
|
|
851
|
+
*
|
|
852
|
+
* @param {any} response http response from Locus API call
|
|
853
|
+
* @returns {boolean} true if response contains a Locus DTO
|
|
854
|
+
*/
|
|
855
|
+
isLocusDtoInAPIResponse(response: any) {
|
|
856
|
+
return (
|
|
857
|
+
response?.body?.locus || // for APIs called on our participant - locus is one of props in the response body
|
|
858
|
+
response?.body?.url // for APIs that act on locus itself (like mute all), the body is the locus
|
|
859
|
+
);
|
|
860
|
+
},
|
|
861
|
+
|
|
848
862
|
/**
|
|
849
863
|
* Updates the locus info for the meeting with the locus
|
|
850
864
|
* information returned from API requests made to Locus
|
|
@@ -853,12 +867,13 @@ const MeetingUtil = {
|
|
|
853
867
|
* @param {Object} response The response of the http request
|
|
854
868
|
* @returns {Object}
|
|
855
869
|
*/
|
|
856
|
-
updateLocusFromApiResponse: (meeting, response) => {
|
|
870
|
+
updateLocusFromApiResponse: (meeting: any, response: any) => {
|
|
857
871
|
if (!meeting) {
|
|
858
872
|
return response;
|
|
859
873
|
}
|
|
860
874
|
|
|
861
|
-
|
|
875
|
+
// locus API responses can come in different shapes:
|
|
876
|
+
if (MeetingUtil.isLocusDtoInAPIResponse(response)) {
|
|
862
877
|
meeting.locusInfo.handleLocusAPIResponse(meeting, response.body);
|
|
863
878
|
}
|
|
864
879
|
|
|
@@ -930,6 +945,9 @@ const MeetingUtil = {
|
|
|
930
945
|
attendeeRequestAiAssistantDeclinedAll: (displayHints = []) =>
|
|
931
946
|
displayHints.includes(DISPLAY_HINTS.ATTENDEE_REQUEST_AI_ASSISTANT_DECLINED_ALL),
|
|
932
947
|
|
|
948
|
+
isAnonymizeDisplayNamesEnabled: (displayHints) =>
|
|
949
|
+
displayHints.includes(DISPLAY_HINTS.ANONYMOUS_DISPLAY_NAMES_ENABLED),
|
|
950
|
+
|
|
933
951
|
selfSupportsFeature: (feature: SELF_POLICY, userPolicies: Record<SELF_POLICY, boolean>) => {
|
|
934
952
|
if (!userPolicies) {
|
|
935
953
|
return true;
|