Skip to content

Commit 18e7f76

Browse files
committed
fix(auth): refresh session cookies on clients
1 parent 1ade942 commit 18e7f76

4 files changed

Lines changed: 76 additions & 2 deletions

File tree

apps/desktop/layer/renderer/src/providers/user-provider.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { useEffect } from "react"
22

33
import { setIntegrationIdentify } from "~/initialize/helper"
4-
import { useSession } from "~/queries/auth"
4+
import { useAuthSessionCookieRefresh, useSession } from "~/queries/auth"
55

66
export const UserProvider = () => {
77
const { session } = useSession()
8+
useAuthSessionCookieRefresh(!!session?.user)
89

910
useEffect(() => {
1011
if (!session?.user) return

apps/desktop/layer/renderer/src/queries/auth.ts

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,51 @@ import { userSyncService } from "@follow/store/user/store"
44
import { tracker } from "@follow/tracker"
55
import { clearStorage } from "@follow/utils/ns"
66
import type { FetchError } from "ofetch"
7+
import { useEffect } from "react"
78

89
import { setLoginModalShow } from "~/atoms/user"
910
import { QUERY_PERSIST_KEY } from "~/constants"
1011
import { useAuthQuery } from "~/hooks/common"
11-
import { deleteUserCustom as deleteUserFn, getAccountInfo, signOut as signOutFn } from "~/lib/auth"
12+
import {
13+
deleteUserCustom as deleteUserFn,
14+
getAccountInfo,
15+
getSession as refreshBetterAuthSession,
16+
signOut as signOutFn,
17+
} from "~/lib/auth"
1218
import { ipcServices } from "~/lib/client"
1319
import { clearAuthSessionToken, getAuthSessionToken } from "~/lib/client-session"
1420
import { defineQuery } from "~/lib/defineQuery"
1521
import { clearLocalPersistStoreData } from "~/store/utils/clear"
1622

23+
const sessionCookieRefreshInterval = 1000 * 60 * 60 * 12
24+
25+
let lastSessionCookieRefreshAt = 0
26+
let sessionCookieRefreshPromise: Promise<unknown> | null = null
27+
28+
const refreshAuthSessionCookie = async () => {
29+
if (IN_ELECTRON && !getAuthSessionToken()) {
30+
return
31+
}
32+
33+
if (Date.now() - lastSessionCookieRefreshAt < sessionCookieRefreshInterval) {
34+
return
35+
}
36+
37+
sessionCookieRefreshPromise ??= refreshBetterAuthSession()
38+
.then((result) => {
39+
if (!result?.error) {
40+
lastSessionCookieRefreshAt = Date.now()
41+
}
42+
return result
43+
})
44+
.catch(() => null)
45+
.finally(() => {
46+
sessionCookieRefreshPromise = null
47+
})
48+
49+
await sessionCookieRefreshPromise
50+
}
51+
1752
export const auth = {
1853
getSession: () => defineQuery(whoamiQueryKey, () => userSyncService.whoami()),
1954
getAccounts: () => defineQuery(["auth", "accounts"], () => getAccountInfo()),
@@ -92,6 +127,32 @@ export const useSession = (options?: { enabled?: boolean }) => {
92127
} as const
93128
}
94129

130+
export const useAuthSessionCookieRefresh = (enabled: boolean) => {
131+
useEffect(() => {
132+
if (!enabled) {
133+
return
134+
}
135+
136+
const refresh = () => {
137+
if (document.visibilityState !== "hidden") {
138+
void refreshAuthSessionCookie()
139+
}
140+
}
141+
142+
refresh()
143+
144+
const interval = window.setInterval(refresh, sessionCookieRefreshInterval)
145+
window.addEventListener("focus", refresh)
146+
document.addEventListener("visibilitychange", refresh)
147+
148+
return () => {
149+
window.clearInterval(interval)
150+
window.removeEventListener("focus", refresh)
151+
document.removeEventListener("visibilitychange", refresh)
152+
}
153+
}, [enabled])
154+
}
155+
95156
export const handleSessionChanges = () => {
96157
setLoginModalShow(false)
97158
const authSessionToken = getAuthSessionToken()

apps/mobile/src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { useIntentHandler } from "./hooks/useIntentHandler"
1212
import { useMessaging, useUpdateMessagingToken } from "./hooks/useMessaging"
1313
import { useOnboarding } from "./hooks/useOnboarding"
1414
import { useUnreadCountBadge } from "./hooks/useUnreadCountBadge"
15+
import { useAuthSessionCookieRefresh } from "./lib/auth"
1516
import { DebugButton, EnvProfileIndicator } from "./modules/debug"
1617
import { ReviewPromptProvider } from "./modules/review-prompt/provider"
1718

@@ -53,6 +54,7 @@ const ScaleableWrapper: FC<PropsWithChildren> = ({ children }) => {
5354
}
5455

5556
const SideEffect = () => {
57+
useAuthSessionCookieRefresh()
5658
usePrefetchSessionUser()
5759
useUnreadCountBadge()
5860
useBackHandler()

apps/mobile/src/lib/auth.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const storagePrefix = "follow_auth"
2525
export const cookieKey = `${storagePrefix}_cookie`
2626
export const sessionTokenKey = "__Secure-better-auth.session_token"
2727
const sessionDataKey = `${storagePrefix}_session_data`
28+
const sessionCookieRefreshIntervalSeconds = 60 * 60 * 12
2829

2930
let authStateRevision = 0
3031
let lastAuthStateChangeAt = 0
@@ -115,6 +116,10 @@ const plugins = [
115116

116117
export const authClient = createAuthClient({
117118
baseURL: `${proxyEnv.API_URL}/better-auth`,
119+
sessionOptions: {
120+
refetchInterval: sessionCookieRefreshIntervalSeconds,
121+
refetchOnWindowFocus: true,
122+
},
118123
fetchOptions: {
119124
cache: "no-store",
120125
// Learn more: https://better-fetch.vercel.app/docs/hooks
@@ -169,6 +174,11 @@ export const {
169174
useSession,
170175
} = authClient
171176

177+
// Mount Better Auth's session atom so the Expo plugin can persist refreshed Set-Cookie metadata.
178+
export const useAuthSessionCookieRefresh = () => {
179+
useSession()
180+
}
181+
172182
export const forgetPassword = authClient.requestPasswordReset
173183

174184
export interface AuthProvider {

0 commit comments

Comments
 (0)