fix(sprint-5/etapa-4b): timeout AbortController en sync de perfil al login
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
upsertUserOnLogin y syncProfileFromDB adquieren el processLock de Supabase para leer/escribir la sesión. Si el servidor GoTrue está lento (token refresh), ese lock puede quedar ocupado varios segundos bloqueando loads de biblioteca/workspace. Agrega AbortController con timeout de 6s a las dos queries de auth sync en AuthContext. Si el servidor no responde a tiempo, la señal de abort cancela el fetch, postgrest-js lanza AbortError, el lock se libera, y el IIFE termina silenciosamente. El profile save ya usa fetch nativo (no le afecta el lock). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -77,25 +77,40 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
// user?.id es primitivo estable: no cambia cuando se edita nombre o empresa.
|
||||
useEffect(() => {
|
||||
if (!user?.id) return
|
||||
const userId = user.id
|
||||
const capturedUser = user
|
||||
|
||||
// 6s timeout: si el servidor no responde, la señal de abort cancela las queries
|
||||
// y libera el processLock de Supabase antes de que bloquee loads de biblioteca/workspace.
|
||||
const controller = new AbortController()
|
||||
const timeoutId = setTimeout(() => controller.abort(), 6000)
|
||||
|
||||
;(async () => {
|
||||
try {
|
||||
// Upsert idempotente: crea la fila si no existe (primer login),
|
||||
// ignoraDuplicates evita sobreescribir name/company editados posteriormente.
|
||||
await authService.upsertUserOnLogin(user)
|
||||
// ignoreDuplicates evita sobreescribir name/company editados posteriormente.
|
||||
await authService.upsertUserOnLogin(capturedUser, controller.signal)
|
||||
// Leer company y name editado desde la tabla (pueden diferir del user_metadata de Google).
|
||||
const profile = await authService.syncProfileFromDB(user.id)
|
||||
const profile = await authService.syncProfileFromDB(userId, controller.signal)
|
||||
if (profile) {
|
||||
setUser((prev) =>
|
||||
prev?.id === user.id
|
||||
prev?.id === userId
|
||||
? { ...prev, company: profile.company, name: profile.name ?? prev.name }
|
||||
: prev
|
||||
)
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof Error && err.name === 'AbortError') return
|
||||
console.error('Error al sincronizar perfil:', err)
|
||||
} finally {
|
||||
clearTimeout(timeoutId)
|
||||
}
|
||||
})()
|
||||
|
||||
return () => {
|
||||
controller.abort()
|
||||
clearTimeout(timeoutId)
|
||||
}
|
||||
}, [user?.id]) // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
const signIn = useCallback(() => authService.signInWithGoogle(), [])
|
||||
|
||||
@@ -59,12 +59,10 @@ export class SupabaseAuthService implements AuthService {
|
||||
|
||||
// Leer company y name editados desde la tabla users (pueden diferir del user_metadata de Google).
|
||||
// Se llama desde AuthContext FUERA del lock de auth, en un useEffect normal.
|
||||
async syncProfileFromDB(userId: string): Promise<Pick<AuthUser, 'name' | 'company'> | null> {
|
||||
const { data } = await supabase
|
||||
.from('users')
|
||||
.select('company, name')
|
||||
.eq('id', userId)
|
||||
.maybeSingle()
|
||||
// signal permite cancelar si el servidor tarda demasiado (libera el processLock).
|
||||
async syncProfileFromDB(userId: string, signal?: AbortSignal): Promise<Pick<AuthUser, 'name' | 'company'> | null> {
|
||||
const base = supabase.from('users').select('company, name').eq('id', userId)
|
||||
const { data } = await (signal ? base.abortSignal(signal) : base).maybeSingle()
|
||||
if (!data) return null
|
||||
return {
|
||||
name: (data.name as string | null) ?? undefined as unknown as string,
|
||||
@@ -74,8 +72,9 @@ export class SupabaseAuthService implements AuthService {
|
||||
|
||||
// Upsert idempotente al login: crea la fila si no existe, ignora si ya existe.
|
||||
// Se llama desde AuthContext FUERA del lock de auth, en un useEffect normal.
|
||||
async upsertUserOnLogin(user: AuthUser): Promise<void> {
|
||||
const { error } = await supabase.from('users').upsert(
|
||||
// signal permite cancelar si el servidor tarda demasiado (libera el processLock).
|
||||
async upsertUserOnLogin(user: AuthUser, signal?: AbortSignal): Promise<void> {
|
||||
const builder = supabase.from('users').upsert(
|
||||
{
|
||||
id: user.id,
|
||||
name: user.name,
|
||||
@@ -84,6 +83,7 @@ export class SupabaseAuthService implements AuthService {
|
||||
},
|
||||
{ onConflict: 'id', ignoreDuplicates: true }
|
||||
)
|
||||
const { error } = await (signal ? builder.abortSignal(signal) : builder)
|
||||
if (error) console.error('Error al crear usuario en DB:', error)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user