Files
2025-11-14 14:44:46 +07:00

379 lines
11 KiB
TypeScript

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<AuthResponse> {
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<AuthResponse> {
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<boolean> {
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<void> {
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<void> {
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<string | null> {
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<boolean> {
try {
const token = await AsyncStorage.getItem('authToken');
const user = await AsyncStorage.getItem('user');
return !!(token && user);
} catch (error) {
return false;
}
}