@webex/plugin-meetings 3.12.0-next.64 → 3.12.0-next.66
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/dist/aiEnableRequest/index.js +1 -1
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/constants.js +17 -3
- package/dist/constants.js.map +1 -1
- package/dist/interceptors/dataChannelAuthToken.js +75 -15
- package/dist/interceptors/dataChannelAuthToken.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/meeting/index.js +738 -679
- package/dist/meeting/index.js.map +1 -1
- package/dist/types/constants.d.ts +1 -1
- package/dist/types/meeting/index.d.ts +2 -2
- package/dist/webinar/index.js +247 -152
- package/dist/webinar/index.js.map +1 -1
- package/package.json +3 -3
- package/src/constants.ts +2 -1
- package/src/interceptors/dataChannelAuthToken.ts +88 -12
- package/src/meeting/index.ts +111 -49
- package/src/webinar/index.ts +140 -29
- package/test/unit/spec/interceptors/dataChannelAuthToken.ts +196 -0
- package/test/unit/spec/meeting/index.js +139 -23
- package/test/unit/spec/webinar/index.ts +257 -21
package/src/meeting/index.ts
CHANGED
|
@@ -136,6 +136,7 @@ import {
|
|
|
136
136
|
STAGE_MANAGER_TYPE,
|
|
137
137
|
LOCUSEVENT,
|
|
138
138
|
LOCUS_LLM_EVENT,
|
|
139
|
+
LLM_DEFAULT_SESSION,
|
|
139
140
|
LLM_PRACTICE_SESSION,
|
|
140
141
|
} from '../constants';
|
|
141
142
|
import BEHAVIORAL_METRICS from '../metrics/constants';
|
|
@@ -3507,8 +3508,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3507
3508
|
this.recordingController.setLocusUrl(this.locusUrl);
|
|
3508
3509
|
this.controlsOptionsManager.setLocusUrl(this.locusUrl, !!isMainLocus);
|
|
3509
3510
|
this.webinar.locusUrlUpdate(url);
|
|
3510
|
-
// @ts-ignore
|
|
3511
|
-
this.webex.internal.llm.setRefreshHandler(() => this.refreshDataChannelToken());
|
|
3512
3511
|
|
|
3513
3512
|
Trigger.trigger(
|
|
3514
3513
|
this,
|
|
@@ -3777,7 +3776,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3777
3776
|
});
|
|
3778
3777
|
this.updateLLMConnection();
|
|
3779
3778
|
});
|
|
3780
|
-
this.locusInfo.on(LOCUSINFO.EVENTS.SELF_ADMITTED_GUEST, (payload) => {
|
|
3779
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.SELF_ADMITTED_GUEST, async (payload) => {
|
|
3781
3780
|
this.stopKeepAlive();
|
|
3782
3781
|
|
|
3783
3782
|
if (payload) {
|
|
@@ -3804,13 +3803,15 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3804
3803
|
}
|
|
3805
3804
|
this.rtcMetrics?.sendNextMetrics();
|
|
3806
3805
|
|
|
3807
|
-
|
|
3806
|
+
try {
|
|
3807
|
+
await this.ensureDefaultDatachannelTokenAfterAdmit();
|
|
3808
|
+
} catch (error) {
|
|
3808
3809
|
LoggerProxy.logger.warn(
|
|
3809
3810
|
`Meeting:index#setUpLocusInfoSelfListener --> failed post-admit token prefetch flow: ${
|
|
3810
3811
|
error?.message || String(error)
|
|
3811
3812
|
}`
|
|
3812
3813
|
);
|
|
3813
|
-
}
|
|
3814
|
+
}
|
|
3814
3815
|
|
|
3815
3816
|
this.updateLLMConnection();
|
|
3816
3817
|
});
|
|
@@ -6499,16 +6500,23 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6499
6500
|
throwOnError?: boolean;
|
|
6500
6501
|
} = {}): Promise<void> => {
|
|
6501
6502
|
// @ts-ignore - Fix type
|
|
6502
|
-
|
|
6503
|
-
const isOwner =
|
|
6503
|
+
// @ts-ignore - Fix type
|
|
6504
|
+
const {currentOwner, isOwner} = this.webex.internal.llm.resolveSessionOwnership(
|
|
6505
|
+
this.id,
|
|
6506
|
+
LLM_DEFAULT_SESSION
|
|
6507
|
+
);
|
|
6504
6508
|
|
|
6505
6509
|
try {
|
|
6506
6510
|
if (isOwner) {
|
|
6507
6511
|
// @ts-ignore - Fix type
|
|
6508
|
-
await this.webex.internal.llm.disconnectLLM(
|
|
6509
|
-
|
|
6510
|
-
|
|
6511
|
-
|
|
6512
|
+
await this.webex.internal.llm.disconnectLLM(
|
|
6513
|
+
{
|
|
6514
|
+
code: 3050,
|
|
6515
|
+
reason: 'done (permanent)',
|
|
6516
|
+
},
|
|
6517
|
+
LLM_DEFAULT_SESSION,
|
|
6518
|
+
this.id
|
|
6519
|
+
);
|
|
6512
6520
|
} else {
|
|
6513
6521
|
LoggerProxy.logger.info(
|
|
6514
6522
|
`Meeting:index#cleanupLLMConneciton --> skipping disconnect; LLM owned by meeting ${currentOwner}, not ${this.id}`
|
|
@@ -6530,27 +6538,31 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6530
6538
|
}
|
|
6531
6539
|
this.stopListeningForLLMEvents();
|
|
6532
6540
|
|
|
6533
|
-
//
|
|
6534
|
-
//
|
|
6535
|
-
// resolved. `disconnectLLM` only clears the owner on its success path,
|
|
6536
|
-
// so a failed disconnect would otherwise leave a stale owner pointing
|
|
6537
|
-
// at a torn-down meeting and permanently block other meetings'
|
|
6538
|
-
// `updateLLMConnection` calls via the ownership guard.
|
|
6541
|
+
// Re-check ownership after awaiting disconnectLLM. If ownership changed
|
|
6542
|
+
// while cleanup was in flight, do not clear another meeting's owner tag.
|
|
6539
6543
|
if (isOwner) {
|
|
6540
|
-
|
|
6541
|
-
|
|
6544
|
+
const {currentOwner: currentOwnerAfterCleanup} =
|
|
6545
|
+
// @ts-ignore - Fix type
|
|
6546
|
+
this.webex.internal.llm.resolveSessionOwnership(this.id, LLM_DEFAULT_SESSION);
|
|
6547
|
+
|
|
6548
|
+
if (currentOwnerAfterCleanup === this.id) {
|
|
6549
|
+
// @ts-ignore - Fix type
|
|
6550
|
+
this.webex.internal.llm.setOwnerMeetingId?.(undefined);
|
|
6551
|
+
}
|
|
6542
6552
|
}
|
|
6543
6553
|
}
|
|
6544
6554
|
};
|
|
6545
6555
|
|
|
6546
6556
|
/**
|
|
6547
|
-
* Clears
|
|
6548
|
-
*
|
|
6557
|
+
* Clears data channel tokens associated with this meeting ownership.
|
|
6558
|
+
* Ownership checks are enforced in internal-plugin-llm.
|
|
6549
6559
|
* @returns {void}
|
|
6550
6560
|
*/
|
|
6551
6561
|
clearDataChannelToken(): void {
|
|
6552
6562
|
// @ts-ignore
|
|
6553
|
-
this.webex.internal.llm.
|
|
6563
|
+
this.webex.internal.llm.clearDatachannelToken(LLM_DEFAULT_SESSION, this.id);
|
|
6564
|
+
// @ts-ignore
|
|
6565
|
+
this.webex.internal.llm.clearDatachannelToken(LLM_PRACTICE_SESSION, this.id);
|
|
6554
6566
|
}
|
|
6555
6567
|
|
|
6556
6568
|
/**
|
|
@@ -6565,14 +6577,15 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6565
6577
|
|
|
6566
6578
|
if (datachannelToken) {
|
|
6567
6579
|
// @ts-ignore
|
|
6568
|
-
this.webex.internal.llm.setDatachannelToken(datachannelToken,
|
|
6580
|
+
this.webex.internal.llm.setDatachannelToken(datachannelToken, LLM_DEFAULT_SESSION, this.id);
|
|
6569
6581
|
}
|
|
6570
6582
|
|
|
6571
6583
|
if (practiceSessionDatachannelToken) {
|
|
6572
6584
|
// @ts-ignore
|
|
6573
6585
|
this.webex.internal.llm.setDatachannelToken(
|
|
6574
6586
|
practiceSessionDatachannelToken,
|
|
6575
|
-
|
|
6587
|
+
LLM_PRACTICE_SESSION,
|
|
6588
|
+
this.id
|
|
6576
6589
|
);
|
|
6577
6590
|
}
|
|
6578
6591
|
}
|
|
@@ -6585,7 +6598,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6585
6598
|
private async ensureDefaultDatachannelTokenAfterAdmit(): Promise<boolean> {
|
|
6586
6599
|
try {
|
|
6587
6600
|
// @ts-ignore
|
|
6588
|
-
const datachannelToken = this.webex.internal.llm.getDatachannelToken(
|
|
6601
|
+
const datachannelToken = this.webex.internal.llm.getDatachannelToken(
|
|
6602
|
+
LLM_DEFAULT_SESSION,
|
|
6603
|
+
this.id
|
|
6604
|
+
);
|
|
6589
6605
|
// @ts-ignore
|
|
6590
6606
|
const isDataChannelTokenEnabled = await this.webex.internal.llm.isDataChannelTokenEnabled();
|
|
6591
6607
|
|
|
@@ -6607,7 +6623,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6607
6623
|
// @ts-ignore
|
|
6608
6624
|
this.webex.internal.llm.setDatachannelToken(
|
|
6609
6625
|
fetchedDatachannelToken,
|
|
6610
|
-
|
|
6626
|
+
LLM_DEFAULT_SESSION,
|
|
6627
|
+
this.id
|
|
6611
6628
|
);
|
|
6612
6629
|
|
|
6613
6630
|
return true;
|
|
@@ -6634,11 +6651,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6634
6651
|
|
|
6635
6652
|
const isJoined = this.isJoined();
|
|
6636
6653
|
|
|
6637
|
-
// @ts-ignore
|
|
6638
|
-
const datachannelToken = this.webex.internal.llm.getDatachannelToken(
|
|
6639
|
-
DataChannelTokenType.Default
|
|
6640
|
-
);
|
|
6641
|
-
|
|
6642
6654
|
const dataChannelUrl = datachannelUrl;
|
|
6643
6655
|
|
|
6644
6656
|
// Ownership guard: when the default LLM session is already connected and
|
|
@@ -6648,10 +6660,33 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6648
6660
|
// connection when this meeting is the current owner, or when no owner is
|
|
6649
6661
|
// set yet (first claim).
|
|
6650
6662
|
// @ts-ignore - Fix type
|
|
6651
|
-
const currentOwner = this.webex.internal.llm.
|
|
6663
|
+
const {currentOwner} = this.webex.internal.llm.resolveSessionOwnership(
|
|
6664
|
+
this.id,
|
|
6665
|
+
LLM_DEFAULT_SESSION
|
|
6666
|
+
);
|
|
6652
6667
|
|
|
6668
|
+
// Capture connectivity before any reconnect attempt. If LLM was already
|
|
6669
|
+
// connected, we must respect current ownership. If it was disconnected,
|
|
6670
|
+
// this flow may reclaim stale owner tags after a fresh connect.
|
|
6653
6671
|
// @ts-ignore - Fix type
|
|
6654
|
-
|
|
6672
|
+
const wasConnected = this.webex.internal.llm.isConnected();
|
|
6673
|
+
|
|
6674
|
+
// Prefer ownership-scoped token read. For disconnected stale-owner reclaim
|
|
6675
|
+
// flows, fallback to ownerless read so initial register can still carry a
|
|
6676
|
+
// token and recover from stale ownership without 401/403 dead-end.
|
|
6677
|
+
// @ts-ignore - Fix type
|
|
6678
|
+
let datachannelToken = this.webex.internal.llm.getDatachannelToken(
|
|
6679
|
+
LLM_DEFAULT_SESSION,
|
|
6680
|
+
this.id
|
|
6681
|
+
);
|
|
6682
|
+
|
|
6683
|
+
if (!datachannelToken && !wasConnected && currentOwner && currentOwner !== this.id) {
|
|
6684
|
+
// @ts-ignore - Fix type
|
|
6685
|
+
datachannelToken = this.webex.internal.llm.getDatachannelToken(LLM_DEFAULT_SESSION);
|
|
6686
|
+
}
|
|
6687
|
+
|
|
6688
|
+
// @ts-ignore - Fix type
|
|
6689
|
+
if (wasConnected) {
|
|
6655
6690
|
if (currentOwner && currentOwner !== this.id) {
|
|
6656
6691
|
// Another meeting owns the live LLM socket. We must not disconnect
|
|
6657
6692
|
// or reconfigure it -- doing so would tear down a session the
|
|
@@ -6684,6 +6719,20 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6684
6719
|
return undefined;
|
|
6685
6720
|
}
|
|
6686
6721
|
|
|
6722
|
+
// Bind refresh handler before registration so interceptor-triggered token
|
|
6723
|
+
// refresh during register POST can resolve a valid handler.
|
|
6724
|
+
// Prefer this meeting as owner, but allow owner-less fallback when a stale
|
|
6725
|
+
// foreign owner tag is present on a disconnected session.
|
|
6726
|
+
const refreshHandlerOwnerMeetingId =
|
|
6727
|
+
currentOwner && currentOwner !== this.id ? undefined : this.id;
|
|
6728
|
+
const shouldAlignRefreshHandlerAfterOwnershipClaim = refreshHandlerOwnerMeetingId !== this.id;
|
|
6729
|
+
// @ts-ignore - Fix type
|
|
6730
|
+
this.webex.internal.llm.setRefreshHandler(
|
|
6731
|
+
() => this.refreshDataChannelToken(),
|
|
6732
|
+
LLM_DEFAULT_SESSION,
|
|
6733
|
+
refreshHandlerOwnerMeetingId
|
|
6734
|
+
);
|
|
6735
|
+
|
|
6687
6736
|
// @ts-ignore - Fix type
|
|
6688
6737
|
return this.webex.internal.llm
|
|
6689
6738
|
.registerAndConnect(url, dataChannelUrl, datachannelToken)
|
|
@@ -6693,7 +6742,33 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6693
6742
|
// subsequent cross-meeting `updateLLMConnection` / `cleanupLLMConneciton`
|
|
6694
6743
|
// calls can detect and skip work that doesn't belong to them.
|
|
6695
6744
|
// @ts-ignore - Fix type
|
|
6696
|
-
this.webex.internal.llm.
|
|
6745
|
+
const {isOwner} = this.webex.internal.llm.resolveSessionOwnership(
|
|
6746
|
+
this.id,
|
|
6747
|
+
LLM_DEFAULT_SESSION
|
|
6748
|
+
);
|
|
6749
|
+
const canReclaimAfterDisconnectedStart = !wasConnected;
|
|
6750
|
+
|
|
6751
|
+
// Refresh handler is pre-bound before registerAndConnect so token
|
|
6752
|
+
// refresh can work even during the registration request itself.
|
|
6753
|
+
if (isOwner || canReclaimAfterDisconnectedStart) {
|
|
6754
|
+
// Record ownership of the default LLM session for this meeting so
|
|
6755
|
+
// subsequent cross-meeting `updateLLMConnection` / `cleanupLLMConneciton`
|
|
6756
|
+
// calls can detect and skip work that doesn't belong to them.
|
|
6757
|
+
// @ts-ignore - Fix type
|
|
6758
|
+
this.webex.internal.llm.setOwnerMeetingId?.(this.id);
|
|
6759
|
+
|
|
6760
|
+
// If we pre-bound refresh ownerlessly (stale-owner reclaim path),
|
|
6761
|
+
// align the handler with the newly claimed owner immediately after
|
|
6762
|
+
// ownership is updated.
|
|
6763
|
+
if (shouldAlignRefreshHandlerAfterOwnershipClaim) {
|
|
6764
|
+
// @ts-ignore - Fix type
|
|
6765
|
+
this.webex.internal.llm.setRefreshHandler(
|
|
6766
|
+
() => this.refreshDataChannelToken(),
|
|
6767
|
+
LLM_DEFAULT_SESSION,
|
|
6768
|
+
this.id
|
|
6769
|
+
);
|
|
6770
|
+
}
|
|
6771
|
+
}
|
|
6697
6772
|
// @ts-ignore - Fix type
|
|
6698
6773
|
this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
|
|
6699
6774
|
// @ts-ignore - Fix type
|
|
@@ -9991,21 +10066,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
9991
10066
|
// again would double-emit MEETING_STOPPED_RECEIVING_TRANSCRIPTION
|
|
9992
10067
|
// because stopTranscription() always fires its trigger.
|
|
9993
10068
|
//
|
|
9994
|
-
// Ownership-aware token clear
|
|
9995
|
-
|
|
9996
|
-
// session. Otherwise we would wipe tokens still in use by another
|
|
9997
|
-
// meeting's active LLM connection.
|
|
9998
|
-
// @ts-ignore - Fix type
|
|
9999
|
-
const currentOwner = this.webex.internal.llm.getOwnerMeetingId();
|
|
10000
|
-
const isOwner = !currentOwner || currentOwner === this.id;
|
|
10001
|
-
|
|
10002
|
-
if (isOwner) {
|
|
10003
|
-
this.clearDataChannelToken();
|
|
10004
|
-
} else {
|
|
10005
|
-
LoggerProxy.logger.info(
|
|
10006
|
-
`Meeting:index#clearMeetingData --> skipping clearDataChannelToken; LLM owned by meeting ${currentOwner}, not ${this.id}`
|
|
10007
|
-
);
|
|
10008
|
-
}
|
|
10069
|
+
// Ownership-aware token clear is encapsulated inside clearDataChannelToken().
|
|
10070
|
+
this.clearDataChannelToken();
|
|
10009
10071
|
|
|
10010
10072
|
await this.cleanupLLMConneciton({throwOnError: false});
|
|
10011
10073
|
};
|
package/src/webinar/index.ts
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
import {WebexPlugin, config} from '@webex/webex-core';
|
|
5
5
|
import uuid from 'uuid';
|
|
6
6
|
import {get} from 'lodash';
|
|
7
|
-
import {DataChannelTokenType} from '@webex/internal-plugin-llm';
|
|
8
7
|
import {
|
|
9
8
|
_ID_,
|
|
10
9
|
HEADERS,
|
|
@@ -14,6 +13,7 @@ import {
|
|
|
14
13
|
SHARE_STATUS,
|
|
15
14
|
DEFAULT_LARGE_SCALE_WEBINAR_ATTENDEE_SEARCH_LIMIT,
|
|
16
15
|
LLM_PRACTICE_SESSION,
|
|
16
|
+
LOCUS_LLM_EVENT,
|
|
17
17
|
} from '../constants';
|
|
18
18
|
|
|
19
19
|
import WebinarCollection from './collection';
|
|
@@ -21,6 +21,19 @@ import LoggerProxy from '../common/logs/logger-proxy';
|
|
|
21
21
|
import MeetingUtil from '../meeting/util';
|
|
22
22
|
import {sanitizeParams} from './utils';
|
|
23
23
|
|
|
24
|
+
const PS_LLM_EVENTS = [
|
|
25
|
+
{
|
|
26
|
+
event: `event:relay.event:${LLM_PRACTICE_SESSION}`,
|
|
27
|
+
listenerKey: 'relay',
|
|
28
|
+
handlerKey: 'processRelayEvent',
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
event: `${LOCUS_LLM_EVENT}:${LLM_PRACTICE_SESSION}`,
|
|
32
|
+
listenerKey: 'locusLLM',
|
|
33
|
+
handlerKey: 'processLocusLLMEvent',
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
|
|
24
37
|
/**
|
|
25
38
|
* @class Webinar
|
|
26
39
|
*/
|
|
@@ -171,28 +184,60 @@ const Webinar = WebexPlugin.extend({
|
|
|
171
184
|
* @returns {Promise<void>}
|
|
172
185
|
*/
|
|
173
186
|
async cleanupPSDataChannel() {
|
|
187
|
+
const {isOwner} = this.webex.internal.llm.resolveSessionOwnership(
|
|
188
|
+
this.meetingId,
|
|
189
|
+
LLM_PRACTICE_SESSION
|
|
190
|
+
);
|
|
191
|
+
this.llmListeners = this.llmListeners || {};
|
|
192
|
+
|
|
174
193
|
if (this._pendingOnlineListener) {
|
|
175
194
|
// @ts-ignore - Fix type
|
|
176
195
|
this.webex.internal.llm.off('online', this._pendingOnlineListener);
|
|
177
196
|
this._pendingOnlineListener = null;
|
|
178
197
|
}
|
|
179
198
|
|
|
180
|
-
|
|
181
|
-
await this.webex.internal.llm.disconnectLLM(
|
|
182
|
-
{
|
|
183
|
-
code: 3050,
|
|
184
|
-
reason: 'done (permanent)',
|
|
185
|
-
},
|
|
186
|
-
LLM_PRACTICE_SESSION
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
if (this._practiceSessionRelayListener) {
|
|
199
|
+
try {
|
|
190
200
|
// @ts-ignore - Fix type
|
|
191
|
-
this.webex.internal.llm.
|
|
192
|
-
|
|
193
|
-
|
|
201
|
+
const disconnected = await this.webex.internal.llm.disconnectLLM(
|
|
202
|
+
{
|
|
203
|
+
code: 3050,
|
|
204
|
+
reason: 'done (permanent)',
|
|
205
|
+
},
|
|
206
|
+
LLM_PRACTICE_SESSION,
|
|
207
|
+
this.meetingId
|
|
194
208
|
);
|
|
209
|
+
|
|
210
|
+
if (!disconnected) {
|
|
211
|
+
LoggerProxy.logger.info(
|
|
212
|
+
`Webinar:index#cleanupPSDataChannel --> skipping disconnect; practice-session LLM is not owned by meeting ${this.meetingId}`
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
} catch (error) {
|
|
216
|
+
// disconnectLLM clears ownership only on success; release a stale owner
|
|
217
|
+
// tag here so other meeting instances can reclaim practice-session LLM.
|
|
218
|
+
if (isOwner) {
|
|
219
|
+
// @ts-ignore - Fix type
|
|
220
|
+
this.webex.internal.llm.setOwnerMeetingId?.(undefined, LLM_PRACTICE_SESSION);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
throw error;
|
|
224
|
+
} finally {
|
|
225
|
+
if (this._practiceSessionRelayListener) {
|
|
226
|
+
// @ts-ignore - Fix type
|
|
227
|
+
this.webex.internal.llm.off(
|
|
228
|
+
`event:relay.event:${LLM_PRACTICE_SESSION}`,
|
|
229
|
+
this._practiceSessionRelayListener
|
|
230
|
+
);
|
|
231
|
+
}
|
|
195
232
|
this._practiceSessionRelayListener = null;
|
|
233
|
+
|
|
234
|
+
for (const {event, listenerKey} of PS_LLM_EVENTS) {
|
|
235
|
+
if (this.llmListeners[listenerKey]) {
|
|
236
|
+
// @ts-ignore - Fix type
|
|
237
|
+
this.webex.internal.llm.off(event, this.llmListeners[listenerKey]);
|
|
238
|
+
this.llmListeners[listenerKey] = null;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
196
241
|
}
|
|
197
242
|
},
|
|
198
243
|
|
|
@@ -213,7 +258,8 @@ const Webinar = WebexPlugin.extend({
|
|
|
213
258
|
|
|
214
259
|
// @ts-ignore
|
|
215
260
|
const cachedToken = this.webex.internal.llm.getDatachannelToken(
|
|
216
|
-
|
|
261
|
+
LLM_PRACTICE_SESSION,
|
|
262
|
+
this.meetingId
|
|
217
263
|
);
|
|
218
264
|
|
|
219
265
|
if (cachedToken) {
|
|
@@ -231,7 +277,8 @@ const Webinar = WebexPlugin.extend({
|
|
|
231
277
|
// @ts-ignore
|
|
232
278
|
this.webex.internal.llm.setDatachannelToken(
|
|
233
279
|
datachannelToken,
|
|
234
|
-
dataChannelTokenType ||
|
|
280
|
+
dataChannelTokenType || LLM_PRACTICE_SESSION,
|
|
281
|
+
this.meetingId
|
|
235
282
|
);
|
|
236
283
|
|
|
237
284
|
return datachannelToken;
|
|
@@ -252,11 +299,17 @@ const Webinar = WebexPlugin.extend({
|
|
|
252
299
|
* @returns {Promise}
|
|
253
300
|
*/
|
|
254
301
|
async updatePSDataChannel() {
|
|
302
|
+
this.llmListeners = this.llmListeners || {};
|
|
303
|
+
|
|
255
304
|
this._updatePSDataChannelSequence = (this._updatePSDataChannelSequence || 0) + 1;
|
|
256
305
|
const invocationSequence = this._updatePSDataChannelSequence;
|
|
257
306
|
|
|
258
307
|
const meeting = this.getValidatedWebinarMeeting();
|
|
259
308
|
const isPracticeSession = meeting?.isJoined() && this.isJoinPracticeSessionDataChannel();
|
|
309
|
+
const {currentOwner, isOwner} = this.webex.internal.llm.resolveSessionOwnership(
|
|
310
|
+
this.meetingId,
|
|
311
|
+
LLM_PRACTICE_SESSION
|
|
312
|
+
);
|
|
260
313
|
|
|
261
314
|
if (!isPracticeSession) {
|
|
262
315
|
await this.cleanupPSDataChannel();
|
|
@@ -264,13 +317,22 @@ const Webinar = WebexPlugin.extend({
|
|
|
264
317
|
return undefined;
|
|
265
318
|
}
|
|
266
319
|
|
|
320
|
+
if (!isOwner) {
|
|
321
|
+
LoggerProxy.logger.info(
|
|
322
|
+
`Webinar:index#updatePSDataChannel --> skipping; practice-session LLM owned by meeting ${currentOwner}, not ${this.meetingId}`
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
return undefined;
|
|
326
|
+
}
|
|
327
|
+
|
|
267
328
|
// @ts-ignore - Fix type
|
|
268
329
|
const {url = undefined, info: {practiceSessionDatachannelUrl = undefined} = {}} =
|
|
269
330
|
meeting?.locusInfo || {};
|
|
270
331
|
|
|
271
332
|
// @ts-ignore
|
|
272
333
|
let practiceSessionDatachannelToken = this.webex.internal.llm.getDatachannelToken(
|
|
273
|
-
|
|
334
|
+
LLM_PRACTICE_SESSION,
|
|
335
|
+
this.meetingId
|
|
274
336
|
);
|
|
275
337
|
|
|
276
338
|
const isCaptionBoxOn = this.webex.internal.voicea.getIsCaptionBoxOn();
|
|
@@ -347,6 +409,30 @@ const Webinar = WebexPlugin.extend({
|
|
|
347
409
|
practiceSessionDatachannelToken = refreshedPracticeSessionToken;
|
|
348
410
|
}
|
|
349
411
|
|
|
412
|
+
const {currentOwner: currentOwnerBeforeConnect, isOwner: isOwnerBeforeConnect} =
|
|
413
|
+
this.webex.internal.llm.resolveSessionOwnership(this.meetingId, LLM_PRACTICE_SESSION);
|
|
414
|
+
|
|
415
|
+
if (!isOwnerBeforeConnect) {
|
|
416
|
+
LoggerProxy.logger.info(
|
|
417
|
+
`Webinar:index#updatePSDataChannel --> skipping pre-connect owner write; practice-session LLM owned by meeting ${currentOwnerBeforeConnect}, not ${this.meetingId}`
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
return undefined;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Ensure refresh for practice datachannel requests is routed to this
|
|
424
|
+
// meeting only when we are actually about to connect the practice session.
|
|
425
|
+
// This avoids claiming ownership in flows that return early (e.g. missing
|
|
426
|
+
// practiceSessionDatachannelUrl or waiting for default session online).
|
|
427
|
+
// @ts-ignore - Fix type
|
|
428
|
+
this.webex.internal.llm.setRefreshHandler(
|
|
429
|
+
() => meeting.refreshDataChannelToken(),
|
|
430
|
+
LLM_PRACTICE_SESSION,
|
|
431
|
+
this.meetingId
|
|
432
|
+
);
|
|
433
|
+
// @ts-ignore - Fix type
|
|
434
|
+
this.webex.internal.llm.setOwnerMeetingId?.(this.meetingId, LLM_PRACTICE_SESSION);
|
|
435
|
+
|
|
350
436
|
// @ts-ignore - Fix type
|
|
351
437
|
return this.webex.internal.llm
|
|
352
438
|
.registerAndConnect(
|
|
@@ -356,22 +442,30 @@ const Webinar = WebexPlugin.extend({
|
|
|
356
442
|
LLM_PRACTICE_SESSION
|
|
357
443
|
)
|
|
358
444
|
.then((registerAndConnectResult) => {
|
|
359
|
-
|
|
445
|
+
const {currentOwner: currentOwnerAfterConnect, isOwner: isOwnerAfterConnect} =
|
|
446
|
+
this.webex.internal.llm.resolveSessionOwnership(this.meetingId, LLM_PRACTICE_SESSION);
|
|
447
|
+
|
|
448
|
+
if (this.meetingId && isOwnerAfterConnect) {
|
|
449
|
+
// @ts-ignore - Fix type
|
|
450
|
+
this.webex.internal.llm.setOwnerMeetingId?.(this.meetingId, LLM_PRACTICE_SESSION);
|
|
451
|
+
} else {
|
|
452
|
+
LoggerProxy.logger.info(
|
|
453
|
+
`Webinar:index#updatePSDataChannel --> skipping post-connect owner write; practice-session LLM owned by meeting ${currentOwnerAfterConnect}, not ${this.meetingId}`
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Track the exact listener references so cleanupPSDataChannel can
|
|
360
458
|
// unsubscribe deterministically, even if the meeting can no longer
|
|
361
459
|
// be resolved at cleanup time.
|
|
362
|
-
|
|
460
|
+
for (const {event, listenerKey, handlerKey} of PS_LLM_EVENTS) {
|
|
461
|
+
if (this.llmListeners[listenerKey]) {
|
|
462
|
+
// @ts-ignore - Fix type
|
|
463
|
+
this.webex.internal.llm.off(event, this.llmListeners[listenerKey]);
|
|
464
|
+
}
|
|
465
|
+
this.llmListeners[listenerKey] = meeting?.[handlerKey];
|
|
363
466
|
// @ts-ignore - Fix type
|
|
364
|
-
this.webex.internal.llm.
|
|
365
|
-
`event:relay.event:${LLM_PRACTICE_SESSION}`,
|
|
366
|
-
this._practiceSessionRelayListener
|
|
367
|
-
);
|
|
467
|
+
this.webex.internal.llm.on(event, this.llmListeners[listenerKey]);
|
|
368
468
|
}
|
|
369
|
-
this._practiceSessionRelayListener = meeting?.processRelayEvent;
|
|
370
|
-
// @ts-ignore - Fix type
|
|
371
|
-
this.webex.internal.llm.on(
|
|
372
|
-
`event:relay.event:${LLM_PRACTICE_SESSION}`,
|
|
373
|
-
this._practiceSessionRelayListener
|
|
374
|
-
);
|
|
375
469
|
// @ts-ignore - Fix type
|
|
376
470
|
this.webex.internal.voicea?.announce?.();
|
|
377
471
|
if (isCaptionBoxOn) {
|
|
@@ -382,6 +476,23 @@ const Webinar = WebexPlugin.extend({
|
|
|
382
476
|
);
|
|
383
477
|
|
|
384
478
|
return Promise.resolve(registerAndConnectResult);
|
|
479
|
+
})
|
|
480
|
+
.catch((error) => {
|
|
481
|
+
const {
|
|
482
|
+
currentOwner: currentOwnerAfterRegisterFailure,
|
|
483
|
+
isOwner: isOwnerAfterRegisterFailure,
|
|
484
|
+
} = this.webex.internal.llm.resolveSessionOwnership(this.meetingId, LLM_PRACTICE_SESSION);
|
|
485
|
+
|
|
486
|
+
if (isOwnerAfterRegisterFailure) {
|
|
487
|
+
// @ts-ignore - Fix type
|
|
488
|
+
this.webex.internal.llm.setOwnerMeetingId?.(undefined, LLM_PRACTICE_SESSION);
|
|
489
|
+
} else {
|
|
490
|
+
LoggerProxy.logger.info(
|
|
491
|
+
`Webinar:index#updatePSDataChannel --> skipping failure owner release; practice-session LLM owned by meeting ${currentOwnerAfterRegisterFailure}, not ${this.meetingId}`
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
throw error;
|
|
385
496
|
});
|
|
386
497
|
},
|
|
387
498
|
|