@webex/plugin-meetings 3.12.0-next.65 → 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 +219 -146
- 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 +114 -16
- 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 +197 -14
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,
|
|
@@ -185,6 +184,10 @@ const Webinar = WebexPlugin.extend({
|
|
|
185
184
|
* @returns {Promise<void>}
|
|
186
185
|
*/
|
|
187
186
|
async cleanupPSDataChannel() {
|
|
187
|
+
const {isOwner} = this.webex.internal.llm.resolveSessionOwnership(
|
|
188
|
+
this.meetingId,
|
|
189
|
+
LLM_PRACTICE_SESSION
|
|
190
|
+
);
|
|
188
191
|
this.llmListeners = this.llmListeners || {};
|
|
189
192
|
|
|
190
193
|
if (this._pendingOnlineListener) {
|
|
@@ -193,20 +196,47 @@ const Webinar = WebexPlugin.extend({
|
|
|
193
196
|
this._pendingOnlineListener = null;
|
|
194
197
|
}
|
|
195
198
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
199
|
+
try {
|
|
200
|
+
// @ts-ignore - Fix type
|
|
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
|
|
208
|
+
);
|
|
204
209
|
|
|
205
|
-
|
|
206
|
-
|
|
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) {
|
|
207
219
|
// @ts-ignore - Fix type
|
|
208
|
-
this.webex.internal.llm.
|
|
209
|
-
|
|
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
|
+
}
|
|
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
|
+
}
|
|
210
240
|
}
|
|
211
241
|
}
|
|
212
242
|
},
|
|
@@ -228,7 +258,8 @@ const Webinar = WebexPlugin.extend({
|
|
|
228
258
|
|
|
229
259
|
// @ts-ignore
|
|
230
260
|
const cachedToken = this.webex.internal.llm.getDatachannelToken(
|
|
231
|
-
|
|
261
|
+
LLM_PRACTICE_SESSION,
|
|
262
|
+
this.meetingId
|
|
232
263
|
);
|
|
233
264
|
|
|
234
265
|
if (cachedToken) {
|
|
@@ -246,7 +277,8 @@ const Webinar = WebexPlugin.extend({
|
|
|
246
277
|
// @ts-ignore
|
|
247
278
|
this.webex.internal.llm.setDatachannelToken(
|
|
248
279
|
datachannelToken,
|
|
249
|
-
dataChannelTokenType ||
|
|
280
|
+
dataChannelTokenType || LLM_PRACTICE_SESSION,
|
|
281
|
+
this.meetingId
|
|
250
282
|
);
|
|
251
283
|
|
|
252
284
|
return datachannelToken;
|
|
@@ -274,6 +306,10 @@ const Webinar = WebexPlugin.extend({
|
|
|
274
306
|
|
|
275
307
|
const meeting = this.getValidatedWebinarMeeting();
|
|
276
308
|
const isPracticeSession = meeting?.isJoined() && this.isJoinPracticeSessionDataChannel();
|
|
309
|
+
const {currentOwner, isOwner} = this.webex.internal.llm.resolveSessionOwnership(
|
|
310
|
+
this.meetingId,
|
|
311
|
+
LLM_PRACTICE_SESSION
|
|
312
|
+
);
|
|
277
313
|
|
|
278
314
|
if (!isPracticeSession) {
|
|
279
315
|
await this.cleanupPSDataChannel();
|
|
@@ -281,13 +317,22 @@ const Webinar = WebexPlugin.extend({
|
|
|
281
317
|
return undefined;
|
|
282
318
|
}
|
|
283
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
|
+
|
|
284
328
|
// @ts-ignore - Fix type
|
|
285
329
|
const {url = undefined, info: {practiceSessionDatachannelUrl = undefined} = {}} =
|
|
286
330
|
meeting?.locusInfo || {};
|
|
287
331
|
|
|
288
332
|
// @ts-ignore
|
|
289
333
|
let practiceSessionDatachannelToken = this.webex.internal.llm.getDatachannelToken(
|
|
290
|
-
|
|
334
|
+
LLM_PRACTICE_SESSION,
|
|
335
|
+
this.meetingId
|
|
291
336
|
);
|
|
292
337
|
|
|
293
338
|
const isCaptionBoxOn = this.webex.internal.voicea.getIsCaptionBoxOn();
|
|
@@ -364,6 +409,30 @@ const Webinar = WebexPlugin.extend({
|
|
|
364
409
|
practiceSessionDatachannelToken = refreshedPracticeSessionToken;
|
|
365
410
|
}
|
|
366
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
|
+
|
|
367
436
|
// @ts-ignore - Fix type
|
|
368
437
|
return this.webex.internal.llm
|
|
369
438
|
.registerAndConnect(
|
|
@@ -373,6 +442,18 @@ const Webinar = WebexPlugin.extend({
|
|
|
373
442
|
LLM_PRACTICE_SESSION
|
|
374
443
|
)
|
|
375
444
|
.then((registerAndConnectResult) => {
|
|
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
|
+
|
|
376
457
|
// Track the exact listener references so cleanupPSDataChannel can
|
|
377
458
|
// unsubscribe deterministically, even if the meeting can no longer
|
|
378
459
|
// be resolved at cleanup time.
|
|
@@ -395,6 +476,23 @@ const Webinar = WebexPlugin.extend({
|
|
|
395
476
|
);
|
|
396
477
|
|
|
397
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;
|
|
398
496
|
});
|
|
399
497
|
},
|
|
400
498
|
|