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
This commit is contained in:
dwindown
2025-12-23 14:48:55 +07:00
parent 7d22a5328f
commit e2d22088c1

View File

@@ -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<string> {
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<str
throw new Error("No access token in response");
}
return data.access_token;
console.log("Successfully obtained access token");
return {
access_token: data.access_token,
expires_in: data.expires_in || 3600
};
} catch (error: any) {
console.error("Error getting Google access token:", error);
throw error;
@@ -119,8 +139,36 @@ serve(async (req: Request): Promise<Response> => {
);
}
// 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