learning_ai_invt_trdg/mobile/providers/MobileAuthProvider.tsx

201 lines
5.6 KiB
TypeScript

import React, { createContext, useContext, useEffect, useMemo, useState } from 'react';
import type { ReactNode } from 'react';
import { mobileTelemetry, trackMobileError } from '@/lib/telemetry';
import {
clearPlatformSession,
loginPlatformSession,
restorePlatformSession,
type PlatformAuthUser,
type StoredPlatformSession,
} from '@/lib/platformAuth';
export interface MobileUserProfile {
user_id: string;
first_name?: string;
last_name?: string;
email?: string;
role?: string;
trade_enable?: boolean;
}
interface MobileAuthContextValue {
session: StoredPlatformSession | null;
user: PlatformAuthUser | null;
profile: MobileUserProfile | null;
loading: boolean;
error: string | null;
signIn: (email: string, password: string) => Promise<{ error?: string }>;
signOut: () => Promise<void>;
invalidateSession: (reason: string) => Promise<void>;
refreshProfile: () => Promise<void>;
accessToken: string | null;
}
const MobileAuthContext = createContext<MobileAuthContextValue | null>(null);
export function MobileAuthProvider({ children }: { children: ReactNode }) {
const [session, setSession] = useState<StoredPlatformSession | null>(null);
const [user, setUser] = useState<PlatformAuthUser | null>(null);
const [profile, setProfile] = useState<MobileUserProfile | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
let active = true;
async function bootstrap() {
try {
if (!active) {
return;
}
const restored = await restorePlatformSession();
setSession(restored);
setUser(restored?.user ?? null);
if (restored?.user) {
mobileTelemetry.trackEvent('info', 'auth', 'session_restored', {
userId: restored.user.id,
});
setProfile(buildProfile(restored.user));
setError(null);
setLoading(false);
} else {
setLoading(false);
}
} catch (bootstrapError) {
trackMobileError('auth', 'session_restore_failed', bootstrapError);
setError(bootstrapError instanceof Error ? bootstrapError.message : 'Failed to restore session');
setLoading(false);
}
}
void bootstrap();
return () => {
active = false;
};
}, []);
function buildProfile(nextUser: PlatformAuthUser | null): MobileUserProfile | null {
if (!nextUser?.id) {
return null;
}
const displayName = String(nextUser.displayName || '').trim();
const parts = displayName ? displayName.split(/\s+/) : [];
return {
user_id: nextUser.id,
first_name: parts[0] || undefined,
last_name: parts.slice(1).join(' ') || undefined,
email: nextUser.email,
role: nextUser.role,
trade_enable: true,
};
}
async function signIn(email: string, password: string) {
setLoading(true);
setError(null);
try {
const nextSession = await loginPlatformSession(email, password);
setSession(nextSession);
setUser(nextSession.user);
setProfile(buildProfile(nextSession.user));
setError(null);
mobileTelemetry.trackEvent('info', 'auth', 'sign_in_succeeded', {
message: email,
userId: nextSession.user.id,
});
return {};
} catch (authError) {
const message = authError instanceof Error ? authError.message : 'Sign in failed';
setError(message);
trackMobileError('auth', 'sign_in_failed', authError, { email });
return { error: message };
} finally {
setLoading(false);
}
}
async function signOut() {
setLoading(true);
try {
await clearPlatformSession();
mobileTelemetry.trackEvent('info', 'auth', 'sign_out_succeeded', {
userId: user?.id,
});
} finally {
setProfile(null);
setSession(null);
setUser(null);
setLoading(false);
}
}
async function invalidateSession(reason: string) {
setLoading(true);
mobileTelemetry.trackEvent('warn', 'auth', 'session_invalidated', {
message: reason,
userId: user?.id,
});
try {
await clearPlatformSession();
} catch (invalidateError) {
trackMobileError('auth', 'session_invalidation_signout_failed', invalidateError);
} finally {
setProfile(null);
setSession(null);
setUser(null);
setError(reason);
setLoading(false);
}
}
async function refreshProfile() {
if (!session) {
return;
}
setLoading(true);
try {
const restored = await restorePlatformSession();
const nextUser = restored?.user ?? null;
setSession(restored);
setUser(nextUser);
setProfile(buildProfile(nextUser));
setError(null);
} catch (refreshError) {
setError(refreshError instanceof Error ? refreshError.message : 'Failed to refresh profile');
trackMobileError('auth', 'profile_refresh_failed', refreshError, { userId: user?.id || 'unknown' });
} finally {
setLoading(false);
}
}
const value = useMemo<MobileAuthContextValue>(
() => ({
session,
user,
profile,
loading,
error,
signIn,
signOut,
invalidateSession,
refreshProfile,
accessToken: session?.accessToken ?? null,
}),
[session, user, profile, loading, error]
);
return <MobileAuthContext.Provider value={value}>{children}</MobileAuthContext.Provider>;
}
export function useMobileAuth() {
const context = useContext(MobileAuthContext);
if (!context) {
throw new Error('useMobileAuth must be used within a MobileAuthProvider');
}
return context;
}