This commit is contained in:
Veljko Tosic
2026-02-16 18:18:21 +01:00
parent c200847c17
commit 5d148db4da
17 changed files with 399 additions and 15 deletions

View File

@@ -1,6 +1,7 @@
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
import type { User, LoginCredentials, SignupCredentials, AuthResponse } from '@/types'
import { useWhiteboardStore } from '@/stores/whiteboards'
import { authService } from '@/services/authService'
const ACCESS_TOKEN = 'auth_token'
@@ -14,7 +15,6 @@ export const useAuthStore = defineStore('auth', () => {
const isAuthenticated = computed(() => !!user.value)
// single-flight promise for refresh to avoid concurrent refresh requests
let refreshPromise: Promise<void> | null = null
function setTokens(access: string | null, refresh: string | null) {
@@ -28,28 +28,23 @@ export const useAuthStore = defineStore('auth', () => {
if (refresh) {
localStorage.setItem(REFRESH_TOKEN, refresh)
} else if (refresh === null) {
// explicit null means remove
localStorage.removeItem(REFRESH_TOKEN)
}
}
async function tryRefresh(): Promise<void> {
// if a refresh is already in progress, return that promise
if (refreshPromise) return refreshPromise
const refreshToken = localStorage.getItem(REFRESH_TOKEN)
if (!refreshToken) {
// nothing to do
throw new Error('No refresh token')
}
refreshPromise = (async () => {
try {
const res = await authService.refreshLogin(refreshToken)
// API expected to return { accessToken, refreshToken }
setTokens(res.accessToken ?? null, res.refreshToken ?? null)
} finally {
// clear so subsequent calls can create a new promise
refreshPromise = null
}
})()
@@ -70,7 +65,6 @@ export const useAuthStore = defineStore('auth', () => {
isLoading.value = false
return
} catch (e) {
// token might be expired; try refresh if we have refresh token
if (savedRefresh) {
try {
await tryRefresh()
@@ -78,14 +72,13 @@ export const useAuthStore = defineStore('auth', () => {
isLoading.value = false
return
} catch (err) {
// refresh failed - fallthrough to clearing auth
console.warn('Token refresh failed during initialize', err)
}
}
}
}
// not authenticated or refresh failed
setTokens(null, null)
user.value = null
isLoading.value = false
@@ -96,14 +89,14 @@ export const useAuthStore = defineStore('auth', () => {
error.value = null
try {
const res: AuthResponse = await authService.login(credentials)
// expect AuthResponse to have accessToken and refreshToken
setTokens(res.accessToken ?? null, res.refreshToken ?? null)
try {
user.value = await authService.getMe()
} catch (e) {
console.error('Logged in but failed to fetch user profile', e)
// keep tokens but clear user
user.value = null
}
} catch (e: any) {
@@ -124,7 +117,7 @@ export const useAuthStore = defineStore('auth', () => {
try {
user.value = await authService.getMe()
} catch (e) {
// keep tokens but no profile
user.value = null
}
} catch (e: any) {
@@ -136,16 +129,17 @@ export const useAuthStore = defineStore('auth', () => {
}
async function logout(allDevices = false) {
const whiteboardStore = useWhiteboardStore()
isLoading.value = true
error.value = null
try {
if (allDevices) await authService.logoutAll()
else await authService.logout(localStorage.getItem(REFRESH_TOKEN)!)
} catch (e) {
// ignore network errors on logout
console.warn('Logout request failed', e)
} finally {
setTokens(null, null)
whiteboardStore.clearWhiteboards()
user.value = null
isLoading.value = false
}