Skip to content

feat(sso): IEsiTokenRefreshSink.OnRefreshFailedAsync - #84

Closed
seraphx2 wants to merge 1 commit into
devfrom
fix/token-refresh-failed-sink
Closed

seraphx2 wants to merge 1 commit into
devfrom
fix/token-refresh-failed-sink

Conversation

@seraphx2

Copy link
Copy Markdown
Owner

What

Adds a failure hook to IEsiTokenRefreshSink: OnRefreshFailedAsync(character, exception), called when a transparent access-token refresh itself throws (refresh token revoked, expired, or the app's scopes changed). character is unchanged from before the attempt, and the triggering call still throws either way - this just gives a registered sink a chance to react (e.g. flag the character in your own store so other jobs stop querying it) before that exception propagates.

Why

Real-world need: a consumer's background jobs query many characters' data on a schedule and need to skip characters whose credentials have gone bad, rather than repeatedly failing against them. Until now there was no signal that a refresh failed at all - only the exception from whichever call happened to trigger it.

Verified

  • Traced the actual failure path in SsoLogic.RefreshAccessTokenAsync - the token exchange throws before mutating character, so "character is unchanged" in the XML doc is accurate, not aspirational.
  • TokenRefreshTests.FakeSink predated this interface member and didn't implement it - broke the test build (CS0535). Fixed, and added Failed_refresh_notifies_the_sink_and_still_throws, asserting: the sink's failure hook fires with the right character/exception, OnRefreshedAsync does not also fire, the exception still propagates, and the character's token/refresh-token are untouched.
  • Full solution build: 0 errors, 0 warnings. Full unit suite: 49/49 passing.
  • Documented in the README's DbTokenSink example (using the real CanQueryEsi-style pattern that motivated this) and in the CHANGELOG.

Notes

  • No per-call (EsiCallOptions) equivalent added deliberately - a one-off caller already gets the exception directly from the failed call, so a duplicate callback would be redundant. The sink is for the DI/background-job case where nothing is already watching that call.
  • Not yet released; folds into the current beta cycle.

🤖 Generated with Claude Code

Fires when a transparent refresh itself throws (refresh token revoked,
expired, or rescoped) - character is unchanged, the triggering call
still fails, but a registered sink now gets a chance to react (e.g.
flag the character so other jobs stop querying it) before that
exception propagates.

Fixes the test build: FakeSink predated this interface member and
didn't implement it. Adds a dedicated failure-path test and documents
the hook in the README's DbTokenSink example and the CHANGELOG.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

❌ The last analysis has failed.

See analysis details on SonarQube Cloud

@seraphx2

Copy link
Copy Markdown
Owner Author

Superseded — cherry-picked this commit (a356771) directly onto dev as 5b6d766 instead of merging this branch, since it had drifted well behind dev's subsequent CA1305/CA1707/CA1062/net10.0 work and a plain merge would've hit conflicts across most of the Logic files. The feature itself is now on dev and shipping in the next release. Closing this PR; branch left in place for reference.

(For the record: the SonarCloud check failure here was the scan itself erroring out — "the last analysis has failed" — not a reported code-quality issue. build/check both passed.)

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.

1 participant