From ae65b98feeefcd6d8c8c2e87c25f8987a71a4bc7 Mon Sep 17 00:00:00 2001 From: Recoup Agent Date: Fri, 13 Mar 2026 17:21:09 +0000 Subject: [PATCH] fix: call getAccessToken() at send time to prevent expired token errors Privy access tokens expire, but the token was only fetched once on mount via useAccessToken and cached in React state. After expiry, subsequent chat requests were sent with the stale token and failed with 401 errors. Now getAccessToken() is called right before each sendMessage() call so Privy's SDK can transparently refresh the token if needed. Co-Authored-By: Claude Sonnet 4.6 --- hooks/useVercelChat.ts | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/hooks/useVercelChat.ts b/hooks/useVercelChat.ts index e50ad1297..e36a0e2f1 100644 --- a/hooks/useVercelChat.ts +++ b/hooks/useVercelChat.ts @@ -17,6 +17,7 @@ import { DEFAULT_MODEL } from "@/lib/consts"; import { usePaymentProvider } from "@/providers/PaymentProvider"; import useArtistFilesForMentions from "@/hooks/useArtistFilesForMentions"; import type { KnowledgeBaseEntry } from "@/lib/supabase/getArtistKnowledge"; +import { usePrivy } from "@privy-io/react-auth"; import { useChatTransport } from "./useChatTransport"; import { useAccessToken } from "./useAccessToken"; import { TextAttachment } from "@/types/textAttachment"; @@ -60,6 +61,7 @@ export function useVercelChat({ availableModels[0]?.id ?? "", ); const { refetchCredits } = usePaymentProvider(); + const { getAccessToken } = usePrivy(); const { transport, headers } = useChatTransport(); const accessToken = useAccessToken(); @@ -195,7 +197,7 @@ export function useVercelChat({ }, }); - const handleSubmit = (e: React.FormEvent) => { + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); // Combine all attachments @@ -234,7 +236,16 @@ export function useVercelChat({ files: nonAudioAttachments.length > 0 ? nonAudioAttachments : undefined, }; - sendMessage(payload, chatRequestOptions); + // Get a fresh token at send time so expired tokens are never used + const freshToken = await getAccessToken(); + const requestOptions = freshToken + ? { + ...chatRequestOptions, + headers: { Authorization: `Bearer ${freshToken}` }, + } + : chatRequestOptions; + + sendMessage(payload, requestOptions); setInput(""); }; @@ -298,7 +309,7 @@ export function useVercelChat({ const messageContent = input; // Submit the message - handleSubmit(event); + await handleSubmit(event); if (!roomId) { // Optimistically append a temporary conversation so it appears in Recent Chats @@ -311,9 +322,16 @@ export function useVercelChat({ const handleSendQueryMessages = useCallback( async (initialMessage: UIMessage) => { silentlyUpdateUrl(); - sendMessage(initialMessage, chatRequestOptions); + const freshToken = await getAccessToken(); + const requestOptions = freshToken + ? { + ...chatRequestOptions, + headers: { Authorization: `Bearer ${freshToken}` }, + } + : chatRequestOptions; + sendMessage(initialMessage, requestOptions); }, - [silentlyUpdateUrl, sendMessage, chatRequestOptions], + [silentlyUpdateUrl, sendMessage, chatRequestOptions, getAccessToken], ); useEffect(() => {