-
Notifications
You must be signed in to change notification settings - Fork 396
fix(datachannel): checkpoint lobby token refresh and llm resubscribe … #4818
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1348a53
50a55c2
6d70cb8
c5a1f77
16219de
105eb82
9d20822
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3734,7 +3734,7 @@ export default class Meeting extends StatelessWebexPlugin { | |
| }); | ||
| this.updateLLMConnection(); | ||
| }); | ||
| this.locusInfo.on(LOCUSINFO.EVENTS.SELF_ADMITTED_GUEST, async (payload) => { | ||
| this.locusInfo.on(LOCUSINFO.EVENTS.SELF_ADMITTED_GUEST, (payload) => { | ||
| this.stopKeepAlive(); | ||
|
|
||
| if (payload) { | ||
|
|
@@ -3760,6 +3760,15 @@ export default class Meeting extends StatelessWebexPlugin { | |
| }); | ||
| } | ||
| this.rtcMetrics?.sendNextMetrics(); | ||
|
|
||
| this.ensureDefaultDatachannelTokenAfterAdmit().catch((error) => { | ||
| LoggerProxy.logger.warn( | ||
| `Meeting:index#setUpLocusInfoSelfListener --> failed post-admit token prefetch flow: ${ | ||
| error?.message || String(error) | ||
| }` | ||
| ); | ||
| }); | ||
|
|
||
| this.updateLLMConnection(); | ||
| }); | ||
|
|
||
|
|
@@ -5960,6 +5969,30 @@ export default class Meeting extends StatelessWebexPlugin { | |
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Restores LLM subchannel subscriptions after reconnect when captions are active. | ||
| * @returns {void} | ||
| */ | ||
| private restoreLLMSubscriptionsIfNeeded(): void { | ||
| try { | ||
| // @ts-ignore | ||
| const isCaptionBoxOn = this.webex.internal.voicea?.getIsCaptionBoxOn?.(); | ||
|
|
||
| if (!isCaptionBoxOn) { | ||
| return; | ||
| } | ||
|
|
||
| // @ts-ignore | ||
| this.webex.internal.voicea.updateSubchannelSubscriptions({subscribe: ['transcription']}); | ||
| } catch (error) { | ||
| const msg = error?.message || String(error); | ||
|
|
||
| LoggerProxy.logger.warn( | ||
| `Meeting:index#restoreLLMSubscriptionsIfNeeded --> failed to restore subscriptions after LLM online: ${msg}` | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * This is a callback for the LLM event that is triggered when it comes online | ||
| * This method in turn will trigger an event to the developers that the LLM is connected | ||
|
|
@@ -5968,8 +6001,8 @@ export default class Meeting extends StatelessWebexPlugin { | |
| * @returns {null} | ||
| */ | ||
| private handleLLMOnline = (): void => { | ||
| // @ts-ignore | ||
| this.webex.internal.llm.off('online', this.handleLLMOnline); | ||
| this.restoreLLMSubscriptionsIfNeeded(); | ||
|
|
||
|
Comment on lines
6003
to
+6005
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
After this change, Useful? React with 👍 / 👎. |
||
| Trigger.trigger( | ||
| this, | ||
| { | ||
|
|
@@ -6200,6 +6233,8 @@ export default class Meeting extends StatelessWebexPlugin { | |
| this.saveDataChannelToken(join); | ||
| // @ts-ignore - config coming from registerPlugin | ||
| if (this.config.enableAutomaticLLM) { | ||
| // @ts-ignore | ||
| this.webex.internal.llm.off('online', this.handleLLMOnline); | ||
| // @ts-ignore | ||
| this.webex.internal.llm.on('online', this.handleLLMOnline); | ||
| this.updateLLMConnection() | ||
|
|
@@ -6343,6 +6378,52 @@ export default class Meeting extends StatelessWebexPlugin { | |
| } | ||
| } | ||
|
|
||
| /** | ||
| * Ensures default-session data channel token exists after lobby admission. | ||
| * Some lobby users do not receive a token until they are admitted. | ||
| * @returns {Promise<boolean>} true when a new token is fetched and cached | ||
| */ | ||
| private async ensureDefaultDatachannelTokenAfterAdmit(): Promise<boolean> { | ||
| try { | ||
| // @ts-ignore | ||
| const datachannelToken = this.webex.internal.llm.getDatachannelToken(); | ||
| // @ts-ignore | ||
| const isDataChannelTokenEnabled = await this.webex.internal.llm.isDataChannelTokenEnabled(); | ||
|
|
||
| if (!isDataChannelTokenEnabled || datachannelToken) { | ||
| return false; | ||
| } | ||
|
|
||
| const response = await this.meetingRequest.fetchDatachannelToken({ | ||
| locusUrl: this.locusUrl, | ||
| requestingParticipantId: this.members.selfId, | ||
| isPracticeSession: false, | ||
| }); | ||
| const fetchedDatachannelToken = response?.body?.datachannelToken; | ||
|
|
||
| if (!fetchedDatachannelToken) { | ||
| return false; | ||
| } | ||
|
|
||
| // @ts-ignore | ||
| this.webex.internal.llm.setDatachannelToken( | ||
| fetchedDatachannelToken, | ||
| DataChannelTokenType.Default | ||
| ); | ||
|
Comment on lines
+6409
to
+6412
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Useful? React with 👍 / 👎. |
||
|
|
||
| return true; | ||
| } catch (error) { | ||
| const msg = error?.message || String(error); | ||
|
|
||
| LoggerProxy.logger.warn( | ||
| `Meeting:index#ensureDefaultDatachannelTokenAfterAdmit --> failed to proactively fetch default data channel token after admit: ${msg}`, | ||
| {statusCode: error?.statusCode} | ||
| ); | ||
|
|
||
| return false; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Connects to low latency mercury and reconnects if the address has changed | ||
| * It will also disconnect if called when the meeting has ended | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -154,12 +154,63 @@ const Webinar = WebexPlugin.extend({ | |
| ); | ||
| }, | ||
|
|
||
| /** | ||
| * Ensures practice-session token exists before registering the practice LLM channel. | ||
| * @param {object} meeting | ||
| * @returns {Promise<string|undefined>} | ||
| */ | ||
| async ensurePracticeSessionDatachannelToken(meeting) { | ||
| // @ts-ignore | ||
| const isDataChannelTokenEnabled = await this.webex.internal.llm.isDataChannelTokenEnabled(); | ||
|
|
||
| if (!isDataChannelTokenEnabled) { | ||
| return undefined; | ||
| } | ||
|
|
||
| // @ts-ignore | ||
| const cachedToken = this.webex.internal.llm.getDatachannelToken( | ||
| DataChannelTokenType.PracticeSession | ||
| ); | ||
|
|
||
| if (cachedToken) { | ||
| return cachedToken; | ||
| } | ||
|
|
||
| try { | ||
| const refreshResponse = await meeting.refreshDataChannelToken(); | ||
| const {datachannelToken, dataChannelTokenType} = refreshResponse?.body ?? {}; | ||
|
|
||
| if (!datachannelToken) { | ||
| return undefined; | ||
| } | ||
|
|
||
| // @ts-ignore | ||
| this.webex.internal.llm.setDatachannelToken( | ||
| datachannelToken, | ||
| dataChannelTokenType || DataChannelTokenType.PracticeSession | ||
| ); | ||
|
Comment on lines
+188
to
+191
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Useful? React with 👍 / 👎. |
||
|
|
||
| return datachannelToken; | ||
| } catch (error) { | ||
| LoggerProxy.logger.warn( | ||
| `Webinar:index#ensurePracticeSessionDatachannelToken --> failed to proactively refresh practice-session token: ${ | ||
| error?.message || String(error) | ||
| }` | ||
| ); | ||
|
|
||
| return undefined; | ||
| } | ||
| }, | ||
|
|
||
| /** | ||
| * Connects to low latency mercury and reconnects if the address has changed | ||
| * It will also disconnect if called when the meeting has ended | ||
| * @returns {Promise} | ||
| */ | ||
| async updatePSDataChannel() { | ||
| this._updatePSDataChannelSequence = (this._updatePSDataChannelSequence || 0) + 1; | ||
| const invocationSequence = this._updatePSDataChannelSequence; | ||
|
|
||
| const meeting = this.webex.meetings.getMeetingByType(_ID_, this.meetingId); | ||
| const isPracticeSession = meeting?.isJoined() && this.isJoinPracticeSessionDataChannel(); | ||
|
|
||
|
|
@@ -174,7 +225,7 @@ const Webinar = WebexPlugin.extend({ | |
| meeting?.locusInfo || {}; | ||
|
|
||
| // @ts-ignore | ||
| const practiceSessionDatachannelToken = this.webex.internal.llm.getDatachannelToken( | ||
| let practiceSessionDatachannelToken = this.webex.internal.llm.getDatachannelToken( | ||
| DataChannelTokenType.PracticeSession | ||
| ); | ||
|
|
||
|
|
@@ -229,6 +280,29 @@ const Webinar = WebexPlugin.extend({ | |
| this._pendingOnlineListener = null; | ||
| } | ||
|
|
||
| const refreshedPracticeSessionToken = await this.ensurePracticeSessionDatachannelToken(meeting); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
In Useful? React with 👍 / 👎. |
||
|
|
||
| const latestPracticeSessionDatachannelUrl = get( | ||
| meeting, | ||
| 'locusInfo.info.practiceSessionDatachannelUrl' | ||
| ); | ||
| const isStillPracticeSession = meeting?.isJoined() && this.isJoinPracticeSessionDataChannel(); | ||
|
|
||
| // Skip stale invocations after async refresh to avoid reconnecting a session | ||
| // that was already updated/cleaned by a newer state transition. | ||
| if ( | ||
| invocationSequence !== this._updatePSDataChannelSequence || | ||
| !isStillPracticeSession || | ||
| !latestPracticeSessionDatachannelUrl || | ||
| latestPracticeSessionDatachannelUrl !== practiceSessionDatachannelUrl | ||
| ) { | ||
| return undefined; | ||
|
Comment on lines
+297
to
+299
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The post-refresh stale check returns when Useful? React with 👍 / 👎. |
||
| } | ||
|
|
||
| if (refreshedPracticeSessionToken) { | ||
| practiceSessionDatachannelToken = refreshedPracticeSessionToken; | ||
| } | ||
|
|
||
| // @ts-ignore - Fix type | ||
| return this.webex.internal.llm | ||
|
Comment on lines
+283
to
307
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Useful? React with 👍 / 👎. |
||
| .registerAndConnect( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
restoreLLMSubscriptionsIfNeeded()wrapsupdateSubchannelSubscriptions()in a synchronoustry/catch, butupdateSubchannelSubscriptionsis async and its promise is not awaited or.catched here. If that promise rejects (for example, during reconnect races), the rejection bypasses this catch and becomes unhandled, so the intended warning log and graceful recovery path do not run.Useful? React with 👍 / 👎.