Skip to content

feat: Support per-attempt URIs and typed unrecoverable status errors in the SSE client#296

Open
kinyoklion wants to merge 3 commits into
mainfrom
rlamb/sdk-2186/sse-uri-provider
Open

feat: Support per-attempt URIs and typed unrecoverable status errors in the SSE client#296
kinyoklion wants to merge 3 commits into
mainfrom
rlamb/sdk-2186/sse-uri-provider

Conversation

@kinyoklion

@kinyoklion kinyoklion commented Jun 10, 2026

Copy link
Copy Markdown
Member

What this adds

  • uriProvider: an optional callback on SSEClient that is invoked before every connection attempt (including automatic reconnects) and whose result is used in place of the fixed URI. This is core to FDv2: streaming reconnects must carry the current basis selector as a query parameter, which advances as payloads are received. Without it, a reconnect re-establishes with a stale basis and the server replays changes the SDK already has. Supported on all platforms — the html implementation builds a fresh EventSource per attempt (its error handling restarts with its own backoff rather than relying on native reconnection), so it consults the provider the same way the HTTP implementation does.
  • UnrecoverableStatusError: unrecoverable HTTP status codes (anything other than 200, 400, 408, 429, or 5xx) were previously reported as a ClientException with the status embedded in the message text. They are now a typed error carrying the status code and response headers, so consumers can distinguish terminal failures from transient ones and read service directives delivered on error responses (e.g. the FDv2 fallback directive). The client's behavior is otherwise unchanged: it still stops reconnecting and reports the error on the stream.

Platform limitation, now documented

When running in the browser, the native EventSource exposes no HTTP status codes or response headers, so the html implementation is incapable of reporting terminal errors: every failure is treated as recoverable and retried with backoff indefinitely, and UnrecoverableStatusError is never produced on web. Consumers that need to react to unrecoverable statuses (e.g. invalid credentials) must detect them through a transport that can observe HTTP responses, such as a polling request. This is now stated on SSEClient, HtmlSseClient, and UnrecoverableStatusError.

Both API changes are additive; existing consumers are unaffected.

Testing

New unit tests cover the per-attempt URI resolution (fixed URI without a provider, provider invoked per attempt) and the typed error's status code and headers. The html implementation was additionally compile-checked for the web target.

SDK-2186


Note

Low Risk
Additive API and error-type change in the SSE client package; existing callers without uriProvider behave the same aside from a more specific stream error type on HTTP.

Overview
Adds optional uriProvider on SSEClient so each connect and reconnect uses a freshly resolved URI (via StateValues.connectUri on HTTP and a new EventSource on web), enabling query params such as an advancing FDv2 basis selector.

Non-retryable HTTP failures on the HTTP transport are now surfaced as exported UnrecoverableStatusError (status + headers) instead of a ClientException with the code in the message; reconnect behavior is unchanged. Docs call out that browser EventSource cannot observe HTTP responses, so web never emits this error and keeps retrying with backoff.

Unit tests cover fixed vs per-attempt URIs and typed 401 errors with response headers.

Reviewed by Cursor Bugbot for commit 023ad08. Bugbot is set up for automated code reviews on this repo. Configure here.

…in the SSE client

A new optional uriProvider is invoked before every connection attempt
(including automatic reconnects), allowing query parameters to vary
between attempts -- for example a state selector that advances as data
is received. Unrecoverable HTTP status codes are now reported as a
typed UnrecoverableStatusError carrying the status code and response
headers, so consumers can distinguish terminal failures from transient
ones and read service directives delivered on error responses.
@kinyoklion kinyoklion requested a review from a team as a code owner June 10, 2026 21:48
@kinyoklion kinyoklion marked this pull request as draft June 10, 2026 21:57
The html implementation constructs a fresh EventSource for every
connection attempt (its error handling restarts with its own backoff
rather than relying on native reconnection), so each attempt now
consults the uriProvider the same way the HTTP implementation does.

Also document that the html implementation is incapable of reporting
unrecoverable errors: the native EventSource exposes no HTTP status
codes or response headers, so every failure is retried indefinitely and
UnrecoverableStatusError is never produced on web.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, currently the event source isn't reporting unrecoverable errors. This will allow for that determination to be made and the data source to stop.

In the web this type of status cannot be reported. Instead the FDv2 data source will be interrupted until eventually it will fall back to polling. Polling would then get an unrecoverable error and we would shutdown the event source.

@kinyoklion kinyoklion marked this pull request as ready for review June 10, 2026 22:11
@kinyoklion

Copy link
Copy Markdown
Member Author

bugbot review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit fb41f85. Configure here.

@tanderson-ld tanderson-ld left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a doc change request.

HtmlSseClient(Uri uri, Set<String> eventTypes, EventSourceLogger? logger)
/// to the [uri] provided, or to the [uriProvider] result when one is
/// given. Every connection attempt constructs a fresh `EventSource`,
/// so the provider is consulted on each reconnect.

@tanderson-ld tanderson-ld Jun 11, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can uri be eliminated in favor of only uriProvider? Perhaps that is breaking and so that was not done.

If uri cannot be eliminated, the comment on the code could be more explicit that uriProvider is consulted first and then uri is used if uriProvider can't provide. As the comment is written, it isn't obvious that uri isn't used initially. The "consulted on each reconnect" part makes one wonder if it is consulted on first connect if not looking at the code.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it could instead me a URI modifier with a default implementation that returns the URI.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, maybe just a comment update. There isn't a strong need for it to be a modifier. I am leaving the URI for compatibility.

The fixed uri is used only when no provider is given; a provider is
consulted for every attempt, not just reconnects.
@kinyoklion kinyoklion requested a review from tanderson-ld June 11, 2026 16:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants