Handle token refresh for supabase
This commit is contained in:
@@ -1,12 +1,11 @@
|
||||
// Track app operations that are in progress
|
||||
const appOperationLocks = new Map<number, Promise<void>>();
|
||||
const locks = new Map<number | string, Promise<void>>();
|
||||
|
||||
/**
|
||||
* Acquires a lock for an app operation
|
||||
* @param appId The app ID to lock
|
||||
* @param lockId The app ID to lock
|
||||
* @returns An object with release function and promise
|
||||
*/
|
||||
export function acquireLock(appId: number): {
|
||||
export function acquireLock(lockId: number | string): {
|
||||
release: () => void;
|
||||
promise: Promise<void>;
|
||||
} {
|
||||
@@ -14,33 +13,33 @@ export function acquireLock(appId: number): {
|
||||
|
||||
const promise = new Promise<void>((resolve) => {
|
||||
release = () => {
|
||||
appOperationLocks.delete(appId);
|
||||
locks.delete(lockId);
|
||||
resolve();
|
||||
};
|
||||
});
|
||||
|
||||
appOperationLocks.set(appId, promise);
|
||||
locks.set(lockId, promise);
|
||||
return { release, promise };
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a function with a lock on the app ID
|
||||
* @param appId The app ID to lock
|
||||
* Executes a function with a lock on the lock ID
|
||||
* @param lockId The lock ID to lock
|
||||
* @param fn The function to execute with the lock
|
||||
* @returns Result of the function
|
||||
*/
|
||||
export async function withLock<T>(
|
||||
appId: number,
|
||||
lockId: number | string,
|
||||
fn: () => Promise<T>
|
||||
): Promise<T> {
|
||||
// Wait for any existing operation to complete
|
||||
const existingLock = appOperationLocks.get(appId);
|
||||
const existingLock = locks.get(lockId);
|
||||
if (existingLock) {
|
||||
await existingLock;
|
||||
}
|
||||
|
||||
// Acquire a new lock
|
||||
const { release, promise } = acquireLock(appId);
|
||||
const { release, promise } = acquireLock(lockId);
|
||||
|
||||
try {
|
||||
const result = await fn();
|
||||
|
||||
@@ -84,6 +84,7 @@ export const SupabaseSchema = z.object({
|
||||
accessToken: SecretSchema.optional(),
|
||||
refreshToken: SecretSchema.optional(),
|
||||
expiresIn: z.number().optional(),
|
||||
tokenTimestamp: z.number().optional(),
|
||||
});
|
||||
export type Supabase = z.infer<typeof SupabaseSchema>;
|
||||
|
||||
|
||||
@@ -1,11 +1,90 @@
|
||||
import { readSettings } from "../main/settings";
|
||||
import { withLock } from "../ipc/utils/lock_utils";
|
||||
import { readSettings, writeSettings } from "../main/settings";
|
||||
import { SupabaseManagementAPI } from "supabase-management-js";
|
||||
|
||||
/**
|
||||
* Checks if the Supabase access token is expired or about to expire
|
||||
* Returns true if token needs to be refreshed
|
||||
*/
|
||||
function isTokenExpired(expiresIn?: number): boolean {
|
||||
if (!expiresIn) return true;
|
||||
|
||||
// Get when the token was saved (expiresIn is stored at the time of token receipt)
|
||||
const settings = readSettings();
|
||||
const tokenTimestamp = settings.supabase?.tokenTimestamp || 0;
|
||||
const currentTime = Math.floor(Date.now() / 1000);
|
||||
|
||||
// Check if the token is expired or about to expire (within 5 minutes)
|
||||
return currentTime >= tokenTimestamp + expiresIn - 300;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the Supabase access token using the refresh token
|
||||
* Updates settings with new tokens and expiration time
|
||||
*/
|
||||
export async function refreshSupabaseToken(): Promise<void> {
|
||||
const settings = readSettings();
|
||||
const refreshToken = settings.supabase?.refreshToken?.value;
|
||||
|
||||
if (!isTokenExpired(settings.supabase?.expiresIn)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!refreshToken) {
|
||||
throw new Error(
|
||||
"Supabase refresh token not found. Please authenticate first."
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
// Make request to Supabase refresh endpoint
|
||||
const response = await fetch(
|
||||
"https://supabase-oauth.dyad.sh/api/connect-supabase/refresh",
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ refreshToken }),
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Token refresh failed: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const {
|
||||
accessToken,
|
||||
refreshToken: newRefreshToken,
|
||||
expiresIn,
|
||||
} = await response.json();
|
||||
|
||||
// Update settings with new tokens
|
||||
writeSettings({
|
||||
supabase: {
|
||||
accessToken: {
|
||||
value: accessToken,
|
||||
},
|
||||
refreshToken: {
|
||||
value: newRefreshToken,
|
||||
},
|
||||
expiresIn,
|
||||
tokenTimestamp: Math.floor(Date.now() / 1000), // Store current timestamp
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error refreshing Supabase token:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Function to get the Supabase Management API client
|
||||
export async function getSupabaseClient(): Promise<SupabaseManagementAPI> {
|
||||
const settings = readSettings();
|
||||
|
||||
// Check if Supabase token exists in settings
|
||||
const supabaseAccessToken = settings.supabase?.accessToken?.value;
|
||||
const expiresIn = settings.supabase?.expiresIn;
|
||||
|
||||
if (!supabaseAccessToken) {
|
||||
throw new Error(
|
||||
@@ -13,6 +92,22 @@ export async function getSupabaseClient(): Promise<SupabaseManagementAPI> {
|
||||
);
|
||||
}
|
||||
|
||||
// Check if token needs refreshing
|
||||
if (isTokenExpired(expiresIn)) {
|
||||
await withLock("refresh-supabase-token", refreshSupabaseToken);
|
||||
// Get updated settings after refresh
|
||||
const updatedSettings = readSettings();
|
||||
const newAccessToken = updatedSettings.supabase?.accessToken?.value;
|
||||
|
||||
if (!newAccessToken) {
|
||||
throw new Error("Failed to refresh Supabase access token");
|
||||
}
|
||||
|
||||
return new SupabaseManagementAPI({
|
||||
accessToken: newAccessToken,
|
||||
});
|
||||
}
|
||||
|
||||
return new SupabaseManagementAPI({
|
||||
accessToken: supabaseAccessToken,
|
||||
});
|
||||
|
||||
@@ -18,6 +18,7 @@ export function handleSupabaseOAuthReturn({
|
||||
value: refreshToken,
|
||||
},
|
||||
expiresIn,
|
||||
tokenTimestamp: Math.floor(Date.now() / 1000),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user