diff --git a/playground/types/database.types.ts b/playground/app/types/database.types.ts similarity index 100% rename from playground/types/database.types.ts rename to playground/app/types/database.types.ts diff --git a/src/module.ts b/src/module.ts index d369c88a..43fc4739 100644 --- a/src/module.ts +++ b/src/module.ts @@ -47,6 +47,7 @@ export interface ModuleOptions { * @docs https://supabase.com/blog/jwt-signing-keys */ secretKey: string + /** * Redirect automatically to login page if user is not authenticated * @default `true` @@ -181,11 +182,27 @@ export default defineNuxtModule({ logger.warn('Missing supabase url, set it either in `nuxt.config.ts` or via env variable') } else { - // Use the default storage key as defined by the supabase-js client if no cookiePrefix is set. - // Source: https://github.com/supabase/supabase-js/blob/3316f2426d7c2e5babaab7ddc17c30bfa189f500/src/SupabaseClient.ts#L86 - const defaultStorageKey = `sb-${new URL(finalUrl).hostname.split('.')[0]}-auth-token` - const currentPrefix = nuxt.options.runtimeConfig.public.supabase.cookiePrefix - nuxt.options.runtimeConfig.public.supabase.cookiePrefix = currentPrefix || defaultStorageKey + try { + // Use the default storage key as defined by the supabase-js client if no cookiePrefix is set. + // Source: https://github.com/supabase/supabase-js/blob/3316f2426d7c2e5babaab7ddc17c30bfa189f500/src/SupabaseClient.ts#L86 + const defaultStorageKey = `sb-${new URL(finalUrl).hostname.split('.')[0]}-auth-token` + const currentPrefix = nuxt.options.runtimeConfig.public.supabase.cookiePrefix + nuxt.options.runtimeConfig.public.supabase.cookiePrefix = currentPrefix || defaultStorageKey + } + catch (error) { + logger.error( + `Invalid Supabase URL: "${finalUrl}". ` + + `Please provide a valid URL (e.g., https://example.supabase.co or http://localhost:5432)`, error) + + // Use fallback prefix + const currentPrefix = nuxt.options.runtimeConfig.public.supabase.cookiePrefix + nuxt.options.runtimeConfig.public.supabase.cookiePrefix = currentPrefix || 'sb-auth-token' + + // Fail build in production + if (!nuxt.options.dev) { + throw new Error('Invalid Supabase URL configuration') + } + } } // Warn if the key isn't set. @@ -266,13 +283,24 @@ export default defineNuxtModule({ filename: 'types/supabase-database.d.ts', getContents: async () => { if (options.types) { - // resolvePath is used to minify user input error. - const path = await resolvePath(options.types) - const typesPath = await resolvePath('~~/.nuxt/types/') // this is the default path for nuxt types - - if (fs.existsSync(path)) { - // Make the path relative to the "types" directory. - return `export * from '${relative(typesPath, path)}'` + try { + // resolvePath is used to minify user input error. + const path = await resolvePath(options.types) + const typesPath = await resolvePath('~~/.nuxt/types/') // this is the default path for nuxt types + + if (fs.existsSync(path)) { + // Make the path relative to the "types" directory. + return `export * from '${relative(typesPath, path)}'` + } + else { + logger.warn( + `Database types configured at "${options.types}" but file not found at "${path}". ` + + `Using "Database = unknown".`, + ) + } + } + catch (error) { + logger.error(`Failed to load Supabase database types from "${options.types}":`, error) } } diff --git a/src/runtime/plugins/auth-redirect.ts b/src/runtime/plugins/auth-redirect.ts index 172f959b..71a934d7 100644 --- a/src/runtime/plugins/auth-redirect.ts +++ b/src/runtime/plugins/auth-redirect.ts @@ -4,6 +4,14 @@ import type { Plugin } from '#app' import { defineNuxtPlugin, addRouteMiddleware, defineNuxtRouteMiddleware, useRuntimeConfig, navigateTo } from '#imports' import type { RouteLocationNormalized } from '#vue-router' +function matchesAnyPattern(path: string, patterns: (string | undefined)[]): boolean { + return patterns.some((pattern) => { + if (!pattern) return false + const regex = new RegExp(`^${pattern.replace(/\*/g, '.*')}$`) + return regex.test(path) + }) +} + export default defineNuxtPlugin({ name: 'auth-redirect', setup() { @@ -15,21 +23,16 @@ export default defineNuxtPlugin({ // Redirect only on included routes (if defined) if (include && include.length > 0) { - const isIncluded = include.some((path: string) => { - const regex = new RegExp(`^${path.replace(/\*/g, '.*')}$`) - return regex.test(to.path) - }) - if (!isIncluded) { + if (!matchesAnyPattern(to.path, include)) { return } } // Do not redirect on login route, callback route and excluded routes - const isExcluded = [...exclude ?? [], login, callback]?.some((path) => { - const regex = new RegExp(`^${path.replace(/\*/g, '.*')}$`) - return regex.test(to.path) - }) - if (isExcluded) return + const excludePatterns = [login, callback, ...(exclude ?? [])] + if (matchesAnyPattern(to.path, excludePatterns)) { + return + } const session = useSupabaseSession() if (!session.value) { diff --git a/src/runtime/server/services/serverSupabaseServiceRole.ts b/src/runtime/server/services/serverSupabaseServiceRole.ts index 64221288..0eb506b2 100644 --- a/src/runtime/server/services/serverSupabaseServiceRole.ts +++ b/src/runtime/server/services/serverSupabaseServiceRole.ts @@ -1,5 +1,6 @@ import type { SupabaseClient } from '@supabase/supabase-js' import { createClient } from '@supabase/supabase-js' +import { fetchWithRetry } from '../../utils/fetch-retry' import type { H3Event } from 'h3' import { useRuntimeConfig } from '#imports' // @ts-expect-error - `#supabase/database` is a runtime alias @@ -27,6 +28,9 @@ export const serverSupabaseServiceRole: (event: H3Event) => Supaba persistSession: false, autoRefreshToken: false, }, + global: { + fetch: fetchWithRetry, + }, }) } diff --git a/src/runtime/utils/fetch-retry.ts b/src/runtime/utils/fetch-retry.ts index 149ce713..a406953f 100644 --- a/src/runtime/utils/fetch-retry.ts +++ b/src/runtime/utils/fetch-retry.ts @@ -14,6 +14,9 @@ export async function fetchWithRetry(req: RequestInfo | URL, init?: RequestInit) throw error } console.warn(`Retrying fetch attempt ${attempt + 1} for request: ${req}`) + + // Small incremental delay before retry + await new Promise(resolve => setTimeout(resolve, 100 * attempt)) } } throw new Error('Unreachable code')