@webex/plugin-meetings 3.12.0-next.4 → 3.12.0-next.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +9 -0
- package/dist/aiEnableRequest/index.js +15 -2
- package/dist/aiEnableRequest/index.js.map +1 -1
- package/dist/breakouts/breakout.js +6 -2
- package/dist/breakouts/breakout.js.map +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/constants.js +1 -1
- 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 +23 -21
- 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 +554 -350
- 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/interceptors/locusRetry.js +23 -8
- package/dist/interceptors/locusRetry.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/index.js +274 -85
- package/dist/locus-info/index.js.map +1 -1
- package/dist/locus-info/types.js +16 -0
- package/dist/locus-info/types.js.map +1 -1
- package/dist/meeting/index.js +710 -499
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/util.js +1 -0
- package/dist/meeting/util.js.map +1 -1
- package/dist/meetings/index.js +174 -77
- package/dist/meetings/index.js.map +1 -1
- package/dist/meetings/util.js +49 -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/types/controls-options-manager/constants.d.ts +6 -1
- package/dist/types/hashTree/constants.d.ts +1 -0
- package/dist/types/hashTree/hashTreeParser.d.ts +53 -15
- package/dist/types/hashTree/utils.d.ts +11 -0
- package/dist/types/interceptors/locusRetry.d.ts +4 -4
- package/dist/types/locus-info/index.d.ts +46 -6
- package/dist/types/locus-info/types.d.ts +17 -1
- package/dist/types/meeting/index.d.ts +64 -1
- 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/webinar/index.js +301 -226
- package/dist/webinar/index.js.map +1 -1
- package/package.json +22 -22
- package/src/aiEnableRequest/index.ts +16 -0
- package/src/breakouts/breakout.ts +2 -1
- package/src/constants.ts +1 -1
- package/src/controls-options-manager/constants.ts +14 -1
- package/src/controls-options-manager/index.ts +26 -19
- package/src/controls-options-manager/util.ts +81 -1
- package/src/hashTree/constants.ts +9 -0
- package/src/hashTree/hashTreeParser.ts +278 -160
- package/src/hashTree/utils.ts +17 -0
- package/src/interceptors/locusRetry.ts +25 -4
- package/src/locus-info/index.ts +274 -93
- package/src/locus-info/types.ts +19 -1
- package/src/meeting/index.ts +206 -22
- package/src/meeting/util.ts +1 -0
- package/src/meetings/index.ts +77 -43
- package/src/meetings/util.ts +56 -1
- package/src/member/index.ts +10 -0
- package/src/member/types.ts +1 -0
- package/src/member/util.ts +3 -0
- package/src/webinar/index.ts +75 -1
- package/test/unit/spec/aiEnableRequest/index.ts +86 -0
- package/test/unit/spec/breakouts/breakout.ts +7 -3
- package/test/unit/spec/controls-options-manager/index.js +114 -6
- package/test/unit/spec/controls-options-manager/util.js +165 -0
- package/test/unit/spec/hashTree/hashTreeParser.ts +996 -51
- package/test/unit/spec/hashTree/utils.ts +88 -1
- package/test/unit/spec/interceptors/locusRetry.ts +205 -4
- package/test/unit/spec/locus-info/index.js +397 -81
- package/test/unit/spec/meeting/index.js +271 -44
- package/test/unit/spec/meeting/utils.js +4 -0
- package/test/unit/spec/meetings/index.js +195 -13
- package/test/unit/spec/meetings/utils.js +137 -0
- package/test/unit/spec/member/index.js +7 -0
- package/test/unit/spec/member/util.js +24 -0
- package/test/unit/spec/webinar/index.ts +60 -0
package/src/locus-info/types.ts
CHANGED
|
@@ -1,15 +1,33 @@
|
|
|
1
|
+
import {Enum} from '../constants';
|
|
1
2
|
import {HtMeta} from '../hashTree/types';
|
|
2
3
|
|
|
4
|
+
export const EndMeetingReason = {
|
|
5
|
+
maxMeetingDuration: 'MAX_MEETING_DURATION',
|
|
6
|
+
allParticipantsLeft: 'ALL_PARTICIPANTS_LEFT',
|
|
7
|
+
sipHostLeft: 'SIP_HOST_LEFT',
|
|
8
|
+
noHost: 'NO_HOST',
|
|
9
|
+
waitingForMpsEndMeetingTimeout: 'WAITING_FOR_MPS_END_MEETING_TIMEOUT',
|
|
10
|
+
fraudDetection: 'FRAUD_DETECTION',
|
|
11
|
+
meetingEndedByHost: 'MEETING_ENDED_BY_HOST',
|
|
12
|
+
meetingUpdated: 'MEETING_UPDATED', // Locus code has comment about EndMeetingIfPossible reason for this one
|
|
13
|
+
meetingCancelled: 'MEETING_CANCELLED', // Locus code has comment about EndMeetingIfPossible reason for this one
|
|
14
|
+
autoEndWithSingleParticipant: 'AUTO_END_WITH_SINGLE_PARTICIPANT',
|
|
15
|
+
breakoutEnded: 'BREAKOUT_ENDED', // indicates that only a breakout session ended, not the whole meeting
|
|
16
|
+
} as const;
|
|
17
|
+
|
|
18
|
+
export type EndMeetingReason = Enum<typeof EndMeetingReason>;
|
|
19
|
+
|
|
3
20
|
export type LocusFullState = {
|
|
4
21
|
active: boolean;
|
|
5
22
|
count: number;
|
|
6
23
|
lastActive: string;
|
|
7
24
|
locked: boolean;
|
|
8
25
|
sessionId: string;
|
|
9
|
-
|
|
26
|
+
sessionIds: string[];
|
|
10
27
|
startTime: number;
|
|
11
28
|
state: string;
|
|
12
29
|
type: string;
|
|
30
|
+
endMeetingReason?: EndMeetingReason;
|
|
13
31
|
};
|
|
14
32
|
|
|
15
33
|
export type Links = {
|
package/src/meeting/index.ts
CHANGED
|
@@ -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
|
|
@@ -3459,6 +3461,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3459
3461
|
this.breakouts.locusUrlUpdate(url);
|
|
3460
3462
|
this.simultaneousInterpretation.locusUrlUpdate(url);
|
|
3461
3463
|
this.annotation.locusUrlUpdate(url);
|
|
3464
|
+
this.aiEnableRequest.locusUrlUpdate(url);
|
|
3462
3465
|
this.locusUrl = url;
|
|
3463
3466
|
this.locusId = this.locusUrl?.split('/').pop();
|
|
3464
3467
|
this.recordingController.setLocusUrl(this.locusUrl);
|
|
@@ -3734,7 +3737,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3734
3737
|
});
|
|
3735
3738
|
this.updateLLMConnection();
|
|
3736
3739
|
});
|
|
3737
|
-
this.locusInfo.on(LOCUSINFO.EVENTS.SELF_ADMITTED_GUEST,
|
|
3740
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.SELF_ADMITTED_GUEST, (payload) => {
|
|
3738
3741
|
this.stopKeepAlive();
|
|
3739
3742
|
|
|
3740
3743
|
if (payload) {
|
|
@@ -3760,6 +3763,15 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3760
3763
|
});
|
|
3761
3764
|
}
|
|
3762
3765
|
this.rtcMetrics?.sendNextMetrics();
|
|
3766
|
+
|
|
3767
|
+
this.ensureDefaultDatachannelTokenAfterAdmit().catch((error) => {
|
|
3768
|
+
LoggerProxy.logger.warn(
|
|
3769
|
+
`Meeting:index#setUpLocusInfoSelfListener --> failed post-admit token prefetch flow: ${
|
|
3770
|
+
error?.message || String(error)
|
|
3771
|
+
}`
|
|
3772
|
+
);
|
|
3773
|
+
});
|
|
3774
|
+
|
|
3763
3775
|
this.updateLLMConnection();
|
|
3764
3776
|
});
|
|
3765
3777
|
|
|
@@ -4650,6 +4662,33 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4650
4662
|
this.sipUri = sipUri;
|
|
4651
4663
|
}
|
|
4652
4664
|
|
|
4665
|
+
/**
|
|
4666
|
+
* After initial locus setup, refreshes destination with synced locus data and optionally
|
|
4667
|
+
* performs deferred meeting info fetch when initial locus was incomplete.
|
|
4668
|
+
* @param {LocusDTO} locus
|
|
4669
|
+
* @returns {void}
|
|
4670
|
+
*/
|
|
4671
|
+
public async finalizeMeetingAfterInitialLocusSetup(locus: LocusDTO): Promise<void> {
|
|
4672
|
+
if (locus && this?.destinationType === DESTINATION_TYPE.LOCUS_ID) {
|
|
4673
|
+
// destination is initialized from the initial locus snapshot in constructor,
|
|
4674
|
+
// so refresh it after locus sync to avoid stale partial hash-tree data.
|
|
4675
|
+
this.destination = locus;
|
|
4676
|
+
}
|
|
4677
|
+
if (
|
|
4678
|
+
(!this.meetingInfo || isEmpty(this.meetingInfo)) &&
|
|
4679
|
+
(this.destination as LocusDTO)?.info &&
|
|
4680
|
+
!this.fetchMeetingInfoTimeoutId
|
|
4681
|
+
) {
|
|
4682
|
+
try {
|
|
4683
|
+
await this.fetchMeetingInfo({});
|
|
4684
|
+
} catch (error: any) {
|
|
4685
|
+
LoggerProxy.logger.info(
|
|
4686
|
+
`Meeting:index#finalizeMeetingAfterInitialLocusSetup --> deferred fetchMeetingInfo failed: ${error.message}`
|
|
4687
|
+
);
|
|
4688
|
+
}
|
|
4689
|
+
}
|
|
4690
|
+
}
|
|
4691
|
+
|
|
4653
4692
|
/**
|
|
4654
4693
|
* Set the locus info the class instance. Should be called with the parsed locus
|
|
4655
4694
|
* we got in the join response.
|
|
@@ -5121,8 +5160,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5121
5160
|
public setMercuryListener() {
|
|
5122
5161
|
// Client will have a socket manager and handle reconnecting to mercury, when we reconnect to mercury
|
|
5123
5162
|
// if the meeting has active peer connections, it should try to reconnect.
|
|
5124
|
-
|
|
5125
|
-
this.webex.internal.mercury.on(ONLINE, () => {
|
|
5163
|
+
this.mercuryOnlineHandler = () => {
|
|
5126
5164
|
LoggerProxy.logger.info('Meeting:index#setMercuryListener --> Web socket online');
|
|
5127
5165
|
|
|
5128
5166
|
// Only send restore event when it was disconnected before and for connected later
|
|
@@ -5132,15 +5170,47 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5132
5170
|
});
|
|
5133
5171
|
}
|
|
5134
5172
|
this.hasWebsocketConnected = true;
|
|
5135
|
-
}
|
|
5173
|
+
};
|
|
5136
5174
|
|
|
5137
|
-
|
|
5138
|
-
this.webex.internal.mercury.on(OFFLINE, () => {
|
|
5175
|
+
this.mercuryOfflineHandler = () => {
|
|
5139
5176
|
LoggerProxy.logger.error('Meeting:index#setMercuryListener --> Web socket offline');
|
|
5140
5177
|
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MERCURY_CONNECTION_FAILURE, {
|
|
5141
5178
|
correlation_id: this.correlationId,
|
|
5142
5179
|
});
|
|
5143
|
-
}
|
|
5180
|
+
};
|
|
5181
|
+
|
|
5182
|
+
// @ts-ignore
|
|
5183
|
+
this.webex.internal.mercury.on(ONLINE, this.mercuryOnlineHandler);
|
|
5184
|
+
// @ts-ignore
|
|
5185
|
+
this.webex.internal.mercury.on(OFFLINE, this.mercuryOfflineHandler);
|
|
5186
|
+
}
|
|
5187
|
+
|
|
5188
|
+
/**
|
|
5189
|
+
* Removes this meeting's Mercury ONLINE/OFFLINE event listeners registered
|
|
5190
|
+
* by setMercuryListener(). Must be called before Locus /leave to avoid
|
|
5191
|
+
* unnecessary syncs/metrics triggered by events received while leaving
|
|
5192
|
+
* (per Locus team recommendation).
|
|
5193
|
+
*
|
|
5194
|
+
* Mercury is a process-wide singleton shared with other plugins, so we
|
|
5195
|
+
* pass the bound handler refs to .off() to avoid clearing every listener
|
|
5196
|
+
* for ONLINE/OFFLINE on the shared emitter.
|
|
5197
|
+
*
|
|
5198
|
+
* Idempotent: subsequent calls are no-ops because the handler refs are
|
|
5199
|
+
* cleared after detaching.
|
|
5200
|
+
* @private
|
|
5201
|
+
* @returns {void}
|
|
5202
|
+
*/
|
|
5203
|
+
private stopListeningForMercuryEvents() {
|
|
5204
|
+
if (this.mercuryOnlineHandler) {
|
|
5205
|
+
// @ts-ignore
|
|
5206
|
+
this.webex.internal.mercury.off(ONLINE, this.mercuryOnlineHandler);
|
|
5207
|
+
this.mercuryOnlineHandler = undefined;
|
|
5208
|
+
}
|
|
5209
|
+
if (this.mercuryOfflineHandler) {
|
|
5210
|
+
// @ts-ignore
|
|
5211
|
+
this.webex.internal.mercury.off(OFFLINE, this.mercuryOfflineHandler);
|
|
5212
|
+
this.mercuryOfflineHandler = undefined;
|
|
5213
|
+
}
|
|
5144
5214
|
}
|
|
5145
5215
|
|
|
5146
5216
|
/**
|
|
@@ -5960,6 +6030,30 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5960
6030
|
);
|
|
5961
6031
|
}
|
|
5962
6032
|
|
|
6033
|
+
/**
|
|
6034
|
+
* Restores LLM subchannel subscriptions after reconnect when captions are active.
|
|
6035
|
+
* @returns {void}
|
|
6036
|
+
*/
|
|
6037
|
+
private restoreLLMSubscriptionsIfNeeded(): void {
|
|
6038
|
+
try {
|
|
6039
|
+
// @ts-ignore
|
|
6040
|
+
const isCaptionBoxOn = this.webex.internal.voicea?.getIsCaptionBoxOn?.();
|
|
6041
|
+
|
|
6042
|
+
if (!isCaptionBoxOn) {
|
|
6043
|
+
return;
|
|
6044
|
+
}
|
|
6045
|
+
|
|
6046
|
+
// @ts-ignore
|
|
6047
|
+
this.webex.internal.voicea.updateSubchannelSubscriptions({subscribe: ['transcription']});
|
|
6048
|
+
} catch (error) {
|
|
6049
|
+
const msg = error?.message || String(error);
|
|
6050
|
+
|
|
6051
|
+
LoggerProxy.logger.warn(
|
|
6052
|
+
`Meeting:index#restoreLLMSubscriptionsIfNeeded --> failed to restore subscriptions after LLM online: ${msg}`
|
|
6053
|
+
);
|
|
6054
|
+
}
|
|
6055
|
+
}
|
|
6056
|
+
|
|
5963
6057
|
/**
|
|
5964
6058
|
* This is a callback for the LLM event that is triggered when it comes online
|
|
5965
6059
|
* This method in turn will trigger an event to the developers that the LLM is connected
|
|
@@ -5968,8 +6062,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5968
6062
|
* @returns {null}
|
|
5969
6063
|
*/
|
|
5970
6064
|
private handleLLMOnline = (): void => {
|
|
5971
|
-
|
|
5972
|
-
|
|
6065
|
+
this.restoreLLMSubscriptionsIfNeeded();
|
|
6066
|
+
|
|
5973
6067
|
Trigger.trigger(
|
|
5974
6068
|
this,
|
|
5975
6069
|
{
|
|
@@ -6200,6 +6294,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6200
6294
|
this.saveDataChannelToken(join);
|
|
6201
6295
|
// @ts-ignore - config coming from registerPlugin
|
|
6202
6296
|
if (this.config.enableAutomaticLLM) {
|
|
6297
|
+
// @ts-ignore
|
|
6298
|
+
this.webex.internal.llm.off('online', this.handleLLMOnline);
|
|
6203
6299
|
// @ts-ignore
|
|
6204
6300
|
this.webex.internal.llm.on('online', this.handleLLMOnline);
|
|
6205
6301
|
this.updateLLMConnection()
|
|
@@ -6266,6 +6362,49 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6266
6362
|
}
|
|
6267
6363
|
}
|
|
6268
6364
|
|
|
6365
|
+
/**
|
|
6366
|
+
* Removes LLM event listeners and clears the health check timer.
|
|
6367
|
+
* Must be called before Locus /leave to avoid unnecessary syncs triggered
|
|
6368
|
+
* by events received while leaving (per Locus team recommendation).
|
|
6369
|
+
* Idempotent: safe to call multiple times; .off() is a no-op when no
|
|
6370
|
+
* matching listener is registered.
|
|
6371
|
+
* @private
|
|
6372
|
+
* @returns {void}
|
|
6373
|
+
*/
|
|
6374
|
+
private stopListeningForLLMEvents() {
|
|
6375
|
+
// @ts-ignore - fix types
|
|
6376
|
+
this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
|
|
6377
|
+
// @ts-ignore - fix types
|
|
6378
|
+
this.webex.internal.llm.off(LOCUS_LLM_EVENT, this.processLocusLLMEvent);
|
|
6379
|
+
this.clearLLMHealthCheckTimer();
|
|
6380
|
+
}
|
|
6381
|
+
|
|
6382
|
+
/**
|
|
6383
|
+
* Stops listening on every event bus (LLM, Mercury, voicea/transcription,
|
|
6384
|
+
* annotation) that could otherwise deliver events to this meeting while
|
|
6385
|
+
* Locus is processing /leave or /end. Per the Locus team recommendation,
|
|
6386
|
+
* this must run before the Locus request is dispatched to avoid
|
|
6387
|
+
* unnecessary syncs triggered by in-flight events.
|
|
6388
|
+
*
|
|
6389
|
+
* Voicea (transcription) subscribes to llm 'event:relay.event' internally,
|
|
6390
|
+
* and the annotation plugin subscribes to both mercury and llm, so both
|
|
6391
|
+
* must be torn down alongside the direct LLM/Mercury listeners.
|
|
6392
|
+
*
|
|
6393
|
+
* Idempotent: safe to call multiple times; .off() is a no-op when no
|
|
6394
|
+
* matching listener is registered, and stopTranscription is guarded.
|
|
6395
|
+
* @private
|
|
6396
|
+
* @returns {void}
|
|
6397
|
+
*/
|
|
6398
|
+
private stopListeningForMeetingEvents() {
|
|
6399
|
+
this.stopListeningForLLMEvents();
|
|
6400
|
+
this.stopListeningForMercuryEvents();
|
|
6401
|
+
if (this.transcription) {
|
|
6402
|
+
this.stopTranscription();
|
|
6403
|
+
this.transcription = undefined;
|
|
6404
|
+
}
|
|
6405
|
+
this.annotation.deregisterEvents();
|
|
6406
|
+
}
|
|
6407
|
+
|
|
6269
6408
|
/**
|
|
6270
6409
|
* Disconnects and cleans up the default LLM session listeners/timers.
|
|
6271
6410
|
* @param {Object} options
|
|
@@ -6300,12 +6439,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6300
6439
|
// @ts-ignore - Fix type
|
|
6301
6440
|
this.webex.internal.llm.off('online', this.handleLLMOnline);
|
|
6302
6441
|
}
|
|
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);
|
|
6307
|
-
|
|
6308
|
-
this.clearLLMHealthCheckTimer();
|
|
6442
|
+
this.stopListeningForLLMEvents();
|
|
6309
6443
|
}
|
|
6310
6444
|
};
|
|
6311
6445
|
|
|
@@ -6343,6 +6477,52 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6343
6477
|
}
|
|
6344
6478
|
}
|
|
6345
6479
|
|
|
6480
|
+
/**
|
|
6481
|
+
* Ensures default-session data channel token exists after lobby admission.
|
|
6482
|
+
* Some lobby users do not receive a token until they are admitted.
|
|
6483
|
+
* @returns {Promise<boolean>} true when a new token is fetched and cached
|
|
6484
|
+
*/
|
|
6485
|
+
private async ensureDefaultDatachannelTokenAfterAdmit(): Promise<boolean> {
|
|
6486
|
+
try {
|
|
6487
|
+
// @ts-ignore
|
|
6488
|
+
const datachannelToken = this.webex.internal.llm.getDatachannelToken();
|
|
6489
|
+
// @ts-ignore
|
|
6490
|
+
const isDataChannelTokenEnabled = await this.webex.internal.llm.isDataChannelTokenEnabled();
|
|
6491
|
+
|
|
6492
|
+
if (!isDataChannelTokenEnabled || datachannelToken) {
|
|
6493
|
+
return false;
|
|
6494
|
+
}
|
|
6495
|
+
|
|
6496
|
+
const response = await this.meetingRequest.fetchDatachannelToken({
|
|
6497
|
+
locusUrl: this.locusUrl,
|
|
6498
|
+
requestingParticipantId: this.members.selfId,
|
|
6499
|
+
isPracticeSession: false,
|
|
6500
|
+
});
|
|
6501
|
+
const fetchedDatachannelToken = response?.body?.datachannelToken;
|
|
6502
|
+
|
|
6503
|
+
if (!fetchedDatachannelToken) {
|
|
6504
|
+
return false;
|
|
6505
|
+
}
|
|
6506
|
+
|
|
6507
|
+
// @ts-ignore
|
|
6508
|
+
this.webex.internal.llm.setDatachannelToken(
|
|
6509
|
+
fetchedDatachannelToken,
|
|
6510
|
+
DataChannelTokenType.Default
|
|
6511
|
+
);
|
|
6512
|
+
|
|
6513
|
+
return true;
|
|
6514
|
+
} catch (error) {
|
|
6515
|
+
const msg = error?.message || String(error);
|
|
6516
|
+
|
|
6517
|
+
LoggerProxy.logger.warn(
|
|
6518
|
+
`Meeting:index#ensureDefaultDatachannelTokenAfterAdmit --> failed to proactively fetch default data channel token after admit: ${msg}`,
|
|
6519
|
+
{statusCode: error?.statusCode}
|
|
6520
|
+
);
|
|
6521
|
+
|
|
6522
|
+
return false;
|
|
6523
|
+
}
|
|
6524
|
+
}
|
|
6525
|
+
|
|
6346
6526
|
/**
|
|
6347
6527
|
* Connects to low latency mercury and reconnects if the address has changed
|
|
6348
6528
|
* It will also disconnect if called when the meeting has ended
|
|
@@ -8716,6 +8896,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
8716
8896
|
});
|
|
8717
8897
|
LoggerProxy.logger.log('Meeting:index#leave --> Leaving a meeting');
|
|
8718
8898
|
|
|
8899
|
+
this.stopListeningForMeetingEvents();
|
|
8900
|
+
|
|
8719
8901
|
return MeetingUtil.leaveMeeting(this, options)
|
|
8720
8902
|
.then(async (leave) => {
|
|
8721
8903
|
// CA team recommends submitting this *after* locus /leave
|
|
@@ -9580,6 +9762,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
9580
9762
|
locus_id: this.locusId,
|
|
9581
9763
|
});
|
|
9582
9764
|
|
|
9765
|
+
this.stopListeningForMeetingEvents();
|
|
9766
|
+
|
|
9583
9767
|
return MeetingUtil.endMeetingForAll(this)
|
|
9584
9768
|
.then(async (end) => {
|
|
9585
9769
|
this.meetingFiniteStateMachine.end();
|
|
@@ -9641,11 +9825,11 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
9641
9825
|
}
|
|
9642
9826
|
this.queuedMediaUpdates = [];
|
|
9643
9827
|
|
|
9644
|
-
|
|
9645
|
-
|
|
9646
|
-
|
|
9647
|
-
|
|
9648
|
-
|
|
9828
|
+
// Listener teardown (transcription, annotation, llm/mercury) runs in
|
|
9829
|
+
// stopListeningForMeetingEvents() before /leave and /end so events
|
|
9830
|
+
// received mid-teardown do not trigger Locus syncs. Calling it here
|
|
9831
|
+
// again would double-emit MEETING_STOPPED_RECEIVING_TRANSCRIPTION
|
|
9832
|
+
// because stopTranscription() always fires its trigger.
|
|
9649
9833
|
this.clearDataChannelToken();
|
|
9650
9834
|
await this.cleanupLLMConneciton({throwOnError: false});
|
|
9651
9835
|
};
|
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(
|
package/src/meetings/index.ts
CHANGED
|
@@ -69,7 +69,9 @@ import JoinForbiddenError from '../common/errors/join-forbidden-error';
|
|
|
69
69
|
import {HashTreeMessage} from '../hashTree/hashTreeParser';
|
|
70
70
|
import {HashTreeObject} from '../hashTree/types';
|
|
71
71
|
import {isSelf} from '../hashTree/utils';
|
|
72
|
+
|
|
72
73
|
import {createLocusFromHashTreeMessage, findMeetingForHashTreeMessage} from '../locus-info';
|
|
74
|
+
import {LocusDTO} from '../locus-info/types';
|
|
73
75
|
|
|
74
76
|
let mediaLogger;
|
|
75
77
|
|
|
@@ -313,7 +315,7 @@ export default class Meetings extends WebexPlugin {
|
|
|
313
315
|
const breakoutLocus = this.meetingCollection.getActiveBreakoutLocus(breakoutUrl);
|
|
314
316
|
|
|
315
317
|
const isSelfJoined = newLocus?.self?.state === _JOINED_;
|
|
316
|
-
const isSelfMoved = newLocus
|
|
318
|
+
const isSelfMoved = MeetingsUtil.isSelfMovedOrBreakoutEnded(newLocus);
|
|
317
319
|
// @ts-ignore
|
|
318
320
|
const deviceFromNewLocus = MeetingsUtil.getThisDevice(newLocus, this.webex.internal.device.url);
|
|
319
321
|
const isResourceMovedOnThisDevice =
|
|
@@ -390,7 +392,7 @@ export default class Meetings extends WebexPlugin {
|
|
|
390
392
|
private isNeedHandleLocusDTO(meeting: any, newLocus: any) {
|
|
391
393
|
if (newLocus) {
|
|
392
394
|
const isNewLocusAsBreakout = MeetingsUtil.isBreakoutLocusDTO(newLocus);
|
|
393
|
-
const isSelfMoved = newLocus
|
|
395
|
+
const isSelfMoved = MeetingsUtil.isSelfMovedOrBreakoutEnded(newLocus);
|
|
394
396
|
const isSelfMovedToLobby =
|
|
395
397
|
newLocus?.self?.devices[0]?.intent?.reason === _ON_HOLD_LOBBY_ &&
|
|
396
398
|
newLocus?.self?.devices[0]?.intent?.type === _WAIT_;
|
|
@@ -435,14 +437,11 @@ export default class Meetings extends WebexPlugin {
|
|
|
435
437
|
if (existingMeeting) {
|
|
436
438
|
return existingMeeting;
|
|
437
439
|
}
|
|
438
|
-
|
|
439
440
|
if (data.eventType === LOCUSEVENT.HASH_TREE_DATA_UPDATED) {
|
|
440
441
|
// need to check if maybe this event indicates a move to/from breakout
|
|
441
442
|
const meetingForHashTreeMessage = findMeetingForHashTreeMessage(
|
|
442
|
-
data
|
|
443
|
-
this.meetingCollection
|
|
444
|
-
// @ts-ignore
|
|
445
|
-
this.webex.internal.device.url
|
|
443
|
+
data?.stateElementsMessage,
|
|
444
|
+
this.meetingCollection
|
|
446
445
|
);
|
|
447
446
|
|
|
448
447
|
if (meetingForHashTreeMessage) {
|
|
@@ -492,7 +491,6 @@ export default class Meetings extends WebexPlugin {
|
|
|
492
491
|
*/
|
|
493
492
|
private handleLocusEvent(data: LocusEvent, useRandomDelayForInfo = false) {
|
|
494
493
|
let meeting = this.getCorrespondingMeetingByLocus(data);
|
|
495
|
-
|
|
496
494
|
// @ts-ignore
|
|
497
495
|
if (this.config.experimental.storeLocusHashTreeEventsForDebugging) {
|
|
498
496
|
storeEventForDebugging('mercury', data);
|
|
@@ -586,17 +584,21 @@ export default class Meetings extends WebexPlugin {
|
|
|
586
584
|
this.create(data.locus, DESTINATION_TYPE.LOCUS_ID, useRandomDelayForInfo)
|
|
587
585
|
.then(async (newMeeting) => {
|
|
588
586
|
meeting = newMeeting;
|
|
589
|
-
|
|
590
587
|
try {
|
|
591
588
|
// It's a new meeting so initialize the locus data
|
|
592
|
-
await meeting.locusInfo.initialSetup(
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
589
|
+
await meeting.locusInfo.initialSetup(
|
|
590
|
+
{
|
|
591
|
+
trigger:
|
|
592
|
+
data.eventType === LOCUSEVENT.SDK_LOCUS_FROM_SYNC_MEETINGS
|
|
593
|
+
? 'get-loci-response'
|
|
594
|
+
: 'locus-message',
|
|
595
|
+
locus: data.locus,
|
|
596
|
+
hashTreeMessage: data.stateElementsMessage,
|
|
597
|
+
},
|
|
598
|
+
(locus: LocusDTO) => {
|
|
599
|
+
meeting.finalizeMeetingAfterInitialLocusSetup(locus);
|
|
600
|
+
}
|
|
601
|
+
);
|
|
600
602
|
} catch (error) {
|
|
601
603
|
LoggerProxy.logger.warn(
|
|
602
604
|
`Meetings:index#handleLocusEvent --> Error initializing locus data: ${error.message}`
|
|
@@ -1765,6 +1767,7 @@ export default class Meetings extends WebexPlugin {
|
|
|
1765
1767
|
extraParams: infoExtraParams,
|
|
1766
1768
|
sendCAevents: !!callStateForMetrics?.correlationId, // if client sends correlation id as argument of public create(), then it means that this meeting creation is part of a pre-join intent from user
|
|
1767
1769
|
};
|
|
1770
|
+
const shouldDeferMeetingInfoFetch = type === DESTINATION_TYPE.LOCUS_ID && !destination?.info;
|
|
1768
1771
|
|
|
1769
1772
|
if (meetingInfo) {
|
|
1770
1773
|
meeting.injectMeetingInfo(meetingInfo, meetingInfoOptions, meetingLookupUrl);
|
|
@@ -1776,8 +1779,12 @@ export default class Meetings extends WebexPlugin {
|
|
|
1776
1779
|
waitingTime
|
|
1777
1780
|
);
|
|
1778
1781
|
meeting.parseMeetingInfo(undefined, destination);
|
|
1779
|
-
} else {
|
|
1782
|
+
} else if (!shouldDeferMeetingInfoFetch) {
|
|
1780
1783
|
await meeting.fetchMeetingInfo(meetingInfoOptions);
|
|
1784
|
+
} else {
|
|
1785
|
+
LoggerProxy.logger.info(
|
|
1786
|
+
'Meetings:index#createMeeting --> defer fetchMeetingInfo for incomplete locus, will do it after locus initialSetup'
|
|
1787
|
+
);
|
|
1781
1788
|
}
|
|
1782
1789
|
}
|
|
1783
1790
|
} catch (err) {
|
|
@@ -1811,7 +1818,11 @@ export default class Meetings extends WebexPlugin {
|
|
|
1811
1818
|
// For type LOCUS_ID we need to parse the locus object to get the information
|
|
1812
1819
|
// about the caller and callee
|
|
1813
1820
|
// Meeting Added event will be created in `handleLocusEvent`
|
|
1814
|
-
if
|
|
1821
|
+
// Only emit MEETING_ADDED if the meeting still exists in the collection.
|
|
1822
|
+
// If fetchMeetingInfo failed and the meeting was destroyed in the catch block,
|
|
1823
|
+
// skip emitting to prevent orphaned meeting references on the consumer side.
|
|
1824
|
+
// @ts-ignore - getMeetingByType types value as object but accepts strings (same as handleLocusEvent)
|
|
1825
|
+
if (type !== DESTINATION_TYPE.LOCUS_ID && this.getMeetingByType(_ID_, meeting.id)) {
|
|
1815
1826
|
if (!meeting.sipUri) {
|
|
1816
1827
|
meeting.setSipUri(destination);
|
|
1817
1828
|
}
|
|
@@ -1886,23 +1897,20 @@ export default class Meetings extends WebexPlugin {
|
|
|
1886
1897
|
* @public
|
|
1887
1898
|
* @memberof Meetings
|
|
1888
1899
|
*/
|
|
1889
|
-
public syncMeetings({keepOnlyLocusMeetings = true} = {}): Promise<void> {
|
|
1900
|
+
public async syncMeetings({keepOnlyLocusMeetings = true} = {}): Promise<void> {
|
|
1890
1901
|
// @ts-ignore
|
|
1891
1902
|
if (this.webex.credentials.isUnverifiedGuest) {
|
|
1892
1903
|
LoggerProxy.logger.info(
|
|
1893
|
-
'Meetings:index#syncMeetings --> skipping
|
|
1904
|
+
'Meetings:index#syncMeetings --> user is unverified guest, skipping calling Locus for meeting sync'
|
|
1894
1905
|
);
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
return this.request
|
|
1900
|
-
.getActiveMeetings()
|
|
1901
|
-
.then((locusArray) => {
|
|
1902
|
-
const activeLocusUrl = [];
|
|
1906
|
+
} else {
|
|
1907
|
+
try {
|
|
1908
|
+
const locusArray = await this.request.getActiveMeetings();
|
|
1909
|
+
const activeLocusUrl: string[] = [];
|
|
1903
1910
|
|
|
1904
1911
|
if (locusArray?.loci && locusArray.loci.length > 0) {
|
|
1905
1912
|
const lociToUpdate = this.sortLocusArrayToUpdate(locusArray.loci);
|
|
1913
|
+
|
|
1906
1914
|
lociToUpdate.forEach((locus) => {
|
|
1907
1915
|
activeLocusUrl.push(locus.url);
|
|
1908
1916
|
this.handleLocusEvent({
|
|
@@ -1920,21 +1928,48 @@ export default class Meetings extends WebexPlugin {
|
|
|
1920
1928
|
// (they had a locusUrl previously but are no longer active) in the sync
|
|
1921
1929
|
for (const meeting of Object.values(meetingsCollection)) {
|
|
1922
1930
|
// @ts-ignore
|
|
1923
|
-
const {locusUrl} = meeting;
|
|
1931
|
+
const {locusUrl, locusInfo} = meeting;
|
|
1924
1932
|
if ((keepOnlyLocusMeetings || locusUrl) && !activeLocusUrl.includes(locusUrl)) {
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1933
|
+
const globalMeetingId = locusInfo?.info?.globalMeetingId;
|
|
1934
|
+
|
|
1935
|
+
if (
|
|
1936
|
+
globalMeetingId &&
|
|
1937
|
+
locusArray?.loci?.some(
|
|
1938
|
+
(locus: LocusDTO) => locus.info?.globalMeetingId === globalMeetingId
|
|
1939
|
+
)
|
|
1940
|
+
) {
|
|
1941
|
+
// don't destroy the meeting as Locus API still returned some Locus that shares
|
|
1942
|
+
// the same globalMeetingId - that happens for example if a webinar user (who hasn't scheduled it)
|
|
1943
|
+
// is in a breakout and gets moved to a different breakout while we were offline
|
|
1944
|
+
} else {
|
|
1945
|
+
// destroy function also uploads logs
|
|
1946
|
+
// @ts-ignore
|
|
1947
|
+
this.destroy(meeting, MEETING_REMOVED_REASON.NO_MEETINGS_TO_SYNC);
|
|
1948
|
+
}
|
|
1928
1949
|
}
|
|
1929
1950
|
}
|
|
1930
1951
|
}
|
|
1931
|
-
})
|
|
1932
|
-
.catch((error) => {
|
|
1952
|
+
} catch (error) {
|
|
1933
1953
|
LoggerProxy.logger.error(
|
|
1934
1954
|
`Meetings:index#syncMeetings --> failed to sync meetings, ${error}`
|
|
1935
1955
|
);
|
|
1936
|
-
throw
|
|
1937
|
-
}
|
|
1956
|
+
throw error;
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
// Trigger hash tree syncs for all remaining meetings
|
|
1961
|
+
const remainingMeetings = this.meetingCollection.getAll();
|
|
1962
|
+
const syncPromises = [];
|
|
1963
|
+
|
|
1964
|
+
for (const meeting of Object.values(remainingMeetings) as any[]) {
|
|
1965
|
+
if (meeting.locusInfo) {
|
|
1966
|
+
syncPromises.push(meeting.locusInfo.syncAllHashTreeDatasets());
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
if (syncPromises.length > 0) {
|
|
1971
|
+
await Promise.all(syncPromises);
|
|
1972
|
+
}
|
|
1938
1973
|
}
|
|
1939
1974
|
|
|
1940
1975
|
/**
|
|
@@ -1950,8 +1985,8 @@ export default class Meetings extends WebexPlugin {
|
|
|
1950
1985
|
this.breakoutLocusForHandleLater = [];
|
|
1951
1986
|
const lociToUpdate = [...mainLoci];
|
|
1952
1987
|
breakoutLoci.forEach((breakoutLocus) => {
|
|
1953
|
-
const associateMainLocus = mainLoci.find(
|
|
1954
|
-
(mainLocus
|
|
1988
|
+
const associateMainLocus = mainLoci.find((mainLocus) =>
|
|
1989
|
+
MeetingsUtil.isMainAssociatedWithBreakout(mainLocus, breakoutLocus)
|
|
1955
1990
|
);
|
|
1956
1991
|
const existCorrespondingMeeting = this.getCorrespondingMeetingByLocus({
|
|
1957
1992
|
eventType: LOCUSEVENT.SDK_NO_EVENT,
|
|
@@ -1979,7 +2014,7 @@ export default class Meetings extends WebexPlugin {
|
|
|
1979
2014
|
* @public
|
|
1980
2015
|
* @memberof Meetings
|
|
1981
2016
|
*/
|
|
1982
|
-
checkHandleBreakoutLocus(newCreatedLocus) {
|
|
2017
|
+
checkHandleBreakoutLocus(newCreatedLocus: any) {
|
|
1983
2018
|
if (
|
|
1984
2019
|
!newCreatedLocus ||
|
|
1985
2020
|
!this.breakoutLocusForHandleLater ||
|
|
@@ -1990,9 +2025,8 @@ export default class Meetings extends WebexPlugin {
|
|
|
1990
2025
|
if (MeetingsUtil.isBreakoutLocusDTO(newCreatedLocus)) {
|
|
1991
2026
|
return;
|
|
1992
2027
|
}
|
|
1993
|
-
const existIndex = this.breakoutLocusForHandleLater.findIndex(
|
|
1994
|
-
(breakoutLocus)
|
|
1995
|
-
breakoutLocus.controls?.breakout?.url === newCreatedLocus.controls?.breakout?.url
|
|
2028
|
+
const existIndex = this.breakoutLocusForHandleLater.findIndex((breakoutLocus: any) =>
|
|
2029
|
+
MeetingsUtil.isMainAssociatedWithBreakout(newCreatedLocus, breakoutLocus)
|
|
1996
2030
|
);
|
|
1997
2031
|
|
|
1998
2032
|
if (existIndex < 0) {
|