Sergei 51d533f133 feat: Validate Deployment ID through API before saving
- Add validateDeploymentId() method in api.ts that checks if ID exists
  in user's deployments list
- Update profile.tsx to validate deployment ID before saving
- Show validation error message if ID is invalid
- Display deployment name alongside ID after validation
- Add loading state during validation
2026-01-24 20:50:40 -08:00

376 lines
11 KiB
TypeScript

import * as SecureStore from 'expo-secure-store';
import type { AuthResponse, ChatResponse, Beneficiary, ApiResponse, ApiError } from '@/types';
const API_BASE_URL = 'https://eluxnetworks.net/function/well-api/api';
const CLIENT_ID = 'MA_001';
class ApiService {
private async getToken(): Promise<string | null> {
try {
return await SecureStore.getItemAsync('accessToken');
} catch {
return null;
}
}
private async getUserName(): Promise<string | null> {
try {
return await SecureStore.getItemAsync('userName');
} catch {
return null;
}
}
private generateNonce(): string {
return Math.floor(Math.random() * 1000000).toString();
}
private async makeRequest<T>(params: Record<string, string>): Promise<ApiResponse<T>> {
try {
const formData = new URLSearchParams();
Object.entries(params).forEach(([key, value]) => {
formData.append(key, value);
});
const response = await fetch(API_BASE_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: formData.toString(),
});
const data = await response.json();
if (data.status === '200 OK' || data.ok === true) {
return { data: data as T, ok: true };
}
return {
ok: false,
error: {
message: data.message || data.error || 'Request failed',
status: response.status,
},
};
} catch (error) {
const apiError: ApiError = {
message: error instanceof Error ? error.message : 'Network error',
code: 'NETWORK_ERROR',
};
return { ok: false, error: apiError };
}
}
// Authentication
async login(username: string, password: string): Promise<ApiResponse<AuthResponse>> {
const response = await this.makeRequest<AuthResponse>({
function: 'credentials',
user_name: username,
ps: password,
clientId: CLIENT_ID,
nonce: this.generateNonce(),
});
if (response.ok && response.data) {
// Save credentials to SecureStore (including password for auto-refresh)
// ВАЖНО: SecureStore принимает ТОЛЬКО строки, проверяем каждое значение
if (response.data.access_token) {
await SecureStore.setItemAsync('accessToken', String(response.data.access_token));
}
if (response.data.user_id != null) {
await SecureStore.setItemAsync('userId', String(response.data.user_id));
}
if (username) {
await SecureStore.setItemAsync('userName', String(username));
}
if (password) {
await SecureStore.setItemAsync('userPassword', String(password)); // Store for token refresh
}
await SecureStore.setItemAsync('privileges', String(response.data.privileges || ''));
await SecureStore.setItemAsync('maxRole', String(response.data.max_role ?? 0));
}
return response;
}
// Refresh token using stored credentials
async refreshToken(): Promise<ApiResponse<AuthResponse>> {
try {
const userName = await SecureStore.getItemAsync('userName');
const password = await SecureStore.getItemAsync('userPassword');
console.log('[API] refreshToken - userName:', userName ? 'exists' : 'missing');
console.log('[API] refreshToken - password:', password ? 'exists' : 'missing');
if (!userName || !password) {
console.log('[API] refreshToken - NO_CREDENTIALS');
return { ok: false, error: { message: 'No stored credentials', code: 'NO_CREDENTIALS' } };
}
console.log('[API] Refreshing token for user:', userName);
const result = await this.login(userName, password);
console.log('[API] refreshToken result:', result.ok ? 'SUCCESS' : result.error?.message);
return result;
} catch (error) {
console.error('[API] refreshToken error:', error);
return {
ok: false,
error: { message: 'Failed to refresh token', code: 'REFRESH_ERROR' }
};
}
}
// Check if token is about to expire (within 1 hour)
async isTokenExpiringSoon(): Promise<boolean> {
try {
const token = await this.getToken();
if (!token) return true;
// Decode JWT to get expiration
const parts = token.split('.');
if (parts.length !== 3) return true;
const payload = JSON.parse(atob(parts[1]));
const exp = payload.exp;
if (!exp) return true;
const now = Math.floor(Date.now() / 1000);
const oneHour = 60 * 60;
return (exp - now) < oneHour;
} catch {
return true;
}
}
// Get all credentials for WebView injection
async getWebViewCredentials(): Promise<{
token: string;
userName: string;
userId: string;
} | null> {
try {
const token = await SecureStore.getItemAsync('accessToken');
const userName = await SecureStore.getItemAsync('userName');
const userId = await SecureStore.getItemAsync('userId');
if (!token || !userName || !userId) return null;
return { token, userName, userId };
} catch {
return null;
}
}
async logout(): Promise<void> {
await SecureStore.deleteItemAsync('accessToken');
await SecureStore.deleteItemAsync('userId');
await SecureStore.deleteItemAsync('userName');
await SecureStore.deleteItemAsync('userPassword');
await SecureStore.deleteItemAsync('privileges');
await SecureStore.deleteItemAsync('maxRole');
}
async isAuthenticated(): Promise<boolean> {
const token = await this.getToken();
return !!token;
}
// Get stored user info
async getStoredUser() {
try {
const userId = await SecureStore.getItemAsync('userId');
const userName = await SecureStore.getItemAsync('userName');
const privileges = await SecureStore.getItemAsync('privileges');
const maxRole = await SecureStore.getItemAsync('maxRole');
if (!userId || !userName) return null;
return {
user_id: parseInt(userId, 10),
user_name: userName,
privileges: privileges || '',
max_role: parseInt(maxRole || '0', 10),
};
} catch {
return null;
}
}
// Deployment ID management
async setDeploymentId(deploymentId: string): Promise<void> {
await SecureStore.setItemAsync('deploymentId', deploymentId);
}
async getDeploymentId(): Promise<string | null> {
try {
return await SecureStore.getItemAsync('deploymentId');
} catch {
return null;
}
}
async clearDeploymentId(): Promise<void> {
await SecureStore.deleteItemAsync('deploymentId');
}
async validateDeploymentId(deploymentId: string): Promise<ApiResponse<{ valid: boolean; name?: string }>> {
const token = await this.getToken();
const userName = await this.getUserName();
if (!token || !userName) {
return { ok: false, error: { message: 'Not authenticated', code: 'UNAUTHORIZED' } };
}
const response = await this.makeRequest<{ result_list: Array<{
deployment_id: number;
email: string;
first_name: string;
last_name: string;
}> }>({
function: 'deployments_list',
user_name: userName,
token: token,
first: '0',
last: '100',
});
if (!response.ok || !response.data?.result_list) {
return { ok: false, error: response.error || { message: 'Failed to validate deployment ID' } };
}
const deploymentIdNum = parseInt(deploymentId, 10);
const deployment = response.data.result_list.find(item => item.deployment_id === deploymentIdNum);
if (deployment) {
return {
ok: true,
data: {
valid: true,
name: `${deployment.first_name} ${deployment.last_name}`.trim(),
},
};
}
return {
ok: true,
data: { valid: false },
};
}
// Beneficiaries (elderly people being monitored)
async getBeneficiaries(): Promise<ApiResponse<{ beneficiaries: Beneficiary[] }>> {
const token = await this.getToken();
if (!token) {
return { ok: false, error: { message: 'Not authenticated', code: 'UNAUTHORIZED' } };
}
// Note: Using mock data since API structure is not fully documented
// Replace with actual API call when available
const mockBeneficiaries: Beneficiary[] = [
{
id: 1,
name: 'Julia Smith',
status: 'online',
relationship: 'Mother',
last_activity: '2 min ago',
sensor_data: {
motion_detected: true,
last_motion: '2 min ago',
door_status: 'closed',
temperature: 22,
humidity: 45,
},
},
{
id: 2,
name: 'Robert Johnson',
status: 'offline',
relationship: 'Father',
last_activity: '1 hour ago',
sensor_data: {
motion_detected: false,
last_motion: '1 hour ago',
door_status: 'closed',
temperature: 21,
humidity: 50,
},
},
];
return { data: { beneficiaries: mockBeneficiaries }, ok: true };
}
async getBeneficiary(id: number): Promise<ApiResponse<Beneficiary>> {
const response = await this.getBeneficiaries();
if (!response.ok || !response.data) {
return { ok: false, error: response.error };
}
const beneficiary = response.data.beneficiaries.find((b) => b.id === id);
if (!beneficiary) {
return { ok: false, error: { message: 'Beneficiary not found', code: 'NOT_FOUND' } };
}
return { data: beneficiary, ok: true };
}
// Get all beneficiaries using deployments_list API (real data)
async getAllBeneficiaries(): Promise<ApiResponse<Beneficiary[]>> {
const token = await this.getToken();
const userName = await this.getUserName();
if (!token || !userName) {
return { ok: false, error: { message: 'Not authenticated', code: 'UNAUTHORIZED' } };
}
const response = await this.makeRequest<{ result_list: Array<{
deployment_id: number;
email: string;
first_name: string;
last_name: string;
}> }>({
function: 'deployments_list',
user_name: userName,
token: token,
first: '0',
last: '100',
});
if (!response.ok || !response.data?.result_list) {
return { ok: false, error: response.error || { message: 'Failed to get beneficiaries' } };
}
const beneficiaries: Beneficiary[] = response.data.result_list.map(item => ({
id: item.deployment_id,
name: `${item.first_name} ${item.last_name}`.trim(),
status: 'offline' as const,
email: item.email,
}));
return { data: beneficiaries, ok: true };
}
// AI Chat
async sendMessage(question: string, deploymentId: string = '21'): Promise<ApiResponse<ChatResponse>> {
const token = await this.getToken();
const userName = await this.getUserName();
if (!token || !userName) {
return { ok: false, error: { message: 'Not authenticated', code: 'UNAUTHORIZED' } };
}
return this.makeRequest<ChatResponse>({
function: 'voice_ask',
clientId: CLIENT_ID,
user_name: userName,
token: token,
question: question,
deployment_id: deploymentId,
});
}
}
export const api = new ApiService();