import { logout, refresh, getPendingRefresh } from './auth.js' import { session, signOut, getSessionRevision, decodeAccessToken } from '../stores/session.js' function replacementForCapturedSession(captured, tokens) { if (!tokens?.accessToken || typeof tokens.refreshToken !== 'string' || !tokens.refreshToken.trim()) { throw new Error('退出刷新响应缺少会话凭据') } const previous = decodeAccessToken(captured.accessToken) const replacement = decodeAccessToken(tokens.accessToken) if (!Number.isFinite(Number(replacement?.exp)) || Number(replacement.exp) * 1000 <= Date.now()) { throw new Error('退出刷新凭据无效或已过期') } for (const name of ['user_id', 'membership_id', 'tenant_id', 'app_code', 'overseas_warehouse_id']) { if (previous[name] == null || String(previous[name]) !== String(replacement[name])) { throw new Error('退出刷新返回了不同作业主体,无法确认原会话撤销') } } return { accessToken: tokens.accessToken, refreshToken: tokens.refreshToken } } // Active user logout only. Ordinary authentication failures continue to use local signOut. export function logoutCurrentSession() { const captured = { accessToken: session.token, refreshToken: session.refreshToken, pendingRefresh: getPendingRefresh(session.refreshToken) } signOut() const clearedRevision = getSessionRevision() const revoke = async () => { if (!captured.accessToken || !captured.refreshToken) return { revoked: false, reason: 'NO_CAPTURED_SESSION' } let credentials = captured let refreshed = false if (captured.pendingRefresh) { // Auth rotates access/refresh credentials. Revoke the captured refresh's result, // while the business refresh wrapper's revision guard prevents local restoration. refreshed = true credentials = replacementForCapturedSession(captured, await captured.pendingRefresh) } try { await logout(credentials.accessToken, credentials.refreshToken) } catch (error) { if (error.network || error.statusCode !== 401 || refreshed) throw error // Only an explicit expired-access HTTP 401 permits one refresh and one revoke. credentials = replacementForCapturedSession(captured, await refresh(captured.refreshToken)) await logout(credentials.accessToken, credentials.refreshToken) } return { revoked: true } } return revoke().then(result => ({ ...result, clearedRevision }), error => { error.logoutRevision = clearedRevision throw error }) }