feat(datachannel): add update subchannel subscriptions function#4749
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c3c22b4ce0
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| public updateSubchannelSubscriptions = ({ | ||
| subscribe = [], | ||
| unsubscribe = [], | ||
| }: { |
There was a problem hiding this comment.
Handle undefined options in updateSubchannelSubscriptions
This destructuring requires an argument object, so calling updateSubchannelSubscriptions() from JavaScript (or any untyped call path) throws a runtime TypeError before the isConnected() guard runs. Because the method already provides defaults for subscribe/unsubscribe, callers reasonably expect the no-arg form to behave like an empty update rather than crash; adding a default for the whole parameter (e.g., {}) avoids this production failure mode.
Useful? React with 👍 / 👎.
|
This pull request is automatically being deployed by Amplify Hosting (learn more). |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d04435fcf4
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const connectUrl = this.isDataChannelTokenEnabled() | ||
| ? LLMChannel.buildUrlWithAwareSubchannels(this.webSocketUrl, AWARE_DATA_CHANNEL) |
There was a problem hiding this comment.
Await token feature gate before selecting connect URL
isDataChannelTokenEnabled() returns a Promise<boolean>, but it is used directly in a ternary, so the condition is always truthy and buildUrlWithAwareSubchannels() runs even when the developer feature flag resolves to false. In environments where the flag is disabled, clients will still connect with subscriptionAwareSubchannels appended, which defeats the intended rollout gate and can change server behavior for all connections on this path.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 84acaf49a0
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| sessionData.datachannelToken = datachannelToken; | ||
| this.connections.set(sessionId, sessionData); | ||
|
|
||
| const connectUrl = isDataChannelTokenEnabled |
There was a problem hiding this comment.
Resolve feature gate before URL selection
In registerAndConnect, the ternary reads isDataChannelTokenEnabled even though that identifier is not defined in this scope, so calling this method with valid locusUrl/datachannelUrl will throw a runtime ReferenceError before connect() runs. Because no-undef is disabled for TypeScript in this repo, this can slip through linting and break all LLM connection attempts on this path.
Useful? React with 👍 / 👎.
| const connectUrl = isDataChannelTokenEnabled | ||
| ? LLMChannel.buildUrlWithAwareSubchannels(this.webSocketUrl, AWARE_DATA_CHANNEL) | ||
| : this.webSocketUrl; |
There was a problem hiding this comment.
Pass subscription-aware URL into connect
This block computes connectUrl with subscriptionAwareSubchannels, but the method still calls connect(sessionData.webSocketUrl, sessionId), so the new query parameter is never actually used. In environments expecting this flag-controlled URL decoration, clients will continue connecting with the unmodified WebSocket URL and the new feature will not take effect.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9ac617cb11
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| @@ -0,0 +1,16 @@ | |||
| import * as jose from 'jose'; | |||
There was a problem hiding this comment.
Declare jose in plugin-meetings runtime dependencies
isJwtTokenExpired now imports jose, but @webex/plugin-meetings does not declare jose in its dependencies (it only appears in packages/byods/package.json), so published consumers that install meetings without byods can hit Cannot find module 'jose' as soon as this interceptor module is loaded. Please add jose to packages/@webex/plugin-meetings/package.json so this new runtime import is always resolvable.
Useful? React with 👍 / 👎.
| return this.connect(sessionData.webSocketUrl, sessionId); | ||
| const isDataChannelTokenEnabled = await this.isDataChannelTokenEnabled(); | ||
|
|
||
| const connectUrl = isDataChannelTokenEnabled |
There was a problem hiding this comment.
Should be isDataChannelTokenEnabled && datachannelToken, not noly the toggle is on but also the token is used when registration
| * @param {string[]} options.unsubscribe Sub‑channels to unsubscribe from. | ||
| * @returns {void} | ||
| */ | ||
| public updateSubchannelSubscriptions = ({ |
There was a problem hiding this comment.
When the function is called?
There was a problem hiding this comment.
💡 Codex Review
webex.internal, not this.internal
static create() captures this.internal.llm, but interceptors are instantiated via Reflect.apply(interceptor, this, []) on each plugin instance, and WebexPlugin exposes the root SDK object as this.webex rather than this.internal. On request paths that carry Data-Channel-Auth-Token, these callbacks can therefore throw when they try to dereference this.internal.llm, which means the new refresh logic never gets a chance to update the token before the request/retry runs.
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const dataChannelToken = this.webex.internal.llm.getDatachannelToken('default'); | ||
| // @ts-ignore | ||
| if (!this.webex.internal.llm.isConnected() || !isDataChannelTokenEnabled || !dataChannelToken) |
There was a problem hiding this comment.
Use the active LLM session for subchannel subscriptions
updateSubchannelSubscriptions() hard-codes the default LLM session (getDatachannelToken('default'), isConnected(), llm.socket, getDatachannelUrl()), but Voicea already routes publish traffic through LLM_PRACTICE_SESSION when that is the active connection. In webinar practice-session flows where only the practice-session data channel is connected, this method now returns early or targets the wrong socket, so requestTurnOnCaptions() never applies the new subchannelSubscriptionRequest and transcription will not be subscribed for that session.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c853f0c83e
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| this.updateSubchannelSubscriptions({subscribe: ['transcription']}); | ||
| this.announce(); |
There was a problem hiding this comment.
Await subchannel subscription before announcing
When data-channel-with-jwt-token is enabled, updateSubchannelSubscriptions() does not send synchronously: it awaits isDataChannelTokenEnabled() before emitting subchannelSubscriptionRequest. Because it is fire-and-forget here, announce() on the next line always runs first, so Voicea can start publishing transcription before this client has subscribed on a subscription-aware socket. That drops the initial caption events right after turning captions on.
Useful? React with 👍 / 👎.
|
|
||
| this.areCaptionsEnabled = true; | ||
| this.captionStatus = TURN_ON_CAPTION_STATUS.ENABLED; | ||
| this.updateSubchannelSubscriptions({subscribe: ['transcription']}); |
There was a problem hiding this comment.
Re-subscribe after opening a replacement LLM socket
This is the only place the new subchannelSubscriptionRequest is triggered. Later LLM connections created by Mercury reconnects or by Webinar.updatePSDataChannel() bring up a fresh websocket, but they only call voicea.announce() and never re-run updateSubchannelSubscriptions(). Since the subscription request is sent over the websocket, it is scoped to that socket; after a network flap or practice-session handoff, the replacement connection stays unsubscribed to transcription until the user toggles captions again.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5cc969c481
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
|
||
| const finalToken = currentToken ?? practiceSessionDatachannelToken; | ||
|
|
||
| const isCaptionEnabled = this.webex.internal.voicea.getIsCaptionEnabled(); |
There was a problem hiding this comment.
Derive practice-session resubscribe from current meeting state
getIsCaptionEnabled() now drives whether updatePSDataChannel() replays a transcription subscription, but the new VoiceaChannel.isCaptionEnabled flag is only set in updateSubchannelSubscriptionsAndSyncCaptionState() and is never cleared in voicea.deregisterEvents(). Because VoiceaChannel is a singleton internal plugin, once a user enables captions in one meeting/rejoin, later webinar practice-session reconnects will still enter this branch even when captions are off in the current meeting, leaking caption opt-in across sessions and potentially subscribing a fresh meeting to transcription unexpectedly.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 43c801c06b
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if (isCaptionBoxOn) { | ||
| this.webex.internal.voicea.updateSubchannelSubscriptions({subscribe: ['transcription']}); |
There was a problem hiding this comment.
Derive practice-session resubscribe from live transcribe state
Fresh evidence after the earlier deregisterEvents() fix: this branch still keys resubscribe off voicea.getIsCaptionBoxOn(), but that flag is only updated when this client turns captions on. In the same meeting, meeting/index.ts's CONTROLS_MEETING_TRANSCRIBE_UPDATED handler only emits MEETING_STOPPED_RECEIVING_TRANSCRIPTION when {transcribing:false} and never clears the Voicea flag, so if captions are later disabled by the host and the practice-session socket reconnects, updatePSDataChannel() will still re-subscribe transcription on the new socket even though the current locus controls say transcription is off.
Useful? React with 👍 / 👎.
| this.webex.internal.llm.socket.send({ | ||
| id: `${this.seqNum}`, | ||
| type: 'subchannelSubscriptionRequest', | ||
| data: { | ||
| // @ts-ignore | ||
| datachannelUri: this.webex.internal.llm.getDatachannelUrl(), | ||
| subscribe, | ||
| unsubscribe, | ||
| }, | ||
| trackingId: `${config.trackingIdPrefix}_${uuid.v4().toString()}`, | ||
| }); |
There was a problem hiding this comment.
Return the Mercury send promise from subchannel updates
updateSubchannelSubscriptions() is declared async, but it drops the promise from llm.socket.send(...). Mercury's send() resolves asynchronously and rejects with INVALID_STATE_ERROR when the socket is no longer open, so any close/reconnect race after the feature-flag await will surface as an unhandled rejection while callers still observe await updateSubchannelSubscriptions() as success. That makes reconnect resubscribe logic believe the subscription request was sent even when it never left the client.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2a26e82a0e
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| this.areCaptionsEnabled = true; | ||
| this.captionStatus = TURN_ON_CAPTION_STATUS.ENABLED; | ||
| this.announce(); | ||
| this.updateSubchannelSubscriptionsAndSyncCaptionState({subscribe: ['transcription']}, true); |
There was a problem hiding this comment.
Send an unsubscribe when transcription is turned off
This new subchannel flow only ever opts the socket into transcription: this call and webinar/index.ts:223-226 both send subscribe:['transcription'], but there is no production path that passes unsubscribe:['transcription'], and meeting/index.ts#stopTranscription() still just removes listeners. On a subscription-aware socket, once a user has enabled captions once, a host stop/start cycle or any local stop path that leaves the websocket open keeps that socket subscribed, so captions will start flowing again on the same connection even though the user has turned them off.
Useful? React with 👍 / 👎.
2a26e82 to
43c801c
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 43c801c06b
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if (isJwtTokenExpired(token)) { | ||
| try { | ||
| const newToken = await this._refreshDataChannelToken(); |
There was a problem hiding this comment.
Pass request context into the pre-send token refresh
onRequest() now refreshes any Data-Channel-Auth-Token that is within the 30s buffer, but _refreshDataChannelToken() still has no request/session context: DataChannelAuthTokenInterceptor.create() delegates to the single global LLM refresh handler, and Meeting.refreshDataChannelToken() chooses the token type from current meeting/practice-session state rather than from the request being refreshed. In practice-session webinars (default + practice sockets) or when two meetings are open, this can rewrite a healthy default-session registration request with the practice-session/other-meeting token before the server has even rejected the original header, causing otherwise valid reconnects to fail.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8cadca8ca1
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const isDataChannelTokenEnabled = await this.webex.internal.llm.isDataChannelTokenEnabled(); | ||
| // @ts-ignore | ||
| const dataChannelToken = this.webex.internal.llm.isLLMWithDataChannelToken(); | ||
| // @ts-ignore | ||
| if (!this.isLLMConnected() || !isDataChannelTokenEnabled || !dataChannelToken) return; |
There was a problem hiding this comment.
Check the publish session, not any session, for a JWT token
isLLMWithDataChannelToken() only asks whether any LLM session has a stored token, but updateSubchannelSubscriptions() sends on the current publish transport. In webinar handoffs we can still connect the practice-session socket with finalToken === undefined (packages/@webex/plugin-meetings/src/webinar/index.ts), while the default session remains connected with its own token; this branch then sends a subchannelSubscriptionRequest on a practice-session socket that was never opened with subscriptionAwareSubchannels. In that mixed-token state, the call looks successful but captions never get subscribed on the practice-session connection.
Useful? React with 👍 / 👎.
| this.webex.internal.voicea?.announce?.(); | ||
| if (isCaptionBoxOn) { | ||
| this.webex.internal.voicea.updateSubchannelSubscriptions({subscribe: ['transcription']}); |
There was a problem hiding this comment.
Await practice-session subchannel subscribe before announce
When captions are already on and updatePSDataChannel() reconnects the practice-session socket, announce() runs before the new subscription request is even started. updateSubchannelSubscriptions() is asynchronous because it awaits the feature gate in packages/@webex/internal-plugin-voicea/src/voicea.ts:647-651, so on JWT-enabled data channels the fresh socket can begin receiving Voicea traffic before transcription has been subscribed. That drops the first caption events after a practice-session reconnect until a later message arrives.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b1f199ff0d
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| 'llm#refreshDataChannelToken --> LLM refreshHandler is not set, skipping token refresh' | ||
| ); | ||
|
|
||
| return null; |
There was a problem hiding this comment.
Preserve refreshDataChannelToken rejection semantics
refreshDataChannelToken() now resolves null when no handler is set or the refresh handler fails, but the interceptor still consumes it as const {body} = await this.internal.llm.refreshDataChannelToken() in packages/@webex/plugin-meetings/src/interceptors/dataChannelAuthToken.ts. When an expired token reaches this path (for example after a locus change/leave), the flow now throws a runtime destructuring TypeError instead of following the prior refresh-error path, which breaks caller assumptions and turns token refresh failures into accidental runtime errors; either keep rejecting here or update callers to explicitly handle null.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5b3b5a2c37
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
|
||
| const finalToken = currentToken ?? practiceSessionDatachannelToken; | ||
|
|
||
| const isCaptionBoxOn = this.webex.internal.voicea.getIsCaptionBoxOn(); |
There was a problem hiding this comment.
Guard optional Voicea plugin before reading caption intent
updatePSDataChannel() now calls this.webex.internal.voicea.getIsCaptionBoxOn() unconditionally, but this same block still treats Voicea as optional (this.webex.internal.voicea?.announce?.()). In environments where the Voicea child plugin is not registered (or during version skew where that method is absent), this throws a runtime TypeError before the practice-session data channel reconnect completes, breaking webinar practice-session updates instead of safely no-oping.
Useful? React with 👍 / 👎.
…x#4749) Co-authored-by: mickelr <121160648+mickelr@users.noreply.github.com>
Add update subchannel subscriptions function
IMPORTANT
FAILING TO FILL OUT THIS TEMPLATE WILL RESULT IN REJECTION OF YOUR PULL REQUEST
This is for compliance purposes with FedRAMP program.
-->
COMPLETES #< INSERT LINK TO ISSUE >
This pull request addresses
https://jira-eng-gpk2.cisco.com/jira/browse/SPARK-763776
by making the following changes
Change Type
The following scenarios were tested
< ENUMERATE TESTS PERFORMED, WHETHER MANUAL OR AUTOMATED >
The GAI Coding Policy And Copyright Annotation Best Practices
I certified that
Make sure to have followed the contributing guidelines before submitting.