Files
gsfdc/portal/app/composables/useMemberAuth.ts
T

77 lines
1.9 KiB
TypeScript

const TOKEN_KEY = "gsfdc_member_token"
export function useMemberAuth() {
const token = useState<string | null>("member-token", () => null)
const email = useState<string | null>("member-email", () => null)
const ready = useState("member-auth-ready", () => false)
const { get } = useSiteApi()
function readStored() {
if (!import.meta.client) return null
return window.localStorage.getItem(TOKEN_KEY)
}
function persist(next: string | null) {
if (!import.meta.client) return
if (next) window.localStorage.setItem(TOKEN_KEY, next)
else window.localStorage.removeItem(TOKEN_KEY)
}
async function refreshProfile() {
if (!token.value) {
email.value = null
return null
}
try {
const me = await get<{ id: number; email: string }>("/member/me", token.value)
email.value = me.email
return me
} catch (exc: any) {
const status = Number(exc?.statusCode || exc?.response?.status || 0)
// 仅令牌失效时清会话;网络抖动不要把本地登录清掉。
if (status === 401 || status === 403) {
token.value = null
email.value = null
persist(null)
}
return null
}
}
async function ensure() {
// localStorage 只在客户端可用;SSR 若把 ready 标真,刷新后会跳过恢复导致“一刷新就掉线”。
if (!import.meta.client) return
if (ready.value) return
const stored = readStored()
token.value = stored
if (stored) await refreshProfile()
ready.value = true
}
async function setSession(accessToken: string) {
token.value = accessToken
persist(accessToken)
ready.value = true
await refreshProfile()
}
function clearSession() {
token.value = null
email.value = null
persist(null)
}
const isAuthed = computed(() => Boolean(token.value))
return {
token,
email,
ready,
isAuthed,
ensure,
setSession,
clearSession,
refreshProfile,
}
}