const SESSION_KEY = 'tkmind_session'; const { createUuid, SESSION_COOKIE_NAME } = require('./config'); const { clearLegalConsent } = require('./legal-consent'); let apiBaseUrl = ''; let cookie = ''; function buildSessionCookie(token) { const value = String(token ?? '').trim(); if (!value) return ''; return `${SESSION_COOKIE_NAME}=${encodeURIComponent(value)}`; } function applySessionToken(token) { const nextCookie = buildSessionCookie(token); if (!nextCookie) return; cookie = nextCookie; const previous = getStoredSession() || {}; saveSession({ ...previous, cookie: nextCookie }); } function restoreSessionCookie() { try { const session = wx.getStorageSync(SESSION_KEY); if (session?.cookie) { cookie = session.cookie; } } catch { // Ignore storage read errors during cold start. } } restoreSessionCookie(); function setApiBaseUrl(value) { apiBaseUrl = String(value || '').replace(/\/$/, ''); } function getMiniProgramAppId() { try { return wx.getAccountInfoSync?.()?.miniProgram?.appId || ''; } catch { return ''; } } function isTouristMode() { const appId = getMiniProgramAppId(); return !appId || appId === 'touristappid'; } function getApiBaseUrl() { return apiBaseUrl; } function formatRequestError(res, url) { const status = Number(res.statusCode || 0); const code = res.data?.error?.code || ''; const serverMessage = res.data?.message || res.data?.error?.message || ''; if (status === 403 && code === 'csrf_failed') { if (/m\.tkmind\.cn/i.test(apiBaseUrl)) { return '线上 Portal 尚未部署小程序登录接口(403 来源校验失败)。请先用账号密码登录,或改用本地 Portal 联调。'; } return serverMessage || '请求被服务器拒绝(来源校验失败)'; } if (status === 404) { return '登录接口不存在,请重启本地 Portal(pnpm dev)后再试'; } if (serverMessage) return serverMessage; if (status === 503) return '服务暂不可用,请稍后重试或使用账号密码登录'; if (status === 405) { return `接口不支持当前请求方法,请检查 API_BASE_URL 是否指向 Portal:${url}`; } return `请求失败 (${status || 'network'})`; } function getStoredSession() { const session = wx.getStorageSync(SESSION_KEY); if (session && typeof session === 'object') { cookie = session.cookie || cookie; return session; } return null; } function saveSession(session) { cookie = session?.cookie || cookie; wx.setStorageSync(SESSION_KEY, session || {}); } function clearSession() { cookie = ''; wx.removeStorageSync(SESSION_KEY); } function normalizeSetCookie(headers = {}) { const raw = headers['Set-Cookie'] || headers['set-cookie']; if (!raw) return ''; const items = Array.isArray(raw) ? raw : [String(raw)]; let sessionCookie = ''; for (const item of items) { const firstPart = String(item).trim().split(';')[0].trim(); const separator = firstPart.indexOf('='); if (separator <= 0) continue; const name = firstPart.slice(0, separator); const value = firstPart.slice(separator + 1); if (name === SESSION_COOKIE_NAME && value) { sessionCookie = `${name}=${value}`; } } return sessionCookie; } function persistLoginResult(result, loginType) { if (result?.sessionToken) { applySessionToken(result.sessionToken); } saveSession({ cookie, user: result?.user || null, loginType, }); const app = getApp?.(); if (app && result?.user) { app.globalData.user = result.user; } } function request(path, options = {}) { const url = /^https?:\/\//i.test(path) ? path : `${apiBaseUrl}${path}`; const header = { Accept: 'application/json', ...(options.body ? { 'Content-Type': 'application/json' } : {}), ...(cookie ? { Cookie: cookie } : {}), ...(options.header || {}) }; return new Promise((resolve, reject) => { wx.request({ url, method: options.method || 'GET', data: options.body == null ? undefined : options.body, header, timeout: options.timeout || 20000, success(res) { const nextCookie = normalizeSetCookie(res.header); if (nextCookie) { cookie = nextCookie; const previous = getStoredSession() || {}; saveSession({ ...previous, cookie }); } const status = Number(res.statusCode || 0); if (status >= 200 && status < 300) { resolve(res.data); return; } const message = formatRequestError(res, url); const error = new Error(message); error.status = status; error.body = res.data; reject(error); }, fail(err) { reject(new Error(err?.errMsg || '网络请求失败')); } }); }); } function portalRequest(path, options = {}) { return request(path, options); } function apiRequest(path, options = {}) { return request(`/api${path}`, options); } async function checkAuth() { return portalRequest('/auth/status'); } async function getMe() { return portalRequest('/auth/me'); } async function loginWithPassword(username, password) { const result = await portalRequest('/auth/login', { method: 'POST', body: { username, password } }); persistLoginResult(result, 'password'); return result; } function wxLogin() { return new Promise((resolve, reject) => { wx.login({ success(res) { if (res.code) resolve(res.code); else reject(new Error('微信登录未返回 code')); }, fail(err) { reject(new Error(err?.errMsg || '微信登录失败')); } }); }); } async function loginWithWechatCode(loginPath) { if (isTouristMode()) { throw new Error( '当前是游客模式(无 AppID 关联)。请在微信开发者工具用 AppID wx3e79ecd530c88da4 重新导入项目,并确认登录 DevTools 的微信号已是该小程序管理员。' ); } const code = await wxLogin(); const result = await portalRequest(loginPath, { method: 'POST', body: { code } }); persistLoginResult(result, 'wechat'); return result; } async function logout() { try { await portalRequest('/auth/logout', { method: 'POST' }); } finally { clearSession(); clearLegalConsent(); } } async function listSessions(options = {}) { const limit = options.limit || 30; const cursor = options.cursor ? `&cursor=${encodeURIComponent(options.cursor)}` : ''; return apiRequest(`/sessions?limit=${encodeURIComponent(limit)}${cursor}`); } async function loadSession(sessionId) { return apiRequest(`/sessions/${encodeURIComponent(sessionId)}`); } async function createAgentRun(input = {}) { const body = { ...input, request_id: input.request_id || createUuid(), }; if (!body.session_id) { delete body.session_id; } const result = await apiRequest('/agent/runs', { method: 'POST', body, timeout: 60000 }); return result?.run ?? result; } async function getAgentRun(runId) { const result = await apiRequest(`/agent/runs/${encodeURIComponent(runId)}`); return result?.run ?? result; } async function listMindSpacePages(options = {}) { const limit = options.limit || 30; const offset = options.offset || 0; return apiRequest( `/mindspace/v1/pages?limit=${encodeURIComponent(limit)}&offset=${encodeURIComponent(offset)}` ); } module.exports = { setApiBaseUrl, getApiBaseUrl, getMiniProgramAppId, isTouristMode, getStoredSession, saveSession, clearSession, checkAuth, getMe, loginWithPassword, loginWithWechatCode, logout, listSessions, loadSession, createAgentRun, getAgentRun, listMindSpacePages };