"use client" import { createContext, useContext, useEffect, useMemo, useState } from "react" import { customSessionClient } from "better-auth/client/plugins" import { createAuthClient } from "better-auth/react" import type { AppAuth } from "@/lib/auth" import { useMutation } from "convex/react" import { api } from "@/convex/_generated/api" import { DEFAULT_TENANT_ID } from "@/lib/constants" import { isAdmin, isStaff } from "@/lib/authz" export type AppSession = { user: { id: string name?: string | null email: string role: string tenantId: string | null avatarUrl: string | null machinePersona?: string | null } session: { id: string expiresAt: number } } type MachineContext = { machineId: string tenantId: string persona: string | null assignedUserId: string | null assignedUserEmail: string | null assignedUserName: string | null assignedUserRole: string | null companyId: string | null } type MachineContextError = { status: number message: string details?: Record | null } declare global { interface Window { __machineContextDebug?: unknown } } const authClient = createAuthClient({ plugins: [customSessionClient()], fetchOptions: { credentials: "include", }, }) type AuthContextValue = { session: AppSession | null isLoading: boolean convexUserId: string | null role: string | null isAdmin: boolean isStaff: boolean isCustomer: boolean machineContext: MachineContext | null machineContextLoading: boolean machineContextError: MachineContextError | null } const AuthContext = createContext({ session: null, isLoading: true, convexUserId: null, role: null, isAdmin: false, isStaff: false, isCustomer: false, machineContext: null, machineContextLoading: false, machineContextError: null, }) export function useAuth() { return useContext(AuthContext) } export const { signIn, signOut, useSession } = authClient export function AuthProvider({ children }: { children: React.ReactNode }) { const { data: session, isPending } = useSession() const ensureUser = useMutation(api.users.ensureUser) const [convexUserId, setConvexUserId] = useState(null) const [machineContext, setMachineContext] = useState(null) const [machineContextLoading, setMachineContextLoading] = useState(false) const [machineContextError, setMachineContextError] = useState(null) useEffect(() => { if (!session?.user || session.user.role === "machine") { setConvexUserId(null) } }, [session?.user]) // Sempre tenta obter o contexto da máquina. // 1) Se a sessão Better Auth indicar role "machine", buscamos normalmente. // 2) Se a sessão vier nula (alguns ambientes WebView), ainda assim tentamos // carregar o contexto — se a API responder 200, assumimos que há sessão válida // do lado do servidor e populamos o contexto para o restante do app. useEffect(() => { const shouldFetch = Boolean(session?.user?.role === "machine") || !session?.user if (!shouldFetch) { setMachineContext(null) setMachineContextError(null) setMachineContextLoading(false) return } let cancelled = false setMachineContextLoading(true) setMachineContextError(null) ;(async () => { try { const response = await fetch("/api/machines/session", { credentials: "include" }) if (!response.ok) { let payload: Record | null = null try { const parsed = await response.clone().json() if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) { payload = parsed as Record } } catch { payload = null } const fallbackMessage = "Falha ao carregar o contexto da m�quina." const message = (payload && typeof payload.error === "string" && payload.error.trim()) || fallbackMessage if (!cancelled) { const debugPayload = { status: response.status, message, payload, timestamp: new Date().toISOString(), } console.error("[auth] machine context request failed", debugPayload) if (typeof window !== "undefined") { window.__machineContextDebug = debugPayload } setMachineContext(null) setMachineContextError({ status: response.status, message, details: payload, }) } return } const data = await response.json() if (!cancelled) { const machine = data.machine as { id: string tenantId: string persona: string | null assignedUserId: string | null assignedUserEmail: string | null assignedUserName: string | null assignedUserRole: string | null companyId: string | null } setMachineContext({ machineId: machine.id, tenantId: machine.tenantId, persona: machine.persona ?? null, assignedUserId: machine.assignedUserId ?? null, assignedUserEmail: machine.assignedUserEmail ?? null, assignedUserName: machine.assignedUserName ?? null, assignedUserRole: machine.assignedUserRole ?? null, companyId: machine.companyId ?? null, }) setMachineContextError(null) } } catch (error) { console.error("Failed to load machine context", error) if (!cancelled) { const debugPayload = { error: error instanceof Error ? { name: error.name, message: error.message, stack: error.stack } : String(error), timestamp: new Date().toISOString(), } console.error("[auth] machine context unexpected error", debugPayload) if (typeof window !== "undefined") { window.__machineContextDebug = debugPayload } setMachineContext(null) setMachineContextError({ status: 0, message: "Erro ao carregar o contexto da m�quina.", details: error instanceof Error ? { message: error.message } : null, }) } } finally { if (!cancelled) { setMachineContextLoading(false) } } })() return () => { cancelled = true } }, [session?.user]) useEffect(() => { if (!session?.user || session.user.role === "machine" || convexUserId) return const controller = new AbortController() ;(async () => { try { const ensured = await ensureUser({ tenantId: session.user.tenantId ?? DEFAULT_TENANT_ID, name: session.user.name ?? session.user.email, email: session.user.email, avatarUrl: session.user.avatarUrl ?? undefined, role: session.user.role.toUpperCase(), }) if (!controller.signal.aborted) { setConvexUserId(ensured?._id ?? null) } } catch (error) { if (!controller.signal.aborted) { console.error("Failed to sync user with Convex", error) } } })() return () => { controller.abort() } // eslint-disable-next-line react-hooks/exhaustive-deps }, [ensureUser, session?.user?.email, session?.user?.tenantId, session?.user?.role, convexUserId]) // Se não houver sessão mas tivermos contexto de máquina, tratamos como "machine" const baseRole = session?.user?.role ? session.user.role.toLowerCase() : (machineContext ? "machine" : null) const personaRole = session?.user?.machinePersona ? session.user.machinePersona.toLowerCase() : null const normalizedRole = baseRole === "machine" ? (machineContext?.persona ?? personaRole ?? null) : baseRole const effectiveConvexUserId = baseRole === "machine" ? (machineContext?.assignedUserId ?? null) : convexUserId const value = useMemo( () => ({ session: session ?? null, isLoading: isPending, convexUserId: effectiveConvexUserId, role: normalizedRole, isAdmin: isAdmin(normalizedRole), isStaff: isStaff(normalizedRole), isCustomer: normalizedRole === "collaborator", machineContext, machineContextLoading, machineContextError, }), [session, isPending, effectiveConvexUserId, normalizedRole, machineContext, machineContextLoading, machineContextError] ) return {children} }