Merge pull request #909 from buster-so/nate/get-from-cookie

supabase session with a cookie
This commit is contained in:
Nate Kelley 2025-09-15 20:00:24 -06:00 committed by GitHub
commit f37fd4d6e3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 77 additions and 59 deletions

View File

@ -1,32 +1,4 @@
import { parse } from '@supabase/ssr';
import { parseCookies } from '@tanstack/react-start/server';
import { jwtDecode } from 'jwt-decode';
import { getExpiresAtMilliseconds } from './expiration-helpers';
export const checkTokenValidityFromToken = (token: string | undefined) => {
const decoded = decodeJwtToken(token);
};
export const decodeJwtToken = (token: string | undefined) => {
try {
const decoded = jwtDecode(token || '');
return decoded as { exp?: number };
} catch {
console.error('Error decoding token', token);
return null;
}
};
export const getExpiresAtFromToken = (token: string | undefined) => {
try {
const expiresAtDecoded = decodeJwtToken(token)?.exp ?? 0;
return getExpiresAtMilliseconds(expiresAtDecoded);
} catch {
console.error('Error decoding token', token);
// If token is missing/invalid, report that it is effectively expired now
return 0;
}
};
let supabaseCookieName = '';
function parseJwt(payload: string) {
// Remove the prefix if present
@ -44,19 +16,43 @@ function parseJwt(payload: string) {
return JSON.parse(jsonStr);
}
export const getSupabaseCookie = () => {
const supabaseCookieRaw = Object.entries(parseCookies()).find(
([name]) => name.startsWith('sb-') && name.endsWith('-auth-token')
)?.[1];
if (!supabaseCookieRaw) {
return '';
}
export const getSupabaseCookieClient = async () => {
const supabaseCookieRaw = await getSupabaseCookieRawClient();
try {
const decodedCookie = parseJwt(supabaseCookieRaw);
} catch (error) {
console.error('nope');
}
return '';
const decodedCookie = parseJwt(supabaseCookieRaw || '') as {
access_token: string;
expires_at: number;
expires_in: number;
token_type: 'bearer';
user: {
id: string;
is_anonymous: boolean;
email: string;
};
};
return decodedCookie;
};
function listAllCookies() {
return Object.fromEntries(
document.cookie.split('; ').map((cookieStr) => {
const [name, ...rest] = cookieStr.split('=');
return [name, rest.join('=')];
})
);
}
async function getSupabaseCookieRawClient() {
const getCookieByKey = (d: [string, string][]) => {
const cookie = d.find(([name]) => name.startsWith('sb-') && name.endsWith('-auth-token'));
supabaseCookieName = cookie?.[0] || '';
return cookie?.[1];
};
if (supabaseCookieName) {
const Cookies = await import('js-cookie').then((m) => m.default);
return Cookies.get(supabaseCookieName);
}
return getCookieByKey(Object.entries(listAllCookies()));
}

View File

@ -1,5 +1,3 @@
import { createServerFn } from '@tanstack/react-start';
import { getCookie } from '@tanstack/react-start/server';
import Cookies from 'js-cookie';
import type { LayoutSize } from '@/components/ui/layouts/AppLayout';
import { createAutoSaveId } from '@/components/ui/layouts/AppSplitter/create-auto-save-id';

View File

@ -1,5 +1,5 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import cookies from 'js-cookie';
import Cookies from 'js-cookie';
import React, { useRef } from 'react';
import { Button } from '@/components/ui/buttons/Button';
import { Text } from '@/components/ui/typography/Text';
@ -83,7 +83,7 @@ export const PassingInitialLayoutFromCookies: Story = {
const tagRaw = 'this-is-a-test';
const tag = createAutoSaveId(tagRaw);
const initialLayoutFromCookie: { value: number } = JSON.parse(
cookies.get(tag) ?? '["100px", "auto"]'
Cookies.get(tag) ?? '["100px", "auto"]'
);
const initialLayout: LayoutSize = [
@ -589,11 +589,11 @@ const ThreePanelWithAnimationExample = () => {
};
const initialLayoutParent = parseLayout(
cookies.get('app-splitter-three-panel-outer') ?? '',
Cookies.get('app-splitter-three-panel-outer') ?? '',
'left'
);
const initialLayoutInner = parseLayout(
cookies.get('app-splitter-three-panel-inner') ?? '',
Cookies.get('app-splitter-three-panel-inner') ?? '',
'right'
);

View File

@ -1,5 +1,5 @@
import { isServer } from '@tanstack/react-query';
import cookies from 'js-cookie';
import Cookies from 'js-cookie';
import { useState } from 'react';
import { useMemoizedFn } from './useMemoizedFn';
import { useMount } from './useMount';
@ -43,7 +43,7 @@ const setCookie = (
const expires = new Date(Date.now() + expirationTime);
const { domain, path = '/', secure = true, sameSite = 'lax' } = options;
cookies.set(name, value, {
Cookies.set(name, value, {
expires,
path,
domain,
@ -57,7 +57,7 @@ const removeCookie = (name: string, options: CookieOptions = {}): void => {
if (isServer) return;
const { domain, path = '/' } = options;
cookies.remove(name, { path, domain });
Cookies.remove(name, { path, domain });
};
export function useCookieState<T>(
@ -79,7 +79,7 @@ export function useCookieState<T>(
return typeof defaultValue === 'function' ? (defaultValue as () => T)() : defaultValue;
});
// Get initial value from cookies or use default
// Get initial value from Cookies or use default
const getInitialValue = useMemoizedFn((): T | undefined => {
// Prefer explicitly provided initialValue if present
if (initialValue !== undefined) {
@ -87,7 +87,7 @@ export function useCookieState<T>(
}
try {
const cookieValue = cookies.get(key);
const cookieValue = Cookies.get(key);
if (!cookieValue) {
return executeBustStorage();
@ -128,7 +128,7 @@ export function useCookieState<T>(
const [state, setState] = useState<T | undefined>(() => getInitialValue());
// Initialize state from cookies on mount
// Initialize state from Cookies on mount
useMount(() => {
setState(getInitialValue());
});

View File

@ -1,9 +1,10 @@
import { isTokenExpired } from '@/api/auth_helpers/expiration-helpers';
import { isTokenAlmostExpired, isTokenExpired } from '@/api/auth_helpers/expiration-helpers';
import {
getSupabaseSessionServerFn,
getSupabaseUserServerFn,
} from '@/api/server-functions/getSupabaseSession';
import { isServer } from '@/lib/window';
import { getSupabaseCookieClient } from '../../api/auth_helpers/cookie-helpers';
import { getBrowserClient } from './client';
const supabase = getBrowserClient();
@ -11,7 +12,7 @@ const supabase = getBrowserClient();
export const getSupabaseSession = async () => {
const { data: sessionData, error: sessionError } = isServer
? await getSupabaseSessionServerFn()
: await supabase.auth.getSession(); //10 - 15ms locally, maybe consider getting it from the cookie instead. console the supabase object it had it there.
: await getClientSupabaseSessionFast();
if ((!sessionData?.session || sessionError) && !isServer) {
return {
@ -30,6 +31,29 @@ export const getSupabaseSession = async () => {
};
};
const getClientSupabaseSessionFast = async () => {
try {
const cookieRes = await getSupabaseCookieClient();
const almostExpired = isTokenAlmostExpired(cookieRes.expires_at);
if (!almostExpired) {
return {
data: {
session: {
access_token: cookieRes.access_token,
isExpired: false,
expires_at: cookieRes.expires_at,
},
},
error: null,
};
}
} catch (error) {
//fail silently
}
return await supabase.auth.getSession(); //100ms on server, that's why we're using the cookie instead.
};
export const getSupabaseUser = async () => {
const { data: userData } = isServer
? await getSupabaseUserServerFn()

View File

@ -1,7 +1,7 @@
import dayjs from 'dayjs';
import timezone from 'dayjs/plugin/timezone';
import utc from 'dayjs/plugin/utc';
import cookies from 'js-cookie';
import Cookies from 'js-cookie';
import { getServerCookie } from '@/api/server-functions/getServerCookie';
import { isServer } from './window';
@ -30,7 +30,7 @@ export const detectClientTimezone = (): string => {
export const setTimezoneInCookie = (timezone: string): void => {
if (isServer || !timezone) return;
cookies.set(TIMEZONE_COOKIE_KEY, timezone, {
Cookies.set(TIMEZONE_COOKIE_KEY, timezone, {
expires: TIMEZONE_COOKIE_EXPIRY,
sameSite: 'lax',
secure: true,
@ -46,7 +46,7 @@ export const getTimezoneFromCookie = async (): Promise<string | null> => {
return cookieData || null;
}
return cookies.get(TIMEZONE_COOKIE_KEY) || null;
return Cookies.get(TIMEZONE_COOKIE_KEY) || null;
};
/**