import { logout, refresh, getPendingRefresh } from './auth.js' import { session, signOut, getSessionRevision, decodeAccessToken } from '../stores/session.js' function replacement(captured, tokens) { if (!tokens?.accessToken || typeof tokens.refreshToken !== 'string' || !tokens.refreshToken.trim()) throw new Error('退出刷新响应缺少会话凭据') const previous = decodeAccessToken(captured.accessToken), next = decodeAccessToken(tokens.accessToken) if (!Number.isFinite(Number(next.exp)) || Number(next.exp) * 1000 <= Date.now()) throw new Error('退出刷新凭据无效或已过期') for (const field of ['user_id', 'membership_id', 'tenant_id', 'app_code']) { if (previous[field] == null || String(previous[field]) !== String(next[field])) throw new Error('退出刷新返回了不同作业主体,无法确认原会话撤销') } if (String(previous.domestic_warehouse_id ?? '') !== String(next.domestic_warehouse_id ?? '')) throw new Error('退出刷新返回了不同作业仓库,无法确认原会话撤销') return { accessToken: tokens.accessToken, refreshToken: tokens.refreshToken } } export function logoutCurrentSession() { const captured = { accessToken: session.token, refreshToken: session.refreshToken, pendingRefresh: getPendingRefresh(session.refreshToken) } // Clear sensitive data and invalidate business requests while revocation is pending. signOut() const clearedRevision = getSessionRevision() const revoke = async () => { if (!captured.accessToken && !captured.refreshToken) return { revoked: false, reason: 'NO_CAPTURED_SESSION' } if (!captured.accessToken || !captured.refreshToken) throw new Error('原会话凭据不完整,服务端退出未确认') let credentials = captured, refreshed = false if (captured.pendingRefresh) { refreshed = true; credentials = replacement(captured, await captured.pendingRefresh) } try { await logout(credentials.refreshToken, credentials.accessToken) } catch (error) { if (error.network || error.statusCode !== 401 || refreshed) throw error credentials = replacement(captured, await refresh(captured.refreshToken)) await logout(credentials.refreshToken, credentials.accessToken) } return { revoked: true } } return revoke().then(result => ({ ...result, clearedRevision }), error => { error.logoutRevision = clearedRevision; throw error }) }