fix(mobile): auto-refresh on PostHog 401/403 auth failures#2408
fix(mobile): auto-refresh on PostHog 401/403 auth failures#2408Gilbert09 wants to merge 2 commits into
Conversation
Mirror the desktop fix from #2186 on mobile. PostHog API requests now transparently refresh the OAuth token and retry once on a 401 — or a 403 whose body is shaped like an authentication error (`code: "authentication_failed"` / `type: "authentication_error"`). If the refresh itself fails, the original response is returned so callers stay on their existing error/sign-out paths. Introduces `authedFetch(url, init)` in `apps/mobile/src/lib/api.ts` and threads it through every PostHog request site (`tasks`, `inbox`, `mcp`, `skills`, push tokens, user/projects queries). A module-level in-flight promise dedups concurrent 401s so a stampede only triggers a single refresh. The SSE stream endpoint (`streamCloudTask`) keeps its raw `fetch` — it has its own resumption flow via `Last-Event-ID` and can't be replayed with a fresh token. Adds `apps/mobile/src/lib/api.test.ts` mirroring the desktop fetcher tests, plus dedup coverage for the stampede case. Generated-By: PostHog Code Task-Id: bece83a6-7305-4a9d-b7c3-c5cb62ee3e49
|
| @@ -1,3 +1,4 @@ | |||
| import type { FetchRequestInit } from "expo/build/winter/fetch/fetch.types"; | |||
There was a problem hiding this comment.
expo/build/winter/fetch/fetch.types is an internal build-output path, not part of expo's public API. It can break silently on an expo version bump when the internal directory structure changes. The standard RequestInit type is typically a safe drop-in here, or the type can be inlined; if expo's extra properties are needed it's better to declare a local intersection or keep the import but document it explicitly so a breakage is obvious in upgrade PRs.
| import type { FetchRequestInit } from "expo/build/winter/fetch/fetch.types"; | |
| // NOTE: importing from an internal expo path; update if expo restructures its build output | |
| import type { FetchRequestInit } from "expo/build/winter/fetch/fetch.types"; |
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/mobile/src/lib/api.ts
Line: 1
Comment:
`expo/build/winter/fetch/fetch.types` is an internal build-output path, not part of expo's public API. It can break silently on an expo version bump when the internal directory structure changes. The standard `RequestInit` type is typically a safe drop-in here, or the type can be inlined; if expo's extra properties are needed it's better to declare a local intersection or keep the import but document it explicitly so a breakage is obvious in upgrade PRs.
```suggestion
// NOTE: importing from an internal expo path; update if expo restructures its build output
import type { FetchRequestInit } from "expo/build/winter/fetch/fetch.types";
```
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
- Drop the import from `expo/build/winter/fetch/fetch.types` (an internal build-output path) and derive the init type from the `fetch` signature itself, so an expo version bump can't silently break the import. - Collapse the structurally identical retry and no-retry tests into two `it.each` tables so new status codes / body shapes can be added without copy-pasting the assertion block. Generated-By: PostHog Code Task-Id: bece83a6-7305-4a9d-b7c3-c5cb62ee3e49
Summary
authedFetch(url, init)inapps/mobile/src/lib/api.tsthat transparently refreshes the OAuth token and retries once when a PostHog request returns 401, or 403 with an authentication-error body (code: "authentication_failed"/type: "authentication_error"). If refresh fails, the original response is returned so callers stay on their existing error / sign-out paths.authedFetchthrough every PostHog request site (tasks,inbox,mcp,skills, push tokens,useUserQuery,useProjectsQuery). A module-level in-flight promise dedups concurrent 401s so a stampede only triggers one refresh.Scope notes
streamCloudTask(the SSE endpoint) keeps its rawfetch— it has its own resumption flow viaLast-Event-ID/ abort signal and can't simply be replayed with a fresh token after the body has started streaming.features/mcp/service.ts) bakes the token into the transport at construction time. That's the mobile analogue of the desktop MCP proxy fix, but rebuilding the long-livedClienton auth failure is meaningfully more invasive than the API layer change and is left for a follow-up.Test plan
pnpm --filter @posthog/mobile test— 115 tests pass (including the newsrc/lib/api.test.ts, which mirrorsapps/code/src/renderer/api/fetcher.test.tsplus a dedup test for concurrent 401s).pnpm --filter @posthog/mobile lintclean.tsc --noEmitclean for the touched files (pre-existing unrelated errors in*.test.tsxcomponent renderer tests remain).