2026-07-11 08:46:08 +08:00
|
|
|
|
const SESSION_KEY = 'tkmind_session';
|
2026-07-11 13:05:44 +08:00
|
|
|
|
const { createUuid, SESSION_COOKIE_NAME } = require('./config');
|
2026-07-11 08:46:08 +08:00
|
|
|
|
|
|
|
|
|
|
let apiBaseUrl = '';
|
|
|
|
|
|
let cookie = '';
|
|
|
|
|
|
|
2026-07-11 13:05:44 +08:00
|
|
|
|
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();
|
|
|
|
|
|
|
2026-07-11 08:46:08 +08:00
|
|
|
|
function setApiBaseUrl(value) {
|
|
|
|
|
|
apiBaseUrl = String(value || '').replace(/\/$/, '');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-11 09:32:33 +08:00
|
|
|
|
function getMiniProgramAppId() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
return wx.getAccountInfoSync?.()?.miniProgram?.appId || '';
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
return '';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-11 13:01:05 +08:00
|
|
|
|
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'})`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-11 08:46:08 +08:00
|
|
|
|
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 '';
|
2026-07-11 13:05:44 +08:00
|
|
|
|
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;
|
2026-07-11 08:46:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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',
|
2026-07-11 13:01:05 +08:00
|
|
|
|
data: options.body == null ? undefined : options.body,
|
2026-07-11 08:46:08 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2026-07-11 13:01:05 +08:00
|
|
|
|
const message = formatRequestError(res, url);
|
2026-07-11 08:46:08 +08:00
|
|
|
|
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 }
|
|
|
|
|
|
});
|
2026-07-11 13:05:44 +08:00
|
|
|
|
persistLoginResult(result, 'password');
|
2026-07-11 08:46:08 +08:00
|
|
|
|
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) {
|
2026-07-11 13:01:05 +08:00
|
|
|
|
if (isTouristMode()) {
|
|
|
|
|
|
throw new Error(
|
|
|
|
|
|
'当前是游客模式(无 AppID 关联)。请在微信开发者工具用 AppID wx3e79ecd530c88da4 重新导入项目,并确认登录 DevTools 的微信号已是该小程序管理员。'
|
|
|
|
|
|
);
|
2026-07-11 09:32:33 +08:00
|
|
|
|
}
|
2026-07-11 08:46:08 +08:00
|
|
|
|
const code = await wxLogin();
|
|
|
|
|
|
const result = await portalRequest(loginPath, {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
body: { code }
|
|
|
|
|
|
});
|
2026-07-11 13:05:44 +08:00
|
|
|
|
persistLoginResult(result, 'wechat');
|
2026-07-11 08:46:08 +08:00
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function logout() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await portalRequest('/auth/logout', { method: 'POST' });
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
clearSession();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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)}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-11 13:01:05 +08:00
|
|
|
|
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', {
|
2026-07-11 08:46:08 +08:00
|
|
|
|
method: 'POST',
|
2026-07-11 13:01:05 +08:00
|
|
|
|
body,
|
2026-07-11 08:46:08 +08:00
|
|
|
|
timeout: 60000
|
|
|
|
|
|
});
|
2026-07-11 13:01:05 +08:00
|
|
|
|
return result?.run ?? result;
|
2026-07-11 08:46:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function getAgentRun(runId) {
|
2026-07-11 13:01:05 +08:00
|
|
|
|
const result = await apiRequest(`/agent/runs/${encodeURIComponent(runId)}`);
|
|
|
|
|
|
return result?.run ?? result;
|
2026-07-11 08:46:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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,
|
2026-07-11 13:01:05 +08:00
|
|
|
|
getApiBaseUrl,
|
|
|
|
|
|
getMiniProgramAppId,
|
|
|
|
|
|
isTouristMode,
|
2026-07-11 08:46:08 +08:00
|
|
|
|
getStoredSession,
|
|
|
|
|
|
saveSession,
|
|
|
|
|
|
clearSession,
|
|
|
|
|
|
checkAuth,
|
|
|
|
|
|
getMe,
|
|
|
|
|
|
loginWithPassword,
|
|
|
|
|
|
loginWithWechatCode,
|
|
|
|
|
|
logout,
|
|
|
|
|
|
listSessions,
|
|
|
|
|
|
loadSession,
|
|
|
|
|
|
createAgentRun,
|
|
|
|
|
|
getAgentRun,
|
|
|
|
|
|
listMindSpacePages
|
|
|
|
|
|
};
|