/** 代码作用（白话）：读取同站 Cookie 中的 CSRF 值，供会改变数据的请求放入校验头；关联文件：AuthController.java、SecurityConfig.java。关联逻辑（调用链/数据流）：csrf 接口写 Cookie -> 此方法读取 Cookie -> POST/PUT/DELETE 请求头。 */
function csrfHeader() { const match = document.cookie.match(/(?:^|; )XSRF-TOKEN=([^;]+)/); return match ? { 'X-XSRF-TOKEN': decodeURIComponent(match[1]) } : {}; }
/** Plain purpose: detect multipart form data so the browser can create its required boundary header. Related files: device-api-client.js, DeviceAssetController.java. Flow: device form -> FormData -> shared request -> browser multipart header -> controller binding. */
function isFormDataBody(body) { return typeof FormData !== 'undefined' && body instanceof FormData; }
/** 代码作用（白话）：把请求失败原因和 HTTP 状态一起交给调用方，便于首次会话检查区分未登录和网络异常。关联文件：auth-store.js、router/index.js。关联逻辑（调用链/数据流）：fetch 失败响应 -> createRequestError() -> bootstrapAuth() -> 登录重定向或网络提示。 */
function createRequestError(message, status) { const error = new Error(message); error.status = status; return error; }
/** Plain purpose: notify the application that the server rejected the current session and it must return to login. Related files: auth-store.js, router/index.js. Flow: protected API 401 -> browser event -> cleared auth state -> login route. */
function notifyExpiredSession() { if (typeof window !== 'undefined') window.dispatchEvent(new Event('xyw-auth-expired')); }
/** 代码作用（白话）：首次写操作前请求 CSRF Cookie，登录接口已由后端单独豁免。关联文件：AuthController.java、SecurityConfig.java。关联逻辑（调用链/数据流）：前端写请求 -> /api/auth/csrf -> XSRF-TOKEN Cookie -> 安全层校验。 */
export async function csrfHeadersFor(path, method) { if (method === 'GET' || path === '/api/auth/login') return csrfHeader(); const existing = csrfHeader(); if (Object.keys(existing).length) return existing; const response = await fetch('/api/auth/csrf', { credentials: 'include' }); if (!response.ok) throw new Error('安全校验初始化失败'); const refreshed = csrfHeader(); if (!Object.keys(refreshed).length) throw new Error('安全校验初始化失败'); return refreshed; }
/** 代码作用（白话）：统一发送认证请求并让浏览器自动携带 HttpOnly 会话 Cookie。关联文件：AuthController.java、auth-store.js。关联逻辑（调用链/数据流）：登录页/路由守卫 -> fetch -> AuthController -> Cookie/当前用户。 */
export async function request(path, options = {}) { const { headers: suppliedHeaders = {}, body, ...requestOptions } = options; const method = (requestOptions.method || 'GET').toUpperCase(); const csrf = await csrfHeadersFor(path, method); const headers = { ...csrf, ...suppliedHeaders }; if (!isFormDataBody(body) && !headers['Content-Type']) headers['Content-Type'] = 'application/json'; const response = await fetch(path, { ...requestOptions, body, credentials: 'include', headers }); const payload = await response.json().catch(() => ({})); if (response.status === 401 && path !== '/api/auth/login' && path !== '/api/auth/me') notifyExpiredSession(); if (!response.ok || payload.code !== 200) throw createRequestError(payload.message || '请求失败', response.ok ? payload.code : response.status); return payload.data; }
/** 代码作用（白话）：提交账号密码以建立 HttpOnly 会话。关联文件：LoginView.js、AuthController.java。关联逻辑（调用链/数据流）：登录表单 -> POST login -> Set-Cookie -> 当前用户。 */
export function login(credentials) { return request('/api/auth/login', { method: 'POST', body: JSON.stringify(credentials) }); }
/** 代码作用（白话）：恢复浏览器已有会话并获取菜单权限。关联文件：auth-store.js、AuthController.java。关联逻辑（调用链/数据流）：应用启动 -> GET me -> 路由/菜单状态。 */
export function getCurrentUser() { return request('/api/auth/me'); }
/** 代码作用（白话）：请求后端清除当前浏览器的登录会话。关联文件：App.js、AuthController.java。关联逻辑（调用链/数据流）：登出按钮 -> POST logout -> Cookie 过期 -> 登录页。 */
export function logout() { return request('/api/auth/logout', { method: 'POST' }); }
