import AsyncStorage from '@react-native-async-storage/async-storage'; const API_URL = "http://192.168.2.141:8080/api/auth"; export interface AuthResponse { token: string; userId: number; email: string; message?: string; } /** * Decode base64Url string * React Native compatible - không dùng atob */ function base64UrlDecode(str: string): string { try { // Replace URL-safe characters let base64 = str.replace(/-/g, '+').replace(/_/g, '/'); // Add padding while (base64.length % 4) { base64 += '='; } // Try to use atob if available (browser environment) if (typeof atob !== 'undefined') { return atob(base64); } // For React Native - use Buffer or manual decode // React Native có thể có Buffer từ polyfills if (typeof Buffer !== 'undefined') { return Buffer.from(base64, 'base64').toString('utf-8'); } // Fallback: manual base64 decode (simple implementation) // Chỉ hoạt động với ASCII characters const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; let result = ''; let i = 0; str = str.replace(/[^A-Za-z0-9\+\/]/g, ''); while (i < str.length) { const encoded1 = chars.indexOf(str.charAt(i++)); const encoded2 = chars.indexOf(str.charAt(i++)); const encoded3 = chars.indexOf(str.charAt(i++)); const encoded4 = chars.indexOf(str.charAt(i++)); const bitmap = (encoded1 << 18) | (encoded2 << 12) | (encoded3 << 6) | encoded4; result += String.fromCharCode((bitmap >> 16) & 255); if (encoded3 !== 64) result += String.fromCharCode((bitmap >> 8) & 255); if (encoded4 !== 64) result += String.fromCharCode(bitmap & 255); } return result; } catch (error) { console.error('Error decoding base64:', error); throw new Error('Không thể decode base64: ' + (error as Error).message); } } /** * Decode JWT token và lấy payload */ function decodeJWT(token: string): any { try { const parts = token.split('.'); if (parts.length !== 3) { throw new Error('Token không đúng format JWT (phải có 3 phần)'); } const payload = parts[1]; const decoded = base64UrlDecode(payload); return JSON.parse(decoded); } catch (error) { console.error('Error decoding JWT:', error); throw new Error('Không thể decode JWT token: ' + (error as Error).message); } } export async function register(email: string, password: string): Promise { const response = await fetch(`${API_URL}/register`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email, password }), }); if (!response.ok) { const error = await response.json().catch(() => ({ message: "Đăng ký thất bại" })); throw new Error(error.message || "Đăng ký thất bại"); } const rawData = await response.json(); console.log('Register API raw response:', JSON.stringify(rawData, null, 2)); // Kiểm tra token if (!rawData.token) { throw new Error('Response không có token'); } // Lấy userId từ response (server trả về kiểu Long) let userId: number; let userEmail: string = rawData.email || email; // Lấy userId từ response - ưu tiên userId (camelCase) trước if (rawData.userId !== undefined && rawData.userId !== null) { userId = typeof rawData.userId === 'string' ? parseInt(rawData.userId, 10) : rawData.userId; } else if (rawData.user_id !== undefined && rawData.user_id !== null) { userId = typeof rawData.user_id === 'string' ? parseInt(rawData.user_id, 10) : rawData.user_id; } else if (rawData.id !== undefined && rawData.id !== null) { userId = typeof rawData.id === 'string' ? parseInt(rawData.id, 10) : rawData.id; } else { // Fallback: thử decode JWT token nếu không có trong response console.warn('UserId không có trong response, thử decode JWT token...'); try { const payload = decodeJWT(rawData.token); console.log('JWT payload decoded (register):', payload); userId = payload.userId || payload.user_id || payload.id || payload.sub; if (typeof userId === 'string') { userId = parseInt(userId, 10); } if (!userId && userId !== 0) { throw new Error('Không tìm thấy userId trong JWT token'); } if (payload.email) { userEmail = payload.email; } console.log('Extracted userId from JWT token (register):', userId); } catch (error: any) { console.error('Error decoding JWT token (register):', error); throw new Error( 'Không thể lấy userId từ response. ' + 'Response fields: ' + Object.keys(rawData).join(', ') + '. ' + 'Vui lòng đảm bảo server trả về userId trong AuthResponse.' ); } } // Validate userId if (userId === undefined || userId === null) { throw new Error('UserId không tồn tại trong response'); } if (isNaN(userId)) { throw new Error('UserId không hợp lệ (phải là số): ' + userId); } console.log('Got userId from response (register):', userId, '(type: number)'); // Tạo AuthResponse với userId const data: AuthResponse = { token: rawData.token, userId: userId, email: userEmail, }; console.log('Final register data:', data); // Validate data if (!data.token) { throw new Error('Token không tồn tại trong response'); } if (data.userId === undefined || data.userId === null) { throw new Error('UserId không tồn tại'); } await saveAuthData(data); return data; } export async function login(email: string, password: string): Promise { const response = await fetch(`${API_URL}/login`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email, password }), }); if (!response.ok) { const error = await response.json().catch(() => ({ message: "Đăng nhập thất bại" })); throw new Error(error.message || "Đăng nhập thất bại"); } const rawData = await response.json(); console.log('Login API raw response:', JSON.stringify(rawData, null, 2)); // Kiểm tra token if (!rawData.token) { throw new Error('Response không có token'); } // Lấy userId từ response (server trả về kiểu Long) let userId: number; let userEmail: string = rawData.email || email; // Lấy userId từ response - ưu tiên userId (camelCase) trước if (rawData.userId !== undefined && rawData.userId !== null) { userId = typeof rawData.userId === 'string' ? parseInt(rawData.userId, 10) : rawData.userId; } else if (rawData.user_id !== undefined && rawData.user_id !== null) { userId = typeof rawData.user_id === 'string' ? parseInt(rawData.user_id, 10) : rawData.user_id; } else if (rawData.id !== undefined && rawData.id !== null) { userId = typeof rawData.id === 'string' ? parseInt(rawData.id, 10) : rawData.id; } else { // Fallback: thử decode JWT token nếu không có trong response console.warn('UserId không có trong response, thử decode JWT token...'); try { const payload = decodeJWT(rawData.token); console.log('JWT payload decoded:', payload); userId = payload.userId || payload.user_id || payload.id || payload.sub; if (typeof userId === 'string') { userId = parseInt(userId, 10); } if (!userId && userId !== 0) { throw new Error('Không tìm thấy userId trong JWT token'); } if (payload.email) { userEmail = payload.email; } console.log('Extracted userId from JWT token:', userId); } catch (error: any) { console.error('Error decoding JWT token:', error); throw new Error( 'Không thể lấy userId từ response. ' + 'Response fields: ' + Object.keys(rawData).join(', ') + '. ' + 'Vui lòng đảm bảo server trả về userId trong AuthResponse.' ); } } // Validate userId if (userId === undefined || userId === null) { throw new Error('UserId không tồn tại trong response'); } if (isNaN(userId)) { throw new Error('UserId không hợp lệ (phải là số): ' + userId); } console.log('Got userId from response:', userId, '(type: number)'); // Tạo AuthResponse với userId const data: AuthResponse = { token: rawData.token, userId: userId, email: userEmail, }; console.log('Final auth data:', data); // Validate data if (!data.token) { throw new Error('Token không tồn tại trong response'); } if (data.userId === undefined || data.userId === null) { throw new Error('UserId không tồn tại'); } // Lưu token và user info vào AsyncStorage await saveAuthData(data); return data; } export async function logout(token: string): Promise { try { const response = await fetch(`${API_URL}/logout`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, }); // Xóa token và user info khỏi AsyncStorage await clearAuthData(); return response.ok; } catch (error) { // Vẫn xóa dữ liệu local ngay cả khi API call thất bại await clearAuthData(); return false; } } /** * Lưu thông tin auth vào AsyncStorage */ export async function saveAuthData(data: AuthResponse): Promise { try { const userData = { userId: data.userId, email: data.email, }; console.log('Saving auth data:', { hasToken: !!data.token, userId: data.userId, email: data.email }); await AsyncStorage.multiSet([ ['authToken', data.token], ['user', JSON.stringify(userData)], ]); // Verify data was saved const savedUser = await AsyncStorage.getItem('user'); const savedToken = await AsyncStorage.getItem('authToken'); console.log('Auth data saved - User:', savedUser, 'Token exists:', !!savedToken); if (!savedUser || !savedToken) { throw new Error('Failed to save auth data'); } } catch (error) { console.error('Error saving auth data:', error); throw new Error('Không thể lưu thông tin đăng nhập'); } } /** * Xóa thông tin auth khỏi AsyncStorage */ export async function clearAuthData(): Promise { try { await AsyncStorage.multiRemove(['authToken', 'user']); } catch (error) { console.error('Error clearing auth data:', error); } } /** * Lấy thông tin user từ AsyncStorage */ export async function getUser(): Promise<{ userId: number; email: string } | null> { try { const userStr = await AsyncStorage.getItem('user'); if (userStr) { return JSON.parse(userStr); } return null; } catch (error) { console.error('Error getting user:', error); return null; } } /** * Lấy auth token từ AsyncStorage */ export async function getAuthToken(): Promise { try { return await AsyncStorage.getItem('authToken'); } catch (error) { console.error('Error getting auth token:', error); return null; } } /** * Kiểm tra user đã đăng nhập chưa */ export async function isAuthenticated(): Promise { try { const token = await AsyncStorage.getItem('authToken'); const user = await AsyncStorage.getItem('user'); return !!(token && user); } catch (error) { return false; } }