129 lines
2.5 KiB
TypeScript
129 lines
2.5 KiB
TypeScript
import { defineStore } from 'pinia'
|
|
import { ref } from 'vue'
|
|
import { ApiError, useApi, type MeAuthor } from '@/composables/useApi'
|
|
import type { Access } from '@/lib/permissions'
|
|
|
|
const REFUSAL_REFRESH_GAP_MS = 5000
|
|
const STALE_AFTER_MS = 30000
|
|
|
|
export type SessionStatus = 'idle' | 'checking' | 'ready' | 'error'
|
|
|
|
export interface Session {
|
|
id?: number
|
|
verified: boolean
|
|
username?: string
|
|
email?: string
|
|
displayName: string
|
|
access?: Access
|
|
author?: MeAuthor
|
|
}
|
|
|
|
export const useSessionStore = defineStore('session', () => {
|
|
const status = ref<SessionStatus>('idle')
|
|
const session = ref<Session | null>(null)
|
|
let pending: Promise<void> | null = null
|
|
|
|
let lastAnswerAt: number | null = null
|
|
let lastAttemptAt: number | null = null
|
|
|
|
function check() {
|
|
if (pending) {
|
|
return pending
|
|
}
|
|
|
|
status.value = 'checking'
|
|
const { getMe } = useApi()
|
|
|
|
pending = getMe()
|
|
.then((result) => {
|
|
session.value = result
|
|
status.value = 'ready'
|
|
lastAnswerAt = Date.now()
|
|
})
|
|
.catch((error) => {
|
|
if (error instanceof ApiError && error.status === 401) {
|
|
session.value = null
|
|
status.value = 'ready'
|
|
return
|
|
}
|
|
status.value = 'error'
|
|
})
|
|
.finally(() => {
|
|
pending = null
|
|
})
|
|
|
|
return pending
|
|
}
|
|
|
|
function retry() {
|
|
status.value = 'idle'
|
|
return check()
|
|
}
|
|
|
|
async function refresh() {
|
|
const { getMe } = useApi()
|
|
lastAttemptAt = Date.now()
|
|
try {
|
|
session.value = await getMe()
|
|
lastAnswerAt = Date.now()
|
|
} catch {
|
|
return
|
|
}
|
|
}
|
|
|
|
async function refreshAfterRefusal() {
|
|
if (lastAttemptAt !== null && Date.now() - lastAttemptAt < REFUSAL_REFRESH_GAP_MS) {
|
|
return
|
|
}
|
|
await refresh()
|
|
}
|
|
|
|
async function refreshIfStale() {
|
|
if (!session.value || lastAnswerAt === null) {
|
|
return
|
|
}
|
|
if (Date.now() - lastAnswerAt > STALE_AFTER_MS) {
|
|
await refresh()
|
|
}
|
|
}
|
|
|
|
function setAuthor(author: MeAuthor | undefined) {
|
|
if (!session.value) {
|
|
return
|
|
}
|
|
session.value = { ...session.value, author }
|
|
}
|
|
|
|
function clear() {
|
|
session.value = null
|
|
status.value = 'ready'
|
|
}
|
|
|
|
function setDisplayName(displayName: string | null | undefined) {
|
|
if (session.value) {
|
|
session.value = { ...session.value, displayName: displayName ?? '' }
|
|
}
|
|
}
|
|
|
|
return {
|
|
status,
|
|
session,
|
|
check,
|
|
retry,
|
|
refresh,
|
|
refreshAfterRefusal,
|
|
refreshIfStale,
|
|
clear,
|
|
setAuthor,
|
|
setDisplayName,
|
|
}
|
|
})
|
|
|
|
export function refreshSessionWhenVisible() {
|
|
document.addEventListener('visibilitychange', () => {
|
|
if (document.visibilityState === 'visible') {
|
|
void useSessionStore().refreshIfStale()
|
|
}
|
|
})
|
|
}
|