import { Capacitor, CapacitorHttp } from '@capacitor/core'; import { getToken } from './auth.js'; const API_BASE_KEY = 'insuite_mobile_api_base'; const MOBILE_CFG_KEY = 'insuite_mobile_cfg'; const DEFAULT_API_BASE = (import.meta.env.VITE_API_BASE || 'http://192.168.1.103/Insuite_backbones/public').replace(/\/$/, ''); const REQUEST_TIMEOUT_MS = 8000; export function normalizeApiBase(value) { return String(value || '').trim().replace(/\/$/, ''); } export function getApiBase() { return normalizeApiBase(localStorage.getItem(API_BASE_KEY) || DEFAULT_API_BASE); } export function setApiBase(value) { const normalized = normalizeApiBase(value); if (!normalized) return ''; localStorage.setItem(API_BASE_KEY, normalized); return normalized; } export function getDefaultApiBase() { return DEFAULT_API_BASE; } function buildUrl(path) { const apiBase = getApiBase(); if (!apiBase) return path; if (path.startsWith('http')) return path; return apiBase + path; } function isNativePlatform() { if (typeof Capacitor?.isNativePlatform === 'function') { return Capacitor.isNativePlatform(); } const platform = typeof Capacitor?.getPlatform === 'function' ? Capacitor.getPlatform() : 'web'; return platform === 'android' || platform === 'ios'; } function normalizeResponseData(data, headers) { const headerEntries = Object.entries(headers || {}); const contentTypeHeader = headerEntries.find(([key]) => String(key).toLowerCase() === 'content-type'); const contentType = String(contentTypeHeader?.[1] || ''); if (typeof data === 'string' && contentType.includes('application/json')) { try { return JSON.parse(data); } catch { return null; } } return data; } async function requestNative(method, path, body, headers) { const response = await withTimeout( CapacitorHttp.request({ url: buildUrl(path), method, headers, data: body, connectTimeout: REQUEST_TIMEOUT_MS, readTimeout: REQUEST_TIMEOUT_MS, }), REQUEST_TIMEOUT_MS, ); const data = normalizeResponseData(response.data, response.headers); if (response.status < 200 || response.status >= 300) { const err = new Error('http_error'); err.status = response.status; err.data = data; throw err; } return data; } async function request(method, path, body, extraHeaders = {}) { const headers = { ...extraHeaders }; const token = getToken(); if (token) headers.Authorization = `Bearer ${token}`; const canUseNativeHttp = isNativePlatform() && !(body instanceof FormData); if (canUseNativeHttp) { let nativeBody = body; if (body instanceof URLSearchParams) { nativeBody = body.toString(); } return requestNative(method, path, nativeBody, headers); } const controller = typeof AbortController !== 'undefined' ? new AbortController() : null; const timeoutId = controller ? window.setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS) : null; try { const res = await fetch(buildUrl(path), { method, headers, body, signal: controller?.signal, }); const contentType = res.headers.get('content-type') || ''; const isJson = contentType.includes('application/json'); const data = isJson ? await res.json().catch(() => null) : await res.text().catch(() => null); if (!res.ok) { const err = new Error('http_error'); err.status = res.status; err.data = data; throw err; } return data; } catch (error) { if (error?.name === 'AbortError') { const timeoutError = new Error('timeout_error'); timeoutError.code = 'timeout'; throw timeoutError; } throw error; } finally { if (timeoutId) { window.clearTimeout(timeoutId); } } } export async function apiGet(path) { return request('GET', path, undefined, { Accept: 'application/json' }); } export async function apiPostForm(path, formData) { return request('POST', path, formData, { Accept: 'application/json' }); } export async function apiPostUrlEncoded(path, obj) { const body = new URLSearchParams(); Object.entries(obj || {}).forEach(([k, v]) => body.set(k, String(v ?? ''))); return request('POST', path, body, { Accept: 'application/json', 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' }); } // ── Config mobile serveur ──────────────────────────────────────────────────── export function getStoredMobileConfig() { try { return JSON.parse(localStorage.getItem(MOBILE_CFG_KEY) || 'null') || {}; } catch { return {}; } } export async function fetchAndApplyMobileConfig() { try { const res = await request('GET', '/api/mobile/config', undefined, { Accept: 'application/json' }); if (!res?.ok || !res.config) return; const cfg = res.config; if (cfg.api_base) setApiBase(cfg.api_base); localStorage.setItem(MOBILE_CFG_KEY, JSON.stringify({ app_name: cfg.app_name || '', logo_url: cfg.logo_url || '', splash_url: cfg.splash_url || '', login_bg_url: cfg.login_bg_url || '', icon_url: cfg.icon_url || '', })); } catch { // réseau indisponible — on conserve la config locale } } function withTimeout(promise, timeoutMs) { let timeoutHandle = null; return new Promise((resolve, reject) => { timeoutHandle = window.setTimeout(() => { const error = new Error('timeout_error'); error.code = 'timeout'; reject(error); }, timeoutMs); promise .then(resolve) .catch(reject) .finally(() => { if (timeoutHandle) { window.clearTimeout(timeoutHandle); } }); }); }