/** * API 工具类 * 统一管理后端 API 调用 */ import { getUserId, getUserToken } from './storage'; /** 后端返回 detail 为 { code: string } 或旧版字符串 */ function getDetailCode(detail: unknown): string | undefined { if (detail && typeof detail === 'object' && 'code' in detail && typeof (detail as { code: unknown }).code === 'string') { return (detail as { code: string }).code; } if (typeof detail === 'string') return detail; return undefined; } /** * 解析 4xx/5xx 响应为带 code 的 ApiError,供前端用 t('error.' + code) 展示 */ export class ApiError extends Error { code: string; status: number; constructor(code: string, status: number, message?: string) { super(message ?? code); this.name = 'ApiError'; this.code = code; this.status = status; } } export async function parseApiError(response: Response, fallbackCode = 'UNKNOWN'): Promise { let code = fallbackCode; const contentType = response.headers.get('content-type') || ''; if (contentType.includes('application/json')) { try { const body = await response.json(); const detail = body?.detail; const parsed = getDetailCode(detail); if (parsed) code = parsed; } catch { // ignore } } return new ApiError(code, response.status, response.statusText); } /** * 获取 API Key(从环境变量读取) */ const getApiKey = (): string => { return import.meta.env.VITE_API_CLIENT_KEY || ''; }; /** * 获取统一的请求头(包含 API Key 与用户会话信息) */ export const getApiHeaders = (): Record => { const headers: Record = { 'Content-Type': 'application/json', }; const apiKey = getApiKey(); if (apiKey) { headers['X-Client-Key'] = apiKey; } // 附加用户会话信息(用于后端基于 token 的 HTTP 鉴权) const userId = getUserId(); const token = getUserToken(); if (userId && token) { headers['X-User-Id'] = userId; headers['X-User-Token'] = token; } return headers; }; /** * 会话确保逻辑: * - 若本地已有 userId + token,直接复用(依赖服务端滑动续期,不主动换 token) * - 若没有,则通过 /api/session/init 创建一次会话 * - 使用单例 Promise 防抖,避免并发多次刷新导致 token 频繁变更 */ let ensureSessionPromise: Promise<{ userId: string; token: string }> | null = null; export const ensureSession = async (): Promise<{ userId: string; token: string }> => { // 已有未完成的会话初始化,直接复用 if (ensureSessionPromise) { return ensureSessionPromise; } // 动态导入,避免循环依赖 const { getUserId: loadUserId, getUserToken: loadUserToken, setUserId, setUserToken } = await import('./storage'); const existingUserId = loadUserId(); const existingToken = loadUserToken(); // 本地已经有 userId + token,则认为会话有效,直接返回 if (existingUserId && existingToken) { return { userId: existingUserId, token: existingToken }; } // 没有有效会话时,串行化一次 /api/session/init 调用 ensureSessionPromise = (async () => { const resp = await initSession(existingUserId || undefined); if (!resp.success || !resp.data?.token || !resp.data?.user_id) { throw new Error('Session init failed'); } setUserId(resp.data.user_id); setUserToken(resp.data.token); return { userId: resp.data.user_id, token: resp.data.token }; })(); try { return await ensureSessionPromise; } finally { ensureSessionPromise = null; } }; /** * 获取后端 API 基础 URL */ export const getApiBaseUrl = (): string => { // 开发环境:使用相对路径,通过 Vite 代理访问后端 // 这样可以避免 Safari 对自签名证书的跨域限制 if (import.meta.env.DEV) { return ''; // 使用相对路径,Vite 会代理到后端 } // 生产环境:使用完整 URL // 优先使用环境变量指定的 API 地址 if (import.meta.env.VITE_API_BASE_URL) { return import.meta.env.VITE_API_BASE_URL; } // 从 WebSocket URL 推导 API 基础 URL const wsUrl = import.meta.env.VITE_WS_URL || 'ws://localhost:8000/ws'; // 将 ws:// 或 wss:// 转换为 http:// 或 https:// const apiUrl = wsUrl .replace(/^wss:\/\//, 'https://') .replace(/^ws:\/\//, 'http://') .replace(/\/ws$/, ''); return apiUrl; }; /** * 上报页面访问(每次打开任意界面时调用,保存到后台数据库) * 不阻塞 UI,失败静默忽略。 */ export const reportPageVisit = (path: string): void => { const apiBaseUrl = getApiBaseUrl(); const body = JSON.stringify({ path: path || '/' }); fetch(`${apiBaseUrl}/api/page-visit`, { method: 'POST', headers: getApiHeaders(), body, }).catch(() => {}); }; export interface UserEventPayload { event_id: string; user_id: string; event_name: string; event_label?: string; event_time: string; session_id?: string; platform: 'ios' | 'android' | 'web'; app_version?: string; timezone_offset_minutes?: number; properties?: Record; } /** * 上报单条用户事件(user_events) * - 不抛异常,失败静默 * - 开发环境会打印请求与响应状态,便于排查未落库问题 */ export const reportUserEvent = (payload: UserEventPayload): void => { const apiBaseUrl = getApiBaseUrl(); const url = `${apiBaseUrl}/api/user-events`; const headers = getApiHeaders(); if (import.meta.env.DEV) { const hasAuth = !!(headers['X-User-Id'] && headers['X-User-Token']); const keys = Object.keys(payload); const required = ['event_id', 'user_id', 'event_name', 'event_time', 'platform']; const missing = required.filter((k) => !(k in payload) || payload[k as keyof UserEventPayload] == null); if (missing.length) { console.warn('[user_events] 请求体缺少必填字段:', missing, '当前 keys:', keys); } console.log('[user_events] 上报:', payload.event_name, { hasAuth, user_id: payload.user_id, keys }); } fetch(url, { method: 'POST', headers, body: JSON.stringify(payload), }) .then(async (res) => { if (import.meta.env.DEV && !res.ok) { const body = await res.text(); try { const parsed = JSON.parse(body); const details = Array.isArray(parsed?.detail) ? parsed.detail : parsed?.detail ? [parsed.detail] : []; const first = details[0]; const loc = first?.loc; console.warn('[user_events] 上报失败:', res.status, payload.event_name, '缺失/错误字段 loc:', loc, 'msg:', first?.msg, '详情:', details); } catch { console.warn('[user_events] 上报失败:', res.status, res.statusText, payload.event_name, body?.slice(0, 200)); } } }) .catch((err) => { if (import.meta.env.DEV) { console.warn('[user_events] 请求异常:', err?.message ?? err, payload.event_name); } }); }; /** * 创建用户 */ export interface CreateUserResponse { success: boolean; data: { user_id: string; username: string; user_type: string; user_level: number; current_background_image_id?: string; created_at: string; }; } export const createUser = async (username: string = ''): Promise => { const apiBaseUrl = getApiBaseUrl(); const url = `${apiBaseUrl}/api/users/create`; try { const response = await fetch(url, { method: 'POST', headers: getApiHeaders(), body: JSON.stringify({ username }), }); if (!response.ok) { throw await parseApiError(response, 'CREATE_USER_FAILED'); } const data = await response.json(); // 同步背景图片到 localStorage(包括metadata中的透明度配置) if (data.success && data.data?.current_background_image_id) { const { syncBackgroundImageToStorage } = await import('./storage'); syncBackgroundImageToStorage( data.data.current_background_image_id, data.data.current_background_image_metadata ); } return data; } catch (error) { console.error('[API] 创建用户异常:', error); // 如果是网络错误,提供更友好的提示 if (error instanceof TypeError && error.message.includes('fetch')) { throw new Error('网络请求失败,请检查后端服务是否运行'); } throw error; } }; /** * 初始化/刷新会话,返回 user_id + token */ export interface InitSessionResponse { success: boolean; data: { user_id: string; token: string; expires_at: string; username?: string; user_level?: number; current_background_image_id?: string; current_background_image_metadata?: Record; }; } export const initSession = async (user_id?: string, username?: string): Promise => { const apiBaseUrl = getApiBaseUrl(); const url = `${apiBaseUrl}/api/session/init`; const body: Record = {}; if (user_id) { body.user_id = user_id; } if (username) { body.username = username; } const response = await fetch(url, { method: 'POST', headers: getApiHeaders(), body: JSON.stringify(body), }); if (!response.ok) { throw await parseApiError(response, 'INIT_SESSION_FAILED'); } return response.json(); }; /** * 获取用户信息 */ export interface GetUserResponse { success: boolean; data: { user_id: string; username: string; user_type: string; user_segment?: string; org_id?: string | null; is_test_user?: boolean; region?: string | null; bot_name?: string; total_speech_duration_ms?: number; user_level?: number; current_background_image_id?: string; current_background_image_metadata?: Record; preferred_language?: 'zh' | 'en'; created_at: string; }; } export const getUserInfo = async (user_id: string): Promise => { const apiBaseUrl = getApiBaseUrl(); const response = await fetch(`${apiBaseUrl}/api/users/${user_id}`, { method: 'GET', headers: getApiHeaders(), }); if (!response.ok) { throw await parseApiError(response, 'GET_USER_INFO_FAILED'); } const data = await response.json(); // 同步背景图片到 localStorage(包括metadata中的透明度配置) if (data.success && data.data?.current_background_image_id) { const { syncBackgroundImageToStorage } = await import('./storage'); syncBackgroundImageToStorage( data.data.current_background_image_id, data.data.current_background_image_metadata ); } return data; }; /** * 更新用户名 */ export interface UpdateUsernameResponse { success: boolean; message: string; } export const updateUsername = async (user_id: string, username: string): Promise => { const apiBaseUrl = getApiBaseUrl(); const response = await fetch(`${apiBaseUrl}/api/users/${user_id}/username`, { method: 'PUT', headers: getApiHeaders(), body: JSON.stringify({ username }), }); if (!response.ok) { throw await parseApiError(response, 'UPDATE_USERNAME_FAILED'); } return response.json(); }; /** * 更新背景图片ID */ export interface UpdateBackgroundImageIdResponse { success: boolean; message: string; } export const updateBackgroundImageId = async ( user_id: string, background_image_id: string, ): Promise => { const apiBaseUrl = getApiBaseUrl(); const response = await fetch(`${apiBaseUrl}/api/users/${user_id}/background-image-id`, { method: 'PUT', headers: getApiHeaders(), body: JSON.stringify({ background_image_id }), }); if (!response.ok) { throw await parseApiError(response, 'UPDATE_BG_IMAGE_FAILED'); } return response.json(); }; /** * 更新机器人名字 */ export interface UpdateBotNameResponse { success: boolean; message: string; } export const updateBotName = async (user_id: string, bot_name: string): Promise => { const apiBaseUrl = getApiBaseUrl(); const response = await fetch(`${apiBaseUrl}/api/users/${user_id}/bot-name`, { method: 'PUT', headers: getApiHeaders(), body: JSON.stringify({ bot_name }), }); if (!response.ok) { throw await parseApiError(response, 'UPDATE_BOT_NAME_FAILED'); } return response.json(); }; /** * 用户 UI 状态相关接口 */ export interface UserUIState { current_page: string | null; visited_routes: string[]; has_shown_tap_to_chat: boolean; has_shown_cancel_tooltip: boolean; has_shown_send_tooltip: boolean; has_shown_progress_bar_tooltip: boolean; has_shown_mission_stone_modal: boolean; has_shown_conversation_stone_modal: boolean; has_clicked_mission_stone: boolean; has_clicked_conversation_stone: boolean; subtitle_on: boolean; avatar_c_unlocked: boolean; has_shared_avatar: boolean; current_avatar_id?: string | null; } export interface GetUserUIStateResponse { success: boolean; data: { user_id: string; } & UserUIState; } export const getUserUIState = async (user_id: string): Promise => { const apiBaseUrl = getApiBaseUrl(); const response = await fetch(`${apiBaseUrl}/api/users/${user_id}/ui-state`, { method: 'GET', headers: getApiHeaders(), }); if (!response.ok) { throw await parseApiError(response, 'GET_UI_STATE_FAILED'); } return response.json(); }; export interface UpdateUserUIStateResponse { success: boolean; data: { user_id: string; } & UserUIState; } export type UpdateUserUIStatePayload = Partial; export const updateUserUIState = async ( user_id: string, payload: UpdateUserUIStatePayload, ): Promise => { const apiBaseUrl = getApiBaseUrl(); const response = await fetch(`${apiBaseUrl}/api/users/${user_id}/ui-state`, { method: 'PUT', headers: getApiHeaders(), body: JSON.stringify(payload), }); if (!response.ok) { throw await parseApiError(response, 'UPDATE_UI_STATE_FAILED'); } return response.json(); }; /** 亲密度:heart_count 0-3,days_since_last_visit 距上次访问天数 */ export interface GetIntimacyResponse { success: boolean; data: { heart_count: number; days_since_last_visit: number }; } export const getIntimacy = async (user_id: string): Promise => { const apiBaseUrl = getApiBaseUrl(); const response = await fetch(`${apiBaseUrl}/api/users/${user_id}/intimacy`, { method: 'GET', headers: getApiHeaders(), }); if (!response.ok) { throw await parseApiError(response, 'GET_INTIMACY_FAILED'); } return response.json(); }; export interface LightUpIntimacyResponse { success: boolean; data: { heart_count: number }; } export const lightUpIntimacy = async (user_id: string): Promise => { const apiBaseUrl = getApiBaseUrl(); const response = await fetch(`${apiBaseUrl}/api/users/${user_id}/intimacy/light-up`, { method: 'POST', headers: getApiHeaders(), }); if (!response.ok) { throw await parseApiError(response, 'LIGHT_UP_INTIMACY_FAILED'); } return response.json(); }; export interface GetIntimacyStatusResponse { success: boolean; data: { heart_count: number; can_light_up_today: boolean; }; } export const getIntimacyStatus = async (user_id: string): Promise => { const apiBaseUrl = getApiBaseUrl(); const response = await fetch(`${apiBaseUrl}/api/users/${user_id}/intimacy/status`, { method: 'GET', headers: getApiHeaders(), }); if (!response.ok) { throw await parseApiError(response, 'GET_INTIMACY_STATUS_FAILED'); } return response.json(); }; /** * 注册/登录相关接口 */ // 手机号注册 export interface RegisterPhoneResponse { success: boolean; data: { user_id: string; username: string; area_code?: string; phone: string; user_type: string; user_level?: number; token?: string; expires_at?: string; created_at: string; }; } export const registerWithPhone = async ( phone: string, username: string = '', user_id?: string | null, area_code?: string, ): Promise => { const apiBaseUrl = getApiBaseUrl(); const body: { phone: string; username: string; user_id?: string; area_code?: string } = { phone, username }; if (user_id) { body.user_id = user_id; } if (area_code) { body.area_code = area_code; } const response = await fetch(`${apiBaseUrl}/api/auth/register/phone`, { method: 'POST', headers: getApiHeaders(), body: JSON.stringify(body), }); if (!response.ok) { throw await parseApiError(response, 'REGISTER_FAILED'); } return response.json(); }; // 邮箱注册 export interface RegisterEmailResponse { success: boolean; data: { user_id: string; username: string; email: string; user_type: string; user_level?: number; token?: string; expires_at?: string; created_at: string; }; } export const registerWithEmail = async (email: string, username: string = '', user_id?: string | null): Promise => { const apiBaseUrl = getApiBaseUrl(); const body: { email: string; username: string; user_id?: string } = { email, username }; if (user_id) { body.user_id = user_id; } const response = await fetch(`${apiBaseUrl}/api/auth/register/email`, { method: 'POST', headers: getApiHeaders(), body: JSON.stringify(body), }); if (!response.ok) { throw await parseApiError(response, 'REGISTER_FAILED'); } return response.json(); }; // 微信登录 export interface LoginWechatResponse { success: boolean; data: { user_id: string; username: string; wechat_openid?: string; user_type: string; user_level?: number; token?: string; expires_at?: string; created_at: string; }; } export const loginWithWechat = async (openid: string, username: string = ''): Promise => { const apiBaseUrl = getApiBaseUrl(); const response = await fetch(`${apiBaseUrl}/api/auth/login/wechat`, { method: 'POST', headers: getApiHeaders(), body: JSON.stringify({ openid, username }), }); if (!response.ok) { throw await parseApiError(response, 'LOGIN_FAILED'); } return response.json(); }; // QQ登录 export interface LoginQQResponse { success: boolean; data: { user_id: string; username: string; qq_openid?: string; user_type: string; user_level?: number; token?: string; expires_at?: string; created_at: string; }; } export const loginWithQQ = async (openid: string, username: string = ''): Promise => { const apiBaseUrl = getApiBaseUrl(); const response = await fetch(`${apiBaseUrl}/api/auth/login/qq`, { method: 'POST', headers: getApiHeaders(), body: JSON.stringify({ openid, username }), }); if (!response.ok) { throw await parseApiError(response, 'LOGIN_FAILED'); } return response.json(); }; // 手机号登录 export interface LoginPhoneResponse { success: boolean; data: { user_id: string; username: string; area_code?: string; phone: string; user_type: string; user_level?: number; token?: string; expires_at?: string; created_at: string; }; } export const loginWithPhone = async (phone: string, area_code?: string): Promise => { const apiBaseUrl = getApiBaseUrl(); const response = await fetch(`${apiBaseUrl}/api/auth/login/phone`, { method: 'POST', headers: getApiHeaders(), body: JSON.stringify({ phone, area_code }), }); if (!response.ok) { throw await parseApiError(response, 'LOGIN_FAILED'); } return response.json(); }; // 邮箱登录 export interface LoginEmailResponse { success: boolean; data: { user_id: string; username: string; email: string; user_type: string; user_level?: number; token?: string; expires_at?: string; created_at: string; }; } export const loginWithEmail = async (email: string): Promise => { const apiBaseUrl = getApiBaseUrl(); const response = await fetch(`${apiBaseUrl}/api/auth/login/email`, { method: 'POST', headers: getApiHeaders(), body: JSON.stringify({ email }), }); if (!response.ok) { throw await parseApiError(response, 'LOGIN_FAILED'); } return response.json(); }; // 发送验证码 export interface SendCodeRequest { area_code?: string; // 手机区号(如 +86) phone?: string; email?: string; send_type: 'domestic_sms' | 'international_sms' | 'email'; } export interface SendCodeResponse { success: boolean; data: { code_id: string; message: string; }; } export const sendCode = async (request: SendCodeRequest): Promise => { const apiBaseUrl = getApiBaseUrl(); const response = await fetch(`${apiBaseUrl}/api/auth/send-code`, { method: 'POST', headers: getApiHeaders(), body: JSON.stringify(request), }); if (!response.ok) { throw await parseApiError(response, 'SEND_CODE_FAILED'); } return response.json(); }; // 验证验证码 export interface VerifyCodeRequest { phone?: string; email?: string; code: string; send_type: 'domestic_sms' | 'international_sms' | 'email'; } export interface VerifyCodeResponse { success: boolean; data: { valid: boolean; message: string; }; } export const verifyCode = async (request: VerifyCodeRequest): Promise => { const apiBaseUrl = getApiBaseUrl(); const response = await fetch(`${apiBaseUrl}/api/auth/verify-code`, { method: 'POST', headers: getApiHeaders(), body: JSON.stringify(request), }); if (!response.ok) { throw await parseApiError(response, 'VERIFY_CODE_FAILED'); } return response.json(); }; // V2.0:验证码通过后统一登录/注册 export interface VerifyAndAuthRequest { type: 'email' | 'phone'; code: string; email?: string; phone?: string; area_code?: string; preferred_language?: 'zh' | 'en'; } export interface VerifyAndAuthResponse { success: boolean; data: { user_id: string; username?: string; email?: string; phone?: string; user_type: string; user_level?: number; token?: string; expires_at?: string; is_new_user: boolean; [key: string]: unknown; }; } export const verifyAndAuth = async (request: VerifyAndAuthRequest): Promise => { const apiBaseUrl = getApiBaseUrl(); const response = await fetch(`${apiBaseUrl}/api/auth/verify-and-auth`, { method: 'POST', headers: getApiHeaders(), body: JSON.stringify(request), }); if (!response.ok) { throw await parseApiError(response, 'CODE_INVALID'); } return response.json(); }; // V2.0:获取「退出早期共创社区」选项(登录页回显) export const getCommunityChoice = async (userId: string): Promise<{ success: boolean; data: { opted_out?: boolean } }> => { const apiBaseUrl = getApiBaseUrl(); const response = await fetch(`${apiBaseUrl}/api/users/${userId}/community-choice`, { method: 'GET', headers: getApiHeaders(), }); if (!response.ok) throw await parseApiError(response, 'SERVER_ERROR'); return response.json(); }; // V2.0:更新「退出早期共创社区」选项 export const updateCommunityChoice = async (userId: string, optedOut: boolean): Promise<{ success: boolean }> => { const apiBaseUrl = getApiBaseUrl(); const response = await fetch(`${apiBaseUrl}/api/users/${userId}/community-choice`, { method: 'PUT', headers: getApiHeaders(), body: JSON.stringify({ opted_out: optedOut }), }); if (!response.ok) throw await parseApiError(response, 'SERVER_ERROR'); return response.json(); }; // V2.0:获取早期共创状态 export const getEarlyCommunity = async (userId: string): Promise<{ success: boolean; data: { popup_shown?: boolean; email?: string | null } }> => { const apiBaseUrl = getApiBaseUrl(); const response = await fetch(`${apiBaseUrl}/api/users/${userId}/early-community`, { method: 'GET', headers: getApiHeaders(), }); if (!response.ok) throw await parseApiError(response, 'SERVER_ERROR'); return response.json(); }; // V2.0:提交早期共创(标记弹窗已展示,可选留邮箱) export const submitEarlyCommunity = async (userId: string, email?: string | null): Promise<{ success: boolean; data: Record }> => { const apiBaseUrl = getApiBaseUrl(); const response = await fetch(`${apiBaseUrl}/api/users/${userId}/early-community`, { method: 'POST', headers: getApiHeaders(), body: JSON.stringify({ email: email || undefined }), }); if (!response.ok) throw await parseApiError(response, 'SERVER_ERROR'); return response.json(); }; /** * OAuth 授权相关接口 */ // 获取微信授权URL export const getWechatAuthUrl = (): string => { const apiBaseUrl = getApiBaseUrl(); return `${apiBaseUrl}/api/auth/wechat/authorize`; }; // 获取QQ授权URL export const getQQAuthUrl = (from?: string, user_id?: string | null): string => { const apiBaseUrl = getApiBaseUrl(); const url = `${apiBaseUrl}/api/auth/qq/authorize`; const params = new URLSearchParams(); if (from) { params.append('from', from); } if (user_id) { params.append('user_id', user_id); } const queryString = params.toString(); return queryString ? `${url}?${queryString}` : url; }; /** * 头像 GIF 配置 */ export interface AvatarGifSet { default: string; positive: string; negative: string; talking: string; } export interface AvatarGifConfigResponse { success: boolean; data: { avatar_default?: Partial; avatar_registered_cap?: Partial; }; } export const getAvatarGifs = async (): Promise => { const apiBaseUrl = getApiBaseUrl(); const response = await fetch(`${apiBaseUrl}/api/avatar-gifs`, { method: 'GET', headers: getApiHeaders(), }); if (!response.ok) { throw await parseApiError(response, 'GET_AVATARS_FAILED'); } return response.json(); }; /** * 分享图片 */ export interface ShareImageItem { id: string; url: string; title?: string; sort_order?: number; is_active?: boolean; } export interface GetShareImagesResponse { success: boolean; data: ShareImageItem[]; } export const getShareImages = async (): Promise => { const apiBaseUrl = getApiBaseUrl(); const response = await fetch(`${apiBaseUrl}/api/share-images`, { method: 'GET', headers: getApiHeaders(), }); if (!response.ok) { throw await parseApiError(response, 'GET_CONFIG_FAILED'); } return response.json(); }; /** * 领取分享奖励 */ export interface ClaimShareRewardResponse { success: boolean; data?: { user_id: string; background_image_id: string; background_image_metadata?: Record; message: string; }; message?: string; } export const claimShareReward = async (user_id: string): Promise => { const apiBaseUrl = getApiBaseUrl(); const response = await fetch(`${apiBaseUrl}/api/users/${user_id}/claim-share-reward`, { method: 'POST', headers: getApiHeaders(), }); if (!response.ok) { throw await parseApiError(response, 'CLAIM_SHARE_REWARD_FAILED'); } const data = await response.json(); // 如果成功,同步背景图片到 localStorage if (data.success && data.data?.background_image_id) { const { syncBackgroundImageToStorage } = await import('./storage'); syncBackgroundImageToStorage( data.data.background_image_id, data.data.background_image_metadata || { opacity: 0.5 } // 使用后端返回的metadata,或默认透明度 ); } return data; }; /** * 获取所有背景图片配置 */ export interface BackgroundImageConfig { image_id: string; image_name: string; image_url: string; thumbnail_url?: string; unlock_type: 'default' | 'level' | 'purchase' | 'mission'; unlock_level?: number; unlock_mission_type?: string; display_order: number; is_active: boolean; description?: string; metadata?: Record; created_at?: string; updated_at?: string; } export interface GetBackgroundImagesResponse { success: boolean; data: BackgroundImageConfig[]; } export const getBackgroundImages = async (): Promise => { const apiBaseUrl = getApiBaseUrl(); const response = await fetch(`${apiBaseUrl}/api/background-images`, { method: 'GET', headers: getApiHeaders(), }); if (!response.ok) { throw await parseApiError(response, 'GET_BG_CONFIG_FAILED'); } return response.json(); };