From e2d22088c15cbb87dcf86da657a3b61f3581f99c Mon Sep 17 00:00:00 2001 From: dwindown Date: Tue, 23 Dec 2025 14:48:55 +0700 Subject: [PATCH] Implement token caching to avoid unnecessary refresh token calls - Add expires_at timestamp to OAuth config - Cache access_token in database to reuse across requests - Only refresh token when it expires (after 1 hour) - Use 60-second buffer to avoid using almost-expired tokens - Auto-update cached token after refresh - This fixes the invalid_grant error from excessive refresh calls --- .../create-google-meet-event/index.ts | 72 +++++++++++++++---- 1 file changed, 60 insertions(+), 12 deletions(-) diff --git a/supabase/functions/create-google-meet-event/index.ts b/supabase/functions/create-google-meet-event/index.ts index 9f909ed..efcf5cf 100644 --- a/supabase/functions/create-google-meet-event/index.ts +++ b/supabase/functions/create-google-meet-event/index.ts @@ -10,6 +10,8 @@ interface GoogleOAuthConfig { client_id: string; client_secret: string; refresh_token: string; + access_token?: string; + expires_at?: number; // Timestamp when access_token expires } interface CreateMeetRequest { @@ -24,22 +26,36 @@ interface CreateMeetRequest { } // Function to get access token from refresh token (OAuth2) -async function getGoogleAccessToken(oauthConfig: GoogleOAuthConfig): Promise { +async function getGoogleAccessToken(oauthConfig: GoogleOAuthConfig): Promise<{ access_token: string; expires_in: number }> { try { + console.log("Attempting to exchange refresh token for access token..."); + console.log("Client ID:", oauthConfig.client_id); + + const tokenRequest = { + client_id: oauthConfig.client_id, + client_secret: oauthConfig.client_secret, + refresh_token: oauthConfig.refresh_token, + grant_type: "refresh_token", + }; + + console.log("Token request payload:", JSON.stringify({ + ...tokenRequest, + client_secret: "***REDACTED***", + refresh_token: tokenRequest.refresh_token.substring(0, 20) + "..." + })); + const response = await fetch("https://oauth2.googleapis.com/token", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, - body: new URLSearchParams({ - client_id: oauthConfig.client_id, - client_secret: oauthConfig.client_secret, - refresh_token: oauthConfig.refresh_token, - grant_type: "refresh_token", - }), + body: new URLSearchParams(tokenRequest), }); + const responseText = await response.text(); + console.log("Token response status:", response.status); + console.log("Token response body:", responseText); + if (!response.ok) { - const errorText = await response.text(); - throw new Error(`Token exchange failed: ${errorText}`); + throw new Error(`Token exchange failed: ${responseText}`); } const data = await response.json(); @@ -48,7 +64,11 @@ async function getGoogleAccessToken(oauthConfig: GoogleOAuthConfig): Promise => { ); } - // Get access token using OAuth2 refresh token - const accessToken = await getGoogleAccessToken(oauthConfig); + // Check if we have a valid cached access_token + let accessToken: string; + const now = Math.floor(Date.now() / 1000); // Current time in seconds + + if (oauthConfig.access_token && oauthConfig.expires_at && oauthConfig.expires_at > now + 60) { + // Token is still valid (with 60 second buffer) + console.log("Using cached access_token (expires at:", new Date(oauthConfig.expires_at * 1000).toISOString(), ")"); + accessToken = oauthConfig.access_token; + } else { + // Need to refresh the token + console.log("Access token expired or missing, refreshing..."); + const tokenData = await getGoogleAccessToken(oauthConfig); + accessToken = tokenData.access_token; + + // Update the cached token in database with new expiry + const newExpiresAt = now + tokenData.expires_in; + const updatedConfig = { + ...oauthConfig, + access_token: accessToken, + expires_at: newExpiresAt + }; + + // Save updated config back to database + await supabase + .from("platform_settings") + .update({ google_oauth_config: JSON.stringify(updatedConfig) }) + .eq("id", settings.id); + + console.log("Updated cached access_token in database"); + } console.log("Got access token"); // Build event data