Repository navigation
feat(realtime): refuse expired client tokens before dialling; add apiKeyProvider for fresh tokens on connect and reconnect - #216
Merged
Conversation
commit: |
…KeyProvider for fresh tokens on connect and reconnect
Client tokens expire 60 s after minting by default. A token minted on page
load is often already expired by the time the user grants the camera and
presses start, and a reconnect after a drop reuses the same token; the
server then refuses the connect after a round trip, and the SDK surfaced
that as a generic connection error.
The SDK now decodes the token's `exp` before every dial (connect, connect
retry, reconnect) and fails with TOKEN_EXPIRED client-side, with a 5 s
clock-skew tolerance and a message that says how late the token is and
what to do.
`createDecartClient({ apiKeyProvider })` accepts an async function returning
a fresh credential; it is called before every connect and reconnect (and
before `subscribe`). The static `apiKey` path is unchanged, including its
timing: the first socket still opens in the same tick.
README documents the 60 s TTL, `expiresIn`, minting right before connect,
and the provider.
AdirAmsalem
force-pushed
the
conductor/sdk-client-token-expiry-preflight
branch
from
October 7, 2026 08:34
fc963e2 to
32a7323
Compare
…ia apiKeyProvider The Next.js example now creates one client with `apiKeyProvider` and lets the SDK fetch a token from /api/realtime-token right before every connect and reconnect, instead of fetching one itself and connecting with it. A Start button makes the point: the token is minted when the user acts, not when the page loads. The route sets `expiresIn` explicitly and the README walks through the flow.
…nce at the retry boundary - credential.ts throws plain DecartSDKErrors like the rest of the SDK and lets provider rejections through unchanged; StreamSession wraps them into DecartSDKException once, where p-retry needs an Error. - Retry permanence is a config list of SDK error codes (REALTIME_CONFIG.session.permanentErrorCodes) next to the existing substring list, so a provider's transient SDK error is retried as the docs say; only TOKEN_EXPIRED and INVALID_API_KEY end the attempts. - subscribe() resolves the credential before its try block, so a provider rejection reaches the caller unchanged, as from connect(). - The per-dial URL builder captures only what it needs instead of the whole connect options; the provider round trip overlaps image encoding; telemetry reports with the credential of the dial that started the session; the dial resolver's stale guard also checks the attempt. - Example: one release() for the teardown copies, and an attempt counter so a Stop or unmount during the camera prompt or connect() releases the camera and the session instead of leaking them. - Tests: shared client-token JWT helper; two session-level tests for the retry boundary; subscribe provider-rejection test; pinned clock in the subscribe expiry test.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit f946417. Configure here.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Why
Client tokens expire 60 s after minting by default. Apps often mint on page load and connect only after the user grants the camera and presses start, or reconnect after a drop with the same token. The server refuses the expired token after a round trip, and the SDK surfaced that as a generic connection error.
What changed
exp. An expired one (5 s clock-skew tolerance) rejects withTOKEN_EXPIREDwithout opening a socket, and the message says how late it is and what to do. Opaque keys pass through for the server to judge.apiKeyProvideroption. Pass a function instead of (or alongside)apiKey; the SDK calls it before every connect and reconnect, so each dial carries a token minted for it.apiKeyis then optional for realtime; the HTTP APIs still useapiKeyorproxy.apiKeypath unchanged, including timing.expiresInknob, and both ways to stay fresh.examples/nextjs-realtimeshows the pattern: onecreateDecartClient({ apiKeyProvider }), a Start button, and the token minted insideconnect()and again on every reconnect by the/api/realtime-tokenroute.Usage
Without a provider, an expired static token now rejects
connect()with:Tests
26 new unit tests: expired token rejected before any socket, clock-skew tolerance, provider called on connect, connect retry and each reconnect, static token expiring mid-session stops the reconnect, provider failures (retried on redial unless the SDK refused the credential outright),
subscribe, andcreateDecartClientoption validation. Suite: 374 passed. Build, lint, format and typecheck clean.Note
Medium Risk
Changes the realtime authentication and retry path for all dials; behavior is additive but static client tokens can now fail fast with TOKEN_EXPIRED, and reconnect semantics depend on provider correctness.
Overview
Adds
apiKeyProvidertocreateDecartClientso realtime connect, connect retries, reconnects, and subscribe can mint a fresh client token per dial instead of reusing one from page load (default 60s TTL).Before any socket opens, the SDK preflights JWT client tokens (
exp, 5s skew) and fails withTOKEN_EXPIREDand actionable messaging; opaque keys still go to the server.redialUrl/ dynamic signaling URLs refreshapi_keyon later dials;TOKEN_EXPIREDandINVALID_API_KEYstop retries, while transient provider/mint failures still retry.The nextjs-realtime example documents the pattern (Start/Stop, provider-backed token route with
expiresIn: 60). README and SDK docs describe TTL and both “mint right before connect” vs provider approaches.Reviewed by Cursor Bugbot for commit f946417. Bugbot is set up for automated code reviews on this repo. Configure here.