Files
memind/miniapp/utils/api.js
john 048f25f580 feat(miniapp): add privacy consent and legal pages
Show privacy popup on launch, gate login on legal consent, and link
user agreement and privacy policy pages in the mini program.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-12 12:18:54 +08:00

291 lines
7.5 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
};