fix: robust mobile suspend and auth recovery
This commit is contained in:
@ -18,21 +18,39 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
const isPassenger = computed(() => !role.value || role.value?.toUpperCase() === 'PASSENGER')
|
||||
|
||||
/** Listens for Supabase Auth state changes */
|
||||
supabase.auth.onAuthStateChange(async (_event, session) => {
|
||||
supabase.auth.onAuthStateChange(async (event, session) => {
|
||||
userSession.value = session
|
||||
|
||||
if (session?.user) {
|
||||
// Immediately fetch their role from standard Public table if present
|
||||
const { data } = await supabase
|
||||
.from('users')
|
||||
.select('*')
|
||||
.eq('id', session.user.id)
|
||||
.single()
|
||||
// Cargar perfil solo en inicio de sesión real, NO en cada token refresh.
|
||||
// Cuando la pantalla se apaga y vuelve, Supabase re-emite TOKEN_REFRESHED.
|
||||
// Si se re-fetchea el perfil en ese momento, la red puede estar inestable
|
||||
// y el fetch queda colgado, dejando userProfile = null y re-disparando
|
||||
// todos los watchers de auto_location y autenticación.
|
||||
const profileLoadEvents = ['SIGNED_IN', 'INITIAL_SESSION']
|
||||
|
||||
if (session?.user && profileLoadEvents.includes(event)) {
|
||||
// Timeout de seguridad: si la red está inestable al despertar,
|
||||
// no bloquear indefinidamente
|
||||
const timeout = new Promise<null>((resolve) => setTimeout(() => resolve(null), 10000))
|
||||
const profileFetch = (async () => {
|
||||
try {
|
||||
const { data } = await supabase
|
||||
.from('users')
|
||||
.select('*')
|
||||
.eq('id', session.user!.id)
|
||||
.single()
|
||||
return data ?? null
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
})()
|
||||
|
||||
const data = await Promise.race([profileFetch, timeout])
|
||||
if (data) userProfile.value = data
|
||||
} else {
|
||||
} else if (!session?.user) {
|
||||
userProfile.value = null
|
||||
}
|
||||
// TOKEN_REFRESHED: no se re-fetchea el perfil, userSession ya se actualizó arriba
|
||||
})
|
||||
|
||||
async function login(email: string, pass: string, keepSession: boolean = false) {
|
||||
|
||||
@ -11,9 +11,9 @@ export const useCouponStore = defineStore('coupon', () => {
|
||||
const myCoupons = ref<any[]>([])
|
||||
const filters = ref<CouponFilters>({})
|
||||
|
||||
async function loadCoupons(newFilters?: CouponFilters) {
|
||||
if (isLoading.value) return;
|
||||
isLoading.value = true
|
||||
async function loadCoupons(newFilters?: CouponFilters, isBackground = false) {
|
||||
if (isLoading.value && !isBackground) return;
|
||||
if (!isBackground) isLoading.value = true
|
||||
error.value = null
|
||||
if (newFilters) {
|
||||
filters.value = newFilters
|
||||
@ -24,7 +24,7 @@ export const useCouponStore = defineStore('coupon', () => {
|
||||
error.value = e instanceof Error ? e.message : 'Failed to load coupons'
|
||||
console.error('Error loading coupons:', e)
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
if (!isBackground) isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -24,6 +24,13 @@ export const useFavoritesStore = defineStore('favorites', () => {
|
||||
|
||||
async function loadFavorites() {
|
||||
isLoading.value = true
|
||||
// Safety: si la red está inestable al awakening del background, no quedar cargando
|
||||
const safetyTimer = setTimeout(() => {
|
||||
if (isLoading.value) {
|
||||
console.warn('SIBU | favoritesStore: safety timeout — reseteando isLoading')
|
||||
isLoading.value = false
|
||||
}
|
||||
}, 12000)
|
||||
try {
|
||||
const { data: userData } = await supabase.auth.getUser()
|
||||
if (!userData?.user) { favorites.value = []; return }
|
||||
@ -37,6 +44,7 @@ export const useFavoritesStore = defineStore('favorites', () => {
|
||||
} catch (error) {
|
||||
console.error('Error loading favorites:', error)
|
||||
} finally {
|
||||
clearTimeout(safetyTimer)
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,19 +18,46 @@ export const useRouteStore = defineStore('route', () => {
|
||||
|
||||
const hasSelectedRoute = computed(() => selectedRouteId.value !== null && selectedRouteName.value !== null)
|
||||
|
||||
async function loadRoutes(filters?: { originCity?: string, destinationCity?: string }, force = false) {
|
||||
// Safety: nunca más de 12s cargando (protección contra thread congelado por OS al apagar pantalla)
|
||||
let _routesSafetyTimer: ReturnType<typeof setTimeout> | null = null
|
||||
let _stopsSafetyTimer: ReturnType<typeof setTimeout> | null = null
|
||||
function _startRoutesSafety() {
|
||||
if (_routesSafetyTimer) clearTimeout(_routesSafetyTimer)
|
||||
_routesSafetyTimer = setTimeout(() => {
|
||||
if (isLoadingRoutes.value) {
|
||||
console.warn('SIBU | routeStore: routes safety timeout — reseteando isLoadingRoutes')
|
||||
isLoadingRoutes.value = false
|
||||
}
|
||||
}, 12000)
|
||||
}
|
||||
function _startStopsSafety() {
|
||||
if (_stopsSafetyTimer) clearTimeout(_stopsSafetyTimer)
|
||||
_stopsSafetyTimer = setTimeout(() => {
|
||||
if (isLoadingStops.value) {
|
||||
console.warn('SIBU | routeStore: stops safety timeout — reseteando isLoadingStops')
|
||||
isLoadingStops.value = false
|
||||
}
|
||||
}, 12000)
|
||||
}
|
||||
|
||||
async function loadRoutes(filters?: { originCity?: string, destinationCity?: string }, force = false, isBackground = false) {
|
||||
const CACHE_TIME = 1000 * 60 * 15; // 15 minutos
|
||||
const now = Date.now();
|
||||
|
||||
// Guard: Si ya se están cargando rutas, no iniciar otra petición
|
||||
if (isLoadingRoutes.value) return;
|
||||
// Guard: Si ya se están cargando rutas y NO estamos en background, omitir
|
||||
// Si estamos en background, podemos sobreescribir la carga sin mostrar spinner
|
||||
if (isLoadingRoutes.value && !isBackground) return;
|
||||
|
||||
// Si no forzamos, no hay filtros raros, ya tenemos rutas y aún no expira el caché, omitir llamada
|
||||
if (!force && !filters && allRoutes.value.length > 0 && (now - lastFetched.value < CACHE_TIME)) {
|
||||
// Excepción: isBackground de refocus obliga a actualizar para asegurar frescura después de mucho rato apagado.
|
||||
if (!force && !isBackground && !filters && allRoutes.value.length > 0 && (now - lastFetched.value < CACHE_TIME)) {
|
||||
return
|
||||
}
|
||||
|
||||
isLoadingRoutes.value = true
|
||||
if (!isBackground) {
|
||||
isLoadingRoutes.value = true
|
||||
_startRoutesSafety()
|
||||
}
|
||||
error.value = null
|
||||
try {
|
||||
allRoutes.value = await routesService.getAllRoutes(filters)
|
||||
@ -39,7 +66,10 @@ export const useRouteStore = defineStore('route', () => {
|
||||
error.value = e instanceof Error ? e.message : 'Failed to load routes'
|
||||
console.error('Error loading routes:', e)
|
||||
} finally {
|
||||
isLoadingRoutes.value = false
|
||||
if (!isBackground) {
|
||||
if (_routesSafetyTimer) { clearTimeout(_routesSafetyTimer); _routesSafetyTimer = null }
|
||||
isLoadingRoutes.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,6 +88,7 @@ export const useRouteStore = defineStore('route', () => {
|
||||
if (isLoadingStops.value) return [];
|
||||
isLoadingStops.value = true
|
||||
error.value = null
|
||||
_startStopsSafety()
|
||||
try {
|
||||
const stops = await routesService.getRouteStops(routeId)
|
||||
selectedRouteStops.value = stops
|
||||
@ -69,6 +100,7 @@ export const useRouteStore = defineStore('route', () => {
|
||||
selectedRouteStops.value = []
|
||||
return []
|
||||
} finally {
|
||||
if (_stopsSafetyTimer) { clearTimeout(_stopsSafetyTimer); _stopsSafetyTimer = null }
|
||||
isLoadingStops.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,29 +9,48 @@ export const useScheduleStore = defineStore('schedule', () => {
|
||||
const isLoading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
async function loadRouteSchedules(routeId: string) {
|
||||
isLoading.value = true
|
||||
// Safety: nunca más de 12s cargando (protección contra thread congelado por OS al apagar pantalla)
|
||||
let _safetyTimer: ReturnType<typeof setTimeout> | null = null
|
||||
function _startSafetyTimer() {
|
||||
_clearSafetyTimer()
|
||||
_safetyTimer = setTimeout(() => {
|
||||
if (isLoading.value) {
|
||||
console.warn('SIBU | scheduleStore: safety timeout — reseteando isLoading')
|
||||
isLoading.value = false
|
||||
}
|
||||
}, 12000)
|
||||
}
|
||||
function _clearSafetyTimer() {
|
||||
if (_safetyTimer) { clearTimeout(_safetyTimer); _safetyTimer = null }
|
||||
}
|
||||
|
||||
async function loadRouteSchedules(routeId: string, isBackground = false) {
|
||||
if (!isBackground) isLoading.value = true
|
||||
error.value = null
|
||||
if (!isBackground) _startSafetyTimer()
|
||||
try {
|
||||
schedules.value = await schedulesService.getRouteSchedules(routeId)
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : 'Failed to load schedules'
|
||||
console.error('Error loading schedules:', e)
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
if (!isBackground) _clearSafetyTimer()
|
||||
if (!isBackground) isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loadStopSchedules(stopId: string) {
|
||||
isLoading.value = true
|
||||
async function loadStopSchedules(stopId: string, isBackground = false) {
|
||||
if (!isBackground) isLoading.value = true
|
||||
error.value = null
|
||||
if (!isBackground) _startSafetyTimer()
|
||||
try {
|
||||
schedules.value = await schedulesService.getStopSchedules(stopId)
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : 'Failed to load schedules'
|
||||
console.error('Error loading schedules:', e)
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
if (!isBackground) _clearSafetyTimer()
|
||||
if (!isBackground) isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -10,19 +10,38 @@ export const useShuttleStore = defineStore('shuttle', () => {
|
||||
const error = ref<string | null>(null)
|
||||
const filters = ref<ShuttleFilters>({})
|
||||
|
||||
async function loadShuttles(newFilters?: ShuttleFilters) {
|
||||
isLoading.value = true
|
||||
// Safety: nunca más de 12s cargando (protección contra thread congelado por OS al apagar pantalla)
|
||||
let _safetyTimer: ReturnType<typeof setTimeout> | null = null
|
||||
function _startSafetyTimer() {
|
||||
_clearSafetyTimer()
|
||||
_safetyTimer = setTimeout(() => {
|
||||
if (isLoading.value) {
|
||||
console.warn('SIBU | shuttleStore: safety timeout — reseteando isLoading')
|
||||
isLoading.value = false
|
||||
}
|
||||
}, 12000)
|
||||
}
|
||||
function _clearSafetyTimer() {
|
||||
if (_safetyTimer) { clearTimeout(_safetyTimer); _safetyTimer = null }
|
||||
}
|
||||
|
||||
async function loadShuttles(newFilters?: ShuttleFilters, isBackground = false) {
|
||||
if (!isBackground) isLoading.value = true
|
||||
error.value = null
|
||||
if (newFilters) {
|
||||
filters.value = newFilters
|
||||
}
|
||||
if (!isBackground) _startSafetyTimer()
|
||||
try {
|
||||
shuttles.value = await shuttlesService.getAllShuttles(filters.value)
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : 'Failed to load shuttles'
|
||||
console.error('Error loading shuttles:', e)
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
if (!isBackground) {
|
||||
_clearSafetyTimer()
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -10,19 +10,38 @@ export const useTaxiStore = defineStore('taxi', () => {
|
||||
const error = ref<string | null>(null)
|
||||
const filters = ref<TaxiFilters>({})
|
||||
|
||||
async function loadTaxis(newFilters?: TaxiFilters) {
|
||||
isLoading.value = true
|
||||
// Safety: nunca más de 12s cargando (protección contra thread congelado por OS al apagar pantalla)
|
||||
let _safetyTimer: ReturnType<typeof setTimeout> | null = null
|
||||
function _startSafetyTimer() {
|
||||
_clearSafetyTimer()
|
||||
_safetyTimer = setTimeout(() => {
|
||||
if (isLoading.value) {
|
||||
console.warn('SIBU | taxiStore: safety timeout — reseteando isLoading')
|
||||
isLoading.value = false
|
||||
}
|
||||
}, 12000)
|
||||
}
|
||||
function _clearSafetyTimer() {
|
||||
if (_safetyTimer) { clearTimeout(_safetyTimer); _safetyTimer = null }
|
||||
}
|
||||
|
||||
async function loadTaxis(newFilters?: TaxiFilters, isBackground = false) {
|
||||
if (!isBackground) isLoading.value = true
|
||||
error.value = null
|
||||
if (newFilters) {
|
||||
filters.value = newFilters
|
||||
}
|
||||
if (!isBackground) _startSafetyTimer()
|
||||
try {
|
||||
taxis.value = await taxisService.getAllTaxis(filters.value)
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : 'Failed to load taxis'
|
||||
console.error('Error loading taxis:', e)
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
if (!isBackground) {
|
||||
_clearSafetyTimer()
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user