Compare commits
2 Commits
8142b07525
...
1c5329915a
| Author | SHA1 | Date | |
|---|---|---|---|
| 1c5329915a | |||
| 3ebfd65a90 |
@@ -30,7 +30,7 @@ function getUserFromStorage(): AuthUser | null {
|
|||||||
return {
|
return {
|
||||||
id: u.id as string,
|
id: u.id as string,
|
||||||
email,
|
email,
|
||||||
name: (u.user_metadata?.full_name as string) ?? email,
|
name: (u.user_metadata?.full_name as string | undefined) || email,
|
||||||
avatarUrl: u.user_metadata?.avatar_url as string | undefined,
|
avatarUrl: u.user_metadata?.avatar_url as string | undefined,
|
||||||
platformRole: email.endsWith('@inquality.com.py') ? 'platform_admin' : 'guest',
|
platformRole: email.endsWith('@inquality.com.py') ? 'platform_admin' : 'guest',
|
||||||
}
|
}
|
||||||
@@ -95,7 +95,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|||||||
if (profile) {
|
if (profile) {
|
||||||
setUser((prev) =>
|
setUser((prev) =>
|
||||||
prev?.id === userId
|
prev?.id === userId
|
||||||
? { ...prev, company: profile.company, name: profile.name ?? prev.name }
|
? { ...prev, company: profile.company, name: profile.name || prev.name }
|
||||||
: prev
|
: prev
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export class SupabaseAuthService implements AuthService {
|
|||||||
const { data } = await (signal ? base.abortSignal(signal) : base).maybeSingle()
|
const { data } = await (signal ? base.abortSignal(signal) : base).maybeSingle()
|
||||||
if (!data) return null
|
if (!data) return null
|
||||||
return {
|
return {
|
||||||
name: (data.name as string | null) ?? undefined as unknown as string,
|
name: ((data.name as string | null | undefined) || undefined) as unknown as string,
|
||||||
company: (data.company as string | null) ?? undefined,
|
company: (data.company as string | null) ?? undefined,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -94,7 +94,7 @@ export class SupabaseAuthService implements AuthService {
|
|||||||
return {
|
return {
|
||||||
id: supabaseUser.id,
|
id: supabaseUser.id,
|
||||||
email,
|
email,
|
||||||
name: supabaseUser.user_metadata?.full_name as string ?? email,
|
name: (supabaseUser.user_metadata?.full_name as string | undefined) || email,
|
||||||
avatarUrl: supabaseUser.user_metadata?.avatar_url as string ?? undefined,
|
avatarUrl: supabaseUser.user_metadata?.avatar_url as string ?? undefined,
|
||||||
platformRole: email.endsWith('@inquality.com.py') ? 'platform_admin' : 'guest',
|
platformRole: email.endsWith('@inquality.com.py') ? 'platform_admin' : 'guest',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export function ProfileSheet({ open, onOpenChange }: ProfileSheetProps) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (open && user) {
|
if (open && user) {
|
||||||
setName(user.name)
|
setName(user.name || user.email || '')
|
||||||
setCompany(user.company ?? '')
|
setCompany(user.company ?? '')
|
||||||
setError(null)
|
setError(null)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { getSessionSafe } from '@/lib/supabase'
|
|
||||||
import { supabaseActivityRepo } from '@/persistence/supabase/activity-repo'
|
import { supabaseActivityRepo } from '@/persistence/supabase/activity-repo'
|
||||||
import { supabaseResourceRepo } from '@/persistence/supabase/resource-repo'
|
import { supabaseResourceRepo } from '@/persistence/supabase/resource-repo'
|
||||||
import { supabaseProcessRepo } from '@/persistence/supabase/process-repo'
|
import { supabaseProcessRepo } from '@/persistence/supabase/process-repo'
|
||||||
@@ -30,8 +29,6 @@ export function useReportData(processId: string): ReportData {
|
|||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
setError(null)
|
setError(null)
|
||||||
try {
|
try {
|
||||||
// Barrera única con timeout: garantiza sesión restaurada antes del fan-out en paralelo
|
|
||||||
await getSessionSafe()
|
|
||||||
const [proc, sim, acts, res] = await Promise.all([
|
const [proc, sim, acts, res] = await Promise.all([
|
||||||
supabaseProcessRepo.getById(processId),
|
supabaseProcessRepo.getById(processId),
|
||||||
supabaseSimulationRepo.getLatestByProcess(processId),
|
supabaseSimulationRepo.getLatestByProcess(processId),
|
||||||
|
|||||||
@@ -18,16 +18,10 @@ export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
|
|||||||
// navigator.locks) — evita el deadlock a costa de no sincronizar refresh entre tabs,
|
// navigator.locks) — evita el deadlock a costa de no sincronizar refresh entre tabs,
|
||||||
// lo cual es aceptable para este producto (uso mayormente single-tab).
|
// lo cual es aceptable para este producto (uso mayormente single-tab).
|
||||||
lock: processLock,
|
lock: processLock,
|
||||||
|
// Aumentado de 5000 (default) a 30000ms.
|
||||||
|
// Con 5s, un token refresh lento dispara ProcessLockAcquireTimeoutError, cuyo handler
|
||||||
|
// hace await previousOperation (colgada) → chain bloqueado permanente.
|
||||||
|
// 30s da margen para refresh en redes lentas o cold starts de Supabase Auth.
|
||||||
|
lockAcquireTimeout: 30000,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// supabase.auth.getSession() puede colgarse indefinidamente si el cliente queda
|
|
||||||
// esperando un lock interno (navigator.locks) que nunca se libera. Esta barrera
|
|
||||||
// nunca bloquea más de 4s: si no resuelve a tiempo, sigue de largo sin sesión confirmada
|
|
||||||
// en vez de dejar la UI con un spinner/skeleton infinito.
|
|
||||||
export async function getSessionSafe(): Promise<void> {
|
|
||||||
await Promise.race([
|
|
||||||
supabase.auth.getSession(),
|
|
||||||
new Promise((resolve) => setTimeout(resolve, 4000)),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { create } from 'zustand'
|
import { create } from 'zustand'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import { getSessionSafe } from '@/lib/supabase'
|
|
||||||
import { supabaseProcessRepo, type ProcessWithUserNames } from '@/persistence/supabase/process-repo'
|
import { supabaseProcessRepo, type ProcessWithUserNames } from '@/persistence/supabase/process-repo'
|
||||||
import { supabaseGroupRepo } from '@/persistence/supabase/group-repo'
|
import { supabaseGroupRepo } from '@/persistence/supabase/group-repo'
|
||||||
import { supabaseSettingsRepo } from '@/persistence/supabase/settings-repo'
|
import { supabaseSettingsRepo } from '@/persistence/supabase/settings-repo'
|
||||||
@@ -42,11 +41,6 @@ export const useLibraryStore = create<LibraryState>((set, get) => ({
|
|||||||
load: async () => {
|
load: async () => {
|
||||||
set({ isLoading: true, error: null })
|
set({ isLoading: true, error: null })
|
||||||
try {
|
try {
|
||||||
// Barrera única: garantiza que el cliente Supabase tenga la sesión restaurada
|
|
||||||
// antes de salir a buscar datos en paralelo (evita 0 filas por RLS en el primer load).
|
|
||||||
// Con timeout defensivo: si el cliente se cuelga, no bloquea la UI más de 4s.
|
|
||||||
await getSessionSafe()
|
|
||||||
|
|
||||||
// Timeout global: si el cliente Supabase queda colgado (ej. lock interno nunca liberado),
|
// Timeout global: si el cliente Supabase queda colgado (ej. lock interno nunca liberado),
|
||||||
// isLoading nunca debe quedar en true para siempre — la UI debe poder mostrar error y reintentar.
|
// isLoading nunca debe quedar en true para siempre — la UI debe poder mostrar error y reintentar.
|
||||||
const [groups, processes, settings] = await Promise.race([
|
const [groups, processes, settings] = await Promise.race([
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { create } from 'zustand'
|
import { create } from 'zustand'
|
||||||
import { getSessionSafe } from '@/lib/supabase'
|
|
||||||
import { supabaseProcessRepo } from '@/persistence/supabase/process-repo'
|
import { supabaseProcessRepo } from '@/persistence/supabase/process-repo'
|
||||||
import { supabaseActivityRepo } from '@/persistence/supabase/activity-repo'
|
import { supabaseActivityRepo } from '@/persistence/supabase/activity-repo'
|
||||||
import { supabaseGatewayRepo } from '@/persistence/supabase/gateway-repo'
|
import { supabaseGatewayRepo } from '@/persistence/supabase/gateway-repo'
|
||||||
@@ -36,9 +35,6 @@ export const useProcessStore = create<ProcessState>((set, get) => ({
|
|||||||
loadProcess: async (processId) => {
|
loadProcess: async (processId) => {
|
||||||
set({ isLoading: true, error: null })
|
set({ isLoading: true, error: null })
|
||||||
try {
|
try {
|
||||||
// Barrera única con timeout: garantiza sesión restaurada antes del fan-out en paralelo
|
|
||||||
// sin bloquear isLoading indefinidamente si el cliente Supabase se cuelga (ver Sprint 4 Etapa 5B)
|
|
||||||
await getSessionSafe()
|
|
||||||
const [process, activities, gateways, resources] = await Promise.all([
|
const [process, activities, gateways, resources] = await Promise.all([
|
||||||
supabaseProcessRepo.getById(processId),
|
supabaseProcessRepo.getById(processId),
|
||||||
supabaseActivityRepo.getByProcess(processId),
|
supabaseActivityRepo.getByProcess(processId),
|
||||||
@@ -54,7 +50,6 @@ export const useProcessStore = create<ProcessState>((set, get) => ({
|
|||||||
|
|
||||||
setCurrentProcess: async (process, userId) => {
|
setCurrentProcess: async (process, userId) => {
|
||||||
await supabaseProcessRepo.save(process, userId)
|
await supabaseProcessRepo.save(process, userId)
|
||||||
await getSessionSafe()
|
|
||||||
const [activities, gateways, resources] = await Promise.all([
|
const [activities, gateways, resources] = await Promise.all([
|
||||||
supabaseActivityRepo.getByProcess(process.id),
|
supabaseActivityRepo.getByProcess(process.id),
|
||||||
supabaseGatewayRepo.getByProcess(process.id),
|
supabaseGatewayRepo.getByProcess(process.id),
|
||||||
|
|||||||
Reference in New Issue
Block a user