mirror of https://github.com/buster-so/buster.git
Changes from background agent bc-654437a1-f840-4d59-90ee-87a733d631b1
This commit is contained in:
parent
48fa337303
commit
6b68d16461
|
@ -1,7 +1,6 @@
|
|||
'use server';
|
||||
|
||||
import type { RequestInit } from 'next/dist/server/web/spec-extension/request';
|
||||
import { cookies } from 'next/headers';
|
||||
import { createSupabaseServerClient } from '@/lib/supabase/server';
|
||||
import { BASE_URL } from './buster_rest/config';
|
||||
import type { RustApiError } from './buster_rest/errors';
|
||||
|
@ -64,8 +63,7 @@ export const serverFetch = async <T>(url: string, config: FetchConfig = {}): Pro
|
|||
};
|
||||
|
||||
export const getSupabaseTokenFromCookies = async () => {
|
||||
const cookiesManager = await cookies();
|
||||
const tokenCookie =
|
||||
cookiesManager.get('sb-127-auth-token') || cookiesManager.get('next-sb-access-token');
|
||||
return tokenCookie?.value || '';
|
||||
const supabase = await createSupabaseServerClient();
|
||||
const sessionData = await supabase.auth.getSession();
|
||||
return sessionData.data?.session?.access_token || '';
|
||||
};
|
||||
|
|
|
@ -27,26 +27,54 @@ const useSupabaseContextInternal = ({
|
|||
!supabaseContext.user?.id || supabaseContext.user?.is_anonymous === true;
|
||||
|
||||
const getExpiresAt = useMemoizedFn((token?: string) => {
|
||||
try {
|
||||
const decoded = jwtDecode(token || accessToken);
|
||||
const expiresAtDecoded = decoded?.exp || 0;
|
||||
const expiresAtDecoded = (decoded as { exp?: number } | undefined)?.exp || 0;
|
||||
const ms = millisecondsFromUnixTimestamp(expiresAtDecoded);
|
||||
return ms;
|
||||
} catch {
|
||||
// If token is missing/invalid, report that it is effectively expired now
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
|
||||
const onUpdateToken = useMemoizedFn(
|
||||
async ({ accessToken, expiresAt: _expiresAt }: { accessToken: string; expiresAt: number }) => {
|
||||
setAccessToken(accessToken);
|
||||
flushSync(() => {
|
||||
//noop
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
const checkTokenValidity = useMemoizedFn(async () => {
|
||||
try {
|
||||
const ms = getExpiresAt();
|
||||
const minutesUntilExpiration = ms / 60000;
|
||||
const isTokenExpired = minutesUntilExpiration < PREEMTIVE_REFRESH_MINUTES; //5 minutes
|
||||
|
||||
// Anonymous users do not require refresh; return current token as-is
|
||||
if (isAnonymousUser) {
|
||||
return {
|
||||
access_token: accessToken,
|
||||
isTokenValid: isTokenExpired
|
||||
isTokenValid: true
|
||||
};
|
||||
}
|
||||
|
||||
if (isTokenExpired) {
|
||||
// If we don't have a token in memory, try to recover it from Supabase session
|
||||
if (!accessToken) {
|
||||
const { data: sessionData } = await supabase.auth.getSession();
|
||||
const recoveredToken = sessionData.session?.access_token || '';
|
||||
if (recoveredToken) {
|
||||
await onUpdateToken({ accessToken: recoveredToken, expiresAt: sessionData.session?.expires_at ?? 0 });
|
||||
return {
|
||||
access_token: recoveredToken,
|
||||
isTokenValid: true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const msUntilExpiration = getExpiresAt();
|
||||
const minutesUntilExpiration = msUntilExpiration / 60000;
|
||||
const needsPreemptiveRefresh = minutesUntilExpiration < PREEMTIVE_REFRESH_MINUTES;
|
||||
|
||||
if (needsPreemptiveRefresh) {
|
||||
const { data: refreshedSession, error: refreshedSessionError } =
|
||||
await supabase.auth.refreshSession();
|
||||
|
||||
|
@ -56,16 +84,23 @@ const useSupabaseContextInternal = ({
|
|||
description: 'Please refresh the page and try again',
|
||||
duration: 120 * 1000 //2 minutes
|
||||
});
|
||||
throw refreshedSessionError;
|
||||
// As a fallback, try to read whatever session is available
|
||||
const { data: sessionData } = await supabase.auth.getSession();
|
||||
const fallbackToken = sessionData.session?.access_token || '';
|
||||
if (fallbackToken) {
|
||||
await onUpdateToken({ accessToken: fallbackToken, expiresAt: sessionData.session?.expires_at ?? 0 });
|
||||
return { access_token: fallbackToken, isTokenValid: true };
|
||||
}
|
||||
throw refreshedSessionError || new Error('Failed to refresh session');
|
||||
}
|
||||
|
||||
const accessToken = refreshedSession.session?.access_token;
|
||||
const refreshedAccessToken = refreshedSession.session?.access_token;
|
||||
const expiresAt = refreshedSession.session?.expires_at ?? 0;
|
||||
|
||||
await onUpdateToken({ accessToken, expiresAt });
|
||||
await onUpdateToken({ accessToken: refreshedAccessToken, expiresAt });
|
||||
await timeout(25);
|
||||
return {
|
||||
access_token: accessToken,
|
||||
access_token: refreshedAccessToken,
|
||||
isTokenValid: true
|
||||
};
|
||||
}
|
||||
|
@ -85,15 +120,6 @@ const useSupabaseContextInternal = ({
|
|||
}
|
||||
});
|
||||
|
||||
const onUpdateToken = useMemoizedFn(
|
||||
async ({ accessToken, expiresAt: _expiresAt }: { accessToken: string; expiresAt: number }) => {
|
||||
setAccessToken(accessToken);
|
||||
flushSync(() => {
|
||||
//noop
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (supabaseContext.accessToken) {
|
||||
setAccessToken(supabaseContext.accessToken);
|
||||
|
@ -106,6 +132,10 @@ const useSupabaseContextInternal = ({
|
|||
const refreshBuffer = PREEMTIVE_REFRESH_MINUTES * 60000; // Refresh minutes before expiration
|
||||
const timeUntilRefresh = Math.max(0, expiresInMs - refreshBuffer);
|
||||
|
||||
if (refreshTimerRef.current) {
|
||||
clearTimeout(refreshTimerRef.current);
|
||||
}
|
||||
|
||||
// Set up timer for future refresh
|
||||
refreshTimerRef.current = setTimeout(() => {
|
||||
checkTokenValidity();
|
||||
|
@ -120,7 +150,40 @@ const useSupabaseContextInternal = ({
|
|||
clearTimeout(refreshTimerRef.current);
|
||||
}
|
||||
};
|
||||
}, [accessToken, checkTokenValidity]);
|
||||
}, [accessToken, checkTokenValidity, getExpiresAt]);
|
||||
|
||||
useEffect(() => {
|
||||
// Keep access token in sync with Supabase client (captures auto-refresh events)
|
||||
const { data: subscription } = supabase.auth.onAuthStateChange((_event, session) => {
|
||||
const newToken = session?.access_token;
|
||||
if (newToken) {
|
||||
void onUpdateToken({ accessToken: newToken, expiresAt: session?.expires_at ?? 0 });
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
subscription.subscription.unsubscribe();
|
||||
};
|
||||
}, [onUpdateToken]);
|
||||
|
||||
useEffect(() => {
|
||||
const onVisibility = () => {
|
||||
if (document.visibilityState === 'visible') {
|
||||
void checkTokenValidity();
|
||||
}
|
||||
};
|
||||
const onOnline = () => {
|
||||
void checkTokenValidity();
|
||||
};
|
||||
|
||||
window.addEventListener('visibilitychange', onVisibility);
|
||||
window.addEventListener('online', onOnline);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('visibilitychange', onVisibility);
|
||||
window.removeEventListener('online', onOnline);
|
||||
};
|
||||
}, [checkTokenValidity]);
|
||||
|
||||
return {
|
||||
isAnonymousUser,
|
||||
|
|
|
@ -30,43 +30,25 @@ export async function updateSession(request: NextRequest) {
|
|||
}
|
||||
);
|
||||
|
||||
// Do not run code between createServerClient and
|
||||
// supabase.auth.getUser(). A simple mistake could make it very hard to debug
|
||||
// issues with users being randomly logged out.
|
||||
|
||||
// Get the session data first
|
||||
// const { data: sessionData } = await supabase.auth.getSession();
|
||||
const { data: sessionData } = await supabase.auth.getSession();
|
||||
|
||||
// // Check if session needs refresh (less than 50 minutes until expiry)
|
||||
// if (sessionData.session?.expires_at) {
|
||||
// const expiresAtTimestamp = sessionData.session.expires_at * 1000; // Convert to ms
|
||||
// const now = Date.now();
|
||||
// const timeUntilExpiry = expiresAtTimestamp - now;
|
||||
// const fiftyMinutesInMs = 50 * 60 * 1000;
|
||||
// Preemptively refresh if expiring soon (within 5 minutes)
|
||||
if (sessionData.session?.expires_at) {
|
||||
const expiresAtTimestamp = sessionData.session.expires_at * 1000; // ms
|
||||
const now = Date.now();
|
||||
const timeUntilExpiry = expiresAtTimestamp - now;
|
||||
const refreshWindowMs = 5 * 60 * 1000; // 5 minutes
|
||||
|
||||
// if (timeUntilExpiry < fiftyMinutesInMs) {
|
||||
// // Refresh the session
|
||||
// await supabase.auth.refreshSession();
|
||||
// }
|
||||
// }
|
||||
if (timeUntilExpiry < refreshWindowMs) {
|
||||
await supabase.auth.refreshSession();
|
||||
}
|
||||
}
|
||||
|
||||
// Get the user (this will use the refreshed session if we refreshed it)
|
||||
const {
|
||||
data: { user }
|
||||
} = await supabase.auth.getUser();
|
||||
|
||||
// IMPORTANT: You *must* return the supabaseResponse object as it is.
|
||||
// If you're creating a new response object with NextResponse.next() make sure to:
|
||||
// 1. Pass the request in it, like so:
|
||||
// const myNewResponse = NextResponse.next({ request })
|
||||
// 2. Copy over the cookies, like so:
|
||||
// myNewResponse.cookies.setAll(supabaseResponse.cookies.getAll())
|
||||
// 3. Change the myNewResponse object to fit your needs, but avoid changing
|
||||
// the cookies!
|
||||
// 4. Finally:
|
||||
// return myNewResponse
|
||||
// If this is not done, you may be causing the browser and server to go out
|
||||
// of sync and terminate the user's session prematurely!
|
||||
|
||||
return [supabaseResponse, user] as [NextResponse, User | null];
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue