Skip to content

fix(FE-95): shared authFetch helper — centralise token injection and 401 handling - #1537

Open
Fran6ix-coder wants to merge 3 commits into
OpenKnight-Foundation:mainfrom
Fran6ix-coder:fix/fe-95-shared-auth-fetch
Open

Fran6ix-coder wants to merge 3 commits into
OpenKnight-Foundation:mainfrom
Fran6ix-coder:fix/fe-95-shared-auth-fetch

Conversation

@Fran6ix-coder

Copy link
Copy Markdown

Summary

Closes #1336 (FE-95).

Replaces ad-hoc localStorage.getItem('access_token') reads and scattered etch calls with a single �uthFetch helper that injects the bearer token, refreshes once on 401, and logs the user out if the retry still returns 401.

Changes

New helper — rontend/lib/api.ts

  • AuthFetchDeps interface {getToken, refresh, logout} — keeps the helper hook-free so plain modules (stores, services) can use it too
  • �uthFetch(url, options, deps) — wraps etchWithRetry, sets Authorization header, handles the single-refresh-on-401 flow

Migrated call sites

File Before After
playerPreferencesService.ts localStorage.getItem fallback + raw etch �uthFetch with AuthFetchDeps
hemeService.ts optional oken?: string param deps: AuthFetchDeps
ThemeContext.tsx called service with no token useAuth → stable useMemo authDeps
GamePreferencesContext.tsx called service with no args useAuth → stable useMemo authDeps
�uthContext.tsx (getSessions/revokeSession/revokeAllSessions) raw etch with stale �ccessToken state �uthFetch via �ccessTokenRef (always current)

|
epertoireStore.ts | localStorage.getItem('access_token') directly | �uthFetch with passed AuthFetchDeps |
| ournament/page.tsx | manual �uthHeaders memo + raw etch | �uthFetch with useMemo authDeps |
| openings/page.tsx | called store fn with no token | �uthFetch with useMemo authDeps |

Bug fix

�uthDeps was a plain object literal inside component bodies — new reference every render — causing useCallback dependents to fire on every render (infinite refetch loop). Fixed with useMemo.

Test fixes

  • BoardThemeProvider and GamePreferencesProvider now call useAuth() internally, so every test that renders them must wrap in AuthProvider — fixed across �ppearance, Chessboard, �lindfoldVoiceMode, piece-set, and �ccessibility test files
  • Fixed import-after-const syntax violations in test files
  • Fixed �ppearance.test.tsx header assertions: �uthFetch passes a Headers instance to etch, so assertions now use .get() instead of oMatchObject

Testing

  • All workflows unaffected except rontend-check.yml which validates these files — CI will confirm
  • Backend, contracts, agent-engines, loadtest, and soroban workflows are path-filtered and untouched

Francis added 3 commits October 21, 2026 15:14
- Add authFetch() to api.ts: injects Bearer token, retries once on 401
  after refresh, calls logout() on second 401
- Add AuthFetchDeps interface {getToken, refresh, logout} so plain
  modules can use the helper without importing React hooks
- Add useAuthOptional() to authContext so contexts that optionally sync
  with the backend (Theme, GamePreferences) do not throw when rendered
  outside AuthProvider (e.g. in tests)
- Migrate playerPreferencesService: remove localStorage fallback and
  currentToken helper; accept AuthFetchDeps directly
- Migrate themeService + ThemeContext: replace token string param with
  AuthFetchDeps; build authDeps via useAuthOptional + useMemo
- Migrate GamePreferencesContext: same pattern as ThemeContext
- Migrate repertoireStore.syncRepertoireToProfile: stop reading
  access_token from localStorage directly
- Migrate authContext getSessions/revokeSession/revokeAllSessions to
  authFetch using accessTokenRef (always current, no stale closure)
- Migrate tournament/page.tsx: replace authHeaders memo + raw fetch
  with authFetch + useMemo'd authDeps; drop credentials:include
- Migrate openings/page.tsx: useMemo authDeps, add to handleSaveLine
  dep array
- Update appearance.test.tsx: replace token-string args with
  noAuthDeps()/tokenDeps() stubs matching new AuthFetchDeps interface

Closes OpenKnight-Foundation#1336 FE-95
… fix tests

- Add authFetch() to api.ts: injects bearer token, refreshes once on 401, logs out on second 401
- Add AuthFetchDeps interface {getToken, refresh, logout} to keep helper hook-free
- Rewrite playerPreferencesService: remove localStorage fallback, both fns accept AuthFetchDeps
- Update themeService: replace token param with AuthFetchDeps
- Fix ThemeContext: import useAuth, build stable authDeps via useMemo, pass to all theme syncs
- Fix GamePreferencesContext: same pattern - useAuth + useMemo authDeps, pass to all pref calls
- Migrate authContext getSessions/revokeSession/revokeAllSessions to authFetch (use accessTokenRef)
- Migrate repertoireStore syncRepertoireToProfile to authFetch (remove direct localStorage read)
- Migrate tournament/page and openings/page to authFetch with stable useMemo authDeps
- Fix infinite refetch bug: use useMemo for authDeps objects, not plain object literals
- Fix tests: wrap BoardThemeProvider and GamePreferencesProvider in AuthProvider across
  appearance, Chessboard, blindfoldVoiceMode, piece-set and accessibility test files
- Fix test import order violations (imports after const declarations)
- Fix appearance test assertions: Headers instance checked via .get() not toMatchObject

This branch has not been deployed

No deployments
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.

FE-95: No Shared Authenticated fetch — Tokens Read Ad Hoc From localStorage and 401s Never Handled

1 participant